github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/vm/stackitem/json.go (about)

     1  package stackitem
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	gio "io"
    10  	"math/big"
    11  	"strconv"
    12  	"strings"
    13  )
    14  
    15  // decoder is a wrapper around json.Decoder helping to mimic C# json decoder behavior.
    16  type decoder struct {
    17  	json.Decoder
    18  
    19  	count int
    20  	depth int
    21  	// bestIntPrecision denotes whether maximum allowed integer precision should
    22  	// be used to parse big.Int items. If false, then default NeoC# value will be
    23  	// used which doesn't allow to precisely parse big values. This behaviour is
    24  	// managed by the config.HFBasilisk.
    25  	bestIntPrecision bool
    26  }
    27  
    28  // MaxAllowedInteger is the maximum integer allowed to be encoded.
    29  const MaxAllowedInteger = 2<<53 - 1
    30  
    31  // MaxJSONDepth is the maximum allowed nesting level of an encoded/decoded JSON.
    32  const MaxJSONDepth = 10
    33  
    34  const (
    35  	// MaxIntegerPrec is the maximum precision allowed for big.Integer parsing.
    36  	// It allows to properly parse integer numbers that our 256-bit VM is able to
    37  	// handle.
    38  	MaxIntegerPrec = 1<<8 + 1
    39  	// CompatIntegerPrec is the maximum precision allowed for big.Integer parsing
    40  	// by the C# node before the Basilisk hardfork. It doesn't allow to precisely
    41  	// parse big numbers, see the https://github.com/neo-project/neo/issues/2879.
    42  	CompatIntegerPrec = 53
    43  )
    44  
    45  // ErrInvalidValue is returned when an item value doesn't fit some constraints
    46  // during serialization or deserialization.
    47  var ErrInvalidValue = errors.New("invalid value")
    48  
    49  // ErrTooDeep is returned when JSON encoder/decoder goes beyond MaxJSONDepth in
    50  // its processing.
    51  var ErrTooDeep = errors.New("too deep")
    52  
    53  // ToJSON encodes Item to JSON.
    54  // It behaves as following:
    55  //
    56  //	ByteArray -> base64 string
    57  //	BigInteger -> number
    58  //	Bool -> bool
    59  //	Null -> null
    60  //	Array, Struct -> array
    61  //	Map -> map with keys as UTF-8 bytes
    62  func ToJSON(item Item) ([]byte, error) {
    63  	seen := make(map[Item]sliceNoPointer, typicalNumOfItems)
    64  	return toJSON(nil, seen, item)
    65  }
    66  
    67  // sliceNoPointer represents a sub-slice of a known slice.
    68  // It doesn't contain any pointer and uses the same amount of memory as `[]byte`,
    69  // but at the same type has additional information about the number of items in
    70  // the stackitem (including the stackitem itself).
    71  type sliceNoPointer struct {
    72  	start, end int
    73  	itemsCount int
    74  }
    75  
    76  func toJSON(data []byte, seen map[Item]sliceNoPointer, item Item) ([]byte, error) {
    77  	if len(data) > MaxSize {
    78  		return nil, errTooBigSize
    79  	}
    80  
    81  	if old, ok := seen[item]; ok {
    82  		if len(data)+old.end-old.start > MaxSize {
    83  			return nil, errTooBigSize
    84  		}
    85  		return append(data, data[old.start:old.end]...), nil
    86  	}
    87  
    88  	start := len(data)
    89  	var err error
    90  
    91  	switch it := item.(type) {
    92  	case *Array, *Struct:
    93  		var items []Item
    94  		if a, ok := it.(*Array); ok {
    95  			items = a.value
    96  		} else {
    97  			items = it.(*Struct).value
    98  		}
    99  
   100  		data = append(data, '[')
   101  		for i, v := range items {
   102  			data, err = toJSON(data, seen, v)
   103  			if err != nil {
   104  				return nil, err
   105  			}
   106  			if i < len(items)-1 {
   107  				data = append(data, ',')
   108  			}
   109  		}
   110  		data = append(data, ']')
   111  		seen[item] = sliceNoPointer{start: start, end: len(data)}
   112  	case *Map:
   113  		data = append(data, '{')
   114  		for i := range it.value {
   115  			// map key can always be converted to []byte
   116  			// but are not always a valid UTF-8.
   117  			raw, err := itemToJSONString(it.value[i].Key)
   118  			if err != nil {
   119  				return nil, err
   120  			}
   121  			data = append(data, raw...)
   122  			data = append(data, ':')
   123  			data, err = toJSON(data, seen, it.value[i].Value)
   124  			if err != nil {
   125  				return nil, err
   126  			}
   127  			if i < len(it.value)-1 {
   128  				data = append(data, ',')
   129  			}
   130  		}
   131  		data = append(data, '}')
   132  		seen[item] = sliceNoPointer{start: start, end: len(data)}
   133  	case *BigInteger:
   134  		if it.Big().CmpAbs(big.NewInt(MaxAllowedInteger)) == 1 {
   135  			return nil, fmt.Errorf("%w (MaxAllowedInteger)", ErrInvalidValue)
   136  		}
   137  		data = append(data, it.Big().String()...)
   138  	case *ByteArray, *Buffer:
   139  		raw, err := itemToJSONString(it)
   140  		if err != nil {
   141  			return nil, err
   142  		}
   143  		data = append(data, raw...)
   144  	case Bool:
   145  		if it {
   146  			data = append(data, "true"...)
   147  		} else {
   148  			data = append(data, "false"...)
   149  		}
   150  	case Null:
   151  		data = append(data, "null"...)
   152  	default:
   153  		return nil, fmt.Errorf("%w: %s", ErrUnserializable, it.String())
   154  	}
   155  	if len(data) > MaxSize {
   156  		return nil, errTooBigSize
   157  	}
   158  	return data, nil
   159  }
   160  
   161  // itemToJSONString converts it to a string
   162  // in quotation marks with control characters escaped.
   163  func itemToJSONString(it Item) ([]byte, error) {
   164  	s, err := ToString(it)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  	data, _ := json.Marshal(s) // error never occurs because `ToString` checks for validity
   169  
   170  	// ref https://github.com/neo-project/neo-modules/issues/375 and https://github.com/dotnet/runtime/issues/35281
   171  	return bytes.Replace(data, []byte{'+'}, []byte("\\u002B"), -1), nil
   172  }
   173  
   174  // FromJSON decodes an Item from JSON.
   175  // It behaves as following:
   176  //
   177  //	string -> ByteArray from base64
   178  //	number -> BigInteger
   179  //	bool -> Bool
   180  //	null -> Null
   181  //	array -> Array
   182  //	map -> Map, keys are UTF-8
   183  func FromJSON(data []byte, maxCount int, bestIntPrecision bool) (Item, error) {
   184  	d := decoder{
   185  		Decoder:          *json.NewDecoder(bytes.NewReader(data)),
   186  		count:            maxCount,
   187  		bestIntPrecision: bestIntPrecision,
   188  	}
   189  	d.UseNumber()
   190  	item, err := d.decode()
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  	_, err = d.Token()
   195  	if !errors.Is(err, gio.EOF) {
   196  		return nil, fmt.Errorf("%w: unexpected items", ErrInvalidValue)
   197  	}
   198  	return item, nil
   199  }
   200  
   201  func (d *decoder) decode() (Item, error) {
   202  	tok, err := d.Token()
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	d.count--
   208  	if d.count < 0 && tok != json.Delim('}') && tok != json.Delim(']') {
   209  		return nil, errTooBigElements
   210  	}
   211  
   212  	switch t := tok.(type) {
   213  	case json.Delim:
   214  		switch t {
   215  		case json.Delim('{'), json.Delim('['):
   216  			if d.depth == MaxJSONDepth {
   217  				return nil, ErrTooDeep
   218  			}
   219  			d.depth++
   220  			var item Item
   221  			if t == json.Delim('{') {
   222  				item, err = d.decodeMap()
   223  			} else {
   224  				item, err = d.decodeArray()
   225  			}
   226  			d.depth--
   227  			return item, err
   228  		default:
   229  			d.count++
   230  			// no error above means corresponding closing token
   231  			// was encountered for map or array respectively
   232  			return nil, nil
   233  		}
   234  	case string:
   235  		return NewByteArray([]byte(t)), nil
   236  	case json.Number:
   237  		ts := t.String()
   238  		var (
   239  			num *big.Int
   240  			ok  bool
   241  		)
   242  		isScientific := strings.Contains(ts, "e+") || strings.Contains(ts, "E+")
   243  		if isScientific {
   244  			// As a special case numbers like 2.8e+22 are allowed (SetString rejects them).
   245  			// That's the way how C# code works.
   246  			var prec uint = CompatIntegerPrec
   247  			if d.bestIntPrecision {
   248  				prec = MaxIntegerPrec
   249  			}
   250  			f, _, err := big.ParseFloat(ts, 10, prec, big.ToNearestEven)
   251  			if err != nil {
   252  				return nil, fmt.Errorf("%w (malformed exp value for int)", ErrInvalidValue)
   253  			}
   254  			num = new(big.Int)
   255  			_, acc := f.Int(num)
   256  			ok = acc == big.Exact
   257  		} else {
   258  			dot := strings.IndexByte(ts, '.')
   259  			if dot != -1 {
   260  				// As a special case numbers like 123.000 are allowed (SetString rejects them).
   261  				// And yes, that's the way C# code works also.
   262  				for _, r := range ts[dot+1:] {
   263  					if r != '0' {
   264  						return nil, fmt.Errorf("%w (real value for int)", ErrInvalidValue)
   265  					}
   266  				}
   267  				ts = ts[:dot]
   268  			}
   269  			num, ok = new(big.Int).SetString(ts, 10)
   270  		}
   271  		if !ok {
   272  			return nil, fmt.Errorf("%w (integer)", ErrInvalidValue)
   273  		}
   274  		return NewBigInteger(num), nil
   275  	case bool:
   276  		return NewBool(t), nil
   277  	default:
   278  		// it can be only `nil`
   279  		return Null{}, nil
   280  	}
   281  }
   282  
   283  func (d *decoder) decodeArray() (*Array, error) {
   284  	items := []Item{}
   285  	for {
   286  		item, err := d.decode()
   287  		if err != nil {
   288  			return nil, err
   289  		}
   290  		if item == nil {
   291  			return NewArray(items), nil
   292  		}
   293  		items = append(items, item)
   294  	}
   295  }
   296  
   297  func (d *decoder) decodeMap() (*Map, error) {
   298  	m := NewMap()
   299  	for {
   300  		key, err := d.Token()
   301  		if err != nil {
   302  			return nil, err
   303  		}
   304  		k, ok := key.(string)
   305  		if !ok {
   306  			return m, nil
   307  		}
   308  
   309  		d.count--
   310  		if d.count < 0 {
   311  			return nil, errTooBigElements
   312  		}
   313  		val, err := d.decode()
   314  		if err != nil {
   315  			return nil, err
   316  		}
   317  		m.Add(NewByteArray([]byte(k)), val)
   318  	}
   319  }
   320  
   321  // ToJSONWithTypes serializes any stackitem to JSON in a lossless way.
   322  func ToJSONWithTypes(item Item) ([]byte, error) {
   323  	return toJSONWithTypes(nil, item, make(map[Item]sliceNoPointer, typicalNumOfItems))
   324  }
   325  
   326  func toJSONWithTypes(data []byte, item Item, seen map[Item]sliceNoPointer) ([]byte, error) {
   327  	if item == nil {
   328  		return nil, fmt.Errorf("%w: nil", ErrUnserializable)
   329  	}
   330  	if old, ok := seen[item]; ok {
   331  		if old.end == 0 {
   332  			// Compound item marshaling which has not yet finished.
   333  			return nil, ErrRecursive
   334  		}
   335  		if len(data)+old.end-old.start > MaxSize {
   336  			return nil, errTooBigSize
   337  		}
   338  		return append(data, data[old.start:old.end]...), nil
   339  	}
   340  
   341  	var val string
   342  	var hasValue bool
   343  	switch item.(type) {
   344  	case Null:
   345  		val = `{"type":"Any"}`
   346  	case *Interop:
   347  		val = `{"type":"InteropInterface"}`
   348  	default:
   349  		val = `{"type":"` + item.Type().String() + `","value":`
   350  		hasValue = true
   351  	}
   352  
   353  	if len(data)+len(val) > MaxSize {
   354  		return nil, errTooBigSize
   355  	}
   356  
   357  	start := len(data)
   358  
   359  	data = append(data, val...)
   360  	if !hasValue {
   361  		return data, nil
   362  	}
   363  
   364  	// Primitive stack items are appended after the switch
   365  	// to reduce the amount of size checks.
   366  	var primitive string
   367  	var isBuffer bool
   368  	var err error
   369  
   370  	switch it := item.(type) {
   371  	case *Array, *Struct:
   372  		seen[item] = sliceNoPointer{}
   373  		data = append(data, '[')
   374  		for i, elem := range it.Value().([]Item) {
   375  			if i != 0 {
   376  				data = append(data, ',')
   377  			}
   378  			data, err = toJSONWithTypes(data, elem, seen)
   379  			if err != nil {
   380  				return nil, err
   381  			}
   382  		}
   383  	case Bool:
   384  		if it {
   385  			primitive = "true"
   386  		} else {
   387  			primitive = "false"
   388  		}
   389  	case *ByteArray:
   390  		primitive = `"` + base64.StdEncoding.EncodeToString(it.Value().([]byte)) + `"`
   391  	case *Buffer:
   392  		isBuffer = true
   393  		primitive = `"` + base64.StdEncoding.EncodeToString(it.Value().([]byte)) + `"`
   394  	case *BigInteger:
   395  		primitive = `"` + it.Big().String() + `"`
   396  	case *Map:
   397  		seen[item] = sliceNoPointer{}
   398  		data = append(data, '[')
   399  		for i := range it.value {
   400  			if i != 0 {
   401  				data = append(data, ',')
   402  			}
   403  			data = append(data, `{"key":`...)
   404  			data, err = toJSONWithTypes(data, it.value[i].Key, seen)
   405  			if err != nil {
   406  				return nil, err
   407  			}
   408  			data = append(data, `,"value":`...)
   409  			data, err = toJSONWithTypes(data, it.value[i].Value, seen)
   410  			if err != nil {
   411  				return nil, err
   412  			}
   413  			data = append(data, '}')
   414  		}
   415  	case *Pointer:
   416  		primitive = strconv.Itoa(it.pos)
   417  	}
   418  	if len(primitive) != 0 {
   419  		if len(data)+len(primitive)+1 > MaxSize {
   420  			return nil, errTooBigSize
   421  		}
   422  		data = append(data, primitive...)
   423  		data = append(data, '}')
   424  
   425  		if isBuffer {
   426  			seen[item] = sliceNoPointer{start: start, end: len(data)}
   427  		}
   428  	} else {
   429  		if len(data)+2 > MaxSize { // also take care of '}'
   430  			return nil, errTooBigSize
   431  		}
   432  		data = append(data, ']', '}')
   433  
   434  		seen[item] = sliceNoPointer{start: start, end: len(data)}
   435  	}
   436  	return data, nil
   437  }
   438  
   439  type (
   440  	rawItem struct {
   441  		Type  string          `json:"type"`
   442  		Value json.RawMessage `json:"value,omitempty"`
   443  	}
   444  
   445  	rawMapElement struct {
   446  		Key   json.RawMessage `json:"key"`
   447  		Value json.RawMessage `json:"value"`
   448  	}
   449  )
   450  
   451  func mkErrValue(err error) error {
   452  	return fmt.Errorf("%w: %w", ErrInvalidValue, err)
   453  }
   454  
   455  // FromJSONWithTypes deserializes an item from typed-json representation.
   456  func FromJSONWithTypes(data []byte) (Item, error) {
   457  	raw := new(rawItem)
   458  	if err := json.Unmarshal(data, raw); err != nil {
   459  		return nil, err
   460  	}
   461  	typ, err := FromString(raw.Type)
   462  	if err != nil {
   463  		return nil, fmt.Errorf("%w: %v", ErrInvalidType, raw.Type)
   464  	}
   465  	switch typ {
   466  	case AnyT:
   467  		return Null{}, nil
   468  	case PointerT:
   469  		var pos int
   470  		if err := json.Unmarshal(raw.Value, &pos); err != nil {
   471  			return nil, mkErrValue(err)
   472  		}
   473  		return NewPointer(pos, nil), nil
   474  	case BooleanT:
   475  		var b bool
   476  		if err := json.Unmarshal(raw.Value, &b); err != nil {
   477  			return nil, mkErrValue(err)
   478  		}
   479  		return NewBool(b), nil
   480  	case IntegerT:
   481  		var s string
   482  		if err := json.Unmarshal(raw.Value, &s); err != nil {
   483  			return nil, mkErrValue(err)
   484  		}
   485  		val, ok := new(big.Int).SetString(s, 10)
   486  		if !ok {
   487  			return nil, mkErrValue(errors.New("not an integer"))
   488  		}
   489  		return NewBigInteger(val), nil
   490  	case ByteArrayT, BufferT:
   491  		var s string
   492  		if err := json.Unmarshal(raw.Value, &s); err != nil {
   493  			return nil, mkErrValue(err)
   494  		}
   495  		val, err := base64.StdEncoding.DecodeString(s)
   496  		if err != nil {
   497  			return nil, mkErrValue(err)
   498  		}
   499  		if typ == ByteArrayT {
   500  			return NewByteArray(val), nil
   501  		}
   502  		return NewBuffer(val), nil
   503  	case ArrayT, StructT:
   504  		var arr []json.RawMessage
   505  		if err := json.Unmarshal(raw.Value, &arr); err != nil {
   506  			return nil, mkErrValue(err)
   507  		}
   508  		items := make([]Item, len(arr))
   509  		for i := range arr {
   510  			it, err := FromJSONWithTypes(arr[i])
   511  			if err != nil {
   512  				return nil, err
   513  			}
   514  			items[i] = it
   515  		}
   516  		if typ == ArrayT {
   517  			return NewArray(items), nil
   518  		}
   519  		return NewStruct(items), nil
   520  	case MapT:
   521  		var arr []rawMapElement
   522  		if err := json.Unmarshal(raw.Value, &arr); err != nil {
   523  			return nil, mkErrValue(err)
   524  		}
   525  		m := NewMap()
   526  		for i := range arr {
   527  			key, err := FromJSONWithTypes(arr[i].Key)
   528  			if err != nil {
   529  				return nil, err
   530  			} else if err = IsValidMapKey(key); err != nil {
   531  				return nil, err
   532  			}
   533  			value, err := FromJSONWithTypes(arr[i].Value)
   534  			if err != nil {
   535  				return nil, err
   536  			}
   537  			m.Add(key, value)
   538  		}
   539  		return m, nil
   540  	case InteropT:
   541  		return NewInterop(nil), nil
   542  	default:
   543  		return nil, fmt.Errorf("%w: %v", ErrInvalidType, typ)
   544  	}
   545  }