github.com/tetratelabs/proxy-wasm-go-sdk@v0.23.1-0.20240517021853-021aa9cf78e8/properties/serialization.go (about)

     1  package properties
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"math"
     8  	"time"
     9  	"unsafe"
    10  )
    11  
    12  // serializeBool converts a boolean value to a byte slice representation.
    13  func serializeBool(value bool) []byte {
    14  	if value {
    15  		return []byte{1}
    16  	}
    17  	return []byte{0}
    18  }
    19  
    20  // deserializeBool converts a byte slice back to a boolean value.
    21  func deserializeBool(bs []byte) (bool, error) {
    22  	if len(bs) == 0 {
    23  		return false, nil
    24  	}
    25  	if len(bs) != 1 {
    26  		return false, fmt.Errorf("invalid byte slice length for boolean deserialization")
    27  	}
    28  	return bs[0] != 0, nil
    29  }
    30  
    31  // serializeByteMap serializes a map where keys are strings and values are raw byte slices.
    32  // The resulting byte slice can be used for efficient storage or transmission.
    33  //   - keys are always string
    34  //   - values are raw byte slices
    35  func serializeByteSliceMap(data map[string][]byte) []byte {
    36  	if len(data) == 0 {
    37  		return []byte{}
    38  	}
    39  
    40  	totalSize := 4
    41  	for key, value := range data {
    42  		totalSize += 4 + len(key) + 1 + 4 + len(value) + 1
    43  	}
    44  	bs := make([]byte, totalSize)
    45  	binary.LittleEndian.PutUint32(bs[0:4], uint32(len(data)))
    46  	var sizeIndex = 4
    47  	var dataIndex = 4 + 4*2*len(data)
    48  	for key, value := range data {
    49  		binary.LittleEndian.PutUint32(bs[sizeIndex:sizeIndex+4], uint32(len(key)))
    50  		sizeIndex += 4
    51  		copy(bs[dataIndex:dataIndex+len(key)], key)
    52  		dataIndex += len(key) + 1
    53  		binary.LittleEndian.PutUint32(bs[sizeIndex:sizeIndex+4], uint32(len(value)))
    54  		sizeIndex += 4
    55  		copy(bs[dataIndex:dataIndex+len(value)], value)
    56  		dataIndex += len(value) + 1
    57  	}
    58  	return bs
    59  }
    60  
    61  // deserializeByteMap deserializes the byte slice to key value map, used for mixed type maps
    62  //   - keys are always string
    63  //   - value are raw byte strings that need further parsing
    64  func deserializeByteSliceMap(bs []byte) map[string][]byte { //nolint:unused
    65  	ret := make(map[string][]byte)
    66  	if len(bs) == 0 {
    67  		return ret
    68  	}
    69  
    70  	numHeaders := binary.LittleEndian.Uint32(bs[0:4])
    71  	var sizeIndex = 4
    72  	var dataIndex = 4 + 4*2*int(numHeaders)
    73  	for i := 0; i < int(numHeaders); i++ {
    74  		keySize := int(binary.LittleEndian.Uint32(bs[sizeIndex : sizeIndex+4]))
    75  		sizeIndex += 4
    76  		keyPtr := bs[dataIndex : dataIndex+keySize]
    77  		key := *(*string)(unsafe.Pointer(&keyPtr))
    78  		dataIndex += keySize + 1
    79  
    80  		valueSize := int(binary.LittleEndian.Uint32(bs[sizeIndex : sizeIndex+4]))
    81  		sizeIndex += 4
    82  		valuePtr := bs[dataIndex : dataIndex+valueSize]
    83  		value := *(*[]byte)(unsafe.Pointer(&valuePtr))
    84  		dataIndex += valueSize + 1
    85  		ret[key] = value
    86  	}
    87  	return ret
    88  }
    89  
    90  // serializeByteSliceSlice serializes a slice of byte slices into a single byte slice.
    91  // The resulting byte slice can be used for efficient storage or transmission.
    92  // Each byte slice in the input is prefixed with its length, allowing for efficient deserialization.
    93  func serializeByteSliceSlice(slices [][]byte) []byte {
    94  	if len(slices) == 0 {
    95  		return []byte{}
    96  	}
    97  
    98  	totalSize := 4
    99  	for _, slice := range slices {
   100  		totalSize += 8 + len(slice) + 2
   101  	}
   102  	bs := make([]byte, totalSize)
   103  	binary.LittleEndian.PutUint32(bs[:4], uint32(len(slices)))
   104  	idx := 4
   105  	dataIdx := 4 + 8*len(slices)
   106  	for _, slice := range slices {
   107  		binary.LittleEndian.PutUint64(bs[idx:idx+8], uint64(len(slice)))
   108  		idx += 8
   109  		copy(bs[dataIdx:dataIdx+len(slice)], slice)
   110  		dataIdx += len(slice) + 2
   111  	}
   112  	return bs
   113  }
   114  
   115  // deserializeByteSliceSlice deserializes the given bytes to string slice.
   116  func deserializeByteSliceSlice(bs []byte) [][]byte {
   117  	if len(bs) == 0 {
   118  		return [][]byte{}
   119  	}
   120  	numStrings := int(binary.LittleEndian.Uint32(bs[:4]))
   121  	ret := make([][]byte, numStrings)
   122  	idx := 4
   123  	dataIdx := 4 + 8*numStrings
   124  	for i := 0; i < numStrings; i++ {
   125  		strLen := int(binary.LittleEndian.Uint64(bs[idx : idx+8]))
   126  		idx += 8
   127  		ret[i] = bs[dataIdx : dataIdx+strLen]
   128  		dataIdx += strLen + 2
   129  	}
   130  	return ret
   131  }
   132  
   133  // serializeFloat64 serializes the given float64 to bytes.
   134  // The resulting byte slice can be used for efficient storage or transmission.
   135  func serializeFloat64(value float64) []byte {
   136  	bits := math.Float64bits(value)
   137  	bs := make([]byte, 8)
   138  	binary.LittleEndian.PutUint64(bs, bits)
   139  	return bs
   140  }
   141  
   142  // deserializeFloat64 deserializes the given bytes to float64.
   143  func deserializeFloat64(bs []byte) float64 {
   144  	bits := binary.LittleEndian.Uint64(bs)
   145  	float := math.Float64frombits(bits)
   146  	return float
   147  }
   148  
   149  // serializeProtoStringSlice serializes a slice of strings into a protobuf-like encoded byte slice.
   150  // The resulting byte slice can be used for efficient storage or transmission.
   151  // Each string in the slice is prefixed with its length, allowing for efficient deserialization.
   152  func serializeProtoStringSlice(strs []string) []byte {
   153  	var bs []byte
   154  	if len(strs) == 0 {
   155  		return bs
   156  	}
   157  
   158  	for _, str := range strs {
   159  		if len(str) > 255 {
   160  			panic("string length exceeds 255 characters")
   161  		}
   162  		bs = append(bs, 0x00)
   163  		bs = append(bs, byte(len(str)))
   164  		bs = append(bs, []byte(str)...)
   165  	}
   166  	return bs
   167  }
   168  
   169  // deserializeProtoStringSlice deserializes a protobuf encoded string slice
   170  func deserializeProtoStringSlice(bs []byte) []string {
   171  	ret := make([]string, 0)
   172  	if len(bs) == 0 {
   173  		return ret
   174  	}
   175  	i := 0
   176  	for i < len(bs) {
   177  		i++
   178  		length := int(bs[i])
   179  		i++
   180  		str := string(bs[i : i+length])
   181  		ret = append(ret, str)
   182  		i += length
   183  	}
   184  	return ret
   185  }
   186  
   187  // serializeStringMap serializes a map of strings to a byte slice.
   188  //   - keys are always string
   189  //   - values are always string
   190  //
   191  // The resulting byte slice starts with a 4-byte representation of the number of key-value pairs.
   192  // This is followed by a series of 4-byte representations of the sizes of the keys and values.
   193  // Finally, the actual key and value data are appended.
   194  func serializeStringMap(m map[string]string) []byte {
   195  	headerBytes := make([]byte, 4)
   196  	if len(m) == 0 {
   197  		return headerBytes
   198  	}
   199  	var buf bytes.Buffer
   200  	numHeaders := uint32(len(m))
   201  	binary.LittleEndian.PutUint32(headerBytes, numHeaders)
   202  	buf.Write(headerBytes)
   203  	var sizeData bytes.Buffer
   204  	var data bytes.Buffer
   205  	for key, value := range m {
   206  		keySize := uint32(len(key))
   207  		keySizeBytes := make([]byte, 4)
   208  		binary.LittleEndian.PutUint32(keySizeBytes, keySize)
   209  		sizeData.Write(keySizeBytes)
   210  		keyData := *(*[]byte)(unsafe.Pointer(&key))
   211  		data.Write(keyData)
   212  		data.WriteByte(0)
   213  		valueSize := uint32(len(value))
   214  		valueSizeBytes := make([]byte, 4)
   215  		binary.LittleEndian.PutUint32(valueSizeBytes, valueSize)
   216  		sizeData.Write(valueSizeBytes)
   217  		valueData := *(*[]byte)(unsafe.Pointer(&value))
   218  		data.Write(valueData)
   219  		data.WriteByte(0)
   220  	}
   221  	buf.Write(sizeData.Bytes())
   222  	buf.Write(data.Bytes())
   223  	return buf.Bytes()
   224  }
   225  
   226  // deserializeStringMap deserializes the bytes to key value map, used for string only type maps
   227  //   - keys are always string
   228  //   - value are always string
   229  func deserializeStringMap(bs []byte) map[string]string {
   230  	numHeaders := binary.LittleEndian.Uint32(bs[0:4])
   231  	if numHeaders == 0 {
   232  		return map[string]string{}
   233  	}
   234  
   235  	var sizeIndex = 4
   236  	var dataIndex = 4 + 4*2*int(numHeaders)
   237  	ret := make(map[string]string, numHeaders)
   238  	for i := 0; i < int(numHeaders); i++ {
   239  		keySize := int(binary.LittleEndian.Uint32(bs[sizeIndex : sizeIndex+4]))
   240  		sizeIndex += 4
   241  		keyPtr := bs[dataIndex : dataIndex+keySize]
   242  		key := *(*string)(unsafe.Pointer(&keyPtr))
   243  		dataIndex += keySize + 1
   244  
   245  		valueSize := int(binary.LittleEndian.Uint32(bs[sizeIndex : sizeIndex+4]))
   246  		sizeIndex += 4
   247  		valuePtr := bs[dataIndex : dataIndex+valueSize]
   248  		value := *(*string)(unsafe.Pointer(&valuePtr))
   249  		dataIndex += valueSize + 1
   250  		ret[key] = value
   251  	}
   252  	return ret
   253  }
   254  
   255  // serializeStringSlice serializes a slice of strings into a single byte slice.
   256  // The resulting byte slice can be used for efficient storage or transmission.
   257  // Each string in the input is prefixed with its length, allowing for efficient deserialization.
   258  func serializeStringSlice(strings []string) []byte {
   259  	if len(strings) == 0 {
   260  		return make([]byte, 4)
   261  	}
   262  	totalSize := 4
   263  	for _, str := range strings {
   264  		totalSize += 8 + len(str) + 2
   265  	}
   266  	bs := make([]byte, totalSize)
   267  	binary.LittleEndian.PutUint32(bs[:4], uint32(len(strings)))
   268  	idx := 4
   269  	dataIdx := 4 + 8*len(strings)
   270  	for _, str := range strings {
   271  		binary.LittleEndian.PutUint64(bs[idx:idx+8], uint64(len(str)))
   272  		idx += 8
   273  		copy(bs[dataIdx:dataIdx+len(str)], str)
   274  		dataIdx += len(str) + 2
   275  	}
   276  	return bs
   277  }
   278  
   279  // deserializeStringSlice deserializes the given byte slice to string slice.
   280  func deserializeStringSlice(bs []byte) []string {
   281  	numStrings := int(binary.LittleEndian.Uint32(bs[:4]))
   282  	if numStrings == 0 {
   283  		return []string{}
   284  	}
   285  	ret := make([]string, numStrings)
   286  	idx := 4
   287  	dataIdx := 4 + 8*numStrings
   288  	for i := 0; i < numStrings; i++ {
   289  		strLen := int(binary.LittleEndian.Uint64(bs[idx : idx+8]))
   290  		idx += 8
   291  		ret[i] = string(bs[dataIdx : dataIdx+strLen])
   292  		dataIdx += strLen + 2
   293  	}
   294  	return ret
   295  }
   296  
   297  // serializeTimestamp serializes the given timestamp to bytes.
   298  // The resulting byte slice can be used for efficient storage or transmission.
   299  func serializeTimestamp(timestamp time.Time) []byte {
   300  	nanos := timestamp.UnixNano()
   301  	bs := make([]byte, 8)
   302  	binary.LittleEndian.PutUint64(bs, uint64(nanos))
   303  	return bs
   304  }
   305  
   306  // deserializeTimestamp deserializes the given bytes to timestamp.
   307  func deserializeTimestamp(bs []byte) time.Time {
   308  	nanos := int64(binary.LittleEndian.Uint64(bs))
   309  	return time.Unix(0, nanos)
   310  }
   311  
   312  // serializeUint64 serializes the given uint64 to bytes.
   313  // The resulting byte slice can be used for efficient storage or transmission.
   314  func serializeUint64(value uint64) []byte {
   315  	bs := make([]byte, 8)
   316  	binary.LittleEndian.PutUint64(bs, value)
   317  	return bs
   318  }
   319  
   320  // deserializeUint64 deserializes  the given bytes to uint64.
   321  func deserializeUint64(bs []byte) uint64 {
   322  	return binary.LittleEndian.Uint64(bs)
   323  }