Skip to content

Commit

Permalink
Merge pull request #1286 from spark/fix/tcp_server
Browse files Browse the repository at this point in the history
TCP server fixes
  • Loading branch information
technobly committed May 10, 2017
2 parents f6a2d5b + 22a606c commit 5ddc904
Show file tree
Hide file tree
Showing 19 changed files with 1,534 additions and 62 deletions.
6 changes: 6 additions & 0 deletions hal/src/photon/socket_hal.cpp
Expand Up @@ -289,6 +289,12 @@ struct tcp_server_t : public wiced_tcp_server_t
*/
wiced_result_t disconnect(wiced_tcp_socket_t* socket) {
wiced_tcp_disconnect(socket);
// remove from client array as this socket is getting closed and
// subsequently destroyed
int idx = index(socket);
if (idx >= 0) {
clients[idx] = NULL;
}
wiced_result_t result = wiced_tcp_server_disconnect_socket(this, socket);
return result;
}
Expand Down
3 changes: 1 addition & 2 deletions system/src/system_control.cpp
Expand Up @@ -271,8 +271,7 @@ uint8_t SystemControlInterface::fetchRequestResult(HAL_USB_SetupRequest* req) {
req->wLength = usbReq_.req.reply_size;
}
usbReq_.active = false;
// FIXME: Don't invalidate reply data for now (simplifies testing with usbtool)
// usbReq_.ready = false;
usbReq_.ready = false;
return 0;
}

