Skip to content

Commit 7030e4a

Browse files
authored
fix: use correct header length for indef-length map on decode (#125)
This also adds support for encode/decode of indef-length maps Signed-off-by: Aurora Gaffney <[email protected]>
1 parent b8b8fe7 commit 7030e4a

File tree

4 files changed

+81
-27
lines changed

4 files changed

+81
-27
lines changed

data/data.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ func NewConstrDefIndef(useIndef bool, tag uint, fields ...PlutusData) PlutusData
6565
// Map
6666

6767
type Map struct {
68-
Pairs [][2]PlutusData // Each pair is [key, value]
68+
Pairs [][2]PlutusData // Each pair is [key, value]
69+
useIndef *bool
6970
}
7071

7172
func (Map) isPlutusData() {}
@@ -79,6 +80,11 @@ func NewMap(pairs [][2]PlutusData) PlutusData {
7980
return &Map{Pairs: pairs}
8081
}
8182

83+
// NewMapDefIndef creates a new Map with the ability to specify whether it should use definite- or indefinite-length encoding
84+
func NewMapDefIndef(useIndef bool, pairs [][2]PlutusData) PlutusData {
85+
return &Map{Pairs: pairs, useIndef: &useIndef}
86+
}
87+
8288
// Integer
8389

8490
type Integer struct {

data/data_test.go

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ var testDefs = []struct {
5252
CborHex: "9f0102ff",
5353
},
5454
{
55-
Data: NewMap(
55+
Data: NewMapDefIndef(
56+
false,
5657
[][2]PlutusData{
5758
{
5859
NewInteger(big.NewInt(1)),
@@ -63,7 +64,8 @@ var testDefs = []struct {
6364
CborHex: "a10102",
6465
},
6566
{
66-
Data: NewMap(
67+
Data: NewMapDefIndef(
68+
false,
6769
[][2]PlutusData{
6870
{
6971
NewConstrDefIndef(
@@ -86,7 +88,8 @@ var testDefs = []struct {
8688
Data: NewConstrDefIndef(
8789
true,
8890
0,
89-
NewMap(
91+
NewMapDefIndef(
92+
false,
9093
[][2]PlutusData{
9194
{
9295
NewConstrDefIndef(
@@ -117,15 +120,17 @@ var testDefs = []struct {
117120
CborHex: "d866821903e79f0607ff",
118121
},
119122
{
120-
Data: NewMap(
123+
Data: NewMapDefIndef(
124+
false,
121125
[][2]PlutusData{
122126
{
123127
NewListDefIndef(
124128
true,
125129
NewInteger(big.NewInt(1)),
126130
NewInteger(big.NewInt(2)),
127131
),
128-
NewMap(
132+
NewMapDefIndef(
133+
false,
129134
[][2]PlutusData{
130135
{
131136
NewListDefIndef(
@@ -148,10 +153,12 @@ var testDefs = []struct {
148153
CborHex: "a19f0102ffa19f40ffd8799f0201ff",
149154
},
150155
{
151-
Data: NewMap(
156+
Data: NewMapDefIndef(
157+
false,
152158
[][2]PlutusData{
153159
{
154-
NewMap(
160+
NewMapDefIndef(
161+
false,
155162
[][2]PlutusData{
156163
{
157164
NewInteger(big.NewInt(1)),
@@ -177,8 +184,10 @@ var testDefs = []struct {
177184
),
178185
CborHex: "d87a81d87980",
179186
},
187+
// Map with specific-order keys
180188
{
181-
Data: NewMap(
189+
Data: NewMapDefIndef(
190+
false,
182191
[][2]PlutusData{
183192
{
184193
NewInteger(big.NewInt(2)),
@@ -197,6 +206,24 @@ var testDefs = []struct {
197206
// {2:2,3:3,1:1}
198207
CborHex: "a3020203030101",
199208
},
209+
// Indef-length map
210+
{
211+
Data: NewMapDefIndef(
212+
true,
213+
[][2]PlutusData{
214+
{
215+
NewByteString([]byte{0x01}),
216+
NewInteger(big.NewInt(1)),
217+
},
218+
{
219+
NewByteString([]byte{0x02}),
220+
NewInteger(big.NewInt(2)),
221+
},
222+
},
223+
),
224+
// {_ h'01': 1, h'02': 2}
225+
CborHex: "bf410101410202ff",
226+
},
200227
}
201228

202229
func TestPlutusDataEncode(t *testing.T) {

data/decode.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,22 +103,29 @@ func decodeCborRawMap(data []byte) (any, error) {
103103
// order when decoding a map. We decode our map to determine its length, create a dummy
104104
// list the same length as our map to determine the header size, and then decode each
105105
// key/value pair individually
106+
useIndef := (data[0] & CborIndefFlag) == CborIndefFlag
106107
var tmpData map[RawMessageStr]RawMessageStr
107108
if err := cborUnmarshal(data, &tmpData); err != nil {
108109
return nil, err
109110
}
110-
// Create dummy list of same length to determine map header length
111-
tmpList := make([]bool, len(tmpData))
112-
tmpListRaw, err := cborMarshal(tmpList)
113-
if err != nil {
114-
return nil, err
111+
if useIndef {
112+
// Strip off indef-length map header byte
113+
data = data[1:]
114+
} else {
115+
// Create dummy list of same length to determine map header length
116+
tmpList := make([]bool, len(tmpData))
117+
tmpListRaw, err := cborMarshal(tmpList)
118+
if err != nil {
119+
return nil, err
120+
}
121+
tmpListHeader := tmpListRaw[0 : len(tmpListRaw)-len(tmpData)]
122+
// Strip off map header bytes
123+
data = data[len(tmpListHeader):]
115124
}
116-
tmpListHeader := tmpListRaw[0 : len(tmpListRaw)-len(tmpData)]
117-
// Strip off map header bytes
118-
data = data[len(tmpListHeader):]
119125
pairs := make([][2]PlutusData, 0, len(tmpData))
120126
var rawKey, rawVal cbor.RawMessage
121127
// Read key/value pairs until we have no data left
128+
var err error
122129
for len(data) > 0 {
123130
// Check for "break" at end of indefinite-length map
124131
if data[0] == 0xFF {
@@ -159,6 +166,7 @@ func decodeCborRawMap(data []byte) (any, error) {
159166
)
160167
}
161168
ret := NewMap(pairs)
169+
ret.(*Map).useIndef = &useIndef
162170
return ret, nil
163171
}
164172

data/encode.go

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ func encodeMap(m *Map) (any, error) {
129129
// steal and modify its header, and build our own output from pieces. This avoids
130130
// needing to support 6 different possible encodings of a map's header byte depending
131131
// on length
132+
useIndef := false
133+
if m.useIndef != nil {
134+
useIndef = *m.useIndef
135+
}
136+
// Build encoded pairs
132137
tmpPairs := make([][]byte, 0, len(m.Pairs))
133138
for _, pair := range m.Pairs {
134139
key, err := encodeToRaw(pair[0])
@@ -152,19 +157,27 @@ func encodeMap(m *Map) (any, error) {
152157
slices.Concat(keyRaw, valueRaw),
153158
)
154159
}
155-
// Create dummy list with simple (one-byte) values so we can easily extract the header
156-
tmpList := make([]bool, len(tmpPairs))
157-
tmpListRaw, err := cborMarshal(tmpList)
158-
if err != nil {
159-
return nil, err
160-
}
161-
tmpListHeader := tmpListRaw[0 : len(tmpListRaw)-len(tmpPairs)]
162-
// Modify header byte to switch type from array to map
163-
tmpListHeader[0] |= 0x20
164160
// Build return value
165161
ret := bytes.NewBuffer(nil)
166-
_, _ = ret.Write(tmpListHeader)
162+
if useIndef {
163+
ret.WriteByte(CborTypeMap | CborIndefFlag)
164+
} else {
165+
// Create dummy list with simple (one-byte) values so we can easily extract the header
166+
tmpList := make([]bool, len(tmpPairs))
167+
tmpListRaw, err := cborMarshal(tmpList)
168+
if err != nil {
169+
return nil, err
170+
}
171+
tmpListHeader := tmpListRaw[0 : len(tmpListRaw)-len(tmpPairs)]
172+
// Modify header byte to switch type from array to map
173+
tmpListHeader[0] |= 0x20
174+
_, _ = ret.Write(tmpListHeader)
175+
}
167176
_, _ = ret.Write(slices.Concat(tmpPairs...))
177+
if useIndef {
178+
// Indef-length "break" byte
179+
ret.WriteByte(0xff)
180+
}
168181
return cbor.RawMessage(ret.Bytes()), nil
169182
}
170183

0 commit comments

Comments
 (0)