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