├── .gitignore ├── README.md ├── build ├── Linux32 │ └── oscer ├── Linux64 │ └── oscer ├── LinuxARM │ └── oscer ├── MacAmd │ └── oscer ├── MacArm │ └── oscer ├── Windows32 │ └── oscer.exe └── Windows64 │ └── oscer.exe └── src ├── go.mod ├── makefile ├── osc └── osc.go ├── oscer.go └── oscer_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | src/oscer 3 | src/oscer.exe 4 | pkg/ 5 | gomi/ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | oscer 2 | ==== 3 | simple command-line OSC messaging tool 4 | 5 | ## FEATURES 6 | - send/receive OSC packet over UDP 7 | - support int32, float32 and string parameters 8 | - sender supports IPv4 and IPv6 protocol 9 | - receiver supports IPv4 protocol 10 | - cross compiled executable binaries are available (M1/AMD macOS, Windows, Linux, RaspberryPi) 11 | 12 | ## Usage 13 | ``` 14 | oscer host port /osc/address [args ...] 15 | oscer receive port 16 | ``` 17 | 18 | ## Example 19 | ``` 20 | oscer localhost 10000 /hello 21 | oscer fe80::1%lo0 11000 /hello world 22 | oscer 192.168.1.100 12000 /1/push1 1 23 | oscer 192.168.1.101 13000 /accxyz 0.5 0.2 1.0 24 | 25 | oscer receive 10000 26 | ``` 27 | 28 | ## WEBSITE 29 | http://github.com/aike/oscer 30 | 31 | ## CREDIT 32 | oscer program is licensed under MIT License. 33 | Contact: twitter @aike1000 34 | -------------------------------------------------------------------------------- /build/Linux32/oscer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aike/oscer/a3c39480772e392cafa83edb7f8d10b88ead3f2a/build/Linux32/oscer -------------------------------------------------------------------------------- /build/Linux64/oscer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aike/oscer/a3c39480772e392cafa83edb7f8d10b88ead3f2a/build/Linux64/oscer -------------------------------------------------------------------------------- /build/LinuxARM/oscer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aike/oscer/a3c39480772e392cafa83edb7f8d10b88ead3f2a/build/LinuxARM/oscer -------------------------------------------------------------------------------- /build/MacAmd/oscer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aike/oscer/a3c39480772e392cafa83edb7f8d10b88ead3f2a/build/MacAmd/oscer -------------------------------------------------------------------------------- /build/MacArm/oscer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aike/oscer/a3c39480772e392cafa83edb7f8d10b88ead3f2a/build/MacArm/oscer -------------------------------------------------------------------------------- /build/Windows32/oscer.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aike/oscer/a3c39480772e392cafa83edb7f8d10b88ead3f2a/build/Windows32/oscer.exe -------------------------------------------------------------------------------- /build/Windows64/oscer.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aike/oscer/a3c39480772e392cafa83edb7f8d10b88ead3f2a/build/Windows64/oscer.exe -------------------------------------------------------------------------------- /src/go.mod: -------------------------------------------------------------------------------- 1 | module oscer 2 | 3 | go 1.23.3 4 | -------------------------------------------------------------------------------- /src/makefile: -------------------------------------------------------------------------------- 1 | oscer: oscer.go osc/osc.go 2 | go build oscer.go 3 | 4 | test: 5 | go test -v oscer_test.go 6 | 7 | all: oscer.go osc/osc.go 8 | set GOOS=darwin 9 | set GOARCH=arm64 10 | go build -o ../build/MacArm/oscer oscer.go 11 | set GOOS=darwin 12 | set GOARCH=amd64 13 | go build -o ../build/MacAmd/oscer oscer.go 14 | set GOOS=windows 15 | set GOARCH=amd64 16 | go build -o ../build/Windows64/oscer.exe oscer.go 17 | set GOOS=windows 18 | set GOARCH=386 19 | go build -o ../build/Windows32/oscer.exe oscer.go 20 | set GOOS=linux 21 | set GOARCH=amd64 22 | go build -o ../build/Linux64/oscer oscer.go 23 | set GOOS=linux 24 | set GOARCH=386 25 | go build -o ../build/Linux32/oscer oscer.go 26 | set GOOS=linux 27 | set GOARCH=arm 28 | go build -o ../build/LinuxARM/oscer oscer.go 29 | -------------------------------------------------------------------------------- /src/osc/osc.go: -------------------------------------------------------------------------------- 1 | // osc.go by aike 2 | // licenced under MIT License. 3 | 4 | package osc 5 | 6 | import ( 7 | "fmt" 8 | "net" 9 | "os" 10 | "regexp" 11 | "strconv" 12 | "bytes" 13 | "encoding/binary" 14 | "errors" 15 | ) 16 | 17 | var serverIP string 18 | var serverPort string 19 | var senddata []byte 20 | var oscarg []byte 21 | var initdata bool = false 22 | 23 | func Send() { 24 | if !initdata { 25 | return 26 | } 27 | 28 | udpAddr, err := net.ResolveUDPAddr("udp", serverIP + ":" + serverPort) 29 | checkError(err) 30 | 31 | conn, err := net.DialUDP("udp", nil, udpAddr) 32 | checkError(err) 33 | defer conn.Close() 34 | 35 | conn.Write(senddata) 36 | } 37 | 38 | func checkError(err error) { 39 | if err != nil { 40 | fmt.Fprintf(os.Stderr, "fatal: error: %s", err.Error()) 41 | os.Exit(1) 42 | } 43 | } 44 | 45 | func CheckArg(arr []string) error { 46 | senddata = []byte{} 47 | oscarg = []byte{} 48 | 49 | if len(arr) < 4 { 50 | return errors.New("args error") 51 | } 52 | 53 | host := arr[1] 54 | if match(`^[A-Za-z0-9\-\.]+$`, host) { 55 | // IPv4 56 | serverIP = host 57 | } else if match(`^[:%A-Za-z0-9]+$`, host) { 58 | // IPv6 59 | serverIP = "[" + host + "]" 60 | } else { 61 | return errors.New("hostname error") 62 | } 63 | 64 | port, err := strconv.Atoi(arr[2]) 65 | if err != nil { 66 | return errors.New("port number error") 67 | } 68 | if port < 0 || port > 65535 { 69 | return errors.New("port number error") 70 | } 71 | serverPort = arr[2] 72 | 73 | adsr := arr[3] 74 | if !match(`^/.*[^/]$`, adsr) { 75 | return errors.New("osc address error") 76 | } 77 | pushAdrsString(adsr) 78 | 79 | idx := 0 80 | for i := 4; i < len(arr); i++ { 81 | if match(`^[+-]?[0-9]+$`, arr[i]) { 82 | // Int32 83 | num_i64, err := strconv.ParseInt(arr[i], 10, 32) 84 | num_i32 := int32(num_i64) 85 | if err != nil { 86 | return errors.New("osc args error") 87 | } 88 | pushDataI32(num_i32) 89 | 90 | } else if match(`^[+-]?[0-9.]+$`, arr[i]) { 91 | // Float32 92 | num_f64, err := strconv.ParseFloat(arr[i], 32) 93 | num_f32 := float32(num_f64) 94 | if err != nil { 95 | return errors.New("osc args error") 96 | } 97 | pushDataF32(num_f32) 98 | 99 | } else { 100 | // String 101 | pushDataString(arr[i]) 102 | } 103 | idx++ 104 | } 105 | 106 | senddata = append(senddata, 0) 107 | fill4byte() 108 | senddata = append(senddata, oscarg...) 109 | 110 | initdata = true 111 | return nil 112 | } 113 | 114 | 115 | func IsServer(arr []string) bool { 116 | if len(arr) != 3 { 117 | return false 118 | } else if arr[1] != "receive" { 119 | return false 120 | } 121 | return true 122 | } 123 | 124 | func CreateServer(portstr string) error { 125 | port, err := strconv.Atoi(portstr) 126 | if err != nil { 127 | return errors.New("port number error") 128 | } 129 | if port < 0 || port > 65535 { 130 | return errors.New("port number error") 131 | } 132 | 133 | addr, err := net.ResolveUDPAddr("udp", ":" + portstr) 134 | if err != nil { 135 | return errors.New("server resolve error") 136 | } 137 | 138 | conn, err := net.ListenUDP("udp", addr) 139 | if err != nil { 140 | return errors.New("server listen error") 141 | } 142 | 143 | defer conn.Close() 144 | buf := make([]byte, 4096) 145 | for { 146 | len, _, err := conn.ReadFromUDP(buf) 147 | if err != nil { 148 | return errors.New("server data read error") 149 | } 150 | 151 | parse(buf, len) 152 | } 153 | 154 | return nil; 155 | } 156 | 157 | 158 | func match(reg, str string) bool { 159 | return regexp.MustCompile(reg).Match([]byte(str)) 160 | } 161 | 162 | func pushAdrsString(str string) { 163 | senddata = append(senddata, []byte(str)...) 164 | senddata = append(senddata, 0) 165 | fill4byte() 166 | senddata = append(senddata, 0x2c) 167 | } 168 | 169 | func fill4byte() { 170 | for datalen := len(senddata); datalen % 4 != 0; datalen++ { 171 | senddata = append(senddata, 0) 172 | } 173 | } 174 | 175 | func pushDataI32(num int32) { 176 | senddata = append(senddata, 'i') 177 | buf := bytes.NewBuffer([]byte{}) 178 | binary.Write(buf, binary.BigEndian, num) 179 | oscarg = append(oscarg, buf.Bytes()...) 180 | } 181 | 182 | func pushDataF32(num float32) { 183 | senddata = append(senddata, 'f') 184 | buf := bytes.NewBuffer([]byte{}) 185 | binary.Write(buf, binary.BigEndian, num) 186 | oscarg = append(oscarg, buf.Bytes()...) 187 | } 188 | 189 | func pushDataString(str string) { 190 | senddata = append(senddata, 's') 191 | buf := bytes.NewBuffer([]byte(str)) 192 | oscarg = append(oscarg, buf.Bytes()...) 193 | 194 | oscarg = append(oscarg, 0) 195 | for datalen := len(oscarg); datalen % 4 != 0; datalen++ { 196 | oscarg = append(oscarg, 0) 197 | } 198 | } 199 | 200 | func GetData() []byte { 201 | return senddata 202 | } 203 | 204 | func parse(arr []byte, datalen int) { 205 | // for i := 0; i < datalen; i++ { 206 | // fmt.Printf("%02x ", arr[i]) 207 | // if i % 16 == 15 { 208 | // fmt.Printf("\n") 209 | // } 210 | // } 211 | // fmt.Printf("\n==================\n") 212 | 213 | var pos int 214 | var i32 int32 215 | var f32 float32 216 | var str string 217 | 218 | adrs, pos := getString(arr, 0) 219 | fmt.Printf("%s", adrs) 220 | 221 | flags, pos := getString(arr, pos) 222 | flags = flags[1:] 223 | 224 | for i := 0; i < len(flags); i++ { 225 | switch flags[i] { 226 | case 'i': 227 | i32, pos = getInt32(arr, pos) 228 | fmt.Printf(" %v", i32) 229 | case 'f': 230 | f32, pos = getFloat32(arr, pos) 231 | fmt.Printf(" %v", f32) 232 | case 's': 233 | str, pos = getString(arr, pos) 234 | fmt.Printf(` "%s"`, str) 235 | } 236 | } 237 | fmt.Printf("\n") 238 | } 239 | 240 | func getString(arr []byte, start int) (string, int) { 241 | pos := start 242 | for ; arr[pos] != 0 && pos < len(arr); pos++ {} 243 | 244 | rest := 4 - (pos % 4) 245 | pos += rest 246 | 247 | return string(arr[start:pos]), pos 248 | } 249 | 250 | func getInt32(arr []byte, start int) (int32, int) { 251 | var n int32 252 | buf := bytes.NewBuffer(arr[start:start + 4]) 253 | binary.Read(buf, binary.BigEndian, &n) 254 | return n, start + 4 255 | } 256 | 257 | func getFloat32(arr []byte, start int) (float32, int) { 258 | var f float32 259 | buf := bytes.NewBuffer(arr[start:start + 4]) 260 | binary.Read(buf, binary.BigEndian, &f) 261 | return f, start + 4 262 | } 263 | 264 | -------------------------------------------------------------------------------- /src/oscer.go: -------------------------------------------------------------------------------- 1 | // oscer.go by aike 2 | // licenced under MIT License. 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "oscer/osc" 10 | ) 11 | 12 | var version string = "1.3" 13 | 14 | func main() { 15 | if osc.IsServer(os.Args) { 16 | // server 17 | err := osc.CreateServer(os.Args[2]) 18 | checkError(err) 19 | os.Exit(0) 20 | 21 | } else { 22 | // client 23 | err := osc.CheckArg(os.Args) 24 | checkError(err) 25 | osc.Send() 26 | os.Exit(0) 27 | } 28 | } 29 | 30 | func checkError(err error) { 31 | if err != nil { 32 | if err.Error() == "args error" { 33 | fmt.Fprintf(os.Stderr, "oscer ver %s\n", version) 34 | fmt.Fprintf(os.Stderr, "usage: oscer host port /osc/address [args ...]\n") 35 | fmt.Fprintf(os.Stderr, " oscer receive port\n") 36 | os.Exit(1) 37 | } else { 38 | fmt.Fprintf(os.Stderr, "%s\n", err) 39 | os.Exit(2) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/oscer_test.go: -------------------------------------------------------------------------------- 1 | // oscer_test.go by aike 2 | // licenced under MIT License. 3 | 4 | package oscer_test 5 | 6 | import ( 7 | "testing" 8 | "bytes" 9 | "oscer/osc" 10 | ) 11 | 12 | func Test_Ok1(t *testing.T) { 13 | arg := []string{"oscer", "localhost", "12000", "/test/test"} 14 | err := osc.CheckArg(arg) 15 | if err != nil { 16 | t.Error("legal arguments NG") 17 | } 18 | } 19 | 20 | func Test_Ok2(t *testing.T) { 21 | arg := []string{"oscer", "192.168.0.1", "12000", "/test/test"} 22 | err := osc.CheckArg(arg) 23 | if err != nil { 24 | t.Error("legal arguments NG") 25 | } 26 | } 27 | 28 | func Test_Ok3(t *testing.T) { 29 | arg := []string{"oscer", "::1", "12000", "/test/test"} 30 | err := osc.CheckArg(arg) 31 | if err != nil { 32 | t.Error("legal arguments NG") 33 | } 34 | } 35 | 36 | func Test_Ok4(t *testing.T) { 37 | arg := []string{"oscer", `fe80::aa66:7fff:fe22:12f8%en0`, "12000", "/test/test"} 38 | err := osc.CheckArg(arg) 39 | if err != nil { 40 | t.Error("legal arguments NG") 41 | } 42 | } 43 | 44 | func Test_Ok_Server1(t *testing.T) { 45 | arg := []string{"oscer", "receive", "12000"} 46 | ok := osc.IsServer(arg) 47 | if !ok { 48 | t.Error("server check NG") 49 | } 50 | } 51 | 52 | func Test_Ok_Server2(t *testing.T) { 53 | arg := []string{"oscer", "192.168.0.1", "12000", "/test/test"} 54 | ok := osc.IsServer(arg) 55 | if ok { 56 | t.Error("server check NG") 57 | } 58 | } 59 | 60 | 61 | /////////////////////////////////////// 62 | 63 | 64 | func Test_NgHost(t *testing.T) { 65 | arg := []string{"oscer", "", "12000", "/test/test"} 66 | err := osc.CheckArg(arg) 67 | if err == nil { 68 | t.Error("illegal host test NG") 69 | } 70 | } 71 | 72 | func Test_NgPort1(t *testing.T) { 73 | arg := []string{"oscer", "localhost", "-1", "test/test"} 74 | err := osc.CheckArg(arg) 75 | if err == nil { 76 | t.Error("illegal port number test NG") 77 | } 78 | } 79 | 80 | func Test_NgPort2(t *testing.T) { 81 | arg := []string{"oscer", "localhost", "65536", "test/test"} 82 | err := osc.CheckArg(arg) 83 | if err == nil { 84 | t.Error("illegal port number test NG") 85 | } 86 | } 87 | 88 | func Test_NgAdsr1(t *testing.T) { 89 | arg := []string{"oscer", "localhost", "12000", "test/test"} 90 | err := osc.CheckArg(arg) 91 | if err == nil { 92 | t.Error("illegal address test NG") 93 | } 94 | } 95 | 96 | func Test_NgAdsr2(t *testing.T) { 97 | arg := []string{"oscer", "localhost", "12000", "/test/test/"} 98 | err := osc.CheckArg(arg) 99 | if err == nil { 100 | t.Error("illegal address test NG") 101 | } 102 | } 103 | 104 | func Test_OkArg1(t *testing.T) { 105 | arg := []string{"oscer", "localhost", "12000", "/test/test", "100"} 106 | err := osc.CheckArg(arg) 107 | if err != nil { 108 | t.Error("legal arguments NG") 109 | } 110 | } 111 | 112 | func Test_OkArg2(t *testing.T) { 113 | arg := []string{"oscer", "localhost", "12000", "/test/test", "-100"} 114 | err := osc.CheckArg(arg) 115 | if err != nil { 116 | t.Error("legal arguments NG") 117 | } 118 | } 119 | 120 | func Test_OkArg3(t *testing.T) { 121 | arg := []string{"oscer", "localhost", "12000", "/test/test", "-100.5"} 122 | err := osc.CheckArg(arg) 123 | if err != nil { 124 | t.Error("legal arguments NG") 125 | } 126 | } 127 | 128 | func Test_OkArg4(t *testing.T) { 129 | arg := []string{"oscer", "localhost", "12000", "/test/test", "-100.5"} 130 | err := osc.CheckArg(arg) 131 | if err != nil { 132 | t.Error("legal arguments NG") 133 | } 134 | } 135 | 136 | func Test_NgArg1(t *testing.T) { 137 | arg := []string{"oscer", "localhost", "12000", "/test/test", "1.1.1"} 138 | err := osc.CheckArg(arg) 139 | if err == nil { 140 | t.Error("illegal arguments test NG") 141 | } 142 | } 143 | 144 | /////////////////////////////////////// 145 | 146 | func Test_MessageArgNone1(t *testing.T) { 147 | arg := []string{"oscer", "localhost", "12000", "/test"} 148 | expected := []byte {0x2f, 0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 149 | 0x2c, 0x00, 0x00, 0x00 } 150 | _ = osc.CheckArg(arg) 151 | if bytes.Compare(expected, osc.GetData()) != 0 { 152 | t.Error("message test none NG") 153 | } 154 | } 155 | 156 | func Test_MessageArgNone2(t *testing.T) { 157 | arg := []string{"oscer", "localhost", "12000", "/testabc"} 158 | expected := []byte {0x2f, 0x74, 0x65, 0x73, 0x74, 0x61, 0x62, 0x63, 159 | 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00 } 160 | _ = osc.CheckArg(arg) 161 | if bytes.Compare(expected, osc.GetData()) != 0 { 162 | t.Error("message test none NG") 163 | } 164 | } 165 | 166 | func Test_MessageArgInt(t *testing.T) { 167 | arg := []string{"oscer", "localhost", "12000", "/test", "100"} 168 | expected := []byte {0x2f, 0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 169 | 0x2c, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64 } 170 | _ = osc.CheckArg(arg) 171 | if bytes.Compare(expected, osc.GetData()) != 0 { 172 | t.Error("message test int NG") 173 | } 174 | } 175 | 176 | func Test_MessageArgFloat(t *testing.T) { 177 | arg := []string{"oscer", "localhost", "12000", "/test", "10.5"} 178 | expected := []byte {0x2f, 0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 179 | 0x2c, 0x66, 0x00, 0x00, 0x41, 0x28, 0x00, 0x00 } 180 | _ = osc.CheckArg(arg) 181 | if bytes.Compare(expected, osc.GetData()) != 0 { 182 | t.Error("message test float NG") 183 | } 184 | } 185 | 186 | func Test_MessageArgIntFloat(t *testing.T) { 187 | arg := []string{"oscer", "localhost", "12000", "/test", "100", "10.5"} 188 | expected := []byte {0x2f, 0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 189 | 0x2c, 0x69, 0x66, 0x00, 0x00, 0x00, 0x00, 0x64, 190 | 0x41, 0x28, 0x00, 0x00 } 191 | _ = osc.CheckArg(arg) 192 | if bytes.Compare(expected, osc.GetData()) != 0 { 193 | t.Error("message test int float NG") 194 | } 195 | } 196 | 197 | func Test_Message3Args(t *testing.T) { 198 | arg := []string{"oscer", "localhost", "12000", "/test", "100", "100", "100"} 199 | expected := []byte {0x2f, 0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 200 | 0x2c, 0x69, 0x69, 0x69, 0x00, 0x00, 0x00, 0x00, 201 | 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 202 | 0x00, 0x00, 0x00, 0x64 } 203 | _ = osc.CheckArg(arg) 204 | if bytes.Compare(expected, osc.GetData()) != 0 { 205 | t.Error("message test 3 args NG") 206 | } 207 | } 208 | 209 | func Test_MessageString(t *testing.T) { 210 | arg := []string{"oscer", "localhost", "12000", "/hello", "world"} 211 | expected := []byte {0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 212 | 0x2c, 0x73, 0x00, 0x00, 0x77, 0x6f, 0x72, 0x6c, 213 | 0x64, 0x00, 0x00, 0x00} 214 | _ = osc.CheckArg(arg) 215 | if bytes.Compare(expected, osc.GetData()) != 0 { 216 | t.Error("message test string NG") 217 | } 218 | } 219 | 220 | func Test_MessageIntStrFloatStr(t *testing.T) { 221 | arg := []string{"oscer", "localhost", "12000", "/hello", "10", "str1", "1.5", "str2"} 222 | expected := []byte {0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 223 | 0x2c, 0x69, 0x73, 0x66, 0x73, 0x00, 0x00, 0x00, 224 | 0x00, 0x00, 0x00, 0x0a, 0x73, 0x74, 0x72, 0x31, 225 | 0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x00, 0x00, 226 | 0x73, 0x74, 0x72, 0x32, 0x00, 0x00, 0x00, 0x00 } 227 | _ = osc.CheckArg(arg) 228 | 229 | // b := osc.GetData() 230 | // for i := 0; i < len(b); i++ { 231 | // fmt.Printf("%02x ", b[i]) 232 | // } 233 | // fmt.Println("") 234 | 235 | if bytes.Compare(expected, osc.GetData()) != 0 { 236 | t.Error("message test int str float str NG") 237 | } 238 | } 239 | 240 | 241 | 242 | --------------------------------------------------------------------------------