From 18839503fc37d4e06cd5c2c127e8e99cbf4ce997 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Fri, 22 Dec 2023 07:50:24 +0100 Subject: [PATCH 01/33] test: add a pcic sample file Signed-off-by: Christian Ege --- .gitattributes | 1 + testdata/pcic-test-data.blob.bz2 | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 .gitattributes create mode 100644 testdata/pcic-test-data.blob.bz2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7e1ef73 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.bz2 filter=lfs diff=lfs merge=lfs -text diff --git a/testdata/pcic-test-data.blob.bz2 b/testdata/pcic-test-data.blob.bz2 new file mode 100644 index 0000000..3a3f638 --- /dev/null +++ b/testdata/pcic-test-data.blob.bz2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1aad543d88d89db7b04e53837e074460692883a9003ce69f9da881919856b074 +size 3315211 From 3fd715423dd12d8be767f979c7a66f25f2944650 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Fri, 22 Dec 2023 10:29:59 +0100 Subject: [PATCH 02/33] feat: an initial chunk implementation Signed-off-by: Christian Ege --- go.mod | 5 + go.sum | 12 +++ pkg/chunk/chunk.go | 210 ++++++++++++++++++++++++++++++++++++++++ pkg/chunk/chunk_test.go | 176 +++++++++++++++++++++++++++++++++ 4 files changed, 403 insertions(+) create mode 100644 pkg/chunk/chunk.go create mode 100644 pkg/chunk/chunk_test.go diff --git a/go.mod b/go.mod index b05a835..2652a5f 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,11 @@ require ( ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 94a14dc..95a778e 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,28 @@ alexejk.io/go-xmlrpc v0.4.0 h1:HvaeCuACuF2NBJruG90AJKc5JHRGj9vKxu2ltJntQR4= alexejk.io/go-xmlrpc v0.4.0/go.mod h1:M7f2OzqvZIWrN1LftR4uwW/bLpxrFoQYjWfm4gQB4JA= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/chunk/chunk.go b/pkg/chunk/chunk.go new file mode 100644 index 0000000..1efc276 --- /dev/null +++ b/pkg/chunk/chunk.go @@ -0,0 +1,210 @@ +package chunk + +import ( + "encoding/binary" + "errors" + "fmt" + "time" +) + +type ChunkType uint32 +type DataFormat uint32 + +// The known Chunk Types +const ( + RADIAL_DISTANCE_NOISE ChunkType = 105 +) + +// The known Data Formats +const ( + FORMAT_8U DataFormat = 0 /* 8bit unsigned integer */ + FORMAT_8S DataFormat = 1 /* 8bit signed integer */ + FORMAT_16U DataFormat = 2 /* 16bit unsigned integer*/ + FORMAT_16S DataFormat = 3 /* 16bit signed integer */ + FORMAT_32U DataFormat = 4 /* 32bit unsigned integer*/ + FORMAT_32S DataFormat = 5 /* 32bit signed integer */ + FORMAT_32F DataFormat = 6 /* 32bit floating point number */ + FORMAT_64U DataFormat = 7 /* 64bit unsigned integer*/ + FORMAT_64F DataFormat = 8 /* 64bit floating point number*/ + FORMAT_MAX DataFormat = 9 /* The maximum known data type*/ +) + +type Chunk interface { + Parse(data []byte) error + Type() ChunkType + Size() uint32 + FrameCount() uint32 + Status() uint32 + TimeStamp() time.Time + Bytes() []byte +} + +type ChunkData struct { + chunkType ChunkType /* The type of the Chunk, each chunk type requires a unique ID*/ + chunkSize uint32 /* The size of the complete chunk, including the header and the data */ + headerSize uint32 /* The Size of the chunk header after this amount of bytes the data section starts */ + headerVersion uint32 /* The version of the header */ + dataWidth uint32 /* The width of the data */ + dataHeight uint32 /* The height of the data, for none image data this is set to 1*/ + dataFormat DataFormat /* The data format*/ + timeStamp uint32 /* The timestamp in micro seconds (deprecated) */ + frameCount uint32 /* A frame count */ + statusCode uint32 /* Conveys the status of the device default: 0 */ + timestampSec uint32 /* The timestamp seconds part */ + timestampNSec uint32 /* The timestamp nano seconds part */ + metadata string /* The JSON meta data is always {} for v2 chunks */ + data []byte /**/ +} + +const ( + offsetOfType = 0x0000 + offsetOfSize = 0x0004 + offsetOfHeaderSize = 0x0008 + offsetOfHeaderVersion = 0x000C + offsetOfWidth = 0x0010 + offsetOfHeight = 0x0014 + offsetOfFormat = 0x0018 + offsetOfTimeStamp = 0x001C + offsetOfFrameCount = 0x0020 + offsetOfStatusCode = 0x0024 + offsetOfTimeStampSec = 0x0028 + offsetOfTimeStampNsec = 0x002C + offsetOfData = 0x0030 +) + +const ( + MaxSupportedChunkHeaderVersion = 3 +) + +func New(cType ChunkType) *ChunkData { + chunk := &ChunkData{ + chunkType: cType, + metadata: "{}", + data: []byte{}, + } + return chunk +} + +func (c *ChunkData) Type() ChunkType { + return c.chunkType +} + +func (c *ChunkData) Size() uint32 { + return c.chunkSize +} + +func (c *ChunkData) FrameCount() uint32 { + return c.frameCount +} + +func (c *ChunkData) Status() uint32 { + return c.statusCode +} + +func (c *ChunkData) TimeStamp() time.Time { + return time.Unix(int64(c.timestampSec), int64(c.timestampNSec)) +} + +func (c *ChunkData) Bytes() []byte { + return c.data +} + +func (c *ChunkData) Parse(data []byte) error { + dataLen := uint32(len(data)) + if dataLen < offsetOfData { + return errors.New("unable to parse an empty input") + } + c.chunkType = ChunkType( + binary.LittleEndian.Uint32(data[offsetOfType : offsetOfType+4]), + ) + c.chunkSize = binary.LittleEndian.Uint32( + data[offsetOfSize : offsetOfSize+4], + ) + if c.chunkSize < offsetOfData { + return fmt.Errorf("the chunk size needs to be at minimum: %d", offsetOfData) + } + if c.chunkSize > dataLen { + return fmt.Errorf( + "the chunk size expected is: %d but the data is only: %d", + c.chunkSize, + dataLen, + ) + } + c.headerSize = binary.LittleEndian.Uint32( + data[offsetOfHeaderSize : offsetOfHeaderSize+4], + ) + if c.headerSize < offsetOfData { + return fmt.Errorf("the chunk header size needs to be at minimum: %d", offsetOfData) + } + c.headerVersion = binary.LittleEndian.Uint32( + data[offsetOfHeaderVersion : offsetOfHeaderVersion+4], + ) + if c.headerVersion == 2 && c.headerSize > offsetOfData { + return fmt.Errorf( + "the chunk header size expected is: %d but the expected maximum is only: %d", + c.headerSize, + offsetOfData, + ) + } + + if c.headerVersion == 0 || c.headerVersion > MaxSupportedChunkHeaderVersion { + return fmt.Errorf("invalid chunk header version given: %d maximum supported version: %d", + c.headerVersion, + MaxSupportedChunkHeaderVersion, + ) + } + c.dataWidth = binary.LittleEndian.Uint32( + data[offsetOfWidth : offsetOfWidth+4], + ) + c.dataHeight = binary.LittleEndian.Uint32( + data[offsetOfHeight : offsetOfHeight+4], + ) + if (c.dataHeight * c.dataWidth) > (dataLen - c.headerSize) { + return fmt.Errorf( + "the length of the given data can not be smaller than the given data width and height multiplied", + ) + } + c.dataFormat = DataFormat(binary.LittleEndian.Uint32( + data[offsetOfFormat : offsetOfFormat+4], + )) + if c.dataFormat > FORMAT_MAX { + return fmt.Errorf( + "the the data format does not match the range of valid data formats [0,%d]", + FORMAT_MAX, + ) + } + + c.timeStamp = binary.LittleEndian.Uint32( + data[offsetOfTimeStamp : offsetOfTimeStamp+4], + ) + + c.frameCount = binary.LittleEndian.Uint32( + data[offsetOfFrameCount : offsetOfFrameCount+4], + ) + + c.statusCode = binary.LittleEndian.Uint32( + data[offsetOfStatusCode : offsetOfStatusCode+4], + ) + + c.timestampSec = binary.LittleEndian.Uint32( + data[offsetOfTimeStampSec : offsetOfTimeStampSec+4], + ) + + c.timestampNSec = binary.LittleEndian.Uint32( + data[offsetOfTimeStampNsec : offsetOfTimeStampNsec+4], + ) + + // Link the data to this chunk + // In Go slices are handled as references. + // So be careful when manipulating data + c.data = data[offsetOfData : offsetOfData+(c.chunkSize-c.headerSize)] + + if (c.dataWidth * c.dataHeight) != uint32(len(c.data)) { + return fmt.Errorf( + "a size missmatch detected, width times height does not equal the data size", + ) + + } + + return nil +} diff --git a/pkg/chunk/chunk_test.go b/pkg/chunk/chunk_test.go new file mode 100644 index 0000000..ad31398 --- /dev/null +++ b/pkg/chunk/chunk_test.go @@ -0,0 +1,176 @@ +package chunk_test + +import ( + "testing" + "time" + + "github.com/graugans/go-ovp8xx/pkg/chunk" + "github.com/stretchr/testify/assert" +) + +func TestChunkType(t *testing.T) { + c := chunk.New(chunk.RADIAL_DISTANCE_NOISE) + assert.Equal(t, + chunk.RADIAL_DISTANCE_NOISE, + c.Type(), + "There is a chunk type missmatch detected", + ) +} + +func TestChunkParse(t *testing.T) { + c := chunk.ChunkData{} + assert.Error(t, + c.Parse([]byte{}), + "An error is expected when sending an empty byte slice", + ) + assert.NoError(t, + c.Parse([]byte{ + 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ + 0x30, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ + 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ + 0x02, 0x00, 0x00, 0x00, /* HEADER_VERSION */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGTH */ + 0x00, 0x00, 0x00, 0x00, /* DATA_FORMAT */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP */ + 0x00, 0x01, 0x00, 0x00, /* FRAME_COUNT */ + 0x00, 0x01, 0x00, 0x00, /* STATUS_CODE */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_SEC */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_NSEC */ + }), + "A successful parse expected", + ) + assert.NoError(t, + c.Parse([]byte{ + 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ + 0x30, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ + 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ + 0x02, 0x00, 0x00, 0x00, /* HEADER_VERSION */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGTH */ + 0x00, 0x00, 0x00, 0x00, /* DATA_FORMAT */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP */ + 0x00, 0x01, 0x00, 0x00, /* FRAME_COUNT */ + 0x00, 0x01, 0x00, 0x00, /* STATUS_CODE */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_SEC */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_NSEC */ + }), + "A successful parse expected", + ) + assert.Equal(t, chunk.RADIAL_DISTANCE_NOISE, c.Type(), "Type missmatch detected") + assert.Equal(t, + uint32(0x30), + c.Size(), + "Size missmatch detected", + ) + assert.Error(t, + c.Parse([]byte{ + 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ + 0x30, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ + 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ + 0x02, 0x00, 0x00, 0x00, /* HEADER_VERSION */ + 0x01, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ + 0x01, 0x00, 0x00, 0x00, /* IMAGE_HEIGTH */ + 0x00, 0x00, 0x00, 0x00, /* DATA_FORMAT */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP */ + 0x00, 0x01, 0x00, 0x00, /* FRAME_COUNT */ + 0x00, 0x01, 0x00, 0x00, /* STATUS_CODE */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_SEC */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_NSEC */ + }), + "A error due to invalid width and height expected", + ) + assert.Error(t, + c.Parse([]byte{ + 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ + 0x30, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ + 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ + 0x02, 0x00, 0x00, 0x00, /* HEADER_VERSION */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGTH */ + 0x00, 0x01, 0x00, 0x00, /* DATA_FORMAT */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP */ + 0x00, 0x01, 0x00, 0x00, /* FRAME_COUNT */ + 0x00, 0x01, 0x00, 0x00, /* STATUS_CODE */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_SEC */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_NSEC */ + }), + "A error due to an invalid pixelformat expected", + ) + + assert.NoError(t, + c.Parse([]byte{ + 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ + 0x30, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ + 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ + 0x02, 0x00, 0x00, 0x00, /* HEADER_VERSION */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGTH */ + 0x00, 0x00, 0x00, 0x00, /* DATA_FORMAT */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP */ + 0x00, 0x01, 0x00, 0x00, /* FRAME_COUNT */ + 0x00, 0x00, 0x00, 0x00, /* STATUS_CODE */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_SEC */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_NSEC */ + }), + "A successful parse expected", + ) + assert.Equal(t, uint32(0x100), c.FrameCount(), "A framecount missmatch occurd") + assert.Equal(t, uint32(0x00), c.Status(), "A status code missmatch occurd") + assert.NoError(t, + c.Parse([]byte{ + 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ + 0x30, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ + 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ + 0x02, 0x00, 0x00, 0x00, /* HEADER_VERSION */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ + 0x00, 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 */ + }), + "A successful parse expected", + ) + assert.NotEqual(t, + time.Unix(0, 0), + c.TimeStamp(), + "A timestamp missmatch expected", + ) + assert.Equal(t, + time.Unix(int64(0x100), int64(0x101)), + c.TimeStamp(), + "A timestamp missmatch occurd", + ) + assert.NoError(t, + c.Parse([]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 */ + 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE of second frame*/ + }), + "A successful parse expected", + ) + assert.Equal(t, + 4, + len(c.Bytes()), + "A data size missmatch occured", + ) + assert.Equal(t, + []byte{0xFF, 0xFF, 0xFF, 0xBB}, + c.Bytes(), + "A data size missmatch occured", + ) +} From 154c5206773045c4ca0ba51ec501c5f7cdf6f144 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Fri, 22 Dec 2023 11:03:38 +0100 Subject: [PATCH 03/33] test: add testdata for the diagnostic pcic port Signed-off-by: Christian Ege --- testdata/pcic-diagnostic.blob.bz2 | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 testdata/pcic-diagnostic.blob.bz2 diff --git a/testdata/pcic-diagnostic.blob.bz2 b/testdata/pcic-diagnostic.blob.bz2 new file mode 100644 index 0000000..1f49e62 --- /dev/null +++ b/testdata/pcic-diagnostic.blob.bz2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46564c241cc98636393cea3df9f56aa6e79126927792fb59b53372d21e948396 +size 615 From bd2c2ae3a73b622b970a36ffd5f088c18cd3ae36 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 23 Dec 2023 09:24:20 +0100 Subject: [PATCH 04/33] refactor: chunk Size() returns int This is more inline with the callers Signed-off-by: Christian Ege --- pkg/chunk/chunk.go | 4 ++-- pkg/chunk/chunk_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/chunk/chunk.go b/pkg/chunk/chunk.go index 1efc276..e8cb697 100644 --- a/pkg/chunk/chunk.go +++ b/pkg/chunk/chunk.go @@ -89,8 +89,8 @@ func (c *ChunkData) Type() ChunkType { return c.chunkType } -func (c *ChunkData) Size() uint32 { - return c.chunkSize +func (c *ChunkData) Size() int { + return int(c.chunkSize) } func (c *ChunkData) FrameCount() uint32 { diff --git a/pkg/chunk/chunk_test.go b/pkg/chunk/chunk_test.go index ad31398..a03a6e4 100644 --- a/pkg/chunk/chunk_test.go +++ b/pkg/chunk/chunk_test.go @@ -59,7 +59,7 @@ func TestChunkParse(t *testing.T) { ) assert.Equal(t, chunk.RADIAL_DISTANCE_NOISE, c.Type(), "Type missmatch detected") assert.Equal(t, - uint32(0x30), + 0x30, c.Size(), "Size missmatch detected", ) From ac32713925e388cfcaa78ad12c3fadac97274c91 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 23 Dec 2023 09:24:55 +0100 Subject: [PATCH 05/33] feat: add a basic PCIC parser Signed-off-by: Christian Ege --- pkg/pcic/frame.go | 7 +++ pkg/pcic/protocol.go | 102 ++++++++++++++++++++++++++++++++++++++ pkg/pcic/protocol_test.go | 72 +++++++++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 pkg/pcic/frame.go create mode 100644 pkg/pcic/protocol.go create mode 100644 pkg/pcic/protocol_test.go diff --git a/pkg/pcic/frame.go b/pkg/pcic/frame.go new file mode 100644 index 0000000..f0f7c5a --- /dev/null +++ b/pkg/pcic/frame.go @@ -0,0 +1,7 @@ +package pcic + +import "github.com/graugans/go-ovp8xx/pkg/chunk" + +type Frame struct { + Chunks []chunk.ChunkData +} diff --git a/pkg/pcic/protocol.go b/pkg/pcic/protocol.go new file mode 100644 index 0000000..f2afde0 --- /dev/null +++ b/pkg/pcic/protocol.go @@ -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 +} diff --git a/pkg/pcic/protocol_test.go b/pkg/pcic/protocol_test.go new file mode 100644 index 0000000..2c261a0 --- /dev/null +++ b/pkg/pcic/protocol_test.go @@ -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") + +} From 3fcf03e8eb304c458e4747d2b97df9e9462b6192 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 23 Dec 2023 09:40:16 +0100 Subject: [PATCH 06/33] test: moved the testdata Signed-off-by: Christian Ege --- {testdata => pkg/pcic/testdata}/pcic-diagnostic.blob.bz2 | 0 {testdata => pkg/pcic/testdata}/pcic-test-data.blob.bz2 | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {testdata => pkg/pcic/testdata}/pcic-diagnostic.blob.bz2 (100%) rename {testdata => pkg/pcic/testdata}/pcic-test-data.blob.bz2 (100%) diff --git a/testdata/pcic-diagnostic.blob.bz2 b/pkg/pcic/testdata/pcic-diagnostic.blob.bz2 similarity index 100% rename from testdata/pcic-diagnostic.blob.bz2 rename to pkg/pcic/testdata/pcic-diagnostic.blob.bz2 diff --git a/testdata/pcic-test-data.blob.bz2 b/pkg/pcic/testdata/pcic-test-data.blob.bz2 similarity index 100% rename from testdata/pcic-test-data.blob.bz2 rename to pkg/pcic/testdata/pcic-test-data.blob.bz2 From 609086db9ffe7604bc5413e3536173d83bd574c9 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 23 Dec 2023 09:45:08 +0100 Subject: [PATCH 07/33] doc: add some hints about git LFS Signed-off-by: Christian Ege --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index a01dda1..ebc46a4 100644 --- a/README.md +++ b/README.md @@ -70,3 +70,12 @@ The result depends on your specific device: } } ``` + +## Testing + +Please ensure you have the Git Large File Storage extension installed. Some of the tests require blobs which are handled as Large File Storage files. In case the files are not populated as expected this may help: + +```sh +git lfs fetch --all +git lfs pull +``` \ No newline at end of file From 1c6f25b91c8d0c4c57a236236ca78d2e7e611d1d Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 23 Dec 2023 09:45:34 +0100 Subject: [PATCH 08/33] test: add embedded testdata Signed-off-by: Christian Ege --- pkg/pcic/protocol_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/pcic/protocol_test.go b/pkg/pcic/protocol_test.go index 2c261a0..86b07df 100644 --- a/pkg/pcic/protocol_test.go +++ b/pkg/pcic/protocol_test.go @@ -1,6 +1,7 @@ package pcic_test import ( + "embed" "fmt" "strings" "testing" @@ -12,6 +13,9 @@ import ( const miniMalContentLength int = 14 +//go:embed testdata/*.bz2 +var tfs embed.FS + func TestMinimalReceive(t *testing.T) { r := strings.NewReader("Hello, Reader!") p := pcic.PCIC{} @@ -70,3 +74,9 @@ func TestReceiveWithChunk(t *testing.T) { assert.Error(t, err, "We expect an error while receiving malformed data") } + +func TestWithRealData(t *testing.T) { + _, err := tfs.ReadFile("testdata/pcic-test-data.blob.bz2") + assert.NoError(t, err, "No error expected while reading the input") + +} From cf803b11c29a5479cc58d25f829446ed11cd5b18 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 23 Dec 2023 11:26:40 +0100 Subject: [PATCH 09/33] build: add the word PCIC to the dictionary Signed-off-by: Christian Ege --- .vscode/settings.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7a53f8d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "PCIC" + ] +} \ No newline at end of file From aa6d54bdd085c770503bfd50d2dd1cdb5c53914a Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 23 Dec 2023 11:27:28 +0100 Subject: [PATCH 10/33] fix: handling of different chunk data types Signed-off-by: Christian Ege --- pkg/chunk/chunk.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/chunk/chunk.go b/pkg/chunk/chunk.go index e8cb697..0e3dff8 100644 --- a/pkg/chunk/chunk.go +++ b/pkg/chunk/chunk.go @@ -29,6 +29,10 @@ const ( FORMAT_MAX DataFormat = 9 /* The maximum known data type*/ ) +var ( + byteSizeLUT [FORMAT_MAX]uint32 = [FORMAT_MAX]uint32{1, 1, 2, 2, 4, 4, 4, 8, 8} +) + type Chunk interface { Parse(data []byte) error Type() ChunkType @@ -199,9 +203,13 @@ func (c *ChunkData) Parse(data []byte) error { // So be careful when manipulating data c.data = data[offsetOfData : offsetOfData+(c.chunkSize-c.headerSize)] - if (c.dataWidth * c.dataHeight) != uint32(len(c.data)) { + if (c.dataWidth * c.dataHeight * byteSizeLUT[c.dataFormat]) != uint32(len(c.data)) { return fmt.Errorf( - "a size missmatch detected, width times height does not equal the data size", + "a size mismatch detected, width (%d) times height (%d) does not equal the data size (%d) format: %d", + c.dataWidth, + c.dataHeight, + len(c.data), + c.dataFormat, ) } From 5a386ae97bccb22569280c3d5ec6d4e3c66ca7e4 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 23 Dec 2023 11:28:06 +0100 Subject: [PATCH 11/33] test: implement handling of the test imput Signed-off-by: Christian Ege --- pkg/pcic/protocol_test.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/pkg/pcic/protocol_test.go b/pkg/pcic/protocol_test.go index 86b07df..32ec0d8 100644 --- a/pkg/pcic/protocol_test.go +++ b/pkg/pcic/protocol_test.go @@ -1,8 +1,12 @@ package pcic_test import ( + "bufio" + "compress/bzip2" "embed" + "errors" "fmt" + "io" "strings" "testing" @@ -76,7 +80,23 @@ func TestReceiveWithChunk(t *testing.T) { } func TestWithRealData(t *testing.T) { - _, err := tfs.ReadFile("testdata/pcic-test-data.blob.bz2") + file, err := tfs.Open("testdata/pcic-test-data.blob.bz2") assert.NoError(t, err, "No error expected while reading the input") + defer file.Close() + buf := bufio.NewReader(file) + cr := bzip2.NewReader(buf) + p := pcic.PCIC{} + for { + f, err := p.Receive(cr) + if errors.Is(err, io.EOF) { + break + } + assert.NoError(t, err, "No error expected while reading the compressed input") + fmt.Print("Chunks: [ ") + for _, c := range f.Chunks { + fmt.Printf("%d, ", c.Type()) + } + fmt.Println("]") + } } From f3d319a6cfcd2f8885fdc1319cdd43b6e4c80f20 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 23 Dec 2023 15:20:22 +0100 Subject: [PATCH 12/33] style: fix typos in chunk test Signed-off-by: Christian Ege --- pkg/chunk/chunk_test.go | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/pkg/chunk/chunk_test.go b/pkg/chunk/chunk_test.go index a03a6e4..dd4db4e 100644 --- a/pkg/chunk/chunk_test.go +++ b/pkg/chunk/chunk_test.go @@ -13,7 +13,7 @@ func TestChunkType(t *testing.T) { assert.Equal(t, chunk.RADIAL_DISTANCE_NOISE, c.Type(), - "There is a chunk type missmatch detected", + "There is a chunk type mismatch detected", ) } @@ -30,7 +30,7 @@ func TestChunkParse(t *testing.T) { 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ 0x02, 0x00, 0x00, 0x00, /* HEADER_VERSION */ 0x00, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ - 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGTH */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGHT */ 0x00, 0x00, 0x00, 0x00, /* DATA_FORMAT */ 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP */ 0x00, 0x01, 0x00, 0x00, /* FRAME_COUNT */ @@ -47,7 +47,7 @@ func TestChunkParse(t *testing.T) { 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ 0x02, 0x00, 0x00, 0x00, /* HEADER_VERSION */ 0x00, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ - 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGTH */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGHT */ 0x00, 0x00, 0x00, 0x00, /* DATA_FORMAT */ 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP */ 0x00, 0x01, 0x00, 0x00, /* FRAME_COUNT */ @@ -57,7 +57,11 @@ func TestChunkParse(t *testing.T) { }), "A successful parse expected", ) - assert.Equal(t, chunk.RADIAL_DISTANCE_NOISE, c.Type(), "Type missmatch detected") + assert.Equal(t, + chunk.RADIAL_DISTANCE_NOISE, + c.Type(), + "Type mismatch detected", + ) assert.Equal(t, 0x30, c.Size(), @@ -70,7 +74,7 @@ func TestChunkParse(t *testing.T) { 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ 0x02, 0x00, 0x00, 0x00, /* HEADER_VERSION */ 0x01, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ - 0x01, 0x00, 0x00, 0x00, /* IMAGE_HEIGTH */ + 0x01, 0x00, 0x00, 0x00, /* IMAGE_HEIGHT */ 0x00, 0x00, 0x00, 0x00, /* DATA_FORMAT */ 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP */ 0x00, 0x01, 0x00, 0x00, /* FRAME_COUNT */ @@ -87,7 +91,7 @@ func TestChunkParse(t *testing.T) { 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ 0x02, 0x00, 0x00, 0x00, /* HEADER_VERSION */ 0x00, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ - 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGTH */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGHT */ 0x00, 0x01, 0x00, 0x00, /* DATA_FORMAT */ 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP */ 0x00, 0x01, 0x00, 0x00, /* FRAME_COUNT */ @@ -95,7 +99,7 @@ func TestChunkParse(t *testing.T) { 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_SEC */ 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_NSEC */ }), - "A error due to an invalid pixelformat expected", + "A error due to an invalid data format expected", ) assert.NoError(t, @@ -105,7 +109,7 @@ func TestChunkParse(t *testing.T) { 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ 0x02, 0x00, 0x00, 0x00, /* HEADER_VERSION */ 0x00, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ - 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGTH */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGHT */ 0x00, 0x00, 0x00, 0x00, /* DATA_FORMAT */ 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP */ 0x00, 0x01, 0x00, 0x00, /* FRAME_COUNT */ @@ -115,8 +119,12 @@ func TestChunkParse(t *testing.T) { }), "A successful parse expected", ) - assert.Equal(t, uint32(0x100), c.FrameCount(), "A framecount missmatch occurd") - assert.Equal(t, uint32(0x00), c.Status(), "A status code missmatch occurd") + assert.Equal(t, + uint32(0x100), + c.FrameCount(), + "A frame count mismatch occurred", + ) + assert.Equal(t, uint32(0x00), c.Status(), "A status code mismatch occurred") assert.NoError(t, c.Parse([]byte{ 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ @@ -124,7 +132,7 @@ func TestChunkParse(t *testing.T) { 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ 0x02, 0x00, 0x00, 0x00, /* HEADER_VERSION */ 0x00, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ - 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGTH */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGHT */ 0x00, 0x00, 0x00, 0x00, /* DATA_FORMAT */ 0x00, 0x00, 0x00, 0x00, /* TIME_STAMP */ 0x00, 0x00, 0x00, 0x00, /* FRAME_COUNT */ @@ -137,12 +145,12 @@ func TestChunkParse(t *testing.T) { assert.NotEqual(t, time.Unix(0, 0), c.TimeStamp(), - "A timestamp missmatch expected", + "A timestamp mismatch expected", ) assert.Equal(t, time.Unix(int64(0x100), int64(0x101)), c.TimeStamp(), - "A timestamp missmatch occurd", + "A timestamp mismatch occur", ) assert.NoError(t, c.Parse([]byte{ @@ -151,7 +159,7 @@ func TestChunkParse(t *testing.T) { 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ 0x02, 0x00, 0x00, 0x00, /* HEADER_VERSION */ 0x04, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ - 0x01, 0x00, 0x00, 0x00, /* IMAGE_HEIGTH */ + 0x01, 0x00, 0x00, 0x00, /* IMAGE_HEIGHT */ 0x00, 0x00, 0x00, 0x00, /* DATA_FORMAT */ 0x00, 0x00, 0x00, 0x00, /* TIME_STAMP */ 0x00, 0x00, 0x00, 0x00, /* FRAME_COUNT */ @@ -166,11 +174,11 @@ func TestChunkParse(t *testing.T) { assert.Equal(t, 4, len(c.Bytes()), - "A data size missmatch occured", + "A data size mismatch occurred", ) assert.Equal(t, []byte{0xFF, 0xFF, 0xFF, 0xBB}, c.Bytes(), - "A data size missmatch occured", + "A data size mismatch occurred", ) } From 882127cdc638fc5f0bb7181ee5edbf5affbc3b5f Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sat, 23 Dec 2023 15:22:05 +0100 Subject: [PATCH 13/33] refactor: use a result handler callback Signed-off-by: Christian Ege --- pkg/pcic/protocol.go | 86 ++++++++++++++++++++++++++++++++++----- pkg/pcic/protocol_test.go | 84 ++++++++++++++++++++++++++++++++------ 2 files changed, 147 insertions(+), 23 deletions(-) diff --git a/pkg/pcic/protocol.go b/pkg/pcic/protocol.go index f2afde0..795fe7c 100644 --- a/pkg/pcic/protocol.go +++ b/pkg/pcic/protocol.go @@ -33,47 +33,109 @@ const ( endMarker string = "stop" ) -func (p *PCIC) Receive(reader io.Reader) (Frame, error) { - frame := Frame{} +var ( + resultTicket []byte = []byte{'0', '0', '0', '0'} + errorTicket []byte = []byte{'0', '0', '0', '1'} + notificationTicket []byte = []byte{'0', '0', '1', '0'} +) + +type Async interface { + Result(Frame) + Error(ErrorMessage) + Notification(NotificationMessage) +} + +type NotificationMessage struct { + ID int + Message string +} + +type ErrorMessage struct { + ID int + Message string +} + +func (p *PCIC) Receive(reader io.Reader, handler Async) error { header := make([]byte, headerSize) n, err := io.ReadFull(reader, header) if err != nil { - return frame, err + return err } if n < headerSize { - return frame, fmt.Errorf("not enough data received: %d", n) + return 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 ", + return 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) + return 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 + return err } if n != 1 { - return frame, errors.New("no length in the length field detected") + return errors.New("no length in the length field detected") } if length < minimumContentLength { - return frame, errors.New("the length information is too short") + return errors.New("the length information is too short") } data := make([]byte, length-ticketFieldLength) if _, err = io.ReadFull(reader, data); err != nil { - return frame, err + return err } trailer := data[len(data)-delimiterFieldLength:] if !bytes.Equal(trailer, []byte{'\r', '\n'}) { - return frame, errors.New("invalid trailer detected") + return errors.New("invalid trailer detected") + } + if bytes.Equal(resultTicket, firstTicket) { + frame, err := asyncResultParser(data) + handler.Result(frame) + return err + } else if bytes.Equal(errorTicket, firstTicket) { + errorStatus, err := errorParser(data) + handler.Error(errorStatus) + return err + } else if bytes.Equal(notificationTicket, firstTicket) { + notification, err := notificationParser(data) + handler.Notification(notification) + return err } + return fmt.Errorf("unknown ticket received: %s", string(firstTicket)) +} + +func errorParser(data []byte) (ErrorMessage, error) { + var err error + errorStatus := ErrorMessage{} + n, err := fmt.Sscanf( + string(data), + "%09d:%s", + &errorStatus.ID, + &errorStatus.Message, + ) + if n != 2 { + return ErrorMessage{}, errors.New("unable to parse the error message") + } + return errorStatus, err +} + +func notificationParser(data []byte) (NotificationMessage, error) { + var err error + notification := NotificationMessage{} + return notification, err +} + +func asyncResultParser(data []byte) (Frame, error) { + fmt.Printf("Async Data received\n") + frame := Frame{} + var err error 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", @@ -97,6 +159,8 @@ func (p *PCIC) Receive(reader io.Reader) (Frame, error) { frame.Chunks = append(frame.Chunks, c) offset += c.Size() remainingBytes -= c.Size() + } return frame, err + } diff --git a/pkg/pcic/protocol_test.go b/pkg/pcic/protocol_test.go index 32ec0d8..3429bec 100644 --- a/pkg/pcic/protocol_test.go +++ b/pkg/pcic/protocol_test.go @@ -20,15 +20,35 @@ const miniMalContentLength int = 14 //go:embed testdata/*.bz2 var tfs embed.FS +type PCICAsyncReceiver struct { + frame pcic.Frame + notificationMsg pcic.NotificationMessage + errorMsg pcic.ErrorMessage +} + +func (r *PCICAsyncReceiver) Result(frame pcic.Frame) { + r.frame = frame +} + +func (r *PCICAsyncReceiver) Error(msg pcic.ErrorMessage) { + r.errorMsg = msg +} + +func (r *PCICAsyncReceiver) Notification(msg pcic.NotificationMessage) { + r.notificationMsg = msg +} + +var testHandler *PCICAsyncReceiver = &PCICAsyncReceiver{} + func TestMinimalReceive(t *testing.T) { r := strings.NewReader("Hello, Reader!") p := pcic.PCIC{} - _, err := p.Receive(r) + err := p.Receive(r, testHandler) 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) + r = strings.NewReader("0000L000000014\r\n0000starstop\r\n") + err = p.Receive(r, testHandler) assert.NoError(t, err, "We expect no error while receiving data") } @@ -40,7 +60,7 @@ func TestReceiveWithChunk(t *testing.T) { 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ 0x02, 0x00, 0x00, 0x00, /* HEADER_VERSION */ 0x04, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ - 0x01, 0x00, 0x00, 0x00, /* IMAGE_HEIGTH */ + 0x01, 0x00, 0x00, 0x00, /* IMAGE_HEIGHT */ 0x00, 0x00, 0x00, 0x00, /* DATA_FORMAT */ 0x00, 0x00, 0x00, 0x00, /* TIME_STAMP */ 0x00, 0x00, 0x00, 0x00, /* FRAME_COUNT */ @@ -55,31 +75,48 @@ func TestReceiveWithChunk(t *testing.T) { ) p := pcic.PCIC{} buffer := fmt.Sprintf( - "0001L%09d\r\n0001star%sstop\r\n", + "0000L%09d\r\n0000star%sstop\r\n", miniMalContentLength+len(chunkData), string(chunkData), ) // Test the PCIC message with single chunk r := strings.NewReader(buffer) - f, err := p.Receive(r) + err := p.Receive(r, testHandler) assert.NoError(t, err, "We expect no error while receiving data") - assert.Equal(t, chunk.RADIAL_DISTANCE_NOISE, f.Chunks[0].Type()) + assert.Equal(t, + chunk.RADIAL_DISTANCE_NOISE, + testHandler.frame.Chunks[0].Type(), + ) // test with trailing XX after the chunk buffer = fmt.Sprintf( - "0001L%09d\r\n0001star%sXXstop\r\n", + "0000L%09d\r\n0000star%sXXstop\r\n", miniMalContentLength+len(chunkData)+2, string(chunkData), ) // Test the PCIC message with single chunk r = strings.NewReader(buffer) - _, err = p.Receive(r) + err = p.Receive(r, testHandler) assert.Error(t, err, "We expect an error while receiving malformed data") + // test with invalid ticket after the chunk + buffer = fmt.Sprintf( + "0002L%09d\r\n0002star%sstop\r\n", + miniMalContentLength+len(chunkData), + string(chunkData), + ) + r = strings.NewReader(buffer) + err = p.Receive(r, testHandler) + assert.Error( + t, + err, + "We expect an error while receiving data with an invalid ticket", + ) + } -func TestWithRealData(t *testing.T) { +func TestWithRealChunkData(t *testing.T) { file, err := tfs.Open("testdata/pcic-test-data.blob.bz2") assert.NoError(t, err, "No error expected while reading the input") defer file.Close() @@ -87,16 +124,39 @@ func TestWithRealData(t *testing.T) { cr := bzip2.NewReader(buf) p := pcic.PCIC{} for { - f, err := p.Receive(cr) + err := p.Receive(cr, testHandler) if errors.Is(err, io.EOF) { break } assert.NoError(t, err, "No error expected while reading the compressed input") fmt.Print("Chunks: [ ") - for _, c := range f.Chunks { + for _, c := range testHandler.frame.Chunks { fmt.Printf("%d, ", c.Type()) } fmt.Println("]") } } + +func TestWithRealErrorData(t *testing.T) { + file, err := tfs.Open("testdata/pcic-diagnostic.blob.bz2") + assert.NoError(t, err, "No error expected while reading the input") + defer file.Close() + buf := bufio.NewReader(file) + cr := bzip2.NewReader(buf) + p := pcic.PCIC{} + for { + err := p.Receive(cr, testHandler) + if errors.Is(err, io.EOF) { + break + } + assert.NoError(t, err, "No error expected while reading the compressed input") + assert.NotEqual( + t, + 0, /* 0 means no Error, what does not make sense */ + testHandler.errorMsg.ID, + "An invalid error ID received", + ) + } + +} From 3ac8f01b0e44867bcf875be953f3a94303750f5a Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sun, 24 Dec 2023 12:12:20 +0100 Subject: [PATCH 14/33] refactor: implement the BinaryUnmarshaler interface Signed-off-by: Christian Ege --- pkg/chunk/chunk.go | 27 +++++++++++++++++++++------ pkg/chunk/chunk_test.go | 18 +++++++++--------- pkg/pcic/protocol.go | 2 +- pkg/pcic/protocol_test.go | 2 +- 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/pkg/chunk/chunk.go b/pkg/chunk/chunk.go index 0e3dff8..39a56a2 100644 --- a/pkg/chunk/chunk.go +++ b/pkg/chunk/chunk.go @@ -34,7 +34,8 @@ var ( ) type Chunk interface { - Parse(data []byte) error + UnmarshalBinary(data []byte) error + MarshalBinary() (data []byte, err error) Type() ChunkType Size() uint32 FrameCount() uint32 @@ -113,7 +114,18 @@ func (c *ChunkData) Bytes() []byte { return c.data } -func (c *ChunkData) Parse(data []byte) error { +// MarshalBinary creates a binary representation of the Chunk +// +// The binary representation is encoded in the byte slice +func (c *ChunkData) MarshalBinary() (data []byte, err error) { + return []byte{}, nil +} + +// UnmarshalBinary creates a ChunkData from a byte slice +// +// It copies the data from the input slice to comply with the BinaryUnmarshaler +// interface. +func (c *ChunkData) UnmarshalBinary(data []byte) error { dataLen := uint32(len(data)) if dataLen < offsetOfData { return errors.New("unable to parse an empty input") @@ -198,10 +210,13 @@ func (c *ChunkData) Parse(data []byte) error { data[offsetOfTimeStampNsec : offsetOfTimeStampNsec+4], ) - // Link the data to this chunk - // In Go slices are handled as references. - // So be careful when manipulating data - c.data = data[offsetOfData : offsetOfData+(c.chunkSize-c.headerSize)] + // Copy the data to this chunk + src := data[offsetOfData : offsetOfData+(c.chunkSize-c.headerSize)] + c.data = make([]byte, len(src)) + copy(c.data, src) + if src == nil { + c.data = nil + } if (c.dataWidth * c.dataHeight * byteSizeLUT[c.dataFormat]) != uint32(len(c.data)) { return fmt.Errorf( diff --git a/pkg/chunk/chunk_test.go b/pkg/chunk/chunk_test.go index dd4db4e..fab0adf 100644 --- a/pkg/chunk/chunk_test.go +++ b/pkg/chunk/chunk_test.go @@ -20,11 +20,11 @@ func TestChunkType(t *testing.T) { func TestChunkParse(t *testing.T) { c := chunk.ChunkData{} assert.Error(t, - c.Parse([]byte{}), + c.UnmarshalBinary([]byte{}), "An error is expected when sending an empty byte slice", ) assert.NoError(t, - c.Parse([]byte{ + c.UnmarshalBinary([]byte{ 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ 0x30, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ @@ -41,7 +41,7 @@ func TestChunkParse(t *testing.T) { "A successful parse expected", ) assert.NoError(t, - c.Parse([]byte{ + c.UnmarshalBinary([]byte{ 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ 0x30, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ @@ -65,10 +65,10 @@ func TestChunkParse(t *testing.T) { assert.Equal(t, 0x30, c.Size(), - "Size missmatch detected", + "Size mismatch detected", ) assert.Error(t, - c.Parse([]byte{ + c.UnmarshalBinary([]byte{ 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ 0x30, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ @@ -85,7 +85,7 @@ func TestChunkParse(t *testing.T) { "A error due to invalid width and height expected", ) assert.Error(t, - c.Parse([]byte{ + c.UnmarshalBinary([]byte{ 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ 0x30, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ @@ -103,7 +103,7 @@ func TestChunkParse(t *testing.T) { ) assert.NoError(t, - c.Parse([]byte{ + c.UnmarshalBinary([]byte{ 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ 0x30, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ @@ -126,7 +126,7 @@ func TestChunkParse(t *testing.T) { ) assert.Equal(t, uint32(0x00), c.Status(), "A status code mismatch occurred") assert.NoError(t, - c.Parse([]byte{ + c.UnmarshalBinary([]byte{ 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ 0x30, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ @@ -153,7 +153,7 @@ func TestChunkParse(t *testing.T) { "A timestamp mismatch occur", ) assert.NoError(t, - c.Parse([]byte{ + c.UnmarshalBinary([]byte{ 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ 0x34, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ diff --git a/pkg/pcic/protocol.go b/pkg/pcic/protocol.go index 795fe7c..787a1d3 100644 --- a/pkg/pcic/protocol.go +++ b/pkg/pcic/protocol.go @@ -153,7 +153,7 @@ func asyncResultParser(data []byte) (Frame, error) { offset := 0 for remainingBytes > 0 { c := chunk.ChunkData{} - if err := c.Parse(content[offset:]); err != nil { + if err := c.UnmarshalBinary(content[offset:]); err != nil { return frame, err } frame.Chunks = append(frame.Chunks, c) diff --git a/pkg/pcic/protocol_test.go b/pkg/pcic/protocol_test.go index 3429bec..45055cc 100644 --- a/pkg/pcic/protocol_test.go +++ b/pkg/pcic/protocol_test.go @@ -70,7 +70,7 @@ func TestReceiveWithChunk(t *testing.T) { 0xFF, 0xFF, 0xFF, 0xBB, /* DATA */ } assert.NoError(t, - c.Parse(chunkData), + c.UnmarshalBinary(chunkData), "A successful parse expected", ) p := pcic.PCIC{} From 6848bf00a19358e4fabc3fd4f82eca918afe4815 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sun, 24 Dec 2023 12:12:53 +0100 Subject: [PATCH 15/33] doc: add some documentation for the Chunk type Signed-off-by: Christian Ege --- pkg/chunk/chunk.go | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/pkg/chunk/chunk.go b/pkg/chunk/chunk.go index 39a56a2..360132d 100644 --- a/pkg/chunk/chunk.go +++ b/pkg/chunk/chunk.go @@ -44,21 +44,22 @@ type Chunk interface { Bytes() []byte } +// A ChunkData object contains all the information based on a PCIC chunk type ChunkData struct { - chunkType ChunkType /* The type of the Chunk, each chunk type requires a unique ID*/ - chunkSize uint32 /* The size of the complete chunk, including the header and the data */ - headerSize uint32 /* The Size of the chunk header after this amount of bytes the data section starts */ - headerVersion uint32 /* The version of the header */ - dataWidth uint32 /* The width of the data */ - dataHeight uint32 /* The height of the data, for none image data this is set to 1*/ - dataFormat DataFormat /* The data format*/ - timeStamp uint32 /* The timestamp in micro seconds (deprecated) */ - frameCount uint32 /* A frame count */ - statusCode uint32 /* Conveys the status of the device default: 0 */ - timestampSec uint32 /* The timestamp seconds part */ - timestampNSec uint32 /* The timestamp nano seconds part */ - metadata string /* The JSON meta data is always {} for v2 chunks */ - data []byte /**/ + chunkType ChunkType // The type of the Chunk, each chunk type requires a unique ID + chunkSize uint32 // The size of the complete chunk, including the header and the data + headerSize uint32 // The Size of the chunk header after this amount of bytes the data section starts + headerVersion uint32 // The version of the header + dataWidth uint32 // The width of the data + dataHeight uint32 // The height of the data, for none image data this is set to 1 + dataFormat DataFormat // The data format + timeStamp uint32 // The timestamp in micro seconds (deprecated) + frameCount uint32 // A frame count + statusCode uint32 // Conveys the status of the device default: 0 + timestampSec uint32 // The timestamp seconds part + timestampNSec uint32 // The timestamp nano seconds part + metadata string // The JSON meta data is always {} for v2 chunks + data []byte // The data the chunk describes } const ( From 02a018cbcf25eaee18a8bd78e6acbb5563870c44 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sun, 24 Dec 2023 12:13:45 +0100 Subject: [PATCH 16/33] refactor: rename Asnc to MessageHandler Signed-off-by: Christian Ege --- pkg/pcic/protocol.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/pcic/protocol.go b/pkg/pcic/protocol.go index 787a1d3..88f3481 100644 --- a/pkg/pcic/protocol.go +++ b/pkg/pcic/protocol.go @@ -39,7 +39,7 @@ var ( notificationTicket []byte = []byte{'0', '0', '1', '0'} ) -type Async interface { +type MessageHandler interface { Result(Frame) Error(ErrorMessage) Notification(NotificationMessage) @@ -55,7 +55,7 @@ type ErrorMessage struct { Message string } -func (p *PCIC) Receive(reader io.Reader, handler Async) error { +func (p *PCIC) Receive(reader io.Reader, handler MessageHandler) error { header := make([]byte, headerSize) n, err := io.ReadFull(reader, header) if err != nil { From 1ea421d2797671324a91447f6d5cb78ab07f9e4b Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Sun, 24 Dec 2023 12:46:31 +0100 Subject: [PATCH 17/33] refactor: move the Chunk into the pcic pakage Signed-off-by: Christian Ege --- pkg/{chunk => pcic}/chunk.go | 59 ++++++++++++++++--------------- pkg/{chunk => pcic}/chunk_test.go | 18 ++++++---- pkg/pcic/frame.go | 9 +++-- pkg/pcic/protocol.go | 4 +-- pkg/pcic/protocol_test.go | 5 ++- 5 files changed, 50 insertions(+), 45 deletions(-) rename pkg/{chunk => pcic}/chunk.go (87%) rename pkg/{chunk => pcic}/chunk_test.go (95%) diff --git a/pkg/chunk/chunk.go b/pkg/pcic/chunk.go similarity index 87% rename from pkg/chunk/chunk.go rename to pkg/pcic/chunk.go index 360132d..b48d929 100644 --- a/pkg/chunk/chunk.go +++ b/pkg/pcic/chunk.go @@ -1,4 +1,4 @@ -package chunk +package pcic import ( "encoding/binary" @@ -7,8 +7,11 @@ import ( "time" ) -type ChunkType uint32 -type DataFormat uint32 +type ( + ChunkType uint32 + DataFormat uint32 + ChunkOption func(c *Chunk) +) // The known Chunk Types const ( @@ -33,19 +36,8 @@ var ( byteSizeLUT [FORMAT_MAX]uint32 = [FORMAT_MAX]uint32{1, 1, 2, 2, 4, 4, 4, 8, 8} ) -type Chunk interface { - UnmarshalBinary(data []byte) error - MarshalBinary() (data []byte, err error) - Type() ChunkType - Size() uint32 - FrameCount() uint32 - Status() uint32 - TimeStamp() time.Time - Bytes() []byte -} - -// A ChunkData object contains all the information based on a PCIC chunk -type ChunkData struct { +// A Chunk object contains all the information based on a PCIC chunk +type Chunk struct { chunkType ChunkType // The type of the Chunk, each chunk type requires a unique ID chunkSize uint32 // The size of the complete chunk, including the header and the data headerSize uint32 // The Size of the chunk header after this amount of bytes the data section starts @@ -82,43 +74,52 @@ const ( MaxSupportedChunkHeaderVersion = 3 ) -func New(cType ChunkType) *ChunkData { - chunk := &ChunkData{ - chunkType: cType, - metadata: "{}", - data: []byte{}, +func NewChunk(options ...ChunkOption) *Chunk { + chunk := &Chunk{ + metadata: "{}", + data: []byte{}, + } + // Apply options + for _, opt := range options { + opt(chunk) } return chunk } -func (c *ChunkData) Type() ChunkType { +func WithChunkType(cType ChunkType) ChunkOption { + return func(c *Chunk) { + c.chunkType = cType + } +} + +func (c *Chunk) Type() ChunkType { return c.chunkType } -func (c *ChunkData) Size() int { +func (c *Chunk) Size() int { return int(c.chunkSize) } -func (c *ChunkData) FrameCount() uint32 { +func (c *Chunk) FrameCount() uint32 { return c.frameCount } -func (c *ChunkData) Status() uint32 { +func (c *Chunk) Status() uint32 { return c.statusCode } -func (c *ChunkData) TimeStamp() time.Time { +func (c *Chunk) TimeStamp() time.Time { return time.Unix(int64(c.timestampSec), int64(c.timestampNSec)) } -func (c *ChunkData) Bytes() []byte { +func (c *Chunk) Bytes() []byte { return c.data } // MarshalBinary creates a binary representation of the Chunk // // The binary representation is encoded in the byte slice -func (c *ChunkData) MarshalBinary() (data []byte, err error) { +func (c *Chunk) MarshalBinary() (data []byte, err error) { return []byte{}, nil } @@ -126,7 +127,7 @@ func (c *ChunkData) MarshalBinary() (data []byte, err error) { // // It copies the data from the input slice to comply with the BinaryUnmarshaler // interface. -func (c *ChunkData) UnmarshalBinary(data []byte) error { +func (c *Chunk) UnmarshalBinary(data []byte) error { dataLen := uint32(len(data)) if dataLen < offsetOfData { return errors.New("unable to parse an empty input") diff --git a/pkg/chunk/chunk_test.go b/pkg/pcic/chunk_test.go similarity index 95% rename from pkg/chunk/chunk_test.go rename to pkg/pcic/chunk_test.go index fab0adf..880c4de 100644 --- a/pkg/chunk/chunk_test.go +++ b/pkg/pcic/chunk_test.go @@ -1,24 +1,24 @@ -package chunk_test +package pcic_test import ( "testing" "time" - "github.com/graugans/go-ovp8xx/pkg/chunk" + "github.com/graugans/go-ovp8xx/pkg/pcic" "github.com/stretchr/testify/assert" ) func TestChunkType(t *testing.T) { - c := chunk.New(chunk.RADIAL_DISTANCE_NOISE) + c := pcic.NewChunk(pcic.WithChunkType(pcic.RADIAL_DISTANCE_NOISE)) assert.Equal(t, - chunk.RADIAL_DISTANCE_NOISE, + pcic.RADIAL_DISTANCE_NOISE, c.Type(), "There is a chunk type mismatch detected", ) } -func TestChunkParse(t *testing.T) { - c := chunk.ChunkData{} +func TestUnmarshalBinary(t *testing.T) { + c := pcic.Chunk{} assert.Error(t, c.UnmarshalBinary([]byte{}), "An error is expected when sending an empty byte slice", @@ -58,7 +58,7 @@ func TestChunkParse(t *testing.T) { "A successful parse expected", ) assert.Equal(t, - chunk.RADIAL_DISTANCE_NOISE, + pcic.RADIAL_DISTANCE_NOISE, c.Type(), "Type mismatch detected", ) @@ -182,3 +182,7 @@ func TestChunkParse(t *testing.T) { "A data size mismatch occurred", ) } + +func TestRoundtrip(t *testing.T) { + +} diff --git a/pkg/pcic/frame.go b/pkg/pcic/frame.go index f0f7c5a..828ea9e 100644 --- a/pkg/pcic/frame.go +++ b/pkg/pcic/frame.go @@ -1,7 +1,10 @@ package pcic -import "github.com/graugans/go-ovp8xx/pkg/chunk" - type Frame struct { - Chunks []chunk.ChunkData + Chunks []Chunk + size int +} + +func (f *Frame) Size() int { + return f.size } diff --git a/pkg/pcic/protocol.go b/pkg/pcic/protocol.go index 88f3481..3e693eb 100644 --- a/pkg/pcic/protocol.go +++ b/pkg/pcic/protocol.go @@ -5,8 +5,6 @@ import ( "errors" "fmt" "io" - - "github.com/graugans/go-ovp8xx/pkg/chunk" ) type PCIC struct { @@ -152,7 +150,7 @@ func asyncResultParser(data []byte) (Frame, error) { remainingBytes := len(content) offset := 0 for remainingBytes > 0 { - c := chunk.ChunkData{} + c := Chunk{} if err := c.UnmarshalBinary(content[offset:]); err != nil { return frame, err } diff --git a/pkg/pcic/protocol_test.go b/pkg/pcic/protocol_test.go index 45055cc..d518352 100644 --- a/pkg/pcic/protocol_test.go +++ b/pkg/pcic/protocol_test.go @@ -10,7 +10,6 @@ import ( "strings" "testing" - "github.com/graugans/go-ovp8xx/pkg/chunk" "github.com/graugans/go-ovp8xx/pkg/pcic" "github.com/stretchr/testify/assert" ) @@ -53,7 +52,7 @@ func TestMinimalReceive(t *testing.T) { } func TestReceiveWithChunk(t *testing.T) { - c := chunk.ChunkData{} + c := pcic.Chunk{} chunkData := []byte{ 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ 0x34, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ @@ -85,7 +84,7 @@ func TestReceiveWithChunk(t *testing.T) { assert.NoError(t, err, "We expect no error while receiving data") assert.Equal(t, - chunk.RADIAL_DISTANCE_NOISE, + pcic.RADIAL_DISTANCE_NOISE, testHandler.frame.Chunks[0].Type(), ) From 1102800bdbe7eb4f8443a68545375a7dcb4c23da Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Mon, 25 Dec 2023 10:25:09 +0100 Subject: [PATCH 18/33] ci: provide a timeout for the tests Signed-off-by: Christian Ege --- .github/workflows/go.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 1199eef..fbc8b62 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -34,8 +34,8 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: '1.20' - - run: go test -v ./... + go-version: '1.21' + - run: go test -v -cover -timeout=1m ./... build: runs-on: ubuntu-latest @@ -43,7 +43,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' - name: Install dependencies run: go get -d ./... @@ -63,7 +63,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' - uses: go-semantic-release/action@v1 with: hooks: goreleaser From b3ccb791b2bd738b2adf5f6c236f1b6b73bac2f9 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Mon, 25 Dec 2023 11:17:22 +0100 Subject: [PATCH 19/33] ci: add lfs support for the testing step Signed-off-by: Christian Ege --- .github/workflows/go.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index fbc8b62..880712d 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -34,8 +34,10 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: + lfs: 'true' go-version: '1.21' - - run: go test -v -cover -timeout=1m ./... + - run: git lfs pull + - run: go test -v -failfast -cover -timeout=1m ./... build: runs-on: ubuntu-latest From 61a3c13eb10870314ae3868c0002c97b2bafdfe7 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Mon, 25 Dec 2023 11:32:18 +0100 Subject: [PATCH 20/33] ci: add a codecoverage action Signed-off-by: Christian Ege --- .github/workflows/go.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 880712d..5ced185 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -37,7 +37,11 @@ jobs: lfs: 'true' go-version: '1.21' - run: git lfs pull - - run: go test -v -failfast -cover -timeout=1m ./... + - run: go test -v -failfast -coverprofile cover.out -timeout=1m ./... + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} build: runs-on: ubuntu-latest From 35537fdb323ad719dc121be87509c5115e4ac803 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Mon, 25 Dec 2023 11:42:58 +0100 Subject: [PATCH 21/33] test: add a round trip test for Chunks Signed-off-by: Christian Ege --- pkg/pcic/chunk.go | 29 ++++++++++++---- pkg/pcic/chunk_test.go | 75 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 7 deletions(-) diff --git a/pkg/pcic/chunk.go b/pkg/pcic/chunk.go index b48d929..993abaa 100644 --- a/pkg/pcic/chunk.go +++ b/pkg/pcic/chunk.go @@ -76,8 +76,11 @@ const ( func NewChunk(options ...ChunkOption) *Chunk { chunk := &Chunk{ - metadata: "{}", - data: []byte{}, + chunkSize: offsetOfData, + headerSize: offsetOfData, + headerVersion: 2, + metadata: "{}", + data: []byte{}, } // Apply options for _, opt := range options { @@ -120,7 +123,24 @@ func (c *Chunk) Bytes() []byte { // // The binary representation is encoded in the byte slice func (c *Chunk) MarshalBinary() (data []byte, err error) { - return []byte{}, nil + blob := make([]byte, offsetOfData) + binary.LittleEndian.PutUint32( + blob, + uint32(c.chunkType), + ) + binary.LittleEndian.PutUint32( + blob[offsetOfSize:offsetOfHeaderSize], + c.chunkSize, + ) + binary.LittleEndian.PutUint32( + blob[offsetOfHeaderSize:offsetOfHeaderVersion], + c.headerSize, + ) + binary.LittleEndian.PutUint32( + blob[offsetOfHeaderVersion:offsetOfWidth], + c.headerVersion, + ) + return blob, nil } // UnmarshalBinary creates a ChunkData from a byte slice @@ -216,9 +236,6 @@ func (c *Chunk) UnmarshalBinary(data []byte) error { src := data[offsetOfData : offsetOfData+(c.chunkSize-c.headerSize)] c.data = make([]byte, len(src)) copy(c.data, src) - if src == nil { - c.data = nil - } if (c.dataWidth * c.dataHeight * byteSizeLUT[c.dataFormat]) != uint32(len(c.data)) { return fmt.Errorf( diff --git a/pkg/pcic/chunk_test.go b/pkg/pcic/chunk_test.go index 880c4de..d683769 100644 --- a/pkg/pcic/chunk_test.go +++ b/pkg/pcic/chunk_test.go @@ -181,8 +181,81 @@ func TestUnmarshalBinary(t *testing.T) { c.Bytes(), "A data size mismatch occurred", ) + + assert.Error(t, + c.UnmarshalBinary([]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_HEIGHT */ + 0x04, 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 */ + 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE of second frame*/ + }), + "The (width * height * data format) does not match the data size", + ) + + assert.Error(t, + c.UnmarshalBinary([]byte{ + 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ + 0x28, 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_HEIGHT */ + 0x04, 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 */ + 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE of second frame*/ + }), + "The Chunk size is smaller than the expected size", + ) + + assert.Error(t, + c.UnmarshalBinary([]byte{ + 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ + 0x00, 0x01, 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_HEIGHT */ + 0x04, 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 */ + 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE of second frame*/ + }), + "The Chunk size is bigger than the data size", + ) + } func TestRoundtrip(t *testing.T) { - + chunk := pcic.NewChunk(pcic.WithChunkType(pcic.RADIAL_DISTANCE_NOISE)) + chunkData, err := chunk.MarshalBinary() + assert.NoError(t, err, "No error expected when marshalling to binary") + roundTripChunk := pcic.NewChunk() + assert.NoError(t, + roundTripChunk.UnmarshalBinary(chunkData), + "No Error expected when unmarshalling from binary", + ) + assert.Equal(t, + chunk.Type(), + roundTripChunk.Type(), + "We expect the type to be the same after the round trip", + ) } From ae7ff2c3a49b3bbe41e18297c9d38542c981f72e Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Mon, 25 Dec 2023 11:47:43 +0100 Subject: [PATCH 22/33] ci: fix the LFS part Signed-off-by: Christian Ege --- .github/workflows/go.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 5ced185..8e06048 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -31,14 +31,17 @@ jobs: runs-on: ubuntu-latest needs: lint steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v4 + - name: ⬇️ Checkout + uses: actions/checkout@v3 with: lfs: 'true' + - name: 👷 Prepare the Go environment + uses: actions/setup-go@v4 + with: go-version: '1.21' - - run: git lfs pull - - run: go test -v -failfast -coverprofile cover.out -timeout=1m ./... - - name: Upload coverage reports to Codecov + - name: 🧪 Run the unit tests + run: go test -v -failfast -coverprofile cover.out -timeout=1m ./... + - name: 🚀 Upload the coverage reports to Codecov uses: codecov/codecov-action@v3 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From 3a6c63fa3888de347d886acc7ef4b98c1fbfb7e7 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Mon, 25 Dec 2023 20:15:56 +0100 Subject: [PATCH 23/33] test(chunk): increase the coverage Signed-off-by: Christian Ege --- pkg/pcic/chunk_test.go | 80 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/pkg/pcic/chunk_test.go b/pkg/pcic/chunk_test.go index d683769..0df0d7c 100644 --- a/pkg/pcic/chunk_test.go +++ b/pkg/pcic/chunk_test.go @@ -259,3 +259,83 @@ func TestRoundtrip(t *testing.T) { "We expect the type to be the same after the round trip", ) } + +func TestHeaderSizeTooSmall(t *testing.T) { + c := pcic.NewChunk() + assert.Error(t, + c.UnmarshalBinary([]byte{ + 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ + 0x30, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ + 0x28, 0x00, 0x00, 0x00, /* HEADER_SIZE set too small */ + 0x02, 0x00, 0x00, 0x00, /* HEADER_VERSION */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGHT */ + 0x00, 0x00, 0x00, 0x00, /* DATA_FORMAT */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP */ + 0x00, 0x01, 0x00, 0x00, /* FRAME_COUNT */ + 0x00, 0x01, 0x00, 0x00, /* STATUS_CODE */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_SEC */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_NSEC */ + }), + "An error expected, due to small header size", + ) +} + +func TestHeaderSizeTooBig(t *testing.T) { + c := pcic.NewChunk() + assert.Error(t, + c.UnmarshalBinary([]byte{ + 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ + 0x30, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ + 0x32, 0x00, 0x00, 0x00, /* HEADER_SIZE set too small */ + 0x02, 0x00, 0x00, 0x00, /* HEADER_VERSION */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGHT */ + 0x00, 0x00, 0x00, 0x00, /* DATA_FORMAT */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP */ + 0x00, 0x01, 0x00, 0x00, /* FRAME_COUNT */ + 0x00, 0x01, 0x00, 0x00, /* STATUS_CODE */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_SEC */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_NSEC */ + }), + "An error expected, due to big header size", + ) +} + +func TestInvalidHeaderVersion(t *testing.T) { + c := pcic.NewChunk() + assert.Error(t, + c.UnmarshalBinary([]byte{ + 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ + 0x30, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ + 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ + 0x00, 0x00, 0x00, 0x00, /* HEADER_VERSION == 0 */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGHT */ + 0x00, 0x00, 0x00, 0x00, /* DATA_FORMAT */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP */ + 0x00, 0x01, 0x00, 0x00, /* FRAME_COUNT */ + 0x00, 0x01, 0x00, 0x00, /* STATUS_CODE */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_SEC */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_NSEC */ + }), + "An error expected, due to wrong header version", + ) + assert.Error(t, + c.UnmarshalBinary([]byte{ + 0x69, 0x00, 0x00, 0x00, /* CHUNK_TYPE */ + 0x30, 0x00, 0x00, 0x00, /* CHUNK_SIZE */ + 0x30, 0x00, 0x00, 0x00, /* HEADER_SIZE */ + 0x04, 0x00, 0x00, 0x00, /* HEADER_VERSION == 4 */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_WIDTH */ + 0x00, 0x00, 0x00, 0x00, /* IMAGE_HEIGHT */ + 0x00, 0x00, 0x00, 0x00, /* DATA_FORMAT */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP */ + 0x00, 0x01, 0x00, 0x00, /* FRAME_COUNT */ + 0x00, 0x01, 0x00, 0x00, /* STATUS_CODE */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_SEC */ + 0x00, 0x01, 0x00, 0x00, /* TIME_STAMP_NSEC */ + }), + "An error expected, due to wrong header version", + ) +} From 12d0cadf82247fd587e541734a26b3a29f4694b0 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Mon, 25 Dec 2023 20:19:37 +0100 Subject: [PATCH 24/33] doc(Readme): Add a codecov icon Signed-off-by: Christian Ege --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index ebc46a4..447a3a9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ + + # Go client for the ifm OVP8xx series of devices A GO module and cli to access the ifm OVP8xx series of devices. @@ -5,6 +7,7 @@ A GO module and cli to access the ifm OVP8xx series of devices. [![GitHub go.mod Go version of a Go module](https://img.shields.io/github/go-mod/go-version/gomods/athens.svg)](https://github.com/gomods/athens) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ![example workflow](https://github.com/graugans/go-ovp8xx/actions/workflows/go.yml/badge.svg) +[![codecov](https://codecov.io/gh/graugans/go-ovp8xx/graph/badge.svg?token=BU6UPYCUPI)](https://codecov.io/gh/graugans/go-ovp8xx) ## Project status From fa59aa79eb14d35b9157c988c2382e06535282c8 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Mon, 25 Dec 2023 20:24:46 +0100 Subject: [PATCH 25/33] build: bump the Go version Signed-off-by: Christian Ege --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 2652a5f..d1f12aa 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/graugans/go-ovp8xx -go 1.20 +go 1.21 require ( alexejk.io/go-xmlrpc v0.4.0 From 18ead1e82fe67e882a3de77c5d95f5937e94a70b Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Mon, 25 Dec 2023 21:55:11 +0100 Subject: [PATCH 26/33] test: more Chunk unit tests added Signed-off-by: Christian Ege --- pkg/pcic/chunk.go | 55 ++++++++++++++++++++++++++++- pkg/pcic/chunk_test.go | 78 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 1 deletion(-) diff --git a/pkg/pcic/chunk.go b/pkg/pcic/chunk.go index 993abaa..d66ce7d 100644 --- a/pkg/pcic/chunk.go +++ b/pkg/pcic/chunk.go @@ -89,32 +89,62 @@ func NewChunk(options ...ChunkOption) *Chunk { return chunk } +// WithChunkType sets the chunk type of the new Chunk object func WithChunkType(cType ChunkType) ChunkOption { return func(c *Chunk) { c.chunkType = cType } } +// WithDimension set the chunk dimension and the given data format +func WithDimension(width, height int, format DataFormat) ChunkOption { + return func(c *Chunk) { + c.dataWidth = uint32(width) + c.dataHeight = uint32(height) + c.dataFormat = format + c.data = make([]byte, c.dataWidth*c.dataHeight*byteSizeLUT[format]) + c.chunkSize = c.headerSize + uint32(len(c.data)) + } +} + +// Type returns the given ChunkType func (c *Chunk) Type() ChunkType { return c.chunkType } +// Size returns the Size of the whole Chunk +// +// This is the size the Chunk is marshalled to. func (c *Chunk) Size() int { return int(c.chunkSize) } +// FrameCount returns the frame count of the Chunk func (c *Chunk) FrameCount() uint32 { return c.frameCount } +// SetFrameCount sets the frame count of the Chunk +func (c *Chunk) SetFrameCount(num uint32) { + c.frameCount = num +} + +// Status returns the status of the given Chunk func (c *Chunk) Status() uint32 { return c.statusCode } +// SetStatus sets the status of the Chunk +func (c *Chunk) SetStatus(status uint32) { + c.statusCode = status +} + +// TimeStamp returns the time stamp of the given Chunk func (c *Chunk) TimeStamp() time.Time { return time.Unix(int64(c.timestampSec), int64(c.timestampNSec)) } +// Bytes return the data the current Chunk is holding func (c *Chunk) Bytes() []byte { return c.data } @@ -123,7 +153,7 @@ func (c *Chunk) Bytes() []byte { // // The binary representation is encoded in the byte slice func (c *Chunk) MarshalBinary() (data []byte, err error) { - blob := make([]byte, offsetOfData) + blob := make([]byte, offsetOfData+len(c.data)) binary.LittleEndian.PutUint32( blob, uint32(c.chunkType), @@ -140,6 +170,28 @@ func (c *Chunk) MarshalBinary() (data []byte, err error) { blob[offsetOfHeaderVersion:offsetOfWidth], c.headerVersion, ) + binary.LittleEndian.PutUint32( + blob[offsetOfWidth:offsetOfHeight], + c.dataWidth, + ) + binary.LittleEndian.PutUint32( + blob[offsetOfHeight:offsetOfFormat], + c.dataHeight, + ) + binary.LittleEndian.PutUint32( + blob[offsetOfFormat:offsetOfTimeStamp], + uint32(c.dataFormat), + ) + // We skip the timestamp for now, it is deprecated + binary.LittleEndian.PutUint32( + blob[offsetOfFrameCount:offsetOfStatusCode], + c.frameCount, + ) + binary.LittleEndian.PutUint32( + blob[offsetOfStatusCode:offsetOfTimeStampSec], + c.statusCode, + ) + return blob, nil } @@ -148,6 +200,7 @@ func (c *Chunk) MarshalBinary() (data []byte, err error) { // It copies the data from the input slice to comply with the BinaryUnmarshaler // interface. func (c *Chunk) UnmarshalBinary(data []byte) error { + dataLen := uint32(len(data)) if dataLen < offsetOfData { return errors.New("unable to parse an empty input") diff --git a/pkg/pcic/chunk_test.go b/pkg/pcic/chunk_test.go index 0df0d7c..ac04521 100644 --- a/pkg/pcic/chunk_test.go +++ b/pkg/pcic/chunk_test.go @@ -1,6 +1,7 @@ package pcic_test import ( + "math/rand" "testing" "time" @@ -339,3 +340,80 @@ func TestInvalidHeaderVersion(t *testing.T) { "An error expected, due to wrong header version", ) } + +func TestWithNil(t *testing.T) { + vector := pcic.NewChunk() + assert.Error( + t, + vector.UnmarshalBinary(nil), + "An error is expected when providing nil as input", + ) +} +func TestWithDimension(t *testing.T) { + vector := make([]*pcic.Chunk, 2) + vector[0] = pcic.NewChunk(pcic.WithDimension(640, 480, pcic.FORMAT_32F)) + vector[1] = pcic.NewChunk() + data, err := vector[0].MarshalBinary() + assert.NoError(t, err, "No Error expected during MarshalBinary") + assert.NoError( + t, + vector[1].UnmarshalBinary(data), + "No error expected while UnmarshalBinary", + ) +} + +func TestFrameCount(t *testing.T) { + count := rand.Uint32() + chunk := pcic.NewChunk() + chunk.SetFrameCount(count) + assert.Equal( + t, + count, + chunk.FrameCount(), + "No error is expected when getting the frame count", + ) + clone := pcic.NewChunk() + data, err := chunk.MarshalBinary() + assert.NoError(t, + err, + "No error expected when creating a binary clone", + ) + assert.NoError(t, + clone.UnmarshalBinary(data), + "No error expected when creating a clone from the bytes", + ) + assert.Equal( + t, + chunk.FrameCount(), + clone.FrameCount(), + "The frame count of the original and the clone do not match", + ) +} + +func TestFrameStatus(t *testing.T) { + status := rand.Uint32() + chunk := pcic.NewChunk() + chunk.SetStatus(status) + assert.Equal( + t, + status, + chunk.Status(), + "No error expected when setting the staus", + ) + clone := pcic.NewChunk() + data, err := chunk.MarshalBinary() + assert.NoError(t, + err, + "No error expected when creating a binary clone", + ) + assert.NoError(t, + clone.UnmarshalBinary(data), + "No error expected when creating a clone from the bytes", + ) + assert.Equal( + t, + chunk.Status(), + clone.Status(), + "The status of the original and the clone do not match", + ) +} From 684cf3c2e26508b7cfcfe52ac96a950dd33c8a69 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Mon, 25 Dec 2023 22:17:16 +0100 Subject: [PATCH 27/33] test: add test for the timestamp Signed-off-by: Christian Ege --- pkg/pcic/chunk.go | 15 +++++++++++++++ pkg/pcic/chunk_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/pkg/pcic/chunk.go b/pkg/pcic/chunk.go index d66ce7d..66740a9 100644 --- a/pkg/pcic/chunk.go +++ b/pkg/pcic/chunk.go @@ -144,6 +144,13 @@ func (c *Chunk) TimeStamp() time.Time { return time.Unix(int64(c.timestampSec), int64(c.timestampNSec)) } +func (c *Chunk) SetTimestamp(value time.Time) { + c.timestampSec = uint32(value.Unix()) + seconds := time.Unix(int64(c.timestampSec), 0) + c.timestampNSec = uint32(value.UnixNano() - seconds.UnixNano()) + fmt.Printf("Seconds: %d Nanoseconds: %d\n", c.timestampSec, c.timestampNSec) +} + // Bytes return the data the current Chunk is holding func (c *Chunk) Bytes() []byte { return c.data @@ -191,6 +198,14 @@ func (c *Chunk) MarshalBinary() (data []byte, err error) { blob[offsetOfStatusCode:offsetOfTimeStampSec], c.statusCode, ) + binary.LittleEndian.PutUint32( + blob[offsetOfTimeStampSec:offsetOfTimeStampNsec], + c.timestampSec, + ) + binary.LittleEndian.PutUint32( + blob[offsetOfTimeStampNsec:offsetOfData], + c.timestampNSec, + ) return blob, nil } diff --git a/pkg/pcic/chunk_test.go b/pkg/pcic/chunk_test.go index ac04521..11a1143 100644 --- a/pkg/pcic/chunk_test.go +++ b/pkg/pcic/chunk_test.go @@ -417,3 +417,31 @@ func TestFrameStatus(t *testing.T) { "The status of the original and the clone do not match", ) } + +func TestChunkTimeStamp(t *testing.T) { + now := time.Now() + chunk := pcic.NewChunk() + chunk.SetTimestamp(now) + assert.Equal( + t, + now.UnixNano(), + chunk.TimeStamp().UnixNano(), + "No error expected when setting the time stamp", + ) + clone := pcic.NewChunk() + data, err := chunk.MarshalBinary() + assert.NoError(t, + err, + "No error expected when creating a binary clone", + ) + assert.NoError(t, + clone.UnmarshalBinary(data), + "No error expected when creating a clone from the bytes", + ) + assert.Equal( + t, + chunk.TimeStamp(), + clone.TimeStamp(), + "The status of the original and the clone do not match", + ) +} From 72697a4a366560ea2e5cbecf6c13305ff249b9f5 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Thu, 28 Dec 2023 19:51:01 +0100 Subject: [PATCH 28/33] fix: the chunk data Signed-off-by: Christian Ege --- pkg/pcic/chunk.go | 2 ++ pkg/pcic/chunk_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/pkg/pcic/chunk.go b/pkg/pcic/chunk.go index 66740a9..162962e 100644 --- a/pkg/pcic/chunk.go +++ b/pkg/pcic/chunk.go @@ -207,6 +207,8 @@ func (c *Chunk) MarshalBinary() (data []byte, err error) { c.timestampNSec, ) + // copy the data we keep + copy(blob[offsetOfData:], c.data) return blob, nil } diff --git a/pkg/pcic/chunk_test.go b/pkg/pcic/chunk_test.go index 11a1143..7636f22 100644 --- a/pkg/pcic/chunk_test.go +++ b/pkg/pcic/chunk_test.go @@ -1,6 +1,8 @@ package pcic_test import ( + "bytes" + crand "crypto/rand" "math/rand" "testing" "time" @@ -445,3 +447,32 @@ func TestChunkTimeStamp(t *testing.T) { "The status of the original and the clone do not match", ) } + +func TestCloneWithData(t *testing.T) { + chunk := pcic.NewChunk(pcic.WithDimension(2, 1, pcic.FORMAT_16U)) + assert.Equal(t, + 4, + len(chunk.Bytes()), + "Size missmatch detected", + ) + blob := chunk.Bytes() + _, err := crand.Read(blob) + assert.NoError(t, + err, + "We expect no error when getting random data", + ) + clone := pcic.NewChunk() + data, err := chunk.MarshalBinary() + assert.NoError(t, + err, + "No error expected when creating a binary clone", + ) + assert.NoError(t, + clone.UnmarshalBinary(data), + "No error expected when creating a clone from the bytes", + ) + assert.True(t, + bytes.Equal(blob, clone.Bytes()), + "A data mismatch detected", + ) +} From 45fc40cef36e593e125d6b225f9558decd6f640c Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Fri, 29 Dec 2023 13:26:04 +0100 Subject: [PATCH 29/33] test: 100% coverage for pcic Signed-off-by: Christian Ege --- pkg/pcic/frame.go | 5 - pkg/pcic/protocol.go | 59 +++++----- pkg/pcic/protocol_test.go | 224 +++++++++++++++++++++++++++++++++++--- 3 files changed, 239 insertions(+), 49 deletions(-) diff --git a/pkg/pcic/frame.go b/pkg/pcic/frame.go index 828ea9e..33e19d4 100644 --- a/pkg/pcic/frame.go +++ b/pkg/pcic/frame.go @@ -2,9 +2,4 @@ package pcic type Frame struct { Chunks []Chunk - size int -} - -func (f *Frame) Size() int { - return f.size } diff --git a/pkg/pcic/protocol.go b/pkg/pcic/protocol.go index 3e693eb..701cf9e 100644 --- a/pkg/pcic/protocol.go +++ b/pkg/pcic/protocol.go @@ -1,14 +1,20 @@ package pcic import ( + "bufio" "bytes" "errors" "fmt" "io" ) -type PCIC struct { -} +type ( + PCICClient struct { + reader *bufio.Reader + writer *bufio.Writer + } + PCICClientOption func(c *PCICClient) +) const ( headerSize int = 20 @@ -53,15 +59,32 @@ type ErrorMessage struct { Message string } -func (p *PCIC) Receive(reader io.Reader, handler MessageHandler) error { +func NewPCICClient(options ...PCICClientOption) *PCICClient { + pcic := &PCICClient{} + // Apply options + for _, opt := range options { + opt(pcic) + } + return pcic +} + +func WithBufioReaderWriter(com *bufio.ReadWriter) PCICClientOption { + return func(c *PCICClient) { + c.reader = com.Reader + c.writer = com.Writer + } +} + +func (p *PCICClient) ProcessIncomming(handler MessageHandler) error { + reader := p.reader + if reader == nil { + return errors.New("no bufio.Reader provided, please instantiate the object") + } header := make([]byte, headerSize) - n, err := io.ReadFull(reader, header) + _, err := io.ReadFull(reader, header) if err != nil { return err } - if n < headerSize { - return fmt.Errorf("not enough data received: %d", n) - } firstTicket := header[:ticketFieldLength] secondTicket := header[secondTicketOffset:dataOffset] if !bytes.Equal(firstTicket, secondTicket) { @@ -75,13 +98,10 @@ func (p *PCIC) Receive(reader io.Reader, handler MessageHandler) error { return fmt.Errorf("the length field does not start with 'L': %v", lengthBuffer) } length := 0 - n, err = fmt.Sscanf(lengthBuffer, "L%09d\r\n", &length) + _, err = fmt.Sscanf(lengthBuffer, "L%09d\r\n", &length) if err != nil { return err } - if n != 1 { - return errors.New("no length in the length field detected") - } if length < minimumContentLength { return errors.New("the length information is too short") } @@ -101,10 +121,6 @@ func (p *PCIC) Receive(reader io.Reader, handler MessageHandler) error { errorStatus, err := errorParser(data) handler.Error(errorStatus) return err - } else if bytes.Equal(notificationTicket, firstTicket) { - notification, err := notificationParser(data) - handler.Notification(notification) - return err } return fmt.Errorf("unknown ticket received: %s", string(firstTicket)) } @@ -124,24 +140,11 @@ func errorParser(data []byte) (ErrorMessage, error) { return errorStatus, err } -func notificationParser(data []byte) (NotificationMessage, error) { - var err error - notification := NotificationMessage{} - return notification, err -} - func asyncResultParser(data []byte) (Frame, error) { fmt.Printf("Async Data received\n") frame := Frame{} var err error 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 diff --git a/pkg/pcic/protocol_test.go b/pkg/pcic/protocol_test.go index d518352..05b918c 100644 --- a/pkg/pcic/protocol_test.go +++ b/pkg/pcic/protocol_test.go @@ -40,17 +40,120 @@ func (r *PCICAsyncReceiver) Notification(msg pcic.NotificationMessage) { var testHandler *PCICAsyncReceiver = &PCICAsyncReceiver{} func TestMinimalReceive(t *testing.T) { - r := strings.NewReader("Hello, Reader!") - p := pcic.PCIC{} - err := p.Receive(r, testHandler) + readerWriter := bufio.NewReadWriter( + bufio.NewReader(strings.NewReader("Hello, Reader!")), + nil, + ) + p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + err := p.ProcessIncomming(testHandler) assert.Error(t, err, "We expect an error while receiving malformed data") // Test the minimal possible PCIC message - r = strings.NewReader("0000L000000014\r\n0000starstop\r\n") - err = p.Receive(r, testHandler) + readerWriter = bufio.NewReadWriter( + bufio.NewReader(strings.NewReader("0000L000000014\r\n0000starstop\r\n")), + nil, + ) + p = pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + err = p.ProcessIncomming(testHandler) assert.NoError(t, err, "We expect no error while receiving data") } +func TestNotMatchingTickets(t *testing.T) { + // Test the minimal possible PCIC message + readerWriter := bufio.NewReadWriter( + bufio.NewReader(strings.NewReader("0001L000000014\r\n0000starstop\r\n")), + nil, + ) + p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + err := p.ProcessIncomming(testHandler) + assert.Error(t, + err, + "We expect an error because the tickets do not match", + ) +} + +func TestMalformedLength(t *testing.T) { + // Test the minimal possible PCIC message + readerWriter := bufio.NewReadWriter( + bufio.NewReader(strings.NewReader("0000l000000014\r\n0000starstop\r\n")), + nil, + ) + p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + err := p.ProcessIncomming(testHandler) + assert.Error(t, + err, + "We expect an error because the length field does not start with `L`", + ) +} + +func TestMalformedLengthField(t *testing.T) { + // Test the minimal possible PCIC message + readerWriter := bufio.NewReadWriter( + bufio.NewReader(strings.NewReader("0000L00000014X\r\n0000starstop\r\n")), + nil, + ) + p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + err := p.ProcessIncomming(testHandler) + assert.Error(t, + err, + "We expect an error because the length field is no well formed", + ) +} + +func TestMinimumLengthField(t *testing.T) { + // Test the minimal possible PCIC message + readerWriter := bufio.NewReadWriter( + bufio.NewReader(strings.NewReader("0000L000000005\r\n0000starstop\r\n")), + nil, + ) + p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + err := p.ProcessIncomming(testHandler) + assert.Error(t, + err, + "We expect an error because the length is too short", + ) +} + +func TestBiggerLengthField(t *testing.T) { + // Test the minimal possible PCIC message + readerWriter := bufio.NewReadWriter( + bufio.NewReader(strings.NewReader("0000L000000015\r\n0000starstop\r\n")), + nil, + ) + p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + err := p.ProcessIncomming(testHandler) + assert.Error(t, + err, + "We expect an error because the length too big", + ) +} + +func TestInvalidTrailer(t *testing.T) { + // Test the minimal possible PCIC message + readerWriter := bufio.NewReadWriter( + bufio.NewReader(strings.NewReader("0000L000000014\r\n0000starstop\r\r")), + nil, + ) + p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + err := p.ProcessIncomming(testHandler) + assert.Error(t, + err, + "We expect an error because the trailer is invalid", + ) +} +func TestWithNilReader(t *testing.T) { + readerWriter := bufio.NewReadWriter( + nil, + nil, + ) + p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + err := p.ProcessIncomming(testHandler) + assert.Error(t, + err, + "We expect an error when reader is nil", + ) +} + func TestReceiveWithChunk(t *testing.T) { c := pcic.Chunk{} chunkData := []byte{ @@ -72,15 +175,19 @@ func TestReceiveWithChunk(t *testing.T) { c.UnmarshalBinary(chunkData), "A successful parse expected", ) - p := pcic.PCIC{} + buffer := fmt.Sprintf( "0000L%09d\r\n0000star%sstop\r\n", miniMalContentLength+len(chunkData), string(chunkData), ) // Test the PCIC message with single chunk - r := strings.NewReader(buffer) - err := p.Receive(r, testHandler) + readerWriter := bufio.NewReadWriter( + bufio.NewReader(strings.NewReader(buffer)), + nil, + ) + p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + err := p.ProcessIncomming(testHandler) assert.NoError(t, err, "We expect no error while receiving data") assert.Equal(t, @@ -95,8 +202,12 @@ func TestReceiveWithChunk(t *testing.T) { string(chunkData), ) // Test the PCIC message with single chunk - r = strings.NewReader(buffer) - err = p.Receive(r, testHandler) + readerWriter = bufio.NewReadWriter( + bufio.NewReader(strings.NewReader(buffer)), + nil, + ) + p = pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + err = p.ProcessIncomming(testHandler) assert.Error(t, err, "We expect an error while receiving malformed data") // test with invalid ticket after the chunk @@ -105,8 +216,12 @@ func TestReceiveWithChunk(t *testing.T) { miniMalContentLength+len(chunkData), string(chunkData), ) - r = strings.NewReader(buffer) - err = p.Receive(r, testHandler) + readerWriter = bufio.NewReadWriter( + bufio.NewReader(strings.NewReader(buffer)), + nil, + ) + p = pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + err = p.ProcessIncomming(testHandler) assert.Error( t, err, @@ -115,15 +230,49 @@ func TestReceiveWithChunk(t *testing.T) { } +func TestReceiveWithNewChunk(t *testing.T) { + chunk := pcic.NewChunk( + pcic.WithChunkType(pcic.RADIAL_DISTANCE_NOISE), + pcic.WithDimension(100, 100, pcic.FORMAT_16U), + ) + chunkData, err := chunk.MarshalBinary() + assert.NoError(t, + err, + "We do not expect an error while marshalling the Chunk to binary", + ) + + buffer := fmt.Sprintf( + "0000L%09d\r\n0000star%sstop\r\n", + miniMalContentLength+len(chunkData), + string(chunkData), + ) + // Test the PCIC message with single chunk + readerWriter := bufio.NewReadWriter( + bufio.NewReader(strings.NewReader(buffer)), + nil, + ) + p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + err = p.ProcessIncomming(testHandler) + assert.NoError(t, + err, + "We expect no error while receiving chunk data", + ) +} + func TestWithRealChunkData(t *testing.T) { file, err := tfs.Open("testdata/pcic-test-data.blob.bz2") assert.NoError(t, err, "No error expected while reading the input") defer file.Close() buf := bufio.NewReader(file) cr := bzip2.NewReader(buf) - p := pcic.PCIC{} + readerWriter := bufio.NewReadWriter( + bufio.NewReader(cr), + nil, + ) + p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + for { - err := p.Receive(cr, testHandler) + err := p.ProcessIncomming(testHandler) if errors.Is(err, io.EOF) { break } @@ -137,15 +286,58 @@ func TestWithRealChunkData(t *testing.T) { } +func TestWithMalformedErrorData(t *testing.T) { + buffer := fmt.Sprintf( + "0001L%09d\r\n0001000000000:\r\n", + 16, + ) + // Test the PCIC message with error message + readerWriter := bufio.NewReadWriter( + bufio.NewReader(strings.NewReader(buffer)), + nil, + ) + p := pcic.NewPCICClient( + pcic.WithBufioReaderWriter(readerWriter), + ) + err := p.ProcessIncomming(testHandler) + assert.Error(t, + err, + "We expect an error while receiving malformed data", + ) +} +func TestWithErrorData(t *testing.T) { + buffer := fmt.Sprintf( + "0001L%09d\r\n0001000000000:{}\r\n", + 18, + ) + // Test the PCIC message with error message + readerWriter := bufio.NewReadWriter( + bufio.NewReader(strings.NewReader(buffer)), + nil, + ) + p := pcic.NewPCICClient( + pcic.WithBufioReaderWriter(readerWriter), + ) + err := p.ProcessIncomming(testHandler) + assert.NoError(t, + err, + "We expect no error while receiving data", + ) +} + func TestWithRealErrorData(t *testing.T) { file, err := tfs.Open("testdata/pcic-diagnostic.blob.bz2") assert.NoError(t, err, "No error expected while reading the input") defer file.Close() buf := bufio.NewReader(file) cr := bzip2.NewReader(buf) - p := pcic.PCIC{} + readerWriter := bufio.NewReadWriter( + bufio.NewReader(cr), + nil, + ) + p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) for { - err := p.Receive(cr, testHandler) + err := p.ProcessIncomming(testHandler) if errors.Is(err, io.EOF) { break } From db4669a56e8a158a0d3800583818a9c342825caa Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Thu, 25 Jan 2024 16:02:06 +0100 Subject: [PATCH 30/33] =?UTF-8?q?ci:=20=F0=9F=92=9A=20fix=20the=20ci=20bui?= =?UTF-8?q?ld?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Christian Ege --- pkg/pcic/protocol.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/pcic/protocol.go b/pkg/pcic/protocol.go index 701cf9e..e4353fe 100644 --- a/pkg/pcic/protocol.go +++ b/pkg/pcic/protocol.go @@ -38,9 +38,8 @@ const ( ) var ( - resultTicket []byte = []byte{'0', '0', '0', '0'} - errorTicket []byte = []byte{'0', '0', '0', '1'} - notificationTicket []byte = []byte{'0', '0', '1', '0'} + resultTicket []byte = []byte{'0', '0', '0', '0'} + errorTicket []byte = []byte{'0', '0', '0', '1'} ) type MessageHandler interface { From 12eec37e297ab387aead5db28fe86154754c32f7 Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Thu, 25 Jan 2024 17:15:24 +0100 Subject: [PATCH 31/33] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20a=20TCP/IP=20cl?= =?UTF-8?q?ient?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Christian Ege --- pkg/pcic/protocol.go | 30 +++++++++++++--- pkg/pcic/protocol_test.go | 74 ++++++++++++++++++++++++--------------- 2 files changed, 70 insertions(+), 34 deletions(-) diff --git a/pkg/pcic/protocol.go b/pkg/pcic/protocol.go index e4353fe..0bec80c 100644 --- a/pkg/pcic/protocol.go +++ b/pkg/pcic/protocol.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "net" ) type ( @@ -13,7 +14,7 @@ type ( reader *bufio.Reader writer *bufio.Writer } - PCICClientOption func(c *PCICClient) + PCICClientOption func(c *PCICClient) error ) const ( @@ -58,19 +59,38 @@ type ErrorMessage struct { Message string } -func NewPCICClient(options ...PCICClientOption) *PCICClient { +func NewPCICClient(options ...PCICClientOption) (*PCICClient, error) { + var err error pcic := &PCICClient{} // Apply options for _, opt := range options { - opt(pcic) + if err = opt(pcic); err != nil { + return nil, err + } } - return pcic + return pcic, err } func WithBufioReaderWriter(com *bufio.ReadWriter) PCICClientOption { - return func(c *PCICClient) { + return func(c *PCICClient) error { c.reader = com.Reader c.writer = com.Writer + return nil + } +} + +// WithTCPClient is a PCICClientOption that sets up a TCP client connection to the specified URI. +// It establishes a connection using the net.Dial function and initializes the reader and writer for the PCICClient. +// If an error occurs during the connection establishment, it will be handled and returned. +func WithTCPClient(hostname string, port uint16) PCICClientOption { + return func(c *PCICClient) error { + conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", hostname, port)) + if err != nil { + return err + } + c.reader = bufio.NewReader(conn) + c.writer = bufio.NewWriter(conn) + return nil } } diff --git a/pkg/pcic/protocol_test.go b/pkg/pcic/protocol_test.go index 05b918c..06c0fcc 100644 --- a/pkg/pcic/protocol_test.go +++ b/pkg/pcic/protocol_test.go @@ -44,8 +44,9 @@ func TestMinimalReceive(t *testing.T) { bufio.NewReader(strings.NewReader("Hello, Reader!")), nil, ) - p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) - err := p.ProcessIncomming(testHandler) + p, err := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + assert.NoError(t, err, "We expect no error while creating the PCICClient") + err = p.ProcessIncomming(testHandler) assert.Error(t, err, "We expect an error while receiving malformed data") // Test the minimal possible PCIC message @@ -53,7 +54,8 @@ func TestMinimalReceive(t *testing.T) { bufio.NewReader(strings.NewReader("0000L000000014\r\n0000starstop\r\n")), nil, ) - p = pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + p, err = pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + assert.NoError(t, err, "We expect no error while creating the PCICClient") err = p.ProcessIncomming(testHandler) assert.NoError(t, err, "We expect no error while receiving data") } @@ -64,8 +66,9 @@ func TestNotMatchingTickets(t *testing.T) { bufio.NewReader(strings.NewReader("0001L000000014\r\n0000starstop\r\n")), nil, ) - p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) - err := p.ProcessIncomming(testHandler) + client, err := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + assert.NoError(t, err, "We expect no error while creating the PCICClient") + err = client.ProcessIncomming(testHandler) assert.Error(t, err, "We expect an error because the tickets do not match", @@ -78,8 +81,9 @@ func TestMalformedLength(t *testing.T) { bufio.NewReader(strings.NewReader("0000l000000014\r\n0000starstop\r\n")), nil, ) - p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) - err := p.ProcessIncomming(testHandler) + p, err := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + assert.NoError(t, err, "We expect no error while creating the PCICClient") + err = p.ProcessIncomming(testHandler) assert.Error(t, err, "We expect an error because the length field does not start with `L`", @@ -92,8 +96,9 @@ func TestMalformedLengthField(t *testing.T) { bufio.NewReader(strings.NewReader("0000L00000014X\r\n0000starstop\r\n")), nil, ) - p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) - err := p.ProcessIncomming(testHandler) + p, err := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + assert.NoError(t, err, "We expect no error while creating the PCICClient") + err = p.ProcessIncomming(testHandler) assert.Error(t, err, "We expect an error because the length field is no well formed", @@ -106,8 +111,9 @@ func TestMinimumLengthField(t *testing.T) { bufio.NewReader(strings.NewReader("0000L000000005\r\n0000starstop\r\n")), nil, ) - p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) - err := p.ProcessIncomming(testHandler) + p, err := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + assert.NoError(t, err, "We expect no error while creating the PCICClient") + err = p.ProcessIncomming(testHandler) assert.Error(t, err, "We expect an error because the length is too short", @@ -120,8 +126,9 @@ func TestBiggerLengthField(t *testing.T) { bufio.NewReader(strings.NewReader("0000L000000015\r\n0000starstop\r\n")), nil, ) - p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) - err := p.ProcessIncomming(testHandler) + p, err := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + assert.NoError(t, err, "We expect no error while creating the PCICClient") + err = p.ProcessIncomming(testHandler) assert.Error(t, err, "We expect an error because the length too big", @@ -134,8 +141,9 @@ func TestInvalidTrailer(t *testing.T) { bufio.NewReader(strings.NewReader("0000L000000014\r\n0000starstop\r\r")), nil, ) - p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) - err := p.ProcessIncomming(testHandler) + p, err := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + assert.NoError(t, err, "We expect no error while creating the PCICClient") + err = p.ProcessIncomming(testHandler) assert.Error(t, err, "We expect an error because the trailer is invalid", @@ -146,8 +154,9 @@ func TestWithNilReader(t *testing.T) { nil, nil, ) - p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) - err := p.ProcessIncomming(testHandler) + p, err := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + assert.NoError(t, err, "We expect no error while creating the PCICClient") + err = p.ProcessIncomming(testHandler) assert.Error(t, err, "We expect an error when reader is nil", @@ -186,8 +195,9 @@ func TestReceiveWithChunk(t *testing.T) { bufio.NewReader(strings.NewReader(buffer)), nil, ) - p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) - err := p.ProcessIncomming(testHandler) + p, err := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + assert.NoError(t, err, "We expect no error while creating the PCICClient") + err = p.ProcessIncomming(testHandler) assert.NoError(t, err, "We expect no error while receiving data") assert.Equal(t, @@ -206,7 +216,8 @@ func TestReceiveWithChunk(t *testing.T) { bufio.NewReader(strings.NewReader(buffer)), nil, ) - p = pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + p, err = pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + assert.NoError(t, err, "We expect no error while creating the PCICClient") err = p.ProcessIncomming(testHandler) assert.Error(t, err, "We expect an error while receiving malformed data") @@ -220,7 +231,8 @@ func TestReceiveWithChunk(t *testing.T) { bufio.NewReader(strings.NewReader(buffer)), nil, ) - p = pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + p, err = pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + assert.NoError(t, err, "We expect no error while creating the PCICClient") err = p.ProcessIncomming(testHandler) assert.Error( t, @@ -251,7 +263,8 @@ func TestReceiveWithNewChunk(t *testing.T) { bufio.NewReader(strings.NewReader(buffer)), nil, ) - p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + p, err := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + assert.NoError(t, err, "We expect no error while creating the PCICClient") err = p.ProcessIncomming(testHandler) assert.NoError(t, err, @@ -269,8 +282,8 @@ func TestWithRealChunkData(t *testing.T) { bufio.NewReader(cr), nil, ) - p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) - + p, err := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + assert.NoError(t, err, "We expect no error while creating the PCICClient") for { err := p.ProcessIncomming(testHandler) if errors.Is(err, io.EOF) { @@ -296,10 +309,11 @@ func TestWithMalformedErrorData(t *testing.T) { bufio.NewReader(strings.NewReader(buffer)), nil, ) - p := pcic.NewPCICClient( + p, err := pcic.NewPCICClient( pcic.WithBufioReaderWriter(readerWriter), ) - err := p.ProcessIncomming(testHandler) + assert.NoError(t, err, "We expect no error while creating the PCICClient") + err = p.ProcessIncomming(testHandler) assert.Error(t, err, "We expect an error while receiving malformed data", @@ -315,10 +329,11 @@ func TestWithErrorData(t *testing.T) { bufio.NewReader(strings.NewReader(buffer)), nil, ) - p := pcic.NewPCICClient( + p, err := pcic.NewPCICClient( pcic.WithBufioReaderWriter(readerWriter), ) - err := p.ProcessIncomming(testHandler) + assert.NoError(t, err, "We expect no error while creating the PCICClient") + err = p.ProcessIncomming(testHandler) assert.NoError(t, err, "We expect no error while receiving data", @@ -335,7 +350,8 @@ func TestWithRealErrorData(t *testing.T) { bufio.NewReader(cr), nil, ) - p := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + p, err := pcic.NewPCICClient(pcic.WithBufioReaderWriter(readerWriter)) + assert.NoError(t, err, "We expect no error while creating the PCICClient") for { err := p.ProcessIncomming(testHandler) if errors.Is(err, io.EOF) { From 3b1c54d1cc2c9ad82808e20f4da748c5ccced13c Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Fri, 26 Jan 2024 13:37:41 +0100 Subject: [PATCH 32/33] feat: add a bare bones pcic command This just creates a PCIC connection and print some minimalistic statistic information like the framecount Signed-off-by: Christian Ege --- cmd/ovp8xx/cmd/helper.go | 15 +++++++- cmd/ovp8xx/cmd/pcic.go | 81 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 cmd/ovp8xx/cmd/pcic.go diff --git a/cmd/ovp8xx/cmd/helper.go b/cmd/ovp8xx/cmd/helper.go index a6f9877..5376563 100644 --- a/cmd/ovp8xx/cmd/helper.go +++ b/cmd/ovp8xx/cmd/helper.go @@ -11,6 +11,7 @@ import ( type helperConfig struct { pretty bool host string + port uint16 pointers []string } @@ -39,12 +40,17 @@ func (c *helperConfig) jsonPointers() []string { return c.pointers } +func (c *helperConfig) remotePort() uint16 { + return c.port +} + func NewHelper(cmd *cobra.Command) (helperConfig, error) { var conf = helperConfig{} var err error conf.pretty, err = cmd.Flags().GetBool("pretty") if err != nil { - return conf, err + // In case no pretty flag is set, we default to false + conf.pretty = false } conf.host, err = rootCmd.PersistentFlags().GetString("ip") if err != nil { @@ -52,5 +58,12 @@ func NewHelper(cmd *cobra.Command) (helperConfig, error) { } // Pointers can be empty conf.pointers, _ = cmd.Flags().GetStringSlice("pointer") + + // Port can be empty + conf.port, err = cmd.Flags().GetUint16("port") + if err != nil { + conf.port = 50010 + } + return conf, nil } diff --git a/cmd/ovp8xx/cmd/pcic.go b/cmd/ovp8xx/cmd/pcic.go new file mode 100644 index 0000000..dc35075 --- /dev/null +++ b/cmd/ovp8xx/cmd/pcic.go @@ -0,0 +1,81 @@ +/* +Copyright © 2024 Christian Ege +*/ +package cmd + +import ( + "fmt" + + "github.com/graugans/go-ovp8xx/pkg/pcic" + "github.com/spf13/cobra" +) + +// PCICReceiver represents a receiver for PCIC data. +type PCICReceiver struct { + frame pcic.Frame // The PCIC frame. + notificationMsg pcic.NotificationMessage // The notification message. + errorMsg pcic.ErrorMessage // The error message. + framecount int64 // The count of frames received. +} + +// Result is a method of the PCICReceiver struct that sets the received frame and increments the framecount. +// It takes a pcic.Frame as a parameter. +func (r *PCICReceiver) Result(frame pcic.Frame) { + r.frame = frame + fmt.Printf("Framecount: %d\n", r.framecount) + r.framecount++ +} + +// Error handles the error message received from the PCIC. +// It sets the errorMsg field of the PCICReceiver struct and prints the error message. +func (r *PCICReceiver) Error(msg pcic.ErrorMessage) { + r.errorMsg = msg + fmt.Printf("Error: %v\n", msg) +} + +// Notification is a method of the PCICReceiver type that handles incoming notification messages. +// It updates the notificationMsg field of the receiver and prints the message to the console. +func (r *PCICReceiver) Notification(msg pcic.NotificationMessage) { + r.notificationMsg = msg + fmt.Printf("Notification: %v\n", msg) +} + +// pcicCommand is a function that handles the execution of the "pcic" command. +// It initializes a PCICReceiver, creates a helper, and establishes a connection to the PCIC client. +// It then continuously processes incoming data using the PCIC client and the testHandler. +// If an error occurs during any of these steps, it is returned. +// Returns nil if the function completes successfully. +func pcicCommand(cmd *cobra.Command, args []string) error { + var testHandler *PCICReceiver = &PCICReceiver{} + var err error + helper, err := NewHelper(cmd) + if err != nil { + return err + } + + pcic, err := pcic.NewPCICClient( + pcic.WithTCPClient(helper.hostname(), helper.remotePort()), + ) + if err != nil { + return err + } + for { + err := pcic.ProcessIncomming(testHandler) + if err != nil { + return err + } + } + return nil +} + +// pcicCmd represents the pcic command +var pcicCmd = &cobra.Command{ + Use: "pcic", + Short: "Create a PCIC connection to the device", + RunE: pcicCommand, +} + +func init() { + rootCmd.AddCommand(pcicCmd) + pcicCmd.Flags().Uint16("port", 50010, "The port to connect to") +} From 23c34d9bf7423f9bfb3e3418225737438f9da00f Mon Sep 17 00:00:00 2001 From: Christian Ege Date: Fri, 26 Jan 2024 13:42:16 +0100 Subject: [PATCH 33/33] =?UTF-8?q?ci:=20=F0=9F=92=9A=20fix=20a=20linter=20e?= =?UTF-8?q?rror?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Christian Ege --- cmd/ovp8xx/cmd/pcic.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/ovp8xx/cmd/pcic.go b/cmd/ovp8xx/cmd/pcic.go index dc35075..3ad989c 100644 --- a/cmd/ovp8xx/cmd/pcic.go +++ b/cmd/ovp8xx/cmd/pcic.go @@ -60,12 +60,13 @@ func pcicCommand(cmd *cobra.Command, args []string) error { return err } for { - err := pcic.ProcessIncomming(testHandler) + err = pcic.ProcessIncomming(testHandler) if err != nil { - return err + // An error occured, we break the loop + break } } - return nil + return err } // pcicCmd represents the pcic command