cluster/identd/ident: add basic ident protocol server

This adds an ident protocol server and tests for it.

Change-Id: I830f85faa7dce4220bd7001635b20e88b4a8b417
diff --git a/cluster/identd/ident/request.go b/cluster/identd/ident/request.go
index 9727893..9c94e75 100644
--- a/cluster/identd/ident/request.go
+++ b/cluster/identd/ident/request.go
@@ -2,6 +2,9 @@
 
 import (
 	"fmt"
+	"net"
+	"regexp"
+	"strconv"
 )
 
 // Request is an ident protocol request, as seen by the client or server.
@@ -12,9 +15,44 @@
 	// ServerPort is the port number on the server side of the ident protocol,
 	// ie. the port local to the ident server.
 	ServerPort uint16
+
+	// ClientAddress is the address of the ident protocol client. This is set
+	// by the ident Server before invoking handlers, and is ignored by the
+	// ident protocol Client.
+	// In handlers this can be used to ensure that responses are only returned
+	// to clients who are running on the remote side of the connection that
+	// they are querying about.
+	ClientAddress net.Addr
 }
 
 // encode encodes ths Request as per RFC1413, including the terminating \r\n.
 func (r *Request) encode() []byte {
 	return []byte(fmt.Sprintf("%d,%d\r\n", r.ServerPort, r.ClientPort))
 }
+
+var (
+	// reRequest matches request from RFC1413, but allows extra whitespace
+	// between significant tokens.
+	reRequest = regexp.MustCompile(`^\s*(\d{1,5})\s*,\s*(\d{1,5})\s*$`)
+)
+
+// decodeRequest parses the given bytes as an ident request. The data must be
+// stripped of the trailing \r\n.
+func decodeRequest(data []byte) (*Request, error) {
+	match := reRequest.FindStringSubmatch(string(data))
+	if match == nil {
+		return nil, fmt.Errorf("unparseable request")
+	}
+	serverPort, err := strconv.ParseUint(match[1], 10, 16)
+	if err != nil {
+		return nil, fmt.Errorf("invalid server port: %w", err)
+	}
+	clientPort, err := strconv.ParseUint(match[2], 10, 16)
+	if err != nil {
+		return nil, fmt.Errorf("invalid client port: %w", err)
+	}
+	return &Request{
+		ClientPort: uint16(clientPort),
+		ServerPort: uint16(serverPort),
+	}, nil
+}