diff --git a/README.md b/README.md index 0e1a1a5..39a1ce8 100644 --- a/README.md +++ b/README.md @@ -1 +1,41 @@ -# msgpack-rawan \ No newline at end of file +# Msgpack Serializer/Deserializer + +This repository implements the Msgpack object serialization/deserialization + +**Serialization** is conversion from application objects into MessagePack formats via MessagePack type system. + +**Deserialization** is conversion from MessagePack formats into application objects via MessagePack type system. + +## Table of Contents + +- [Installation](#installation) +- [Usage](#usage) + + +## Installation + +1. Clone the repository + + ```bash + git clone https://github.com/codescalersinternships/msgpack-rawan.git + ``` +## APIs +- `Serialize(object interface{}) ([]byte, error)` : Represents the serialization API, given the object itself, it converts it to its serialized bytes + +- `Deserialize(bytes []byte) (interface{}, error)` : Represents the deserialization API, given the serialize bytes, it turns them back to objects + +## Usage + +```go + bytes, err := msgpack.Serialize(true) + if err != nil { + panic(err) + } + + deserialized, err := msgpack.Deserialize(bytes) + if err != nil { + panic(err) + } + fmt.Printf("Deserialized: %v\n", deserialized) + fmt.Printf("Type: %T\n", deserialized) +``` \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..2841538 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + + msgpack "github.com/codescalersinternships/msgpack-rawan/pkg" +) + +func main() { + bytes, err := msgpack.Serialize(true) + if err != nil { + panic(err) + } + + deserialized, err := msgpack.Deserialize(bytes) + if err != nil { + panic(err) + } + fmt.Printf("Deserialized: %v\n", deserialized) + fmt.Printf("Type: %T\n", deserialized) + +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c0875b6 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/codescalersinternships/msgpack-rawan + +go 1.25.0 diff --git a/pkg/deserializer.go b/pkg/deserializer.go new file mode 100644 index 0000000..e389bfe --- /dev/null +++ b/pkg/deserializer.go @@ -0,0 +1,148 @@ +package pkg + +import "math" + +func Deserialize(bytes []byte) (interface{}, error) { + result,_,err:= deserialize(bytes) + return result, err +} + +func deserialize(bytes []byte) (interface{}, int, error) { + var result interface{} + + switch bytes[0] { + + case 0xC0: //nil + result = nil + return result, 1, nil + case 0xC3: //bool true + result = true + return result, 1, nil + case 0xC2: // bool false + result = false + return result, 1, nil + case 0xCC: // uint8 + result = uint8(bytes[1]) + return result, 2, nil + case 0xCD: // uint16 + result = uint16(bytes[1])<<8 | uint16(bytes[2]) + return result, 3, nil + case 0xCE: // uint32 + result = uint32(bytes[1])<<24 | uint32(bytes[2])<<16 | uint32(bytes[3])<<8 | uint32(bytes[4]) + return result, 5, nil + case 0xCF: // uint64 + result = uint64(bytes[1])<<56 | uint64(bytes[2])<<48 | uint64(bytes[3])<<40 | uint64(bytes[4])<<32 | uint64(bytes[5])<<24 | uint64(bytes[6])<<16 | uint64(bytes[7])<<8 | uint64(bytes[8]) + return result, 9, nil + case 0xD0: // int8 + result = int8(bytes[1]) + return result, 2, nil + case 0xD1: // int16 + result = int16(bytes[1])<<8 | int16(bytes[2]) + return result, 3, nil + case 0xD2: // int32 + result = int32(bytes[1])<<24 | int32(bytes[2])<<16 | int32(bytes[3])<<8 | int32(bytes[4]) + return result, 5, nil + case 0xD3: // int64 + result = int64(bytes[1])<<56 | int64(bytes[2])<<48 | int64(bytes[3])<<40 | int64(bytes[4])<<32 | int64(bytes[5])<<24 | int64(bytes[6])<<16 | int64(bytes[7])<<8 | int64(bytes[8]) + return result, 9, nil + case 0xCA: // float32 + result = math.Float32frombits(uint32(bytes[1])<<24 | uint32(bytes[2])<<16 | uint32(bytes[3])<<8 | uint32(bytes[4])) + return result, 5, nil + case 0xCB: // float64 + result = math.Float64frombits(uint64(bytes[1])<<56 | uint64(bytes[2])<<48 | uint64(bytes[3])<<40 | uint64(bytes[4])<<32 | uint64(bytes[5])<<24 | uint64(bytes[6])<<16 | uint64(bytes[7])<<8 | uint64(bytes[8])) + return result, 9, nil + //fixstr + case 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF: + strLen := int(bytes[0] & 0x1F) // lower 5 bits + result = string(bytes[1 : 1+strLen]) + return result, int(bytes[0]&0x1F) + 1, nil // lower 5 bits + case 0xD9: // str8 + strLen := int(bytes[1]) + result = string(bytes[2 : 2+strLen]) + return result, strLen + 2, nil + case 0xDA: // str16 + strLen := int(bytes[1])<<8 | int(bytes[2]) + result = string(bytes[3 : 3+strLen]) + return result, strLen + 3, nil + case 0xDB: // str32 + strLen := int(bytes[1])<<24 | int(bytes[2])<<16 | int(bytes[3])<<8 | int(bytes[4]) + result = string(bytes[5 : 5+strLen]) + return result, strLen + 5, nil + case 0xC4: // bin8 + binLen := int(bytes[1]) + result = bytes[2 : 2+binLen] + return result, binLen + 2, nil + case 0xC5: // bin16 + binLen := int(bytes[1])<<8 | int(bytes[2]) + result = bytes[3 : 3+binLen] + return result, binLen + 3, nil + case 0xC6: // bin32 + binLen := int(bytes[1])<<24 | int(bytes[2])<<16 | int(bytes[3])<<8 | int(bytes[4]) + result = bytes[5 : 5+binLen] + return result, binLen + 5, nil + //fixarray + case 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F: + arrayLen := int(bytes[0] & 0x0F) //get lower 4 bits + arr := make([]any, 0, arrayLen) + rest := bytes[1:] + for i := 0; i < arrayLen; i++ { + elem, n, _ := deserialize(rest) + arr = append(arr, elem) + rest = rest[n:] + } + result = arr + return result, len(bytes) - len(rest), nil + case 0xDC: // array16 + arrayLen := int(bytes[1])<<8 | int(bytes[2]) + arr := make([]any, 0, arrayLen) + + rest := bytes[3:] + for i := 0; i < arrayLen; i++ { + elem, n, _ := deserialize(rest) + arr = append(arr, elem) + rest = rest[n:] + } + result = arr + return result, len(bytes) - len(rest), nil + case 0xDD: // array32 + arrayLen := int(bytes[1])<<24 | (int(bytes[2]) << 16) | (int(bytes[3]) << 8) | int(bytes[4]) + arr := make([]any, 0, arrayLen) + + rest := bytes[5:] + for i := 0; i < arrayLen; i++ { + elem, n, _ := deserialize(rest) + arr = append(arr, elem) + rest = rest[n:] + } + result = arr + return result, len(bytes) - len(rest), nil + //fixmap + case 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F: + mapLen := int(bytes[0] & 0x0F) //get lower 4 bits + temp_map := make(map[any]any, mapLen) + + rest := bytes[1:] + consumed := 1 + for i := 0; i < mapLen; i++ { + key, n, _ := deserialize(rest) + consumed += n + rest = rest[n:] + + val, n, _ := deserialize(rest) + consumed += n + rest = rest[n:] + temp_map[key] = val + } + result = temp_map + return result, len(bytes) - len(rest), nil + default: + if bytes[0] < 128 { // positive fixint + result = int(bytes[0]) + return result, 1, nil + } else if bytes[0] >= 0xE0 { // negative fixint + result = int(int8(bytes[0])) + return result, 1, nil + } + } + return result, 0, nil +} diff --git a/pkg/deserializer_test.go b/pkg/deserializer_test.go new file mode 100644 index 0000000..895d517 --- /dev/null +++ b/pkg/deserializer_test.go @@ -0,0 +1,157 @@ +package pkg + +import ( + "bytes" + "reflect" + "testing" +) + +func TestDeserialize(t *testing.T) { + testcases := []struct { + name string + input []byte + expected interface{} + }{ + { + name: "nil", + input: []byte{0xC0}, + expected: nil, + }, + { + name: "boolean true", + input: []byte{0xC3}, + expected: true, + }, + { + name: "boolean false", + input: []byte{0xC2}, + expected: false, + }, + { + name: "uint8", + input: []byte{0xCC, 0xFF}, + expected: uint8(255), + }, + { + name: "uint16", + input: []byte{0xCD, 0xFF, 0xFF}, + expected: uint16(65535), + }, + { + name: "uint32", + input: []byte{0xCE, 0xFF, 0xFF, 0xFF, 0xFF}, + expected: uint32(4294967295), + }, + { + name: "uint64", + input: []byte{0xCF, 0x00, 0x00, 0x00, 0x01, 0x2A, 0x05, 0xF2, 0x00}, + expected: uint64(5000000000), + }, + { + name: "float32", + input: []byte{0xCA, 0x40, 0x48, 0xF5, 0xC3}, + expected: float32(3.14), + }, + { + name: "float64", + input: []byte{0xCB, 0x40, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18}, + expected: float64(3.141592653589793), + }, + { + name: "fix string", + input: []byte{0xA5, 'h', 'e', 'l', 'l', 'o'}, + expected: "hello", + }, + { + name: "str8", + input: append([]byte{0xD9, 100}, make([]byte, 100)...), + expected: string(make([]byte, 100)), + }, + { + name: "str16", + input: append([]byte{0xDA, 0x1B, 0x58}, make([]byte, 7000)...), + expected: string(make([]byte, 7000)), + }, + { + name: "str32", + input: append([]byte{0xDB, 0x00, 0x01, 0x11, 0x70}, make([]byte, 70000)...), + expected: string(make([]byte, 70000)), + }, + { + name: "bin8", + input: []byte{0xC4, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05}, + expected: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, + }, + { + name: "bin16", + input: append([]byte{0xC5, 0x01, 0x2C}, make([]byte, 300)...), + expected: make([]byte, 300), + }, + { + name: "bin32", + input: append([]byte{0xC6, 0x00, 0x01, 0x11, 0x70}, make([]byte, 70000)...), + expected: make([]byte, 70000), + }, + { + name: "empty array", + input: []byte{0x90}, + expected: []any{}, + }, + { + name: "fix array", + input: []byte{0x93, 0x01, 0x02, 0x03}, + expected: []any{1, 2, 3}, + }, + { + name: "array16", + input: append([]byte{0xDC, 0x00, 0xC8}, bytes.Repeat([]byte{0xC0}, 200)...), + expected: make([]any, 200), + }, + { + name: "array32", + input: append([]byte{0xDD, 0x00, 0x01, 0x11, 0x70}, bytes.Repeat([]byte{0xC0}, 70000)...), + expected: make([]any, 70000), + }, + { + name: "nested array", + input: []byte{0x93, 0x01, 0x92, 0x02, 0x03, 0x04}, + expected: []any{1, []any{2, 3}, 4}, + }, + { + name: "empty map", + input: []byte{0x80}, + expected: map[any]any{}, + }, + { + name: "fix map", + input: []byte{0x82, 0xA1, 'a', 0x01, 0xA1, 'b', 0xC3}, + expected: map[any]any{ + "a": 1, + "b": true, + }, + }, + { + name: "positive fixint", + input: []byte{0x64}, + expected: 100, + }, + { + name: "negative fixint", + input: []byte{0xEC}, + expected: -20, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + result, err := Deserialize(tc.input) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if !reflect.DeepEqual(result, tc.expected) { + t.Errorf("Expected %v, got %v", tc.expected, result) + t.Errorf("Type of expected: %T, Type of got: %T", tc.expected, result) + } + }) + } +} diff --git a/pkg/serializer_test.go b/pkg/serializer_test.go new file mode 100644 index 0000000..2c061cf --- /dev/null +++ b/pkg/serializer_test.go @@ -0,0 +1,176 @@ +package pkg + +import ( + "bytes" + "reflect" + "testing" +) + +func TestSerialize(t *testing.T) { + testcases := []struct { + name string + input interface{} + expected []byte + }{ + { + name: "nil", + input: nil, + expected: []byte{0xC0}, + }, + { + name: "boolean true", + input: true, + expected: []byte{0xC3}, + }, + { + name: "boolean false", + input: false, + expected: []byte{0xC2}, + }, + { + name: "uint8", + input: uint8(255), + expected: []byte{0xCC, 0xFF}, + }, + { + name: "uint16", + input: uint16(65535), + expected: []byte{0xCD, 0xFF, 0xFF}, + }, + { + name: "uint32", + input: uint32(4294967295), + expected: []byte{0xCE, 0xFF, 0xFF, 0xFF, 0xFF}, + }, + { + name: "uint64", + input: uint64(5000000000), + expected: []byte{0xCF, 0x00, 0x00, 0x00, 0x01, 0x2A, 0x05, 0xF2, 0x00}, + }, + { + name: "positive fixint", + input: 100, + expected: []byte{0x64}, + }, + { + name: "negative fixint", + input: -20, + expected: []byte{0xEC}, + }, + { + name: "int8", + input: -128, + expected: []byte{0xD0, 0x80}, + }, + { + name: "int16", + input: -300, + expected: []byte{0xD1, 0xFE, 0xD4}, + }, + { + name: "int32", + input: -70000, + expected: []byte{0xD2, 0xFF, 0xFE, 0xEE, 0x90}, + }, + { + name: "int64", + input: -5000000000, + expected: []byte{0xD3, 0xFF, 0xFF, 0xFF, 0xFE, 0xD5, 0xFA, 0x0E, 0x00}, + }, + { + name: "float32", + input: float32(3.14), + expected: []byte{0xCA, 0x40, 0x48, 0xF5, 0xC3}, + }, + { + name: "float64", + input: float64(3.141592653589793), + expected: []byte{0xCB, 0x40, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18}, + }, + { + name: "fix string", + input: "hello", + expected: []byte{0xA5, 'h', 'e', 'l', 'l', 'o'}, + }, + { + name: "str8", + input: string(make([]byte, 100)), + expected: append([]byte{0xD9, 100}, make([]byte, 100)...), + }, + { + name: "str16", + input: string(make([]byte, 7000)), + expected: append([]byte{0xDA, 0x1B, 0x58}, make([]byte, 7000)...), + }, + { + name: "str32", + input: string(make([]byte, 70000)), + expected: append([]byte{0xDB, 0x00, 0x01, 0x11, 0x70}, make([]byte, 70000)...), + }, + { + name: "bin8", + input: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, + expected: []byte{0xC4, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05}, + }, + { + name: "bin16", + input: make([]byte, 300), + expected: append([]byte{0xC5, 0x01, 0x2C}, make([]byte, 300)...), + }, + { + name: "bin32", + input: make([]byte, 70000), + expected: append([]byte{0xC6, 0x00, 0x01, 0x11, 0x70}, make([]byte, 70000)...), + }, + { + name: "empty array", + input: []any{}, + expected: []byte{0x90}, + }, + { + name: "fix array", + input: []any{1, 2, 3}, + expected: []byte{0x93, 0x01, 0x02, 0x03}, + }, + { + name: "array16", + input: make([]any, 200), + expected: append([]byte{0xDC, 0x00, 0xC8}, bytes.Repeat([]byte{0xC0}, 200)...), + }, + { + name: "array32", + input: make([]any, 70000), + expected: append([]byte{0xDD, 0x00, 0x01, 0x11, 0x70}, bytes.Repeat([]byte{0xC0}, 70000)...), + }, + { + name: "nested array", + input: []any{1, []any{2, 3}, 4}, + expected: []byte{0x93, 0x01, 0x92, 0x02, 0x03, 0x04}, + }, + { + name: "empty map", + input: map[any]any{}, + expected: []byte{0x80}, + }, + { + name: "fix map", + input: map[any]any{ + "a": 1, + "b": true, + }, + expected: []byte{0x82, 0xA1, 'a', 0x01, 0xA1, 'b', 0xC3}, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + result, err := Serialize(tc.input) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !reflect.DeepEqual(result, tc.expected) { + t.Errorf("expected %v, got %v", tc.expected, result) + } + }) + } + +} diff --git a/pkg/serlializer.go b/pkg/serlializer.go new file mode 100644 index 0000000..970c6fe --- /dev/null +++ b/pkg/serlializer.go @@ -0,0 +1,162 @@ +package pkg + +import ( + "math" + "reflect" +) + +func handleInt(objType int64, result []byte) []byte { + if objType >= 0 && objType < 128 { // positive fixint + result = append(result, byte(objType)) + + } else if objType >= -32 && objType < 0 { // negative fixint + result = append(result, byte(int8(objType))) + + } else if objType >= math.MinInt8 && objType <= math.MaxInt8 { // int 8 + result = append(result, 0xD0) + result = append(result, byte(int8(objType))) + + } else if objType >= math.MinInt16 && objType <= math.MaxInt16 { // int 16 + result = append(result, 0xD1) + result = append(result, byte(int16(objType)>>8), byte(int16(objType))) + + } else if objType >= math.MinInt32 && objType <= math.MaxInt32 { // int 32 + result = append(result, 0xD2) + result = append(result, byte(int32(objType)>>24), byte(int32(objType)>>16), byte(int32(objType)>>8), byte(int32(objType))) + + } else { // int 64 + result = append(result, 0xD3) + result = append(result, byte(int64(objType)>>56), byte(int64(objType)>>48), byte(int64(objType)>>40), byte(int64(objType)>>32), byte(int64(objType)>>24), byte(int64(objType)>>16), byte(int64(objType)>>8), byte(int64(objType))) + } + return result +} + +func handleUint(objType uint64, result []byte) []byte { + if objType < (1 << 7) { // positive fixint + result = append(result, byte(objType)) + } else if objType < (1 << 8) { // uint 8 + result = append(result, 0xCC) + result = append(result, byte(objType)) + } else if objType < (1 << 16) { // uint 16 + result = append(result, 0xCD) + result = append(result, byte(objType>>8), byte(objType)) + } else if objType < (1 << 32) { // uint 32 + result = append(result, 0xCE) + result = append(result, byte(objType>>24), byte(objType>>16), byte(objType>>8), byte(objType)) + } else { // uint 64 + result = append(result, 0xCF) + result = append(result, byte(objType>>56), byte(objType>>48), byte(objType>>40), byte(objType>>32), byte(objType>>24), byte(objType>>16), byte(objType>>8), byte(objType)) + } + return result +} + +func Serialize(object interface{}) ([]byte, error) { + var result []byte + + switch objType := object.(type) { + + case nil: + result = append(result, 0xC0) + + case bool: + if objType { + result = append(result, 0xC3) + } else { + result = append(result, 0xC2) + } + + case uint, uint8, uint16, uint32, uint64: + result = handleUint(reflect.ValueOf(objType).Uint(), result) + + case int, int8, int16, int32, int64: + result = handleInt(reflect.ValueOf(objType).Int(), result) + case float32: + result = append(result, 0xCA) + floatBits := math.Float32bits(objType) + result = append(result, byte(floatBits>>24), byte(floatBits>>16), byte(floatBits>>8), byte(floatBits)) + case float64: + result = append(result, 0xCB) + floatBits := math.Float64bits(objType) + result = append(result, byte(floatBits>>56), byte(floatBits>>48), byte(floatBits>>40), byte(floatBits>>32), byte(floatBits>>24), byte(floatBits>>16), byte(floatBits>>8), byte(floatBits)) + case string: + strLen := len(objType) + if strLen < 32 { + result = append(result, 0xA0|byte(strLen)) + + } else if strLen < int(math.Pow(2, 8)) { + result = append(result, 0xD9) + result = append(result, byte(strLen)) + + } else if strLen < int(math.Pow(2, 16)) { + result = append(result, 0xDA) + result = append(result, byte(strLen>>8), byte(strLen)) + + } else if strLen < int(math.Pow(2, 32)) { + result = append(result, 0xDB) + result = append(result, byte(strLen>>24), byte(strLen>>16), byte(strLen>>8), byte(strLen)) + } + result = append(result, []byte(objType)...) + case []byte: + byteLen := len(objType) + if byteLen < int(math.Pow(2, 8)) { + result = append(result, 0xC4) + result = append(result, byte(byteLen)) + + } else if byteLen < int(math.Pow(2, 16)) { + result = append(result, 0xC5) + result = append(result, byte(byteLen>>8), byte(byteLen)) + + } else if byteLen < int(math.Pow(2, 32)) { + result = append(result, 0xC6) + result = append(result, byte(byteLen>>24), byte(byteLen>>16), byte(byteLen>>8), byte(byteLen)) + } + result = append(result, []byte(objType)...) + case []any: + arrLen := len(objType) + if arrLen < 16 { //fixarray + result = append(result, 0x90|byte(arrLen)) + + } else if arrLen < int(math.Pow(2, 16)) { //array16 + result = append(result, 0xDC) + result = append(result, byte(arrLen>>8), byte(arrLen)) + + } else if arrLen < int(math.Pow(2, 32)) { //array32 + result = append(result, 0xDD) + result = append(result, byte(arrLen>>24), byte(arrLen>>16), byte(arrLen>>8), byte(arrLen)) + } + + for _, element := range objType { + serializedElement, err := Serialize(element) + if err != nil { + return nil, err + } + result = append(result, serializedElement...) + } + case map[any]any: + mapLen := len(objType) + if mapLen < 16 { //fixmap + result = append(result, 0x80|byte(mapLen)) + } else if mapLen < int(math.Pow(2, 16)) { //map16 + result = append(result, 0xDE) + result = append(result, byte(mapLen>>8), byte(mapLen)) + } else if mapLen < int(math.Pow(2, 32)) { //map32 + result = append(result, 0xDF) + result = append(result, byte(mapLen>>24), byte(mapLen>>16), byte(mapLen>>8), byte(mapLen)) + } + for key, value := range objType { + serializedElement, err := Serialize(key) + if err != nil { + return nil, err + } + result = append(result, serializedElement...) + + + serializedElement, err = Serialize(value) + if err != nil { + return nil, err + } + result = append(result, serializedElement...) + } + } + return result, nil +}