Files
Joel Wetzell f7850ce70e Merge pull request #24 from jwetzell/fix/float-64-arg-size-check
fix size check on readOSCFloat64
2026-05-20 17:39:59 -05:00

420 lines
11 KiB
Go

package osc
// TODO(jwetzell): split things up
import (
"encoding/binary"
"errors"
"fmt"
"math"
"strings"
)
func stringToOSCBytes(rawString string) []byte {
var sb strings.Builder
sb.WriteString(rawString)
sb.WriteString("\u0000")
padLength := 4 - (len(sb.String()) % 4)
if padLength < 4 {
for range padLength {
sb.WriteString("\u0000")
}
}
return []byte(sb.String())
}
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 {
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 {
bytes := make([]byte, 4)
binary.BigEndian.PutUint32(bytes, math.Float32bits(number))
return bytes
}
func float64ToOSCBytes(number float64) []byte {
bytes := make([]byte, 8)
binary.BigEndian.PutUint64(bytes, math.Float64bits(number))
return bytes
}
func byteArrayToOSCBytes(bytes []byte) []byte {
oscBytes := []byte{}
bytesSize := len(bytes)
bytesSizeBytes := int32ToOSCBytes(int32(bytesSize))
oscBytes = append(oscBytes, bytesSizeBytes...)
oscBytes = append(oscBytes, bytes...)
padLength := 4 - (bytesSize % 4)
if padLength < 4 {
for range padLength {
oscBytes = append(oscBytes, 0)
}
}
return oscBytes
}
func timeTagToOSCBytes(timeTag OSCTimeTag) []byte {
timeTagBytes := int32ToOSCBytes(timeTag.seconds)
fractionalSecondsBytes := int32ToOSCBytes(timeTag.fractionalSeconds)
timeTagBytes = append(timeTagBytes, fractionalSecondsBytes...)
return timeTagBytes
}
func argsToBuffer(args []OSCArg) ([]byte, error) {
//TODO(jwetzell): add error handling
var argBuffers = []byte{}
for _, arg := range args {
switch arg.Type {
case "s":
if value, ok := arg.Value.(string); ok {
argBuffers = append(argBuffers, stringToOSCBytes(value)...)
} else {
return nil, errors.New("OSC arg had string type but non-string value")
}
case "i":
if value, ok := arg.Value.(int); ok {
valueBytes := int32ToOSCBytes(int32(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int32); ok {
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 := float32ToOSCBytes(value)
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(float64); ok {
valueBytes := float32ToOSCBytes(float32(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int); ok {
valueBytes := float32ToOSCBytes(float32(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int32); ok {
valueBytes := float32ToOSCBytes(float32(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int64); ok {
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 := byteArrayToOSCBytes(value)
argBuffers = append(argBuffers, valueBytes...)
} else {
return nil, errors.New("OSC arg had blob type but non-blob value")
}
case "T":
argBuffers = append(argBuffers, make([]byte, 0)...)
case "F":
argBuffers = append(argBuffers, make([]byte, 0)...)
case "N":
argBuffers = append(argBuffers, make([]byte, 0)...)
case "I":
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 {
valueBytes := int64ToOSCBytes(int64(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int32); ok {
valueBytes := int64ToOSCBytes(int64(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int64); ok {
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 := float64ToOSCBytes(float64(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(float64); ok {
valueBytes := float64ToOSCBytes(value)
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int); ok {
valueBytes := float64ToOSCBytes(float64(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int32); ok {
valueBytes := float64ToOSCBytes(float64(value))
argBuffers = append(argBuffers, valueBytes...)
} else if value, ok := arg.Value.(int64); ok {
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", arg.Type)
}
}
return argBuffers, nil
}
func readOSCString(bytes []byte) (string, []byte, error) {
oscString := ""
stringEndIndex := 0
nullByteFound := false
for index, byteIn := range bytes {
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
}
if stringEndIndex > len(bytes) {
return "", bytes, errors.New("OSC string is not properly padded")
}
remainingBytes := bytes[stringEndIndex:]
return oscString, remainingBytes, nil
}
func readOSCInt32(bytes []byte) (int32, []byte, error) {
if len(bytes) < 4 {
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
}
func readOSCInt64(bytes []byte) (int64, []byte, error) {
if len(bytes) < 8 {
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
}
func readOSCFloat32(bytes []byte) (float32, []byte, error) {
if len(bytes) < 4 {
return 0, bytes, errors.New("OSC float32 arg is not 4 bytes")
}
bits := binary.BigEndian.Uint32(bytes[0:4])
return math.Float32frombits(bits), bytes[4:], nil
}
func readOSCFloat64(bytes []byte) (float64, []byte, error) {
if len(bytes) < 8 {
return 0, bytes, errors.New("OSC float64 arg is not 8 bytes")
}
bits := binary.BigEndian.Uint64(bytes[0:8])
return math.Float64frombits(bits), bytes[8:], nil
}
func readOSCBlob(bytes []byte) ([]byte, []byte, error) {
blobLength, remainingBytes, err := readOSCInt32(bytes)
if err != nil {
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")
}
blobLengthPadding := 4 - (blobLength % 4)
blobEnd := 4 + blobLength
if blobLengthPadding < 4 {
blobEnd = blobEnd + blobLengthPadding
}
if int(blobEnd) > len(bytes) {
return []byte{}, bytes, errors.New("OSC blob arg size not valid: size specified is larger than remaining bytes when accounting for padding")
}
return bytes[4 : 4+blobLength], bytes[blobEnd:], nil
}
func readOSCColor(bytes []byte) (OSCColor, []byte, error) {
if len(bytes) < 4 {
return OSCColor{0, 0, 0, 0}, bytes, errors.New("OSC color arg is not 4 bytes")
}
oscColor := OSCColor{
r: bytes[0],
g: bytes[1],
b: bytes[2],
a: bytes[3],
}
return oscColor, bytes[4:], nil
}
func readOSCTimeTag(bytes []byte) (OSCTimeTag, []byte, error) {
seconds, bytesAfterSeconds, err := readOSCInt32(bytes)
if err != nil {
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, fmt.Errorf("OSC time tag fractional seconds are not valid: %s", err)
}
return OSCTimeTag{
seconds: seconds,
fractionalSeconds: fractionalSeconds,
},
remainingBytes,
nil
}
func readOSCArg(bytes []byte, oscType string) (OSCArg, []byte, error) {
var readArgError error
oscArg := OSCArg{}
oscArg.Type = oscType
var remainingBytes []byte
//TODO(jwetzell): add error handling
switch oscType {
case "s":
argString, bytesLeft, err := readOSCString(bytes)
if err != nil {
return OSCArg{}, bytes, err
}
oscArg.Value = argString
remainingBytes = bytesLeft
case "i":
argInt, bytesLeft, err := readOSCInt32(bytes)
if err != nil {
readArgError = err
}
oscArg.Value = argInt
remainingBytes = bytesLeft
case "f":
argFloat, bytesLeft, err := readOSCFloat32(bytes)
if err != nil {
readArgError = err
}
oscArg.Value = argFloat
remainingBytes = bytesLeft
case "b":
argBytes, bytesLeft, err := readOSCBlob(bytes)
if err != nil {
readArgError = err
}
oscArg.Value = argBytes
remainingBytes = bytesLeft
case "T":
oscArg.Value = true
remainingBytes = bytes
case "F":
oscArg.Value = false
remainingBytes = bytes
case "N":
oscArg.Value = nil
remainingBytes = bytes
case "I":
oscArg.Value = math.MaxInt32
remainingBytes = bytes
case "r":
argColor, bytesLeft, err := readOSCColor(bytes)
if err != nil {
readArgError = err
}
oscArg.Value = argColor
remainingBytes = bytesLeft
case "h":
argInt, bytesLeft, err := readOSCInt64(bytes)
if err != nil {
readArgError = err
}
oscArg.Value = argInt
remainingBytes = bytesLeft
case "d":
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:
return OSCArg{}, bytes, fmt.Errorf("unsupported OSC argument type: %s", oscType)
}
return oscArg, remainingBytes, readArgError
}
func PacketFromBytes(bytes []byte) (OSCPacket, []byte, error) {
if len(bytes) == 0 {
return nil, bytes, errors.New("cannot create OSC Packet from empty byte array")
}
switch bytes[0] {
case '#':
bundle, remainingBytes, err := BundleFromBytes(bytes)
if err != nil {
return nil, bytes, err
}
return bundle, remainingBytes, nil
case '/':
message, err := MessageFromBytes(bytes)
if err != nil {
return nil, bytes, err
}
return message, []byte{}, nil
default:
return nil, bytes, errors.New("OSC Packet must start with # for bundle or / for message")
}
}