Compare commits

...

71 Commits

Author SHA1 Message Date
Joel Wetzell d82919e9f1 remove codecov badge 2026-04-13 22:59:44 -05:00
Joel Wetzell 4078efa090 update readme 2026-04-13 22:56:57 -05:00
Joel Wetzell 53864a35dc Merge pull request #20 from jwetzell/error-handling
add error handling to arg encoding and message/bundle ToBytes()
2026-04-13 22:53:07 -05:00
Joel Wetzell 2ec4be5426 more tests for bad arg parsing 2026-04-13 22:51:52 -05:00
Joel Wetzell 5f5c34c4ea error message formatting 2026-04-13 22:51:43 -05:00
Joel Wetzell 16093f2509 add support for OSC timetag arg 2026-04-13 22:51:29 -05:00
Joel Wetzell 191d74a20a error message formatting 2026-04-13 22:42:39 -05:00
Joel Wetzell 738b87b03f add tests for PacketFromBytes 2026-04-13 22:42:30 -05:00
Joel Wetzell 42b1f9cf8a add error tests for arg decoding in message 2026-04-13 22:42:15 -05:00
Joel Wetzell 16e1ad039e add test for nested bundle 2026-04-13 22:41:56 -05:00
Joel Wetzell eb539dd319 add test for bundle with bad contents 2026-04-13 22:41:43 -05:00
Joel Wetzell 1465663a63 add tests just for arg array to bytes 2026-04-13 22:22:57 -05:00
Joel Wetzell dd430dd380 add layout for testing bundle error cases 2026-04-13 22:22:44 -05:00
Joel Wetzell 9452268042 add layout for testing OSCBundle error cases 2026-04-13 19:11:11 -05:00
Joel Wetzell b621944dc5 error tests for arg values when encoding 2026-04-13 18:59:43 -05:00
Joel Wetzell 8fa8aa3a55 return errors instead of printing 2026-04-13 18:53:35 -05:00
Joel Wetzell 1d2684f446 cleanup test layout 2026-04-13 18:53:21 -05:00
Joel Wetzell 2548771fe3 add error handling for OSC address and bad arg type encoding 2026-04-13 18:48:50 -05:00
Joel Wetzell 4744321d71 add error handling to arg encoding and message/bundle ToBytes() 2026-04-13 18:38:25 -05:00
Joel Wetzell 1130adf046 Merge pull request #19 from jwetzell/message-tests
fix up some OSC string parsing issues and test for them
2026-04-13 18:21:56 -05:00
Joel Wetzell ad3c449149 fix up some OSC string parsing issues and test for them 2026-04-13 18:17:43 -05:00
Joel Wetzell fbd2bf3905 Merge pull request #18 from jwetzell/more-bad-tests
fill out some more error tests for messages
2026-04-13 16:24:11 -05:00
Joel Wetzell e1c8563522 fill out some more error tests for messages 2026-04-13 16:20:55 -05:00
Joel Wetzell e2a8daf013 Merge pull request #17 from jwetzell/coverage-test
cleanup message_test layout
2026-04-13 16:14:18 -05:00
Joel Wetzell baa140297f cleanup message_test layout 2026-04-13 16:12:19 -05:00
Joel Wetzell 8bb3f3fc97 add workflow for tests and code coverage report 2026-04-13 15:57:47 -05:00
Joel Wetzell e70934e705 Merge pull request #16 from jwetzell/dependabot/go_modules/github.com/urfave/cli/v3-3.8.0
Bump github.com/urfave/cli/v3 from 3.7.0 to 3.8.0
2026-03-30 12:25:19 -05:00
dependabot[bot] 597b9ad053 Bump github.com/urfave/cli/v3 from 3.7.0 to 3.8.0
Bumps [github.com/urfave/cli/v3](https://github.com/urfave/cli) from 3.7.0 to 3.8.0.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v3.7.0...v3.8.0)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli/v3
  dependency-version: 3.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 02:55:56 +00:00
Joel Wetzell 6f141eb742 Merge pull request #15 from jwetzell/dependabot/go_modules/github.com/urfave/cli/v3-3.7.0
Bump github.com/urfave/cli/v3 from 3.6.2 to 3.7.0
2026-03-03 10:16:30 -06:00
Joel Wetzell 9bc51c787f add error checking to readstring method 2026-03-02 21:12:48 -06:00
dependabot[bot] 37cc551793 Bump github.com/urfave/cli/v3 from 3.6.2 to 3.7.0
Bumps [github.com/urfave/cli/v3](https://github.com/urfave/cli) from 3.6.2 to 3.7.0.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v3.6.2...v3.7.0)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli/v3
  dependency-version: 3.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-02 02:56:24 +00:00
Joel Wetzell 67a1cc083b remove fmt.println 2026-02-04 11:36:16 -06:00
Joel Wetzell 25c159013b fix variable name 2026-01-31 15:52:42 -06:00
Joel Wetzell 5ac60b0685 add support for SLIP and bundles 2026-01-31 15:52:08 -06:00
Joel Wetzell 6251acb852 use switch 2026-01-31 15:49:42 -06:00
Joel Wetzell 5600ef75f5 add method to parse bytes as packet 2026-01-31 15:49:07 -06:00
Joel Wetzell 77c92c5456 Merge pull request #14 from jwetzell/pointers
return pointers from byte parsing
2026-01-31 15:39:51 -06:00
Joel Wetzell 594c51d2a4 return pointers from byte parsing 2026-01-31 15:34:16 -06:00
Joel Wetzell d7c6da5c5b support OSC 1.0 TCP format if SLIP is not enabled 2026-01-28 14:37:23 -06:00
Joel Wetzell 6f3a65322b rework send string 2026-01-28 14:36:59 -06:00
Joel Wetzell e34a589c5f prevent parsing empty byte slice 2026-01-28 14:34:52 -06:00
Joel Wetzell 41112d583b Merge pull request #13 from jwetzell/dependabot/go_modules/github.com/urfave/cli/v3-3.6.2
Bump github.com/urfave/cli/v3 from 3.6.1 to 3.6.2
2026-01-28 14:26:26 -06:00
dependabot[bot] 24a0501a4d Bump github.com/urfave/cli/v3 from 3.6.1 to 3.6.2
Bumps [github.com/urfave/cli/v3](https://github.com/urfave/cli) from 3.6.1 to 3.6.2.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v3.6.1...v3.6.2)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli/v3
  dependency-version: 3.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 03:08:19 +00:00
Joel Wetzell e06a0a2776 fix slip encoding 2025-12-24 18:46:39 -06:00
Joel Wetzell d1696baa31 Merge pull request #12 from jwetzell/dependabot/github_actions/actions/checkout-6
Bump actions/checkout from 5 to 6
2025-12-11 15:19:19 -06:00
dependabot[bot] c0304bf7a6 Bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 02:31:39 +00:00
Joel Wetzell d62d65c386 Merge pull request #10 from jwetzell/dependabot/go_modules/github.com/urfave/cli/v3-3.6.1
Bump github.com/urfave/cli/v3 from 3.6.0 to 3.6.1
2025-11-20 09:32:02 -06:00
dependabot[bot] f2888cc038 Bump github.com/urfave/cli/v3 from 3.6.0 to 3.6.1
Bumps [github.com/urfave/cli/v3](https://github.com/urfave/cli) from 3.6.0 to 3.6.1.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v3.6.0...v3.6.1)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli/v3
  dependency-version: 3.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-17 02:22:46 +00:00
jwetzell 24077a77d6 Merge pull request #9 from jwetzell/dependabot/go_modules/github.com/urfave/cli/v3-3.6.0
Bump github.com/urfave/cli/v3 from 3.4.1 to 3.6.0
2025-11-14 14:36:32 -06:00
dependabot[bot] 9e515cc28e Bump github.com/urfave/cli/v3 from 3.4.1 to 3.6.0
Bumps [github.com/urfave/cli/v3](https://github.com/urfave/cli) from 3.4.1 to 3.6.0.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v3.4.1...v3.6.0)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli/v3
  dependency-version: 3.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-10 02:27:40 +00:00
jwetzell 1ad942e489 fix cmd name and description 2025-10-01 13:07:00 -05:00
jwetzell e748adfa1c Merge pull request #7 from jwetzell/json-support
add json output to receiveosc
2025-10-01 13:03:26 -05:00
jwetzell 74a62f6287 add support for json output to receiveosc 2025-09-30 12:54:38 -05:00
jwetzell a1a550b4bb add json struct tags 2025-09-30 12:54:13 -05:00
jwetzell c039245298 update names of workflows 2025-09-27 10:28:58 -05:00
jwetzell 085bd082b4 Merge pull request #4 from jwetzell/dependabot/github_actions/actions/checkout-5
Bump actions/checkout from 4 to 5
2025-09-27 10:27:30 -05:00
jwetzell 0a4eff30b3 update go version 2025-09-27 10:22:45 -05:00
jwetzell 6482158fd6 Merge pull request #6 from jwetzell/urfave-cli
convert from cobra to urfave/cli
2025-09-27 10:20:21 -05:00
jwetzell 6808c36e88 remove cobra 2025-09-27 10:19:46 -05:00
jwetzell 257465d216 convert receiveosc to urfave/cli 2025-09-27 10:19:40 -05:00
jwetzell ce5e65e8f8 switch makeosc to urfave/cli 2025-09-27 09:55:09 -05:00
jwetzell e519f2d53c switch sendosc to urfave/cli 2025-09-27 09:54:58 -05:00
dependabot[bot] 048eb57d5a Bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-18 03:36:57 +00:00
jwetzell 039d4e6f1a explicitly discard opening END when SLIP decoding 2025-05-19 09:23:57 -05:00
jwetzell 87853b8ccb SLIP encoding should be double END-ed 2025-05-19 09:23:06 -05:00
jwetzell d03f032e72 SLIP encoding should be double END-ed 2025-05-19 09:22:39 -05:00
jwetzell 2f4b90fc16 fix name of cmd in makeosc 2025-05-19 09:08:24 -05:00
jwetzell 278435e066 fix name of utility 2025-05-19 09:03:05 -05:00
jwetzell b50dc9969f add github actions to dependabot 2025-05-18 20:13:33 -05:00
jwetzell 30ffe3a965 Merge pull request #3 from jwetzell/dependabot/go_modules/github.com/spf13/cobra-1.9.1
Bump github.com/spf13/cobra from 1.8.1 to 1.9.1
2025-05-18 20:11:59 -05:00
dependabot[bot] 06e8ca7da3 Bump github.com/spf13/cobra from 1.8.1 to 1.9.1
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.8.1 to 1.9.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.8.1...v1.9.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-version: 1.9.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-19 00:33:19 +00:00
19 changed files with 1402 additions and 380 deletions
+4
View File
@@ -1,5 +1,9 @@
version: 2
updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'
- package-ecosystem: gomod
directory: /
schedule:
+3 -3
View File
@@ -1,4 +1,4 @@
name: "release go binaries for multiple os/arch"
name: "release go binaries for makeosc"
on:
push:
@@ -36,13 +36,13 @@ jobs:
- goarch: "386"
goos: darwin
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: wangyoucao577/go-release-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
goversion: "1.23.1"
goversion: "1.25.1"
project_path: "./cmd/makeosc"
binary_name: "makeosc"
asset_name: makeosc-${{ matrix.goos }}-${{ matrix.goarch }}
+3 -3
View File
@@ -1,4 +1,4 @@
name: "release go binaries for multiple os/arch"
name: "release go binaries for receiveosc"
on:
push:
@@ -36,13 +36,13 @@ jobs:
- goarch: "386"
goos: darwin
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: wangyoucao577/go-release-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
goversion: "1.23.1"
goversion: "1.25.1"
project_path: "./cmd/receiveosc"
binary_name: "receiveosc"
asset_name: receiveosc-${{ matrix.goos }}-${{ matrix.goarch }}
+3 -3
View File
@@ -1,4 +1,4 @@
name: "release go binaries for multiple os/arch"
name: "release go binaries for sendosc"
on:
push:
@@ -36,13 +36,13 @@ jobs:
- goarch: "386"
goos: darwin
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: wangyoucao577/go-release-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }}
goversion: "1.23.1"
goversion: "1.25.1"
project_path: "./cmd/sendosc"
binary_name: "sendosc"
asset_name: sendosc-${{ matrix.goos }}-${{ matrix.goarch }}
+24
View File
@@ -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@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage.txt
+2 -1
View File
@@ -1 +1,2 @@
build
build
coverage*
+6 -4
View File
@@ -1,5 +1,7 @@
A collection of command line OSC utilities written in Go. Mainly an exercise in learning Go.
A mostly complete OSC implementation and collection of command line OSC utilities written in Go. Mainly an exercise in learning Go.
# `sendosc`
# `makeosc`
# `receiveosc`
## Utilities
### `sendosc`
### `makeosc`
### `receiveosc`
+35 -20
View File
@@ -4,42 +4,57 @@ import (
"errors"
)
func (b *OSCBundle) ToBytes() []byte {
func (b *OSCBundle) ToBytes() ([]byte, error) {
bytes := stringToOSCBytes("#bundle")
bytes = append(bytes, timeTagToOSCBytes(b.TimeTag)...)
timeTagBytes, err := timeTagToOSCBytes(b.TimeTag)
if err != nil {
return nil, err
}
bytes = append(bytes, timeTagBytes...)
for _, packet := range b.Contents {
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, err := int32ToOSCBytes(int32(packetLength))
if err != nil {
return nil, err
}
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 +67,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 +81,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 +100,7 @@ func BundleFromBytes(bytes []byte) (OSCBundle, []byte, error) {
}
return OSCBundle{
return &OSCBundle{
TimeTag: timeTag,
Contents: bundleContents,
},
+170 -47
View File
@@ -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)
}
})
}
}
+43 -14
View File
@@ -1,13 +1,14 @@
package main
import (
"context"
"encoding/hex"
"fmt"
"os"
"strconv"
osc "github.com/jwetzell/osc-go"
"github.com/spf13/cobra"
"github.com/urfave/cli/v3"
)
func main() {
@@ -16,18 +17,43 @@ func main() {
var Types []string
var Slip bool
var rootCmd = &cobra.Command{
Use: "sendosc",
Run: func(cmd *cobra.Command, args []string) {
cmd := &cli.Command{
Name: "makeosc",
Usage: "make osc bytes",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "address",
Value: "",
Usage: "OSC address",
Destination: &Address,
Required: true,
},
&cli.StringSliceFlag{
Name: "arg",
Usage: "OSC args",
Destination: &Args,
},
&cli.StringSliceFlag{
Name: "type",
Usage: "OSC types",
Destination: &Types,
},
&cli.BoolFlag{
Name: "slip",
Value: false,
Usage: "whether to slip encode the OSC Message bytes",
Destination: &Slip,
},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
make(Address, Args, Types, Slip)
return nil
},
}
rootCmd.Flags().StringVar(&Address, "address", "", "OSC address")
rootCmd.Flags().StringArrayVar(&Args, "arg", []string{}, "OSC args")
rootCmd.Flags().StringArrayVar(&Types, "type", []string{}, "OSC types")
rootCmd.Flags().BoolVar(&Slip, "slip", false, "whether to slip encode the OSC Message bytes")
rootCmd.MarkFlagRequired("address")
rootCmd.Execute()
if err := cmd.Run(context.Background(), os.Args); err != nil {
panic(err)
}
}
func argToTypedArg(rawArg string, oscType string) osc.OSCArg {
@@ -117,13 +143,13 @@ func slipEncode(bytes []byte) []byte {
ESC_END := byte(0xdc)
ESC_ESC := byte(0xdd)
var encodedBytes = []byte{}
var encodedBytes = []byte{END}
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)
}
@@ -149,7 +175,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)
+119 -37
View File
@@ -1,36 +1,91 @@
package main
import (
"context"
"encoding/json"
"fmt"
"net"
"os"
osc "github.com/jwetzell/osc-go"
"github.com/spf13/cobra"
"github.com/urfave/cli/v3"
)
func main() {
var Host string
var Port string
var IP string
var Port int32
var Protocol string
var Format string
var Slip bool
var rootCmd = &cobra.Command{
Use: "sendosc",
Run: func(cmd *cobra.Command, args []string) {
netAddress := Host + ":" + Port
if Protocol == "udp" {
listenUDP(netAddress)
} else if Protocol == "tcp" {
listenTCP(netAddress)
cmd := &cli.Command{
Name: "receiveosc",
Usage: "receive OSC messages via UDP or TCP",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "ip",
Usage: "ip to receive OSC messages on",
Value: "0.0.0.0",
Destination: &IP,
},
&cli.Int32Flag{
Name: "port",
Usage: "port to receive OSC messages on",
Destination: &Port,
Value: 8888,
},
&cli.StringFlag{
Name: "protocol",
Usage: "protocol to use to receive (tcp or udp)",
Value: "udp",
Destination: &Protocol,
Validator: func(flag string) error {
if flag != "udp" && flag != "tcp" {
return fmt.Errorf("protocol must be either 'udp' or 'tcp'")
}
return nil
},
},
&cli.StringFlag{
Name: "format",
Usage: "format for messages to be output in ('json')",
Value: "json",
Destination: &Format,
Validator: func(flag string) error {
if flag != "json" {
return fmt.Errorf("format must be 'json'")
}
return nil
},
},
&cli.BoolFlag{
Name: "slip",
Value: false,
Usage: "whether to slip encode the OSC Message bytes",
Destination: &Slip,
},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
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
},
}
rootCmd.Flags().StringVar(&Host, "host", "127.0.0.1", "host to send OSC message to")
rootCmd.Flags().StringVar(&Port, "port", "8888", "port to send OSC message to")
rootCmd.Flags().StringVar(&Protocol, "protocol", "udp", "protocol to use to send (tcp or udp)")
rootCmd.Execute()
if err := cmd.Run(context.Background(), os.Args); err != nil {
panic(err)
}
}
func listenTCP(netAddress string) {
func listenTCP(netAddress string, useSLIP bool, format string) {
socket, err := net.Listen("tcp4", netAddress)
if err != nil {
fmt.Println(err)
@@ -39,21 +94,19 @@ func listenTCP(netAddress string) {
defer socket.Close()
fmt.Printf("listening on %s (tcp w/ SLIP)\n", netAddress)
for {
conn, err := socket.Accept()
if err != nil {
fmt.Println(err)
continue
}
go handleConnection(conn)
go handleTCPConnection(conn, useSLIP, format)
}
}
type SLIP struct {
pendingBytes []byte
Messages chan osc.OSCMessage
Packets chan osc.OSCPacket
}
func (s *SLIP) decode(bytes []byte) {
@@ -78,12 +131,15 @@ func (s *SLIP) decode(bytes []byte) {
}
escapeNext = false
} else if packetByte == END {
if len(s.pendingBytes) > 0 {
message, err := osc.MessageFromBytes(s.pendingBytes)
if len(s.pendingBytes) == 0 {
// probably opening END byte, can discard
continue
} else {
oscPacket, _, err := osc.PacketFromBytes(s.pendingBytes)
if err != nil {
fmt.Println(err)
panic(err)
} else {
s.Messages <- message
s.Packets <- oscPacket
}
}
s.pendingBytes = []byte{}
@@ -91,21 +147,20 @@ func (s *SLIP) decode(bytes []byte) {
s.pendingBytes = append(s.pendingBytes, packetByte)
}
}
}
func handleMessages(slip SLIP) {
for message := range slip.Messages {
fmt.Printf("%v\n", message)
func handleSLIP(slip SLIP, format string) {
for message := range slip.Packets {
handlePacket(message, format)
}
}
func handleConnection(conn net.Conn) {
func handleTCPConnection(conn net.Conn, useSLIP bool, format string) {
slip := SLIP{
pendingBytes: []byte{},
Messages: make(chan osc.OSCMessage),
Packets: make(chan osc.OSCPacket),
}
go handleMessages(slip)
go handleSLIP(slip, format)
defer conn.Close()
buffer := make([]byte, 1024)
@@ -116,12 +171,41 @@ func handleConnection(conn net.Conn) {
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 listenUDP(netAddress 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))
} else {
fmt.Printf("%v\n", message)
}
}
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)
if err != nil {
@@ -135,8 +219,6 @@ func listenUDP(netAddress string) {
return
}
fmt.Printf("listening on %s (udp)\n", netAddress)
defer connection.Close()
buffer := make([]byte, 1024)
@@ -147,11 +229,11 @@ func listenUDP(netAddress string) {
panic(err)
}
oscMessage, err := osc.MessageFromBytes(buffer[0:bytesRead])
oscPacket, _, err := osc.PacketFromBytes(buffer[0:bytesRead])
if err != nil {
panic(err)
}
fmt.Println(oscMessage)
handlePacket(oscPacket, format)
}
}
+76 -19
View File
@@ -1,14 +1,17 @@
package main
import (
"context"
"encoding/binary"
"encoding/hex"
"fmt"
"net"
"os"
"strconv"
osc "github.com/jwetzell/osc-go"
"github.com/spf13/cobra"
"github.com/urfave/cli/v3"
)
func main() {
@@ -20,23 +23,68 @@ func main() {
var Types []string
var Slip bool
var rootCmd = &cobra.Command{
Use: "sendosc",
Run: func(cmd *cobra.Command, args []string) {
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,
},
&cli.Int32Flag{
Name: "port",
Usage: "port to send OSC message to",
Destination: &Port,
Required: true,
},
&cli.StringFlag{
Name: "protocol",
Usage: "protocol to use to send (tcp or udp)",
Value: "udp",
Destination: &Protocol,
Validator: func(flag string) error {
if flag != "udp" && flag != "tcp" {
return fmt.Errorf("protocol must be either 'udp' or 'tcp'")
}
return nil
},
},
&cli.StringFlag{
Name: "address",
Usage: "OSC address",
Destination: &Address,
Required: true,
},
&cli.StringSliceFlag{
Name: "arg",
Usage: "OSC args",
Value: []string{},
Destination: &Args,
},
&cli.StringSliceFlag{
Name: "type",
Usage: "OSC types",
Value: []string{},
Destination: &Types,
},
&cli.BoolFlag{
Name: "slip",
Value: false,
Usage: "whether to slip encode the OSC Message bytes",
Destination: &Slip,
},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
send(Host, Port, Address, Args, Types, Protocol, Slip)
return nil
},
}
rootCmd.Flags().StringVar(&Host, "host", "", "host to send OSC message to")
rootCmd.Flags().Int32Var(&Port, "port", 9999, "port to send OSC message to")
rootCmd.Flags().StringVar(&Address, "address", "", "OSC address")
rootCmd.Flags().StringVar(&Protocol, "protocol", "udp", "protocol to use to send (tcp or udp)")
rootCmd.Flags().StringArrayVar(&Args, "arg", []string{}, "OSC args")
rootCmd.Flags().StringArrayVar(&Types, "type", []string{}, "OSC types")
rootCmd.Flags().BoolVar(&Slip, "slip", false, "whether to slip encode the OSC Message bytes")
rootCmd.MarkFlagRequired("host")
rootCmd.MarkFlagRequired("port")
rootCmd.MarkFlagRequired("address")
rootCmd.Execute()
if err := cmd.Run(context.Background(), os.Args); err != nil {
panic(err)
}
}
func argToTypedArg(rawArg string, oscType string) osc.OSCArg {
@@ -126,13 +174,13 @@ func slipEncode(bytes []byte) []byte {
ESC_END := byte(0xdc)
ESC_ESC := byte(0xdd)
var encodedBytes = []byte{}
var encodedBytes = []byte{END}
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)
}
@@ -160,10 +208,19 @@ 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)
+2 -7
View File
@@ -1,10 +1,5 @@
module github.com/jwetzell/osc-go
go 1.23.1
go 1.25.1
require github.com/spf13/cobra v1.8.1
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
)
require github.com/urfave/cli/v3 v3.8.0
+9 -9
View File
@@ -1,10 +1,10 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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.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
View File
@@ -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
View File
@@ -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)
}
})
}
}
+202 -85
View File
@@ -26,47 +26,51 @@ func stringToOSCBytes(rawString string) []byte {
return []byte(sb.String())
}
func int32ToOSCBytes(number int32) []byte {
func int32ToOSCBytes(number int32) ([]byte, error) {
var buf bytes.Buffer
err := binary.Write(&buf, binary.BigEndian, number)
if err != nil {
panic(err)
return nil, err
}
return buf.Bytes()
return buf.Bytes(), nil
}
func int64ToOSCBytes(number int64) []byte {
func int64ToOSCBytes(number int64) ([]byte, error) {
var buf bytes.Buffer
err := binary.Write(&buf, binary.BigEndian, number)
if err != nil {
panic(err)
return nil, err
}
return buf.Bytes()
return buf.Bytes(), nil
}
func float32ToOSCBytes(number float32) []byte {
func float32ToOSCBytes(number float32) ([]byte, error) {
var buf bytes.Buffer
err := binary.Write(&buf, binary.BigEndian, number)
if err != nil {
panic(err)
return nil, err
}
return buf.Bytes()
return buf.Bytes(), nil
}
func float64ToOSCBytes(number float64) []byte {
func float64ToOSCBytes(number float64) ([]byte, error) {
var buf bytes.Buffer
err := binary.Write(&buf, binary.BigEndian, number)
if err != nil {
panic(err)
return nil, err
}
return buf.Bytes()
return buf.Bytes(), nil
}
func byteArrayToOSCBytes(bytes []byte) []byte {
func byteArrayToOSCBytes(bytes []byte) ([]byte, error) {
oscBytes := []byte{}
bytesSize := len(bytes)
oscBytes = append(oscBytes, int32ToOSCBytes(int32(bytesSize))...)
bytesSizeBytes, err := int32ToOSCBytes(int32(bytesSize))
if err != nil {
return nil, err
}
oscBytes = append(oscBytes, bytesSizeBytes...)
oscBytes = append(oscBytes, bytes...)
padLength := 4 - (bytesSize % 4)
@@ -76,17 +80,24 @@ func byteArrayToOSCBytes(bytes []byte) []byte {
}
}
return oscBytes
return oscBytes, nil
}
func timeTagToOSCBytes(timeTag OSCTimeTag) []byte {
timeTagBytes := int32ToOSCBytes(timeTag.seconds)
timeTagBytes = append(timeTagBytes, int32ToOSCBytes(timeTag.fractionalSeconds)...)
func timeTagToOSCBytes(timeTag OSCTimeTag) ([]byte, error) {
timeTagBytes, err := int32ToOSCBytes(timeTag.seconds)
if err != nil {
return nil, err
}
fractionalSecondsBytes, err := int32ToOSCBytes(timeTag.fractionalSeconds)
if err != nil {
return nil, err
}
timeTagBytes = append(timeTagBytes, fractionalSecondsBytes...)
return timeTagBytes
return timeTagBytes, nil
}
func argsToBuffer(args []OSCArg) []byte {
func argsToBuffer(args []OSCArg) ([]byte, error) {
//TODO(jwetzell): add error handling
var argBuffers = []byte{}
@@ -96,35 +107,67 @@ func argsToBuffer(args []OSCArg) []byte {
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, err := int32ToOSCBytes(int32(value))
if err != nil {
return nil, err
}
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int32); ok {
argBuffers = append(argBuffers, int32ToOSCBytes(value)...)
valueBytes, err := int32ToOSCBytes(int32(value))
if err != nil {
return nil, err
}
argBuffers = append(argBuffers, valueBytes...)
} else {
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, err := float32ToOSCBytes(value)
if err != nil {
return nil, err
}
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(float64); ok {
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
valueBytes, err := float32ToOSCBytes(float32(value))
if err != nil {
return nil, err
}
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int); ok {
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
valueBytes, err := float32ToOSCBytes(float32(value))
if err != nil {
return nil, err
}
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int32); ok {
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
valueBytes, err := float32ToOSCBytes(float32(value))
if err != nil {
return nil, err
}
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int64); ok {
argBuffers = append(argBuffers, float32ToOSCBytes(float32(value))...)
valueBytes, err := float32ToOSCBytes(float32(value))
if err != nil {
return nil, err
}
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, err := byteArrayToOSCBytes(value)
if err != nil {
return nil, err
}
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 +179,112 @@ 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, err := int64ToOSCBytes(int64(value))
if err != nil {
return nil, err
}
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int32); ok {
argBuffers = append(argBuffers, int64ToOSCBytes(int64(value))...)
valueBytes, err := int64ToOSCBytes(int64(value))
if err != nil {
return nil, err
}
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int64); ok {
argBuffers = append(argBuffers, int64ToOSCBytes(value)...)
valueBytes, err := int64ToOSCBytes(value)
if err != nil {
return nil, err
}
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, err := float64ToOSCBytes(float64(value))
if err != nil {
return nil, err
}
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(float64); ok {
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
valueBytes, err := float64ToOSCBytes(value)
if err != nil {
return nil, err
}
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int); ok {
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
valueBytes, err := float64ToOSCBytes(float64(value))
if err != nil {
return nil, err
}
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int32); ok {
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
valueBytes, err := float64ToOSCBytes(float64(value))
if err != nil {
return nil, err
}
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int64); ok {
argBuffers = append(argBuffers, float64ToOSCBytes(float64(value))...)
valueBytes, err := float64ToOSCBytes(float64(value))
if err != nil {
return nil, err
}
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", oscType)
}
}
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 +292,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,7 +300,7 @@ 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
@@ -225,7 +308,7 @@ func readOSCFloat32(bytes []byte) (float32, []byte, error) {
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")
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 +318,11 @@ 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 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)
@@ -253,7 +336,7 @@ func readOSCBlob(bytes []byte) ([]byte, []byte, error) {
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 +346,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 +375,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 +415,59 @@ 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)
readArgError = errors.New("unsupported OSC argument type: " + 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
View File
@@ -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)
}
})
}
}
+7 -7
View File
@@ -1,22 +1,22 @@
package osc
type OSCPacket interface {
ToBytes() []byte
ToBytes() ([]byte, error)
}
type OSCBundle struct {
Contents []OSCPacket
TimeTag OSCTimeTag
Contents []OSCPacket `json:"contents"`
TimeTag OSCTimeTag `json:"timeTag"`
}
type OSCArg struct {
Value any
Type string
Value any `json:"value"`
Type string `json:"type"`
}
type OSCMessage struct {
Address string
Args []OSCArg
Address string `json:"address"`
Args []OSCArg `json:"args"`
}
type OSCColor struct {