mirror of
https://github.com/jwetzell/osc-go.git
synced 2026-07-26 10:28:42 +00:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ed1ab065c | |||
| d6d4ac4419 | |||
| a86a9b3121 | |||
| 216f84a2d9 | |||
| bd6a548e34 | |||
| d8dff0794a | |||
| 0c4d36b376 | |||
| a5ac419e3e | |||
| 3b66c38d18 | |||
| 4d8ba2a47b | |||
| 0caf1b02a7 | |||
| d82919e9f1 | |||
| 4078efa090 | |||
| 53864a35dc | |||
| 2ec4be5426 | |||
| 5f5c34c4ea | |||
| 16093f2509 | |||
| 191d74a20a | |||
| 738b87b03f | |||
| 42b1f9cf8a | |||
| 16e1ad039e | |||
| eb539dd319 | |||
| 1465663a63 | |||
| dd430dd380 | |||
| 9452268042 | |||
| b621944dc5 | |||
| 8fa8aa3a55 | |||
| 1d2684f446 | |||
| 2548771fe3 | |||
| 4744321d71 | |||
| 1130adf046 | |||
| ad3c449149 | |||
| fbd2bf3905 | |||
| e1c8563522 | |||
| e2a8daf013 | |||
| baa140297f | |||
| 8bb3f3fc97 | |||
| e70934e705 | |||
| 597b9ad053 | |||
| 6f141eb742 | |||
| 9bc51c787f | |||
| 37cc551793 | |||
| 67a1cc083b | |||
| 25c159013b | |||
| 5ac60b0685 | |||
| 6251acb852 | |||
| 5600ef75f5 | |||
| 77c92c5456 | |||
| 594c51d2a4 | |||
| d7c6da5c5b | |||
| 6f3a65322b | |||
| e34a589c5f | |||
| 41112d583b | |||
| 24a0501a4d | |||
| e06a0a2776 | |||
| d1696baa31 | |||
| c0304bf7a6 | |||
| d62d65c386 | |||
| f2888cc038 | |||
| 24077a77d6 | |||
| 9e515cc28e |
@@ -36,7 +36,7 @@ jobs:
|
||||
- goarch: "386"
|
||||
goos: darwin
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: wangyoucao577/go-release-action@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
- goarch: "386"
|
||||
goos: darwin
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: wangyoucao577/go-release-action@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
- goarch: "386"
|
||||
goos: darwin
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: wangyoucao577/go-release-action@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
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
|
||||
+2
-1
@@ -1 +1,2 @@
|
||||
build
|
||||
build
|
||||
coverage*
|
||||
@@ -1,5 +1,9 @@
|
||||
A collection of command line OSC utilities written in Go. Mainly an exercise in learning Go.
|
||||
[](https://codecov.io/gh/jwetzell/osc-go)
|
||||
|
||||
# `sendosc`
|
||||
# `makeosc`
|
||||
# `receiveosc`
|
||||
A mostly complete OSC implementation and collection of command line OSC utilities written in Go. Mainly an exercise in learning Go.
|
||||
|
||||
## Utilities
|
||||
|
||||
### `sendosc`
|
||||
### `makeosc`
|
||||
### `receiveosc`
|
||||
|
||||
@@ -4,42 +4,51 @@ import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
func (b *OSCBundle) ToBytes() []byte {
|
||||
func (b *OSCBundle) ToBytes() ([]byte, error) {
|
||||
|
||||
bytes := stringToOSCBytes("#bundle")
|
||||
|
||||
bytes = append(bytes, timeTagToOSCBytes(b.TimeTag)...)
|
||||
timeTagBytes := timeTagToOSCBytes(b.TimeTag)
|
||||
bytes = append(bytes, timeTagBytes...)
|
||||
|
||||
for _, packet := range b.Contents {
|
||||
packetBytes := packet.ToBytes()
|
||||
packetLength := len(packet.ToBytes())
|
||||
packetBytes, err := packet.ToBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
packetLength := len(packetBytes)
|
||||
|
||||
bytes = append(bytes, int32ToOSCBytes(int32(packetLength))...)
|
||||
packetLengthBytes := int32ToOSCBytes(int32(packetLength))
|
||||
bytes = append(bytes, packetLengthBytes...)
|
||||
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 {
|
||||
return OSCBundle{}, 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 {
|
||||
return OSCBundle{}, bytes, errors.New("bundle must start with a #")
|
||||
return nil, bytes, errors.New("OSC Bundle must start with a #")
|
||||
}
|
||||
|
||||
bundleHeader, bytesAfterBundleHeader := readOSCString(bytes)
|
||||
bundleHeader, bytesAfterBundleHeader, err := readOSCString(bytes)
|
||||
|
||||
if err != nil {
|
||||
return nil, bytes, err
|
||||
}
|
||||
|
||||
if bundleHeader != "#bundle" {
|
||||
return OSCBundle{}, 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)
|
||||
|
||||
if err != nil {
|
||||
return OSCBundle{}, bytesAfterBundleHeader, err
|
||||
return nil, bytesAfterBundleHeader, err
|
||||
}
|
||||
|
||||
bundleContents := []OSCPacket{}
|
||||
@@ -52,13 +61,13 @@ func BundleFromBytes(bytes []byte) (OSCBundle, []byte, error) {
|
||||
contentSize, bytesAfterContentSize, err := readOSCInt32(remainingBytes)
|
||||
|
||||
if err != nil {
|
||||
return OSCBundle{}, remainingBytes, err
|
||||
return nil, remainingBytes, err
|
||||
}
|
||||
|
||||
remainingBytes = bytesAfterContentSize
|
||||
|
||||
if len(remainingBytes) < int(contentSize) {
|
||||
return OSCBundle{}, remainingBytes, errors.New("bundle doesn't have enough bytes for the content size it specifies")
|
||||
return nil, remainingBytes, errors.New("bundle doesn't have enough bytes for the content size it specifies")
|
||||
}
|
||||
|
||||
bundleContentBytes := remainingBytes[0:contentSize]
|
||||
@@ -66,17 +75,17 @@ func BundleFromBytes(bytes []byte) (OSCBundle, []byte, error) {
|
||||
if bundleContentBytes[0] == 35 {
|
||||
content, _, err := BundleFromBytes(bundleContentBytes)
|
||||
if err != nil {
|
||||
return OSCBundle{}, remainingBytes, err
|
||||
return nil, remainingBytes, err
|
||||
}
|
||||
bundleContents = append(bundleContents, &content)
|
||||
bundleContents = append(bundleContents, content)
|
||||
} else if bundleContentBytes[0] == 47 {
|
||||
content, err := MessageFromBytes(bundleContentBytes)
|
||||
if err != nil {
|
||||
return OSCBundle{}, remainingBytes, err
|
||||
return nil, remainingBytes, err
|
||||
}
|
||||
bundleContents = append(bundleContents, &content)
|
||||
bundleContents = append(bundleContents, content)
|
||||
} else {
|
||||
return OSCBundle{}, remainingBytes, errors.New("bundle contents does not look a bundle or message")
|
||||
return nil, remainingBytes, errors.New("bundle contents does not look a bundle or message")
|
||||
}
|
||||
remainingBytes = bytesAfterContentSize[contentSize:]
|
||||
if len(remainingBytes) == 0 {
|
||||
@@ -85,7 +94,7 @@ func BundleFromBytes(bytes []byte) (OSCBundle, []byte, error) {
|
||||
|
||||
}
|
||||
|
||||
return OSCBundle{
|
||||
return &OSCBundle{
|
||||
TimeTag: timeTag,
|
||||
Contents: bundleContents,
|
||||
},
|
||||
|
||||
+170
-47
@@ -1,28 +1,27 @@
|
||||
package osc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOSCBundleEncoding(t *testing.T) {
|
||||
func TestGoodOSCBundleEncoding(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
description string
|
||||
bundle OSCBundle
|
||||
expected []byte
|
||||
name string
|
||||
bundle *OSCBundle
|
||||
expected []byte
|
||||
}{
|
||||
{
|
||||
"simple contents single message",
|
||||
OSCBundle{
|
||||
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)}}}},
|
||||
},
|
||||
[]byte{35, 98, 117, 110, 100, 108, 101, 0, 0, 0, 0,
|
||||
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,
|
||||
@@ -31,59 +30,183 @@ func TestOSCBundleEncoding(t *testing.T) {
|
||||
}
|
||||
|
||||
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) {
|
||||
t.Errorf("Test '%s' failed to encode properly", testCase.description)
|
||||
fmt.Printf("expected: %v\n", testCase.expected)
|
||||
fmt.Printf("actual: %v\n", actual)
|
||||
}
|
||||
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 TestOSCBundleDecoding(t *testing.T) {
|
||||
func TestBadOSCBundleEncoding(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
description string
|
||||
expected OSCBundle
|
||||
name string
|
||||
bundle *OSCBundle
|
||||
errorString string
|
||||
}{
|
||||
{
|
||||
name: "bundle contains message with bad address",
|
||||
bundle: &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{
|
||||
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},
|
||||
},
|
||||
{
|
||||
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 {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
actual, remainingBytes, error := BundleFromBytes(testCase.bytes)
|
||||
|
||||
if error != nil {
|
||||
t.Fatalf("failed to decode properly: %s", error.Error())
|
||||
}
|
||||
|
||||
if len(remainingBytes) > 0 {
|
||||
t.Fatalf("should not have any remaining bytes")
|
||||
}
|
||||
|
||||
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
|
||||
}{
|
||||
{
|
||||
"simple contents single message",
|
||||
OSCBundle{
|
||||
TimeTag: OSCTimeTag{
|
||||
seconds: 32,
|
||||
fractionalSeconds: 0,
|
||||
},
|
||||
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,
|
||||
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},
|
||||
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)
|
||||
|
||||
actual, remainingBytes, error := BundleFromBytes(testCase.bytes)
|
||||
|
||||
if error != nil {
|
||||
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 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
-26
@@ -12,41 +12,37 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
var Address string
|
||||
var Args []string
|
||||
var Types []string
|
||||
var Slip bool
|
||||
|
||||
cmd := &cli.Command{
|
||||
Name: "makeosc",
|
||||
Usage: "make osc bytes",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "address",
|
||||
Value: "",
|
||||
Usage: "OSC address",
|
||||
Destination: &Address,
|
||||
Required: true,
|
||||
Name: "address",
|
||||
Value: "",
|
||||
Usage: "OSC address",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "arg",
|
||||
Usage: "OSC args",
|
||||
Destination: &Args,
|
||||
Name: "arg",
|
||||
Usage: "OSC args",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "type",
|
||||
Usage: "OSC types",
|
||||
Destination: &Types,
|
||||
Name: "type",
|
||||
Usage: "OSC types",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "slip",
|
||||
Value: false,
|
||||
Usage: "whether to slip encode the OSC Message bytes",
|
||||
Destination: &Slip,
|
||||
Name: "slip",
|
||||
Value: false,
|
||||
Usage: "whether to slip encode the OSC Message bytes",
|
||||
},
|
||||
},
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
make(Address, Args, Types, Slip)
|
||||
address := cmd.String("address")
|
||||
args := cmd.StringSlice("arg")
|
||||
types := cmd.StringSlice("type")
|
||||
slip := cmd.Bool("slip")
|
||||
makeMsg(address, args, types, slip)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -130,8 +126,7 @@ func argToTypedArg(rawArg string, oscType string) osc.OSCArg {
|
||||
Type: "N",
|
||||
}
|
||||
default:
|
||||
fmt.Print("unhandled osc type: ")
|
||||
fmt.Printf("%s.\n", oscType)
|
||||
fmt.Printf("unsupported OSC arg type: %s\n", oscType)
|
||||
// TODO(jwetzell): something better than this like actual nil, err thing
|
||||
return osc.OSCArg{}
|
||||
}
|
||||
@@ -147,9 +142,9 @@ func slipEncode(bytes []byte) []byte {
|
||||
|
||||
for _, byteToEncode := range bytes {
|
||||
if byteToEncode == END {
|
||||
encodedBytes = append(encodedBytes, ESC_END)
|
||||
encodedBytes = append(encodedBytes, ESC, ESC_END)
|
||||
} else if byteToEncode == ESC {
|
||||
encodedBytes = append(encodedBytes, ESC_ESC)
|
||||
encodedBytes = append(encodedBytes, ESC, ESC_ESC)
|
||||
} else {
|
||||
encodedBytes = append(encodedBytes, byteToEncode)
|
||||
}
|
||||
@@ -159,7 +154,7 @@ func slipEncode(bytes []byte) []byte {
|
||||
return encodedBytes
|
||||
}
|
||||
|
||||
func make(address string, args []string, types []string, slip bool) {
|
||||
func makeMsg(address string, args []string, types []string, slip bool) {
|
||||
|
||||
oscMessage := osc.OSCMessage{
|
||||
Address: address,
|
||||
@@ -175,7 +170,10 @@ func make(address string, args []string, types []string, slip bool) {
|
||||
oscMessage.Args = append(oscMessage.Args, argToTypedArg(arg, oscType))
|
||||
}
|
||||
|
||||
oscMessageBuffer := oscMessage.ToBytes()
|
||||
oscMessageBuffer, err := oscMessage.ToBytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if slip {
|
||||
oscMessageBuffer = slipEncode(oscMessageBuffer)
|
||||
|
||||
@@ -12,32 +12,25 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
var Host string
|
||||
var Port int32
|
||||
var Protocol string
|
||||
var Format string
|
||||
|
||||
cmd := &cli.Command{
|
||||
Name: "receiveosc",
|
||||
Usage: "receive OSC messages via UDP or TCP",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "host",
|
||||
Usage: "host to send OSC message to",
|
||||
Value: "127.0.0.1",
|
||||
Destination: &Host,
|
||||
Name: "ip",
|
||||
Usage: "ip to receive OSC messages on",
|
||||
Value: "0.0.0.0",
|
||||
},
|
||||
&cli.Int32Flag{
|
||||
Name: "port",
|
||||
Usage: "port to send OSC message to",
|
||||
Destination: &Port,
|
||||
Value: 8888,
|
||||
Name: "port",
|
||||
Usage: "port to receive OSC messages on",
|
||||
Value: 8888,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "protocol",
|
||||
Usage: "protocol to use to send (tcp or udp)",
|
||||
Value: "udp",
|
||||
Destination: &Protocol,
|
||||
Name: "protocol",
|
||||
Usage: "protocol to use to receive (tcp or udp)",
|
||||
Value: "udp",
|
||||
Validator: func(flag string) error {
|
||||
if flag != "udp" && flag != "tcp" {
|
||||
return fmt.Errorf("protocol must be either 'udp' or 'tcp'")
|
||||
@@ -46,10 +39,9 @@ func main() {
|
||||
},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "format",
|
||||
Usage: "format for messages to be output in ('json')",
|
||||
Value: "json",
|
||||
Destination: &Format,
|
||||
Name: "format",
|
||||
Usage: "format for messages to be output in ('json')",
|
||||
Value: "json",
|
||||
Validator: func(flag string) error {
|
||||
if flag != "json" {
|
||||
return fmt.Errorf("format must be 'json'")
|
||||
@@ -57,13 +49,28 @@ func main() {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "slip",
|
||||
Value: false,
|
||||
Usage: "whether to slip encode the OSC Message bytes",
|
||||
},
|
||||
},
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
netAddress := fmt.Sprintf("%s:%d", Host, Port)
|
||||
if Protocol == "udp" {
|
||||
listenUDP(netAddress, Format)
|
||||
} else if Protocol == "tcp" {
|
||||
listenTCP(netAddress, Format)
|
||||
ip := cmd.String("ip")
|
||||
port := cmd.Int32("port")
|
||||
protocol := cmd.String("protocol")
|
||||
format := cmd.String("format")
|
||||
slip := cmd.Bool("slip")
|
||||
|
||||
netAddress := fmt.Sprintf("%s:%d", ip, port)
|
||||
switch protocol {
|
||||
case "udp":
|
||||
listenUDP(netAddress, format)
|
||||
case "tcp":
|
||||
if !slip {
|
||||
return fmt.Errorf("OSC 1.0 over TCP is not supported yet")
|
||||
}
|
||||
listenTCP(netAddress, slip, format)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
@@ -74,7 +81,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func listenTCP(netAddress string, format string) {
|
||||
func listenTCP(netAddress string, useSLIP bool, format string) {
|
||||
socket, err := net.Listen("tcp4", netAddress)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
@@ -89,13 +96,13 @@ func listenTCP(netAddress string, format string) {
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
go handleConnection(conn, format)
|
||||
go handleTCPConnection(conn, useSLIP, format)
|
||||
}
|
||||
}
|
||||
|
||||
type SLIP struct {
|
||||
pendingBytes []byte
|
||||
Messages chan osc.OSCMessage
|
||||
Packets chan osc.OSCPacket
|
||||
}
|
||||
|
||||
func (s *SLIP) decode(bytes []byte) {
|
||||
@@ -121,14 +128,14 @@ func (s *SLIP) decode(bytes []byte) {
|
||||
escapeNext = false
|
||||
} else if packetByte == END {
|
||||
if len(s.pendingBytes) == 0 {
|
||||
// opening END byte, can discard
|
||||
// probably opening END byte, can discard
|
||||
continue
|
||||
} else {
|
||||
message, err := osc.MessageFromBytes(s.pendingBytes)
|
||||
oscPacket, _, err := osc.PacketFromBytes(s.pendingBytes)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
panic(err)
|
||||
} else {
|
||||
s.Messages <- message
|
||||
s.Packets <- oscPacket
|
||||
}
|
||||
}
|
||||
s.pendingBytes = []byte{}
|
||||
@@ -136,19 +143,18 @@ func (s *SLIP) decode(bytes []byte) {
|
||||
s.pendingBytes = append(s.pendingBytes, packetByte)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func handleSLIP(slip SLIP, format string) {
|
||||
for message := range slip.Messages {
|
||||
handleMessage(message, format)
|
||||
for message := range slip.Packets {
|
||||
handlePacket(message, format)
|
||||
}
|
||||
}
|
||||
|
||||
func handleConnection(conn net.Conn, format string) {
|
||||
func handleTCPConnection(conn net.Conn, useSLIP bool, format string) {
|
||||
slip := SLIP{
|
||||
pendingBytes: []byte{},
|
||||
Messages: make(chan osc.OSCMessage),
|
||||
Packets: make(chan osc.OSCPacket),
|
||||
}
|
||||
go handleSLIP(slip, format)
|
||||
|
||||
@@ -161,12 +167,26 @@ func handleConnection(conn net.Conn, format string) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if useSLIP {
|
||||
slip.decode(buffer[0:bytesRead])
|
||||
} else {
|
||||
// TODO(jwetzell): handle non-SLIP TCP messages properly
|
||||
}
|
||||
|
||||
slip.decode(buffer[0:bytesRead])
|
||||
}
|
||||
}
|
||||
|
||||
func handleMessage(message osc.OSCMessage, format string) {
|
||||
func handlePacket(message osc.OSCPacket, format string) {
|
||||
if bundle, ok := message.(*osc.OSCBundle); ok {
|
||||
handleBundle(bundle, format)
|
||||
} else if msg, ok := message.(*osc.OSCMessage); ok {
|
||||
handleMessage(msg, format)
|
||||
} else {
|
||||
fmt.Println("Received unknown OSC Packet type")
|
||||
}
|
||||
}
|
||||
|
||||
func handleMessage(message *osc.OSCMessage, format string) {
|
||||
if format == "json" {
|
||||
jsonData, _ := json.Marshal(message)
|
||||
fmt.Println(string(jsonData))
|
||||
@@ -175,6 +195,12 @@ func handleMessage(message osc.OSCMessage, format string) {
|
||||
}
|
||||
}
|
||||
|
||||
func handleBundle(bundle *osc.OSCBundle, format string) {
|
||||
for _, packet := range bundle.Contents {
|
||||
handlePacket(packet, format)
|
||||
}
|
||||
}
|
||||
|
||||
func listenUDP(netAddress string, format string) {
|
||||
|
||||
s, err := net.ResolveUDPAddr("udp4", netAddress)
|
||||
@@ -199,11 +225,11 @@ func listenUDP(netAddress string, format string) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
oscMessage, err := osc.MessageFromBytes(buffer[0:bytesRead])
|
||||
oscPacket, _, err := osc.PacketFromBytes(buffer[0:bytesRead])
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
handleMessage(oscMessage, format)
|
||||
handlePacket(oscPacket, format)
|
||||
}
|
||||
}
|
||||
|
||||
+44
-42
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -14,35 +15,25 @@ import (
|
||||
)
|
||||
|
||||
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{
|
||||
Name: "sendosc",
|
||||
Usage: "send OSC messages via UDP or TCP",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "host",
|
||||
Usage: "host to send OSC message to",
|
||||
Destination: &Host,
|
||||
Required: true,
|
||||
Name: "host",
|
||||
Usage: "host to send OSC message to",
|
||||
Required: true,
|
||||
},
|
||||
&cli.Int32Flag{
|
||||
Name: "port",
|
||||
Usage: "port to send OSC message to",
|
||||
Destination: &Port,
|
||||
Required: true,
|
||||
Name: "port",
|
||||
Usage: "port to send OSC message to",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "protocol",
|
||||
Usage: "protocol to use to send (tcp or udp)",
|
||||
Value: "udp",
|
||||
Destination: &Protocol,
|
||||
Name: "protocol",
|
||||
Usage: "protocol to use to send (tcp or udp)",
|
||||
Value: "udp",
|
||||
Validator: func(flag string) error {
|
||||
if flag != "udp" && flag != "tcp" {
|
||||
return fmt.Errorf("protocol must be either 'udp' or 'tcp'")
|
||||
@@ -51,32 +42,35 @@ func main() {
|
||||
},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "address",
|
||||
Usage: "OSC address",
|
||||
Destination: &Address,
|
||||
Required: true,
|
||||
Name: "address",
|
||||
Usage: "OSC address",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "arg",
|
||||
Usage: "OSC args",
|
||||
Value: []string{},
|
||||
Destination: &Args,
|
||||
Name: "arg",
|
||||
Usage: "OSC args",
|
||||
Value: []string{},
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "type",
|
||||
Usage: "OSC types",
|
||||
Value: []string{},
|
||||
Destination: &Types,
|
||||
Name: "type",
|
||||
Usage: "OSC types",
|
||||
Value: []string{},
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "slip",
|
||||
Value: false,
|
||||
Usage: "whether to slip encode the OSC Message bytes",
|
||||
Destination: &Slip,
|
||||
Name: "slip",
|
||||
Value: false,
|
||||
Usage: "whether to slip encode the OSC Message bytes",
|
||||
},
|
||||
},
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
send(Host, Port, Address, Args, Types, Protocol, Slip)
|
||||
host := cmd.String("host")
|
||||
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
|
||||
},
|
||||
}
|
||||
@@ -160,8 +154,7 @@ func argToTypedArg(rawArg string, oscType string) osc.OSCArg {
|
||||
Type: "N",
|
||||
}
|
||||
default:
|
||||
fmt.Print("unhandled osc type: ")
|
||||
fmt.Printf("%s.\n", oscType)
|
||||
fmt.Printf("unsupported OSC arg type: %s\n", oscType)
|
||||
// TODO(jwetzell): something better than this like actual nil, err thing
|
||||
return osc.OSCArg{}
|
||||
}
|
||||
@@ -177,9 +170,9 @@ func slipEncode(bytes []byte) []byte {
|
||||
|
||||
for _, byteToEncode := range bytes {
|
||||
if byteToEncode == END {
|
||||
encodedBytes = append(encodedBytes, ESC_END)
|
||||
encodedBytes = append(encodedBytes, ESC, ESC_END)
|
||||
} else if byteToEncode == ESC {
|
||||
encodedBytes = append(encodedBytes, ESC_ESC)
|
||||
encodedBytes = append(encodedBytes, ESC, ESC_ESC)
|
||||
} else {
|
||||
encodedBytes = append(encodedBytes, byteToEncode)
|
||||
}
|
||||
@@ -207,13 +200,22 @@ 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 {
|
||||
oscMessageBuffer = slipEncode(oscMessageBuffer)
|
||||
} else if protocol == "tcp" {
|
||||
// OSC 1.0 prepends a 4 byte size header for non-SLIP TCP messages
|
||||
size := uint32(len(oscMessageBuffer))
|
||||
sizeBytes := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(sizeBytes, size)
|
||||
oscMessageBuffer = append(sizeBytes, oscMessageBuffer...)
|
||||
}
|
||||
|
||||
netAddress := fmt.Sprintf("%s:%d", host, port)
|
||||
netAddress := net.JoinHostPort(host, fmt.Sprintf("%d", port))
|
||||
conn, err := net.Dial(protocol, netAddress)
|
||||
if err != nil {
|
||||
fmt.Printf("Dial err %v", err)
|
||||
|
||||
@@ -2,4 +2,4 @@ module github.com/jwetzell/osc-go
|
||||
|
||||
go 1.25.1
|
||||
|
||||
require github.com/urfave/cli/v3 v3.4.1
|
||||
require github.com/urfave/cli/v3 v3.8.0
|
||||
|
||||
@@ -2,9 +2,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM=
|
||||
github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
|
||||
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/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI=
|
||||
github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
+42
-15
@@ -5,8 +5,16 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (m *OSCMessage) ToBytes() []byte {
|
||||
//TODO(jwetzell): add error handling
|
||||
func (m *OSCMessage) ToBytes() ([]byte, error) {
|
||||
|
||||
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 = append(oscBuffer, stringToOSCBytes(m.Address)...)
|
||||
@@ -18,18 +26,28 @@ func (m *OSCMessage) ToBytes() []byte {
|
||||
for _, arg := range m.Args {
|
||||
sb.WriteString(arg.Type)
|
||||
}
|
||||
|
||||
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) {
|
||||
address, typeAndArgBytes := readOSCString(bytes)
|
||||
func MessageFromBytes(bytes []byte) (*OSCMessage, error) {
|
||||
if len(bytes) == 0 {
|
||||
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 /")
|
||||
}
|
||||
|
||||
if address[0] != 47 {
|
||||
return OSCMessage{}, errors.New("OSC Message address must start with /")
|
||||
address, typeAndArgBytes, err := readOSCString(bytes)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oscMessage := OSCMessage{
|
||||
@@ -37,22 +55,31 @@ func MessageFromBytes(bytes []byte) (OSCMessage, error) {
|
||||
Args: []OSCArg{},
|
||||
}
|
||||
|
||||
typeString, argBytes := readOSCString(typeAndArgBytes)
|
||||
if len(typeAndArgBytes) == 0 {
|
||||
// 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 {
|
||||
if index == 0 {
|
||||
if oscType != ',' {
|
||||
return OSCMessage{}, errors.New("type string is malformed")
|
||||
return nil, errors.New("type string is malformed")
|
||||
}
|
||||
} else {
|
||||
oscArg, remainingBytes, error := readOSCArg(argBytes, string(oscType))
|
||||
if error != nil {
|
||||
return oscMessage, error
|
||||
oscArg, remainingBytes, err := readOSCArg(argBytes, string(oscType))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
argBytes = remainingBytes
|
||||
oscMessage.Args = append(oscMessage.Args, oscArg)
|
||||
}
|
||||
}
|
||||
|
||||
return oscMessage, nil
|
||||
return &oscMessage, nil
|
||||
}
|
||||
|
||||
+334
-106
@@ -1,30 +1,29 @@
|
||||
package osc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOSCMessageEncoding(t *testing.T) {
|
||||
func TestGoodOSCMessageEncoding(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
description string
|
||||
message OSCMessage
|
||||
expected []byte
|
||||
name string
|
||||
message *OSCMessage
|
||||
expected []byte
|
||||
}{
|
||||
{
|
||||
"simple hello",
|
||||
OSCMessage{
|
||||
name: "simple hello",
|
||||
message: &OSCMessage{
|
||||
Address: "/hello",
|
||||
Args: []OSCArg{},
|
||||
},
|
||||
[]byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 0, 0, 0},
|
||||
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 0, 0, 0},
|
||||
},
|
||||
{
|
||||
"simple address string arg",
|
||||
OSCMessage{
|
||||
name: "simple address string arg",
|
||||
message: &OSCMessage{
|
||||
Address: "/hello",
|
||||
Args: []OSCArg{
|
||||
{
|
||||
@@ -33,58 +32,58 @@ func TestOSCMessageEncoding(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
[]byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 115, 0, 0, 97, 114, 103, 49, 0, 0, 0, 0},
|
||||
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 115, 0, 0, 97, 114, 103, 49, 0, 0, 0, 0},
|
||||
},
|
||||
{
|
||||
description: "simple address integer arg",
|
||||
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},
|
||||
name: "simple address integer arg",
|
||||
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},
|
||||
},
|
||||
{
|
||||
description: "simple address float arg",
|
||||
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},
|
||||
name: "simple address float arg",
|
||||
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},
|
||||
},
|
||||
{
|
||||
description: "simple address blob arg",
|
||||
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},
|
||||
name: "simple address blob arg",
|
||||
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},
|
||||
},
|
||||
{
|
||||
description: "simple address True arg",
|
||||
message: OSCMessage{Address: "/hello", Args: []OSCArg{OSCArg{Type: "T", Value: true}}},
|
||||
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 84, 0, 0},
|
||||
name: "simple address True arg",
|
||||
message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "T", Value: true}}},
|
||||
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 84, 0, 0},
|
||||
},
|
||||
{
|
||||
description: "simple address False arg",
|
||||
message: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "F", Value: false}}},
|
||||
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 70, 0, 0},
|
||||
name: "simple address False arg",
|
||||
message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "F", Value: false}}},
|
||||
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 70, 0, 0},
|
||||
},
|
||||
{
|
||||
description: "simple address color arg",
|
||||
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},
|
||||
name: "simple address color arg",
|
||||
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},
|
||||
},
|
||||
{
|
||||
description: "simple address nil arg",
|
||||
message: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "N", Value: nil}}},
|
||||
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 78, 0, 0},
|
||||
name: "simple address nil arg",
|
||||
message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "N", Value: nil}}},
|
||||
expected: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 78, 0, 0},
|
||||
},
|
||||
{
|
||||
description: "simple address int64 arg",
|
||||
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},
|
||||
name: "simple address int64 arg",
|
||||
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},
|
||||
},
|
||||
{
|
||||
description: "simple address float64 arg",
|
||||
message: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "d", Value: 12.7654763}}},
|
||||
name: "simple address float64 arg",
|
||||
message: &OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "d", Value: 12.7654763}}},
|
||||
expected: []byte{
|
||||
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
|
||||
// {
|
||||
// description: "simple address array arg",
|
||||
// name: "simple address array arg",
|
||||
// message: OSCMessage{
|
||||
// Address: "/hello",
|
||||
// Args: []OSCArg{
|
||||
@@ -100,16 +99,16 @@ func TestOSCMessageEncoding(t *testing.T) {
|
||||
// },
|
||||
// },
|
||||
{
|
||||
description: "osc 1.0 spec example 1",
|
||||
message: OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: 440}}},
|
||||
name: "osc 1.0 spec example 1",
|
||||
message: &OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: 440}}},
|
||||
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,
|
||||
102, 0, 0, 67, 220, 0, 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "osc 1.0 spec example 2",
|
||||
message: OSCMessage{
|
||||
name: "osc 1.0 spec example 2",
|
||||
message: &OSCMessage{
|
||||
Address: "/foo",
|
||||
Args: []OSCArg{
|
||||
{Type: "i", Value: 1000},
|
||||
@@ -128,81 +127,181 @@ func TestOSCMessageEncoding(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
got, err := testCase.message.ToBytes()
|
||||
|
||||
actual := testCase.message.ToBytes()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to encode properly: %s", err.Error())
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
if !reflect.DeepEqual(got, testCase.expected) {
|
||||
t.Fatalf("failed to encode properly got '%v', expected '%v'", got, testCase.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestOSCMessageDecoding(t *testing.T) {
|
||||
func TestBadOSCMessageEncoding(t *testing.T) {
|
||||
testCases := []struct {
|
||||
description string
|
||||
bytes []byte
|
||||
expected OSCMessage
|
||||
name string
|
||||
message *OSCMessage
|
||||
errorString string
|
||||
}{
|
||||
{
|
||||
description: "simple address no args",
|
||||
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 0, 0, 0},
|
||||
expected: OSCMessage{Address: "/hello", Args: []OSCArg{}},
|
||||
name: "empty message",
|
||||
message: &OSCMessage{},
|
||||
errorString: "OSC Message must have an address",
|
||||
},
|
||||
{
|
||||
description: "simple address string arg",
|
||||
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 115, 0, 0, 97, 114, 103, 49, 0, 0, 0, 0},
|
||||
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "s", Value: "arg1"}}},
|
||||
name: "address does not start with /",
|
||||
message: &OSCMessage{Address: "hello"},
|
||||
errorString: "OSC Message address must start with /",
|
||||
},
|
||||
{
|
||||
description: "simple address integer arg",
|
||||
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 105, 0, 0, 0, 0, 0, 35},
|
||||
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "i", Value: int32(35)}}},
|
||||
name: "arg with unsupported type",
|
||||
message: &OSCMessage{
|
||||
Address: "/hello",
|
||||
Args: []OSCArg{{Type: "x", Value: "unsupported"}},
|
||||
},
|
||||
errorString: "unsupported OSC argument type: x",
|
||||
},
|
||||
{
|
||||
description: "simple address float arg",
|
||||
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 102, 0, 0, 66, 10, 0, 0},
|
||||
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "f", Value: float32(34.5)}}},
|
||||
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",
|
||||
},
|
||||
{
|
||||
description: "simple address blob arg",
|
||||
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 98, 0, 0, 0, 0, 0, 4, 98, 108, 111, 98},
|
||||
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "b", Value: []byte{98, 108, 111, 98}}}},
|
||||
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",
|
||||
},
|
||||
{
|
||||
description: "simple address True arg",
|
||||
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 84, 0, 0},
|
||||
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "T", Value: true}}},
|
||||
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",
|
||||
},
|
||||
{
|
||||
description: "simple address False arg",
|
||||
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 70, 0, 0},
|
||||
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "F", Value: false}}},
|
||||
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",
|
||||
},
|
||||
{
|
||||
description: "simple address color arg",
|
||||
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 114, 0, 0, 20, 21, 22, 10},
|
||||
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "r", Value: OSCColor{r: 20, g: 21, b: 22, a: 10}}}},
|
||||
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",
|
||||
},
|
||||
{
|
||||
description: "simple address nil arg",
|
||||
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 78, 0, 0},
|
||||
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "N", Value: nil}}},
|
||||
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",
|
||||
},
|
||||
{
|
||||
description: "simple address Inifinitum arg",
|
||||
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 73, 0, 0},
|
||||
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "I", Value: math.MaxInt32}}},
|
||||
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) {
|
||||
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{}},
|
||||
},
|
||||
{
|
||||
description: "simple address int64 arg",
|
||||
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 104, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255},
|
||||
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "h", Value: int64(281474976710655)}}},
|
||||
name: "simple address string arg",
|
||||
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 115, 0, 0, 97, 114, 103, 49, 0, 0, 0, 0},
|
||||
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "s", Value: "arg1"}}},
|
||||
},
|
||||
{
|
||||
description: "simple address float64 arg",
|
||||
name: "simple address integer arg",
|
||||
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0, 44, 105, 0, 0, 0, 0, 0, 35},
|
||||
expected: OSCMessage{Address: "/hello", Args: []OSCArg{{Type: "i", Value: int32(35)}}},
|
||||
},
|
||||
{
|
||||
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{
|
||||
47, 104, 101, 108, 108, 111, 0, 0, 44, 100, 0, 0, 0x40, 0x29, 0x87, 0xec, 0x82, 0x74, 0xb9, 0xe6,
|
||||
},
|
||||
@@ -210,7 +309,7 @@ func TestOSCMessageDecoding(t *testing.T) {
|
||||
},
|
||||
// TODO(jwetzell): support OSC array
|
||||
// {
|
||||
// description: "simple address array arg",
|
||||
// name: "simple address array arg",
|
||||
// bytes: []byte{
|
||||
// 47, 104, 101, 108, 108, 111, 0, 0, 44, 91, 100, 105, 93, 0, 0, 0, 0x40, 0x29, 0x87, 0xec, 0x82, 0x74, 0xb9, 0xe6,
|
||||
// 0, 0, 3, 232,
|
||||
@@ -226,15 +325,15 @@ func TestOSCMessageDecoding(t *testing.T) {
|
||||
// },
|
||||
// },
|
||||
{
|
||||
description: "simple address no type string",
|
||||
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0},
|
||||
name: "simple address no type string",
|
||||
bytes: []byte{47, 104, 101, 108, 108, 111, 0, 0},
|
||||
expected: OSCMessage{
|
||||
Address: "/hello",
|
||||
Args: []OSCArg{},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "osc 1.0 spec example 1",
|
||||
name: "osc 1.0 spec example 1",
|
||||
bytes: []byte{
|
||||
47, 111, 115, 99, 105, 108, 108, 97, 116, 111, 114, 47, 52, 47, 102, 114, 101, 113, 117, 101, 110, 99, 121, 0, 44,
|
||||
102, 0, 0, 67, 220, 0, 0,
|
||||
@@ -242,7 +341,7 @@ func TestOSCMessageDecoding(t *testing.T) {
|
||||
expected: OSCMessage{Address: "/oscillator/4/frequency", Args: []OSCArg{{Type: "f", Value: float32(440)}}},
|
||||
},
|
||||
{
|
||||
description: "osc 1.0 spec example 2",
|
||||
name: "osc 1.0 spec example 2",
|
||||
bytes: []byte{
|
||||
47, 102, 111, 111, 0, 0, 0, 0, 44, 105, 105, 115, 102, 102, 0, 0, 0, 0, 3, 232, 255, 255, 255, 255, 104, 101, 108,
|
||||
108, 111, 0, 0, 0, 63, 157, 243, 182, 64, 181, 178, 45,
|
||||
@@ -262,24 +361,153 @@ func TestOSCMessageDecoding(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
|
||||
actual, error := MessageFromBytes(testCase.bytes)
|
||||
actual, err := MessageFromBytes(testCase.bytes)
|
||||
|
||||
if error != nil {
|
||||
fmt.Println(error)
|
||||
t.Errorf("Test '%s' failed to encode properly", testCase.description)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("failed to encode properly: %s", err.Error())
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual.Address, testCase.expected.Address) {
|
||||
t.Errorf("Test '%s' failed to encode address properly", testCase.description)
|
||||
fmt.Printf("expected: %v\n", testCase.expected.Address)
|
||||
fmt.Printf("actual: %v\n", actual.Address)
|
||||
}
|
||||
if !reflect.DeepEqual(actual.Address, testCase.expected.Address) {
|
||||
t.Fatalf("failed to encode address propertly got '%s', expected '%s'", actual.Address, testCase.expected.Address)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual.Args, testCase.expected.Args) {
|
||||
t.Errorf("Test '%s' failed to encode args properly", testCase.description)
|
||||
fmt.Printf("expected: %v\n", testCase.expected.Args)
|
||||
fmt.Printf("actual: %v\n", actual.Args)
|
||||
}
|
||||
if !reflect.DeepEqual(actual.Args, testCase.expected.Args) {
|
||||
t.Fatalf("failed to encode args properly got '%+v', expected '%+v'", actual.Args, testCase.expected.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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package osc
|
||||
|
||||
// TODO(jwetzell): split things up
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -18,7 +17,7 @@ func stringToOSCBytes(rawString string) []byte {
|
||||
|
||||
padLength := 4 - (len(sb.String()) % 4)
|
||||
if padLength < 4 {
|
||||
for i := 0; i < padLength; i++ {
|
||||
for range padLength {
|
||||
sb.WriteString("\u0000")
|
||||
}
|
||||
}
|
||||
@@ -27,51 +26,50 @@ func stringToOSCBytes(rawString string) []byte {
|
||||
}
|
||||
|
||||
func int32ToOSCBytes(number int32) []byte {
|
||||
var buf bytes.Buffer
|
||||
err := binary.Write(&buf, binary.BigEndian, number)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return buf.Bytes()
|
||||
bytes := make([]byte, 4)
|
||||
bytes[0] = byte((number >> 24) & 0xFF)
|
||||
bytes[1] = byte((number >> 16) & 0xFF)
|
||||
bytes[2] = byte((number >> 8) & 0xFF)
|
||||
bytes[3] = byte(number & 0xFF)
|
||||
return bytes
|
||||
}
|
||||
|
||||
func int64ToOSCBytes(number int64) []byte {
|
||||
var buf bytes.Buffer
|
||||
err := binary.Write(&buf, binary.BigEndian, number)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return buf.Bytes()
|
||||
bytes := make([]byte, 8)
|
||||
bytes[0] = byte((number >> 56) & 0xFF)
|
||||
bytes[1] = byte((number >> 48) & 0xFF)
|
||||
bytes[2] = byte((number >> 40) & 0xFF)
|
||||
bytes[3] = byte((number >> 32) & 0xFF)
|
||||
bytes[4] = byte((number >> 24) & 0xFF)
|
||||
bytes[5] = byte((number >> 16) & 0xFF)
|
||||
bytes[6] = byte((number >> 8) & 0xFF)
|
||||
bytes[7] = byte(number & 0xFF)
|
||||
return bytes
|
||||
}
|
||||
|
||||
func float32ToOSCBytes(number float32) []byte {
|
||||
var buf bytes.Buffer
|
||||
err := binary.Write(&buf, binary.BigEndian, number)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return buf.Bytes()
|
||||
bytes := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(bytes, math.Float32bits(number))
|
||||
return bytes
|
||||
}
|
||||
|
||||
func float64ToOSCBytes(number float64) []byte {
|
||||
var buf bytes.Buffer
|
||||
err := binary.Write(&buf, binary.BigEndian, number)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return buf.Bytes()
|
||||
bytes := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(bytes, math.Float64bits(number))
|
||||
return bytes
|
||||
}
|
||||
|
||||
func byteArrayToOSCBytes(bytes []byte) []byte {
|
||||
oscBytes := []byte{}
|
||||
|
||||
bytesSize := len(bytes)
|
||||
oscBytes = append(oscBytes, int32ToOSCBytes(int32(bytesSize))...)
|
||||
bytesSizeBytes := int32ToOSCBytes(int32(bytesSize))
|
||||
oscBytes = append(oscBytes, bytesSizeBytes...)
|
||||
oscBytes = append(oscBytes, bytes...)
|
||||
|
||||
padLength := 4 - (bytesSize % 4)
|
||||
if padLength < 4 {
|
||||
for i := 0; i < padLength; i++ {
|
||||
for range padLength {
|
||||
oscBytes = append(oscBytes, 0)
|
||||
}
|
||||
}
|
||||
@@ -81,50 +79,59 @@ func byteArrayToOSCBytes(bytes []byte) []byte {
|
||||
|
||||
func timeTagToOSCBytes(timeTag OSCTimeTag) []byte {
|
||||
timeTagBytes := int32ToOSCBytes(timeTag.seconds)
|
||||
timeTagBytes = append(timeTagBytes, int32ToOSCBytes(timeTag.fractionalSeconds)...)
|
||||
fractionalSecondsBytes := int32ToOSCBytes(timeTag.fractionalSeconds)
|
||||
timeTagBytes = append(timeTagBytes, fractionalSecondsBytes...)
|
||||
|
||||
return timeTagBytes
|
||||
}
|
||||
|
||||
func argsToBuffer(args []OSCArg) []byte {
|
||||
func argsToBuffer(args []OSCArg) ([]byte, error) {
|
||||
//TODO(jwetzell): add error handling
|
||||
var argBuffers = []byte{}
|
||||
|
||||
for _, arg := range args {
|
||||
switch oscType := arg.Type; oscType {
|
||||
switch arg.Type {
|
||||
case "s":
|
||||
if value, ok := arg.Value.(string); ok {
|
||||
argBuffers = append(argBuffers, stringToOSCBytes(value)...)
|
||||
} 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":
|
||||
if value, ok := arg.Value.(int); ok {
|
||||
argBuffers = append(argBuffers, int32ToOSCBytes(int32(value))...)
|
||||
valueBytes := int32ToOSCBytes(int32(value))
|
||||
argBuffers = append(argBuffers, valueBytes...)
|
||||
} else if value, ok := arg.Value.(int32); ok {
|
||||
argBuffers = append(argBuffers, int32ToOSCBytes(value)...)
|
||||
valueBytes := int32ToOSCBytes(int32(value))
|
||||
argBuffers = append(argBuffers, valueBytes...)
|
||||
} 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":
|
||||
if value, ok := arg.Value.(float32); ok {
|
||||
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
|
||||
valueBytes := float32ToOSCBytes(value)
|
||||
argBuffers = append(argBuffers, valueBytes...)
|
||||
} else if value, ok := arg.Value.(float64); ok {
|
||||
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
|
||||
valueBytes := float32ToOSCBytes(float32(value))
|
||||
argBuffers = append(argBuffers, valueBytes...)
|
||||
} else if value, ok := arg.Value.(int); ok {
|
||||
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
|
||||
valueBytes := float32ToOSCBytes(float32(value))
|
||||
argBuffers = append(argBuffers, valueBytes...)
|
||||
} else if value, ok := arg.Value.(int32); ok {
|
||||
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
|
||||
valueBytes := float32ToOSCBytes(float32(value))
|
||||
argBuffers = append(argBuffers, valueBytes...)
|
||||
} else if value, ok := arg.Value.(int64); ok {
|
||||
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
|
||||
valueBytes := float32ToOSCBytes(float32(value))
|
||||
argBuffers = append(argBuffers, valueBytes...)
|
||||
} 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":
|
||||
if value, ok := arg.Value.([]byte); ok {
|
||||
argBuffers = append(argBuffers, byteArrayToOSCBytes(value)...)
|
||||
valueBytes := byteArrayToOSCBytes(value)
|
||||
argBuffers = append(argBuffers, valueBytes...)
|
||||
} 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":
|
||||
argBuffers = append(argBuffers, make([]byte, 0)...)
|
||||
@@ -136,72 +143,88 @@ func argsToBuffer(args []OSCArg) []byte {
|
||||
argBuffers = append(argBuffers, make([]byte, 0)...)
|
||||
case "r":
|
||||
color, ok := arg.Value.(OSCColor)
|
||||
if !ok {
|
||||
return nil, errors.New("OSC arg had color type but non-color value")
|
||||
}
|
||||
if ok {
|
||||
colorBytes := []byte{color.r, color.g, color.b, color.a}
|
||||
argBuffers = append(argBuffers, colorBytes...)
|
||||
}
|
||||
case "h":
|
||||
if value, ok := arg.Value.(int); ok {
|
||||
argBuffers = append(argBuffers, int64ToOSCBytes(int64(value))...)
|
||||
valueBytes := int64ToOSCBytes(int64(value))
|
||||
argBuffers = append(argBuffers, valueBytes...)
|
||||
} else if value, ok := arg.Value.(int32); ok {
|
||||
argBuffers = append(argBuffers, int64ToOSCBytes(int64(value))...)
|
||||
valueBytes := int64ToOSCBytes(int64(value))
|
||||
argBuffers = append(argBuffers, valueBytes...)
|
||||
} else if value, ok := arg.Value.(int64); ok {
|
||||
argBuffers = append(argBuffers, int64ToOSCBytes(value)...)
|
||||
valueBytes := int64ToOSCBytes(value)
|
||||
argBuffers = append(argBuffers, valueBytes...)
|
||||
} 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":
|
||||
if value, ok := arg.Value.(float32); ok {
|
||||
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
|
||||
valueBytes := float64ToOSCBytes(float64(value))
|
||||
argBuffers = append(argBuffers, valueBytes...)
|
||||
} else if value, ok := arg.Value.(float64); ok {
|
||||
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
|
||||
valueBytes := float64ToOSCBytes(value)
|
||||
argBuffers = append(argBuffers, valueBytes...)
|
||||
} else if value, ok := arg.Value.(int); ok {
|
||||
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
|
||||
valueBytes := float64ToOSCBytes(float64(value))
|
||||
argBuffers = append(argBuffers, valueBytes...)
|
||||
} else if value, ok := arg.Value.(int32); ok {
|
||||
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
|
||||
valueBytes := float64ToOSCBytes(float64(value))
|
||||
argBuffers = append(argBuffers, valueBytes...)
|
||||
} else if value, ok := arg.Value.(int64); ok {
|
||||
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
|
||||
valueBytes := float64ToOSCBytes(float64(value))
|
||||
argBuffers = append(argBuffers, valueBytes...)
|
||||
} 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:
|
||||
fmt.Printf("unhandled osc type: %s.\n", oscType)
|
||||
return nil, fmt.Errorf("unsupported OSC argument type: %s", arg.Type)
|
||||
}
|
||||
}
|
||||
return argBuffers
|
||||
return argBuffers, nil
|
||||
}
|
||||
|
||||
func readOSCString(bytes []byte) (string, []byte) {
|
||||
//TODO(jwetzell): add error handling
|
||||
func readOSCString(bytes []byte) (string, []byte, error) {
|
||||
oscString := ""
|
||||
stringFinished := false
|
||||
stringEndIndex := 0
|
||||
remainingBytes := []byte{}
|
||||
|
||||
nullByteFound := false
|
||||
for index, byteIn := range bytes {
|
||||
if !stringFinished {
|
||||
if byteIn == 0 {
|
||||
oscString = string(bytes[0:index])
|
||||
stringEndIndex = index + 1
|
||||
break
|
||||
}
|
||||
if byteIn == 0 {
|
||||
nullByteFound = true
|
||||
oscString = string(bytes[0:index])
|
||||
stringEndIndex = index + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !nullByteFound {
|
||||
return "", bytes, errors.New("OSC string must be null-terminated")
|
||||
}
|
||||
|
||||
stringPadding := 4 - (stringEndIndex % 4)
|
||||
|
||||
if stringPadding < 4 {
|
||||
stringEndIndex = stringEndIndex + stringPadding
|
||||
}
|
||||
|
||||
remainingBytes = bytes[stringEndIndex:]
|
||||
if stringEndIndex > len(bytes) {
|
||||
return "", bytes, errors.New("OSC string is not properly padded")
|
||||
}
|
||||
|
||||
return oscString, remainingBytes
|
||||
remainingBytes := bytes[stringEndIndex:]
|
||||
|
||||
return oscString, remainingBytes, nil
|
||||
}
|
||||
|
||||
func readOSCInt32(bytes []byte) (int32, []byte, error) {
|
||||
if len(bytes) < 4 {
|
||||
return 0, bytes, errors.New("int data must be at least 4 bytes large")
|
||||
return 0, bytes, errors.New("OSC int32 arg is not 4 bytes")
|
||||
}
|
||||
bits := binary.BigEndian.Uint32(bytes[0:4])
|
||||
return int32(bits), bytes[4:], nil
|
||||
@@ -209,7 +232,7 @@ func readOSCInt32(bytes []byte) (int32, []byte, error) {
|
||||
|
||||
func readOSCInt64(bytes []byte) (int64, []byte, error) {
|
||||
if len(bytes) < 8 {
|
||||
return 0, bytes, errors.New("int data must be at least 4 bytes large")
|
||||
return 0, bytes, errors.New("OSC int64 arg is not 8 bytes")
|
||||
}
|
||||
bits := binary.BigEndian.Uint64(bytes[0:8])
|
||||
return int64(bits), bytes[8:], nil
|
||||
@@ -217,15 +240,15 @@ func readOSCInt64(bytes []byte) (int64, []byte, error) {
|
||||
|
||||
func readOSCFloat32(bytes []byte) (float32, []byte, error) {
|
||||
if len(bytes) < 4 {
|
||||
return 0, bytes, errors.New("float data must be at least 4 bytes large")
|
||||
return 0, bytes, errors.New("OSC float32 arg is not 4 bytes")
|
||||
}
|
||||
bits := binary.BigEndian.Uint32(bytes[0:4])
|
||||
return math.Float32frombits(bits), bytes[4:], nil
|
||||
}
|
||||
|
||||
func readOSCFloat64(bytes []byte) (float64, []byte, error) {
|
||||
if len(bytes) < 4 {
|
||||
return 0, bytes, errors.New("float data must be at least 4 bytes large")
|
||||
if len(bytes) < 8 {
|
||||
return 0, bytes, errors.New("OSC float64 arg is not 8 bytes")
|
||||
}
|
||||
bits := binary.BigEndian.Uint64(bytes[0:8])
|
||||
return math.Float64frombits(bits), bytes[8:], nil
|
||||
@@ -235,11 +258,15 @@ func readOSCBlob(bytes []byte) ([]byte, []byte, error) {
|
||||
blobLength, remainingBytes, err := readOSCInt32(bytes)
|
||||
|
||||
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 blobLength < 0 {
|
||||
return []byte{}, bytes, errors.New("OSC blob arg size not valid: size cannot be negative")
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -248,12 +275,15 @@ func readOSCBlob(bytes []byte) ([]byte, []byte, error) {
|
||||
if blobLengthPadding < 4 {
|
||||
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
|
||||
}
|
||||
|
||||
func readOSCColor(bytes []byte) (OSCColor, []byte, error) {
|
||||
if len(bytes) < 4 {
|
||||
return OSCColor{0, 0, 0, 0}, bytes, errors.New("color data must be at least 4 bytes large")
|
||||
return OSCColor{0, 0, 0, 0}, bytes, errors.New("OSC color arg is not 4 bytes")
|
||||
}
|
||||
oscColor := OSCColor{
|
||||
r: bytes[0],
|
||||
@@ -263,14 +293,15 @@ func readOSCColor(bytes []byte) (OSCColor, []byte, error) {
|
||||
}
|
||||
return oscColor, bytes[4:], nil
|
||||
}
|
||||
|
||||
func readOSCTimeTag(bytes []byte) (OSCTimeTag, []byte, error) {
|
||||
seconds, bytesAfterSeconds, err := readOSCInt32(bytes)
|
||||
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)
|
||||
if err != nil {
|
||||
return OSCTimeTag{}, bytes, err
|
||||
return OSCTimeTag{}, bytes, fmt.Errorf("OSC time tag fractional seconds are not valid: %s", err)
|
||||
}
|
||||
|
||||
return OSCTimeTag{
|
||||
@@ -291,27 +322,30 @@ func readOSCArg(bytes []byte, oscType string) (OSCArg, []byte, error) {
|
||||
//TODO(jwetzell): add error handling
|
||||
switch oscType {
|
||||
case "s":
|
||||
argString, bytesLeft := readOSCString(bytes)
|
||||
argString, bytesLeft, err := readOSCString(bytes)
|
||||
if err != nil {
|
||||
return OSCArg{}, bytes, err
|
||||
}
|
||||
oscArg.Value = argString
|
||||
remainingBytes = bytesLeft
|
||||
case "i":
|
||||
argInt, bytesLeft, error := readOSCInt32(bytes)
|
||||
if error != nil {
|
||||
readArgError = error
|
||||
argInt, bytesLeft, err := readOSCInt32(bytes)
|
||||
if err != nil {
|
||||
readArgError = err
|
||||
}
|
||||
oscArg.Value = argInt
|
||||
remainingBytes = bytesLeft
|
||||
case "f":
|
||||
argFloat, bytesLeft, error := readOSCFloat32(bytes)
|
||||
if error != nil {
|
||||
readArgError = error
|
||||
argFloat, bytesLeft, err := readOSCFloat32(bytes)
|
||||
if err != nil {
|
||||
readArgError = err
|
||||
}
|
||||
oscArg.Value = argFloat
|
||||
remainingBytes = bytesLeft
|
||||
case "b":
|
||||
argBytes, bytesLeft, error := readOSCBlob(bytes)
|
||||
if error != nil {
|
||||
readArgError = error
|
||||
argBytes, bytesLeft, err := readOSCBlob(bytes)
|
||||
if err != nil {
|
||||
readArgError = err
|
||||
}
|
||||
oscArg.Value = argBytes
|
||||
remainingBytes = bytesLeft
|
||||
@@ -328,29 +362,58 @@ func readOSCArg(bytes []byte, oscType string) (OSCArg, []byte, error) {
|
||||
oscArg.Value = math.MaxInt32
|
||||
remainingBytes = bytes
|
||||
case "r":
|
||||
argColor, bytesLeft, error := readOSCColor(bytes)
|
||||
if error != nil {
|
||||
readArgError = error
|
||||
argColor, bytesLeft, err := readOSCColor(bytes)
|
||||
if err != nil {
|
||||
readArgError = err
|
||||
}
|
||||
oscArg.Value = argColor
|
||||
remainingBytes = bytesLeft
|
||||
case "h":
|
||||
argInt, bytesLeft, error := readOSCInt64(bytes)
|
||||
if error != nil {
|
||||
readArgError = error
|
||||
argInt, bytesLeft, err := readOSCInt64(bytes)
|
||||
if err != nil {
|
||||
readArgError = err
|
||||
}
|
||||
oscArg.Value = argInt
|
||||
remainingBytes = bytesLeft
|
||||
case "d":
|
||||
argFloat, bytesLeft, error := readOSCFloat64(bytes)
|
||||
if error != nil {
|
||||
readArgError = error
|
||||
argFloat, bytesLeft, err := readOSCFloat64(bytes)
|
||||
if err != nil {
|
||||
readArgError = err
|
||||
}
|
||||
oscArg.Value = argFloat
|
||||
remainingBytes = bytesLeft
|
||||
case "t":
|
||||
argTimeTag, bytesLeft, err := readOSCTimeTag(bytes)
|
||||
if err != nil {
|
||||
readArgError = err
|
||||
}
|
||||
oscArg.Value = argTimeTag
|
||||
remainingBytes = bytesLeft
|
||||
default:
|
||||
fmt.Printf("unsupported osc type: %s\n", oscType)
|
||||
readArgError = errors.New("unsupported osc type: " + oscType)
|
||||
return OSCArg{}, bytes, fmt.Errorf("unsupported OSC argument type: %s", oscType)
|
||||
}
|
||||
return oscArg, remainingBytes, readArgError
|
||||
}
|
||||
|
||||
func PacketFromBytes(bytes []byte) (OSCPacket, []byte, error) {
|
||||
if len(bytes) == 0 {
|
||||
return nil, bytes, errors.New("cannot create OSC Packet from empty byte array")
|
||||
}
|
||||
|
||||
switch bytes[0] {
|
||||
case '#':
|
||||
bundle, remainingBytes, err := BundleFromBytes(bytes)
|
||||
if err != nil {
|
||||
return nil, bytes, err
|
||||
}
|
||||
return bundle, remainingBytes, nil
|
||||
case '/':
|
||||
message, err := MessageFromBytes(bytes)
|
||||
if err != nil {
|
||||
return nil, bytes, err
|
||||
}
|
||||
return message, []byte{}, nil
|
||||
default:
|
||||
return nil, bytes, errors.New("OSC Packet must start with # for bundle or / for message")
|
||||
}
|
||||
}
|
||||
|
||||
+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