Skip to content

Commit

Permalink
feat: add a basic PCIC parser
Browse files Browse the repository at this point in the history
Signed-off-by: Christian Ege <[email protected]>
  • Loading branch information
graugans committed Dec 23, 2023
1 parent c2c37fa commit 4427e13
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 0 deletions.
7 changes: 7 additions & 0 deletions pkg/pcic/frame.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pcic

import "github.com/graugans/go-ovp8xx/pkg/chunk"

type Frame struct {
Chunks []chunk.ChunkData
}
102 changes: 102 additions & 0 deletions pkg/pcic/protocol.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package pcic

import (
"bytes"
"errors"
"fmt"
"io"

"github.com/graugans/go-ovp8xx/pkg/chunk"
)

type PCIC struct {
}

const (
headerSize int = 20
minimumContentLength int = 6
ticketFieldLength int = 4
lengthFieldLength int = 10
delimiterFieldLength int = 2
)

const (
firstTicketOffset int = 0
lengthOffset int = 4
secondTicketOffset int = 16
delimiterOffset int = 14
dataOffset int = 20
)

const (
startMarker string = "star"
endMarker string = "stop"
)

func (p *PCIC) Receive(reader io.Reader) (Frame, error) {
frame := Frame{}
header := make([]byte, headerSize)
n, err := io.ReadFull(reader, header)
if err != nil {
return frame, err
}
if n < headerSize {
return frame, fmt.Errorf("not enough data received: %d", n)
}
firstTicket := header[:ticketFieldLength]
secondTicket := header[secondTicketOffset:dataOffset]
if !bytes.Equal(firstTicket, secondTicket) {
return frame, fmt.Errorf("mismatch in the tickets %s != %s ",
string(firstTicket),
string(secondTicket),
)
}
lengthBuffer := string(header[lengthOffset:secondTicketOffset])
if lengthBuffer[0] != 'L' {
return frame, fmt.Errorf("the length field does not start with 'L': %v", lengthBuffer)
}
length := 0
n, err = fmt.Sscanf(lengthBuffer, "L%09d\r\n", &length)
if err != nil {
return frame, err
}
if n != 1 {
return frame, errors.New("no length in the length field detected")
}
if length < minimumContentLength {
return frame, errors.New("the length information is too short")
}
data := make([]byte, length-ticketFieldLength)
if _, err = io.ReadFull(reader, data); err != nil {
return frame, err
}
trailer := data[len(data)-delimiterFieldLength:]
if !bytes.Equal(trailer, []byte{'\r', '\n'}) {
return frame, errors.New("invalid trailer detected")
}
contentDecorated := data[:len(data)-delimiterFieldLength]
if len(startMarker)+len(endMarker) > len(contentDecorated) {
return frame, fmt.Errorf("missing start (%s) and end markers (%s) buffer length: %d",
startMarker,
endMarker,
len(contentDecorated),
)
}
content := contentDecorated[len(endMarker) : len(contentDecorated)-len(endMarker)]
if len(content) == 0 {
// no content is available
return frame, nil
}
remainingBytes := len(content)
offset := 0
for remainingBytes > 0 {
c := chunk.ChunkData{}
if err := c.Parse(content[offset:]); err != nil {
return frame, err
}
frame.Chunks = append(frame.Chunks, c)
offset += c.Size()
remainingBytes -= c.Size()
}
return frame, err
}
72 changes: 72 additions & 0 deletions pkg/pcic/protocol_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package pcic_test

import (
"fmt"
"strings"
"testing"

"github.com/graugans/go-ovp8xx/pkg/chunk"
"github.com/graugans/go-ovp8xx/pkg/pcic"
"github.com/stretchr/testify/assert"
)

const miniMalContentLength int = 14

func TestMinimalReceive(t *testing.T) {
r := strings.NewReader("Hello, Reader!")
p := pcic.PCIC{}
_, err := p.Receive(r)
assert.Error(t, err, "We expect an error while receiving malformed data")

// Test the minimal possible PCIC message
r = strings.NewReader("0001L000000014\r\n0001starstop\r\n")
_, err = p.Receive(r)
assert.NoError(t, err, "We expect no error while receiving data")
}

func TestReceiveWithChunk(t *testing.T) {
c := chunk.ChunkData{}
chunkData := []byte{
0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */
0x34, 0x00, 0x00, 0x00, /* CHUNK_SIZE */
0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */
0x02, 0x00, 0x00, 0x00, /* HEADER_VERSION */
0x04, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */
0x01, 0x00, 0x00, 0x00, /* IMAGE_HEIGTH */
0x00, 0x00, 0x00, 0x00, /* DATA_FORMAT */
0x00, 0x00, 0x00, 0x00, /* TIME_STAMP */
0x00, 0x00, 0x00, 0x00, /* FRAME_COUNT */
0x00, 0x00, 0x00, 0x00, /* STATUS_CODE */
0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_SEC */
0x01, 0x01, 0x00, 0x00, /* TIME_STAMP_NSEC */
0xFF, 0xFF, 0xFF, 0xBB, /* DATA */
}
assert.NoError(t,
c.Parse(chunkData),
"A successful parse expected",
)
p := pcic.PCIC{}
buffer := fmt.Sprintf(
"0001L%09d\r\n0001star%sstop\r\n",
miniMalContentLength+len(chunkData),
string(chunkData),
)
// Test the PCIC message with single chunk
r := strings.NewReader(buffer)
f, err := p.Receive(r)
assert.NoError(t, err, "We expect no error while receiving data")

assert.Equal(t, chunk.RADIAL_DISTANCE_NOISE, f.Chunks[0].Type())

// test with trailing XX after the chunk
buffer = fmt.Sprintf(
"0001L%09d\r\n0001star%sXXstop\r\n",
miniMalContentLength+len(chunkData)+2,
string(chunkData),
)
// Test the PCIC message with single chunk
r = strings.NewReader(buffer)
_, err = p.Receive(r)
assert.Error(t, err, "We expect an error while receiving malformed data")

}

0 comments on commit 4427e13

Please sign in to comment.