```unison:hide serverSocket = compose2 reraise serverSocket.impl socketPort = compose reraise socketPort.impl listen = compose reraise listen.impl closeSocket = compose reraise closeSocket.impl clientSocket = compose2 reraise clientSocket.impl socketSend = compose2 reraise socketSend.impl socketReceive = compose2 reraise socketReceive.impl socketAccept = compose reraise socketAccept.impl ``` ```ucm:hide .> add ``` # Tests for network related builtins ### Creating server sockets This section tests functions in the IO builtin related to binding to TCP server socket, as to be able to accept incoming TCP connections. ``` builtin.io2.IO.serverSocket : Optional Text -> Text ->{io2.IO} Either Failure io2.Socket ``` This function takes two parameters, The first is the Hostname. If None is provided, We will attempt to bind to 0.0.0.0 (All ipv4 addresses). We currently only support IPV4 (we should fix this!) The second is the name of the port to bind to. This can be a decimal representation of a port number between 1-65535. This can be a named port like "ssh" (for port 22) or "kermit" (for port 1649), This mapping of names to port numbers is maintained by the [nsswitch service](https://en.wikipedia.org/wiki/Name_Service_Switch), typically stored in `/etc/services` and queried with the `getent` tool: # map number to name $ getent services 22 ssh 22/tcp # map name to number $ getent services finger finger 79/tcp # get a list of all known names $ getent services | head tcpmux 1/tcp echo 7/tcp echo 7/udp discard 9/tcp sink null discard 9/udp sink null systat 11/tcp users daytime 13/tcp daytime 13/udp netstat 15/tcp qotd 17/tcp quote Below shows different examples of how we might specify the server coordinates. ``` unison testExplicitHost : '{io2.IO} [Result] testExplicitHost _ = test = 'let sock = serverSocket (Some "127.0.0.1") "1028" emit (Ok "successfully created socket") port = socketPort sock putBytes (stdHandle StdOut) (toUtf8 (toText port)) expectU "should have bound to port 1028" 1028 port runTest test testDefaultHost : '{io2.IO} [Result] testDefaultHost _ = test = 'let sock = serverSocket None "1028" emit (Ok "successfully created socket") port = socketPort sock putBytes (stdHandle StdOut) (toUtf8 (toText port)) expectU "should have bound to port 1028" 1028 port runTest test testDefaultPort : '{io2.IO} [Result] testDefaultPort _ = test = 'let sock = serverSocket None "0" emit (Ok "successfully created socket") port = socketPort sock putBytes (stdHandle StdOut) (toUtf8 (toText port)) check "port should be > 1024" (1024 < port) check "port should be < 65536" (65536 > port) runTest test ``` ```ucm .> add .> io.test testDefaultPort ``` This example demonstrates connecting a TCP client socket to a TCP server socket. A thread is started for both client and server. The server socket asks for any availalbe port (by passing "0" as the port number). The server thread then queries for the actual assigned port number, and puts that into an MVar which the client thread can read. The client thread then reads a string from the server and reports it back to the main thread via a different MVar. ```unison serverThread: MVar Nat -> Text -> '{io2.IO}() serverThread portVar toSend = 'let go : '{io2.IO, Exception}() go = 'let sock = serverSocket (Some "127.0.0.1") "0" port = socketPort sock put portVar port listen sock sock' = socketAccept sock socketSend sock' (toUtf8 toSend) closeSocket sock' match (toEither go) with Left (Failure _ t _) -> watch t () _ -> () clientThread : MVar Nat -> MVar Text -> '{io2.IO}() clientThread portVar resultVar = 'let go = 'let port = take portVar sock = clientSocket "127.0.0.1" (Nat.toText port) msg = fromUtf8 (socketReceive sock 100) put resultVar msg match (toEither go) with Left (Failure _ t _) -> watch t () _ -> () testTcpConnect : '{io2.IO}[Result] testTcpConnect = 'let test = 'let portVar = !MVar.newEmpty resultVar = !MVar.newEmpty toSend = "12345" forkComp (serverThread portVar toSend) forkComp (clientThread portVar resultVar) received = take resultVar expectU "should have reaped what we've sown" toSend received runTest test ``` ```ucm .> add .> io.test testTcpConnect ```