mirror of
https://github.com/jwetzell/osc-go.git
synced 2026-08-18 04:53:58 +00:00
Compare commits
19 Commits
v0.2.1
...
makeosc/v1.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
| d82919e9f1 | |||
| 4078efa090 | |||
| 53864a35dc | |||
| 2ec4be5426 | |||
| 5f5c34c4ea | |||
| 16093f2509 | |||
| 191d74a20a | |||
| 738b87b03f | |||
| 42b1f9cf8a | |||
| 16e1ad039e | |||
| eb539dd319 | |||
| 1465663a63 | |||
| dd430dd380 | |||
| 9452268042 | |||
| b621944dc5 | |||
| 8fa8aa3a55 | |||
| 1d2684f446 | |||
| 2548771fe3 | |||
| 4744321d71 |
+2
-1
@@ -1 +1,2 @@
|
|||||||
build
|
build
|
||||||
|
coverage*
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
A collection of command line OSC utilities written in Go. Mainly an exercise in learning Go.
|
A mostly complete OSC implementation and collection of command line OSC utilities written in Go. Mainly an exercise in learning Go.
|
||||||
|
|
||||||
# `sendosc`
|
## Utilities
|
||||||
# `makeosc`
|
|
||||||
# `receiveosc`
|
### `sendosc`
|
||||||
|
### `makeosc`
|
||||||
|
### `receiveosc`
|
||||||
|
|||||||
@@ -4,30 +4,41 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *OSCBundle) ToBytes() []byte {
|
func (b *OSCBundle) ToBytes() ([]byte, error) {
|
||||||
|
|
||||||
bytes := stringToOSCBytes("#bundle")
|
bytes := stringToOSCBytes("#bundle")
|
||||||
|
|
||||||
bytes = append(bytes, timeTagToOSCBytes(b.TimeTag)...)
|
timeTagBytes, err := timeTagToOSCBytes(b.TimeTag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bytes = append(bytes, timeTagBytes...)
|
||||||
|
|
||||||
for _, packet := range b.Contents {
|
for _, packet := range b.Contents {
|
||||||
packetBytes := packet.ToBytes()
|
packetBytes, err := packet.ToBytes()
|
||||||
packetLength := len(packet.ToBytes())
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
packetLength := len(packetBytes)
|
||||||
|
|
||||||
bytes = append(bytes, int32ToOSCBytes(int32(packetLength))...)
|
packetLengthBytes, err := int32ToOSCBytes(int32(packetLength))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bytes = append(bytes, packetLengthBytes...)
|
||||||
bytes = append(bytes, packetBytes...)
|
bytes = append(bytes, packetBytes...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return bytes
|
return bytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func BundleFromBytes(bytes []byte) (*OSCBundle, []byte, error) {
|
func BundleFromBytes(bytes []byte) (*OSCBundle, []byte, error) {
|
||||||
if len(bytes) < 20 {
|
if len(bytes) < 20 {
|
||||||
return nil, bytes, errors.New("bundle has to be at least 20 bytes")
|
return nil, bytes, errors.New("OSC Bundle has to be at least 20 bytes")
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes[0] != 35 {
|
if bytes[0] != 35 {
|
||||||
return nil, bytes, errors.New("bundle must start with a #")
|
return nil, bytes, errors.New("OSC Bundle must start with a #")
|
||||||
}
|
}
|
||||||
|
|
||||||
bundleHeader, bytesAfterBundleHeader, err := readOSCString(bytes)
|
bundleHeader, bytesAfterBundleHeader, err := readOSCString(bytes)
|
||||||
@@ -37,7 +48,7 @@ func BundleFromBytes(bytes []byte) (*OSCBundle, []byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if bundleHeader != "#bundle" {
|
if bundleHeader != "#bundle" {
|
||||||
return nil, bytesAfterBundleHeader, errors.New("bundle must start with #bundle string")
|
return nil, bytesAfterBundleHeader, errors.New("OSC Bundle must start with #bundle string")
|
||||||
}
|
}
|
||||||
|
|
||||||
timeTag, bytesAfterTimeTag, err := readOSCTimeTag(bytesAfterBundleHeader)
|
timeTag, bytesAfterTimeTag, err := readOSCTimeTag(bytesAfterBundleHeader)
|
||||||
|
|||||||
+164
-41
@@ -1,89 +1,212 @@
|
|||||||
package osc
|
package osc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOSCBundleEncoding(t *testing.T) {
|
func TestGoodOSCBundleEncoding(t *testing.T) {
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
description string
|
name string
|
||||||
|
bundle *OSCBundle
|
||||||
|
expected []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple contents single message",
|
||||||
|
bundle: &OSCBundle{
|
||||||
|
TimeTag: OSCTimeTag{
|
||||||
|
seconds: 32,
|
||||||
|
fractionalSeconds: 0,
|
||||||
|
},
|
||||||
|
Contents: []OSCPacket{&OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: float32(440)}}}},
|
||||||
|
},
|
||||||
|
expected: []byte{35, 98, 117, 110, 100, 108, 101, 0, 0, 0, 0,
|
||||||
|
32, 0, 0, 0, 0, 0, 0, 0, 32, 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},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
got, err := testCase.bundle.ToBytes()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to encode properly: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, testCase.expected) {
|
||||||
|
t.Fatalf("failed to encode properly got '%v', expected '%v'", got, testCase.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadOSCBundleEncoding(t *testing.T) {
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
bundle *OSCBundle
|
bundle *OSCBundle
|
||||||
expected []byte
|
errorString string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"simple contents single message",
|
name: "bundle contains message with bad address",
|
||||||
&OSCBundle{
|
bundle: &OSCBundle{
|
||||||
TimeTag: OSCTimeTag{
|
TimeTag: OSCTimeTag{
|
||||||
seconds: 32,
|
seconds: 32,
|
||||||
fractionalSeconds: 0,
|
fractionalSeconds: 0,
|
||||||
},
|
},
|
||||||
Contents: []OSCPacket{&OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: float32(440)}}}},
|
Contents: []OSCPacket{&OSCMessage{Address: "hello", Args: []OSCArg{}}},
|
||||||
},
|
},
|
||||||
[]byte{35, 98, 117, 110, 100, 108, 101, 0, 0, 0, 0,
|
errorString: "OSC Message address must start with /",
|
||||||
32, 0, 0, 0, 0, 0, 0, 0, 32, 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},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
|
||||||
actual := testCase.bundle.ToBytes()
|
got, err := testCase.bundle.ToBytes()
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, testCase.expected) {
|
if err == nil {
|
||||||
t.Errorf("Test '%s' failed to encode properly", testCase.description)
|
t.Fatalf("OSCBundle.ToBytes() expected to fail but got: %+v", got)
|
||||||
fmt.Printf("expected: %v\n", testCase.expected)
|
}
|
||||||
fmt.Printf("actual: %v\n", actual)
|
|
||||||
}
|
if err.Error() != testCase.errorString {
|
||||||
|
t.Fatalf("OSCBundle.ToBytes() got error '%s', expected '%s'", err.Error(), testCase.errorString)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOSCBundleDecoding(t *testing.T) {
|
func TestGoodOSCBundleDecoding(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
description string
|
name string
|
||||||
expected *OSCBundle
|
expected *OSCBundle
|
||||||
bytes []byte
|
bytes []byte
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"simple contents single message",
|
name: "simple contents single message",
|
||||||
&OSCBundle{
|
expected: &OSCBundle{
|
||||||
TimeTag: OSCTimeTag{
|
TimeTag: OSCTimeTag{
|
||||||
seconds: 32,
|
seconds: 32,
|
||||||
fractionalSeconds: 0,
|
fractionalSeconds: 0,
|
||||||
},
|
},
|
||||||
Contents: []OSCPacket{&OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: float32(440)}}}},
|
Contents: []OSCPacket{&OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: float32(440)}}}},
|
||||||
},
|
},
|
||||||
[]byte{35, 98, 117, 110, 100, 108, 101, 0, 0, 0, 0,
|
bytes: []byte{35, 98, 117, 110, 100, 108, 101, 0, 0, 0, 0,
|
||||||
32, 0, 0, 0, 0, 0, 0, 0, 32, 47, 111,
|
32, 0, 0, 0, 0, 0, 0, 0, 32, 47, 111,
|
||||||
115, 99, 105, 108, 108, 97, 116, 111, 114, 47, 52,
|
115, 99, 105, 108, 108, 97, 116, 111, 114, 47, 52,
|
||||||
47, 102, 114, 101, 113, 117, 101, 110, 99, 121, 0,
|
47, 102, 114, 101, 113, 117, 101, 110, 99, 121, 0,
|
||||||
44, 102, 0, 0, 67, 220, 0, 0},
|
44, 102, 0, 0, 67, 220, 0, 0},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "simple contents nested bundle",
|
||||||
|
expected: &OSCBundle{
|
||||||
|
TimeTag: OSCTimeTag{
|
||||||
|
seconds: 32,
|
||||||
|
fractionalSeconds: 0,
|
||||||
|
},
|
||||||
|
Contents: []OSCPacket{&OSCBundle{
|
||||||
|
TimeTag: OSCTimeTag{
|
||||||
|
seconds: 64,
|
||||||
|
fractionalSeconds: 0,
|
||||||
|
},
|
||||||
|
Contents: []OSCPacket{&OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: float32(440)}}}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
bytes: []byte{35, 98, 117, 110, 100, 108, 101, 0, // #bundle
|
||||||
|
0, 0, 0, 32, 0, 0, 0, 0, // time tag
|
||||||
|
0, 0, 0, 52, // content size
|
||||||
|
35, 98, 117, 110, 100, 108, 101, 0, // #bundle
|
||||||
|
0, 0, 0, 64, 0, 0, 0, 0, // time tag
|
||||||
|
0, 0, 0, 32, // content size
|
||||||
|
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},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
actual, remainingBytes, error := BundleFromBytes(testCase.bytes)
|
||||||
|
|
||||||
actual, remainingBytes, error := BundleFromBytes(testCase.bytes)
|
if error != nil {
|
||||||
|
t.Fatalf("failed to decode properly: %s", error.Error())
|
||||||
|
}
|
||||||
|
|
||||||
if error != nil {
|
if len(remainingBytes) > 0 {
|
||||||
fmt.Println(error)
|
t.Fatalf("should not have any remaining bytes")
|
||||||
t.Errorf("Test '%s' failed to encode properly", testCase.description)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if len(remainingBytes) > 0 {
|
|
||||||
t.Errorf("Test '%s' should not have any remaining bytes", testCase.description)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, testCase.expected) {
|
|
||||||
t.Errorf("Test '%s' failed to encode bundle properly", testCase.description)
|
|
||||||
fmt.Printf("expected: %v\n", testCase.expected)
|
|
||||||
fmt.Printf("actual: %v\n", actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, testCase.expected) {
|
||||||
|
t.Fatalf("failed to decode properly got '%v', expected '%v'", actual, testCase.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadOSCBundleDecoding(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
bytes []byte
|
||||||
|
errorString string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty byte array",
|
||||||
|
bytes: []byte{},
|
||||||
|
errorString: "OSC Bundle has to be at least 20 bytes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "does not start with #",
|
||||||
|
bytes: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
errorString: "OSC Bundle must start with a #",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "does not start with #bundle",
|
||||||
|
bytes: []byte{35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
errorString: "OSC Bundle must start with #bundle string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bundle header not properly null terminated",
|
||||||
|
bytes: []byte{
|
||||||
|
35, 98, 117, 110, 100, 108, 101,
|
||||||
|
35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35},
|
||||||
|
errorString: "OSC string must be null-terminated",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bundle contains incorrect size",
|
||||||
|
bytes: []byte{
|
||||||
|
35, 98, 117, 110, 100, 108, 101, 0, // #bundle
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, // time tag
|
||||||
|
0, 0, 0, 100, // content size of 100 but only 10 bytes of content
|
||||||
|
35, 35, 35, 35, 35, 35, 35, 35, 35, 35},
|
||||||
|
errorString: "bundle doesn't have enough bytes for the content size it specifies",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bundle doesn't contain message or bundle",
|
||||||
|
bytes: []byte{
|
||||||
|
35, 98, 117, 110, 100, 108, 101, 0, // #bundle
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, // time tag
|
||||||
|
0, 0, 0, 10,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
errorString: "bundle contents does not look a bundle or message",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
got, _, err := BundleFromBytes(testCase.bytes)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("BundleFromBytes expected to fail but got: %+v", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err.Error() != testCase.errorString {
|
||||||
|
t.Fatalf("BundleFromBytes got error '%s', expected '%s'", err.Error(), testCase.errorString)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,10 @@ func make(address string, args []string, types []string, slip bool) {
|
|||||||
oscMessage.Args = append(oscMessage.Args, argToTypedArg(arg, oscType))
|
oscMessage.Args = append(oscMessage.Args, argToTypedArg(arg, oscType))
|
||||||
}
|
}
|
||||||
|
|
||||||
oscMessageBuffer := oscMessage.ToBytes()
|
oscMessageBuffer, err := oscMessage.ToBytes()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
if slip {
|
if slip {
|
||||||
oscMessageBuffer = slipEncode(oscMessageBuffer)
|
oscMessageBuffer = slipEncode(oscMessageBuffer)
|
||||||
|
|||||||
@@ -208,7 +208,10 @@ func send(host string, port int32, address string, args []string, types []string
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
oscMessageBuffer := oscMessage.ToBytes()
|
oscMessageBuffer, err := oscMessage.ToBytes()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
if slip {
|
if slip {
|
||||||
oscMessageBuffer = slipEncode(oscMessageBuffer)
|
oscMessageBuffer = slipEncode(oscMessageBuffer)
|
||||||
|
|||||||
+16
-5
@@ -5,8 +5,16 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *OSCMessage) ToBytes() []byte {
|
func (m *OSCMessage) ToBytes() ([]byte, error) {
|
||||||
//TODO(jwetzell): add error handling
|
|
||||||
|
if len(m.Address) == 0 {
|
||||||
|
return nil, errors.New("OSC Message must have an address")
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Address[0] != '/' {
|
||||||
|
return nil, errors.New("OSC Message address must start with /")
|
||||||
|
}
|
||||||
|
|
||||||
oscBuffer := []byte{}
|
oscBuffer := []byte{}
|
||||||
|
|
||||||
oscBuffer = append(oscBuffer, stringToOSCBytes(m.Address)...)
|
oscBuffer = append(oscBuffer, stringToOSCBytes(m.Address)...)
|
||||||
@@ -18,11 +26,14 @@ func (m *OSCMessage) ToBytes() []byte {
|
|||||||
for _, arg := range m.Args {
|
for _, arg := range m.Args {
|
||||||
sb.WriteString(arg.Type)
|
sb.WriteString(arg.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
oscBuffer = append(oscBuffer, stringToOSCBytes(sb.String())...)
|
oscBuffer = append(oscBuffer, stringToOSCBytes(sb.String())...)
|
||||||
oscBuffer = append(oscBuffer, argsToBuffer(m.Args)...)
|
argsBuffer, err := argsToBuffer(m.Args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
oscBuffer = append(oscBuffer, argsBuffer...)
|
||||||
|
|
||||||
return oscBuffer
|
return oscBuffer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func MessageFromBytes(bytes []byte) (*OSCMessage, error) {
|
func MessageFromBytes(bytes []byte) (*OSCMessage, error) {
|
||||||
|
|||||||
+174
-3
@@ -128,16 +128,117 @@ func TestGoodOSCMessageEncoding(t *testing.T) {
|
|||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
actual := testCase.message.ToBytes()
|
got, err := testCase.message.ToBytes()
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, testCase.expected) {
|
if err != nil {
|
||||||
t.Fatalf("failed to encode properly got '%v', expected '%v'", actual, testCase.expected)
|
t.Fatalf("failed to encode properly: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, testCase.expected) {
|
||||||
|
t.Fatalf("failed to encode properly got '%v', expected '%v'", got, testCase.expected)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBadOSCMessageEncoding(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
message *OSCMessage
|
||||||
|
errorString string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty message",
|
||||||
|
message: &OSCMessage{},
|
||||||
|
errorString: "OSC Message must have an address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "address does not start with /",
|
||||||
|
message: &OSCMessage{Address: "hello"},
|
||||||
|
errorString: "OSC Message address must start with /",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arg with unsupported type",
|
||||||
|
message: &OSCMessage{
|
||||||
|
Address: "/hello",
|
||||||
|
Args: []OSCArg{{Type: "x", Value: "unsupported"}},
|
||||||
|
},
|
||||||
|
errorString: "unsupported OSC argument type: x",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string arg that is not a string",
|
||||||
|
message: &OSCMessage{
|
||||||
|
Address: "/hello",
|
||||||
|
Args: []OSCArg{{Type: "s", Value: 123}},
|
||||||
|
},
|
||||||
|
errorString: "OSC arg had string type but non-string value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int32 arg that is not a number",
|
||||||
|
message: &OSCMessage{
|
||||||
|
Address: "/hello",
|
||||||
|
Args: []OSCArg{{Type: "i", Value: "not an int"}},
|
||||||
|
},
|
||||||
|
errorString: "OSC arg had int32 type but non-number value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float32 arg that is not a number",
|
||||||
|
message: &OSCMessage{
|
||||||
|
Address: "/hello",
|
||||||
|
Args: []OSCArg{{Type: "f", Value: "not a float"}},
|
||||||
|
},
|
||||||
|
errorString: "OSC arg had float32 type but non-number value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64 arg that is not a number",
|
||||||
|
message: &OSCMessage{
|
||||||
|
Address: "/hello",
|
||||||
|
Args: []OSCArg{{Type: "h", Value: "not an int"}},
|
||||||
|
},
|
||||||
|
errorString: "OSC arg had int64 type but non-number value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64 arg that is not a number",
|
||||||
|
message: &OSCMessage{
|
||||||
|
Address: "/hello",
|
||||||
|
Args: []OSCArg{{Type: "d", Value: "not a float"}},
|
||||||
|
},
|
||||||
|
errorString: "OSC arg had float64 type but non-number value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "blob arg that is not a byte array",
|
||||||
|
message: &OSCMessage{
|
||||||
|
Address: "/hello",
|
||||||
|
Args: []OSCArg{{Type: "b", Value: "not a blob"}},
|
||||||
|
},
|
||||||
|
errorString: "OSC arg had blob type but non-blob value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "color arg that is not an OSCColor",
|
||||||
|
message: &OSCMessage{
|
||||||
|
Address: "/hello",
|
||||||
|
Args: []OSCArg{{Type: "r", Value: "not a color"}},
|
||||||
|
},
|
||||||
|
errorString: "OSC arg had color type but non-color value",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
got, err := testCase.message.ToBytes()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("OSCMessage.ToBytes() expected to fail but got: %+v", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err.Error() != testCase.errorString {
|
||||||
|
t.Fatalf("OSCMessage.ToBytes() got error '%s', expected '%s'", err.Error(), testCase.errorString)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGoodOSCMessageDecoding(t *testing.T) {
|
func TestGoodOSCMessageDecoding(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -324,6 +425,76 @@ func TestBadOSCMessageDecoding(t *testing.T) {
|
|||||||
},
|
},
|
||||||
errorString: "OSC string is not properly padded",
|
errorString: "OSC string is not properly padded",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "int32 arg not 4 bytes",
|
||||||
|
bytes: []byte{
|
||||||
|
47, 104, 101, 108, 108, 111, 0, 0, 44, 105, 0, 0, 0,
|
||||||
|
},
|
||||||
|
errorString: "OSC int32 arg is not 4 bytes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64 arg not 8 bytes",
|
||||||
|
bytes: []byte{
|
||||||
|
47, 104, 101, 108, 108, 111, 0, 0, 44, 104, 0, 0, 0, 0, 0, 0,
|
||||||
|
},
|
||||||
|
errorString: "OSC int64 arg is not 8 bytes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float32 arg not 4 bytes",
|
||||||
|
bytes: []byte{
|
||||||
|
47, 104, 101, 108, 108, 111, 0, 0, 44, 102, 0, 0, 66,
|
||||||
|
},
|
||||||
|
errorString: "OSC float32 arg is not 4 bytes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64 arg not 8 bytes",
|
||||||
|
bytes: []byte{
|
||||||
|
47, 104, 101, 108, 108, 111, 0, 0, 44, 100, 0, 0, 0,
|
||||||
|
},
|
||||||
|
errorString: "OSC float64 arg is not 8 bytes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "blob arg size not valid",
|
||||||
|
bytes: []byte{
|
||||||
|
47, 104, 101, 108, 108, 111, 0, 0, 44, 98, 0, 0, 0, 0, 0,
|
||||||
|
},
|
||||||
|
errorString: "OSC blob arg size not valid: OSC int32 arg is not 4 bytes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "blob arg size mismatch",
|
||||||
|
bytes: []byte{
|
||||||
|
47, 104, 101, 108, 108, 111, 0, 0, 44, 98, 0, 0, 0, 0, 0, 4, 98, 108, 111,
|
||||||
|
},
|
||||||
|
errorString: "OSC blob arg size not valid: size specified is larger than remaining bytes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "color arg not 4 bytes",
|
||||||
|
bytes: []byte{
|
||||||
|
47, 104, 101, 108, 108, 111, 0, 0, 44, 114, 0, 0, 20, 21,
|
||||||
|
},
|
||||||
|
errorString: "OSC color arg is not 4 bytes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "time tag arg seconds not complete",
|
||||||
|
bytes: []byte{
|
||||||
|
47, 104, 101, 108, 108, 111, 0, 0, 44, 116, 0, 0, 0,
|
||||||
|
},
|
||||||
|
errorString: "OSC time tag seconds are not valid: OSC int32 arg is not 4 bytes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "time tag arg fractional seconds not complete",
|
||||||
|
bytes: []byte{
|
||||||
|
47, 104, 101, 108, 108, 111, 0, 0, 44, 116, 0, 0, 0, 32, 0, 0, 0,
|
||||||
|
},
|
||||||
|
errorString: "OSC time tag fractional seconds are not valid: OSC int32 arg is not 4 bytes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown arg type",
|
||||||
|
bytes: []byte{
|
||||||
|
47, 104, 101, 108, 108, 111, 0, 0, 44, 120, 0, 0,
|
||||||
|
},
|
||||||
|
errorString: "unsupported OSC argument type: x",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
|
|||||||
@@ -26,47 +26,51 @@ func stringToOSCBytes(rawString string) []byte {
|
|||||||
return []byte(sb.String())
|
return []byte(sb.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func int32ToOSCBytes(number int32) []byte {
|
func int32ToOSCBytes(number int32) ([]byte, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err := binary.Write(&buf, binary.BigEndian, number)
|
err := binary.Write(&buf, binary.BigEndian, number)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
return buf.Bytes()
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func int64ToOSCBytes(number int64) []byte {
|
func int64ToOSCBytes(number int64) ([]byte, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err := binary.Write(&buf, binary.BigEndian, number)
|
err := binary.Write(&buf, binary.BigEndian, number)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
return buf.Bytes()
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func float32ToOSCBytes(number float32) []byte {
|
func float32ToOSCBytes(number float32) ([]byte, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err := binary.Write(&buf, binary.BigEndian, number)
|
err := binary.Write(&buf, binary.BigEndian, number)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
return buf.Bytes()
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func float64ToOSCBytes(number float64) []byte {
|
func float64ToOSCBytes(number float64) ([]byte, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err := binary.Write(&buf, binary.BigEndian, number)
|
err := binary.Write(&buf, binary.BigEndian, number)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
return buf.Bytes()
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func byteArrayToOSCBytes(bytes []byte) []byte {
|
func byteArrayToOSCBytes(bytes []byte) ([]byte, error) {
|
||||||
oscBytes := []byte{}
|
oscBytes := []byte{}
|
||||||
|
|
||||||
bytesSize := len(bytes)
|
bytesSize := len(bytes)
|
||||||
oscBytes = append(oscBytes, int32ToOSCBytes(int32(bytesSize))...)
|
bytesSizeBytes, err := int32ToOSCBytes(int32(bytesSize))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
oscBytes = append(oscBytes, bytesSizeBytes...)
|
||||||
oscBytes = append(oscBytes, bytes...)
|
oscBytes = append(oscBytes, bytes...)
|
||||||
|
|
||||||
padLength := 4 - (bytesSize % 4)
|
padLength := 4 - (bytesSize % 4)
|
||||||
@@ -76,17 +80,24 @@ func byteArrayToOSCBytes(bytes []byte) []byte {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return oscBytes
|
return oscBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func timeTagToOSCBytes(timeTag OSCTimeTag) []byte {
|
func timeTagToOSCBytes(timeTag OSCTimeTag) ([]byte, error) {
|
||||||
timeTagBytes := int32ToOSCBytes(timeTag.seconds)
|
timeTagBytes, err := int32ToOSCBytes(timeTag.seconds)
|
||||||
timeTagBytes = append(timeTagBytes, int32ToOSCBytes(timeTag.fractionalSeconds)...)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fractionalSecondsBytes, err := int32ToOSCBytes(timeTag.fractionalSeconds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
timeTagBytes = append(timeTagBytes, fractionalSecondsBytes...)
|
||||||
|
|
||||||
return timeTagBytes
|
return timeTagBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func argsToBuffer(args []OSCArg) []byte {
|
func argsToBuffer(args []OSCArg) ([]byte, error) {
|
||||||
//TODO(jwetzell): add error handling
|
//TODO(jwetzell): add error handling
|
||||||
var argBuffers = []byte{}
|
var argBuffers = []byte{}
|
||||||
|
|
||||||
@@ -96,35 +107,67 @@ func argsToBuffer(args []OSCArg) []byte {
|
|||||||
if value, ok := arg.Value.(string); ok {
|
if value, ok := arg.Value.(string); ok {
|
||||||
argBuffers = append(argBuffers, stringToOSCBytes(value)...)
|
argBuffers = append(argBuffers, stringToOSCBytes(value)...)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("OSC arg had string type but non-string value.")
|
return nil, errors.New("OSC arg had string type but non-string value")
|
||||||
}
|
}
|
||||||
case "i":
|
case "i":
|
||||||
if value, ok := arg.Value.(int); ok {
|
if value, ok := arg.Value.(int); ok {
|
||||||
argBuffers = append(argBuffers, int32ToOSCBytes(int32(value))...)
|
valueBytes, err := int32ToOSCBytes(int32(value))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argBuffers = append(argBuffers, valueBytes...)
|
||||||
} else if value, ok := arg.Value.(int32); ok {
|
} else if value, ok := arg.Value.(int32); ok {
|
||||||
argBuffers = append(argBuffers, int32ToOSCBytes(value)...)
|
valueBytes, err := int32ToOSCBytes(int32(value))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argBuffers = append(argBuffers, valueBytes...)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("OSC arg had integer type but non-integer value.")
|
return nil, errors.New("OSC arg had int32 type but non-number value")
|
||||||
}
|
}
|
||||||
case "f":
|
case "f":
|
||||||
if value, ok := arg.Value.(float32); ok {
|
if value, ok := arg.Value.(float32); ok {
|
||||||
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
|
valueBytes, err := float32ToOSCBytes(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argBuffers = append(argBuffers, valueBytes...)
|
||||||
} else if value, ok := arg.Value.(float64); ok {
|
} else if value, ok := arg.Value.(float64); ok {
|
||||||
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
|
valueBytes, err := float32ToOSCBytes(float32(value))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argBuffers = append(argBuffers, valueBytes...)
|
||||||
} else if value, ok := arg.Value.(int); ok {
|
} else if value, ok := arg.Value.(int); ok {
|
||||||
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
|
valueBytes, err := float32ToOSCBytes(float32(value))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argBuffers = append(argBuffers, valueBytes...)
|
||||||
} else if value, ok := arg.Value.(int32); ok {
|
} else if value, ok := arg.Value.(int32); ok {
|
||||||
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
|
valueBytes, err := float32ToOSCBytes(float32(value))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argBuffers = append(argBuffers, valueBytes...)
|
||||||
} else if value, ok := arg.Value.(int64); ok {
|
} else if value, ok := arg.Value.(int64); ok {
|
||||||
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
|
valueBytes, err := float32ToOSCBytes(float32(value))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argBuffers = append(argBuffers, valueBytes...)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("OSC arg had float type but non-float value.")
|
return nil, errors.New("OSC arg had float32 type but non-number value")
|
||||||
}
|
}
|
||||||
case "b":
|
case "b":
|
||||||
if value, ok := arg.Value.([]byte); ok {
|
if value, ok := arg.Value.([]byte); ok {
|
||||||
argBuffers = append(argBuffers, byteArrayToOSCBytes(value)...)
|
valueBytes, err := byteArrayToOSCBytes(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argBuffers = append(argBuffers, valueBytes...)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("OSC arg had blob type but non-blob value.")
|
return nil, errors.New("OSC arg had blob type but non-blob value")
|
||||||
}
|
}
|
||||||
case "T":
|
case "T":
|
||||||
argBuffers = append(argBuffers, make([]byte, 0)...)
|
argBuffers = append(argBuffers, make([]byte, 0)...)
|
||||||
@@ -136,39 +179,74 @@ func argsToBuffer(args []OSCArg) []byte {
|
|||||||
argBuffers = append(argBuffers, make([]byte, 0)...)
|
argBuffers = append(argBuffers, make([]byte, 0)...)
|
||||||
case "r":
|
case "r":
|
||||||
color, ok := arg.Value.(OSCColor)
|
color, ok := arg.Value.(OSCColor)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("OSC arg had color type but non-color value")
|
||||||
|
}
|
||||||
if ok {
|
if ok {
|
||||||
colorBytes := []byte{color.r, color.g, color.b, color.a}
|
colorBytes := []byte{color.r, color.g, color.b, color.a}
|
||||||
argBuffers = append(argBuffers, colorBytes...)
|
argBuffers = append(argBuffers, colorBytes...)
|
||||||
}
|
}
|
||||||
case "h":
|
case "h":
|
||||||
if value, ok := arg.Value.(int); ok {
|
if value, ok := arg.Value.(int); ok {
|
||||||
argBuffers = append(argBuffers, int64ToOSCBytes(int64(value))...)
|
valueBytes, err := int64ToOSCBytes(int64(value))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argBuffers = append(argBuffers, valueBytes...)
|
||||||
} else if value, ok := arg.Value.(int32); ok {
|
} else if value, ok := arg.Value.(int32); ok {
|
||||||
argBuffers = append(argBuffers, int64ToOSCBytes(int64(value))...)
|
valueBytes, err := int64ToOSCBytes(int64(value))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argBuffers = append(argBuffers, valueBytes...)
|
||||||
} else if value, ok := arg.Value.(int64); ok {
|
} else if value, ok := arg.Value.(int64); ok {
|
||||||
argBuffers = append(argBuffers, int64ToOSCBytes(value)...)
|
valueBytes, err := int64ToOSCBytes(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argBuffers = append(argBuffers, valueBytes...)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("OSC arg had integer type but non-integer value.")
|
return nil, errors.New("OSC arg had int64 type but non-number value")
|
||||||
}
|
}
|
||||||
case "d":
|
case "d":
|
||||||
if value, ok := arg.Value.(float32); ok {
|
if value, ok := arg.Value.(float32); ok {
|
||||||
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
|
valueBytes, err := float64ToOSCBytes(float64(value))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argBuffers = append(argBuffers, valueBytes...)
|
||||||
} else if value, ok := arg.Value.(float64); ok {
|
} else if value, ok := arg.Value.(float64); ok {
|
||||||
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
|
valueBytes, err := float64ToOSCBytes(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argBuffers = append(argBuffers, valueBytes...)
|
||||||
} else if value, ok := arg.Value.(int); ok {
|
} else if value, ok := arg.Value.(int); ok {
|
||||||
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
|
valueBytes, err := float64ToOSCBytes(float64(value))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argBuffers = append(argBuffers, valueBytes...)
|
||||||
} else if value, ok := arg.Value.(int32); ok {
|
} else if value, ok := arg.Value.(int32); ok {
|
||||||
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
|
valueBytes, err := float64ToOSCBytes(float64(value))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argBuffers = append(argBuffers, valueBytes...)
|
||||||
} else if value, ok := arg.Value.(int64); ok {
|
} else if value, ok := arg.Value.(int64); ok {
|
||||||
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
|
valueBytes, err := float64ToOSCBytes(float64(value))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
argBuffers = append(argBuffers, valueBytes...)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("OSC arg had float type but non-float value.")
|
return nil, errors.New("OSC arg had float64 type but non-number value")
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
fmt.Printf("unhandled osc type: %s.\n", oscType)
|
return nil, fmt.Errorf("unsupported OSC argument type: %s", oscType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return argBuffers
|
return argBuffers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readOSCString(bytes []byte) (string, []byte, error) {
|
func readOSCString(bytes []byte) (string, []byte, error) {
|
||||||
@@ -206,7 +284,7 @@ func readOSCString(bytes []byte) (string, []byte, error) {
|
|||||||
|
|
||||||
func readOSCInt32(bytes []byte) (int32, []byte, error) {
|
func readOSCInt32(bytes []byte) (int32, []byte, error) {
|
||||||
if len(bytes) < 4 {
|
if len(bytes) < 4 {
|
||||||
return 0, bytes, errors.New("int data must be at least 4 bytes large")
|
return 0, bytes, errors.New("OSC int32 arg is not 4 bytes")
|
||||||
}
|
}
|
||||||
bits := binary.BigEndian.Uint32(bytes[0:4])
|
bits := binary.BigEndian.Uint32(bytes[0:4])
|
||||||
return int32(bits), bytes[4:], nil
|
return int32(bits), bytes[4:], nil
|
||||||
@@ -214,7 +292,7 @@ func readOSCInt32(bytes []byte) (int32, []byte, error) {
|
|||||||
|
|
||||||
func readOSCInt64(bytes []byte) (int64, []byte, error) {
|
func readOSCInt64(bytes []byte) (int64, []byte, error) {
|
||||||
if len(bytes) < 8 {
|
if len(bytes) < 8 {
|
||||||
return 0, bytes, errors.New("int data must be at least 4 bytes large")
|
return 0, bytes, errors.New("OSC int64 arg is not 8 bytes")
|
||||||
}
|
}
|
||||||
bits := binary.BigEndian.Uint64(bytes[0:8])
|
bits := binary.BigEndian.Uint64(bytes[0:8])
|
||||||
return int64(bits), bytes[8:], nil
|
return int64(bits), bytes[8:], nil
|
||||||
@@ -222,7 +300,7 @@ func readOSCInt64(bytes []byte) (int64, []byte, error) {
|
|||||||
|
|
||||||
func readOSCFloat32(bytes []byte) (float32, []byte, error) {
|
func readOSCFloat32(bytes []byte) (float32, []byte, error) {
|
||||||
if len(bytes) < 4 {
|
if len(bytes) < 4 {
|
||||||
return 0, bytes, errors.New("float data must be at least 4 bytes large")
|
return 0, bytes, errors.New("OSC float32 arg is not 4 bytes")
|
||||||
}
|
}
|
||||||
bits := binary.BigEndian.Uint32(bytes[0:4])
|
bits := binary.BigEndian.Uint32(bytes[0:4])
|
||||||
return math.Float32frombits(bits), bytes[4:], nil
|
return math.Float32frombits(bits), bytes[4:], nil
|
||||||
@@ -230,7 +308,7 @@ func readOSCFloat32(bytes []byte) (float32, []byte, error) {
|
|||||||
|
|
||||||
func readOSCFloat64(bytes []byte) (float64, []byte, error) {
|
func readOSCFloat64(bytes []byte) (float64, []byte, error) {
|
||||||
if len(bytes) < 4 {
|
if len(bytes) < 4 {
|
||||||
return 0, bytes, errors.New("float data must be at least 4 bytes large")
|
return 0, bytes, errors.New("OSC float64 arg is not 8 bytes")
|
||||||
}
|
}
|
||||||
bits := binary.BigEndian.Uint64(bytes[0:8])
|
bits := binary.BigEndian.Uint64(bytes[0:8])
|
||||||
return math.Float64frombits(bits), bytes[8:], nil
|
return math.Float64frombits(bits), bytes[8:], nil
|
||||||
@@ -240,11 +318,11 @@ func readOSCBlob(bytes []byte) ([]byte, []byte, error) {
|
|||||||
blobLength, remainingBytes, err := readOSCInt32(bytes)
|
blobLength, remainingBytes, err := readOSCInt32(bytes)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, bytes, errors.New("problem reading blob data size")
|
return []byte{}, bytes, errors.New("OSC blob arg size not valid: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(remainingBytes) < int(blobLength) {
|
if len(remainingBytes) < int(blobLength) {
|
||||||
return []byte{}, bytes, errors.New("blob data specified a size larger than the remaining message data")
|
return []byte{}, bytes, errors.New("OSC blob arg size not valid: size specified is larger than remaining bytes")
|
||||||
}
|
}
|
||||||
|
|
||||||
blobLengthPadding := 4 - (blobLength % 4)
|
blobLengthPadding := 4 - (blobLength % 4)
|
||||||
@@ -258,7 +336,7 @@ func readOSCBlob(bytes []byte) ([]byte, []byte, error) {
|
|||||||
|
|
||||||
func readOSCColor(bytes []byte) (OSCColor, []byte, error) {
|
func readOSCColor(bytes []byte) (OSCColor, []byte, error) {
|
||||||
if len(bytes) < 4 {
|
if len(bytes) < 4 {
|
||||||
return OSCColor{0, 0, 0, 0}, bytes, errors.New("color data must be at least 4 bytes large")
|
return OSCColor{0, 0, 0, 0}, bytes, errors.New("OSC color arg is not 4 bytes")
|
||||||
}
|
}
|
||||||
oscColor := OSCColor{
|
oscColor := OSCColor{
|
||||||
r: bytes[0],
|
r: bytes[0],
|
||||||
@@ -268,14 +346,15 @@ func readOSCColor(bytes []byte) (OSCColor, []byte, error) {
|
|||||||
}
|
}
|
||||||
return oscColor, bytes[4:], nil
|
return oscColor, bytes[4:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readOSCTimeTag(bytes []byte) (OSCTimeTag, []byte, error) {
|
func readOSCTimeTag(bytes []byte) (OSCTimeTag, []byte, error) {
|
||||||
seconds, bytesAfterSeconds, err := readOSCInt32(bytes)
|
seconds, bytesAfterSeconds, err := readOSCInt32(bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return OSCTimeTag{}, bytes, err
|
return OSCTimeTag{}, bytes, fmt.Errorf("OSC time tag seconds are not valid: %s", err)
|
||||||
}
|
}
|
||||||
fractionalSeconds, remainingBytes, err := readOSCInt32(bytesAfterSeconds)
|
fractionalSeconds, remainingBytes, err := readOSCInt32(bytesAfterSeconds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return OSCTimeTag{}, bytes, err
|
return OSCTimeTag{}, bytes, fmt.Errorf("OSC time tag fractional seconds are not valid: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return OSCTimeTag{
|
return OSCTimeTag{
|
||||||
@@ -356,9 +435,16 @@ func readOSCArg(bytes []byte, oscType string) (OSCArg, []byte, error) {
|
|||||||
}
|
}
|
||||||
oscArg.Value = argFloat
|
oscArg.Value = argFloat
|
||||||
remainingBytes = bytesLeft
|
remainingBytes = bytesLeft
|
||||||
|
case "t":
|
||||||
|
argTimeTag, bytesLeft, err := readOSCTimeTag(bytes)
|
||||||
|
if err != nil {
|
||||||
|
readArgError = err
|
||||||
|
}
|
||||||
|
oscArg.Value = argTimeTag
|
||||||
|
remainingBytes = bytesLeft
|
||||||
default:
|
default:
|
||||||
fmt.Printf("unsupported osc type: %s\n", oscType)
|
fmt.Printf("unsupported osc type: %s\n", oscType)
|
||||||
readArgError = errors.New("unsupported osc type: " + oscType)
|
readArgError = errors.New("unsupported OSC argument type: " + oscType)
|
||||||
}
|
}
|
||||||
return oscArg, remainingBytes, readArgError
|
return oscArg, remainingBytes, readArgError
|
||||||
}
|
}
|
||||||
|
|||||||
+318
@@ -0,0 +1,318 @@
|
|||||||
|
package osc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGoodOSCArgsToBuffer(t *testing.T) {
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []OSCArg
|
||||||
|
expected []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "int arg",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "i",
|
||||||
|
Value: int(123),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{0, 0, 0, 123},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int32 arg",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "i",
|
||||||
|
Value: int32(123),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{0, 0, 0, 123},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float32 arg",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "f",
|
||||||
|
Value: float32(123),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{66, 246, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float32 arg with int value",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "f",
|
||||||
|
Value: int(123),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{66, 246, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float32 arg with int32 value",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "f",
|
||||||
|
Value: int32(123),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{66, 246, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float32 arg with int64 value",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "f",
|
||||||
|
Value: int64(123),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{66, 246, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64 arg",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "d",
|
||||||
|
Value: float64(123),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{64, 94, 192, 0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64 arg with float32 value",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "d",
|
||||||
|
Value: float32(123),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{64, 94, 192, 0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64 arg with int value",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "d",
|
||||||
|
Value: int(123),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{64, 94, 192, 0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64 arg with int32 value",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "d",
|
||||||
|
Value: int32(123),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{64, 94, 192, 0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "float64 arg with int64 value",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "d",
|
||||||
|
Value: int64(123),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{64, 94, 192, 0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64 arg",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "h",
|
||||||
|
Value: int64(123),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{0, 0, 0, 0, 0, 0, 0, 123},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64 arg with int32 value",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "h",
|
||||||
|
Value: int32(123),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{0, 0, 0, 0, 0, 0, 0, 123},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "blob arg",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "b",
|
||||||
|
Value: []byte{1, 2, 3},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{0, 0, 0, 3, 1, 2, 3, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "true arg",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "T",
|
||||||
|
Value: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "false arg",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "F",
|
||||||
|
Value: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil arg",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "N",
|
||||||
|
Value: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "inifinitum arg",
|
||||||
|
args: []OSCArg{
|
||||||
|
{
|
||||||
|
Type: "I",
|
||||||
|
Value: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []byte{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
got, err := argsToBuffer(testCase.args)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to encode properly: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, testCase.expected) {
|
||||||
|
t.Fatalf("failed to encode properly got '%v', expected '%v'", got, testCase.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadOSCArgsToBuffer(t *testing.T) {
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []OSCArg
|
||||||
|
errorString string
|
||||||
|
}{}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
got, err := argsToBuffer(testCase.args)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("argsToBuffer expected to fail but got: %+v", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err.Error() != testCase.errorString {
|
||||||
|
t.Fatalf("argsToBuffer got error '%s', expected '%s'", err.Error(), testCase.errorString)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoodPacketFromBytes(t *testing.T) {
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
expected OSCPacket
|
||||||
|
bytes []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "message with no args",
|
||||||
|
expected: &OSCMessage{
|
||||||
|
Address: "/hello",
|
||||||
|
Args: []OSCArg{},
|
||||||
|
},
|
||||||
|
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bundle with one message with no args",
|
||||||
|
expected: &OSCBundle{
|
||||||
|
TimeTag: OSCTimeTag{
|
||||||
|
seconds: 32,
|
||||||
|
fractionalSeconds: 0,
|
||||||
|
},
|
||||||
|
Contents: []OSCPacket{&OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: float32(440)}}}},
|
||||||
|
},
|
||||||
|
bytes: []byte{35, 98, 117, 110, 100, 108, 101, 0, 0, 0, 0,
|
||||||
|
32, 0, 0, 0, 0, 0, 0, 0, 32, 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},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
got, remainingBytes, err := PacketFromBytes(testCase.bytes)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to decode properly: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(remainingBytes) != 0 {
|
||||||
|
t.Fatalf("failed to decode properly, expected no remaining bytes but got: %v", remainingBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, testCase.expected) {
|
||||||
|
t.Fatalf("failed to decode properly got '%v', expected '%v'", got, testCase.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadPacketFromBytes(t *testing.T) {
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
bytes []byte
|
||||||
|
errorString string
|
||||||
|
}{
|
||||||
|
{name: "empty bytes",
|
||||||
|
bytes: []byte{},
|
||||||
|
errorString: "cannot create OSC Packet from empty byte array",
|
||||||
|
},
|
||||||
|
{name: "packet that does not start with / or #",
|
||||||
|
bytes: []byte{0, 1, 2, 3},
|
||||||
|
errorString: "OSC Packet must start with # for bundle or / for message",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
got, _, err := PacketFromBytes(testCase.bytes)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("PacketFromBytes expected to fail but got: %+v", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err.Error() != testCase.errorString {
|
||||||
|
t.Fatalf("PacketFromBytes got error '%s', expected '%s'", err.Error(), testCase.errorString)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user