1 /** 
2  * Socket encoding/decoding functions
3  */
4 module bformat.client;
5 
6 import std.socket : Socket;
7 import river.core;
8 import river.impls.sock : SockStream;
9 import niknaks.bits : bytesToIntegral, order, Order;
10 
11 /** 
12  * Bformat client to encode and decode via a
13  * `Socket` or river-based `Stream`
14  */
15 public class BClient
16 {
17 	/** 
18 	 * Underlying stream
19 	 */
20 	private Stream stream;
21 
22 	/** 
23 	 * Constructs a new `BClient` for encoding and decoding
24 	 * to and from the provided `Socket`
25 	 *
26 	 * Params:
27 	 *   socket = the `Socket` to use for writing and reading
28 	 */
29 	this(Socket socket)
30 	{
31 		this(new SockStream(socket));
32 	}
33 
34 	/** 
35 	 * Constructs a new `BClient` for encoding and decoding
36 	 * to and from the provided river-based `Stream`
37 	 *
38 	 * Params:
39 	 *   stream = the `Stream` to use for writing and reading
40 	 */
41 	this(Stream stream)
42 	{
43 		this.stream = stream;
44 	}
45 
46 	/** 
47 	 * Receives a message from the provided socket
48 	 * by decoding the streamed bytes into bformat
49 	 * and finally placing the resulting payload in
50 	 * the provided array
51 	 *
52 	 * Params:
53 	 *   originator = the socket to receive from
54 	 *   receiveMessage = the nbuffer to receive into
55 	 *
56 	 * Returns: true if the receive succeeded, false otheriwse
57 	 */
58 	public bool receiveMessage(ref byte[] receiveMessage)
59 	{
60 		/* Construct a buffer to receive into */
61 		byte[] receiveBuffer;
62 
63 		/* Get the length of the message */
64 		byte[4] messageLengthBytes;
65 
66 		try
67 		{
68 			stream.readFully(messageLengthBytes);
69 		}
70 		catch(StreamException streamErr)
71 		{
72 			/* If there was an error reading from the socket */
73 			return false;
74 		}
75 
76 
77 		/* Response message length */
78 		uint messageLength;
79 
80 		/* Order the bytes into Little endian (only flips if host order doesn't match LE) */
81 		messageLength = order(bytesToIntegral!(uint)(cast(ubyte[])messageLengthBytes), Order.LE);
82 		
83 		/* Read the full message */
84 		receiveBuffer.length = messageLength;
85 		try
86 		{
87 			stream.readFully(receiveBuffer);
88 			receiveMessage = receiveBuffer;
89 
90 			/* If there was no error receiving the message */
91 			return true;
92 		}
93 		catch(StreamException streamErr)
94 		{
95 			/* If there was an error reading from the socket */
96 			return false;
97 		}
98 	}
99 
100 	/** 
101 	 * Encodes the provided message into the bformat format
102 	 * and sends it over the provided socket
103 	 *
104 	 * Params:
105 	 *   recipient = the socket to send over
106 	 *   message = the message to encode and send
107 	 *
108 	 * Returns: true if the send succeeded, false otherwise
109 	 */
110 	public bool sendMessage(byte[] message)
111 	{
112 		/* The message buffer */
113 		byte[] messageBuffer;
114 
115 		import bformat.marshall : encodeBformat;
116 		messageBuffer = encodeBformat(message);
117 
118 		try
119 		{
120 			/* Send the message */
121 			stream.writeFully(messageBuffer);
122 
123 			return true;
124 		}
125 		catch(StreamException streamError)
126 		{
127 			return false;
128 		}
129 	}
130 
131 	/** 
132 	 * Closes the client
133 	 */
134 	public void close()
135 	{
136 		/* Close the underlying stream */
137 		stream.close();
138 	}
139 }
140 
141 version(unittest)
142 {
143 	import std.socket;
144 	import core.thread;
145 	import std.stdio;
146 }
147 
148 /**
149  * Create a server that encodes a message to the client
150  * and then let the client decode it from us; both making
151  * use of `BClient` to accomplish this
152  */
153 unittest
154 {
155 	UnixAddress unixAddr = new UnixAddress("/tmp/bformatServer.sock");
156 
157 	scope(exit)
158 	{
159 		import std.stdio;
160 		remove(cast(char*)unixAddr.path());
161 	}
162 
163 	Socket serverSocket = new Socket(AddressFamily.UNIX, SocketType.STREAM);
164 	serverSocket.bind(unixAddr);
165 	serverSocket.listen(0);
166 
167 	class ServerThread : Thread
168 	{
169 		private Socket servSock;
170 
171 		this(Socket servSock)
172 		{
173 			this.servSock = servSock;
174 			super(&worker);
175 		}
176 
177 		private void worker()
178 		{
179 			Socket clientSock = servSock.accept();
180 
181 			BClient bClient = new BClient(clientSock);
182 
183 			byte[] message = cast(byte[])"ABBA";
184 			bClient.sendMessage(message);
185 		}
186 	}
187 
188 	Thread serverThread = new ServerThread(serverSocket);
189 	serverThread.start();
190 
191 	Socket client = new Socket(AddressFamily.UNIX, SocketType.STREAM);
192 	client.connect(unixAddr);
193 	BClient bClient = new BClient(client);
194 
195 	byte[] receivedMessage;
196 	bClient.receiveMessage(receivedMessage);
197 	assert(receivedMessage == "ABBA");
198 	writeln(receivedMessage);
199 	writeln(cast(string)receivedMessage);
200 
201 	bClient.close();
202 }