diff --git a/encode.go b/encode.go index 6cb5236..30549f4 100644 --- a/encode.go +++ b/encode.go @@ -1,6 +1,7 @@ package gpp import ( + "errors" "fmt" "github.com/prebid/go-gpp/constants" "github.com/prebid/go-gpp/util" @@ -15,7 +16,7 @@ const ( ) var ( - duplicatedSectionErr = fmt.Errorf("duplicated sections") + duplicatedSectionErr = errors.New("duplicated sections") ) func Encode(sections []Section) (string, error) { @@ -53,8 +54,8 @@ func Encode(sections []Section) (string, error) { builder.Write(bs.Base64Encode()) for _, sec := range sections { - // TODO: add a parameter to decide whether a GPC segment should be included. builder.WriteByte('~') + // By default, GPP is included. builder.Write(sec.Encode(true)) } diff --git a/encode_test.go b/encode_test.go index 877d8ce..4e30b61 100644 --- a/encode_test.go +++ b/encode_test.go @@ -23,31 +23,32 @@ var testData = []gppEncodeTestData{ { description: "USPCA GPP string encoding", expected: "DBABh4A~BlgWEYCY.QA~BSFgmiU", - sections: []Section{uspca.USPCA{ - CoreSegment: uspca.USPCACoreSegment{ - Version: 1, - SaleOptOutNotice: 2, - SharingOptOutNotice: 1, - SensitiveDataLimitUseNotice: 1, - SaleOptOut: 2, - SharingOptOut: 0, - SensitiveDataProcessing: []byte{ - 0, 1, 1, 2, 0, 1, 0, 1, 2, + sections: []Section{ + uspca.USPCA{ + CoreSegment: uspca.USPCACoreSegment{ + Version: 1, + SaleOptOutNotice: 2, + SharingOptOutNotice: 1, + SensitiveDataLimitUseNotice: 1, + SaleOptOut: 2, + SharingOptOut: 0, + SensitiveDataProcessing: []byte{ + 0, 1, 1, 2, 0, 1, 0, 1, 2, + }, + KnownChildSensitiveDataConsents: []byte{ + 0, 0, + }, + PersonalDataConsents: 0, + MspaCoveredTransaction: 2, + MspaOptOutOptionMode: 1, + MspaServiceProviderMode: 2, }, - KnownChildSensitiveDataConsents: []byte{ - 0, 0, + GPCSegment: sections.CommonUSGPCSegment{ + SubsectionType: 1, + Gpc: false, }, - PersonalDataConsents: 0, - MspaCoveredTransaction: 2, - MspaOptOutOptionMode: 1, - MspaServiceProviderMode: 2, - }, - GPCSegment: sections.CommonUSGPCSegment{ - SubsectionType: 1, - Gpc: false, - }, - SectionID: constants.SectionUSPCA, - Value: "BlgWEYCY.QA"}, + SectionID: constants.SectionUSPCA, + Value: "BlgWEYCY.QA"}, uspva.USPVA{ CoreSegment: sections.CommonUSCoreSegment{ Version: 1, @@ -71,138 +72,143 @@ var testData = []gppEncodeTestData{ { description: "USPVA GPP string encoding", expected: "DBABRg~bSFgmiU", - sections: []Section{uspva.USPVA{ - CoreSegment: sections.CommonUSCoreSegment{ - Version: 27, - SharingNotice: 1, - SaleOptOutNotice: 0, - TargetedAdvertisingOptOutNotice: 2, - SaleOptOut: 0, - TargetedAdvertisingOptOut: 1, - SensitiveDataProcessing: []byte{ - 1, 2, 0, 0, 2, 1, 2, 2, + sections: []Section{ + uspva.USPVA{ + CoreSegment: sections.CommonUSCoreSegment{ + Version: 27, + SharingNotice: 1, + SaleOptOutNotice: 0, + TargetedAdvertisingOptOutNotice: 2, + SaleOptOut: 0, + TargetedAdvertisingOptOut: 1, + SensitiveDataProcessing: []byte{ + 1, 2, 0, 0, 2, 1, 2, 2, + }, + KnownChildSensitiveDataConsents: []byte{0}, + MspaCoveredTransaction: 2, + MspaOptOutOptionMode: 1, + MspaServiceProviderMode: 1, }, - KnownChildSensitiveDataConsents: []byte{0}, - MspaCoveredTransaction: 2, - MspaOptOutOptionMode: 1, - MspaServiceProviderMode: 1, - }, - SectionID: constants.SectionUSPVA, - Value: "bSFgmiU"}, + SectionID: constants.SectionUSPVA, + Value: "bSFgmiU"}, }, }, { description: "USPCO GPP string encoding", expected: "DBABJg~bSFgmJQ.YA", - sections: []Section{uspco.USPCO{ - CoreSegment: sections.CommonUSCoreSegment{ - Version: 27, - SharingNotice: 1, - SaleOptOutNotice: 0, - TargetedAdvertisingOptOutNotice: 2, - SaleOptOut: 0, - TargetedAdvertisingOptOut: 1, - SensitiveDataProcessing: []byte{ - 1, 2, 0, 0, 2, 1, 2, + sections: []Section{ + uspco.USPCO{ + CoreSegment: sections.CommonUSCoreSegment{ + Version: 27, + SharingNotice: 1, + SaleOptOutNotice: 0, + TargetedAdvertisingOptOutNotice: 2, + SaleOptOut: 0, + TargetedAdvertisingOptOut: 1, + SensitiveDataProcessing: []byte{ + 1, 2, 0, 0, 2, 1, 2, + }, + KnownChildSensitiveDataConsents: []byte{0}, + MspaCoveredTransaction: 2, + MspaOptOutOptionMode: 1, + MspaServiceProviderMode: 1, }, - KnownChildSensitiveDataConsents: []byte{0}, - MspaCoveredTransaction: 2, - MspaOptOutOptionMode: 1, - MspaServiceProviderMode: 1, - }, - GPCSegment: sections.CommonUSGPCSegment{ - SubsectionType: 1, - Gpc: true, - }, - SectionID: constants.SectionUSPCO, - Value: "bSFgmJQ.YA"}, + GPCSegment: sections.CommonUSGPCSegment{ + SubsectionType: 1, + Gpc: true, + }, + SectionID: constants.SectionUSPCO, + Value: "bSFgmJQ.YA"}, }, }, { description: "USPCT GPP string encoding", expected: "DBABVg~bSFgmSZQ.YA", - sections: []Section{uspct.USPCT{ - CoreSegment: sections.CommonUSCoreSegment{ - Version: 27, - SharingNotice: 1, - SaleOptOutNotice: 0, - TargetedAdvertisingOptOutNotice: 2, - SaleOptOut: 0, - TargetedAdvertisingOptOut: 1, - SensitiveDataProcessing: []byte{ - 1, 2, 0, 0, 2, 1, 2, 1, + sections: []Section{ + uspct.USPCT{ + CoreSegment: sections.CommonUSCoreSegment{ + Version: 27, + SharingNotice: 1, + SaleOptOutNotice: 0, + TargetedAdvertisingOptOutNotice: 2, + SaleOptOut: 0, + TargetedAdvertisingOptOut: 1, + SensitiveDataProcessing: []byte{ + 1, 2, 0, 0, 2, 1, 2, 1, + }, + KnownChildSensitiveDataConsents: []byte{ + 0, 2, 1, + }, + MspaCoveredTransaction: 2, + MspaOptOutOptionMode: 1, + MspaServiceProviderMode: 1, }, - KnownChildSensitiveDataConsents: []byte{ - 0, 2, 1, + GPCSegment: sections.CommonUSGPCSegment{ + SubsectionType: 1, + Gpc: true, }, - MspaCoveredTransaction: 2, - MspaOptOutOptionMode: 1, - MspaServiceProviderMode: 1, - }, - GPCSegment: sections.CommonUSGPCSegment{ - SubsectionType: 1, - Gpc: true, - }, - SectionID: constants.SectionUSPCT, - Value: "bSFgmSZQ.YA"}, + SectionID: constants.SectionUSPCT, + Value: "bSFgmSZQ.YA"}, }, }, { description: "USPNAT GPP string encoding", expected: "DBABLA~DSJgmkoZJSA.YA", - sections: []Section{uspnat.USPNAT{ - CoreSegment: uspnat.USPNATCoreSegment{ - Version: 3, - SharingNotice: 1, - SaleOptOutNotice: 0, - SharingOptOutNotice: 2, - TargetedAdvertisingOptOutNotice: 0, - SensitiveDataProcessingOptOutNotice: 2, - SensitiveDataLimitUseNotice: 1, - SaleOptOut: 2, - SharingOptOut: 0, - TargetedAdvertisingOptOut: 0, - SensitiveDataProcessing: []byte{ - 2, 1, 2, 2, 1, 0, 2, 2, 0, 1, 2, 1, + sections: []Section{ + uspnat.USPNAT{ + CoreSegment: uspnat.USPNATCoreSegment{ + Version: 3, + SharingNotice: 1, + SaleOptOutNotice: 0, + SharingOptOutNotice: 2, + TargetedAdvertisingOptOutNotice: 0, + SensitiveDataProcessingOptOutNotice: 2, + SensitiveDataLimitUseNotice: 1, + SaleOptOut: 2, + SharingOptOut: 0, + TargetedAdvertisingOptOut: 0, + SensitiveDataProcessing: []byte{ + 2, 1, 2, 2, 1, 0, 2, 2, 0, 1, 2, 1, + }, + KnownChildSensitiveDataConsents: []byte{ + 0, 2, + }, + PersonalDataConsents: 1, + MspaCoveredTransaction: 1, + MspaOptOutOptionMode: 0, + MspaServiceProviderMode: 2, }, - KnownChildSensitiveDataConsents: []byte{ - 0, 2, + GPCSegment: sections.CommonUSGPCSegment{ + SubsectionType: 1, + Gpc: true, }, - PersonalDataConsents: 1, - MspaCoveredTransaction: 1, - MspaOptOutOptionMode: 0, - MspaServiceProviderMode: 2, - }, - GPCSegment: sections.CommonUSGPCSegment{ - SubsectionType: 1, - Gpc: true, - }, - SectionID: constants.SectionUSPNAT, - Value: "DSJgmkoZJSA.YA"}, + SectionID: constants.SectionUSPNAT, + Value: "DSJgmkoZJSA.YA"}, }, }, { description: "USPUT GPP string encoding", expected: "DBADLO8~BSJgmkoZJSA.YA~BSFgmiU~BWJYJllA~BSFgmSZQ.YA", - sections: []Section{usput.USPUT{ - CoreSegment: usput.USPUTCoreSegment{ - Version: 1, - SharingNotice: 1, - SaleOptOutNotice: 1, - TargetedAdvertisingOptOutNotice: 2, - SensitiveDataProcessingOptOutNotice: 0, - SaleOptOut: 2, - TargetedAdvertisingOptOut: 1, - SensitiveDataProcessing: []byte{ - 1, 2, 0, 0, 2, 1, 2, 1, + sections: []Section{ + usput.USPUT{ + CoreSegment: usput.USPUTCoreSegment{ + Version: 1, + SharingNotice: 1, + SaleOptOutNotice: 1, + TargetedAdvertisingOptOutNotice: 2, + SensitiveDataProcessingOptOutNotice: 0, + SaleOptOut: 2, + TargetedAdvertisingOptOut: 1, + SensitiveDataProcessing: []byte{ + 1, 2, 0, 0, 2, 1, 2, 1, + }, + KnownChildSensitiveDataConsents: 1, + MspaCoveredTransaction: 2, + MspaOptOutOptionMode: 1, + MspaServiceProviderMode: 1, }, - KnownChildSensitiveDataConsents: 1, - MspaCoveredTransaction: 2, - MspaOptOutOptionMode: 1, - MspaServiceProviderMode: 1, - }, - SectionID: constants.SectionUSPUT, - Value: "BWJYJllA"}, + SectionID: constants.SectionUSPUT, + Value: "BWJYJllA"}, uspnat.USPNAT{ CoreSegment: uspnat.USPNATCoreSegment{ Version: 1, diff --git a/util/encoding.go b/util/encoding.go index f056eb8..5b9ec01 100644 --- a/util/encoding.go +++ b/util/encoding.go @@ -2,13 +2,13 @@ package util import ( "encoding/base64" - "fmt" + "errors" "unsafe" ) var ( - fibEncodeNumOutOfRangeErr = fmt.Errorf("the number to be encoded is out of range") - fibEncodeInvalidRange = fmt.Errorf("the range is invalid") + fibEncodeNumOutOfRangeErr = errors.New("the number to be encoded is out of range") + fibEncodeInvalidRange = errors.New("the range is invalid") ) func getByteSlice() []byte { diff --git a/util/encoding_test.go b/util/encoding_test.go index af5e344..f3c445f 100644 --- a/util/encoding_test.go +++ b/util/encoding_test.go @@ -1,163 +1,328 @@ package util import ( - "fmt" "github.com/stretchr/testify/assert" - "math/rand" "testing" - "time" ) -type encodingTestDefinition struct { - description string - data []byte -} - -func writeNBits(bs *BitStream, data []byte, pointer, n int) (newPointer int) { - var dataToWrite uint16 +func prepareNBits(data []byte, pointer, n int) (dataToWrite uint16) { offset := 0 for i := pointer + n - 1; i >= pointer; i-- { dataToWrite |= uint16(data[i/8]>>(7-i%8)) & 0x0001 << offset offset++ } - - switch n { - case 1: - bs.WriteByte1(byte(dataToWrite)) - case 2: - bs.WriteByte2(byte(dataToWrite)) - case 4: - bs.WriteByte4(byte(dataToWrite)) - case 6: - bs.WriteByte6(byte(dataToWrite)) - case 8: - bs.WriteByte8(byte(dataToWrite)) - case 12: - bs.WriteUInt12(dataToWrite) - case 16: - bs.WriteUInt16(dataToWrite) - } - return pointer + n -} - -var testCases = []*encodingTestDefinition{ - { - "Test 1", - []byte("too young too simple sometimes naive"), - }, - { - "Test 2", - []byte("I shall dedicate myself to the interests of the country in life and death"), - }, - { - "Test 3", - testData, - }, + return dataToWrite } func TestWriteByte1(t *testing.T) { - for no, c := range testCases { - bs := NewBitStream(nil) - p := 0 - for p < len(c.data)*8 { - p = writeNBits(bs, c.data, p, 1) - } - assert.Equal(t, c.data, bs.b, fmt.Sprintf("test case %d [%s] failed", no, c.description)) + testCases := []struct { + name string + data []byte + }{ + { + name: "single_byte", + data: []byte("a"), + }, + { + name: "bytes_string", + data: []byte("too young too simple sometimes naive"), + }, + { + name: "bytes_unicode", + data: []byte{0x04, 0xa2, 0x03, 0xb1, 0x00, 0x2b}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + bs := NewBitStream(nil) + p := 0 + for p < len(tc.data)*8 { + dataToWrite := prepareNBits(tc.data, p, 1) + bs.WriteByte1(byte(dataToWrite)) + p += 1 + } + assert.Equal(t, tc.data, bs.b) + }) } } func TestWriteByte2(t *testing.T) { - for no, c := range testCases { - bs := NewBitStream(nil) - p := 0 - for p < len(c.data)*8 { - p = writeNBits(bs, c.data, p, 2) - } - assert.Equal(t, c.data, bs.b, fmt.Sprintf("test case %d [%s] failed", no, c.description)) + testCases := []struct { + name string + data []byte + }{ + { + name: "single_byte", + data: []byte("a"), + }, + { + name: "bytes_string", + data: []byte("too young too simple sometimes naive"), + }, + { + name: "bytes_unicode", + data: []byte{0x04, 0xa2, 0x03, 0xb1, 0x00, 0x2b}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + bs := NewBitStream(nil) + p := 0 + for p < len(tc.data)*8 { + dataToWrite := prepareNBits(tc.data, p, 2) + bs.WriteByte2(byte(dataToWrite)) + p += 2 + } + assert.Equal(t, tc.data, bs.b) + }) } } func TestWriteByte4(t *testing.T) { - for no, c := range testCases { - bs := NewBitStream(nil) - p := 0 - for p < len(c.data)*8 { - p = writeNBits(bs, c.data, p, 4) - } - assert.Equal(t, c.data, bs.b, fmt.Sprintf("test case %d [%s] failed", no, c.description)) + testCases := []struct { + name string + data []byte + }{ + { + name: "nil_bytes", + data: nil, + }, + { + name: "single_byte", + data: []byte{'a'}, + }, + { + name: "bytes_string", + data: []byte("too young too simple sometimes naive"), + }, + { + name: "bytes_unicode", + data: []byte{0x04, 0xa2, 0x03, 0xb1, 0x00, 0x2b}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + bs := NewBitStream(nil) + p := 0 + for p < len(tc.data)*8 { + dataToWrite := prepareNBits(tc.data, p, 4) + bs.WriteByte4(byte(dataToWrite)) + p += 4 + } + assert.Equal(t, tc.data, bs.b) + }) } } func TestWriteByte6(t *testing.T) { - for no, c := range testCases { - bs := NewBitStream(nil) - // Cut the data so that the number of bits of the data is a multiple of 6. - // The least common multiple of 6 and 8 is 24, which is 3 bytes. - c.data = c.data[:len(c.data)/3*3] - p := 0 - for p < len(c.data)*8 { - p = writeNBits(bs, c.data, p, 6) - } - assert.Equal(t, c.data, bs.b, fmt.Sprintf("test case %d [%s] failed", no, c.description)) + testCases := []struct { + name string + data []byte + }{ + { + name: "nil_bytes", + data: nil, + }, + { + name: "triple_bytes", + data: []byte{'a', 'a', 'a'}, + }, + { + name: "bytes_string", + data: []byte("too young too simple sometimes naive"), + }, + { + name: "bytes_unicode", + data: []byte{0x04, 0xa2, 0x03, 0xb1, 0x00, 0x2b}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + bs := NewBitStream(nil) + p := 0 + for p < len(tc.data)*8 { + dataToWrite := prepareNBits(tc.data, p, 6) + bs.WriteByte6(byte(dataToWrite)) + p += 6 + } + assert.Equal(t, tc.data, bs.b) + }) } } func TestWriteByte8(t *testing.T) { - for no, c := range testCases { - bs := NewBitStream(nil) - p := 0 - for p < len(c.data)*8 { - p = writeNBits(bs, c.data, p, 8) - } - assert.Equal(t, c.data, bs.b, fmt.Sprintf("test case %d [%s] failed", no, c.description)) + testCases := []struct { + name string + data []byte + }{ + { + name: "nil_bytes", + data: nil, + }, + { + name: "single_byte", + data: []byte{'a'}, + }, + { + name: "bytes_string", + data: []byte("too young too simple sometimes naive"), + }, + { + name: "bytes_unicode", + data: []byte{0x04, 0xa2, 0x03, 0xb1, 0x00, 0x2b}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + bs := NewBitStream(nil) + p := 0 + for p < len(tc.data)*8 { + dataToWrite := prepareNBits(tc.data, p, 8) + bs.WriteByte8(byte(dataToWrite)) + p += 8 + } + assert.Equal(t, tc.data, bs.b) + }) } } func TestWriteUInt12(t *testing.T) { - for no, c := range testCases { - bs := NewBitStream(nil) - c.data = c.data[:len(c.data)/3*3] // the least common multiple of 12 and 8 is 24, which is 3 bytes. - p := 0 - for p < len(c.data)*8 { - p = writeNBits(bs, c.data, p, 12) - } - assert.Equal(t, c.data, bs.b, fmt.Sprintf("test case %d [%s] failed", no, c.description)) + testCases := []struct { + name string + data []byte + }{ + { + name: "triple_bytes", + data: []byte{'a', 'a', 'a'}, + }, + { + name: "bytes_string", + data: []byte("too young too simple sometimes naive"), + }, + { + name: "bytes_unicode", + data: []byte{0x04, 0xa2, 0x03, 0xb1, 0x00, 0x2b}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + bs := NewBitStream(nil) + p := 0 + for p < len(tc.data)*8 { + dataToWrite := prepareNBits(tc.data, p, 12) + bs.WriteUInt12(dataToWrite) + p += 12 + } + assert.Equal(t, tc.data, bs.b) + }) } } func TestWriteUInt16(t *testing.T) { - for no, c := range testCases { - bs := NewBitStream(nil) - c.data = c.data[:len(c.data)/2*2] // the least common multiple of 16 and 8 is 16, which is 2 bytes. - p := 0 - for p < len(c.data)*8 { - p = writeNBits(bs, c.data, p, 16) - } - assert.Equal(t, c.data, bs.b, fmt.Sprintf("test case %d [%s] failed", no, c.description)) + testCases := []struct { + name string + data []byte + }{ + { + name: "double_bytes", + data: []byte{'a', 'a'}, + }, + { + name: "bytes_string", + data: []byte("too young too simple sometimes naive"), + }, + { + name: "bytes_unicode", + data: []byte{0x04, 0xa2, 0x03, 0xb1, 0x00, 0x2b}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + bs := NewBitStream(nil) + p := 0 + for p < len(tc.data)*8 { + dataToWrite := prepareNBits(tc.data, p, 16) + bs.WriteUInt16(dataToWrite) + p += 16 + } + assert.Equal(t, tc.data, bs.b) + }) } } func TestArbitraryWrite(t *testing.T) { - choices := []int{1, 2, 4, 6, 8, 12, 16} - rand.Seed(time.Now().UnixNano()) - for no, c := range testCases { - bs := NewBitStream(nil) - p := 0 - N := len(c.data) * 8 - for p < N { - rest := N - p - for choices[len(choices)-1] > rest { - choices = choices[:len(choices)-1] + testCases := []*struct { + name string + data []byte + offsets []int + }{ + { + name: "arbitrary_write_1", + data: []byte{0xde, 0xad, 0xbe, 0xef}, + offsets: []int{16, 12, 4}, + }, + { + name: "arbitrary_write_2", + data: []byte{0xde, 0xad, 0xbe, 0xef}, + offsets: []int{12, 12, 8}, + }, + { + name: "arbitrary_write_3", + data: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0b01000000}, + offsets: []int{16, 12, 4, 8, 6, 6, 6, 1, 4, 1, 2}, + }, + { + name: "arbitrary_write_4", + data: []byte{0x81, 0x09, 0x75}, + offsets: []int{1, 4, 2, 4, 6, 2, 4, 1}, + }, + { + name: "arbitrary_write_string", + data: []byte("GPP framework"), + offsets: []int{16, 16, 8, 8, 8, 4, 4, 4, 4, 6, 6, 6, 6, 4, 2, 1, 1}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + bs := NewBitStream(nil) + p := 0 + for _, offset := range tc.offsets { + dataToWrite := prepareNBits(tc.data, p, offset) + switch offset { + case 1: + bs.WriteByte1(byte(dataToWrite)) + case 2: + bs.WriteByte2(byte(dataToWrite)) + case 4: + bs.WriteByte4(byte(dataToWrite)) + case 6: + bs.WriteByte6(byte(dataToWrite)) + case 8: + bs.WriteByte8(byte(dataToWrite)) + case 12: + bs.WriteUInt12(dataToWrite) + case 16: + bs.WriteUInt16(dataToWrite) + } + p += offset } - p = writeNBits(bs, c.data, p, choices[rand.Intn(len(choices))]) - } - assert.Equal(t, c.data, bs.b, fmt.Sprintf("test case %d [%s] failed", no, c.description)) + assert.Equal(t, tc.data, bs.b) + }) } } func TestWriteFibonacciInt(t *testing.T) { var fibWriteTestData = []*struct { + name string values []uint16 result []byte err error @@ -169,67 +334,76 @@ func TestWriteFibonacciInt(t *testing.T) { // 7 = 01011 // 12 = 101011 // 6764 = 0101 0101 0101 0101 011 - {[]uint16{1}, []byte{0xc0}, nil}, - {[]uint16{2}, []byte{0x60}, nil}, - {[]uint16{4}, []byte{0xb0}, nil}, - {[]uint16{5}, []byte{0x18}, nil}, - {[]uint16{7, 12}, []byte{0x5d, 0x60}, nil}, - {[]uint16{0, 6765}, nil, fibEncodeNumOutOfRangeErr}, - {[]uint16{6764}, []byte{0x55, 0x55, 0x60}, nil}, + {"write_int_1", []uint16{1}, []byte{0xc0}, nil}, + {"write_int_2", []uint16{2}, []byte{0x60}, nil}, + {"write_int_4", []uint16{4}, []byte{0xb0}, nil}, + {"write_int_5", []uint16{5}, []byte{0x18}, nil}, + {"write_int_7_12", []uint16{7, 12}, []byte{0x5d, 0x60}, nil}, + {"out_of_range", []uint16{0, 6765}, nil, fibEncodeNumOutOfRangeErr}, + {"write_int_6764", []uint16{6764}, []byte{0x55, 0x55, 0x60}, nil}, } for _, test := range fibWriteTestData { - bs := NewBitStream(nil) - var err error - for _, v := range test.values { - err = bs.WriteFibonacciInt(v) - assert.Equal(t, test.err, err) - } - assert.Equal(t, test.result, bs.b) + t.Run(test.name, func(t *testing.T) { + bs := NewBitStream(nil) + var err error + for _, v := range test.values { + err = bs.WriteFibonacciInt(v) + assert.Equal(t, test.err, err) + } + assert.Equal(t, test.result, bs.b) + }) } } func TestWriteIntRange(t *testing.T) { - cases := []*struct { + testCases := []*struct { + name string r []IRange result []byte err error }{ { + "write_int_range_1", []IRange{{2, 2}, {4, 4}, {6, 9}}, []byte{0b00000000, 0b00110011, 0b00111011, 0b00110000}, nil, }, { + "write_int_range_2", []IRange{{2, 5}, {6, 8}, {13, 16}, {28, 39}}, []byte{0b00000000, 0b01001011, 0b00111110, 0b11100011, 0b00111101, 0b01100101, 0b10000000}, nil, }, { + "write_invalid_range_1", []IRange{{2, 1}, {4, 4}, {6, 9}}, - nil, + []byte{0b00000000, 0b00110000}, // Only the size will be successfully written fibEncodeInvalidRange, }, { + "write_invalid_range_2", []IRange{{2, 2}, {2, 4}, {6, 9}}, - nil, + // 0000 0000 0011 for size, 0 for single-int-range indicator and 011 for fibonacci encoded 2 + []byte{0b00000000, 0b00110011}, fibEncodeInvalidRange, }, { + "write_out_of_range", []IRange{{2, 5}, {6, 8}, {13, 16}, {28, 9999}}, - nil, + // error encountered when try writing the last range + []byte{0b00000000, 0b01001011, 0b00111110, 0b11100011, 0b00111101, 0b01100000}, fibEncodeNumOutOfRangeErr, }, } - var err error - for _, c := range cases { - bs := NewBitStream(nil) - intRange := IntRange{Range: c.r, Size: uint16(len(c.r))} - err = bs.WriteIntRange(&intRange) - assert.Equal(t, c.err, err) - if err == nil { - assert.Equal(t, c.result, bs.b) - } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + bs := NewBitStream(nil) + intRange := IntRange{Range: tc.r, Size: uint16(len(tc.r))} + err := bs.WriteIntRange(&intRange) + assert.Equal(t, tc.result, bs.b) + assert.Equal(t, tc.err, err) + }) } }