Repository: tomarrell/lbadd
Pull request #116 (2020-05-06 11:33:56)

network.Conn shared ids locally and remote


Patch:
diff --git a/internal/network/doc.go b/internal/network/doc.go
index 1c72fcc..2cca460 100644
--- a/internal/network/doc.go
+++ b/internal/network/doc.go
@@ -1,3 +1,35 @@
 // Package network implements a communication layer with a server and a client.
-// Clients can connect to servers and receive and send messages.
+// Clients can connect to servers and receive and send messages. Depending on
+// the implementation, communication may differ.
+//
+//  srv := network.NewTCPServer(zerolog.Nop()) // or any other available server
+//  srv.OnConnect(handleConnection)
+//  if err := srv.Open(":3900"); err != nil {
+//      panic(err)
+//  }
+//
+// In the above example, handleConnection is a func that accepts a network.Conn
+// as parameter. Do with this connection what you'd like. The connections have
+// an ID.
+//
+//  func handleConnection(conn network.Conn) {
+//      loginMsg, err := conn.Receive() // receive a message
+//      // handle loginMsg and err
+//      // create loginResponse
+//      err = conn.Send(loginResponse) // send a message
+//      // handle err
+//      connectionPool.Add(conn) // remember the connection for further use
+//  }
+//
+// To connect to the above server, do as follows.
+//
+//  conn, err := network.DialTCP(":3900") // or any other available dial method
+//  // handle err
+//  defer conn.Close()
+//  err = conn.Send(loginMsg) // send a message
+//  // handle err
+//  loginResponse, err := conn.Receive() // receive a message
+//
+// Please note, that the dial functions will only work with the respective
+// server, e.g. DialTCP will only work on TCPServers.
 package network
diff --git a/internal/network/id.go b/internal/network/id.go
index fc227f5..99b4c1d 100644
--- a/internal/network/id.go
+++ b/internal/network/id.go
@@ -24,7 +24,7 @@ func createID() ID {
 	lock.Lock()
 	defer lock.Unlock()
 
-	id, err := ulid.New(ulid.Now(), entropy)
+	genID, err := ulid.New(ulid.Now(), entropy)
 	if err != nil {
 		// For this to happen, the random module would have to fail. Since we
 		// use Go's pseudo RNG, which just jumps around a few numbers, instead
@@ -34,9 +34,21 @@ func createID() ID {
 		// 2121-04-11 11:53:25.01172576 UTC.
 		log.Fatal(fmt.Errorf("new ulid: %w", err))
 	}
-	return ID(id)
+	return id(genID)
+}
+
+func parseID(idBytes []byte) (ID, error) {
+	parsed, err := ulid.Parse(string(idBytes))
+	if err != nil {
+		return nil, fmt.Errorf("parse: %w", err)
+	}
+	return id(parsed), nil
 }
 
 func (id id) String() string {
 	return ulid.ULID(id).String()
 }
+
+func (id id) Bytes() []byte {
+	return []byte(id.String())
+}
diff --git a/internal/network/server.go b/internal/network/server.go
index d957d05..cd907e2 100644
--- a/internal/network/server.go
+++ b/internal/network/server.go
@@ -54,4 +54,5 @@ type Conn interface {
 // unique application-wide. IDs must not be re-used.
 type ID interface {
 	fmt.Stringer
+	Bytes() []byte
 }
diff --git a/internal/network/tcp_conn.go b/internal/network/tcp_conn.go
index 5aa2c5b..cffc93a 100644
--- a/internal/network/tcp_conn.go
+++ b/internal/network/tcp_conn.go
@@ -32,7 +32,19 @@ func DialTCP(addr string) (Conn, error) {
 	if err != nil {
 		return nil, fmt.Errorf("dial tcp: %w", err)
 	}
-	return newTCPConn(conn), nil
+	tcpConn := newTCPConn(conn)
+	myID, err := tcpConn.Receive()
+	if err != nil {
+		_ = tcpConn.Close()
+		return nil, fmt.Errorf("receive ID: %w", err)
+	}
+	parsedID, err := parseID(myID)
+	if err != nil {
+		_ = tcpConn.Close()
+		return nil, fmt.Errorf("parse ID: %w", err)
+	}
+	tcpConn.id = parsedID
+	return tcpConn, nil
 }
 
 func newTCPConn(underlying net.Conn) *tcpConn {
diff --git a/internal/network/tcp_server.go b/internal/network/tcp_server.go
index e23ee83..2d8c359 100644
--- a/internal/network/tcp_server.go
+++ b/internal/network/tcp_server.go
@@ -86,9 +86,18 @@ func (s *tcpServer) handleIncomingConnections() {
 			s.log.Error().
 				Err(err).
 				Msg("accept")
+			continue
 		}
 
 		tcpConn := newTCPConn(conn)
+		err = tcpConn.Send(tcpConn.id.Bytes())
+		if err != nil {
+			s.log.Error().
+				Err(err).
+				Msg("send ID")
+			_ = tcpConn.Close()
+			continue
+		}
 
 		if s.onConnect != nil {
 			go s.onConnect(tcpConn)


Test patch:
diff --git a/internal/network/example_test.go b/internal/network/example_test.go
new file mode 100644
index 0000000..cc00069
--- /dev/null
+++ b/internal/network/example_test.go
@@ -0,0 +1,33 @@
+package network_test
+
+import (
+	"fmt"
+	"log"
+	"time"
+
+	"github.com/rs/zerolog"
+	"github.com/tomarrell/lbadd/internal/network"
+)
+
+func ExampleServer() {
+	// When using, please don't ignore all the errors as we do here.
+
+	srv := network.NewTCPServer(zerolog.Nop()) // or whatever server is available
+	srv.OnConnect(func(conn network.Conn) {
+		_ = conn.Send([]byte("Hello, World!"))
+	})
+	go func() {
+		if err := srv.Open(":59513"); err != nil {
+			log.Fatal(err)
+		}
+	}()
+
+	time.Sleep(10 * time.Millisecond)
+	client, _ := network.DialTCP(":59513")
+	defer func() {
+		_ = client.Close()
+	}()
+	received, _ := client.Receive()
+	fmt.Println(string(received))
+	// Output: Hello, World!
+}
diff --git a/internal/network/tcp_conn_test.go b/internal/network/tcp_conn_test.go
index 2c97b8d..c45ac6c 100644
--- a/internal/network/tcp_conn_test.go
+++ b/internal/network/tcp_conn_test.go
@@ -39,11 +39,14 @@ func TestDialTCP(t *testing.T) {
 	lis, err := net.Listen("tcp", ":0")
 	assert.NoError(err)
 
+	var srvConnID string
 	go func() {
 		conn, err := lis.Accept()
 		assert.NoError(err)
 
 		tcpConn := newTCPConn(conn)
+		srvConnID = tcpConn.ID().String()
+		assert.NoError(tcpConn.Send(tcpConn.ID().Bytes()))
 		assert.NoError(tcpConn.Send(payload))
 	}()
 
@@ -52,6 +55,7 @@ func TestDialTCP(t *testing.T) {
 	conn, err := DialTCP(":" + strconv.Itoa(port))
 	assert.NoError(err)
 	defer func() { assert.NoError(conn.Close()) }()
+	assert.Equal(srvConnID, conn.ID().String())
 
 	recv, err := conn.Receive()
 	assert.NoError(err)