Files
VRCWT-OSC/internal/osc/server.go
2026-06-25 02:36:55 +09:00

142 lines
2.8 KiB
Go

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
}