Added Parser with Tests
This commit is contained in:
parent
23e67a801e
commit
f7a5f595ab
155
Parser/parser.go
Normal file
155
Parser/parser.go
Normal 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
118
Parser/parser_test.go
Normal 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
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user