github.com/taubyte/vm-wasm-utils@v1.0.2/binary/value.go (about)

     1  package binary
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"unicode/utf8"
     8  
     9  	wasm "github.com/taubyte/vm-wasm-utils"
    10  	"github.com/taubyte/vm-wasm-utils/leb128"
    11  )
    12  
    13  var noValType = []byte{0}
    14  
    15  // encodedValTypes is a cache of size prefixed binary encoding of known val types.
    16  var encodedValTypes = map[wasm.ValueType][]byte{
    17  	wasm.ValueTypeI32:       {1, wasm.ValueTypeI32},
    18  	wasm.ValueTypeI64:       {1, wasm.ValueTypeI64},
    19  	wasm.ValueTypeF32:       {1, wasm.ValueTypeF32},
    20  	wasm.ValueTypeF64:       {1, wasm.ValueTypeF64},
    21  	wasm.ValueTypeExternref: {1, wasm.ValueTypeExternref},
    22  	wasm.ValueTypeFuncref:   {1, wasm.ValueTypeFuncref},
    23  	wasm.ValueTypeV128:      {1, wasm.ValueTypeV128},
    24  }
    25  
    26  // encodeValTypes fast paths binary encoding of common value type lengths
    27  func encodeValTypes(vt []wasm.ValueType) []byte {
    28  	// Special case nullary and parameter lengths of wasi_snapshot_preview1 to avoid excess allocations
    29  	switch uint32(len(vt)) {
    30  	case 0: // nullary
    31  		return noValType
    32  	case 1: // ex $wasi.fd_close or any result
    33  		if encoded, ok := encodedValTypes[vt[0]]; ok {
    34  			return encoded
    35  		}
    36  	case 2: // ex $wasi.environ_sizes_get
    37  		return []byte{2, vt[0], vt[1]}
    38  	case 4: // ex $wasi.fd_write
    39  		return []byte{4, vt[0], vt[1], vt[2], vt[3]}
    40  	case 9: // ex $wasi.fd_write
    41  		return []byte{9, vt[0], vt[1], vt[2], vt[3], vt[4], vt[5], vt[6], vt[7], vt[8]}
    42  	}
    43  	// Slow path others until someone complains with a valid signature
    44  	count := leb128.EncodeUint32(uint32(len(vt)))
    45  	return append(count, vt...)
    46  }
    47  
    48  func decodeValueTypes(r *bytes.Reader, num uint32) ([]wasm.ValueType, error) {
    49  	if num == 0 {
    50  		return nil, nil
    51  	}
    52  	ret := make([]wasm.ValueType, num)
    53  	buf := make([]wasm.ValueType, num)
    54  	_, err := io.ReadFull(r, buf)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  
    59  	for i, v := range buf {
    60  		switch v {
    61  		case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64,
    62  			wasm.ValueTypeExternref, wasm.ValueTypeFuncref, wasm.ValueTypeV128:
    63  			ret[i] = v
    64  		default:
    65  			return nil, fmt.Errorf("invalid value type: %d", v)
    66  		}
    67  	}
    68  	return ret, nil
    69  }
    70  
    71  // decodeUTF8 decodes a size prefixed string from the reader, returning it and the count of bytes read.
    72  // contextFormat and contextArgs apply an error format when present
    73  func decodeUTF8(r *bytes.Reader, contextFormat string, contextArgs ...interface{}) (string, uint32, error) {
    74  	size, sizeOfSize, err := leb128.DecodeUint32(r)
    75  	if err != nil {
    76  		return "", 0, fmt.Errorf("failed to read %s size: %w", fmt.Sprintf(contextFormat, contextArgs...), err)
    77  	}
    78  
    79  	buf := make([]byte, size)
    80  	if _, err = io.ReadFull(r, buf); err != nil {
    81  		return "", 0, fmt.Errorf("failed to read %s: %w", fmt.Sprintf(contextFormat, contextArgs...), err)
    82  	}
    83  
    84  	if !utf8.Valid(buf) {
    85  		return "", 0, fmt.Errorf("%s is not valid UTF-8", fmt.Sprintf(contextFormat, contextArgs...))
    86  	}
    87  
    88  	return string(buf), size + uint32(sizeOfSize), nil
    89  }