From 3e1449f374040524ad8a6ded6cb28b877264061a Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sun, 15 Dec 2024 22:52:06 -0600 Subject: [PATCH] add decoding for all types except array --- pkg/osc/osc.go | 72 ++++++++++++++++++++-- pkg/osc/osc_test.go | 145 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 212 insertions(+), 5 deletions(-) diff --git a/pkg/osc/osc.go b/pkg/osc/osc.go index 99bd21b..7ecd500 100644 --- a/pkg/osc/osc.go +++ b/pkg/osc/osc.go @@ -212,7 +212,7 @@ func readOSCString(bytes []byte) (string, []byte) { return oscString, remainingBytes } -func readOSCInt(bytes []byte) (int32, []byte, error) { +func readOSCInt32(bytes []byte) (int32, []byte, error) { if len(bytes) < 4 { return 0, bytes, errors.New("int data must be at least 4 bytes large") } @@ -220,7 +220,15 @@ func readOSCInt(bytes []byte) (int32, []byte, error) { return int32(bits), bytes[4:], nil } -func readOSCFloat(bytes []byte) (float32, []byte, error) { +func readOSCInt64(bytes []byte) (int64, []byte, error) { + if len(bytes) < 8 { + return 0, bytes, errors.New("int data must be at least 4 bytes large") + } + bits := binary.BigEndian.Uint64(bytes[0:8]) + return int64(bits), bytes[8:], nil +} + +func readOSCFloat32(bytes []byte) (float32, []byte, error) { if len(bytes) < 4 { return 0, bytes, errors.New("float data must be at least 4 bytes large") } @@ -228,8 +236,16 @@ func readOSCFloat(bytes []byte) (float32, []byte, error) { return math.Float32frombits(bits), bytes[4:], nil } +func readOSCFloat64(bytes []byte) (float64, []byte, error) { + if len(bytes) < 4 { + return 0, bytes, errors.New("float data must be at least 4 bytes large") + } + bits := binary.BigEndian.Uint64(bytes[0:8]) + return math.Float64frombits(bits), bytes[8:], nil +} + func readOSCBlob(bytes []byte) ([]byte, []byte, error) { - blobLength, remainingBytes, err := readOSCInt(bytes) + blobLength, remainingBytes, err := readOSCInt32(bytes) if err != nil { return []byte{}, bytes, errors.New("problem reading blob data size") @@ -248,6 +264,19 @@ func readOSCBlob(bytes []byte) ([]byte, []byte, error) { return bytes[4 : 4+blobLength], bytes[blobEnd:], nil } +func readOSCColor(bytes []byte) (OSCColor, []byte, error) { + if len(bytes) < 4 { + return OSCColor{0, 0, 0, 0}, bytes, errors.New("color data must be at least 4 bytes large") + } + oscColor := OSCColor{ + r: bytes[0], + g: bytes[1], + b: bytes[2], + a: bytes[3], + } + return oscColor, bytes[4:], nil +} + func readOSCArg(bytes []byte, oscType string) (OSCArg, []byte, error) { var readArgError error @@ -262,14 +291,14 @@ func readOSCArg(bytes []byte, oscType string) (OSCArg, []byte, error) { oscArg.Value = argString remainingBytes = bytesLeft case "i": - argInt, bytesLeft, error := readOSCInt(bytes) + argInt, bytesLeft, error := readOSCInt32(bytes) if error != nil { readArgError = error } oscArg.Value = argInt remainingBytes = bytesLeft case "f": - argFloat, bytesLeft, error := readOSCFloat(bytes) + argFloat, bytesLeft, error := readOSCFloat32(bytes) if error != nil { readArgError = error } @@ -282,6 +311,39 @@ func readOSCArg(bytes []byte, oscType string) (OSCArg, []byte, error) { } oscArg.Value = argBytes remainingBytes = bytesLeft + case "T": + oscArg.Value = true + remainingBytes = bytes + case "F": + oscArg.Value = false + remainingBytes = bytes + case "N": + oscArg.Value = nil + remainingBytes = bytes + case "I": + oscArg.Value = math.MaxInt32 + remainingBytes = bytes + case "r": + argColor, bytesLeft, error := readOSCColor(bytes) + if error != nil { + readArgError = error + } + oscArg.Value = argColor + remainingBytes = bytesLeft + case "h": + argInt, bytesLeft, error := readOSCInt64(bytes) + if error != nil { + readArgError = error + } + oscArg.Value = argInt + remainingBytes = bytesLeft + case "d": + argFloat, bytesLeft, error := readOSCFloat64(bytes) + if error != nil { + readArgError = error + } + oscArg.Value = argFloat + remainingBytes = bytesLeft default: fmt.Printf("unsupported osc type: %s\n", oscType) readArgError = errors.New("unsupported osc type: " + oscType) diff --git a/pkg/osc/osc_test.go b/pkg/osc/osc_test.go index 6b42c32..c6da803 100644 --- a/pkg/osc/osc_test.go +++ b/pkg/osc/osc_test.go @@ -2,6 +2,7 @@ package osc import ( "fmt" + "math" "reflect" "testing" ) @@ -140,3 +141,147 @@ func TestOSCEncoding(t *testing.T) { } } + +func TestOSCDecoding(t *testing.T) { + testCases := []struct { + description string + bytes []byte + expected OSCMessage + }{ + { + description: "simple address no args", + bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 0, 0, 0}, + expected: OSCMessage{Address: "/hello", Args: []OSCArg{}}, + }, + { + description: "simple address string arg", + bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 115, 0, 0, 97, 114, 103, 49, 0, 0, 0, 0}, + expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "s", Value: "arg1"}}}, + }, + { + description: "simple address integer arg", + bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 105, 0, 0, 0, 0, 0, 35}, + expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "i", Value: int32(35)}}}, + }, + { + description: "simple address float arg", + bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 102, 0, 0, 66, 10, 0, 0}, + expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "f", Value: float32(34.5)}}}, + }, + { + description: "simple address blob arg", + bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 98, 0, 0, 0, 0, 0, 4, 98, 108, 111, 98}, + expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "b", Value: []byte{98, 108, 111, 98}}}}, + }, + { + description: "simple address True arg", + bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 84, 0, 0}, + expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "T", Value: true}}}, + }, + { + description: "simple address False arg", + bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 70, 0, 0}, + expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "F", Value: false}}}, + }, + { + description: "simple address color arg", + bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 114, 0, 0, 20, 21, 22, 10}, + expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "r", Value: OSCColor{r: 20, g: 21, b: 22, a: 10}}}}, + }, + { + description: "simple address nil arg", + bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 78, 0, 0}, + expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "N", Value: nil}}}, + }, + { + description: "simple address Inifinitum arg", + bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 73, 0, 0}, + expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "I", Value: math.MaxInt32}}}, + }, + { + description: "simple address int64 arg", + bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 104, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255}, + expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "h", Value: int64(281474976710655)}}}, + }, + { + description: "simple address float64 arg", + bytes: []byte{ + 47, 104, 101, 108, 108, 111, 0, 0, 44, 100, 0, 0, 0x40, 0x29, 0x87, 0xec, 0x82, 0x74, 0xb9, 0xe6, + }, + expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "d", Value: float64(12.7654763)}}}, + }, + // TODO(jwetzell): support OSC array + // { + // description: "simple address array arg", + // bytes: []byte{ + // 47, 104, 101, 108, 108, 111, 0, 0, 44, 91, 100, 105, 93, 0, 0, 0, 0x40, 0x29, 0x87, 0xec, 0x82, 0x74, 0xb9, 0xe6, + // 0, 0, 3, 232, + // }, + // expected: OSCMessage{ + // Address: "/hello", + // Args: []OSCArg{ + // []OSCArg{ + // {Type: "d", Value: 12.7654763}, + // {Type: "i", Value: 1000}, + // }, + // }, + // }, + // }, + { + description: "simple address no type string", + bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0}, + expected: OSCMessage{ + Address: "/hello", + Args: []OSCArg{}, + }, + }, + { + description: "osc 1.0 spec example 1", + bytes: []byte{ + 47, 111, 115, 99, 105, 108, 108, 97, 116, 111, 114, 47, 52, 47, 102, 114, 101, 113, 117, 101, 110, 99, 121, 0, 44, + 102, 0, 0, 67, 220, 0, 0, + }, + expected: OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: float32(440)}}}, + }, + { + description: "osc 1.0 spec example 2", + bytes: []byte{ + 47, 102, 111, 111, 0, 0, 0, 0, 44, 105, 105, 115, 102, 102, 0, 0, 0, 0, 3, 232, 255, 255, 255, 255, 104, 101, 108, + 108, 111, 0, 0, 0, 63, 157, 243, 182, 64, 181, 178, 45, + }, + expected: OSCMessage{ + Address: "/foo", + Args: []OSCArg{ + {Type: "i", Value: int32(1000)}, + {Type: "i", Value: int32(-1)}, + {Type: "s", Value: "hello"}, + // thanks IEEE 754 + {Type: "f", Value: float32(1.2339999675750732421875)}, + {Type: "f", Value: float32(5.677999973297119140625)}, + }, + }, + }, + } + + for _, testCase := range testCases { + + actual, error := FromBytes(testCase.bytes) + + if error != nil { + fmt.Println(error) + t.Errorf("Test '%s' failed to encode properly", testCase.description) + } + + if !reflect.DeepEqual(actual.Address, testCase.expected.Address) { + t.Errorf("Test '%s' failed to encode address properly", testCase.description) + fmt.Printf("expected: %v\n", testCase.expected.Address) + fmt.Printf("actual: %v\n", actual.Address) + } + + if !reflect.DeepEqual(actual.Args, testCase.expected.Args) { + t.Errorf("Test '%s' failed to encode args properly", testCase.description) + fmt.Printf("expected: %v\n", testCase.expected.Args) + fmt.Printf("actual: %v\n", actual.Args) + } + } +}