Java2SocketIO is a java connection to the new LPP, for use in the Loebner Prize Contest.

The AISB are now using a new LPP protocol based on Socket.IO for the Loebner Prize contest. Java2SocketIO makes a direct connection with the new LPP using Java. The main components needed are a Socket object, input/output streams & a timer to send regular PINGs. (NOTE: i/o streams must run in seperate threads, outputstream in the main program thread & inputstream in a seperate thread) Hopefully the java code can easily be converted to other programming languages. Java2SocketIO mimics how Firefox communicates with 'controlPanel.html' & 'judge.html' via 'server.js'. Java2SocketIO was tested on Firefox & IE 11 on a Windows 8.1 computer. Ideally this should be tested across a network, but at the moment I don't have access to one. Running the new LPP.(see the AISB for more details and downloads)


NOTE: '/socket.io' is added to the HOST, so 'http://127.0.0.1:8080' becomes 'http://127.0.0.1:8080/socket.io'
Start by opening a connection with the SERVER & getting input/outputstreams for that connection.

Send initial handshake.

CLIENT: request.
(Join the following lines together & send as 1 stream of bytes)

GET  /socket.io/?EIO=3&transport=polling&t=1501597433827 HTTP/1.1\r\n           (NOTE: t = current time in milliseconds)
Host: 127.0.0.1:8080\r\n
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:54) Gecko/20100101Firefox/5.4\r\n
Accept: */*\r\n
Accept-language: en-GB,en;q=0.5\r\n
Accept-encoding: gzip, deflate\r\n
Origin: null\r\n
Cookie: io=q8tfVr2ltwzBeEeAAAd\r\n
DNT: 1\r\n
Connection: keep-alive\r\n
\r\n

......................................

SERVER: response.

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 101
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: null
Set-Cookie: io=tkfQ1KCnLLdnKj8RAAAD; Path=/; HttpOnly
Date: Tue, 01 Aug 2017 14:23:53 GMT
Connection: keep-alive
+ one empty line, just \r\n
+ payload of {"sid":"tkfQ1KCnLLdnKj8RAAAD","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":60000}

from this we get the Session ID(sid) & the pingInterval.
Start timer to send PING every pingInterval(or less, I set mine to 20000).

......................................

This time add 'sid' to query string.

CLIENT: request.
(Join the following lines together & send as 1 stream of bytes)

GET /socket.io/?EIO=3&transport=polling&t=1501597433887&sid=tkfQ1KCnLLdnKj8RAAAD HTTP/1.1\r\n
Host: 127.0.0.1:8080\r\n
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:54) Gecko/20100101Firefox/5.4 \r\n
Accept: */*\r\n
Accept-language: en-GB,en;q=0.5\r\n
Accept-encoding: gzip, deflate\r\n
Origin: null\r\n
Cookie: io=tkfQ1KCnLLdnKj8RAAAD\r\n
DNT: 1\r\n
Connection: keep-alive\r\n
\r\n

......................................

SERVER: response.

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 5
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: null
Set-Cookie: io=tkfQ1KCnLLdnKj8RAAAD; Path=/; HttpOnly
Date: Tue, 01 Aug 2017 14:23:53 GMT
Connection: keep-alive
+ one empty line, just \r\n
+ a payload of 5 bytes(not sure what they mean)
......................................

Now we send an Upgrade request.

CLIENT: request.
(Join the following lines together & send as 1 stream of bytes)

GET /socket.io/?EIO=3&transport=websocket&sid=tkfQ1KCnLLdnKj8RAAAD HTTP/1.1\r\n
Host: 127.0.0.1:8080\r\n
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:54) Gecko/20100101Firefox/54.0\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n
Accept-language: en-GB,en;q=0.5\r\n
Accept-encoding: gzip, deflate\r\n
Sec-WebSocket-Version: 13\r\n
Sec-WebSocket-Extensions: permessage-deflate\r\n
Sec-WebSocket-Key: niv2Jtuon9N8G0kHHa/nbQ==\r\n
Cookie: io=tkfQ1KCnLLdnKj8RAAAD\r\n
DNT: 1\r\n
Connection: keep-alive, Upgrade\r\n
Pragma: no-cache\r\n
Cache-Control: no-cache\r\n
Upgrade: websocket\r\n
\r\n

(NOTE: The 'Sec-Websocket-Key' value is a randomly generated 16 bit code)
Java code:
	private static String getKey() {
		byte b[] = new byte[16];
		SecureRandom secRandom = new SecureRandom();  (NOTE: import java.security.SecureRandom;)
		secRandom.nextBytes(b);
		Base64.Encoder be = Base64.getEncoder();  (NOTE: import java.util.*;  Base64 is part of jdk 8);
		return be.encodeToString(b);
	}

	(NOTE: 'niv2Jtuon9N8G0kHHa/nbQ==' This can be faked by producing 22 random digits/characters + ==)
......................................


SERVER: response.

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 0VjsqLKjYYIjYDT9C80voMoQoVg=
Sec-WebSocket-Extensions: permessage-deflate
+ one empty line, just \r\n

No payload.

......................................



From this point on, all messages sent & received are in websocket FRAMEs.

