diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..00d37e3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = 120 +tab_width = 4 + +[{*.go,*.go2}] +indent_style = tab diff --git a/client/main.go b/client/main.go index 6f7b4ff..2692696 100644 --- a/client/main.go +++ b/client/main.go @@ -2,7 +2,6 @@ package main import ( "flag" - "io" "os" "os/signal" "sync" @@ -45,13 +44,12 @@ func main() { requestChannel := protocol.NewSignerProtocolRequestChannel() responseChannel := make(chan *datastructures.SignerResponse, 1) - readWriteCloser := (io.ReadWriteCloser)(port) clientProtocolConfig := protocol.NewSignerProtocolConfig() if clientConfig.BufferSize != 0 { clientProtocolConfig.BufferSize = int(clientConfig.BufferSize) } protocolHandler := protocol.NewProtocolHandler( - requestChannel, &responseChannel, &readWriteCloser, clientProtocolConfig, + requestChannel, &responseChannel, port, clientProtocolConfig, ) cancelChannel := make(chan os.Signal, 1) diff --git a/client/processing/process.go b/client/processing/process.go index ed93b3d..4617b6c 100644 --- a/client/processing/process.go +++ b/client/processing/process.go @@ -2,7 +2,10 @@ package processing import ( "fmt" + "git.cacert.org/cacert-gosigner/datastructures" + "git.cacert.org/cacert-gosigner/shared" + "github.com/sirupsen/logrus" ) @@ -11,7 +14,7 @@ func Process(response *datastructures.SignerResponse) (err error) { logrus.Tracef("process response %+v", response) switch response.Action { - case datastructures.ActionNul: + case shared.ActionNul: logrus.Trace("received response for NUL request") return default: diff --git a/client/protocol/protocol.go b/client/protocol/protocol.go index 24290e4..5cf2df2 100644 --- a/client/protocol/protocol.go +++ b/client/protocol/protocol.go @@ -3,12 +3,14 @@ package protocol import ( "bytes" "fmt" - "git.cacert.org/cacert-gosigner/datastructures" - "git.cacert.org/cacert-gosigner/shared" - log "github.com/sirupsen/logrus" "io" "sync" "time" + + log "github.com/sirupsen/logrus" + + "git.cacert.org/cacert-gosigner/datastructures" + "git.cacert.org/cacert-gosigner/shared" ) type SignerProtocolHandler interface { @@ -52,7 +54,7 @@ func (rc *SignerProtocolRequestChannel) IsClosed() bool { type protocolHandler struct { requestChannel *SignerProtocolRequestChannel responseChannel *chan *datastructures.SignerResponse - serialConnection *io.ReadWriteCloser + serialConnection io.ReadWriteCloser config *signerProtocolConfig } @@ -76,7 +78,12 @@ func (ph *protocolHandler) Close() error { return nil } -func NewProtocolHandler(requests *SignerProtocolRequestChannel, response *chan *datastructures.SignerResponse, serialConnection *io.ReadWriteCloser, config *signerProtocolConfig) SignerProtocolHandler { +func NewProtocolHandler( + requests *SignerProtocolRequestChannel, + response *chan *datastructures.SignerResponse, + serialConnection io.ReadWriteCloser, + config *signerProtocolConfig, +) SignerProtocolHandler { return &protocolHandler{ requestChannel: requests, responseChannel: response, @@ -123,13 +130,13 @@ func (ph *protocolHandler) HandleSignerProtocol() error { func (ph *protocolHandler) sendHandshake() (err error) { var bytesWritten, bytesRead int - if bytesWritten, err = (*ph.serialConnection).Write([]byte{shared.HandshakeByte}); err != nil { + if bytesWritten, err = ph.serialConnection.Write([]byte{shared.HandshakeByte}); err != nil { return } else { log.Tracef("wrote %d bytes of handshake info", bytesWritten) } data := make([]byte, 1) - if bytesRead, err = (*ph.serialConnection).Read(data); err != nil { + if bytesRead, err = ph.serialConnection.Read(data); err != nil { return } log.Tracef("%d bytes read", bytesRead) @@ -142,13 +149,13 @@ func (ph *protocolHandler) sendHandshake() (err error) { func (ph *protocolHandler) sendRequest(requestBytes *[]byte) error { for { - if length, err := (*ph.serialConnection).Write(*requestBytes); err != nil { + if length, err := ph.serialConnection.Write(*requestBytes); err != nil { return err } else { log.Tracef("wrote %d request bytes", length) } - if length, err := (*ph.serialConnection).Write([]byte{ + if length, err := ph.serialConnection.Write([]byte{ datastructures.CalculateXorCheckSum([][]byte{*requestBytes}), }); err != nil { return err @@ -156,7 +163,7 @@ func (ph *protocolHandler) sendRequest(requestBytes *[]byte) error { log.Tracef("wrote %d checksum bytes", length) } - if length, err := (*ph.serialConnection).Write([]byte(shared.MagicTrailer)); err != nil { + if length, err := ph.serialConnection.Write([]byte(shared.MagicTrailer)); err != nil { return err } else { log.Tracef("wrote %d trailer bytes", length) @@ -184,7 +191,7 @@ func (ph *protocolHandler) waitForResponseHandshake() (err error) { log.Warnf("received invalid handshake byte 0x%x", data[0]) return UnExpectedHandshakeByte{data[0]} } - if err = shared.SendByte(ph.serialConnection, shared.AckByte); err != nil { + if err = shared.SendBytes(ph.serialConnection, []byte{shared.AckByte}); err != nil { return } @@ -229,7 +236,7 @@ func (ph *protocolHandler) readResponse() (*[]byte, *[]byte, byte, error) { if calculatedChecksum != checkSum { return nil, nil, 0, fmt.Errorf("calculated checksum mismatch 0x%x vs 0x%x", calculatedChecksum, checkSum) } - if err := shared.SendByte(ph.serialConnection, shared.AckByte); err != nil { + if err := shared.SendBytes(ph.serialConnection, []byte{shared.AckByte}); err != nil { return nil, nil, 0, err } diff --git a/cmd/signer/main.go b/cmd/signer/main.go new file mode 100644 index 0000000..dcf94b3 --- /dev/null +++ b/cmd/signer/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "flag" + "os" + "os/signal" + + log "github.com/sirupsen/logrus" + "go.bug.st/serial" + + "git.cacert.org/cacert-gosigner/signer" +) + +func main() { + var ( + address string + baudRate int + ) + + flag.StringVar(&address, "a", "/dev/ttyUSB0", "address") + flag.IntVar(&baudRate, "b", 115200, "baud rate") + flag.Parse() + + log.SetFormatter(&log.TextFormatter{ + DisableColors: true, + FullTimestamp: true, + }) + log.SetLevel(log.TraceLevel) + + serialMode := &serial.Mode{ + BaudRate: baudRate, + DataBits: 8, + StopBits: serial.OneStopBit, + Parity: serial.NoParity, + } + log.Infof("connecting to %s using %+v", address, serialMode) + port, err := serial.Open(address, serialMode) + if err != nil { + log.Fatalf("could not open serial port: %v", err) + } + log.Info("connected") + + defer func() { + err := port.Close() + if err != nil { + log.Fatalf("could not close port: %v", err) + } + log.Info("serial port closed") + }() + + done := make(chan bool) + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + + go func() { + <-quit + log.Info("server is shutting down...") + + close(done) + }() + + go func() { + signerProcess := signer.NewSignerProcess(port) + signerProcess.MainLoop() + + close(quit) + }() + + <-done + log.Infoln("server stopped") +} diff --git a/datastructures/common.go b/datastructures/common.go index f9cbdd7..4ca9031 100644 --- a/datastructures/common.go +++ b/datastructures/common.go @@ -2,27 +2,6 @@ package datastructures import "encoding/binary" -type Action uint8 - -const ( - ActionNul = Action(0) - ActionSign = Action(1) - ActionRevoke = Action(2) -) - -func (a Action) String() string { - switch a { - case ActionNul: - return "NUL" - case ActionSign: - return "SIGN" - case ActionRevoke: - return "REVOKE" - default: - return "unknown" - } -} - func encode24BitLength(data []byte) []byte { lengthBytes := make([]byte, 4) binary.BigEndian.PutUint32(lengthBytes, uint32(len(data))) diff --git a/datastructures/signerrequest.go b/datastructures/signerrequest.go index 90f5cb4..89670fa 100644 --- a/datastructures/signerrequest.go +++ b/datastructures/signerrequest.go @@ -3,14 +3,14 @@ package datastructures import ( "bytes" "encoding/binary" - "errors" - "fmt" "time" + + "git.cacert.org/cacert-gosigner/shared" ) type SignerRequest struct { Version uint8 - Action Action + Action shared.Action System uint8 Root uint8 Configuration uint8 @@ -22,9 +22,7 @@ type SignerRequest struct { Content3 string } -const protocolVersion = 1 - -func SignerRequestFromData(lengthBytes []byte, blockData []byte, checkSum byte) (*SignerRequest, error) { +func SignerRequestFromData(blockData []byte) (*SignerRequest, error) { headerLength := Decode24BitLength(blockData[0:3]) headerBytes := blockData[3 : 3+headerLength] @@ -40,13 +38,9 @@ func SignerRequestFromData(lengthBytes []byte, blockData []byte, checkSum byte) content3Length := Decode24BitLength(contentBytes[content3Offset : content3Offset+3]) content3 := string(contentBytes[3+content3Offset : 3+content3Offset+content3Length]) - calculated := CalculateXorCheckSum([][]byte{lengthBytes, blockData}) - if checkSum != calculated { - return nil, errors.New(fmt.Sprintf("invalid checksum expected 0x%x got 0x%x", calculated, checkSum)) - } return &SignerRequest{ Version: headerBytes[0], - Action: Action(headerBytes[1]), + Action: shared.Action(headerBytes[1]), System: headerBytes[2], Root: headerBytes[3], Configuration: headerBytes[4], @@ -79,8 +73,8 @@ func (r SignerRequest) Serialize() []byte { func NewNulRequest() *SignerRequest { return &SignerRequest{ - Version: protocolVersion, - Action: ActionNul, + Version: shared.ProtocolVersion, + Action: shared.ActionNul, Content1: time.Now().UTC().Format("010203042006.05"), } } diff --git a/datastructures/signerresponse.go b/datastructures/signerresponse.go index 9502e48..7c7685f 100644 --- a/datastructures/signerresponse.go +++ b/datastructures/signerresponse.go @@ -4,11 +4,13 @@ import ( "bytes" "errors" "fmt" + + "git.cacert.org/cacert-gosigner/shared" ) type SignerResponse struct { Version uint8 - Action Action + Action shared.Action Reserved1 uint8 Reserved2 uint8 Content1 string @@ -45,7 +47,7 @@ func SignerResponseFromData(lengthBytes []byte, blockData []byte, checkSum byte) return &SignerResponse{ Version: headerBytes[0], - Action: Action(headerBytes[1]), + Action: shared.Action(headerBytes[1]), Reserved1: headerBytes[2], Reserved2: headerBytes[3], Content1: content[0], diff --git a/shared/io.go b/shared/io.go index 59e6200..5cb06dc 100644 --- a/shared/io.go +++ b/shared/io.go @@ -2,30 +2,37 @@ package shared import ( "fmt" - log "github.com/sirupsen/logrus" "io" "time" + + log "github.com/sirupsen/logrus" ) // receive at maximum the requested number of bytes from serial port and stop after the given timeout -func ReceiveBytes(port *io.ReadWriteCloser, count int, timeout time.Duration) ([]byte, error) { +func ReceiveBytes(port io.Reader, count int, timeout time.Duration) ([]byte, error) { readCh := make(chan []byte, 1) errCh := make(chan error, 1) go func() { - data := make([]byte, count) - if readBytes, err := (*port).Read(data); err != nil { - errCh <- err - } else if readBytes > 0 { - log.Tracef("%d bytes read", readBytes) - readCh <- data[0:readBytes] - } else { - readCh <- make([]byte, 0) + sumRead := 0 + for { + data := make([]byte, count) + if readBytes, err := port.Read(data); err != nil { + errCh <- err + } else if readBytes > 0 { + log.Tracef("%d bytes read", readBytes) + sumRead += readBytes + readCh <- data[0:readBytes] + } else { + readCh <- make([]byte, 0) + } + if sumRead >= count { + break + } } - return }() select { case <-time.After(timeout): - return nil, fmt.Errorf("timeout passed %v: %v", timeout) + return nil, fmt.Errorf("timeout passed %v", timeout) case err := <-errCh: return nil, err case data := <-readCh: @@ -33,11 +40,11 @@ func ReceiveBytes(port *io.ReadWriteCloser, count int, timeout time.Duration) ([ } } -func SendByte(port *io.ReadWriteCloser, data byte) error { - if bytesWritten, err := (*port).Write([]byte{data}); err != nil { +func SendBytes(port io.Writer, data []byte) error { + if bytesWritten, err := port.Write(data); err != nil { return err } else { - log.Tracef("wrote %d bytes of handshake info", bytesWritten) + log.Tracef("wrote %d bytes", bytesWritten) } return nil } diff --git a/shared/shared.go b/shared/shared.go index 93d22b7..1965b8d 100644 --- a/shared/shared.go +++ b/shared/shared.go @@ -1,7 +1,36 @@ package shared -const MagicTrailer = "rie4Ech7" +const ( + ProtocolVersion = 1 -const HandshakeByte = 0x02 -const AckByte = 0x10 -const ResendByte = 0x11 + HandshakeByte = 0x02 + AckByte = 0x10 + ResendByte = 0x11 + + MagicTrailer = "rie4Ech7" + + LengthFieldSize = 3 + CheckSumFieldSize = 1 + TrailerFieldSize = len(MagicTrailer) +) + +type Action uint8 + +const ( + ActionNul = Action(0) + ActionSign = Action(1) + ActionRevoke = Action(2) +) + +func (a Action) String() string { + switch a { + case ActionNul: + return "NUL" + case ActionSign: + return "SIGN" + case ActionRevoke: + return "REVOKE" + default: + return "unknown" + } +} diff --git a/signer/command_processor.go b/signer/command_processor.go new file mode 100644 index 0000000..00bc763 --- /dev/null +++ b/signer/command_processor.go @@ -0,0 +1,90 @@ +package signer + +import ( + "errors" + "fmt" + "unsafe" + + log "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" + + "git.cacert.org/cacert-gosigner/datastructures" + "git.cacert.org/cacert-gosigner/shared" +) + +// The CommandProcessor takes parsed protocol data and executes the actual +// functionality. +type CommandProcessor struct { +} + +// Process the signer request +func (p *CommandProcessor) Process(command datastructures.SignerRequest) ( + response *datastructures.SignerResponse, + err error, +) { + log.Infof("analyze %+v", command) + + switch command.Action { + case shared.ActionNul: + response, err = p.handleNulAction(command) + return + case shared.ActionSign: + response, err = p.handleSignAction(command) + return + case shared.ActionRevoke: + response, err = p.handleRevokeAction(command) + return + default: + return nil, errors.New(fmt.Sprintf( + "unsupported Action 0x%02x %s", + int(command.Action), + command.Action, + )) + } +} + +func (*CommandProcessor) handleNulAction(command datastructures.SignerRequest) ( + *datastructures.SignerResponse, + error, +) { + var timeSpec unix.Timespec + err := unix.ClockGettime(unix.CLOCK_REALTIME, &timeSpec) + if err != nil { + log.Errorf("could not get system time: %v", err) + } + log.Debugf("current system time is %v", timeSpec) + // TODO: calculate the actual system time from the payload + _, _, e1 := unix.Syscall( + unix.SYS_CLOCK_SETTIME, + uintptr(unix.CLOCK_REALTIME), + uintptr(unsafe.Pointer(&timeSpec)), + 0, + ) + if e1 != 0 { + log.Errorf("could not set system time: %v", e1) + } + + return &datastructures.SignerResponse{ + Version: command.Version, Action: command.Action, + Content1: "", Content2: "", Content3: ""}, nil +} + +func (p *CommandProcessor) handleSignAction( + command datastructures.SignerRequest, +) ( + *datastructures.SignerResponse, + error, +) { + log.Debugf("handle sign call: %v", command) + return nil, errors.New("not implemented yet") +} + +func (p *CommandProcessor) handleRevokeAction( + command datastructures.SignerRequest, +) ( + *datastructures.SignerResponse, + error, +) { + log.Debugf("handle revoke call: %v", command) + return nil, errors.New("not implemented yet") +} diff --git a/signer/main.go b/signer/main.go deleted file mode 100644 index 0ee9687..0000000 --- a/signer/main.go +++ /dev/null @@ -1,205 +0,0 @@ -package main - -import ( - "errors" - "flag" - "fmt" - "io" - "time" - - log "github.com/sirupsen/logrus" - "go.bug.st/serial" - - "git.cacert.org/cacert-gosigner/datastructures" - "git.cacert.org/cacert-gosigner/shared" -) - -var ( - address string - baudrate int -) - -func main() { - flag.StringVar(&address, "a", "/dev/ttyUSB0", "address") - flag.IntVar(&baudrate, "b", 115200, "baud rate") - flag.Parse() - - log.SetFormatter(&log.TextFormatter{ - DisableColors: true, - FullTimestamp: true, - }) - - serialMode := &serial.Mode{ - BaudRate: baudrate, - DataBits: 8, - StopBits: serial.OneStopBit, - Parity: serial.NoParity, - } - log.Infof("connecting to %s using %+v", address, serialMode) - port, err := serial.Open(address, serialMode) - if err != nil { - log.Fatalf("could not open serial port: %v", err) - } - log.Info("connected") - - count := 0 - - defer func() { - err := port.Close() - if err != nil { - log.Fatalf("could not close port: %v", err) - } - log.Info("closed") - }() - -readLoop: - for { - // timeout if no command has been received within 15 seconds - timeout := time.After(15 * time.Second) - - commandChan := make(chan datastructures.SignerRequest, 1) - errChan := make(chan error, 1) - readWriteCloser := (io.ReadWriteCloser)(port) - go Receive(&readWriteCloser, &commandChan, &errChan) - - select { - case command := <-commandChan: - response, err := Process(command) - if err != nil { - log.Errorf("error processing command: %v", err) - break readLoop - } - _ = SendResponse(&readWriteCloser, response) - case <-timeout: - log.Error("timeout in main loop") - break readLoop - case err := <-errChan: - log.Errorf("error from io goroutine %v", err) - } - - count++ - log.Infof("%d requests processed. Waiting for next request ...", count) - } -} - -// Send a response to the client -func SendResponse(port *io.ReadWriteCloser, response *datastructures.SignerResponse) error { - if _, err := (*port).Write([]byte{0x02}); err != nil { - return err - } - - if ack, err := shared.ReceiveBytes(port, 1, 5*time.Second); err != nil { - return err - } else if ack[0] != 0x10 { - return errors.New(fmt.Sprintf("invalid ack byte 0x%02x", ack[0])) - } - - tryAgain := true - for tryAgain { - data := response.Serialize() - if _, err := (*port).Write(data); err != nil { - return err - } - - checksum := datastructures.CalculateXorCheckSum([][]byte{data}) - if _, err := (*port).Write([]byte{checksum}); err != nil { - return err - } - - if _, err := (*port).Write([]byte(shared.MagicTrailer)); err != nil { - return err - } - - if ack, err := shared.ReceiveBytes(port, 1, 5*time.Second); err != nil { - return err - } else if ack[0] == 0x10 { - tryAgain = false - } else if ack[0] != 0x11 { - return errors.New(fmt.Sprintf("invalid ack byte 0x%02x", ack[0])) - } - } - - return nil -} - -// Process the signer request -func Process(command datastructures.SignerRequest) (response *datastructures.SignerResponse, err error) { - log.Infof("analyze %+v", command) - - switch command.Action { - case datastructures.ActionNul: - response, err = handleNulAction(command) - return - default: - return nil, errors.New(fmt.Sprintf( - "unsupported Action 0x%02x %s", - int(command.Action), - command.Action, - )) - } -} - -func handleNulAction(command datastructures.SignerRequest) (*datastructures.SignerResponse, error) { - // todo: generate script to set system time from command time data in command.content1 - return &datastructures.SignerResponse{ - Version: command.Version, Action: command.Action, - Content1: "", Content2: "", Content3: ""}, nil -} - -// Receive a request and generate a request data structure -func Receive(port *io.ReadWriteCloser, commandChan *chan datastructures.SignerRequest, errorChan *chan error) { - header, err := shared.ReceiveBytes(port, 1, 20*time.Second) - if err != nil { - *errorChan <- err - return - } - if header[0] != shared.HandshakeByte { - *errorChan <- fmt.Errorf("unexpected byte 0x%x expected 0x%x", header[0], shared.HandshakeByte) - } - - if _, err := (*port).Write([]byte{shared.AckByte}); err != nil { - *errorChan <- errors.New("could not write ACK") - return - } - - lengthBytes, err := shared.ReceiveBytes(port, 3, 2*time.Second) - if err != nil { - *errorChan <- err - return - } - blockLength := datastructures.Decode24BitLength(lengthBytes) - blockData, err := shared.ReceiveBytes(port, blockLength, 5*time.Second) - if err != nil { - *errorChan <- err - return - } - - checkSum, err := shared.ReceiveBytes(port, 1, 2*time.Second) - if err != nil { - *errorChan <- err - return - } - - command, err := datastructures.SignerRequestFromData(lengthBytes, blockData, checkSum[0]) - if err != nil { - *errorChan <- err - return - } - - trailer, err := shared.ReceiveBytes(port, len(shared.MagicTrailer), 2*time.Second) - if err != nil { - *errorChan <- err - return - } - if string(trailer) != shared.MagicTrailer { - *errorChan <- errors.New("expected trailer bytes not found") - return - } - - if _, err := (*port).Write([]byte{0x10}); err != nil { - *errorChan <- err - return - } - - *commandChan <- *command -} diff --git a/signer/port_handler.go b/signer/port_handler.go new file mode 100644 index 0000000..05750e4 --- /dev/null +++ b/signer/port_handler.go @@ -0,0 +1,212 @@ +package signer + +import ( + "errors" + "fmt" + "io" + "time" + + log "github.com/sirupsen/logrus" + + "git.cacert.org/cacert-gosigner/datastructures" + "git.cacert.org/cacert-gosigner/shared" +) + +const ( + waitForHandShake = time.Second * 5 + waitForInitialByte = time.Second * 20 + waitForLengthBytes = time.Second * 2 + waitForChecksumByte = time.Second * 2 + waitForTrailerBytes = time.Second * 2 + waitForBlock = time.Minute * 1 + + maxTriesPerBlock = 10_000 +) + +// The PortHandler takes care of reading and writing of raw bytes from/to a serial port +type PortHandler struct { + port io.ReadWriteCloser + processor *CommandProcessor + errors chan error + commandChan chan *datastructures.SignerRequest +} + +func (p *PortHandler) MainLoop() { + count := 0 + for { + go p.Receive() + + select { + case command := <-p.commandChan: + if command == nil { + log.Infof("command channel has been closed. stopping execution.") + return + } + log.Debugf("received command %v", command) + response, err := p.processor.Process(*command) + if err != nil { + log.Errorf("error processing command: %v", err) + close(p.commandChan) + } else { + _ = p.SendResponse(response) + } + case err := <-p.errors: + log.Errorf("error from io goroutine %v", err) + } + + count++ + log.Infof("%d requests processed. Waiting for next request ...", count) + } +} + +// Receive a request and generate a request data structure +func (p *PortHandler) Receive() { + header, err := shared.ReceiveBytes(p.port, 1, waitForInitialByte) + if err != nil { + p.errors <- err + return + } + if header[0] != shared.HandshakeByte { + p.errors <- fmt.Errorf( + "unexpected byte 0x%x expected 0x%x", + header[0], + shared.HandshakeByte, + ) + return + } + + tries := maxTriesPerBlock + + var command *datastructures.SignerRequest + for { + if tries <= 0 { + p.errors <- errors.New("tried reading block too often") + break + } + tries-- + if err := shared.SendBytes(p.port, []byte{shared.AckByte}); err != nil { + p.errors <- fmt.Errorf("could not write ACK byte: %v", err) + break + } + + lengthBytes, err := shared.ReceiveBytes(p.port, shared.LengthFieldSize, waitForLengthBytes) + if err != nil { + p.errors <- fmt.Errorf("could not read lenght bytes: %v", err) + break + } + blockLength := datastructures.Decode24BitLength(lengthBytes) + blockData, err := shared.ReceiveBytes(p.port, blockLength, waitForBlock) + if err != nil { + p.errors <- fmt.Errorf("could not read data block: %v", err) + if !p.requestResend() { + break + } + continue + } + + checkSum, err := shared.ReceiveBytes(p.port, shared.CheckSumFieldSize, waitForChecksumByte) + if err != nil { + p.errors <- fmt.Errorf("could not read checksum byte: %v", err) + break + } + + calculated := datastructures.CalculateXorCheckSum([][]byte{lengthBytes, blockData}) + if checkSum[0] != calculated { + p.errors <- fmt.Errorf("CRC error. expected 0x%02x, got 0x%02x", calculated, checkSum[0]) + if !p.requestResend() { + break + } + continue + } + + trailer, err := shared.ReceiveBytes(p.port, shared.TrailerFieldSize, waitForTrailerBytes) + if err != nil { + p.errors <- fmt.Errorf("could not read trailer bytes: %v", err) + break + } + + if string(trailer) != shared.MagicTrailer { + p.errors <- fmt.Errorf( + "BROKEN block detected, expected trailer bytes '%s' not found, got '%s' instead", + shared.MagicTrailer, + trailer, + ) + if !p.requestResend() { + break + } + continue + } + + command, err = datastructures.SignerRequestFromData(blockData) + if err != nil { + p.errors <- fmt.Errorf("failed to parse block as signer request: %v", err) + break + } + + if err := shared.SendBytes(p.port, []byte{shared.AckByte}); err != nil { + p.errors <- fmt.Errorf("failed to send ACK byte: %v", err) + break + } + + p.commandChan <- command + break + } +} + +func (p *PortHandler) requestResend() bool { + if err := shared.SendBytes(p.port, []byte{shared.ResendByte}); err != nil { + p.errors <- err + return false + } + return true +} + +// Send a response to the client +func (p *PortHandler) SendResponse(response *datastructures.SignerResponse) error { + if err := shared.SendBytes(p.port, []byte{shared.HandshakeByte}); err != nil { + return err + } + + if ack, err := shared.ReceiveBytes(p.port, 1, waitForHandShake); err != nil { + return err + } else if ack[0] != shared.AckByte { + return errors.New(fmt.Sprintf("invalid ack byte 0x%02x", ack[0])) + } + + tryAgain := true + for tryAgain { + data := response.Serialize() + if err := shared.SendBytes(p.port, data); err != nil { + return err + } + + checksum := datastructures.CalculateXorCheckSum([][]byte{data}) + if err := shared.SendBytes(p.port, []byte{checksum}); err != nil { + return err + } + + if err := shared.SendBytes(p.port, []byte(shared.MagicTrailer)); err != nil { + return err + } + + if ack, err := shared.ReceiveBytes(p.port, 1, waitForHandShake); err != nil { + return err + } else if ack[0] == shared.AckByte { + tryAgain = false + } else if ack[0] != 0x11 { + return errors.New(fmt.Sprintf("invalid ack byte 0x%02x", ack[0])) + } + } + + return nil +} + +func NewSignerProcess(port io.ReadWriteCloser) *PortHandler { + errorChan := make(chan error) + return &PortHandler{ + port: port, + errors: errorChan, + commandChan: make(chan *datastructures.SignerRequest, 1), + processor: &CommandProcessor{}, + } +}