Implement serial link and protocol handling infrastructure
This commit adds basic serial link and protocol support. None of the commands from the docs/design.md document is implemented yet. The following new packages have been added: - seriallink containing the serial link handler including COBS decoding and encoding - protocol containing the protocol handler including msgpack unmarshalling and marshaling - health containing a rudimentary health check implementation - messages containing command and response types and generated msgpack marshaling code A client simulation command has been added in cmd/clientsim. README.md got instructions how to run the client simulator. The docs/config.sample.yaml contains a new section for the serial connection parameters.main
parent
c2b987fd31
commit
3107ad8abb
@ -0,0 +1,144 @@
|
||||
/*
|
||||
Copyright 2022 CAcert Inc.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// client simulator
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/justincpresley/go-cobs"
|
||||
"github.com/shamaton/msgpackgen/msgpack"
|
||||
|
||||
"git.cacert.org/cacert-gosigner/pkg/messages"
|
||||
)
|
||||
|
||||
const cobsDelimiter = 0x00
|
||||
|
||||
var cobsConfig = cobs.Config{SpecialByte: cobsDelimiter, Delimiter: true, EndingSave: true}
|
||||
|
||||
func main() {
|
||||
const (
|
||||
bufferSize = 1024 * 1024
|
||||
readInterval = 50 * time.Millisecond
|
||||
)
|
||||
|
||||
errors := make(chan error)
|
||||
|
||||
errorLog := log.New(os.Stderr, "", log.LstdFlags)
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
done := make(chan struct{})
|
||||
frame := make(chan []byte)
|
||||
|
||||
go func(done chan struct{}) {
|
||||
buf := make([]byte, bufferSize)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
wg.Done()
|
||||
|
||||
return
|
||||
|
||||
default:
|
||||
count, err := os.Stdin.Read(buf)
|
||||
if err != nil {
|
||||
errors <- err
|
||||
|
||||
wg.Done()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
time.Sleep(readInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
data := buf[:count]
|
||||
|
||||
err = cobs.Verify(data, cobsConfig)
|
||||
if err != nil {
|
||||
errors <- err
|
||||
|
||||
wg.Done()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
frame <- cobs.Decode(data, cobsConfig)
|
||||
}
|
||||
}
|
||||
}(done)
|
||||
|
||||
err := writeTestCommands(frame, errorLog)
|
||||
if err != nil {
|
||||
errorLog.Printf("could not write test commands")
|
||||
}
|
||||
|
||||
err = <-errors
|
||||
if err != nil {
|
||||
errorLog.Printf("error: %v", err)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func writeTestCommands(responses chan []byte, errorLog *log.Logger) error {
|
||||
messages.RegisterGeneratedResolver()
|
||||
|
||||
commands := []messages.Command{
|
||||
{
|
||||
Code: messages.CmdHealth,
|
||||
TimeStamp: time.Now().UTC(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, command := range commands {
|
||||
commandBytes, err := msgpack.Marshal(command)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not marshal command bytes: %w", err)
|
||||
}
|
||||
|
||||
_, err = os.Stdout.Write(cobs.Encode(commandBytes, cobsConfig))
|
||||
if err != nil {
|
||||
return fmt.Errorf("write failed: %w", err)
|
||||
}
|
||||
|
||||
responseBytes := <-responses
|
||||
|
||||
var response messages.Response
|
||||
|
||||
err = msgpack.Unmarshal(responseBytes, &response)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not unmarshal msgpack data: %w", err)
|
||||
}
|
||||
|
||||
errorLog.Printf("received response: %+v", response)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
Copyright 2022 CAcert Inc.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package health implements Health checks
|
||||
package health
|
||||
|
||||
type Handler struct {
|
||||
Version string
|
||||
}
|
||||
|
||||
func New(version string) *Handler {
|
||||
return &Handler{Version: version}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2022 CAcert Inc.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
//go:generate go run github.com/shamaton/msgpackgen
|
||||
|
||||
// Package messages contains structure definitions for protocol messages
|
||||
package messages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CommandCode int
|
||||
|
||||
const (
|
||||
CmdHealth CommandCode = iota
|
||||
)
|
||||
|
||||
func (c CommandCode) String() string {
|
||||
switch c {
|
||||
case CmdHealth:
|
||||
return "HEALTH"
|
||||
default:
|
||||
return fmt.Sprintf("unknown (%d)", int(c))
|
||||
}
|
||||
}
|
||||
|
||||
type Command struct {
|
||||
Code CommandCode `msgpack:"code"`
|
||||
TimeStamp time.Time `msgpack:"created"`
|
||||
Payload interface{} `msgpack:"payload"` // optional payload
|
||||
}
|
||||
|
||||
type ResponseCode int
|
||||
|
||||
const (
|
||||
RspError ResponseCode = -1
|
||||
RspHealth ResponseCode = iota
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Code ResponseCode `msgpack:"code"`
|
||||
TimeStamp time.Time `msgpack:"created"`
|
||||
Payload interface{} `msgpack:"payload"`
|
||||
}
|
||||
|
||||
type HealthResponse struct {
|
||||
Version string `msgpack:"version"`
|
||||
}
|
||||
|
||||
type ErrorResponse struct {
|
||||
Message string `msgpack:"message"`
|
||||
}
|
@ -0,0 +1,439 @@
|
||||
// Code generated by msgpackgen. DO NOT EDIT.
|
||||
|
||||
package messages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
msgpack "github.com/shamaton/msgpackgen/msgpack"
|
||||
dec "github.com/shamaton/msgpackgen/msgpack/dec"
|
||||
enc "github.com/shamaton/msgpackgen/msgpack/enc"
|
||||
)
|
||||
|
||||
// RegisterGeneratedResolver registers generated resolver.
|
||||
func RegisterGeneratedResolver() {
|
||||
msgpack.SetResolver(___encodeAsMap, ___encodeAsArray, ___decodeAsMap, ___decodeAsArray)
|
||||
}
|
||||
|
||||
// encode
|
||||
func ___encode(i interface{}) ([]byte, error) {
|
||||
if msgpack.StructAsArray() {
|
||||
return ___encodeAsArray(i)
|
||||
} else {
|
||||
return ___encodeAsMap(i)
|
||||
}
|
||||
}
|
||||
|
||||
// encodeAsArray
|
||||
func ___encodeAsArray(i interface{}) ([]byte, error) {
|
||||
switch v := i.(type) {
|
||||
case HealthResponse:
|
||||
encoder := enc.NewEncoder()
|
||||
size, err := ___calcArraySizeHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encoder.MakeBytes(size)
|
||||
b, offset, err := ___encodeArrayHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if size != offset {
|
||||
return nil, fmt.Errorf("%s size / offset different %d : %d", "HealthResponse", size, offset)
|
||||
}
|
||||
return b, err
|
||||
case *HealthResponse:
|
||||
encoder := enc.NewEncoder()
|
||||
size, err := ___calcArraySizeHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encoder.MakeBytes(size)
|
||||
b, offset, err := ___encodeArrayHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if size != offset {
|
||||
return nil, fmt.Errorf("%s size / offset different %d : %d", "HealthResponse", size, offset)
|
||||
}
|
||||
return b, err
|
||||
case ErrorResponse:
|
||||
encoder := enc.NewEncoder()
|
||||
size, err := ___calcArraySizeErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encoder.MakeBytes(size)
|
||||
b, offset, err := ___encodeArrayErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if size != offset {
|
||||
return nil, fmt.Errorf("%s size / offset different %d : %d", "ErrorResponse", size, offset)
|
||||
}
|
||||
return b, err
|
||||
case *ErrorResponse:
|
||||
encoder := enc.NewEncoder()
|
||||
size, err := ___calcArraySizeErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encoder.MakeBytes(size)
|
||||
b, offset, err := ___encodeArrayErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if size != offset {
|
||||
return nil, fmt.Errorf("%s size / offset different %d : %d", "ErrorResponse", size, offset)
|
||||
}
|
||||
return b, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// encodeAsMap
|
||||
func ___encodeAsMap(i interface{}) ([]byte, error) {
|
||||
switch v := i.(type) {
|
||||
case HealthResponse:
|
||||
encoder := enc.NewEncoder()
|
||||
size, err := ___calcMapSizeHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encoder.MakeBytes(size)
|
||||
b, offset, err := ___encodeMapHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if size != offset {
|
||||
return nil, fmt.Errorf("%s size / offset different %d : %d", "HealthResponse", size, offset)
|
||||
}
|
||||
return b, err
|
||||
case *HealthResponse:
|
||||
encoder := enc.NewEncoder()
|
||||
size, err := ___calcMapSizeHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encoder.MakeBytes(size)
|
||||
b, offset, err := ___encodeMapHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if size != offset {
|
||||
return nil, fmt.Errorf("%s size / offset different %d : %d", "HealthResponse", size, offset)
|
||||
}
|
||||
return b, err
|
||||
case ErrorResponse:
|
||||
encoder := enc.NewEncoder()
|
||||
size, err := ___calcMapSizeErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encoder.MakeBytes(size)
|
||||
b, offset, err := ___encodeMapErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, encoder, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if size != offset {
|
||||
return nil, fmt.Errorf("%s size / offset different %d : %d", "ErrorResponse", size, offset)
|
||||
}
|
||||
return b, err
|
||||
case *ErrorResponse:
|
||||
encoder := enc.NewEncoder()
|
||||
size, err := ___calcMapSizeErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encoder.MakeBytes(size)
|
||||
b, offset, err := ___encodeMapErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, encoder, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if size != offset {
|
||||
return nil, fmt.Errorf("%s size / offset different %d : %d", "ErrorResponse", size, offset)
|
||||
}
|
||||
return b, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// decode
|
||||
func ___decode(data []byte, i interface{}) (bool, error) {
|
||||
if msgpack.StructAsArray() {
|
||||
return ___decodeAsArray(data, i)
|
||||
} else {
|
||||
return ___decodeAsMap(data, i)
|
||||
}
|
||||
}
|
||||
|
||||
// decodeAsArray
|
||||
func ___decodeAsArray(data []byte, i interface{}) (bool, error) {
|
||||
switch v := i.(type) {
|
||||
case *HealthResponse:
|
||||
decoder := dec.NewDecoder(data)
|
||||
offset, err := ___decodeArrayHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, decoder, 0)
|
||||
if err == nil && offset != decoder.Len() {
|
||||
return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len())
|
||||
}
|
||||
return true, err
|
||||
case **HealthResponse:
|
||||
decoder := dec.NewDecoder(data)
|
||||
offset, err := ___decodeArrayHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, decoder, 0)
|
||||
if err == nil && offset != decoder.Len() {
|
||||
return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len())
|
||||
}
|
||||
return true, err
|
||||
case *ErrorResponse:
|
||||
decoder := dec.NewDecoder(data)
|
||||
offset, err := ___decodeArrayErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, decoder, 0)
|
||||
if err == nil && offset != decoder.Len() {
|
||||
return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len())
|
||||
}
|
||||
return true, err
|
||||
case **ErrorResponse:
|
||||
decoder := dec.NewDecoder(data)
|
||||
offset, err := ___decodeArrayErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, decoder, 0)
|
||||
if err == nil && offset != decoder.Len() {
|
||||
return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len())
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// decodeAsMap
|
||||
func ___decodeAsMap(data []byte, i interface{}) (bool, error) {
|
||||
switch v := i.(type) {
|
||||
case *HealthResponse:
|
||||
decoder := dec.NewDecoder(data)
|
||||
offset, err := ___decodeMapHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, decoder, 0)
|
||||
if err == nil && offset != decoder.Len() {
|
||||
return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len())
|
||||
}
|
||||
return true, err
|
||||
case **HealthResponse:
|
||||
decoder := dec.NewDecoder(data)
|
||||
offset, err := ___decodeMapHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, decoder, 0)
|
||||
if err == nil && offset != decoder.Len() {
|
||||
return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len())
|
||||
}
|
||||
return true, err
|
||||
case *ErrorResponse:
|
||||
decoder := dec.NewDecoder(data)
|
||||
offset, err := ___decodeMapErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v, decoder, 0)
|
||||
if err == nil && offset != decoder.Len() {
|
||||
return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len())
|
||||
}
|
||||
return true, err
|
||||
case **ErrorResponse:
|
||||
decoder := dec.NewDecoder(data)
|
||||
offset, err := ___decodeMapErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(*v, decoder, 0)
|
||||
if err == nil && offset != decoder.Len() {
|
||||
return true, fmt.Errorf("read length is different [%d] [%d] ", offset, decoder.Len())
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// calculate size from git.cacert.org/cacert-gosigner/pkg/messages.HealthResponse
|
||||
func ___calcArraySizeHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v HealthResponse, encoder *enc.Encoder) (int, error) {
|
||||
size := 0
|
||||
size += encoder.CalcStructHeaderFix(1)
|
||||
size += encoder.CalcString(v.Version)
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// calculate size from git.cacert.org/cacert-gosigner/pkg/messages.HealthResponse
|
||||
func ___calcMapSizeHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v HealthResponse, encoder *enc.Encoder) (int, error) {
|
||||
size := 0
|
||||
size += encoder.CalcStructHeaderFix(1)
|
||||
size += encoder.CalcStringFix(7)
|
||||
size += encoder.CalcString(v.Version)
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// encode from git.cacert.org/cacert-gosigner/pkg/messages.HealthResponse
|
||||
func ___encodeArrayHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v HealthResponse, encoder *enc.Encoder, offset int) ([]byte, int, error) {
|
||||
var err error
|
||||
offset = encoder.WriteStructHeaderFixAsArray(1, offset)
|
||||
offset = encoder.WriteString(v.Version, offset)
|
||||
return encoder.EncodedBytes(), offset, err
|
||||
}
|
||||
|
||||
// encode from git.cacert.org/cacert-gosigner/pkg/messages.HealthResponse
|
||||
func ___encodeMapHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v HealthResponse, encoder *enc.Encoder, offset int) ([]byte, int, error) {
|
||||
var err error
|
||||
offset = encoder.WriteStructHeaderFixAsMap(1, offset)
|
||||
offset = encoder.WriteStringFix("version", 7, offset)
|
||||
offset = encoder.WriteString(v.Version, offset)
|
||||
return encoder.EncodedBytes(), offset, err
|
||||
}
|
||||
|
||||
// decode to git.cacert.org/cacert-gosigner/pkg/messages.HealthResponse
|
||||
func ___decodeArrayHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v *HealthResponse, decoder *dec.Decoder, offset int) (int, error) {
|
||||
offset, err := decoder.CheckStructHeader(1, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
{
|
||||
var vv string
|
||||
vv, offset, err = decoder.AsString(offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v.Version = vv
|
||||
}
|
||||
return offset, err
|
||||
}
|
||||
|
||||
// decode to git.cacert.org/cacert-gosigner/pkg/messages.HealthResponse
|
||||
func ___decodeMapHealthResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v *HealthResponse, decoder *dec.Decoder, offset int) (int, error) {
|
||||
keys := [][]byte{
|
||||
{uint8(0x76), uint8(0x65), uint8(0x72), uint8(0x73), uint8(0x69), uint8(0x6f), uint8(0x6e)}, // version
|
||||
}
|
||||
offset, err := decoder.CheckStructHeader(1, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
count := 0
|
||||
for count < 1 {
|
||||
var dataKey []byte
|
||||
dataKey, offset, err = decoder.AsStringBytes(offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
fieldIndex := -1
|
||||
for i, key := range keys {
|
||||
if len(dataKey) != len(key) {
|
||||
continue
|
||||
}
|
||||
fieldIndex = i
|
||||
for dataKeyIndex := range dataKey {
|
||||
if dataKey[dataKeyIndex] != key[dataKeyIndex] {
|
||||
fieldIndex = -1
|
||||
break
|
||||
}
|
||||
}
|
||||
if fieldIndex >= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
switch fieldIndex {
|
||||
case 0:
|
||||
{
|
||||
var vv string
|
||||
vv, offset, err = decoder.AsString(offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v.Version = vv
|
||||
}
|
||||
count++
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown key[%s] found", string(dataKey))
|
||||
}
|
||||
}
|
||||
return offset, err
|
||||
}
|
||||
|
||||
// calculate size from git.cacert.org/cacert-gosigner/pkg/messages.ErrorResponse
|
||||
func ___calcArraySizeErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v ErrorResponse, encoder *enc.Encoder) (int, error) {
|
||||
size := 0
|
||||
size += encoder.CalcStructHeaderFix(1)
|
||||
size += encoder.CalcString(v.Message)
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// calculate size from git.cacert.org/cacert-gosigner/pkg/messages.ErrorResponse
|
||||
func ___calcMapSizeErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v ErrorResponse, encoder *enc.Encoder) (int, error) {
|
||||
size := 0
|
||||
size += encoder.CalcStructHeaderFix(1)
|
||||
size += encoder.CalcStringFix(7)
|
||||
size += encoder.CalcString(v.Message)
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// encode from git.cacert.org/cacert-gosigner/pkg/messages.ErrorResponse
|
||||
func ___encodeArrayErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v ErrorResponse, encoder *enc.Encoder, offset int) ([]byte, int, error) {
|
||||
var err error
|
||||
offset = encoder.WriteStructHeaderFixAsArray(1, offset)
|
||||
offset = encoder.WriteString(v.Message, offset)
|
||||
return encoder.EncodedBytes(), offset, err
|
||||
}
|
||||
|
||||
// encode from git.cacert.org/cacert-gosigner/pkg/messages.ErrorResponse
|
||||
func ___encodeMapErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v ErrorResponse, encoder *enc.Encoder, offset int) ([]byte, int, error) {
|
||||
var err error
|
||||
offset = encoder.WriteStructHeaderFixAsMap(1, offset)
|
||||
offset = encoder.WriteStringFix("message", 7, offset)
|
||||
offset = encoder.WriteString(v.Message, offset)
|
||||
return encoder.EncodedBytes(), offset, err
|
||||
}
|
||||
|
||||
// decode to git.cacert.org/cacert-gosigner/pkg/messages.ErrorResponse
|
||||
func ___decodeArrayErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v *ErrorResponse, decoder *dec.Decoder, offset int) (int, error) {
|
||||
offset, err := decoder.CheckStructHeader(1, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
{
|
||||
var vv string
|
||||
vv, offset, err = decoder.AsString(offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v.Message = vv
|
||||
}
|
||||
return offset, err
|
||||
}
|
||||
|
||||
// decode to git.cacert.org/cacert-gosigner/pkg/messages.ErrorResponse
|
||||
func ___decodeMapErrorResponse_e587a81c7cb163b35488bdef0f58c292f99f4bd65a81377f81e5b18c3d86855d(v *ErrorResponse, decoder *dec.Decoder, offset int) (int, error) {
|
||||
keys := [][]byte{
|
||||
{uint8(0x6d), uint8(0x65), uint8(0x73), uint8(0x73), uint8(0x61), uint8(0x67), uint8(0x65)}, // message
|
||||
}
|
||||
offset, err := decoder.CheckStructHeader(1, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
count := 0
|
||||
for count < 1 {
|
||||
var dataKey []byte
|
||||
dataKey, offset, err = decoder.AsStringBytes(offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
fieldIndex := -1
|
||||
for i, key := range keys {
|
||||
if len(dataKey) != len(key) {
|
||||
continue
|
||||
}
|
||||
fieldIndex = i
|
||||
for dataKeyIndex := range dataKey {
|
||||
if dataKey[dataKeyIndex] != key[dataKeyIndex] {
|
||||
fieldIndex = -1
|
||||
break
|
||||
}
|
||||
}
|
||||
if fieldIndex >= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
switch fieldIndex {
|
||||
case 0:
|
||||
{
|
||||
var vv string
|
||||
vv, offset, err = decoder.AsString(offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v.Message = vv
|
||||
}
|
||||
count++
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown key[%s] found", string(dataKey))
|
||||
}
|
||||
}
|
||||
return offset, err
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
/*
|
||||
Copyright 2022 CAcert Inc.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package protocol handles the protocol message marshaling and unmarshalling.
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/shamaton/msgpackgen/msgpack"
|
||||
|
||||
"git.cacert.org/cacert-gosigner/pkg/health"
|
||||
"git.cacert.org/cacert-gosigner/pkg/messages"
|
||||
)
|
||||
|
||||
// Handler is responsible for parsing incoming frames and calling commands
|
||||
type Handler interface {
|
||||
HandleFrame([]byte) ([]byte, error)
|
||||
}
|
||||
|
||||
type MsgPackHandler struct {
|
||||
infoLog, errorLog *log.Logger
|
||||
healthHandler *health.Handler
|
||||
}
|
||||
|
||||
func (m *MsgPackHandler) HandleFrame(frame []byte) ([]byte, error) {
|
||||
var command messages.Command
|
||||
|
||||
err := msgpack.Unmarshal(frame, &command)
|
||||
if err != nil {
|
||||
m.errorLog.Printf("unmarshal failed: %v", err)
|
||||
|
||||
errorResponse, innerErr := buildErrorResponse("do not understand")
|
||||
if innerErr != nil {
|
||||
return nil, innerErr
|
||||
}
|
||||
|
||||
return errorResponse, nil
|
||||
}
|
||||
|
||||
m.infoLog.Printf("Received %s command sent at %s", command.Code, command.TimeStamp)
|
||||
|
||||
response, err := m.handleCommand(&command)
|
||||
if err != nil {
|
||||
m.errorLog.Printf("command failed: %v", err)
|
||||
|
||||
errorResponse, innerErr := buildErrorResponse("command failed")
|
||||
if innerErr != nil {
|
||||
return nil, innerErr
|
||||
}
|
||||
|
||||
return errorResponse, nil
|
||||
}
|
||||
|
||||
responseData, err := msgpack.Marshal(response)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not marshal response: %w", err)
|
||||
}
|
||||
|
||||
return responseData, nil
|
||||
}
|
||||
|
||||
func (m *MsgPackHandler) handleCommand(command *messages.Command) (*messages.Response, error) {
|
||||
var (
|
||||
payload interface{}
|
||||
responseCode messages.ResponseCode
|
||||
)
|
||||
|
||||
switch command.Code {
|
||||
case messages.CmdHealth:
|
||||
responseCode, payload = messages.RspHealth, messages.HealthResponse{Version: m.healthHandler.Version}
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled command %s", command)
|
||||
}
|
||||
|
||||
return &messages.Response{TimeStamp: time.Now().UTC(), Code: responseCode, Payload: payload}, nil
|
||||
}
|
||||
|
||||
func buildErrorResponse(errMsg string) ([]byte, error) {
|
||||
marshal, err := msgpack.Marshal(&messages.Response{
|
||||
Code: messages.RspError,
|
||||
TimeStamp: time.Now().UTC(),
|
||||
Payload: messages.ErrorResponse{Message: errMsg},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not marshal error response: %w", err)
|
||||
}
|
||||
|
||||
return marshal, nil
|
||||
}
|
||||
|
||||
func New(infoLog *log.Logger, errorLog *log.Logger, handlers ...RegisterHandler) (Handler, error) {
|
||||
messages.RegisterGeneratedResolver()
|
||||
|
||||
h := &MsgPackHandler{
|
||||
infoLog: infoLog,
|
||||
errorLog: errorLog,
|
||||
}
|
||||
|
||||
for _, reg := range handlers {
|
||||
reg(h)
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
type RegisterHandler func(handler *MsgPackHandler)
|
||||
|
||||
func RegisterHealthHandler(healthHandler *health.Handler) func(*MsgPackHandler) {
|
||||
return func(h *MsgPackHandler) {
|
||||
h.healthHandler = healthHandler
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
/*
|
||||
Copyright 2022 CAcert Inc.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package seriallink provides a handler for the serial connection of the signer machine.
|
||||
package seriallink
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/justincpresley/go-cobs"
|
||||
"github.com/tarm/serial"
|
||||
|
||||
"git.cacert.org/cacert-gosigner/pkg/config"
|
||||
"git.cacert.org/cacert-gosigner/pkg/protocol"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
protocolHandler protocol.Handler
|
||||
config *serial.Config
|
||||
port *serial.Port
|
||||
}
|
||||
|
||||
func (h *Handler) setupConnection() error {
|
||||
s, err := serial.OpenPort(h.config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open serial port: %w", err)
|
||||
}
|
||||
|
||||
h.port = s
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) Close() error {
|
||||
err := h.port.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not close serial port: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const cobsDelimiter = 0x00
|
||||
|
||||
func (h *Handler) Run() error {
|
||||
const (
|
||||
bufferSize = 1024 * 1024
|
||||
readInterval = 50 * time.Millisecond
|
||||
)
|
||||
|
||||
errors := make(chan error)
|
||||
|
||||
cobsConfig := cobs.Config{SpecialByte: cobsDelimiter, Delimiter: true, EndingSave: true}
|
||||
|
||||
go func() {
|
||||
buf := make([]byte, bufferSize)
|
||||
|
||||
for {
|
||||
count, err := h.port.Read(buf)
|
||||
if err != nil {
|
||||
errors <- err
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
time.Sleep(readInterval)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
err = cobs.Verify(buf[:count], cobsConfig)
|
||||
if err != nil {
|
||||
errors <- err
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// perform COBS decoding
|
||||
decoded := cobs.Decode(buf[:count], cobsConfig)
|
||||
|
||||
msg, err := h.protocolHandler.HandleFrame(decoded)
|
||||
if err != nil {
|
||||
errors <- err
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// perform COBS encoding
|
||||
encoded := cobs.Encode(msg, cobsConfig)
|
||||
|
||||
_, err = h.port.Write(encoded)
|
||||
if err != nil {
|
||||
errors <- err
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err := <-errors
|
||||
if err != nil {
|
||||
return fmt.Errorf("error from handler loop: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func New(cfg *config.Serial, protocolHandler protocol.Handler) (*Handler, error) {
|
||||
h := &Handler{protocolHandler: protocolHandler}
|
||||
h.config = &serial.Config{Name: cfg.Device, Baud: cfg.Baud, ReadTimeout: cfg.Timeout}
|
||||
|
||||
err := h.setupConnection()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
Loading…
Reference in New Issue