Added Parser with Tests

This commit is contained in:
simon 2025-05-29 22:11:16 +02:00
parent 23e67a801e
commit f7a5f595ab
2 changed files with 273 additions and 0 deletions

155
Parser/parser.go Normal file
View File

@ -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
}

118
Parser/parser_test.go Normal file
View File

@ -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
}