From 6978b4be129a84f3a44374ca59ce0bff6bb8e5dd Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sat, 23 Nov 2019 11:22:09 -0500 Subject: [PATCH 01/42] add NetworkProtocol, Client.networkProtocol that defaults to UDP --- osc/osc.go | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/osc/osc.go b/osc/osc.go index a16370a..1adc570 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -21,6 +21,17 @@ const ( bundleTagString = "#bundle" ) +// NetworkProtocol represents a network protocol that can be used to transport +// OSC messages. +type NetworkProtocol int + +const ( + // UDP represents the UDP network protocol. + UDP NetworkProtocol = iota + // TCP represents the TCP network protocol. + TCP +) + // Packet is the interface for Message and Bundle. type Packet interface { encoding.BinaryMarshaler @@ -52,9 +63,10 @@ var _ Packet = (*Bundle)(nil) // Client enables you to send OSC packets. It sends OSC messages and bundles to // the given IP address and port. type Client struct { - ip string - port int - laddr *net.UDPAddr + ip string + port int + laddr *net.UDPAddr + networkProtocol NetworkProtocol } // Server represents an OSC server. The server listens on Address and Port for @@ -476,7 +488,7 @@ func (b *Bundle) MarshalBinary() ([]byte, error) { // specifies the IP address and `port` defines the target port where the // messages and bundles will be send to. func NewClient(ip string, port int) *Client { - return &Client{ip: ip, port: port, laddr: nil} + return &Client{ip: ip, port: port, laddr: nil, networkProtocol: UDP} } // IP returns the IP address. @@ -501,6 +513,16 @@ func (c *Client) SetLocalAddr(ip string, port int) error { return nil } +// NetworkProtocol returns the network protocol. +func (c *Client) NetworkProtocol() NetworkProtocol { + return c.networkProtocol +} + +// SetNetworkProtocol sets the network protocol. +func (c *Client) SetNetworkProtocol(protocol NetworkProtocol) { + c.networkProtocol = protocol +} + // Send sends an OSC Bundle or an OSC Message. func (c *Client) Send(packet Packet) error { addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", c.ip, c.port)) From 1797219612a3388798afcdcc4811be5b25c7f325 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sat, 23 Nov 2019 11:54:21 -0500 Subject: [PATCH 02/42] add Server.networkProtocol, NewServer constructor that defaults networkProtocol to UDP --- osc/osc.go | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/osc/osc.go b/osc/osc.go index 1adc570..81dad11 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -72,9 +72,10 @@ type Client struct { // Server represents an OSC server. The server listens on Address and Port for // incoming OSC packets and bundles. type Server struct { - Addr string - Dispatcher *OscDispatcher - ReadTimeout time.Duration + Addr string + Dispatcher *OscDispatcher + ReadTimeout time.Duration + networkProtocol NetworkProtocol } // Timetag represents an OSC Time Tag. @@ -550,6 +551,31 @@ func (c *Client) Send(packet Packet) error { // Server //// +// NewServer creates a new OSC client. The Server is used to send OSC +// messages and OSC bundles over an UDP network connection. The `ip` argument +// specifies the IP address and `port` defines the target port where the +// messages and bundles will be send to. +func NewServer( + addr string, dispatcher *OscDispatcher, readTimeout time.Duration, +) *Server { + return &Server{ + Addr: addr, + Dispatcher: dispatcher, + ReadTimeout: readTimeout, + networkProtocol: UDP, + } +} + +// NetworkProtocol returns the network protocol. +func (s *Server) NetworkProtocol() NetworkProtocol { + return s.networkProtocol +} + +// SetNetworkProtocol sets the network protocol. +func (s *Server) SetNetworkProtocol(protocol NetworkProtocol) { + s.networkProtocol = protocol +} + // Handle registers a new message handler function for an OSC address. The // handler is the function called for incoming OscMessages that match 'address'. func (s *Server) Handle(addr string, handler HandlerFunc) error { From 1af723095057ad5e0171ae65a581f854764c66ee Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sat, 23 Nov 2019 16:03:37 -0500 Subject: [PATCH 03/42] WIP send/receive test; implement receiving TCP packet on the server side --- osc/osc.go | 28 ++++++++++++++++++++++++++++ osc/osc_test.go | 48 ++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/osc/osc.go b/osc/osc.go index 81dad11..def3518 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -652,6 +652,34 @@ func (s *Server) readFromConnection(c net.PacketConn) (Packet, error) { return p, nil } +// ReceiveTCPPacket listens for incoming OSC packets and returns the packet if +// one is received. +func (s *Server) ReceiveTCPPacket(l net.Listener) (Packet, error) { + conn, err := l.Accept() + if err != nil { + return nil, err + } + + if s.ReadTimeout != 0 { + if err := conn.SetReadDeadline(time.Now().Add(s.ReadTimeout)); err != nil { + return nil, err + } + } + + data := make([]byte, 65535) + n, err := conn.Read(data) + if err != nil { + return nil, err + } + + var start int + p, err := readPacket(bufio.NewReader(bytes.NewBuffer(data)), &start, n) + if err != nil { + return nil, err + } + return p, nil +} + // ParsePacket parses the given msg string and returns a Packet func ParsePacket(msg string) (Packet, error) { var start int diff --git a/osc/osc_test.go b/osc/osc_test.go index 942e8c6..1a1d5a9 100644 --- a/osc/osc_test.go +++ b/osc/osc_test.go @@ -5,6 +5,7 @@ import ( "bytes" "net" "reflect" + "strconv" "sync" "testing" "time" @@ -171,7 +172,9 @@ func TestServerMessageDispatching(t *testing.T) { done.Wait() } -func TestServerMessageReceiving(t *testing.T) { +func testServerMessageReceiving(t *testing.T, protocol NetworkProtocol) { + port := 6677 + finish := make(chan bool) start := make(chan bool) done := sync.WaitGroup{} @@ -180,18 +183,38 @@ func TestServerMessageReceiving(t *testing.T) { // Start the server in a go-routine go func() { server := &Server{} - c, err := net.ListenPacket("udp", "localhost:6677") - if err != nil { - t.Fatal(err) + server.SetNetworkProtocol(protocol) + + var receivePacket func() (Packet, error) + switch protocol { + case UDP: + receivePacket = func() (Packet, error) { + c, err := net.ListenPacket("udp", "localhost:"+strconv.Itoa(port)) + if err != nil { + t.Fatal(err) + } + defer c.Close() + + return server.ReceivePacket(c) + } + case TCP: + receivePacket = func() (Packet, error) { + l, err := net.Listen("tcp", ":"+strconv.Itoa(port)) + if err != nil { + t.Fatal(err) + } + defer l.Close() + + return server.ReceiveTCPPacket(l) + } } - defer c.Close() // Start the client start <- true - packet, err := server.ReceivePacket(c) + packet, err := receivePacket() if err != nil { - t.Error("Server error") + t.Errorf("Server error: %s", err.Error()) return } if packet == nil { @@ -210,7 +233,6 @@ func TestServerMessageReceiving(t *testing.T) { t.Error("Argument should be 3344 and is: " + string(msg.Arguments[1].(int32))) } - c.Close() finish <- true }() @@ -220,6 +242,8 @@ func TestServerMessageReceiving(t *testing.T) { case <-timeout: case <-start: client := NewClient("localhost", 6677) + client.SetNetworkProtocol(protocol) + msg := NewMessage("/address/test") msg.Append(int32(1122)) msg.Append(int32(3344)) @@ -239,6 +263,14 @@ func TestServerMessageReceiving(t *testing.T) { done.Wait() } +func TestServerMessageReceivingUDP(t *testing.T) { + testServerMessageReceiving(t, UDP) +} + +func TestServerMessageReceivingTCP(t *testing.T) { + testServerMessageReceiving(t, TCP) +} + func TestReadTimeout(t *testing.T) { start := make(chan bool) wg := sync.WaitGroup{} From f7cf8694fe8ee56cafc450586bdf67e6ce6952b4 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sat, 23 Nov 2019 16:17:06 -0500 Subject: [PATCH 04/42] test sending and receiving a packet too large for UDP --- osc/osc_test.go | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/osc/osc_test.go b/osc/osc_test.go index 1a1d5a9..8deba7b 100644 --- a/osc/osc_test.go +++ b/osc/osc_test.go @@ -3,6 +3,7 @@ package osc import ( "bufio" "bytes" + "math/rand" "net" "reflect" "strconv" @@ -172,7 +173,9 @@ func TestServerMessageDispatching(t *testing.T) { done.Wait() } -func testServerMessageReceiving(t *testing.T, protocol NetworkProtocol) { +func testServerMessageReceiving( + t *testing.T, protocol NetworkProtocol, stringArgument string, +) { port := 6677 finish := make(chan bool) @@ -223,8 +226,8 @@ func testServerMessageReceiving(t *testing.T, protocol NetworkProtocol) { } msg := packet.(*Message) - if msg.CountArguments() != 2 { - t.Errorf("Argument length should be 2 and is: %d\n", msg.CountArguments()) + if msg.CountArguments() != 3 { + t.Errorf("Argument length should be 3 and is: %d\n", msg.CountArguments()) } if msg.Arguments[0].(int32) != 1122 { t.Error("Argument should be 1122 and is: " + string(msg.Arguments[0].(int32))) @@ -232,6 +235,9 @@ func testServerMessageReceiving(t *testing.T, protocol NetworkProtocol) { if msg.Arguments[1].(int32) != 3344 { t.Error("Argument should be 3344 and is: " + string(msg.Arguments[1].(int32))) } + if msg.Arguments[2].(string) != stringArgument { + t.Errorf("Argument should be %s and is: " + msg.Arguments[2].(string)) + } finish <- true }() @@ -247,6 +253,7 @@ func testServerMessageReceiving(t *testing.T, protocol NetworkProtocol) { msg := NewMessage("/address/test") msg.Append(int32(1122)) msg.Append(int32(3344)) + msg.Append(stringArgument) time.Sleep(500 * time.Millisecond) client.Send(msg) } @@ -263,12 +270,24 @@ func testServerMessageReceiving(t *testing.T, protocol NetworkProtocol) { done.Wait() } +// source: https://www.calhoun.io/creating-random-strings-in-go/ +func randomString(length int) string { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + var seededRand = rand.New(rand.NewSource(time.Now().UnixNano())) + + b := make([]byte, length) + for i := range b { + b[i] = charset[seededRand.Intn(len(charset))] + } + return string(b) +} + func TestServerMessageReceivingUDP(t *testing.T) { - testServerMessageReceiving(t, UDP) + testServerMessageReceiving(t, UDP, randomString(500)) } func TestServerMessageReceivingTCP(t *testing.T) { - testServerMessageReceiving(t, TCP) + testServerMessageReceiving(t, TCP, randomString(100000)) } func TestReadTimeout(t *testing.T) { From ccb466714f532f262b5c0ec3baf525b93628dd9a Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sun, 24 Nov 2019 07:56:37 -0500 Subject: [PATCH 05/42] fix test so that it propagates client send errors --- osc/osc_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osc/osc_test.go b/osc/osc_test.go index 8deba7b..c3e277a 100644 --- a/osc/osc_test.go +++ b/osc/osc_test.go @@ -255,7 +255,12 @@ func testServerMessageReceiving( msg.Append(int32(3344)) msg.Append(stringArgument) time.Sleep(500 * time.Millisecond) - client.Send(msg) + if err := client.Send(msg); err != nil { + t.Error(err) + done.Done() + done.Done() + return + } } done.Done() From ce6b52303b22488c0ff56726e2d71970b7959e67 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sun, 24 Nov 2019 17:10:44 -0500 Subject: [PATCH 06/42] for TCP, use ioutil.ReadAll, since we don't know the packet size and it could be > 65535 --- osc/osc.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osc/osc.go b/osc/osc.go index def3518..8dfa011 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -9,6 +9,7 @@ import ( "encoding/binary" "errors" "fmt" + "io/ioutil" "net" "reflect" "regexp" @@ -666,14 +667,14 @@ func (s *Server) ReceiveTCPPacket(l net.Listener) (Packet, error) { } } - data := make([]byte, 65535) - n, err := conn.Read(data) + data, err := ioutil.ReadAll(conn) if err != nil { return nil, err } var start int - p, err := readPacket(bufio.NewReader(bytes.NewBuffer(data)), &start, n) + end := len(data) + p, err := readPacket(bufio.NewReader(bytes.NewBuffer(data)), &start, end) if err != nil { return nil, err } From d3abe211eb950a13a53a5b9961e3d9ee64f63fff Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sun, 24 Nov 2019 17:13:22 -0500 Subject: [PATCH 07/42] improve/fix NewClient and NewServer docstrings --- osc/osc.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osc/osc.go b/osc/osc.go index 8dfa011..b225e5f 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -486,9 +486,13 @@ func (b *Bundle) MarshalBinary() ([]byte, error) { //// // NewClient creates a new OSC client. The Client is used to send OSC -// messages and OSC bundles over an UDP network connection. The `ip` argument -// specifies the IP address and `port` defines the target port where the -// messages and bundles will be send to. +// messages and OSC bundles over a network connection. +// +// The default network protocol is UDP. To use TCP instead, use +// client.SetNetworkProtocol(TCP). +// +// The `ip` argument specifies the IP address and `port` defines the target port +// where the messages and bundles will be send to. func NewClient(ip string, port int) *Client { return &Client{ip: ip, port: port, laddr: nil, networkProtocol: UDP} } @@ -552,10 +556,11 @@ func (c *Client) Send(packet Packet) error { // Server //// -// NewServer creates a new OSC client. The Server is used to send OSC -// messages and OSC bundles over an UDP network connection. The `ip` argument -// specifies the IP address and `port` defines the target port where the -// messages and bundles will be send to. +// NewServer creates a new OSC server. The server receives OSC messages and +// bundles over a network connection. +// +// The default network protocol is UDP. To use TCP instead, use +// server.SetNetworkProtocol(TCP). func NewServer( addr string, dispatcher *OscDispatcher, readTimeout time.Duration, ) *Server { From bdf2feb2becb5e32a6d6c52f1e95340e83f53800 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sun, 24 Nov 2019 17:14:36 -0500 Subject: [PATCH 08/42] add client.laddrTCP; adjust SetLocalAddr to set laddrTCP when the protocol is TCP --- osc/osc.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/osc/osc.go b/osc/osc.go index b225e5f..7793dc8 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -67,6 +67,7 @@ type Client struct { ip string port int laddr *net.UDPAddr + laddrTCP *net.TCPAddr networkProtocol NetworkProtocol } @@ -511,11 +512,21 @@ func (c *Client) SetPort(port int) { c.port = port } // SetLocalAddr sets the local address. func (c *Client) SetLocalAddr(ip string, port int) error { - laddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ip, port)) - if err != nil { - return err + switch c.networkProtocol { + case UDP: + laddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ip, port)) + if err != nil { + return err + } + c.laddr = laddr + case TCP: + laddrTCP, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", ip, port)) + if err != nil { + return err + } + c.laddrTCP = laddrTCP } - c.laddr = laddr + return nil } From f79e90b242f8ee1e91128ef22612c5925a0c493a Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sun, 24 Nov 2019 17:15:44 -0500 Subject: [PATCH 09/42] fix/improve test error message --- osc/osc_test.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osc/osc_test.go b/osc/osc_test.go index c3e277a..c1b926d 100644 --- a/osc/osc_test.go +++ b/osc/osc_test.go @@ -235,8 +235,18 @@ func testServerMessageReceiving( if msg.Arguments[1].(int32) != 3344 { t.Error("Argument should be 3344 and is: " + string(msg.Arguments[1].(int32))) } - if msg.Arguments[2].(string) != stringArgument { - t.Errorf("Argument should be %s and is: " + msg.Arguments[2].(string)) + + receivedString := msg.Arguments[2].(string) + if len(receivedString) != len(stringArgument) { + t.Errorf( + "String argument length should be %d and is %d", + len(stringArgument), + len(receivedString), + ) + } else if receivedString != stringArgument { + t.Errorf( + "Argument should be %s and is: %s", stringArgument, receivedString, + ) } finish <- true From 0f5a2db492912b62117e5d13dfb23533be153059 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sun, 24 Nov 2019 17:16:44 -0500 Subject: [PATCH 10/42] document why the UDP test only sends a string of length 500 --- osc/osc_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osc/osc_test.go b/osc/osc_test.go index c1b926d..c39c023 100644 --- a/osc/osc_test.go +++ b/osc/osc_test.go @@ -298,6 +298,16 @@ func randomString(length int) string { } func TestServerMessageReceivingUDP(t *testing.T) { + // Attempting to send a message larger than this (the exact threshold depends + // on your OS, etc.) results in an error like: + // + // write udp 127.0.0.1:52496->127.0.0.1:6677: write: message too long + // + // This is expected for UDP. The practical guaranteed packet size limit for + // UDP is 576 bytes, and some of that is taken up by protocol overhead. + // + // Reference: + // https://forum.juce.com/t/osc-blobs-are-lost-above-certain-size/20241/2 testServerMessageReceiving(t, UDP, randomString(500)) } From a1a56120ffbf52685c13863075e257a95a7ecedd Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sun, 24 Nov 2019 17:17:05 -0500 Subject: [PATCH 11/42] use a 1,000,000 byte string for the TCP test --- osc/osc_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osc/osc_test.go b/osc/osc_test.go index c39c023..13f4be5 100644 --- a/osc/osc_test.go +++ b/osc/osc_test.go @@ -312,7 +312,7 @@ func TestServerMessageReceivingUDP(t *testing.T) { } func TestServerMessageReceivingTCP(t *testing.T) { - testServerMessageReceiving(t, TCP, randomString(100000)) + testServerMessageReceiving(t, TCP, randomString(1000000)) } func TestReadTimeout(t *testing.T) { From cad5813a28ada8233ddf8de2a3931544bdfaf8f7 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sun, 24 Nov 2019 17:17:41 -0500 Subject: [PATCH 12/42] implement TCP sending on the client side --- osc/osc.go | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/osc/osc.go b/osc/osc.go index 7793dc8..540f6bb 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -542,24 +542,42 @@ func (c *Client) SetNetworkProtocol(protocol NetworkProtocol) { // Send sends an OSC Bundle or an OSC Message. func (c *Client) Send(packet Packet) error { - addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", c.ip, c.port)) - if err != nil { - return err - } - conn, err := net.DialUDP("udp", c.laddr, addr) - if err != nil { - return err - } - defer conn.Close() - data, err := packet.MarshalBinary() if err != nil { return err } - if _, err = conn.Write(data); err != nil { - return err + switch c.networkProtocol { + case UDP: + addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", c.ip, c.port)) + if err != nil { + return err + } + conn, err := net.DialUDP("udp", c.laddr, addr) + if err != nil { + return err + } + defer conn.Close() + + if _, err = conn.Write(data); err != nil { + return err + } + case TCP: + addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", c.ip, c.port)) + if err != nil { + return err + } + conn, err := net.DialTCP("tcp", c.laddrTCP, addr) + if err != nil { + return err + } + defer conn.Close() + + if _, err = conn.Write(data); err != nil { + return err + } } + return nil } From 750795b638adc906f69eb82b1191eebfa7206e99 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sun, 24 Nov 2019 21:55:58 -0500 Subject: [PATCH 13/42] document use of TCP --- osc/doc.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osc/doc.go b/osc/doc.go index 9ebbf93..a43282c 100644 --- a/osc/doc.go +++ b/osc/doc.go @@ -21,8 +21,8 @@ Features: - Support for OSC address pattern including '*', '?', '{,}' and '[]' wildcards - TODO: Describe registering methods -This OSC implementation uses the UDP protocol for sending and receiving -OSC packets. +This OSC implementation supports using UDP or TCP as the protocol for sending +and receiving OSC packets. UDP is used by default. The unit of transmission of OSC is an OSC Packet. Any application that sends OSC Packets is an OSC Client; any application that receives OSC Packets is @@ -59,6 +59,8 @@ Usage OSC client example: client := osc.NewClient("localhost", 8765) + // To use TCP instead of UDP: + // client.SetNetworkProtocol(osc.TCP) msg := osc.NewMessage("/osc/address") msg.Append(int32(111)) msg.Append(true) @@ -68,7 +70,9 @@ OSC client example: OSC server example: addr := "127.0.0.1:8765" - server := &osc.Server{Addr: addr} + server := osc.Server{Addr: addr} + // To use TCP instead of UDP: + // server.SetNetworkProtocol(osc.TCP) server.Handle("/message/address", func(msg *osc.Message) { osc.PrintMessage(msg) From 1e1356a8bd91d77bca4690b5ee1445823d523387 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sun, 24 Nov 2019 21:57:04 -0500 Subject: [PATCH 14/42] mention TCP support in README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c5a90e6..6bafaea 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ ## Features +* UDP (default) or TCP * OSC Bundles, including timetags * OSC Messages * OSC Client @@ -70,4 +71,4 @@ func main() { ``` go test -``` \ No newline at end of file +``` From e255750c80d74496d63a525e188bb72cb5754dbb Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Mon, 25 Nov 2019 11:17:31 -0500 Subject: [PATCH 15/42] use `port` instead of hard-coded 6677 in one place I missed --- osc/osc_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osc/osc_test.go b/osc/osc_test.go index 13f4be5..c20fd30 100644 --- a/osc/osc_test.go +++ b/osc/osc_test.go @@ -257,7 +257,7 @@ func testServerMessageReceiving( select { case <-timeout: case <-start: - client := NewClient("localhost", 6677) + client := NewClient("localhost", port) client.SetNetworkProtocol(protocol) msg := NewMessage("/address/test") From 760ff36301a5728a2a6ae9340a63c5c37519c17a Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Mon, 25 Nov 2019 11:21:51 -0500 Subject: [PATCH 16/42] refactor TestServerMessageDispatching in order to test TCP; handle errors from server.Serve --- osc/osc_test.go | 75 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/osc/osc_test.go b/osc/osc_test.go index c20fd30..48971df 100644 --- a/osc/osc_test.go +++ b/osc/osc_test.go @@ -3,10 +3,12 @@ package osc import ( "bufio" "bytes" + "fmt" "math/rand" "net" "reflect" "strconv" + "strings" "sync" "testing" "time" @@ -113,22 +115,63 @@ func TestHandleWithInvalidAddress(t *testing.T) { } } -func TestServerMessageDispatching(t *testing.T) { +func testServerMessageDispatching(t *testing.T, protocol NetworkProtocol) { finish := make(chan bool) start := make(chan bool) done := sync.WaitGroup{} done.Add(2) + port := 6677 + addr := "localhost:" + strconv.Itoa(port) + + var startServer func() error + stopServer := func() {} + defer stopServer() + // Start the OSC server in a new go-routine go func() { - conn, err := net.ListenPacket("udp", "localhost:6677") - if err != nil { - t.Fatal(err) + server := &Server{Addr: addr} + server.SetNetworkProtocol(protocol) + + switch protocol { + case UDP: + startServer = func() error { + c, err := net.ListenPacket("udp", addr) + if err != nil { + t.Fatal(err) + } + + stopServer = func() { c.Close() } + + // We could ideally just do this: + // + // return server.Serve(conn) + // + // ...but closing the connection causes a "use of closed network + // connection" error the next time we try to read from the connection. + // + // Open question: is this desired behavior, or should server.Serve + // return successfully in cases where it would otherwise throw this + // error? + if err := server.Serve(c); err != nil && + !strings.Contains(err.Error(), "use of closed network connection") { + return err + } + + return nil + } + case TCP: + startServer = func() error { + return fmt.Errorf("TODO: implement") + } } - defer conn.Close() - server := &Server{Addr: "localhost:6677"} - err = server.Handle("/address/test", func(msg *Message) { + if err := server.Handle("/address/test", func(msg *Message) { + defer func() { + stopServer() + finish <- true + }() + if len(msg.Arguments) != 1 { t.Error("Argument length should be 1 and is: " + string(len(msg.Arguments))) } @@ -136,17 +179,14 @@ func TestServerMessageDispatching(t *testing.T) { if msg.Arguments[0].(int32) != 1122 { t.Error("Argument should be 1122 and is: " + string(msg.Arguments[0].(int32))) } - - // Stop OSC server - conn.Close() - finish <- true - }) - if err != nil { + }); err != nil { t.Error("Error adding message handler") } start <- true - server.Serve(conn) + if err := startServer(); err != nil { + t.Errorf("error during Serve: %s", err.Error()) + } }() go func() { @@ -155,7 +195,8 @@ func TestServerMessageDispatching(t *testing.T) { case <-timeout: case <-start: time.Sleep(500 * time.Millisecond) - client := NewClient("localhost", 6677) + client := NewClient("localhost", port) + client.SetNetworkProtocol(protocol) msg := NewMessage("/address/test") msg.Append(int32(1122)) client.Send(msg) @@ -173,6 +214,10 @@ func TestServerMessageDispatching(t *testing.T) { done.Wait() } +func TestServerMessageDispatchingUDP(t *testing.T) { + testServerMessageDispatching(t, UDP) +} + func testServerMessageReceiving( t *testing.T, protocol NetworkProtocol, stringArgument string, ) { From d0cdbedc219ac85303cade9eb02f518bf9436e97 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Mon, 25 Nov 2019 11:39:39 -0500 Subject: [PATCH 17/42] tests should propagate errors immediately instead of logging them and attempting to continue --- osc/osc_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osc/osc_test.go b/osc/osc_test.go index 48971df..2cd6e64 100644 --- a/osc/osc_test.go +++ b/osc/osc_test.go @@ -138,7 +138,7 @@ func testServerMessageDispatching(t *testing.T, protocol NetworkProtocol) { startServer = func() error { c, err := net.ListenPacket("udp", addr) if err != nil { - t.Fatal(err) + return err } stopServer = func() { c.Close() } @@ -239,7 +239,7 @@ func testServerMessageReceiving( receivePacket = func() (Packet, error) { c, err := net.ListenPacket("udp", "localhost:"+strconv.Itoa(port)) if err != nil { - t.Fatal(err) + return nil, err } defer c.Close() @@ -249,7 +249,7 @@ func testServerMessageReceiving( receivePacket = func() (Packet, error) { l, err := net.Listen("tcp", ":"+strconv.Itoa(port)) if err != nil { - t.Fatal(err) + return nil, err } defer l.Close() From baf2e0bf33c8bfaf7aa7f4e39470924d624d612e Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Mon, 25 Nov 2019 11:52:24 -0500 Subject: [PATCH 18/42] implement server.ServeTCP; augment & refactor tests a bit --- osc/osc.go | 19 +++++++++++++---- osc/osc_test.go | 54 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 57 insertions(+), 16 deletions(-) diff --git a/osc/osc.go b/osc/osc.go index 540f6bb..8420e9d 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -634,12 +634,10 @@ func (s *Server) ListenAndServe() error { return s.Serve(ln) } -// Serve retrieves incoming OSC packets from the given connection and dispatches -// retrieved OSC packets. If something goes wrong an error is returned. -func (s *Server) Serve(c net.PacketConn) error { +func (s *Server) serve(readPacket func() (Packet, error)) error { var tempDelay time.Duration for { - msg, err := s.readFromConnection(c) + msg, err := readPacket() if err != nil { if ne, ok := err.(net.Error); ok && ne.Temporary() { if tempDelay == 0 { @@ -660,6 +658,19 @@ func (s *Server) Serve(c net.PacketConn) error { } } +// Serve retrieves incoming OSC packets from the given connection and dispatches +// retrieved OSC packets. If something goes wrong an error is returned. +func (s *Server) Serve(c net.PacketConn) error { + return s.serve(func() (Packet, error) { return s.readFromConnection(c) }) +} + +// ServeTCP retrieves incoming OSC packets from the given connection and +// dispatches retrieved OSC packets. If something goes wrong an error is +// returned. +func (s *Server) ServeTCP(l net.Listener) error { + return s.serve(func() (Packet, error) { return s.ReceiveTCPPacket(l) }) +} + // ReceivePacket listens for incoming OSC packets and returns the packet if one is received. func (s *Server) ReceivePacket(c net.PacketConn) (Packet, error) { return s.readFromConnection(c) diff --git a/osc/osc_test.go b/osc/osc_test.go index 2cd6e64..028e943 100644 --- a/osc/osc_test.go +++ b/osc/osc_test.go @@ -3,7 +3,6 @@ package osc import ( "bufio" "bytes" - "fmt" "math/rand" "net" "reflect" @@ -115,6 +114,26 @@ func TestHandleWithInvalidAddress(t *testing.T) { } } +// These tests use the implementation-level functions server.Serve / +// server.ServeTCP, instead of the API-level server.ListenAndServe. +// +// server.Serve and server.ServeTCP take as arguments things that need to be +// closed (net.PacketConn, net.Listener), and the tests stop the server by +// abruptly closing them. This causes a "use of closed network connection" error +// the next time we try to read from the connection. +// +// Open question: is this desired behavior, or should server.Serve and +// server.ServeTCP return successfully in cases where they would otherwise throw +// this error? +func serveUntilInterrupted(serve func() error) error { + if err := serve(); err != nil && + !strings.Contains(err.Error(), "use of closed network connection") { + return err + } + + return nil +} + func testServerMessageDispatching(t *testing.T, protocol NetworkProtocol) { finish := make(chan bool) start := make(chan bool) @@ -144,17 +163,10 @@ func testServerMessageDispatching(t *testing.T, protocol NetworkProtocol) { stopServer = func() { c.Close() } // We could ideally just do this: - // // return server.Serve(conn) - // - // ...but closing the connection causes a "use of closed network - // connection" error the next time we try to read from the connection. - // - // Open question: is this desired behavior, or should server.Serve - // return successfully in cases where it would otherwise throw this - // error? - if err := server.Serve(c); err != nil && - !strings.Contains(err.Error(), "use of closed network connection") { + if err := serveUntilInterrupted( + func() error { return server.Serve(c) }, + ); err != nil { return err } @@ -162,7 +174,21 @@ func testServerMessageDispatching(t *testing.T, protocol NetworkProtocol) { } case TCP: startServer = func() error { - return fmt.Errorf("TODO: implement") + l, err := net.Listen("tcp", ":"+strconv.Itoa(port)) + if err != nil { + return err + } + stopServer = func() { l.Close() } + + // We could ideally just do this: + // return server.ServeTCP(l) + if err := serveUntilInterrupted( + func() error { return server.ServeTCP(l) }, + ); err != nil { + return err + } + + return nil } } @@ -218,6 +244,10 @@ func TestServerMessageDispatchingUDP(t *testing.T) { testServerMessageDispatching(t, UDP) } +func TestServerMessageDispatchingTCP(t *testing.T) { + testServerMessageDispatching(t, TCP) +} + func testServerMessageReceiving( t *testing.T, protocol NetworkProtocol, stringArgument string, ) { From 0589c1e3f31c799fea8989389f829ed9e0b9a6d3 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Mon, 25 Nov 2019 12:45:37 -0500 Subject: [PATCH 19/42] add mechanism for closing server connection; refactor test code to use server.ListenAndServe and server.CloseConnection --- osc/osc.go | 17 ++++++++ osc/osc_test.go | 102 ++++++++++++++---------------------------------- 2 files changed, 46 insertions(+), 73 deletions(-) diff --git a/osc/osc.go b/osc/osc.go index 8420e9d..0257d52 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -78,6 +78,8 @@ type Server struct { Dispatcher *OscDispatcher ReadTimeout time.Duration networkProtocol NetworkProtocol + udpConnection net.PacketConn + tcpListener net.Listener } // Timetag represents an OSC Time Tag. @@ -661,6 +663,7 @@ func (s *Server) serve(readPacket func() (Packet, error)) error { // Serve retrieves incoming OSC packets from the given connection and dispatches // retrieved OSC packets. If something goes wrong an error is returned. func (s *Server) Serve(c net.PacketConn) error { + s.udpConnection = c return s.serve(func() (Packet, error) { return s.readFromConnection(c) }) } @@ -668,9 +671,23 @@ func (s *Server) Serve(c net.PacketConn) error { // dispatches retrieved OSC packets. If something goes wrong an error is // returned. func (s *Server) ServeTCP(l net.Listener) error { + s.tcpListener = l return s.serve(func() (Packet, error) { return s.ReceiveTCPPacket(l) }) } +// CloseConnection forcibly closes a server's connection. +// +// This causes a "use of closed network connection" error the next time the +// server attempts to read from the connection. +func (s *Server) CloseConnection() { + switch s.networkProtocol { + case UDP: + s.udpConnection.Close() + case TCP: + s.tcpListener.Close() + } +} + // ReceivePacket listens for incoming OSC packets and returns the packet if one is received. func (s *Server) ReceivePacket(c net.PacketConn) (Packet, error) { return s.readFromConnection(c) diff --git a/osc/osc_test.go b/osc/osc_test.go index 028e943..232942d 100644 --- a/osc/osc_test.go +++ b/osc/osc_test.go @@ -114,19 +114,16 @@ func TestHandleWithInvalidAddress(t *testing.T) { } } -// These tests use the implementation-level functions server.Serve / -// server.ServeTCP, instead of the API-level server.ListenAndServe. +// These tests stop the server by forcibly closing the connection, which causes +// a "use of closed network connection" error the next time we try to read from +// the connection. As a workaround, this wraps server.ListenAndServe() in an +// error-handling layer that doesn't consider "use of closed network connection" +// an error. // -// server.Serve and server.ServeTCP take as arguments things that need to be -// closed (net.PacketConn, net.Listener), and the tests stop the server by -// abruptly closing them. This causes a "use of closed network connection" error -// the next time we try to read from the connection. -// -// Open question: is this desired behavior, or should server.Serve and -// server.ServeTCP return successfully in cases where they would otherwise throw -// this error? -func serveUntilInterrupted(serve func() error) error { - if err := serve(); err != nil && +// Open question: is this desired behavior, or should server.serve return +// successfully in cases where it would otherwise throw this error? +func serveUntilInterrupted(server *Server) error { + if err := server.ListenAndServe(); err != nil && !strings.Contains(err.Error(), "use of closed network connection") { return err } @@ -143,78 +140,37 @@ func testServerMessageDispatching(t *testing.T, protocol NetworkProtocol) { port := 6677 addr := "localhost:" + strconv.Itoa(port) - var startServer func() error - stopServer := func() {} - defer stopServer() - - // Start the OSC server in a new go-routine - go func() { - server := &Server{Addr: addr} - server.SetNetworkProtocol(protocol) - - switch protocol { - case UDP: - startServer = func() error { - c, err := net.ListenPacket("udp", addr) - if err != nil { - return err - } - - stopServer = func() { c.Close() } + server := &Server{Addr: addr} + server.SetNetworkProtocol(protocol) + defer server.CloseConnection() - // We could ideally just do this: - // return server.Serve(conn) - if err := serveUntilInterrupted( - func() error { return server.Serve(c) }, - ); err != nil { - return err - } - - return nil - } - case TCP: - startServer = func() error { - l, err := net.Listen("tcp", ":"+strconv.Itoa(port)) - if err != nil { - return err - } - stopServer = func() { l.Close() } - - // We could ideally just do this: - // return server.ServeTCP(l) - if err := serveUntilInterrupted( - func() error { return server.ServeTCP(l) }, - ); err != nil { - return err - } + if err := server.Handle("/address/test", func(msg *Message) { + defer func() { + server.CloseConnection() + finish <- true + }() - return nil - } + if len(msg.Arguments) != 1 { + t.Error("Argument length should be 1 and is: " + string(len(msg.Arguments))) } - if err := server.Handle("/address/test", func(msg *Message) { - defer func() { - stopServer() - finish <- true - }() - - if len(msg.Arguments) != 1 { - t.Error("Argument length should be 1 and is: " + string(len(msg.Arguments))) - } - - if msg.Arguments[0].(int32) != 1122 { - t.Error("Argument should be 1122 and is: " + string(msg.Arguments[0].(int32))) - } - }); err != nil { - t.Error("Error adding message handler") + if msg.Arguments[0].(int32) != 1122 { + t.Error("Argument should be 1122 and is: " + string(msg.Arguments[0].(int32))) } + }); err != nil { + t.Error("Error adding message handler") + } + // Server goroutine + go func() { start <- true - if err := startServer(); err != nil { + + if err := serveUntilInterrupted(server); err != nil { t.Errorf("error during Serve: %s", err.Error()) } }() + // Client goroutine go func() { timeout := time.After(5 * time.Second) select { From 1e0bc1335a1741b2791ee7bc7a952ef0c4e6789f Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Mon, 25 Nov 2019 12:50:12 -0500 Subject: [PATCH 20/42] ensure that ListenAndServe closes its connection when done --- osc/osc.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osc/osc.go b/osc/osc.go index 0257d52..a92250d 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -622,9 +622,13 @@ func (s *Server) Handle(addr string, handler HandlerFunc) error { return s.Dispatcher.AddMsgHandler(addr, handler) } -// ListenAndServe retrieves incoming OSC packets and dispatches the retrieved -// OSC packets. +// ListenAndServe opens a connection, retrieves incoming OSC packets and +// dispatches the retrieved OSC packets. +// +// The connection is closed in the event of an error or interruption. func (s *Server) ListenAndServe() error { + defer s.CloseConnection() + if s.Dispatcher == nil { s.Dispatcher = NewOscDispatcher() } From a9950d0d7d87b757d1b3021185f5d03161d4afcc Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Mon, 25 Nov 2019 12:52:56 -0500 Subject: [PATCH 21/42] make CloseConnection idempotent / nil-safe --- osc/osc.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osc/osc.go b/osc/osc.go index a92250d..99b113e 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -686,9 +686,13 @@ func (s *Server) ServeTCP(l net.Listener) error { func (s *Server) CloseConnection() { switch s.networkProtocol { case UDP: - s.udpConnection.Close() + if s.udpConnection != nil { + s.udpConnection.Close() + } case TCP: - s.tcpListener.Close() + if s.tcpListener != nil { + s.tcpListener.Close() + } } } From 2bd0f80b73f87af79cd07556205519487d1a9e66 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Mon, 25 Nov 2019 12:57:33 -0500 Subject: [PATCH 22/42] implement TCP in ListenAndServe --- osc/osc.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/osc/osc.go b/osc/osc.go index 99b113e..c716c06 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -633,11 +633,24 @@ func (s *Server) ListenAndServe() error { s.Dispatcher = NewOscDispatcher() } - ln, err := net.ListenPacket("udp", s.Addr) - if err != nil { - return err + switch s.networkProtocol { + case UDP: + ln, err := net.ListenPacket("udp", s.Addr) + if err != nil { + return err + } + + return s.Serve(ln) + case TCP: + l, err := net.Listen("tcp", s.Addr) + if err != nil { + return err + } + + return s.ServeTCP(l) + default: + return fmt.Errorf("unsupported network protocol: %v", s.networkProtocol) } - return s.Serve(ln) } func (s *Server) serve(readPacket func() (Packet, error)) error { From 7e381978ce4a428a59c04c7a9d0635aa81c4ec00 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Mon, 25 Nov 2019 13:09:43 -0500 Subject: [PATCH 23/42] use a 1,000,000 byte string for the TCP test --- osc/osc_test.go | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/osc/osc_test.go b/osc/osc_test.go index 232942d..759a0e0 100644 --- a/osc/osc_test.go +++ b/osc/osc_test.go @@ -131,7 +131,9 @@ func serveUntilInterrupted(server *Server) error { return nil } -func testServerMessageDispatching(t *testing.T, protocol NetworkProtocol) { +func testServerMessageDispatching( + t *testing.T, protocol NetworkProtocol, stringArgument string, +) { finish := make(chan bool) start := make(chan bool) done := sync.WaitGroup{} @@ -150,13 +152,26 @@ func testServerMessageDispatching(t *testing.T, protocol NetworkProtocol) { finish <- true }() - if len(msg.Arguments) != 1 { - t.Error("Argument length should be 1 and is: " + string(len(msg.Arguments))) + if len(msg.Arguments) != 2 { + t.Error("Argument length should be 2 and is: " + string(len(msg.Arguments))) } if msg.Arguments[0].(int32) != 1122 { t.Error("Argument should be 1122 and is: " + string(msg.Arguments[0].(int32))) } + + receivedString := msg.Arguments[1].(string) + if len(receivedString) != len(stringArgument) { + t.Errorf( + "String argument length should be %d and is %d", + len(stringArgument), + len(receivedString), + ) + } else if receivedString != stringArgument { + t.Errorf( + "Argument should be %s and is: %s", stringArgument, receivedString, + ) + } }); err != nil { t.Error("Error adding message handler") } @@ -181,7 +196,13 @@ func testServerMessageDispatching(t *testing.T, protocol NetworkProtocol) { client.SetNetworkProtocol(protocol) msg := NewMessage("/address/test") msg.Append(int32(1122)) - client.Send(msg) + msg.Append(stringArgument) + if err := client.Send(msg); err != nil { + t.Error(err) + done.Done() + done.Done() + return + } } done.Done() @@ -197,11 +218,21 @@ func testServerMessageDispatching(t *testing.T, protocol NetworkProtocol) { } func TestServerMessageDispatchingUDP(t *testing.T) { - testServerMessageDispatching(t, UDP) + // Attempting to send a message larger than this (the exact threshold depends + // on your OS, etc.) results in an error like: + // + // write udp 127.0.0.1:52496->127.0.0.1:6677: write: message too long + // + // This is expected for UDP. The practical guaranteed packet size limit for + // UDP is 576 bytes, and some of that is taken up by protocol overhead. + // + // Reference: + // https://forum.juce.com/t/osc-blobs-are-lost-above-certain-size/20241/2 + testServerMessageDispatching(t, UDP, randomString(500)) } func TestServerMessageDispatchingTCP(t *testing.T) { - testServerMessageDispatching(t, TCP) + testServerMessageDispatching(t, TCP, randomString(1000000)) } func testServerMessageReceiving( From f18edda5b0a85b9053fcbd9785c1f5f437387f7a Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sat, 11 Jan 2020 20:47:54 -0500 Subject: [PATCH 24/42] adjust examples/client/client.go to generate random packets with nested bundles and args of various types --- examples/client/client.go | 125 ++++++++++++++++++++++++++++++-------- 1 file changed, 101 insertions(+), 24 deletions(-) diff --git a/examples/client/client.go b/examples/client/client.go index 75d7a33..b628350 100644 --- a/examples/client/client.go +++ b/examples/client/client.go @@ -4,18 +4,110 @@ import ( "bufio" "fmt" "io" + "math/rand" "os" + "strconv" "strings" "time" "github.com/hypebeast/go-osc/osc" ) -// TODO: Revise the client! +func testString() string { + return "test string " + strconv.Itoa(rand.Intn(1000)) +} + +func testBlob() []byte { + return []byte(testString()) +} + +var argFunctions = []func(*osc.Message){ + func(msg *osc.Message) { + msg.Append(int32(rand.Intn(100000))) + }, + func(msg *osc.Message) { + msg.Append(rand.Float32() * 100000) + }, + func(msg *osc.Message) { + msg.Append(testString()) + }, + func(msg *osc.Message) { + msg.Append(testBlob()) + }, + func(msg *osc.Message) { + msg.Append(*osc.NewTimetag(time.Now())) + }, + func(msg *osc.Message) { + msg.Append(true) + }, + func(msg *osc.Message) { + msg.Append(false) + }, + func(msg *osc.Message) { + msg.Append(nil) + }, +} + +func randomMessage(address string) *osc.Message { + message := osc.NewMessage(address) + + for i := 0; i < 1+rand.Intn(5); i++ { + argFunctions[rand.Intn(len(argFunctions))](message) + } + + return message +} + +func randomBundle() *osc.Bundle { + bundle := osc.NewBundle(time.Now()) + + for i := 0; i < 1+rand.Intn(5); i++ { + if rand.Float32() < 0.25 { + bundle.Append(randomBundle()) + } else { + bundle.Append(randomMessage("/bundle/message/" + strconv.Itoa(i+1))) + } + } + + return bundle +} + +func printUsage() { + fmt.Printf("Usage: %s PROTOCOL PORT\n", os.Args[0]) +} + func main() { + rand.Seed(time.Now().Unix()) + + numArgs := len(os.Args[1:]) + + if numArgs != 2 { + printUsage() + os.Exit(1) + } + + var protocol osc.NetworkProtocol + switch strings.ToLower(os.Args[1]) { + case "udp": + protocol = osc.UDP + case "tcp": + protocol = osc.TCP + default: + fmt.Println("Invalid protocol: " + os.Args[1]) + printUsage() + os.Exit(1) + } + + port, err := strconv.ParseInt(os.Args[2], 10, 32) + if err != nil { + fmt.Println(err) + printUsage() + os.Exit(1) + } + ip := "localhost" - port := 8765 - client := osc.NewClient(ip, port) + client := osc.NewClient(ip, int(port)) + client.SetNetworkProtocol(protocol) fmt.Println("### Welcome to go-osc transmitter demo") fmt.Println("Please, select the OSC packet type you would like to send:") @@ -38,28 +130,13 @@ func main() { sline := strings.TrimRight(string(line), "\n") if sline == "m" { - message := osc.NewMessage("/message/address") - message.Append(int32(12345)) - message.Append("teststring") - message.Append(true) - message.Append(false) - client.Send(message) + if err := client.Send(randomMessage("/message/address")); err != nil { + fmt.Println(err) + } } else if sline == "b" { - bundle := osc.NewBundle(time.Now()) - message1 := osc.NewMessage("/bundle/message/1") - message1.Append(int32(12345)) - message1.Append("teststring") - message1.Append(true) - message1.Append(false) - message2 := osc.NewMessage("/bundle/message/2") - message2.Append(int32(3344)) - message2.Append(float32(101.9)) - message2.Append("string1") - message2.Append("string2") - message2.Append(true) - bundle.Append(message1) - bundle.Append(message2) - client.Send(bundle) + if err := client.Send(randomBundle()); err != nil { + fmt.Println(err) + } } else if sline == "q" { fmt.Println("Exit!") os.Exit(0) From 8973d7b174208c7f9975d0f13b5e1130e6933a63 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sun, 12 Jan 2020 08:04:27 -0500 Subject: [PATCH 25/42] fix typo --- examples/basic_server/basic_server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/basic_server/basic_server.go b/examples/basic_server/basic_server.go index 93aad96..a04851c 100644 --- a/examples/basic_server/basic_server.go +++ b/examples/basic_server/basic_server.go @@ -34,7 +34,7 @@ func main() { if packet != nil { switch packet.(type) { default: - fmt.Println("Unknow packet type!") + fmt.Println("Unknown packet type!") case *osc.Message: fmt.Printf("-- OSC Message: ") From 90bad456cea898efc372f59612691bace03f2061 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sun, 12 Jan 2020 08:44:20 -0500 Subject: [PATCH 26/42] adjust dispatching_server to allow setting the network protocol --- .../dispatching_server/dispatching_server.go | 65 ++++++++++++++++--- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/examples/dispatching_server/dispatching_server.go b/examples/dispatching_server/dispatching_server.go index c4d199e..3660982 100644 --- a/examples/dispatching_server/dispatching_server.go +++ b/examples/dispatching_server/dispatching_server.go @@ -1,18 +1,67 @@ package main -import "github.com/hypebeast/go-osc/osc" +import ( + "fmt" + "math/rand" + "os" + "strconv" + "strings" + "time" + + "github.com/hypebeast/go-osc/osc" +) + +func printUsage() { + fmt.Printf("Usage: %s PROTOCOL PORT\n", os.Args[0]) +} func main() { - addr := "127.0.0.1:8765" + rand.Seed(time.Now().Unix()) + + numArgs := len(os.Args[1:]) + + if numArgs != 2 { + printUsage() + os.Exit(1) + } + + var protocol osc.NetworkProtocol + switch strings.ToLower(os.Args[1]) { + case "udp": + protocol = osc.UDP + case "tcp": + protocol = osc.TCP + default: + fmt.Println("Invalid protocol: " + os.Args[1]) + printUsage() + os.Exit(1) + } + + port, err := strconv.ParseInt(os.Args[2], 10, 32) + if err != nil { + fmt.Println(err) + printUsage() + os.Exit(1) + } + + addr := fmt.Sprintf("127.0.0.1:%d", port) d := osc.NewStandardDispatcher() - d.AddMsgHandler("/message/address", func(msg *osc.Message) { + + if err := d.AddMsgHandler("/message/address", func(msg *osc.Message) { osc.PrintMessage(msg) - }) - server := &osc.Server{ - Addr: addr, - Dispatcher: d, + }); err != nil { + fmt.Println(err) + os.Exit(1) } - server.ListenAndServe() + server := osc.NewServer(addr, d, 0) + server.SetNetworkProtocol(protocol) + + fmt.Printf("Listening via %s on port %d...\n", protocol, port) + + if err := server.ListenAndServe(); err != nil { + fmt.Println(err) + os.Exit(1) + } } From 9bd7cb25ad558a3a9912ba2ff2a101aa30de6c2e Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sun, 12 Jan 2020 08:45:25 -0500 Subject: [PATCH 27/42] implement String() on NetworkProtocol --- osc/osc.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osc/osc.go b/osc/osc.go index 2522474..0c06b0d 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -13,6 +13,7 @@ import ( "net" "reflect" "regexp" + "strconv" "strings" "time" ) @@ -33,6 +34,17 @@ const ( TCP ) +func (protocol NetworkProtocol) String() string { + switch protocol { + case UDP: + return "UDP" + case TCP: + return "TCP" + default: + return strconv.Itoa(int(protocol)) + } +} + // Packet is the interface for Message and Bundle. type Packet interface { encoding.BinaryMarshaler From 7d349fabf79374f84b6f73f4011c5e78045c613e Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sun, 12 Jan 2020 08:46:23 -0500 Subject: [PATCH 28/42] support reading 'N' (null) arguments --- osc/osc.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osc/osc.go b/osc/osc.go index 0c06b0d..c907067 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -951,6 +951,9 @@ func readArguments(msg *Message, reader *bufio.Reader, start *int) error { case 'F': // false msg.Append(false) + + case 'N': // null + msg.Append(nil) } } From 64360b368cc1853a7667457df34c57e018f55f73 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sun, 12 Jan 2020 08:48:00 -0500 Subject: [PATCH 29/42] fix references to Timetag that should be *Timetag When Timetag arguments are read from incoming messages, they are parsed as *Timetag, so these references to Timetag needed to be adjusted accordingly. --- osc/osc.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osc/osc.go b/osc/osc.go index c907067..86b5861 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -294,9 +294,9 @@ func (msg *Message) String() string { formatString += " %s" args = append(args, "blob") - case Timetag: + case *Timetag: formatString += " %d" - timeTag := arg.(Timetag) + timeTag := arg.(*Timetag) args = append(args, timeTag.TimeTag()) } } @@ -1233,7 +1233,7 @@ func getTypeTag(arg interface{}) (string, error) { return "h", nil case float64: return "d", nil - case Timetag: + case *Timetag: return "t", nil default: return "", fmt.Errorf("Unsupported type: %T", t) From 87688fa1647432cdd1e6d4e8c0e65f5a9576b8ff Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sun, 12 Jan 2020 08:54:10 -0500 Subject: [PATCH 30/42] basic_server updates: support setting protocol; print nested packets example output: -- OSC Bundle (2020-01-12 08:42:13.430108205 -0500 EST): -- OSC Message #1: /bundle/message/1 ,Tsb true test string 956 blob -- OSC Message #2: /bundle/message/3 ,Ni Nil 70030 -- OSC Bundle (2020-01-12 08:42:13.430141439 -0500 EST): -- OSC Message #1: /bundle/message/2 ,s test string 353 -- OSC Message #2: /bundle/message/3 ,iTb 24665 true blob -- OSC Bundle (2020-01-12 08:42:13.430142487 -0500 EST): -- OSC Message #1: /bundle/message/2 ,FN false Nil -- OSC Bundle (2020-01-12 08:42:13.430143456 -0500 EST): -- OSC Bundle (2020-01-12 08:42:13.430144334 -0500 EST): -- OSC Message #1: /bundle/message/1 ,si test string 370 18937 -- OSC Message #2: /bundle/message/2 ,Ts true test string 275 -- OSC Bundle (2020-01-12 08:42:13.430158528 -0500 EST): -- OSC Message #1: /bundle/message/1 ,TiT true 11536 true -- OSC Bundle (2020-01-12 08:42:13.430168011 -0500 EST): -- OSC Message #1: /bundle/message/1 ,iF 53026 false -- OSC Message #2: /bundle/message/2 ,bi blob 91772 -- OSC Message #3: /bundle/message/3 ,N Nil -- OSC Message #4: /bundle/message/4 ,T true --- examples/basic_server/basic_server.go | 145 +++++++++++++++++--------- 1 file changed, 96 insertions(+), 49 deletions(-) diff --git a/examples/basic_server/basic_server.go b/examples/basic_server/basic_server.go index a04851c..931d3b1 100644 --- a/examples/basic_server/basic_server.go +++ b/examples/basic_server/basic_server.go @@ -1,67 +1,114 @@ package main import ( - "bufio" "fmt" - "net" + "math/rand" "os" + "strconv" + "strings" + "time" "github.com/hypebeast/go-osc/osc" ) -func main() { - addr := "127.0.0.1:8765" - server := &osc.Server{} - conn, err := net.ListenPacket("udp", addr) - if err != nil { - fmt.Println("Couldn't listen: ", err) - } - defer conn.Close() +func indent(str string, indentLevel int) string { + indentation := strings.Repeat(" ", indentLevel) - fmt.Println("### Welcome to go-osc receiver demo") - fmt.Println("Press \"q\" to exit") - - go func() { - fmt.Println("Start listening on", addr) - - for { - packet, err := server.ReceivePacket(conn) - if err != nil { - fmt.Println("Server error: " + err.Error()) - os.Exit(1) - } - - if packet != nil { - switch packet.(type) { - default: - fmt.Println("Unknown packet type!") - - case *osc.Message: - fmt.Printf("-- OSC Message: ") - osc.PrintMessage(packet.(*osc.Message)) - - case *osc.Bundle: - fmt.Println("-- OSC Bundle:") - bundle := packet.(*osc.Bundle) - for i, message := range bundle.Messages { - fmt.Printf(" -- OSC Message #%d: ", i+1) - osc.PrintMessage(message) - } - } - } + result := "" + + for i, line := range strings.Split(str, "\n") { + if i != 0 { + result += "\n" } - }() - reader := bufio.NewReader(os.Stdin) + result += indentation + line + } + + return result +} + +func debug(packet osc.Packet, indentLevel int) string { + switch packet.(type) { + default: + return "Unknown packet type!" - for { - c, err := reader.ReadByte() - if err != nil { - os.Exit(0) + case *osc.Message: + return fmt.Sprintf("-- OSC Message: %s", packet.(*osc.Message)) + + case *osc.Bundle: + bundle := packet.(*osc.Bundle) + + result := fmt.Sprintf("-- OSC Bundle (%s):", bundle.Timetag.Time()) + + for i, message := range bundle.Messages { + result += "\n" + indent( + fmt.Sprintf("-- OSC Message #%d: %s", i+1, message), + indentLevel+1, + ) } - if c == 'q' { - os.Exit(0) + for _, bundle := range bundle.Bundles { + result += "\n" + indent(debug(bundle, 0), indentLevel+1) } + + return result + } +} + +// Debugger is a simple Dispatcher that prints all messages and bundles as they +// are received. +type Debugger struct{} + +// Dispatch implements Dispatcher.Dispatch by printing the packet received. +func (Debugger) Dispatch(packet osc.Packet) { + if packet != nil { + fmt.Println(debug(packet, 0) + "\n") + } +} + +func printUsage() { + fmt.Printf("Usage: %s PROTOCOL PORT\n", os.Args[0]) +} + +func main() { + rand.Seed(time.Now().Unix()) + + numArgs := len(os.Args[1:]) + + if numArgs != 2 { + printUsage() + os.Exit(1) + } + + var protocol osc.NetworkProtocol + switch strings.ToLower(os.Args[1]) { + case "udp": + protocol = osc.UDP + case "tcp": + protocol = osc.TCP + default: + fmt.Println("Invalid protocol: " + os.Args[1]) + printUsage() + os.Exit(1) + } + + port, err := strconv.ParseInt(os.Args[2], 10, 32) + if err != nil { + fmt.Println(err) + printUsage() + os.Exit(1) + } + + addr := fmt.Sprintf("127.0.0.1:%d", port) + + server := osc.NewServer(addr, Debugger{}, 0) + server.SetNetworkProtocol(protocol) + + fmt.Println("### Welcome to go-osc receiver demo") + fmt.Printf("Listening via %s on port %d...\n", protocol, port) + + if err := server.ListenAndServe(); err != nil { + fmt.Println(err) + os.Exit(1) } } From b645a08e7eef7ad09e7cca53adf566a58fc6ff14 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Sun, 12 Jan 2020 21:16:07 -0500 Subject: [PATCH 31/42] bugfix: correctly handle padding 0-bytes at the end of a packet --- osc/osc.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osc/osc.go b/osc/osc.go index 86b5861..cdf9606 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -835,6 +835,10 @@ func readBundle(reader *bufio.Reader, start *int, end int) (*Bundle, error) { } *start += 4 + if length == 0 { + continue + } + p, err := readPacket(reader, start, end) if err != nil { return nil, err From 3d015df6f75e5a703a35f76b899d4bde6ca29175 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Thu, 16 Jan 2020 07:30:19 -0500 Subject: [PATCH 32/42] Revert "bugfix: correctly handle padding 0-bytes at the end of a packet" This reverts commit b645a08e7eef7ad09e7cca53adf566a58fc6ff14. It turns out that when I was testing with a JavaOSC client, there was a bug in the client causing additional 0 bytes to be added to the end. This is actually incorrect. What we had before was correct. --- osc/osc.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osc/osc.go b/osc/osc.go index cdf9606..86b5861 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -835,10 +835,6 @@ func readBundle(reader *bufio.Reader, start *int, end int) (*Bundle, error) { } *start += 4 - if length == 0 { - continue - } - p, err := readPacket(reader, start, end) if err != nil { return nil, err From c60efeeaa6e52e66b3722667eea0200acd0ba621 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Thu, 16 Jan 2020 22:58:20 -0500 Subject: [PATCH 33/42] fix inadvertent syntax error --- osc/osc.go | 1 + 1 file changed, 1 insertion(+) diff --git a/osc/osc.go b/osc/osc.go index 735812d..df2e612 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -954,6 +954,7 @@ func readArguments(msg *Message, reader *bufio.Reader, start *int) error { case 'F': // false msg.Append(false) + } } return nil From 3525c23a6ff20a17424677895d1a721147ec4f39 Mon Sep 17 00:00:00 2001 From: Glyn Owen Hanmer Date: Thu, 27 Feb 2020 21:29:24 +0300 Subject: [PATCH 34/42] Use functional options for protocol selection --- examples/basic_server/basic_server.go | 5 +- .../dispatching_server/dispatching_server.go | 4 +- osc/doc.go | 3 +- osc/osc.go | 102 +++++++++--------- osc/osc_test.go | 14 ++- 5 files changed, 64 insertions(+), 64 deletions(-) diff --git a/examples/basic_server/basic_server.go b/examples/basic_server/basic_server.go index 931d3b1..e01aba5 100644 --- a/examples/basic_server/basic_server.go +++ b/examples/basic_server/basic_server.go @@ -101,8 +101,9 @@ func main() { addr := fmt.Sprintf("127.0.0.1:%d", port) - server := osc.NewServer(addr, Debugger{}, 0) - server.SetNetworkProtocol(protocol) + server := osc.NewServer(addr, Debugger{}, 0, + // defaults to UDP if not used + osc.WithProtocol(protocol)) fmt.Println("### Welcome to go-osc receiver demo") fmt.Printf("Listening via %s on port %d...\n", protocol, port) diff --git a/examples/dispatching_server/dispatching_server.go b/examples/dispatching_server/dispatching_server.go index 3660982..6953381 100644 --- a/examples/dispatching_server/dispatching_server.go +++ b/examples/dispatching_server/dispatching_server.go @@ -55,8 +55,8 @@ func main() { os.Exit(1) } - server := osc.NewServer(addr, d, 0) - server.SetNetworkProtocol(protocol) + server := osc.NewServer(addr, d, 0, + osc.WithProtocol(protocol)) fmt.Printf("Listening via %s on port %d...\n", protocol, port) diff --git a/osc/doc.go b/osc/doc.go index 481a39a..e7537e0 100644 --- a/osc/doc.go +++ b/osc/doc.go @@ -80,8 +80,7 @@ OSC server example: } // To use TCP instead of UDP: - // server.SetNetworkProtocol(osc.TCP) - + // server.WithProtocol(osc.TCP) server.ListenAndServe() */ package osc diff --git a/osc/osc.go b/osc/osc.go index df2e612..4d0069a 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -90,8 +90,7 @@ type Server struct { Dispatcher Dispatcher ReadTimeout time.Duration networkProtocol NetworkProtocol - udpConnection net.PacketConn - tcpListener net.Listener + close func() error } // Timetag represents an OSC Time Tag. @@ -547,7 +546,7 @@ func (c *Client) NetworkProtocol() NetworkProtocol { return c.networkProtocol } -// SetNetworkProtocol sets the network protocol. +// WithProtocol sets the network protocol. func (c *Client) SetNetworkProtocol(protocol NetworkProtocol) { c.networkProtocol = protocol } @@ -597,20 +596,26 @@ func (c *Client) Send(packet Packet) error { // Server //// +type Option func(*Server) + // NewServer creates a new OSC server. The server receives OSC messages and // bundles over a network connection. // // The default network protocol is UDP. To use TCP instead, use -// server.SetNetworkProtocol(TCP). +// server.WithProtocol(TCP). func NewServer( addr string, dispatcher Dispatcher, readTimeout time.Duration, + opts ...Option, ) *Server { - return &Server{ - Addr: addr, - Dispatcher: dispatcher, - ReadTimeout: readTimeout, - networkProtocol: UDP, + srv := &Server{ + Addr: addr, + Dispatcher: dispatcher, + ReadTimeout: readTimeout, + } + for _, opt := range opts { + opt(srv) } + return srv } // NetworkProtocol returns the network protocol. @@ -618,9 +623,11 @@ func (s *Server) NetworkProtocol() NetworkProtocol { return s.networkProtocol } -// SetNetworkProtocol sets the network protocol. -func (s *Server) SetNetworkProtocol(protocol NetworkProtocol) { - s.networkProtocol = protocol +// WithProtocol sets the network protocol. +func WithProtocol(protocol NetworkProtocol) Option { + return func(server *Server) { + server.networkProtocol = protocol + } } // ListenAndServe opens a connection, retrieves incoming OSC packets and @@ -641,23 +648,25 @@ func (s *Server) ListenAndServe() error { return err } - return s.Serve(ln) + s.close = ln.Close + return s.Serve(UDPReceive(ln)) case TCP: l, err := net.Listen("tcp", s.Addr) if err != nil { return err } - return s.ServeTCP(l) + s.close = l.Close + return s.Serve(TCPReceive(l)) default: return fmt.Errorf("unsupported network protocol: %v", s.networkProtocol) } } -func (s *Server) serve(readPacket func() (Packet, error)) error { +func (s *Server) Serve(readPacket ReceiveFunc) error { var tempDelay time.Duration for { - msg, err := readPacket() + msg, err := readPacket(s.ReadTimeout) if err != nil { if ne, ok := err.(net.Error); ok && ne.Temporary() { if tempDelay == 0 { @@ -678,47 +687,34 @@ func (s *Server) serve(readPacket func() (Packet, error)) error { } } -// Serve retrieves incoming OSC packets from the given connection and dispatches -// retrieved OSC packets. If something goes wrong an error is returned. -func (s *Server) Serve(c net.PacketConn) error { - s.udpConnection = c - return s.serve(func() (Packet, error) { return s.readFromConnection(c) }) -} - -// ServeTCP retrieves incoming OSC packets from the given connection and -// dispatches retrieved OSC packets. If something goes wrong an error is -// returned. -func (s *Server) ServeTCP(l net.Listener) error { - s.tcpListener = l - return s.serve(func() (Packet, error) { return s.ReceiveTCPPacket(l) }) -} - // CloseConnection forcibly closes a server's connection. // // This causes a "use of closed network connection" error the next time the // server attempts to read from the connection. +// TODO(glynternet): return error here func (s *Server) CloseConnection() { - switch s.networkProtocol { - case UDP: - if s.udpConnection != nil { - s.udpConnection.Close() - } - case TCP: - if s.tcpListener != nil { - s.tcpListener.Close() - } + if s.close == nil { + return } + s.close() } // ReceivePacket listens for incoming OSC packets and returns the packet if one is received. -func (s *Server) ReceivePacket(c net.PacketConn) (Packet, error) { - return s.readFromConnection(c) +func (s *Server) ReceivePacket(read ReceiveFunc) (Packet, error) { + return read(s.ReadTimeout) +} + +type ReceiveFunc func(readTimeout time.Duration) (Packet, error) + +func UDPReceive(c net.PacketConn) ReceiveFunc { + return func(readTimeout time.Duration) (packet Packet, err error) { + return receivePacketUDP(readTimeout, c) + } } -// readFromConnection retrieves OSC packets. -func (s *Server) readFromConnection(c net.PacketConn) (Packet, error) { - if s.ReadTimeout != 0 { - if err := c.SetReadDeadline(time.Now().Add(s.ReadTimeout)); err != nil { +func receivePacketUDP(readTimeout time.Duration, c net.PacketConn) (Packet, error) { + if readTimeout != 0 { + if err := c.SetReadDeadline(time.Now().Add(readTimeout)); err != nil { return nil, err } } @@ -737,16 +733,22 @@ func (s *Server) readFromConnection(c net.PacketConn) (Packet, error) { return p, nil } -// ReceiveTCPPacket listens for incoming OSC packets and returns the packet if +func TCPReceive(l net.Listener) ReceiveFunc { + return func(readTimeout time.Duration) (packet Packet, err error) { + return receivePacketTCP(readTimeout, l) + } +} + +// receiveTCPPacket listens for incoming OSC packets and returns the packet if // one is received. -func (s *Server) ReceiveTCPPacket(l net.Listener) (Packet, error) { +func receivePacketTCP(readTimeout time.Duration, l net.Listener) (Packet, error) { conn, err := l.Accept() if err != nil { return nil, err } - if s.ReadTimeout != 0 { - if err := conn.SetReadDeadline(time.Now().Add(s.ReadTimeout)); err != nil { + if readTimeout != 0 { + if err := conn.SetReadDeadline(time.Now().Add(readTimeout)); err != nil { return nil, err } } diff --git a/osc/osc_test.go b/osc/osc_test.go index ffa0e1f..6e18278 100644 --- a/osc/osc_test.go +++ b/osc/osc_test.go @@ -142,8 +142,7 @@ func testServerMessageDispatching( port := 6677 addr := "localhost:" + strconv.Itoa(port) - server := &Server{Addr: addr, Dispatcher: NewStandardDispatcher()} - server.SetNetworkProtocol(protocol) + server := NewServer(addr, NewStandardDispatcher(), 0, WithProtocol(protocol)) defer server.CloseConnection() if err := server.Dispatcher.(*StandardDispatcher).AddMsgHandler( @@ -250,7 +249,6 @@ func testServerMessageReceiving( // Start the server in a go-routine go func() { server := &Server{} - server.SetNetworkProtocol(protocol) var receivePacket func() (Packet, error) switch protocol { @@ -262,7 +260,7 @@ func testServerMessageReceiving( } defer c.Close() - return server.ReceivePacket(c) + return server.ReceivePacket(UDPReceive(c)) } case TCP: receivePacket = func() (Packet, error) { @@ -272,7 +270,7 @@ func testServerMessageReceiving( } defer l.Close() - return server.ReceiveTCPPacket(l) + return server.ReceivePacket(TCPReceive(l)) } } @@ -417,7 +415,7 @@ func TestReadTimeout(t *testing.T) { defer c.Close() start <- true - p, err := server.ReceivePacket(c) + p, err := server.ReceivePacket(UDPReceive(c)) if err != nil { t.Errorf("server error: %v", err) return @@ -428,13 +426,13 @@ func TestReadTimeout(t *testing.T) { } // Second receive should time out since client is delayed 150 milliseconds - if _, err = server.ReceivePacket(c); err == nil { + if _, err = server.ReceivePacket(UDPReceive(c)); err == nil { t.Errorf("expected error") return } // Next receive should get it - p, err = server.ReceivePacket(c) + p, err = server.ReceivePacket(UDPReceive(c)) if err != nil { t.Errorf("server error: %v", err) return From 0d5fecc4ff0346fcc5dcc87f10caa62ff0aca075 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Fri, 28 Feb 2020 18:35:37 -0500 Subject: [PATCH 35/42] pass error through as the return value of s.CloseConnection --- osc/osc.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osc/osc.go b/osc/osc.go index 4d0069a..c3496b7 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -691,12 +691,12 @@ func (s *Server) Serve(readPacket ReceiveFunc) error { // // This causes a "use of closed network connection" error the next time the // server attempts to read from the connection. -// TODO(glynternet): return error here -func (s *Server) CloseConnection() { +func (s *Server) CloseConnection() error { if s.close == nil { - return + return nil } - s.close() + + return s.close() } // ReceivePacket listens for incoming OSC packets and returns the packet if one is received. From 7d5ed2eca6cf8102a803c3010e90eb687e7817e0 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Fri, 28 Feb 2020 18:40:45 -0500 Subject: [PATCH 36/42] rename Option => ServerOption in preparation for ClientOption --- osc/osc.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osc/osc.go b/osc/osc.go index c3496b7..0e25993 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -596,7 +596,8 @@ func (c *Client) Send(packet Packet) error { // Server //// -type Option func(*Server) +// ServerOption is a function that customizes a Server instance. +type ServerOption func(*Server) // NewServer creates a new OSC server. The server receives OSC messages and // bundles over a network connection. @@ -605,7 +606,7 @@ type Option func(*Server) // server.WithProtocol(TCP). func NewServer( addr string, dispatcher Dispatcher, readTimeout time.Duration, - opts ...Option, + opts ...ServerOption, ) *Server { srv := &Server{ Addr: addr, @@ -624,7 +625,7 @@ func (s *Server) NetworkProtocol() NetworkProtocol { } // WithProtocol sets the network protocol. -func WithProtocol(protocol NetworkProtocol) Option { +func WithProtocol(protocol NetworkProtocol) ServerOption { return func(server *Server) { server.networkProtocol = protocol } From ee682ad57b2d3e6ec7a95bf70b6e3b5c176781c9 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Fri, 28 Feb 2020 19:07:10 -0500 Subject: [PATCH 37/42] collapse private UDP/TCP receive packet functions into the implementation of the public functions; add doc comments --- osc/osc.go | 80 ++++++++++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/osc/osc.go b/osc/osc.go index 0e25993..9a3d719 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -705,67 +705,63 @@ func (s *Server) ReceivePacket(read ReceiveFunc) (Packet, error) { return read(s.ReadTimeout) } +// ReceiveFunc is a function that listens for incoming OSC packets and returns +// either the first packet received, or an error if something went awry. type ReceiveFunc func(readTimeout time.Duration) (Packet, error) +// UDPReceive returns a ReceiveFunc that uses the provided net.PacketConn to +// receive a packet over UDP. func UDPReceive(c net.PacketConn) ReceiveFunc { return func(readTimeout time.Duration) (packet Packet, err error) { - return receivePacketUDP(readTimeout, c) - } -} + if readTimeout != 0 { + if err := c.SetReadDeadline(time.Now().Add(readTimeout)); err != nil { + return nil, err + } + } -func receivePacketUDP(readTimeout time.Duration, c net.PacketConn) (Packet, error) { - if readTimeout != 0 { - if err := c.SetReadDeadline(time.Now().Add(readTimeout)); err != nil { + data := make([]byte, 65535) + n, _, err := c.ReadFrom(data) + if err != nil { return nil, err } - } - - data := make([]byte, 65535) - n, _, err := c.ReadFrom(data) - if err != nil { - return nil, err - } - var start int - p, err := readPacket(bufio.NewReader(bytes.NewBuffer(data)), &start, n) - if err != nil { - return nil, err + var start int + p, err := readPacket(bufio.NewReader(bytes.NewBuffer(data)), &start, n) + if err != nil { + return nil, err + } + return p, nil } - return p, nil } +// TCPReceive returns a ReceiveFunc that uses the provided net.Listener to +// receive a packet over TCP. func TCPReceive(l net.Listener) ReceiveFunc { return func(readTimeout time.Duration) (packet Packet, err error) { - return receivePacketTCP(readTimeout, l) - } -} + conn, err := l.Accept() + if err != nil { + return nil, err + } -// receiveTCPPacket listens for incoming OSC packets and returns the packet if -// one is received. -func receivePacketTCP(readTimeout time.Duration, l net.Listener) (Packet, error) { - conn, err := l.Accept() - if err != nil { - return nil, err - } + if readTimeout != 0 { + if err := conn.SetReadDeadline(time.Now().Add(readTimeout)); err != nil { + return nil, err + } + } - if readTimeout != 0 { - if err := conn.SetReadDeadline(time.Now().Add(readTimeout)); err != nil { + data, err := ioutil.ReadAll(conn) + if err != nil { return nil, err } - } - data, err := ioutil.ReadAll(conn) - if err != nil { - return nil, err - } - - var start int - end := len(data) - p, err := readPacket(bufio.NewReader(bytes.NewBuffer(data)), &start, end) - if err != nil { - return nil, err + var start int + end := len(data) + p, err := readPacket(bufio.NewReader(bytes.NewBuffer(data)), &start, end) + if err != nil { + return nil, err + } + return p, nil } - return p, nil } // ParsePacket parses the given msg string and returns a Packet From d63ce0558094cb7f9445999cc531ce75d2972f55 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Fri, 28 Feb 2020 19:11:12 -0500 Subject: [PATCH 38/42] fix doc references to osc.WithProtocol --- osc/doc.go | 2 +- osc/osc.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osc/doc.go b/osc/doc.go index e7537e0..e80e218 100644 --- a/osc/doc.go +++ b/osc/doc.go @@ -80,7 +80,7 @@ OSC server example: } // To use TCP instead of UDP: - // server.WithProtocol(osc.TCP) + // osc.WithProtocol(osc.TCP) server.ListenAndServe() */ package osc diff --git a/osc/osc.go b/osc/osc.go index 9a3d719..2302ea7 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -603,7 +603,7 @@ type ServerOption func(*Server) // bundles over a network connection. // // The default network protocol is UDP. To use TCP instead, use -// server.WithProtocol(TCP). +// osc.WithProtocol(TCP). func NewServer( addr string, dispatcher Dispatcher, readTimeout time.Duration, opts ...ServerOption, From dbf3c4546ad19a7c607470e390b9f19abe25e677 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Fri, 28 Feb 2020 19:17:49 -0500 Subject: [PATCH 39/42] add doc comment for *server.Serve --- osc/osc.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osc/osc.go b/osc/osc.go index 2302ea7..db19a6b 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -664,6 +664,10 @@ func (s *Server) ListenAndServe() error { } } +// Serve uses the provided ReceiveFunc to retrieve and dispatch incoming OSC +// packets in a loop. +// +// Returns an error if something goes awry. func (s *Server) Serve(readPacket ReceiveFunc) error { var tempDelay time.Duration for { From 4d547973c5e0959b19f1f47914800104e7579b03 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Fri, 28 Feb 2020 19:30:54 -0500 Subject: [PATCH 40/42] rename WithProtocol => ServerProtocol in preparation for ClientProtocol --- examples/basic_server/basic_server.go | 2 +- examples/dispatching_server/dispatching_server.go | 3 +-- osc/doc.go | 9 +++------ osc/osc.go | 8 ++++---- osc/osc_test.go | 4 +++- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/examples/basic_server/basic_server.go b/examples/basic_server/basic_server.go index e01aba5..4dea01d 100644 --- a/examples/basic_server/basic_server.go +++ b/examples/basic_server/basic_server.go @@ -103,7 +103,7 @@ func main() { server := osc.NewServer(addr, Debugger{}, 0, // defaults to UDP if not used - osc.WithProtocol(protocol)) + osc.ServerProtocol(protocol)) fmt.Println("### Welcome to go-osc receiver demo") fmt.Printf("Listening via %s on port %d...\n", protocol, port) diff --git a/examples/dispatching_server/dispatching_server.go b/examples/dispatching_server/dispatching_server.go index 6953381..46a33f6 100644 --- a/examples/dispatching_server/dispatching_server.go +++ b/examples/dispatching_server/dispatching_server.go @@ -55,8 +55,7 @@ func main() { os.Exit(1) } - server := osc.NewServer(addr, d, 0, - osc.WithProtocol(protocol)) + server := osc.NewServer(addr, d, 0, osc.ServerProtocol(protocol)) fmt.Printf("Listening via %s on port %d...\n", protocol, port) diff --git a/osc/doc.go b/osc/doc.go index e80e218..2f7e05c 100644 --- a/osc/doc.go +++ b/osc/doc.go @@ -74,13 +74,10 @@ OSC server example: osc.PrintMessage(msg) }) - server := &osc.Server{ - Addr: addr, - Dispatcher:d, - } - + server := osc.NewServer(addr, d, 0) // To use TCP instead of UDP: - // osc.WithProtocol(osc.TCP) + // server := osc.NewServer(addr, d, 0, osc.ServerProtocol(osc.TCP)) + server.ListenAndServe() */ package osc diff --git a/osc/osc.go b/osc/osc.go index db19a6b..496be21 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -602,8 +602,8 @@ type ServerOption func(*Server) // NewServer creates a new OSC server. The server receives OSC messages and // bundles over a network connection. // -// The default network protocol is UDP. To use TCP instead, use -// osc.WithProtocol(TCP). +// The default network protocol is UDP. To use TCP instead, use the ServerOption +// osc.ServerProtocol(TCP). func NewServer( addr string, dispatcher Dispatcher, readTimeout time.Duration, opts ...ServerOption, @@ -624,8 +624,8 @@ func (s *Server) NetworkProtocol() NetworkProtocol { return s.networkProtocol } -// WithProtocol sets the network protocol. -func WithProtocol(protocol NetworkProtocol) ServerOption { +// ServerProtocol sets the network protocol. +func ServerProtocol(protocol NetworkProtocol) ServerOption { return func(server *Server) { server.networkProtocol = protocol } diff --git a/osc/osc_test.go b/osc/osc_test.go index 6e18278..936d2c0 100644 --- a/osc/osc_test.go +++ b/osc/osc_test.go @@ -142,7 +142,9 @@ func testServerMessageDispatching( port := 6677 addr := "localhost:" + strconv.Itoa(port) - server := NewServer(addr, NewStandardDispatcher(), 0, WithProtocol(protocol)) + server := NewServer( + addr, NewStandardDispatcher(), 0, ServerProtocol(protocol), + ) defer server.CloseConnection() if err := server.Dispatcher.(*StandardDispatcher).AddMsgHandler( From 4e71bc3c07f9c4ab815809936a46e7c620191643 Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Fri, 28 Feb 2020 19:32:38 -0500 Subject: [PATCH 41/42] be consistent about not prefixing funcs in doc comments with "osc." --- osc/osc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osc/osc.go b/osc/osc.go index 496be21..4fff6a4 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -603,7 +603,7 @@ type ServerOption func(*Server) // bundles over a network connection. // // The default network protocol is UDP. To use TCP instead, use the ServerOption -// osc.ServerProtocol(TCP). +// ServerProtocol(TCP). func NewServer( addr string, dispatcher Dispatcher, readTimeout time.Duration, opts ...ServerOption, From 0675d0af5e0b64f67f96a6ca169663783983091f Mon Sep 17 00:00:00 2001 From: Dave Yarwood Date: Fri, 28 Feb 2020 20:34:06 -0500 Subject: [PATCH 42/42] refactor client using functional options --- examples/client/client.go | 3 +- osc/doc.go | 2 +- osc/osc.go | 93 ++++++++++++++++++++++++++++++--------- osc/osc_test.go | 11 +++-- 4 files changed, 79 insertions(+), 30 deletions(-) diff --git a/examples/client/client.go b/examples/client/client.go index b628350..3131ce1 100644 --- a/examples/client/client.go +++ b/examples/client/client.go @@ -106,8 +106,7 @@ func main() { } ip := "localhost" - client := osc.NewClient(ip, int(port)) - client.SetNetworkProtocol(protocol) + client := osc.NewClient(ip, int(port), osc.ClientProtocol(protocol)) fmt.Println("### Welcome to go-osc transmitter demo") fmt.Println("Please, select the OSC packet type you would like to send:") diff --git a/osc/doc.go b/osc/doc.go index 2f7e05c..2219792 100644 --- a/osc/doc.go +++ b/osc/doc.go @@ -59,7 +59,7 @@ OSC client example: client := osc.NewClient("localhost", 8765) // To use TCP instead of UDP: - // client.SetNetworkProtocol(osc.TCP) + // client := osc.NewClient("localhost", 8765, osc.ClientProtocol(osc.TCP)) msg := osc.NewMessage("/osc/address") msg.Append(int32(111)) msg.Append(true) diff --git a/osc/osc.go b/osc/osc.go index 4fff6a4..391760e 100644 --- a/osc/osc.go +++ b/osc/osc.go @@ -78,9 +78,9 @@ var _ Packet = (*Bundle)(nil) type Client struct { ip string port int - laddr *net.UDPAddr - laddrTCP *net.TCPAddr networkProtocol NetworkProtocol + sendBytes SendFunc + localAddrString string } // Server represents an OSC server. The server listens on Address and Port for @@ -497,16 +497,35 @@ func (b *Bundle) MarshalBinary() ([]byte, error) { // Client //// +// ClientOption is a function that customizes a Client instance. +type ClientOption func(*Client) + // NewClient creates a new OSC client. The Client is used to send OSC // messages and OSC bundles over a network connection. // -// The default network protocol is UDP. To use TCP instead, use -// client.SetNetworkProtocol(TCP). +// The default network protocol is UDP. To use TCP instead, use the ClientOption +// ClientProtocol(TCP). // // The `ip` argument specifies the IP address and `port` defines the target port // where the messages and bundles will be send to. -func NewClient(ip string, port int) *Client { - return &Client{ip: ip, port: port, laddr: nil, networkProtocol: UDP} +func NewClient(ip string, port int, opts ...ClientOption) *Client { + client := &Client{ip: ip, port: port, networkProtocol: UDP} + + for _, opt := range opts { + opt(client) + } + + if client.sendBytes == nil { + switch client.networkProtocol { + case UDP: + client.sendBytes = client.UDPSend(nil) + + case TCP: + client.sendBytes = client.TCPSend(nil) + } + } + + return client } // IP returns the IP address. @@ -529,26 +548,40 @@ func (c *Client) SetLocalAddr(ip string, port int) error { if err != nil { return err } - c.laddr = laddr + c.sendBytes = c.UDPSend(laddr) + c.localAddrString = laddr.String() case TCP: - laddrTCP, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", ip, port)) + laddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", ip, port)) if err != nil { return err } - c.laddrTCP = laddrTCP + c.sendBytes = c.TCPSend(laddr) + c.localAddrString = laddr.String() } return nil } +// LocalAddrString returns a string representation of the local address, or +// "unspecified" if unspecified. +func (c *Client) LocalAddrString() string { + if c.localAddrString == "" { + return "unspecified" + } + + return c.localAddrString +} + // NetworkProtocol returns the network protocol. func (c *Client) NetworkProtocol() NetworkProtocol { return c.networkProtocol } -// WithProtocol sets the network protocol. -func (c *Client) SetNetworkProtocol(protocol NetworkProtocol) { - c.networkProtocol = protocol +// ClientProtocol sets the network protocol. +func ClientProtocol(protocol NetworkProtocol) ClientOption { + return func(client *Client) { + client.networkProtocol = protocol + } } // Send sends an OSC Bundle or an OSC Message. @@ -558,38 +591,56 @@ func (c *Client) Send(packet Packet) error { return err } - switch c.networkProtocol { - case UDP: + return c.sendBytes(data) +} + +// SendFunc is a function that sends bytes somewhere and returns an error if +// something goes awry. +type SendFunc func([]byte) error + +// UDPSend returns a SendFunc that sends bytes over UDP from the specified local +// address. If laddr is nil, a local address is automatically chosen. +func (c *Client) UDPSend(laddr *net.UDPAddr) SendFunc { + return func(data []byte) error { addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", c.ip, c.port)) if err != nil { return err } - conn, err := net.DialUDP("udp", c.laddr, addr) + + conn, err := net.DialUDP("udp", laddr, addr) if err != nil { return err } defer conn.Close() - if _, err = conn.Write(data); err != nil { + if _, err := conn.Write(data); err != nil { return err } - case TCP: + + return nil + } +} + +// TCPSend returns a SendFunc that sends bytes over TCP from the specified local +// address. If laddr is nil, a local address is automatically chosen. +func (c *Client) TCPSend(laddr *net.TCPAddr) SendFunc { + return func(data []byte) error { addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", c.ip, c.port)) if err != nil { return err } - conn, err := net.DialTCP("tcp", c.laddrTCP, addr) + conn, err := net.DialTCP("tcp", laddr, addr) if err != nil { return err } defer conn.Close() - if _, err = conn.Write(data); err != nil { + if _, err := conn.Write(data); err != nil { return err } - } - return nil + return nil + } } //// diff --git a/osc/osc_test.go b/osc/osc_test.go index 936d2c0..d49ccd1 100644 --- a/osc/osc_test.go +++ b/osc/osc_test.go @@ -195,8 +195,7 @@ func testServerMessageDispatching( case <-timeout: case <-start: time.Sleep(500 * time.Millisecond) - client := NewClient("localhost", port) - client.SetNetworkProtocol(protocol) + client := NewClient("localhost", port, ClientProtocol(protocol)) msg := NewMessage("/address/test") msg.Append(int32(1122)) msg.Append(stringArgument) @@ -321,8 +320,7 @@ func testServerMessageReceiving( select { case <-timeout: case <-start: - client := NewClient("localhost", port) - client.SetNetworkProtocol(protocol) + client := NewClient("localhost", port, ClientProtocol(protocol)) msg := NewMessage("/address/test") msg.Append(int32(1122)) @@ -548,8 +546,9 @@ func TestClientSetLocalAddr(t *testing.T) { t.Error(err.Error()) } expectedAddr := "127.0.0.1:41789" - if client.laddr.String() != expectedAddr { - t.Errorf("Expected laddr to be %s but was %s", expectedAddr, client.laddr.String()) + actualAddr := client.LocalAddrString() + if actualAddr != expectedAddr { + t.Errorf("Expected laddr to be %s but was %s", expectedAddr, actualAddr) } }