Compare commits

...

11 Commits

Author SHA1 Message Date
Joel Wetzell 8ed1ab065c fix size check on readOSCFloat64 2026-05-20 08:38:26 -05:00
Joel Wetzell d6d4ac4419 stop using destination 2026-05-09 14:06:33 -05:00
Joel Wetzell a86a9b3121 chore: go fix 2026-05-09 13:44:28 -05:00
Joel Wetzell 216f84a2d9 Merge pull request #22 from jwetzell/error-handling-cleanup
clean out error return from funcs that don't return error
2026-04-29 13:56:47 -05:00
Joel Wetzell bd6a548e34 Add Codecov badge to README 2026-04-29 13:56:32 -05:00
Joel Wetzell d8dff0794a clean out error return from funcs that don't return error
Co-authored-by: Copilot <copilot@github.com>
2026-04-29 13:46:43 -05:00
Joel Wetzell 0c4d36b376 cleanup printing 2026-04-29 13:41:50 -05:00
Joel Wetzell a5ac419e3e return on unsupported arg 2026-04-29 13:41:42 -05:00
Joel Wetzell 3b66c38d18 Merge pull request #21 from jwetzell/dependabot/github_actions/codecov/codecov-action-6
Bump codecov/codecov-action from 5 to 6
2026-04-27 08:35:42 -05:00
Joel Wetzell 4d8ba2a47b get rid of bytes.Buffer use
Co-authored-by: Copilot <copilot@github.com>
2026-04-26 13:37:48 -05:00
dependabot[bot] 0caf1b02a7 Bump codecov/codecov-action from 5 to 6
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5 to 6.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-20 02:55:47 +00:00
7 changed files with 142 additions and 217 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
run: go test -v -coverprofile=coverage.txt .
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage.txt
+2
View File
@@ -1,3 +1,5 @@
[![codecov](https://codecov.io/gh/jwetzell/osc-go/branch/main/graph/badge.svg?token=DW3CCZELGI)](https://codecov.io/gh/jwetzell/osc-go)
A mostly complete OSC implementation and collection of command line OSC utilities written in Go. Mainly an exercise in learning Go.
## Utilities
+2 -8
View File
@@ -8,10 +8,7 @@ func (b *OSCBundle) ToBytes() ([]byte, error) {
bytes := stringToOSCBytes("#bundle")
timeTagBytes, err := timeTagToOSCBytes(b.TimeTag)
if err != nil {
return nil, err
}
timeTagBytes := timeTagToOSCBytes(b.TimeTag)
bytes = append(bytes, timeTagBytes...)
for _, packet := range b.Contents {
@@ -21,10 +18,7 @@ func (b *OSCBundle) ToBytes() ([]byte, error) {
}
packetLength := len(packetBytes)
packetLengthBytes, err := int32ToOSCBytes(int32(packetLength))
if err != nil {
return nil, err
}
packetLengthBytes := int32ToOSCBytes(int32(packetLength))
bytes = append(bytes, packetLengthBytes...)
bytes = append(bytes, packetBytes...)
}
+18 -23
View File
@@ -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{}
}
@@ -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,
+26 -30
View File
@@ -12,33 +12,25 @@ import (
)
func main() {
var IP string
var Port int32
var Protocol string
var Format string
var Slip bool
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,
Name: "ip",
Usage: "ip to receive OSC messages on",
Value: "0.0.0.0",
},
&cli.Int32Flag{
Name: "port",
Usage: "port to receive OSC messages on",
Destination: &Port,
Value: 8888,
Name: "port",
Usage: "port to receive OSC messages on",
Value: 8888,
},
&cli.StringFlag{
Name: "protocol",
Usage: "protocol to use to receive (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'")
@@ -47,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'")
@@ -59,22 +50,27 @@ func main() {
},
},
&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 {
netAddress := fmt.Sprintf("%s:%d", IP, Port)
switch Protocol {
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)
listenUDP(netAddress, format)
case "tcp":
if !Slip {
if !slip {
return fmt.Errorf("OSC 1.0 over TCP is not supported yet")
}
listenTCP(netAddress, Slip, Format)
listenTCP(netAddress, slip, format)
}
return nil
},
+31 -39
View File
@@ -15,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'")
@@ -52,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
},
}
@@ -161,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{}
}
@@ -223,7 +215,7 @@ func send(host string, port int32, address string, args []string, types []string
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)
+62 -116
View File
@@ -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")
}
}
@@ -26,75 +25,64 @@ func stringToOSCBytes(rawString string) []byte {
return []byte(sb.String())
}
func int32ToOSCBytes(number int32) ([]byte, error) {
var buf bytes.Buffer
err := binary.Write(&buf, binary.BigEndian, number)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
func int32ToOSCBytes(number int32) []byte {
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, error) {
var buf bytes.Buffer
err := binary.Write(&buf, binary.BigEndian, number)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
func int64ToOSCBytes(number int64) []byte {
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, error) {
var buf bytes.Buffer
err := binary.Write(&buf, binary.BigEndian, number)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
func float32ToOSCBytes(number float32) []byte {
bytes := make([]byte, 4)
binary.BigEndian.PutUint32(bytes, math.Float32bits(number))
return bytes
}
func float64ToOSCBytes(number float64) ([]byte, error) {
var buf bytes.Buffer
err := binary.Write(&buf, binary.BigEndian, number)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
func float64ToOSCBytes(number float64) []byte {
bytes := make([]byte, 8)
binary.BigEndian.PutUint64(bytes, math.Float64bits(number))
return bytes
}
func byteArrayToOSCBytes(bytes []byte) ([]byte, error) {
func byteArrayToOSCBytes(bytes []byte) []byte {
oscBytes := []byte{}
bytesSize := len(bytes)
bytesSizeBytes, err := int32ToOSCBytes(int32(bytesSize))
if err != nil {
return nil, err
}
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)
}
}
return oscBytes, nil
return oscBytes
}
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
}
func timeTagToOSCBytes(timeTag OSCTimeTag) []byte {
timeTagBytes := int32ToOSCBytes(timeTag.seconds)
fractionalSecondsBytes := int32ToOSCBytes(timeTag.fractionalSeconds)
timeTagBytes = append(timeTagBytes, fractionalSecondsBytes...)
return timeTagBytes, nil
return timeTagBytes
}
func argsToBuffer(args []OSCArg) ([]byte, error) {
@@ -102,7 +90,7 @@ func argsToBuffer(args []OSCArg) ([]byte, error) {
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)...)
@@ -111,60 +99,36 @@ func argsToBuffer(args []OSCArg) ([]byte, error) {
}
case "i":
if value, ok := arg.Value.(int); ok {
valueBytes, err := int32ToOSCBytes(int32(value))
if err != nil {
return nil, err
}
valueBytes := int32ToOSCBytes(int32(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int32); ok {
valueBytes, err := int32ToOSCBytes(int32(value))
if err != nil {
return nil, err
}
valueBytes := int32ToOSCBytes(int32(value))
argBuffers = append(argBuffers, valueBytes...)
} else {
return nil, errors.New("OSC arg had int32 type but non-number value")
}
case "f":
if value, ok := arg.Value.(float32); ok {
valueBytes, err := float32ToOSCBytes(value)
if err != nil {
return nil, err
}
valueBytes := float32ToOSCBytes(value)
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(float64); ok {
valueBytes, err := float32ToOSCBytes(float32(value))
if err != nil {
return nil, err
}
valueBytes := float32ToOSCBytes(float32(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int); ok {
valueBytes, err := float32ToOSCBytes(float32(value))
if err != nil {
return nil, err
}
valueBytes := float32ToOSCBytes(float32(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int32); ok {
valueBytes, err := float32ToOSCBytes(float32(value))
if err != nil {
return nil, err
}
valueBytes := float32ToOSCBytes(float32(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int64); ok {
valueBytes, err := float32ToOSCBytes(float32(value))
if err != nil {
return nil, err
}
valueBytes := float32ToOSCBytes(float32(value))
argBuffers = append(argBuffers, valueBytes...)
} else {
return nil, errors.New("OSC arg had float32 type but non-number value")
}
case "b":
if value, ok := arg.Value.([]byte); ok {
valueBytes, err := byteArrayToOSCBytes(value)
if err != nil {
return nil, err
}
valueBytes := byteArrayToOSCBytes(value)
argBuffers = append(argBuffers, valueBytes...)
} else {
return nil, errors.New("OSC arg had blob type but non-blob value")
@@ -188,62 +152,38 @@ func argsToBuffer(args []OSCArg) ([]byte, error) {
}
case "h":
if value, ok := arg.Value.(int); ok {
valueBytes, err := int64ToOSCBytes(int64(value))
if err != nil {
return nil, err
}
valueBytes := int64ToOSCBytes(int64(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int32); ok {
valueBytes, err := int64ToOSCBytes(int64(value))
if err != nil {
return nil, err
}
valueBytes := int64ToOSCBytes(int64(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int64); ok {
valueBytes, err := int64ToOSCBytes(value)
if err != nil {
return nil, err
}
valueBytes := int64ToOSCBytes(value)
argBuffers = append(argBuffers, valueBytes...)
} else {
return nil, errors.New("OSC arg had int64 type but non-number value")
}
case "d":
if value, ok := arg.Value.(float32); ok {
valueBytes, err := float64ToOSCBytes(float64(value))
if err != nil {
return nil, err
}
valueBytes := float64ToOSCBytes(float64(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(float64); ok {
valueBytes, err := float64ToOSCBytes(value)
if err != nil {
return nil, err
}
valueBytes := float64ToOSCBytes(value)
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int); ok {
valueBytes, err := float64ToOSCBytes(float64(value))
if err != nil {
return nil, err
}
valueBytes := float64ToOSCBytes(float64(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int32); ok {
valueBytes, err := float64ToOSCBytes(float64(value))
if err != nil {
return nil, err
}
valueBytes := float64ToOSCBytes(float64(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int64); ok {
valueBytes, err := float64ToOSCBytes(float64(value))
if err != nil {
return nil, err
}
valueBytes := float64ToOSCBytes(float64(value))
argBuffers = append(argBuffers, valueBytes...)
} else {
return nil, errors.New("OSC arg had float64 type but non-number value")
}
default:
return nil, fmt.Errorf("unsupported OSC argument type: %s", oscType)
return nil, fmt.Errorf("unsupported OSC argument type: %s", arg.Type)
}
}
return argBuffers, nil
@@ -307,7 +247,7 @@ func readOSCFloat32(bytes []byte) (float32, []byte, error) {
}
func readOSCFloat64(bytes []byte) (float64, []byte, error) {
if len(bytes) < 4 {
if len(bytes) < 8 {
return 0, bytes, errors.New("OSC float64 arg is not 8 bytes")
}
bits := binary.BigEndian.Uint64(bytes[0:8])
@@ -321,6 +261,10 @@ func readOSCBlob(bytes []byte) ([]byte, []byte, error) {
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("OSC blob arg size not valid: size specified is larger than remaining bytes")
}
@@ -331,6 +275,9 @@ 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
}
@@ -443,8 +390,7 @@ func readOSCArg(bytes []byte, oscType string) (OSCArg, []byte, error) {
oscArg.Value = argTimeTag
remainingBytes = bytesLeft
default:
fmt.Printf("unsupported osc type: %s\n", oscType)
readArgError = errors.New("unsupported OSC argument type: " + oscType)
return OSCArg{}, bytes, fmt.Errorf("unsupported OSC argument type: %s", oscType)
}
return oscArg, remainingBytes, readArgError
}