From f7a5f595abc389f15775ae5b0b6103b054b14cb3 Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 29 May 2025 22:11:16 +0200 Subject: [PATCH] Added Parser with Tests --- Parser/parser.go | 155 ++++++++++++++++++++++++++++++++++++++++++ Parser/parser_test.go | 118 ++++++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+) create mode 100644 Parser/parser.go create mode 100644 Parser/parser_test.go diff --git a/Parser/parser.go b/Parser/parser.go new file mode 100644 index 0000000..e4ebd58 --- /dev/null +++ b/Parser/parser.go @@ -0,0 +1,155 @@ +package parser + +import ( + "fmt" +) + +type ParsedMessage struct { + typeByte byte + payloadBytes []byte +} + +func (pm *ParsedMessage) Type() byte { + return pm.typeByte +} + +func (pm *ParsedMessage) Payload() []byte { + return pm.payloadBytes +} + +type ParserConfig struct { + StartByte byte +} + +func NewParserConfig() *ParserConfig { + return &ParserConfig{ + StartByte: 0xAA, + } +} + +type ParserErrorCodes int +type ParserError struct { + Code ParserErrorCodes + Message string +} + +const ( + ChecksumError ParserErrorCodes = iota +) + +func (e *ParserError) Error() string { + return e.Message +} + +type ParserState int + +const ( + StateWaitForStartByte ParserState = iota + StateReadType + StateReadLength + StateReadPayload + StateCheckChecksum +) + +type Parser struct { + messages []ParsedMessage + currentMessageBuffer []byte + currentMsgType byte + readLength byte + readIndex byte + parserState ParserState + parserConfig ParserConfig + checksum byte +} + +func NewParser(cfg ParserConfig) *Parser { + return &Parser{ + messages: []ParsedMessage{}, + currentMessageBuffer: []byte{}, + currentMsgType: 0, + readLength: 0, + readIndex: 0, + parserState: StateWaitForStartByte, + parserConfig: cfg, + checksum: 0, + } +} + +func (pa *Parser) ParseBytes(p []byte) (n int, err error) { + for i, b := range p { + err := pa.parseByte(b) + if err != nil { + return i, err + } + } + return len(p), nil +} + +func (pa *Parser) parseByte(p byte) error { + switch pa.parserState { + case StateWaitForStartByte: + if p == pa.parserConfig.StartByte { + pa.parserState = StateReadType + pa.readIndex = 0 + pa.checksum = 0 + } + break + case StateReadType: + pa.currentMsgType = p + pa.checksum ^= p + pa.parserState = StateReadLength + case StateReadLength: + pa.readLength = p + pa.checksum ^= p + pa.parserState = StateReadPayload + break + case StateReadPayload: + if pa.readIndex < pa.readLength { + pa.currentMessageBuffer = append(pa.currentMessageBuffer, p) + pa.checksum ^= p + pa.readIndex++ + if pa.readIndex == pa.readLength { + pa.parserState = StateCheckChecksum + } + } + break + case StateCheckChecksum: + if pa.checksum != p { + return &ParserError{ + Code: ChecksumError, + Message: fmt.Sprintf("Checksum does not Match, want %v, got %v", pa.checksum, p), + } + } + pm := &ParsedMessage{ + typeByte: pa.currentMsgType, + payloadBytes: pa.currentMessageBuffer, + } + pa.messages = append(pa.messages, *pm) + pa.reset() + break + } + return nil +} + +func (pa *Parser) reset() { + pa.currentMessageBuffer = nil + pa.currentMsgType = 0 + pa.readIndex = 0 + pa.readLength = 0 + pa.checksum = 0 + pa.parserState = StateWaitForStartByte +} + +func (pa *Parser) IsMessageAvailable() bool { + return len(pa.messages) > 0 +} + +func (pa *Parser) GetNextMessage() (pm ParsedMessage, err error) { + if !pa.IsMessageAvailable() { + return ParsedMessage{}, fmt.Errorf("No message available") + } + lastMessage := pa.messages[0] + pa.messages = pa.messages[1:] + + return lastMessage, nil +} diff --git a/Parser/parser_test.go b/Parser/parser_test.go new file mode 100644 index 0000000..248e2f4 --- /dev/null +++ b/Parser/parser_test.go @@ -0,0 +1,118 @@ +package parser + +import ( + "testing" +) + +// Hilfsfunktion zum Berechnen der Checksumme +func calcChecksum(msgType byte, length byte, payload []byte) byte { + cs := msgType ^ length + for _, b := range payload { + cs ^= b + } + return cs +} + +func TestParser_ParseBytes(t *testing.T) { + tests := []struct { + name string + input []byte + expectErr bool + expectMessages []ParsedMessage + }{ + { + name: "valid single message", + input: func() []byte { + msgType := byte(0x01) + payload := []byte{0x10, 0x20} + length := byte(len(payload)) + cs := calcChecksum(msgType, length, payload) + return []byte{0xAA, msgType, length, payload[0], payload[1], cs} + }(), + expectErr: false, + expectMessages: []ParsedMessage{ + {typeByte: 0x01, payloadBytes: []byte{0x10, 0x20}}, + }, + }, + { + name: "invalid checksum", + input: []byte{ + 0xAA, 0x02, 0x01, 0x33, 0x00, // falsche Checksumme (letztes Byte) + }, + expectErr: true, + expectMessages: nil, + }, + { + name: "no start byte", + input: []byte{ + 0x01, 0x02, 0x03, + }, + expectErr: false, + expectMessages: nil, + }, + { + name: "multiple valid messages", + input: func() []byte { + payload1 := []byte{0x11} + payload2 := []byte{0x22, 0x23} + cs1 := calcChecksum(0x01, 1, payload1) + cs2 := calcChecksum(0x02, 2, payload2) + return append( + []byte{0xAA, 0x01, 0x01, payload1[0], cs1, + 0xAA, 0x02, 0x02, payload2[0], payload2[1], cs2}, + ) + }(), + expectErr: false, + expectMessages: []ParsedMessage{ + {typeByte: 0x01, payloadBytes: []byte{0x11}}, + {typeByte: 0x02, payloadBytes: []byte{0x22, 0x23}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + parser := NewParser(ParserConfig{StartByte: 0xAA}) + n, err := parser.ParseBytes(tt.input) + + if tt.expectErr && err == nil { + t.Errorf("expected error, got none") + } + if !tt.expectErr && err != nil { + t.Errorf("unexpected error: %v", err) + } + if !tt.expectErr && n != len(tt.input) { + t.Errorf("expected to read %d bytes, read %d", len(tt.input), n) + } + + for i, expected := range tt.expectMessages { + if !parser.IsMessageAvailable() { + t.Errorf("expected message %d to be available", i) + break + } + msg, err := parser.GetNextMessage() + if err != nil { + t.Errorf("unexpected error when getting message: %v", err) + } + if msg.Type() != expected.Type() { + t.Errorf("message %d: expected type 0x%02X, got 0x%02X", i, expected.Type(), msg.Type()) + } + if !equalBytes(msg.Payload(), expected.Payload()) { + t.Errorf("message %d: expected payload %v, got %v", i, expected.Payload(), msg.Payload()) + } + } + }) + } +} + +func equalBytes(a, b []byte) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +}