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 }