Each FRAME contains a payload of a Socket.IO packet(usually some bytes and a JSON string).
Unfortuneatly java org.JSON ("java") is not compatible with Socket.io which uses javascript JSON (\"javascript\").
So I had to implement my own way of creating javascript JSON strings.

FRAMEs sent from CLIENT to SERVER, the payloads need to be masked.
FRAMEs sent from SERVER to CLIENT, the payloads are not masked.

CLIENT:
maskedFRAME payload = 2probe

SERVER:
FRAME payload = 3probe

CLIENT:
maskedFRAME payload = 5  (NOTE: no response expected from this message)

Timer sends PINGs at regular intervals, & SERVER responds with a PONG.
CLIENT:(PING)
maskedFRAME payload = 2

SERVER:(PONG)
FRAME payload = 3

Now we can register as detailed in confederate.html
 
CLIENT:
Send a maskedFRAME with payload: 42["control","{\"status\":\"register\",\"id\":\"ai0\",\"secret\":\"abc123\"}"]  (NOTE: no response expected from the SERVER)

CLIENT:
Send a maskedFRAME with payload: 42["control","{\"status\":\"roundStatus\",\"id\":\"ai0\",\"secret\":\"abc123\"}"]  (NOTE: no response expected from the SERVER)

Hopefully now the ai0 label on controlPanel.html will be green.
if so click 'New round', then 'Start round'.

SERVER: response.
UnmaskedFRAME with payload: 42["control","{\"status\":\"newRound\",\"partners\": {\"judge0\":[\"conf0\",\"ai0\"],\"judge1\":[\"conf1\",\"ai1\"],\"judge2\":[\"conf2\",
			\"ai2\"],\"judge3\":[\"conf3\",\"ai3\"],\"ai0\":[\"judge0\"],\"ai1\":[\"judge1\"],\"ai2\":[\"judge2\"],\"ai3\":[\"judge3\"],
			\"conf0\":[\"judge0\"],\"conf1\":[\"judge1\"],\"conf2\":[\"judge2\"],\"conf3\":[\"judge3\"]}}"]
 
SERVER: response.
UnmaskedFRAME with payload: 42["control","{\"status\":\"startRound\"}"]

A dialog indicating that a new round is starting should appear on Java2SocketIO.

On judge.html enter some text in the input boxes, and a message should appear in Java2SocketIO 'Chat' text box.

Something like, judge sends a message 'hello'
SERVER: 
UnmaskedFRAME with payload: 42["message","{\"contents\":\"hello\",\"to\":\"ai0\",\"id\":\"judge0\",\"secret\":\"\"}"]

Enter response into input box.
CLIENT: ai0 sends a message 'hello'
maskedFRAME with payload: 42["message","{\"contents\":\"hello\",\"to\":\"judge0\",\"id\":\"ai0\",\"secret\":\"abc123\"}"]

Wait for next message from judge.

......................................

Round ending.
SERVER:
UnmaskedFRAME with payload: 42["control","{\"status\":\"endRound\"}"]

......................................

Closing Handshake.
CLIENT:
Send a maskedFRAME with payload: Normal Closure

SERVER: response.
UnmaskedFRAME with payload: Normal Closure

Now socket/connection can be closed.



FRAMEs use the Websocket protocol
Payloads use Socket.IO protocol / engine.IO protocol.

CLIENT FRAMEs
A FRAME is made up from some FRAME data(bytes) and a payload.
FIN bit is bit 7 of byte[0] and is set to 1(128) signifying a complete payload(payloads can be sent in multiple parts).
byte[0] = 128 + opCode(Text = 1, Binary = 2, Close = 8, Ping = 9, Pong = 10). 
We are sending Text so byte[0] = 128 + 1.
byte[1] = 128(masked payload) + payload length (if < 126). 
if payload length > 125 & < 65535, byte[1] = 128(masked payload) + 126. 
	The next 2 bytes will contain the payload length.
if payload length >= 65535, byte[1] = 128(masked payload) + 127.
	The next 8 bytes will contain the payload length.

Now add 4 random bytes for masking the payload to the end of FRAME data.(server uses these to decode payload)
Java code:
	private byte[] getMask(int size) {
		SecureRandom secRandom = new SecrureRandom();  (NOTE: import java.security.SecureRandom;)
		byte b[] = new byte[size];
		secRandom.nextBytes(b);
		return b;
	}

	(NOTE: you could try just 4 random bytes)

Now mask the payload. 
Java code:
	private byte[] maskPayload(byte mask[], byte payload[]) {
		if(mask == null) || (mask.length < 4) || (payload == null)) return payload;
		for(int i = 0; i < payload.length; i++) {
			payload[i] ^= mask[i % 4];
		}
		return payload;
	}

Now add masked payload data to the end of FRAME data.

So a FRAME should look like:
2 bytes of FRAME info + 2(if > 125 & < 65535) or 8(if > 65535) bytes for payload length + 4 bytes for Mask + masked payload bytes.

Send FRAME to SERVER.

SERVER FRAMEs 
FRAMEs are the same except the payload is not masked, so bit 7 of byte[1] will be 0, and no 4 masking bytes.
The payload lengths are set the same way as CLIENT FRAMEs.



Resources

Firefox Tools->Web developer->Web Console->and select 'Network' tab.   (NOTE: you may need to download 'Websocket Monitor' from Firefox)
Create ai.html from confederate.html, and rename NAME = 'ai0', SECRET = 'abc123'.
Run 'node server.js' at CMD prompt.
Load controlPanel.html, judge.html, ai.html & confederate.html
Add Firebug to ai.html, then reload ai.html to get a full list of communications with server.js('Network' tab).

Java2SocketIO, compiled(JDK 1.8) .jar file.
Java2SocketIO Source code, in plain text.
AISB (Loebner Prize Contest)
Chatbots.org discussion on the new LPP(2017)
Chatbots.org Loebner Prize Forum
Socket.IO 
Websocket protocol
Socket.IO protocol 
engine.IO protocol 
Latest version of LPP from GitHub
Old version of LPP

A listing of Java2SocketIO communicating with 'controlPanel.html' & 'judge.html' via 'server.js'.
TRACE.txt







© Copyright SC Mann 2018. All rights reserved.