Expand Down
12 changes: 2 additions & 10 deletions user/tests/accept/tools/send_usb_req
Expand Up @@ -39,11 +39,7 @@ send_out_req() {

# Sends device-to-host request
send_in_req() {
size_arg="-n $max_size"
if [ $1 ]; then
size_arg="-n $1"
fi
usbtool -v 0x$USB_VENDOR_ID -p 0x$USB_PRODUCT_ID $size_arg control in vendor device $usb_req $usb_val $usb_index
usbtool -v 0x$USB_VENDOR_ID -p 0x$USB_PRODUCT_ID -n $max_size control in vendor device $usb_req $usb_val $usb_index
}

# Prints current UNIX time with nanosecond precision
Expand Down Expand Up @@ -121,10 +117,6 @@ if [ $req_type == reqrep ] || [ $req_type == in ]; then
while : ; do
sleep 0.1
data=$(send_in_req 2>/dev/null) && break
if [ $max_size != 0 ]; then
# Empty reply data seems to require some special handling
data=$(send_in_req 0 2>/dev/null) && break
fi
less_than $(timestamp) $time_end || error "Request timeout"
done
else
Expand All @@ -134,7 +126,7 @@ if [ $req_type == reqrep ] || [ $req_type == in ]; then

if [ "$data" ]; then
# "0x61 0x62 0x63" -> "616263"
data=$(printf "$data" | sed 's/ //g;s/0x//g')
data=$(printf "$data" | tr -d ' \n' | sed 's/0x//g')
if [ ! $hex_flag ]; then
# "616263" -> "abc"
data=$(printf "$data" | xxd -r -p)
Expand Down
10 changes: 10 additions & 0 deletions user/tests/app/tcp_server/README.md
@@ -0,0 +1,10 @@
Flash server application to the device:
$ cd ~/firmware/modules
$ make -s all program-dfu PLATFORM=photon TEST=app/tcp_server

Install client dependencies:
$ cd ~/firmware/user/tests/app/tcp_server/client
$ npm install

Run client:
$ ./client
16 changes: 16 additions & 0 deletions user/tests/app/tcp_server/client/client
@@ -0,0 +1,16 @@
#!/bin/bash

set -e

if [ ! $PLATFORM ]; then
echo 'PLATFORM is not defined, assuming Photon'
export PLATFORM=photon
fi

this_dir=$(cd $(dirname "$0") && pwd)
test_dir=$(cd "$this_dir/../../../accept" && pwd)

PATH="$test_dir/tools:$PATH"
source "$test_dir/init_env"

node "$this_dir/main.js"
215 changes: 215 additions & 0 deletions user/tests/app/tcp_server/client/lib/client.js
@@ -0,0 +1,215 @@
const randomstring = require('randomstring');
const net = require('net');
const _ = require('lodash');

const EventEmitter = require('events');
const Promise = require('bluebird');

const DEFAULT_TIMEOUT = 30000;

const State = {
NEW: 1,
CONNECTING: 2,
CONNECTED: 3,
READING: 4,
WRITING: 5,
CLOSING: 6,
CLOSED: 7
};

// Promise-based TCP client for testing purposes
class Client extends EventEmitter {
constructor(host, port) {
super();
this._host = host;
this._port = port;
this._data = '';
this._readSize = 0;
this._resolve = null;
this._reject = null;
this._timer = null;
this._timeout = DEFAULT_TIMEOUT;
this._sock = this._newSocket();
this._state = State.NEW;
}

connect() {
return this._setState(State.NEW, State.CONNECTING, () => {
this._sock.connect({ host: this._host, port: this._port });
});
}

disconnect() {
return this._setState(State.CONNECTED, State.CLOSING, () => {
this._sock.end();
});
}

read(size) {
return this._setState(State.CONNECTED, State.READING, () => {
this._readSize = size;
this._read();
});
}

write(data) {
return this._setState(State.CONNECTED, State.WRITING, () => {
this._sock.write(data, null, () => {
this._resolveState(State.WRITING, State.CONNECTED);
});
});
}

waitUntilClosed() {
return this._setState(State.CONNECTED, State.CLOSING);
}

// Closes connection synchronously
close() {
this._state = State.CLOSED;
this._sock.destroy();
if (this._timer) {
clearTimeout(this._timer);
this._timer = null
}
if (this._reject) {
this._reject(new Error('Operation has been aborted'));
this._reject = null;
}
this._resolve = null;
this._data = '';
}

get connecting() {
return this._state == State.CONNECTING;
}

get connected() {
return this._state == State.CONNECTED || this._state == State.READING || this._state == State.WRITING;
}

get closing() {
return this._state == State.CLOSING;
}

get closed() {
return this._state == State.CLOSED;
}

get host() {
return this._host;
}

get port() {
return this._port;
}

get timeout() {
return this._timeout;
}

set timeout(timeout) {
this._timeout = timeout;
}

_setState(curState, newState, func) {
return new Promise((resolve, reject) => {
if (this._state != curState) {
throw new Error('Invalid object state');
}
this._state = newState;
this._resolve = resolve;
this._reject = reject;
this._timer = setTimeout(() => {
this._error(new Error('Operation has timed out'));
}, this._timeout);
if (func) {
func();
}
});
}

_resolveState(curState, newState, data) {
if (this._state == curState) {
this._state = newState;
if (this._timer) {
clearTimeout(this._timer);
this._timer = null;
}
if (this._resolve) {
this._resolve(data);
this._resolve = null;
}
this._reject = null;
}
}

_error(err) {
if (this._reject) {
this._reject(err);
this._reject = null;
}
this.close();
}

_read() {
if (this._state == State.READING && this._data.length >= this._readSize) {
const d = this._data.substr(0, this._readSize);
this._data = this._data.substr(this._readSize);
this._resolveState(State.READING, State.CONNECTED, d);
}
}

_newSocket() {
const sock = new net.Socket();
sock.setEncoding('utf8');
sock.on('connect', () => {
this._resolveState(State.CONNECTING, State.CONNECTED);
this.emit('connect');
});
sock.on('close', () => {
if (this._state == State.CLOSING) {
this._resolveState(State.CLOSING, State.CLOSED);
} else {
this._error(new Error('Connection has been closed'));
}
this.close();
this.emit('close');
});
sock.on('data', (data) => {
this._data += data;
this._read();
});
sock.on('error', (err) => {
this._error(err);
});
return sock;
}
}

class EchoClient extends Client {
echo(strOrLength) {
let str = strOrLength;
if (_.isNumber(strOrLength)) {
str = randomstring.generate(strOrLength);
} else if (_.isUndefined(strOrLength)) {
const len = Math.floor(Math.random() * 125 + 4); // 4 to 128 characters
str = randomstring.generate(len);
}
return this.write(str)
.then(() => {
return this.read(str.length)
})
.then((reply) => {
if (reply != str) {
throw new Error('Unexpected reply from server');
}
});
}
}

module.exports = {
DEFAULT_TIMEOUT: DEFAULT_TIMEOUT,
Client: Client,
EchoClient: EchoClient
};

0 comments on commit 5ddc904

Please sign in to comment.