Compare commits

..

1 Commits

Author SHA1 Message Date
Joel Wetzell a117f668fa remove all fmt.println from receiveosc 2026-02-04 11:41:25 -06:00
15 changed files with 374 additions and 1138 deletions
-24
View File
@@ -1,24 +0,0 @@
name: Test
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
- name: Run tests
run: go test -v -coverprofile=coverage.txt .
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage.txt
+1 -2
View File
@@ -1,2 +1 @@
build build
coverage*
+4 -8
View File
@@ -1,9 +1,5 @@
[![codecov](https://codecov.io/gh/jwetzell/osc-go/branch/main/graph/badge.svg?token=DW3CCZELGI)](https://codecov.io/gh/jwetzell/osc-go) 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`
# `makeosc`
## Utilities # `receiveosc`
### `sendosc`
### `makeosc`
### `receiveosc`
+10 -19
View File
@@ -4,45 +4,36 @@ import (
"errors" "errors"
) )
func (b *OSCBundle) ToBytes() ([]byte, error) { func (b *OSCBundle) ToBytes() []byte {
bytes := stringToOSCBytes("#bundle") bytes := stringToOSCBytes("#bundle")
timeTagBytes := timeTagToOSCBytes(b.TimeTag) bytes = append(bytes, timeTagToOSCBytes(b.TimeTag)...)
bytes = append(bytes, timeTagBytes...)
for _, packet := range b.Contents { for _, packet := range b.Contents {
packetBytes, err := packet.ToBytes() packetBytes := packet.ToBytes()
if err != nil { packetLength := len(packet.ToBytes())
return nil, err
}
packetLength := len(packetBytes)
packetLengthBytes := int32ToOSCBytes(int32(packetLength)) bytes = append(bytes, int32ToOSCBytes(int32(packetLength))...)
bytes = append(bytes, packetLengthBytes...)
bytes = append(bytes, packetBytes...) bytes = append(bytes, packetBytes...)
} }
return bytes, nil return bytes
} }
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("OSC Bundle has to be at least 20 bytes") return nil, bytes, errors.New("bundle has to be at least 20 bytes")
} }
if bytes[0] != 35 { if bytes[0] != 35 {
return nil, bytes, errors.New("OSC Bundle must start with a #") return nil, bytes, errors.New("bundle must start with a #")
} }
bundleHeader, bytesAfterBundleHeader, err := readOSCString(bytes) bundleHeader, bytesAfterBundleHeader := readOSCString(bytes)
if err != nil {
return nil, bytes, err
}
if bundleHeader != "#bundle" { if bundleHeader != "#bundle" {
return nil, bytesAfterBundleHeader, errors.New("OSC Bundle must start with #bundle string") return nil, bytesAfterBundleHeader, errors.New("bundle must start with #bundle string")
} }
timeTag, bytesAfterTimeTag, err := readOSCTimeTag(bytesAfterBundleHeader) timeTag, bytesAfterTimeTag, err := readOSCTimeTag(bytesAfterBundleHeader)
+49 -172
View File
@@ -1,212 +1,89 @@
package osc package osc
import ( import (
"fmt"
"reflect" "reflect"
"testing" "testing"
) )
func TestGoodOSCBundleEncoding(t *testing.T) { func TestOSCBundleEncoding(t *testing.T) {
testCases := []struct { testCases := []struct {
name string description 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
errorString string expected []byte
}{ }{
{ {
name: "bundle contains message with bad address", "simple contents single message",
bundle: &OSCBundle{ &OSCBundle{
TimeTag: OSCTimeTag{
seconds: 32,
fractionalSeconds: 0,
},
Contents: []OSCPacket{&OSCMessage{Address: "hello", Args: []OSCArg{}}},
},
errorString: "OSC Message address must start with /",
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
got, err := testCase.bundle.ToBytes()
if err == nil {
t.Fatalf("OSCBundle.ToBytes() expected to fail but got: %+v", got)
}
if err.Error() != testCase.errorString {
t.Fatalf("OSCBundle.ToBytes() got error '%s', expected '%s'", err.Error(), testCase.errorString)
}
})
}
}
func TestGoodOSCBundleDecoding(t *testing.T) {
testCases := []struct {
name string
expected *OSCBundle
bytes []byte
}{
{
name: "simple contents single message",
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)}}}},
}, },
bytes: []byte{35, 98, 117, 110, 100, 108, 101, 0, 0, 0, 0, []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},
}, },
}
for _, testCase := range testCases {
actual := testCase.bundle.ToBytes()
if !reflect.DeepEqual(actual, testCase.expected) {
t.Errorf("Test '%s' failed to encode properly", testCase.description)
fmt.Printf("expected: %v\n", testCase.expected)
fmt.Printf("actual: %v\n", actual)
}
}
}
func TestOSCBundleDecoding(t *testing.T) {
testCases := []struct {
description string
expected *OSCBundle
bytes []byte
}{
{ {
name: "simple contents nested bundle", "simple contents single message",
expected: &OSCBundle{ &OSCBundle{
TimeTag: OSCTimeTag{ TimeTag: OSCTimeTag{
seconds: 32, seconds: 32,
fractionalSeconds: 0, fractionalSeconds: 0,
}, },
Contents: []OSCPacket{&OSCBundle{ Contents: []OSCPacket{&OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: float32(440)}}}},
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 []byte{35, 98, 117, 110, 100, 108, 101, 0, 0, 0, 0,
0, 0, 0, 32, 0, 0, 0, 0, // time tag 32, 0, 0, 0, 0, 0, 0, 0, 32, 47, 111,
0, 0, 0, 52, // content size 115, 99, 105, 108, 108, 97, 116, 111, 114, 47, 52,
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, 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},
}, },
} }
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
actual, remainingBytes, error := BundleFromBytes(testCase.bytes)
if error != nil { actual, remainingBytes, error := BundleFromBytes(testCase.bytes)
t.Fatalf("failed to decode properly: %s", error.Error())
}
if len(remainingBytes) > 0 { if error != nil {
t.Fatalf("should not have any remaining bytes") fmt.Println(error)
} 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)
}
})
} }
} }
+24 -22
View File
@@ -12,37 +12,41 @@ import (
) )
func main() { func main() {
var Address string
var Args []string
var Types []string
var Slip bool
cmd := &cli.Command{ cmd := &cli.Command{
Name: "makeosc", Name: "makeosc",
Usage: "make osc bytes", Usage: "make osc bytes",
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "address", Name: "address",
Value: "", Value: "",
Usage: "OSC address", Usage: "OSC address",
Required: true, Destination: &Address,
Required: true,
}, },
&cli.StringSliceFlag{ &cli.StringSliceFlag{
Name: "arg", Name: "arg",
Usage: "OSC args", Usage: "OSC args",
Destination: &Args,
}, },
&cli.StringSliceFlag{ &cli.StringSliceFlag{
Name: "type", Name: "type",
Usage: "OSC types", Usage: "OSC types",
Destination: &Types,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "slip", Name: "slip",
Value: false, Value: false,
Usage: "whether to slip encode the OSC Message bytes", Usage: "whether to slip encode the OSC Message bytes",
Destination: &Slip,
}, },
}, },
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
address := cmd.String("address") make(Address, Args, Types, Slip)
args := cmd.StringSlice("arg")
types := cmd.StringSlice("type")
slip := cmd.Bool("slip")
makeMsg(address, args, types, slip)
return nil return nil
}, },
} }
@@ -126,7 +130,8 @@ func argToTypedArg(rawArg string, oscType string) osc.OSCArg {
Type: "N", Type: "N",
} }
default: default:
fmt.Printf("unsupported OSC arg type: %s\n", oscType) fmt.Print("unhandled osc type: ")
fmt.Printf("%s.\n", oscType)
// TODO(jwetzell): something better than this like actual nil, err thing // TODO(jwetzell): something better than this like actual nil, err thing
return osc.OSCArg{} return osc.OSCArg{}
} }
@@ -154,7 +159,7 @@ func slipEncode(bytes []byte) []byte {
return encodedBytes return encodedBytes
} }
func makeMsg(address string, args []string, types []string, slip bool) { func make(address string, args []string, types []string, slip bool) {
oscMessage := osc.OSCMessage{ oscMessage := osc.OSCMessage{
Address: address, Address: address,
@@ -170,10 +175,7 @@ func makeMsg(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, err := oscMessage.ToBytes() oscMessageBuffer := oscMessage.ToBytes()
if err != nil {
panic(err)
}
if slip { if slip {
oscMessageBuffer = slipEncode(oscMessageBuffer) oscMessageBuffer = slipEncode(oscMessageBuffer)
+35 -32
View File
@@ -12,25 +12,33 @@ import (
) )
func main() { func main() {
var IP string
var Port int32
var Protocol string
var Format string
var Slip bool
cmd := &cli.Command{ cmd := &cli.Command{
Name: "receiveosc", Name: "receiveosc",
Usage: "receive OSC messages via UDP or TCP", Usage: "receive OSC messages via UDP or TCP",
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "ip", Name: "ip",
Usage: "ip to receive OSC messages on", Usage: "ip to receive OSC messages on",
Value: "0.0.0.0", Value: "0.0.0.0",
Destination: &IP,
}, },
&cli.Int32Flag{ &cli.Int32Flag{
Name: "port", Name: "port",
Usage: "port to receive OSC messages on", Usage: "port to receive OSC messages on",
Value: 8888, Destination: &Port,
Value: 8888,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "protocol", Name: "protocol",
Usage: "protocol to use to receive (tcp or udp)", Usage: "protocol to use to receive (tcp or udp)",
Value: "udp", Value: "udp",
Destination: &Protocol,
Validator: func(flag string) error { Validator: func(flag string) error {
if flag != "udp" && flag != "tcp" { if flag != "udp" && flag != "tcp" {
return fmt.Errorf("protocol must be either 'udp' or 'tcp'") return fmt.Errorf("protocol must be either 'udp' or 'tcp'")
@@ -39,9 +47,10 @@ func main() {
}, },
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "format", Name: "format",
Usage: "format for messages to be output in ('json')", Usage: "format for messages to be output in ('json')",
Value: "json", Value: "json",
Destination: &Format,
Validator: func(flag string) error { Validator: func(flag string) error {
if flag != "json" { if flag != "json" {
return fmt.Errorf("format must be 'json'") return fmt.Errorf("format must be 'json'")
@@ -50,27 +59,22 @@ func main() {
}, },
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "slip", Name: "slip",
Value: false, Value: false,
Usage: "whether to slip encode the OSC Message bytes", Usage: "whether to slip encode the OSC Message bytes",
Destination: &Slip,
}, },
}, },
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
ip := cmd.String("ip") netAddress := fmt.Sprintf("%s:%d", IP, Port)
port := cmd.Int32("port") switch Protocol {
protocol := cmd.String("protocol")
format := cmd.String("format")
slip := cmd.Bool("slip")
netAddress := fmt.Sprintf("%s:%d", ip, port)
switch protocol {
case "udp": case "udp":
listenUDP(netAddress, format) listenUDP(netAddress, Format)
case "tcp": case "tcp":
if !slip { if !Slip {
return fmt.Errorf("OSC 1.0 over TCP is not supported yet") return fmt.Errorf("OSC 1.0 over TCP is not supported yet")
} }
listenTCP(netAddress, slip, format) listenTCP(netAddress, Slip, Format)
} }
return nil return nil
}, },
@@ -84,7 +88,7 @@ func main() {
func listenTCP(netAddress string, useSLIP bool, format string) { func listenTCP(netAddress string, useSLIP bool, format string) {
socket, err := net.Listen("tcp4", netAddress) socket, err := net.Listen("tcp4", netAddress)
if err != nil { if err != nil {
fmt.Println(err) // TODO(jwetzell): output error properly
return return
} }
@@ -93,7 +97,7 @@ func listenTCP(netAddress string, useSLIP bool, format string) {
for { for {
conn, err := socket.Accept() conn, err := socket.Accept()
if err != nil { if err != nil {
fmt.Println(err) // TODO(jwetzell): output error properly
continue continue
} }
go handleTCPConnection(conn, useSLIP, format) go handleTCPConnection(conn, useSLIP, format)
@@ -181,9 +185,8 @@ func handlePacket(message osc.OSCPacket, format string) {
handleBundle(bundle, format) handleBundle(bundle, format)
} else if msg, ok := message.(*osc.OSCMessage); ok { } else if msg, ok := message.(*osc.OSCMessage); ok {
handleMessage(msg, format) handleMessage(msg, format)
} else {
fmt.Println("Received unknown OSC Packet type")
} }
// TODO(jwetzell): handle other packet types?
} }
func handleMessage(message *osc.OSCMessage, format string) { func handleMessage(message *osc.OSCMessage, format string) {
@@ -205,13 +208,13 @@ func listenUDP(netAddress string, format string) {
s, err := net.ResolveUDPAddr("udp4", netAddress) s, err := net.ResolveUDPAddr("udp4", netAddress)
if err != nil { if err != nil {
fmt.Println(err) // TODO(jwetzell): output error properly
return return
} }
connection, err := net.ListenUDP("udp4", s) connection, err := net.ListenUDP("udp4", s)
if err != nil { if err != nil {
fmt.Println(err) // TODO(jwetzell): output error properly
return return
} }
+40 -35
View File
@@ -15,25 +15,35 @@ import (
) )
func main() { func main() {
var Host string
var Port int32
var Address string
var Protocol string
var Args []string
var Types []string
var Slip bool
cmd := &cli.Command{ cmd := &cli.Command{
Name: "sendosc", Name: "sendosc",
Usage: "send OSC messages via UDP or TCP", Usage: "send OSC messages via UDP or TCP",
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "host", Name: "host",
Usage: "host to send OSC message to", Usage: "host to send OSC message to",
Required: true, Destination: &Host,
Required: true,
}, },
&cli.Int32Flag{ &cli.Int32Flag{
Name: "port", Name: "port",
Usage: "port to send OSC message to", Usage: "port to send OSC message to",
Required: true, Destination: &Port,
Required: true,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "protocol", Name: "protocol",
Usage: "protocol to use to send (tcp or udp)", Usage: "protocol to use to send (tcp or udp)",
Value: "udp", Value: "udp",
Destination: &Protocol,
Validator: func(flag string) error { Validator: func(flag string) error {
if flag != "udp" && flag != "tcp" { if flag != "udp" && flag != "tcp" {
return fmt.Errorf("protocol must be either 'udp' or 'tcp'") return fmt.Errorf("protocol must be either 'udp' or 'tcp'")
@@ -42,35 +52,32 @@ func main() {
}, },
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "address", Name: "address",
Usage: "OSC address", Usage: "OSC address",
Required: true, Destination: &Address,
Required: true,
}, },
&cli.StringSliceFlag{ &cli.StringSliceFlag{
Name: "arg", Name: "arg",
Usage: "OSC args", Usage: "OSC args",
Value: []string{}, Value: []string{},
Destination: &Args,
}, },
&cli.StringSliceFlag{ &cli.StringSliceFlag{
Name: "type", Name: "type",
Usage: "OSC types", Usage: "OSC types",
Value: []string{}, Value: []string{},
Destination: &Types,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "slip", Name: "slip",
Value: false, Value: false,
Usage: "whether to slip encode the OSC Message bytes", Usage: "whether to slip encode the OSC Message bytes",
Destination: &Slip,
}, },
}, },
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
host := cmd.String("host") send(Host, Port, Address, Args, Types, Protocol, Slip)
port := cmd.Int32("port")
address := cmd.String("address")
args := cmd.StringSlice("arg")
types := cmd.StringSlice("type")
protocol := cmd.String("protocol")
slip := cmd.Bool("slip")
send(host, port, address, args, types, protocol, slip)
return nil return nil
}, },
} }
@@ -154,7 +161,8 @@ func argToTypedArg(rawArg string, oscType string) osc.OSCArg {
Type: "N", Type: "N",
} }
default: default:
fmt.Printf("unsupported OSC arg type: %s\n", oscType) fmt.Print("unhandled osc type: ")
fmt.Printf("%s.\n", oscType)
// TODO(jwetzell): something better than this like actual nil, err thing // TODO(jwetzell): something better than this like actual nil, err thing
return osc.OSCArg{} return osc.OSCArg{}
} }
@@ -200,10 +208,7 @@ func send(host string, port int32, address string, args []string, types []string
} }
oscMessageBuffer, err := oscMessage.ToBytes() oscMessageBuffer := oscMessage.ToBytes()
if err != nil {
panic(err)
}
if slip { if slip {
oscMessageBuffer = slipEncode(oscMessageBuffer) oscMessageBuffer = slipEncode(oscMessageBuffer)
@@ -215,7 +220,7 @@ func send(host string, port int32, address string, args []string, types []string
oscMessageBuffer = append(sizeBytes, oscMessageBuffer...) oscMessageBuffer = append(sizeBytes, oscMessageBuffer...)
} }
netAddress := net.JoinHostPort(host, fmt.Sprintf("%d", port)) netAddress := fmt.Sprintf("%s:%d", host, port)
conn, err := net.Dial(protocol, netAddress) conn, err := net.Dial(protocol, netAddress)
if err != nil { if err != nil {
fmt.Printf("Dial err %v", err) fmt.Printf("Dial err %v", err)
+1 -1
View File
@@ -2,4 +2,4 @@ module github.com/jwetzell/osc-go
go 1.25.1 go 1.25.1
require github.com/urfave/cli/v3 v3.8.0 require github.com/urfave/cli/v3 v3.6.2
+2 -2
View File
@@ -4,7 +4,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI= github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8=
github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+13 -36
View File
@@ -5,16 +5,8 @@ import (
"strings" "strings"
) )
func (m *OSCMessage) ToBytes() ([]byte, error) { func (m *OSCMessage) ToBytes() []byte {
//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)...)
@@ -26,28 +18,22 @@ func (m *OSCMessage) ToBytes() ([]byte, error) {
for _, arg := range m.Args { for _, arg := range m.Args {
sb.WriteString(arg.Type) sb.WriteString(arg.Type)
} }
oscBuffer = append(oscBuffer, stringToOSCBytes(sb.String())...)
argsBuffer, err := argsToBuffer(m.Args)
if err != nil {
return nil, err
}
oscBuffer = append(oscBuffer, argsBuffer...)
return oscBuffer, nil oscBuffer = append(oscBuffer, stringToOSCBytes(sb.String())...)
oscBuffer = append(oscBuffer, argsToBuffer(m.Args)...)
return oscBuffer
} }
func MessageFromBytes(bytes []byte) (*OSCMessage, error) { func MessageFromBytes(bytes []byte) (*OSCMessage, error) {
if len(bytes) == 0 { if len(bytes) == 0 {
return nil, errors.New("cannot create OSC Message from empty byte array") return nil, errors.New("cannot create OSC Message from empty byte array")
} }
if bytes[0] != 47 {
return nil, errors.New("OSC Message must start with /")
}
address, typeAndArgBytes, err := readOSCString(bytes) address, typeAndArgBytes := readOSCString(bytes)
if err != nil { if address[0] != 47 {
return nil, err return nil, errors.New("OSC Message address must start with /")
} }
oscMessage := OSCMessage{ oscMessage := OSCMessage{
@@ -55,16 +41,7 @@ func MessageFromBytes(bytes []byte) (*OSCMessage, error) {
Args: []OSCArg{}, Args: []OSCArg{},
} }
if len(typeAndArgBytes) == 0 { typeString, argBytes := readOSCString(typeAndArgBytes)
// NOTE(jwetzell): no type string return early.
return &oscMessage, nil
}
typeString, argBytes, err := readOSCString(typeAndArgBytes)
if err != nil {
return nil, err
}
for index, oscType := range typeString { for index, oscType := range typeString {
if index == 0 { if index == 0 {
@@ -72,9 +49,9 @@ func MessageFromBytes(bytes []byte) (*OSCMessage, error) {
return nil, errors.New("type string is malformed") return nil, errors.New("type string is malformed")
} }
} else { } else {
oscArg, remainingBytes, err := readOSCArg(argBytes, string(oscType)) oscArg, remainingBytes, error := readOSCArg(argBytes, string(oscType))
if err != nil { if error != nil {
return nil, err return nil, error
} }
argBytes = remainingBytes argBytes = remainingBytes
oscMessage.Args = append(oscMessage.Args, oscArg) oscMessage.Args = append(oscMessage.Args, oscArg)
+105 -333
View File
@@ -1,29 +1,30 @@
package osc package osc
import ( import (
"fmt"
"math" "math"
"reflect" "reflect"
"testing" "testing"
) )
func TestGoodOSCMessageEncoding(t *testing.T) { func TestOSCMessageEncoding(t *testing.T) {
testCases := []struct { testCases := []struct {
name string description string
message *OSCMessage message *OSCMessage
expected []byte expected []byte
}{ }{
{ {
name: "simple hello", "simple hello",
message: &OSCMessage{ &OSCMessage{
Address: "/hello", Address: "/hello",
Args: []OSCArg{}, Args: []OSCArg{},
}, },
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 0, 0, 0}, []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 0, 0, 0},
}, },
{ {
name: "simple address string arg", "simple address string arg",
message: &OSCMessage{ &OSCMessage{
Address: "/hello", Address: "/hello",
Args: []OSCArg{ Args: []OSCArg{
{ {
@@ -32,58 +33,58 @@ func TestGoodOSCMessageEncoding(t *testing.T) {
}, },
}, },
}, },
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 115, 0, 0, 97, 114, 103, 49, 0, 0, 0, 0}, []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 115, 0, 0, 97, 114, 103, 49, 0, 0, 0, 0},
}, },
{ {
name: "simple address integer arg", description: "simple address integer arg",
message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "i", Value: 35}}}, message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "i", Value: 35}}},
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 105, 0, 0, 0, 0, 0, 35}, expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 105, 0, 0, 0, 0, 0, 35},
}, },
{ {
name: "simple address float arg", description: "simple address float arg",
message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "f", Value: 34.5}}}, message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "f", Value: 34.5}}},
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 102, 0, 0, 66, 10, 0, 0}, expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 102, 0, 0, 66, 10, 0, 0},
}, },
{ {
name: "simple address blob arg", description: "simple address blob arg",
message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "b", Value: []byte{98, 108, 111, 98}}}}, message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "b", Value: []byte{98, 108, 111, 98}}}},
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 98, 0, 0, 0, 0, 0, 4, 98, 108, 111, 98}, expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 98, 0, 0, 0, 0, 0, 4, 98, 108, 111, 98},
}, },
{ {
name: "simple address True arg", description: "simple address True arg",
message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "T", Value: true}}}, message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "T", Value: true}}},
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 84, 0, 0}, expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 84, 0, 0},
}, },
{ {
name: "simple address False arg", description: "simple address False arg",
message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "F", Value: false}}}, message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "F", Value: false}}},
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 70, 0, 0}, expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 70, 0, 0},
}, },
{ {
name: "simple address color arg", description: "simple address color arg",
message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "r", Value: OSCColor{r: 20, g: 21, b: 22, a: 10}}}}, message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "r", Value: OSCColor{r: 20, g: 21, b: 22, a: 10}}}},
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 114, 0, 0, 20, 21, 22, 10}, expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 114, 0, 0, 20, 21, 22, 10},
}, },
{ {
name: "simple address nil arg", description: "simple address nil arg",
message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "N", Value: nil}}}, message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "N", Value: nil}}},
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 78, 0, 0}, expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 78, 0, 0},
}, },
{ {
name: "simple address int64 arg", description: "simple address int64 arg",
message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "h", Value: 281474976710655}}}, message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "h", Value: 281474976710655}}},
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 104, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255}, expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 104, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255},
}, },
{ {
name: "simple address float64 arg", description: "simple address float64 arg",
message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "d", Value: 12.7654763}}}, message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "d", Value: 12.7654763}}},
expected: []byte{ expected: []byte{
47, 104, 101, 108, 108, 111, 0, 0, 44, 100, 0, 0, 0x40, 0x29, 0x87, 0xec, 0x82, 0x74, 0xb9, 0xe6, 47, 104, 101, 108, 108, 111, 0, 0, 44, 100, 0, 0, 0x40, 0x29, 0x87, 0xec, 0x82, 0x74, 0xb9, 0xe6,
}, },
}, },
// TODO(jwetzell): get array args working working // TODO(jwetzell): get array args working working
// { // {
// name: "simple address array arg", // description: "simple address array arg",
// message: OSCMessage{ // message: OSCMessage{
// Address: "/hello", // Address: "/hello",
// Args: []OSCArg{ // Args: []OSCArg{
@@ -99,15 +100,15 @@ func TestGoodOSCMessageEncoding(t *testing.T) {
// }, // },
// }, // },
{ {
name: "osc 1.0 spec example 1", description: "osc 1.0 spec example 1",
message: &OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: 440}}}, message: &OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: 440}}},
expected: []byte{ expected: []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, 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, 102, 0, 0, 67, 220, 0, 0,
}, },
}, },
{ {
name: "osc 1.0 spec example 2", description: "osc 1.0 spec example 2",
message: &OSCMessage{ message: &OSCMessage{
Address: "/foo", Address: "/foo",
Args: []OSCArg{ Args: []OSCArg{
@@ -127,181 +128,81 @@ func TestGoodOSCMessageEncoding(t *testing.T) {
} }
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
got, err := testCase.message.ToBytes()
if err != nil { actual := testCase.message.ToBytes()
t.Fatalf("failed to encode properly: %s", err.Error())
}
if !reflect.DeepEqual(got, testCase.expected) { if !reflect.DeepEqual(actual, testCase.expected) {
t.Fatalf("failed to encode properly got '%v', expected '%v'", got, testCase.expected) t.Errorf("Test '%s' failed to encode properly", testCase.description)
} fmt.Printf("expected: %v\n", testCase.expected)
}) fmt.Printf("actual: %v\n", actual)
}
} }
} }
func TestBadOSCMessageEncoding(t *testing.T) { func TestOSCMessageDecoding(t *testing.T) {
testCases := []struct { testCases := []struct {
name string description string
message *OSCMessage bytes []byte
errorString string expected OSCMessage
}{ }{
{ {
name: "empty message", description: "simple address no args",
message: &OSCMessage{}, bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 0, 0, 0},
errorString: "OSC Message must have an address", expected: OSCMessage{Address: "/hello", Args: []OSCArg{}},
}, },
{ {
name: "address does not start with /", description: "simple address string arg",
message: &OSCMessage{Address: "hello"}, bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 115, 0, 0, 97, 114, 103, 49, 0, 0, 0, 0},
errorString: "OSC Message address must start with /", expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "s", Value: "arg1"}}},
}, },
{ {
name: "arg with unsupported type", description: "simple address integer arg",
message: &OSCMessage{ bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 105, 0, 0, 0, 0, 0, 35},
Address: "/hello", expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "i", Value: int32(35)}}},
Args: []OSCArg{{Type: "x", Value: "unsupported"}},
},
errorString: "unsupported OSC argument type: x",
}, },
{ {
name: "string arg that is not a string", description: "simple address float arg",
message: &OSCMessage{ bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 102, 0, 0, 66, 10, 0, 0},
Address: "/hello", expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "f", Value: float32(34.5)}}},
Args: []OSCArg{{Type: "s", Value: 123}},
},
errorString: "OSC arg had string type but non-string value",
}, },
{ {
name: "int32 arg that is not a number", description: "simple address blob arg",
message: &OSCMessage{ bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 98, 0, 0, 0, 0, 0, 4, 98, 108, 111, 98},
Address: "/hello", expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "b", Value: []byte{98, 108, 111, 98}}}},
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", description: "simple address True arg",
message: &OSCMessage{ bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 84, 0, 0},
Address: "/hello", expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "T", Value: true}}},
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", description: "simple address False arg",
message: &OSCMessage{ bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 70, 0, 0},
Address: "/hello", expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "F", Value: false}}},
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", description: "simple address color arg",
message: &OSCMessage{ bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 114, 0, 0, 20, 21, 22, 10},
Address: "/hello", expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "r", Value: OSCColor{r: 20, g: 21, b: 22, a: 10}}}},
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", description: "simple address nil arg",
message: &OSCMessage{ bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 78, 0, 0},
Address: "/hello", expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "N", Value: nil}}},
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", description: "simple address Inifinitum arg",
message: &OSCMessage{ bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 73, 0, 0},
Address: "/hello", expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "I", Value: math.MaxInt32}}},
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) {
testCases := []struct {
name string
bytes []byte
expected OSCMessage
}{
{
name: "simple address no args",
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 0, 0, 0},
expected: OSCMessage{Address: "/hello", Args: []OSCArg{}},
}, },
{ {
name: "simple address string arg", description: "simple address int64 arg",
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 115, 0, 0, 97, 114, 103, 49, 0, 0, 0, 0}, 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: "s", Value: "arg1"}}}, expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "h", Value: int64(281474976710655)}}},
}, },
{ {
name: "simple address integer arg", description: "simple address float64 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)}}},
},
{
name: "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)}}},
},
{
name: "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}}}},
},
{
name: "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}}},
},
{
name: "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}}},
},
{
name: "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}}}},
},
{
name: "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}}},
},
{
name: "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}}},
},
{
name: "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)}}},
},
{
name: "simple address float64 arg",
bytes: []byte{ bytes: []byte{
47, 104, 101, 108, 108, 111, 0, 0, 44, 100, 0, 0, 0x40, 0x29, 0x87, 0xec, 0x82, 0x74, 0xb9, 0xe6, 47, 104, 101, 108, 108, 111, 0, 0, 44, 100, 0, 0, 0x40, 0x29, 0x87, 0xec, 0x82, 0x74, 0xb9, 0xe6,
}, },
@@ -309,7 +210,7 @@ func TestGoodOSCMessageDecoding(t *testing.T) {
}, },
// TODO(jwetzell): support OSC array // TODO(jwetzell): support OSC array
// { // {
// name: "simple address array arg", // description: "simple address array arg",
// bytes: []byte{ // 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, // 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, // 0, 0, 3, 232,
@@ -325,15 +226,15 @@ func TestGoodOSCMessageDecoding(t *testing.T) {
// }, // },
// }, // },
{ {
name: "simple address no type string", description: "simple address no type string",
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0}, bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0},
expected: OSCMessage{ expected: OSCMessage{
Address: "/hello", Address: "/hello",
Args: []OSCArg{}, Args: []OSCArg{},
}, },
}, },
{ {
name: "osc 1.0 spec example 1", description: "osc 1.0 spec example 1",
bytes: []byte{ 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, 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, 102, 0, 0, 67, 220, 0, 0,
@@ -341,7 +242,7 @@ func TestGoodOSCMessageDecoding(t *testing.T) {
expected: OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: float32(440)}}}, expected: OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: float32(440)}}},
}, },
{ {
name: "osc 1.0 spec example 2", description: "osc 1.0 spec example 2",
bytes: []byte{ 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, 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, 108, 111, 0, 0, 0, 63, 157, 243, 182, 64, 181, 178, 45,
@@ -361,153 +262,24 @@ func TestGoodOSCMessageDecoding(t *testing.T) {
} }
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
actual, err := MessageFromBytes(testCase.bytes) actual, error := MessageFromBytes(testCase.bytes)
if err != nil { if error != nil {
t.Fatalf("failed to encode properly: %s", err.Error()) fmt.Println(error)
} t.Errorf("Test '%s' failed to encode properly", testCase.description)
}
if !reflect.DeepEqual(actual.Address, testCase.expected.Address) { if !reflect.DeepEqual(actual.Address, testCase.expected.Address) {
t.Fatalf("failed to encode address propertly got '%s', expected '%s'", 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) { if !reflect.DeepEqual(actual.Args, testCase.expected.Args) {
t.Fatalf("failed to encode args properly got '%+v', expected '%+v'", 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)
} }
}
func TestBadOSCMessageDecoding(t *testing.T) {
testCases := []struct {
name string
bytes []byte
errorString string
}{
{
name: "empty byte array",
bytes: []byte{},
errorString: "cannot create OSC Message from empty byte array",
},
{
name: "does not start with /",
bytes: []byte{0, 104, 101, 108, 108, 111, 0, 0},
errorString: "OSC Message must start with /",
},
{
name: "address string not padded",
bytes: []byte{47, 104, 101, 108, 108, 111, 0},
errorString: "OSC string is not properly padded",
},
{
name: "type string not padded",
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 0},
errorString: "OSC string is not properly padded",
},
{
name: "type string does not start with ,",
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 45, 0, 0, 0},
errorString: "type string is malformed",
},
{
name: "string arg not null-terminated",
bytes: []byte{
47, 104, 101, 108, 108, 111, 0, 0, 44, 115, 0, 0, 97, 114, 103, 49,
},
errorString: "OSC string must be null-terminated",
},
{
name: "string arg not padded",
bytes: []byte{
47, 104, 101, 108, 108, 111, 0, 0, 44, 115, 0, 0, 104, 105, 0,
},
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 {
t.Run(testCase.name, func(t *testing.T) {
got, err := MessageFromBytes(testCase.bytes)
if err == nil {
t.Fatalf("MessageFromBytes expected to fail but got: %+v", got)
}
if err.Error() != testCase.errorString {
t.Fatalf("MessageFromBytes got error '%s', expected '%s'", err.Error(), testCase.errorString)
}
})
} }
} }
+89 -133
View File
@@ -2,6 +2,7 @@ package osc
// TODO(jwetzell): split things up // TODO(jwetzell): split things up
import ( import (
"bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
@@ -17,7 +18,7 @@ func stringToOSCBytes(rawString string) []byte {
padLength := 4 - (len(sb.String()) % 4) padLength := 4 - (len(sb.String()) % 4)
if padLength < 4 { if padLength < 4 {
for range padLength { for i := 0; i < padLength; i++ {
sb.WriteString("\u0000") sb.WriteString("\u0000")
} }
} }
@@ -26,50 +27,51 @@ func stringToOSCBytes(rawString string) []byte {
} }
func int32ToOSCBytes(number int32) []byte { func int32ToOSCBytes(number int32) []byte {
bytes := make([]byte, 4) var buf bytes.Buffer
bytes[0] = byte((number >> 24) & 0xFF) err := binary.Write(&buf, binary.BigEndian, number)
bytes[1] = byte((number >> 16) & 0xFF) if err != nil {
bytes[2] = byte((number >> 8) & 0xFF) panic(err)
bytes[3] = byte(number & 0xFF) }
return bytes return buf.Bytes()
} }
func int64ToOSCBytes(number int64) []byte { func int64ToOSCBytes(number int64) []byte {
bytes := make([]byte, 8) var buf bytes.Buffer
bytes[0] = byte((number >> 56) & 0xFF) err := binary.Write(&buf, binary.BigEndian, number)
bytes[1] = byte((number >> 48) & 0xFF) if err != nil {
bytes[2] = byte((number >> 40) & 0xFF) panic(err)
bytes[3] = byte((number >> 32) & 0xFF) }
bytes[4] = byte((number >> 24) & 0xFF) return buf.Bytes()
bytes[5] = byte((number >> 16) & 0xFF)
bytes[6] = byte((number >> 8) & 0xFF)
bytes[7] = byte(number & 0xFF)
return bytes
} }
func float32ToOSCBytes(number float32) []byte { func float32ToOSCBytes(number float32) []byte {
bytes := make([]byte, 4) var buf bytes.Buffer
binary.BigEndian.PutUint32(bytes, math.Float32bits(number)) err := binary.Write(&buf, binary.BigEndian, number)
return bytes if err != nil {
panic(err)
}
return buf.Bytes()
} }
func float64ToOSCBytes(number float64) []byte { func float64ToOSCBytes(number float64) []byte {
bytes := make([]byte, 8) var buf bytes.Buffer
binary.BigEndian.PutUint64(bytes, math.Float64bits(number)) err := binary.Write(&buf, binary.BigEndian, number)
return bytes if err != nil {
panic(err)
}
return buf.Bytes()
} }
func byteArrayToOSCBytes(bytes []byte) []byte { func byteArrayToOSCBytes(bytes []byte) []byte {
oscBytes := []byte{} oscBytes := []byte{}
bytesSize := len(bytes) bytesSize := len(bytes)
bytesSizeBytes := int32ToOSCBytes(int32(bytesSize)) oscBytes = append(oscBytes, int32ToOSCBytes(int32(bytesSize))...)
oscBytes = append(oscBytes, bytesSizeBytes...)
oscBytes = append(oscBytes, bytes...) oscBytes = append(oscBytes, bytes...)
padLength := 4 - (bytesSize % 4) padLength := 4 - (bytesSize % 4)
if padLength < 4 { if padLength < 4 {
for range padLength { for i := 0; i < padLength; i++ {
oscBytes = append(oscBytes, 0) oscBytes = append(oscBytes, 0)
} }
} }
@@ -79,59 +81,50 @@ func byteArrayToOSCBytes(bytes []byte) []byte {
func timeTagToOSCBytes(timeTag OSCTimeTag) []byte { func timeTagToOSCBytes(timeTag OSCTimeTag) []byte {
timeTagBytes := int32ToOSCBytes(timeTag.seconds) timeTagBytes := int32ToOSCBytes(timeTag.seconds)
fractionalSecondsBytes := int32ToOSCBytes(timeTag.fractionalSeconds) timeTagBytes = append(timeTagBytes, int32ToOSCBytes(timeTag.fractionalSeconds)...)
timeTagBytes = append(timeTagBytes, fractionalSecondsBytes...)
return timeTagBytes return timeTagBytes
} }
func argsToBuffer(args []OSCArg) ([]byte, error) { func argsToBuffer(args []OSCArg) []byte {
//TODO(jwetzell): add error handling //TODO(jwetzell): add error handling
var argBuffers = []byte{} var argBuffers = []byte{}
for _, arg := range args { for _, arg := range args {
switch arg.Type { switch oscType := arg.Type; oscType {
case "s": case "s":
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 {
return nil, errors.New("OSC arg had string type but non-string value") fmt.Println("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 {
valueBytes := int32ToOSCBytes(int32(value)) argBuffers = append(argBuffers, int32ToOSCBytes(int32(value))...)
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int32); ok { } else if value, ok := arg.Value.(int32); ok {
valueBytes := int32ToOSCBytes(int32(value)) argBuffers = append(argBuffers, int32ToOSCBytes(value)...)
argBuffers = append(argBuffers, valueBytes...)
} else { } else {
return nil, errors.New("OSC arg had int32 type but non-number value") fmt.Println("OSC arg had integer type but non-integer value.")
} }
case "f": case "f":
if value, ok := arg.Value.(float32); ok { if value, ok := arg.Value.(float32); ok {
valueBytes := float32ToOSCBytes(value) argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(float64); ok { } else if value, ok := arg.Value.(float64); ok {
valueBytes := float32ToOSCBytes(float32(value)) argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int); ok { } else if value, ok := arg.Value.(int); ok {
valueBytes := float32ToOSCBytes(float32(value)) argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int32); ok { } else if value, ok := arg.Value.(int32); ok {
valueBytes := float32ToOSCBytes(float32(value)) argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int64); ok { } else if value, ok := arg.Value.(int64); ok {
valueBytes := float32ToOSCBytes(float32(value)) argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
argBuffers = append(argBuffers, valueBytes...)
} else { } else {
return nil, errors.New("OSC arg had float32 type but non-number value") fmt.Println("OSC arg had float type but non-float value.")
} }
case "b": case "b":
if value, ok := arg.Value.([]byte); ok { if value, ok := arg.Value.([]byte); ok {
valueBytes := byteArrayToOSCBytes(value) argBuffers = append(argBuffers, byteArrayToOSCBytes(value)...)
argBuffers = append(argBuffers, valueBytes...)
} else { } else {
return nil, errors.New("OSC arg had blob type but non-blob value") fmt.Println("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)...)
@@ -143,88 +136,68 @@ func argsToBuffer(args []OSCArg) ([]byte, error) {
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 {
valueBytes := int64ToOSCBytes(int64(value)) argBuffers = append(argBuffers, int64ToOSCBytes(int64(value))...)
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int32); ok { } else if value, ok := arg.Value.(int32); ok {
valueBytes := int64ToOSCBytes(int64(value)) argBuffers = append(argBuffers, int64ToOSCBytes(int64(value))...)
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int64); ok { } else if value, ok := arg.Value.(int64); ok {
valueBytes := int64ToOSCBytes(value) argBuffers = append(argBuffers, int64ToOSCBytes(value)...)
argBuffers = append(argBuffers, valueBytes...)
} else { } else {
return nil, errors.New("OSC arg had int64 type but non-number value") fmt.Println("OSC arg had integer type but non-integer value.")
} }
case "d": case "d":
if value, ok := arg.Value.(float32); ok { if value, ok := arg.Value.(float32); ok {
valueBytes := float64ToOSCBytes(float64(value)) argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(float64); ok { } else if value, ok := arg.Value.(float64); ok {
valueBytes := float64ToOSCBytes(value) argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int); ok { } else if value, ok := arg.Value.(int); ok {
valueBytes := float64ToOSCBytes(float64(value)) argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int32); ok { } else if value, ok := arg.Value.(int32); ok {
valueBytes := float64ToOSCBytes(float64(value)) argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int64); ok { } else if value, ok := arg.Value.(int64); ok {
valueBytes := float64ToOSCBytes(float64(value)) argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
argBuffers = append(argBuffers, valueBytes...)
} else { } else {
return nil, errors.New("OSC arg had float64 type but non-number value") fmt.Println("OSC arg had float type but non-float value.")
} }
default: default:
return nil, fmt.Errorf("unsupported OSC argument type: %s", arg.Type) fmt.Printf("unhandled osc type: %s.\n", oscType)
} }
} }
return argBuffers, nil return argBuffers
} }
func readOSCString(bytes []byte) (string, []byte, error) { func readOSCString(bytes []byte) (string, []byte) {
//TODO(jwetzell): add error handling
oscString := "" oscString := ""
stringEndIndex := 0 stringEndIndex := 0
nullByteFound := false
for index, byteIn := range bytes { for index, byteIn := range bytes {
if byteIn == 0 { if byteIn == 0 {
nullByteFound = true
oscString = string(bytes[0:index]) oscString = string(bytes[0:index])
stringEndIndex = index + 1 stringEndIndex = index + 1
break break
} }
} }
if !nullByteFound {
return "", bytes, errors.New("OSC string must be null-terminated")
}
stringPadding := 4 - (stringEndIndex % 4) stringPadding := 4 - (stringEndIndex % 4)
if stringPadding < 4 { if stringPadding < 4 {
stringEndIndex = stringEndIndex + stringPadding stringEndIndex = stringEndIndex + stringPadding
} }
if stringEndIndex > len(bytes) {
return "", bytes, errors.New("OSC string is not properly padded")
}
remainingBytes := bytes[stringEndIndex:] remainingBytes := bytes[stringEndIndex:]
return oscString, remainingBytes, nil return oscString, remainingBytes
} }
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("OSC int32 arg is not 4 bytes") return 0, bytes, errors.New("int data must be at least 4 bytes large")
} }
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
@@ -232,7 +205,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("OSC int64 arg is not 8 bytes") return 0, bytes, errors.New("int data must be at least 4 bytes large")
} }
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
@@ -240,15 +213,15 @@ 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("OSC float32 arg is not 4 bytes") return 0, bytes, errors.New("float data must be at least 4 bytes large")
} }
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
} }
func readOSCFloat64(bytes []byte) (float64, []byte, error) { func readOSCFloat64(bytes []byte) (float64, []byte, error) {
if len(bytes) < 8 { if len(bytes) < 4 {
return 0, bytes, errors.New("OSC float64 arg is not 8 bytes") return 0, bytes, errors.New("float data must be at least 4 bytes large")
} }
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
@@ -258,15 +231,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("OSC blob arg size not valid: " + err.Error()) return []byte{}, bytes, errors.New("problem reading blob data size")
}
if blobLength < 0 {
return []byte{}, bytes, errors.New("OSC blob arg size not valid: size cannot be negative")
} }
if len(remainingBytes) < int(blobLength) { if len(remainingBytes) < int(blobLength) {
return []byte{}, bytes, errors.New("OSC blob arg size not valid: size specified is larger than remaining bytes") return []byte{}, bytes, errors.New("blob data specified a size larger than the remaining message data")
} }
blobLengthPadding := 4 - (blobLength % 4) blobLengthPadding := 4 - (blobLength % 4)
@@ -275,15 +244,12 @@ func readOSCBlob(bytes []byte) ([]byte, []byte, error) {
if blobLengthPadding < 4 { if blobLengthPadding < 4 {
blobEnd = blobEnd + blobLengthPadding blobEnd = blobEnd + blobLengthPadding
} }
if int(blobEnd) > len(bytes) {
return []byte{}, bytes, errors.New("OSC blob arg size not valid: size specified is larger than remaining bytes when accounting for padding")
}
return bytes[4 : 4+blobLength], bytes[blobEnd:], nil return bytes[4 : 4+blobLength], bytes[blobEnd:], nil
} }
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("OSC color arg is not 4 bytes") return OSCColor{0, 0, 0, 0}, bytes, errors.New("color data must be at least 4 bytes large")
} }
oscColor := OSCColor{ oscColor := OSCColor{
r: bytes[0], r: bytes[0],
@@ -293,15 +259,14 @@ 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, fmt.Errorf("OSC time tag seconds are not valid: %s", err) return OSCTimeTag{}, bytes, err
} }
fractionalSeconds, remainingBytes, err := readOSCInt32(bytesAfterSeconds) fractionalSeconds, remainingBytes, err := readOSCInt32(bytesAfterSeconds)
if err != nil { if err != nil {
return OSCTimeTag{}, bytes, fmt.Errorf("OSC time tag fractional seconds are not valid: %s", err) return OSCTimeTag{}, bytes, err
} }
return OSCTimeTag{ return OSCTimeTag{
@@ -322,30 +287,27 @@ func readOSCArg(bytes []byte, oscType string) (OSCArg, []byte, error) {
//TODO(jwetzell): add error handling //TODO(jwetzell): add error handling
switch oscType { switch oscType {
case "s": case "s":
argString, bytesLeft, err := readOSCString(bytes) argString, bytesLeft := readOSCString(bytes)
if err != nil {
return OSCArg{}, bytes, err
}
oscArg.Value = argString oscArg.Value = argString
remainingBytes = bytesLeft remainingBytes = bytesLeft
case "i": case "i":
argInt, bytesLeft, err := readOSCInt32(bytes) argInt, bytesLeft, error := readOSCInt32(bytes)
if err != nil { if error != nil {
readArgError = err readArgError = error
} }
oscArg.Value = argInt oscArg.Value = argInt
remainingBytes = bytesLeft remainingBytes = bytesLeft
case "f": case "f":
argFloat, bytesLeft, err := readOSCFloat32(bytes) argFloat, bytesLeft, error := readOSCFloat32(bytes)
if err != nil { if error != nil {
readArgError = err readArgError = error
} }
oscArg.Value = argFloat oscArg.Value = argFloat
remainingBytes = bytesLeft remainingBytes = bytesLeft
case "b": case "b":
argBytes, bytesLeft, err := readOSCBlob(bytes) argBytes, bytesLeft, error := readOSCBlob(bytes)
if err != nil { if error != nil {
readArgError = err readArgError = error
} }
oscArg.Value = argBytes oscArg.Value = argBytes
remainingBytes = bytesLeft remainingBytes = bytesLeft
@@ -362,35 +324,29 @@ func readOSCArg(bytes []byte, oscType string) (OSCArg, []byte, error) {
oscArg.Value = math.MaxInt32 oscArg.Value = math.MaxInt32
remainingBytes = bytes remainingBytes = bytes
case "r": case "r":
argColor, bytesLeft, err := readOSCColor(bytes) argColor, bytesLeft, error := readOSCColor(bytes)
if err != nil { if error != nil {
readArgError = err readArgError = error
} }
oscArg.Value = argColor oscArg.Value = argColor
remainingBytes = bytesLeft remainingBytes = bytesLeft
case "h": case "h":
argInt, bytesLeft, err := readOSCInt64(bytes) argInt, bytesLeft, error := readOSCInt64(bytes)
if err != nil { if error != nil {
readArgError = err readArgError = error
} }
oscArg.Value = argInt oscArg.Value = argInt
remainingBytes = bytesLeft remainingBytes = bytesLeft
case "d": case "d":
argFloat, bytesLeft, err := readOSCFloat64(bytes) argFloat, bytesLeft, error := readOSCFloat64(bytes)
if err != nil { if error != nil {
readArgError = err readArgError = 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:
return OSCArg{}, bytes, fmt.Errorf("unsupported OSC argument type: %s", oscType) fmt.Printf("unsupported osc type: %s\n", oscType)
readArgError = errors.New("unsupported osc type: " + oscType)
} }
return oscArg, remainingBytes, readArgError return oscArg, remainingBytes, readArgError
} }
-318
View File
@@ -1,318 +0,0 @@
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)
}
})
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
package osc package osc
type OSCPacket interface { type OSCPacket interface {
ToBytes() ([]byte, error) ToBytes() []byte
} }
type OSCBundle struct { type OSCBundle struct {