package osc import ( "bytes" "encoding/binary" "fmt" "math" "net" "strings" "sync" ) type Value struct { Type byte Float float32 Int int32 Bool bool Str string } type Handler func(address string, args []Value) error type Server struct { addr string conn *net.UDPConn handlers map[string]Handler mu sync.RWMutex } func NewServer(host string, port int) *Server { return &Server{ addr: fmt.Sprintf("%s:%d", host, port), handlers: map[string]Handler{}, } } func (s *Server) Map(param string, h Handler) { s.handlers["/avatar/parameters/"+param] = h } func (s *Server) Close() error { if s.conn != nil { return s.conn.Close() } return nil } func (s *Server) Serve() error { addr, err := net.ResolveUDPAddr("udp", s.addr) if err != nil { return err } conn, err := net.ListenUDP("udp", addr) if err != nil { return err } s.conn = conn buf := make([]byte, 2048) for { n, _, err := conn.ReadFromUDP(buf) if err != nil { return err } a, v, err := parseMessage(buf[:n]) if err != nil { continue } s.mu.RLock() h := s.handlers[a] s.mu.RUnlock() if h != nil { _ = h(a, v) } } } func parseMessage(b []byte) (string, []Value, error) { address, offset, err := parsePaddedString(b, 0) if err != nil { return "", nil, err } if !strings.HasPrefix(address, "/") { return "", nil, fmt.Errorf("invalid address") } if offset >= len(b) { return address, nil, nil } typetags, offset, err := parsePaddedString(b, offset) if err != nil { return "", nil, err } if !strings.HasPrefix(typetags, ",") { return address, nil, nil } var values []Value for _, tag := range typetags[1:] { switch tag { case 'i': if offset+4 > len(b) { return "", nil, fmt.Errorf("invalid int") } values = append(values, Value{Type: 'i', Int: int32(binary.BigEndian.Uint32(b[offset : offset+4]))}) offset += 4 case 'f': if offset+4 > len(b) { return "", nil, fmt.Errorf("invalid float") } bits := binary.BigEndian.Uint32(b[offset : offset+4]) values = append(values, Value{Type: 'f', Float: math.Float32frombits(bits)}) offset += 4 case 's': s, next, err := parsePaddedString(b, offset) if err != nil { return "", nil, err } values = append(values, Value{Type: 's', Str: s}) offset = next case 'T': values = append(values, Value{Type: 'T', Bool: true}) case 'F': values = append(values, Value{Type: 'F', Bool: false}) } } return address, values, nil } func parsePaddedString(b []byte, offset int) (string, int, error) { if offset >= len(b) { return "", offset, fmt.Errorf("invalid osc string") } end := bytes.IndexByte(b[offset:], 0) if end < 0 { return "", offset, fmt.Errorf("invalid osc string") } s := string(b[offset : offset+end]) next := offset + end + 1 for next%4 != 0 { next++ } if next > len(b) { next = len(b) } return s, next, nil }