1515package cbor
1616
1717import (
18+ "encoding/hex"
19+ "encoding/json"
1820 "fmt"
21+ "sort"
22+ "strings"
1923)
2024
2125// Helpful wrapper for parsing arbitrary CBOR data which may contain types that
2226// cannot be easily represented in Go (such as maps with bytestring keys)
2327type Value struct {
24- Value interface {}
28+ value interface {}
2529 // We store this as a string so that the type is still hashable for use as map keys
2630 cborData string
2731}
2832
29- func (v * Value ) UnmarshalCBOR (data []byte ) ( err error ) {
33+ func (v * Value ) UnmarshalCBOR (data []byte ) error {
3034 // Save the original CBOR
3135 v .cborData = string (data [:])
3236 cborType := data [0 ] & CBOR_TYPE_MASK
3337 switch cborType {
3438 case CBOR_TYPE_MAP :
35- // There are certain types that cannot be used as map keys in Go but are valid in CBOR. Trying to
36- // parse CBOR containing a map with keys of one of those types will cause a panic. We setup this
37- // deferred function to recover from a possible panic and return an error
38- defer func () {
39- if r := recover (); r != nil {
40- err = fmt .Errorf ("decode failure, probably due to type unsupported by Go: %v" , r )
41- }
42- }()
43- tmpValue := map [Value ]Value {}
44- if _ , err := Decode (data , & tmpValue ); err != nil {
45- return err
46- }
47- // Extract actual value from each child value
48- newValue := map [interface {}]interface {}{}
49- for key , value := range tmpValue {
50- newValue [key .Value ] = value .Value
51- }
52- v .Value = newValue
39+ return v .processMap (data )
5340 case CBOR_TYPE_ARRAY :
54- tmpValue := []Value {}
55- if _ , err := Decode (data , & tmpValue ); err != nil {
56- return err
57- }
58- // Extract actual value from each child value
59- newValue := []interface {}{}
60- for _ , value := range tmpValue {
61- newValue = append (newValue , value .Value )
62- }
63- v .Value = newValue
41+ return v .processArray (data )
6442 case CBOR_TYPE_TEXT_STRING :
6543 var tmpValue string
6644 if _ , err := Decode (data , & tmpValue ); err != nil {
6745 return err
6846 }
69- v .Value = tmpValue
47+ v .value = tmpValue
7048 case CBOR_TYPE_BYTE_STRING :
7149 // Use our custom type which stores the bytestring in a way that allows it to be used as a map key
7250 var tmpValue ByteString
7351 if _ , err := Decode (data , & tmpValue ); err != nil {
7452 return err
7553 }
76- v .Value = tmpValue
54+ v .value = tmpValue
7755 case CBOR_TYPE_TAG :
7856 // Parse as a raw tag to get number and nested CBOR data
7957 tmpTag := RawTag {}
8058 if _ , err := Decode (data , & tmpTag ); err != nil {
8159 return err
8260 }
83- // Parse the tag value via our custom Value object to handle problem types
84- tmpValue := Value {}
85- if _ , err := Decode (tmpTag .Content , & tmpValue ); err != nil {
86- return err
87- }
88- // Create new tag object with decoded value
89- newValue := Tag {
90- Number : tmpTag .Number ,
91- Content : tmpValue .Value ,
61+ switch tmpTag .Number {
62+ case CborTagSet :
63+ return v .processArray (tmpTag .Content )
64+ case CborTagMap :
65+ return v .processMap (tmpTag .Content )
66+ default :
67+ // Parse the tag value via our custom Value object to handle problem types
68+ tmpValue := Value {}
69+ if _ , err := Decode (tmpTag .Content , & tmpValue ); err != nil {
70+ return err
71+ }
72+ // These tag numbers correspond to the Enumerated Alternative Data Items notable CBOR tags. These
73+ // are often used in Plutus script datums
74+ // https://www.ietf.org/archive/id/draft-bormann-cbor-notable-tags-07.html#name-enumerated-alternative-data
75+ if tmpTag .Number >= 121 && tmpTag .Number <= 127 {
76+ // Alternatives 0-6
77+ v .value = Constructor {
78+ constructor : uint (tmpTag .Number - 121 ),
79+ value : & tmpValue ,
80+ }
81+ } else if tmpTag .Number >= 1280 && tmpTag .Number <= 1400 {
82+ // Alternatives 7-127
83+ v .value = Constructor {
84+ constructor : uint (tmpTag .Number - 1280 + 7 ),
85+ value : & tmpValue ,
86+ }
87+ } else if tmpTag .Number == 101 {
88+ // Alternatives 128+
89+ newValue := Value {
90+ value : tmpValue .Value ().([]interface {})[1 ],
91+ }
92+ v .value = Constructor {
93+ constructor : tmpValue .Value ().([]interface {})[0 ].(uint ),
94+ value : & newValue ,
95+ }
96+ } else {
97+ return fmt .Errorf ("unsupported CBOR tag: %d" , tmpTag .Number )
98+ }
9299 }
93- v .Value = newValue
94100 default :
95101 var tmpValue interface {}
96102 if _ , err := Decode (data , & tmpValue ); err != nil {
97103 return err
98104 }
99- v .Value = tmpValue
105+ v .value = tmpValue
100106 }
101107 return nil
102108}
@@ -105,19 +111,179 @@ func (v Value) Cbor() []byte {
105111 return []byte (v .cborData )
106112}
107113
114+ func (v Value ) Value () interface {} {
115+ return v .value
116+ }
117+
118+ func (v Value ) MarshalJSON () ([]byte , error ) {
119+ var tmpJson string
120+ if v .value != nil {
121+ astJson , err := generateAstJson (v .value )
122+ if err != nil {
123+ return nil , err
124+ }
125+ tmpJson = fmt .Sprintf (
126+ `{"cbor":"%s","json":%s}` ,
127+ hex .EncodeToString ([]byte (v .cborData )),
128+ astJson ,
129+ )
130+ } else {
131+ tmpJson = fmt .Sprintf (
132+ `{"cbor":"%s"}` ,
133+ hex .EncodeToString ([]byte (v .cborData )),
134+ )
135+ }
136+ return []byte (tmpJson ), nil
137+ }
138+
139+ func (v * Value ) processMap (data []byte ) (err error ) {
140+ // There are certain types that cannot be used as map keys in Go but are valid in CBOR. Trying to
141+ // parse CBOR containing a map with keys of one of those types will cause a panic. We setup this
142+ // deferred function to recover from a possible panic and return an error
143+ defer func () {
144+ if r := recover (); r != nil {
145+ err = fmt .Errorf ("decode failure, probably due to type unsupported by Go: %v" , r )
146+ }
147+ }()
148+ tmpValue := map [Value ]Value {}
149+ if _ , err = Decode (data , & tmpValue ); err != nil {
150+ return err
151+ }
152+ // Extract actual value from each child value
153+ newValue := map [interface {}]interface {}{}
154+ for key , value := range tmpValue {
155+ newValue [key .Value ()] = value .Value ()
156+ }
157+ v .value = newValue
158+ return nil
159+ }
160+
161+ func (v * Value ) processArray (data []byte ) error {
162+ tmpValue := []Value {}
163+ if _ , err := Decode (data , & tmpValue ); err != nil {
164+ return err
165+ }
166+ // Extract actual value from each child value
167+ newValue := []interface {}{}
168+ for _ , value := range tmpValue {
169+ newValue = append (newValue , value .Value ())
170+ }
171+ v .value = newValue
172+ return nil
173+ }
174+
175+ func generateAstJson (obj interface {}) ([]byte , error ) {
176+ tmpJsonObj := map [string ]interface {}{}
177+ switch v := obj .(type ) {
178+ case ByteString :
179+ tmpJsonObj ["bytes" ] = hex .EncodeToString (v .Bytes ())
180+ case []interface {}:
181+ tmpJson := `{"list":[`
182+ for idx , val := range v {
183+ tmpVal , err := generateAstJson (val )
184+ if err != nil {
185+ return nil , err
186+ }
187+ tmpJson += string (tmpVal )
188+ if idx != (len (v ) - 1 ) {
189+ tmpJson += `,`
190+ }
191+ }
192+ tmpJson += `]}`
193+ return []byte (tmpJson ), nil
194+ case map [interface {}]interface {}:
195+ tmpItems := []string {}
196+ for key , val := range v {
197+ keyAstJson , err := generateAstJson (key )
198+ if err != nil {
199+ return nil , err
200+ }
201+ valAstJson , err := generateAstJson (val )
202+ if err != nil {
203+ return nil , err
204+ }
205+ tmpJson := fmt .Sprintf (
206+ `{"k":%s,"v":%s}` ,
207+ keyAstJson ,
208+ valAstJson ,
209+ )
210+ tmpItems = append (tmpItems , string (tmpJson ))
211+ }
212+ // We naively sort the rendered map items to give consistent ordering
213+ sort .Strings (tmpItems )
214+ tmpJson := fmt .Sprintf (
215+ `{"map":[%s]}` ,
216+ strings .Join (tmpItems , "," ),
217+ )
218+ return []byte (tmpJson ), nil
219+ case Constructor :
220+ return json .Marshal (obj )
221+ case int , uint , uint64 , int64 :
222+ tmpJsonObj ["int" ] = v
223+ case bool :
224+ tmpJsonObj ["bool" ] = v
225+ case string :
226+ tmpJsonObj ["string" ] = v
227+ default :
228+ return nil , fmt .Errorf ("unknown data type (%T) for value: %#v" , obj , obj )
229+ }
230+ return json .Marshal (& tmpJsonObj )
231+ }
232+
233+ type Constructor struct {
234+ constructor uint
235+ value * Value
236+ }
237+
238+ func (v Constructor ) MarshalJSON () ([]byte , error ) {
239+ tmpJson := fmt .Sprintf (`{"constructor":%d,"fields":[` , v .constructor )
240+ tmpList := [][]byte {}
241+ for _ , val := range v .value .Value ().([]any ) {
242+ tmpVal , err := generateAstJson (val )
243+ if err != nil {
244+ return nil , err
245+ }
246+ tmpList = append (tmpList , tmpVal )
247+ }
248+ for idx , val := range tmpList {
249+ tmpJson += string (val )
250+ if idx != (len (tmpList ) - 1 ) {
251+ tmpJson += `,`
252+ }
253+ }
254+ tmpJson += `]}`
255+ return []byte (tmpJson ), nil
256+ }
257+
108258type LazyValue struct {
109- * Value
259+ value * Value
110260}
111261
112262func (l * LazyValue ) UnmarshalCBOR (data []byte ) error {
113- if l .Value == nil {
114- l .Value = & Value {}
263+ if l .value == nil {
264+ l .value = & Value {}
115265 }
116- l .cborData = string (data [:])
266+ l .value . cborData = string (data [:])
117267 return nil
118268}
119269
120- func (l * LazyValue ) Decode () (* Value , error ) {
121- err := l .Value .UnmarshalCBOR ([]byte (l .cborData ))
122- return l .Value , err
270+ func (l * LazyValue ) MarshalJSON () ([]byte , error ) {
271+ if l .Value () == nil {
272+ // Try to decode if we can, but don't blow up if we can't
273+ _ , _ = l .Decode ()
274+ }
275+ return l .value .MarshalJSON ()
276+ }
277+
278+ func (l * LazyValue ) Decode () (interface {}, error ) {
279+ err := l .value .UnmarshalCBOR ([]byte (l .value .cborData ))
280+ return l .Value (), err
281+ }
282+
283+ func (l * LazyValue ) Value () interface {} {
284+ return l .value .Value ()
285+ }
286+
287+ func (l * LazyValue ) Cbor () []byte {
288+ return l .value .Cbor ()
123289}
0 commit comments