From cb99729e4c88da309c5805b7b4d9f5687352eb5f Mon Sep 17 00:00:00 2001 From: Aaron Date: Fri, 9 Feb 2018 16:21:40 -0800 Subject: [PATCH 01/11] V2 Responder --- build.gradle | 2 +- .../com/acuity/iot/dsa/dslink/DSSession.java | 39 +++ .../iot/dsa/dslink/io/DSByteBuffer.java | 299 +++++++++++------- ...{BaseMessage.java => AbstractMessage.java} | 16 +- .../dslink/protocol/message/CloseMessage.java | 66 ---- .../protocol/message/ErrorResponse.java | 2 +- .../protocol/protocol_v1/CloseMessage.java | 38 +++ .../protocol/protocol_v1/DS1Session.java | 31 +- .../protocol_v1/requester/DS1Requester.java | 4 +- .../protocol_v1/responder/DS1InboundList.java | 28 -- .../responder/DS1InboundRequest.java | 36 --- .../protocol_v1/responder/DS1Responder.java | 92 +++--- .../protocol_v1/responder/ErrorMessage.java | 77 +++++ .../protocol/protocol_v2/CloseMessage.java | 37 +++ .../protocol_v2/DS2LinkConnection.java | 9 +- .../protocol_v2/DS2MessageReader.java | 10 + .../protocol_v2/DS2MessageWriter.java | 135 ++++---- .../protocol/protocol_v2/DS2Session.java | 4 +- .../responder/DS2InboundInvoke.java | 33 ++ .../protocol_v2/responder/DS2InboundList.java | 22 +- .../protocol_v2/responder/DS2InboundSet.java | 23 ++ .../responder/DS2InboundSubscription.java | 58 ++++ .../responder/DS2InboundSubscriptions.java | 48 +++ .../protocol_v2/responder/DS2Responder.java | 163 +++++----- .../protocol_v2/responder/ErrorMessage.java | 70 ++++ .../DSInboundInvoke.java} | 160 ++++++---- .../protocol/responder/DSInboundList.java | 12 +- .../protocol/responder/DSInboundRequest.java | 2 - .../DSInboundSet.java} | 29 +- .../DSInboundSubscription.java} | 76 +++-- .../DSInboundSubscriptions.java} | 80 +++-- .../protocol/responder/DSResponder.java | 11 +- .../java/org/iot/dsa/io/AbstractWriter.java | 24 +- .../org/iot/dsa/io/msgpack/MsgpackWriter.java | 6 +- .../main/java/org/iot/dsa/node/DSBytes.java | 3 + .../org/iot/dsa/security/DSPermission.java | 34 +- .../org/iot/dsa/dslink/V2HandshakeTest.java | 37 +-- 37 files changed, 1116 insertions(+), 700 deletions(-) rename dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/{BaseMessage.java => AbstractMessage.java} (90%) delete mode 100644 dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/CloseMessage.java create mode 100644 dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/CloseMessage.java delete mode 100644 dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundList.java delete mode 100644 dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundRequest.java create mode 100644 dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/ErrorMessage.java create mode 100644 dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/CloseMessage.java create mode 100644 dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundInvoke.java create mode 100644 dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSet.java create mode 100644 dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscription.java create mode 100644 dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscriptions.java create mode 100644 dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/ErrorMessage.java rename dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/{protocol_v1/responder/DS1InboundInvoke.java => responder/DSInboundInvoke.java} (85%) rename dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/{protocol_v1/responder/DS1InboundSet.java => responder/DSInboundSet.java} (71%) rename dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/{protocol_v1/responder/DS1InboundSubscription.java => responder/DSInboundSubscription.java} (79%) rename dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/{protocol_v1/responder/DS1InboundSubscriptions.java => responder/DSInboundSubscriptions.java} (63%) diff --git a/build.gradle b/build.gradle index 4d318744..35e90e90 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'java' apply plugin: 'maven' group 'org.iot.dsa' -version '0.17.0' +version '0.18.0' sourceCompatibility = 1.6 targetCompatibility = 1.6 diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/DSSession.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/DSSession.java index 9e28fb00..bf772421 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/DSSession.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/DSSession.java @@ -17,10 +17,18 @@ */ public abstract class DSSession extends DSNode { + /////////////////////////////////////////////////////////////////////////// + // Constants + /////////////////////////////////////////////////////////////////////////// + + private static final int MAX_MSG_ID = 2147483647; + /////////////////////////////////////////////////////////////////////////// // Fields /////////////////////////////////////////////////////////////////////////// + private int nextAck = -1; + private int nextMessage = 1; private boolean connected = false; private DSLinkConnection connection; private Logger logger; @@ -134,6 +142,26 @@ public Logger getLogger() { return logger; } + /** + * The next ack id, or -1. + */ + public synchronized int getNextAck() { + int ret = nextAck; + nextAck = -1; + return ret; + } + + /** + * Returns the next new message id. + */ + public synchronized int getNextMessageId() { + int ret = nextMessage; + if (++nextMessage > MAX_MSG_ID) { + nextMessage = 1; + } + return ret; + } + public abstract DSIRequester getRequester(); public DSTransport getTransport() { @@ -144,6 +172,9 @@ public DSTransport getTransport() { * True if there are any outbound requests or responses queued up. */ protected final boolean hasMessagesToSend() { + if (nextAck > 0) { + return true; + } if (!outgoingResponses.isEmpty()) { return true; } @@ -206,6 +237,14 @@ public void onDisconnect() { } } + /** + * Call for each incoming message id that needs to be acked. + */ + protected synchronized void setNextAck(int nextAck) { + this.nextAck = nextAck; + notifyOutgoing(); + } + /** * Called when the broker signifies that requests are allowed. */ diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/io/DSByteBuffer.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/io/DSByteBuffer.java index deb2963f..16e4dd91 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/io/DSByteBuffer.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/io/DSByteBuffer.java @@ -44,9 +44,10 @@ public int available() { return length; } - public void clear() { + public DSByteBuffer clear() { length = 0; offset = 0; + return this; } /** @@ -71,85 +72,31 @@ public int length() { } /** - * Overwrites bytes in the internal buffer, does not change the current length or position. - */ - public void overwrite(int dest, byte b1, byte b2) { - buffer[dest] = b1; - buffer[++dest] = b2; - } - - /** - * Overwrites bytes in the internal buffer, does not change the current length or position. - */ - public void overwrite(int dest, byte b1, byte b2, byte b3, byte b4) { - buffer[dest] = b1; - buffer[++dest] = b2; - buffer[++dest] = b3; - buffer[++dest] = b4; - } - - /** - * Overwrites the primitive in the internal buffer. Does not change the buffer length or - * position. - * - * @param dest The offset in the internal buffer to write the bytes. - * @param v The value to encode. - * @param bigEndian Whether to encode in big or little endian byte ordering. - */ - public void overwriteInt(int dest, int v, boolean bigEndian) { - if (bigEndian) { - overwrite(dest, (byte) ((v >>> 24) & 0xFF), - (byte) ((v >>> 16) & 0xFF), - (byte) ((v >>> 8) & 0xFF), - (byte) ((v >>> 0) & 0xFF)); - } else { - overwrite(dest, (byte) ((v >>> 0) & 0xFF), - (byte) ((v >>> 8) & 0xFF), - (byte) ((v >>> 16) & 0xFF), - (byte) ((v >>> 24) & 0xFF)); - } - } - - /** - * Overwrites the primitive in the internal buffer. Does not change the buffer length or* - * position. - * - * @param dest The offset in the internal buffer to write the bytes. - * @param v The value to encode. - * @param bigEndian Whether to encode in big or little endian byte ordering. - */ - public void overwriteShort(int dest, short v, boolean bigEndian) { - if (bigEndian) { - overwrite(dest, (byte) ((v >>> 8) & 0xFF), (byte) ((v >>> 0) & 0xFF)); - } else { - overwrite(dest, (byte) ((v >>> 0) & 0xFF), (byte) ((v >>> 8) & 0xFF)); - } - } - - /** + * /** * Gets the bytes from the given buffer, which will be flipped, then cleared. */ - public void put(ByteBuffer buf) { - int len = buf.position(); + public DSByteBuffer put(ByteBuffer buf) { + int pos = buf.position(); int bufLen = buffer.length; - if ((len + length + offset) >= bufLen) { - if ((len + length) > bufLen) { //the buffer is too small - growBuffer(len + length); + if ((pos + length + offset) >= bufLen) { + if ((pos + length) > bufLen) { //the buffer is too small + growBuffer(pos + length); } else { //offset must be > 0, shift everything to index 0 System.arraycopy(buffer, offset, buffer, 0, length); offset = 0; } } buf.flip(); - buf.get(buffer, length + offset, len); + buf.get(buffer, length + offset, pos); buf.clear(); - length += len; + length += pos; + return this; } /** * Add the byte to the buffer for reading. */ - public void put(byte b) { + public DSByteBuffer put(byte b) { int bufLen = buffer.length; int msgLen = 1; if ((msgLen + length + offset) >= bufLen) { @@ -162,12 +109,13 @@ public void put(byte b) { } buffer[length + offset] = b; length++; + return this; } /** * Add the byte to the buffer for reading. */ - public void put(byte b1, byte b2) { + public DSByteBuffer put(byte b1, byte b2) { int bufLen = buffer.length; int msgLen = 2; if ((msgLen + length + offset) >= bufLen) { @@ -182,12 +130,13 @@ public void put(byte b1, byte b2) { buffer[idx] = b1; buffer[++idx] = b2; length += msgLen; + return this; } /** * Add the byte to the buffer for reading. */ - public void put(byte b1, byte b2, byte b3, byte b4) { + public DSByteBuffer put(byte b1, byte b2, byte b3, byte b4) { int bufLen = buffer.length; int msgLen = 4; if ((msgLen + length + offset) >= bufLen) { @@ -204,12 +153,14 @@ public void put(byte b1, byte b2, byte b3, byte b4) { buffer[++idx] = b3; buffer[++idx] = b4; length += msgLen; + return this; } /** * Add the byte to the buffer for reading. */ - public void put(byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7, byte b8) { + public DSByteBuffer put(byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7, + byte b8) { int bufLen = buffer.length; int msgLen = 8; if ((msgLen + length + offset) >= bufLen) { @@ -230,6 +181,7 @@ public void put(byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7, b buffer[++idx] = b7; buffer[++idx] = b8; length += msgLen; + return this; } /** @@ -237,8 +189,8 @@ public void put(byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7, b * * @param msg The data source. */ - public void put(byte[] msg) { - put(msg, 0, msg.length); + public DSByteBuffer put(byte[] msg) { + return put(msg, 0, msg.length); } /** @@ -248,7 +200,7 @@ public void put(byte[] msg) { * @param off The start offset in the buffer to put data. * @param len The maximum number of bytes to read. */ - public void put(byte[] msg, int off, int len) { + public DSByteBuffer put(byte[] msg, int off, int len) { int bufLen = buffer.length; if ((len + length + offset) >= bufLen) { if ((len + length) > bufLen) { //the buffer is too small @@ -260,6 +212,7 @@ public void put(byte[] msg, int off, int len) { } System.arraycopy(msg, off, buffer, length + offset, len); length += len; + return this; } /** @@ -270,7 +223,7 @@ public void put(byte[] msg, int off, int len) { * @param off The start offset in the msg to put data. * @param len The maximum number of bytes to put. */ - public void put(int dest, byte[] msg, int off, int len) { + public DSByteBuffer put(int dest, byte[] msg, int off, int len) { if (offset > 0) { System.arraycopy(buffer, offset, buffer, 0, length); offset = 0; @@ -286,13 +239,14 @@ public void put(int dest, byte[] msg, int off, int len) { if ((dest + len) > length) { length += len; } + return this; } /** * Encodes the primitive into buffer using big endian encoding. */ - public void putDouble(double v) { - putDouble(v, true); + public DSByteBuffer putDouble(double v) { + return putDouble(v, true); } /** @@ -301,15 +255,15 @@ public void putDouble(double v) { * @param v The value to encode. * @param bigEndian Whether to encode in big or little endian byte ordering. */ - public void putDouble(double v, boolean bigEndian) { - putLong(Double.doubleToRawLongBits(v), bigEndian); + public DSByteBuffer putDouble(double v, boolean bigEndian) { + return putLong(Double.doubleToRawLongBits(v), bigEndian); } /** * Encodes the primitive into buffer using big endian encoding. */ - public void putFloat(float v) { - putFloat(v, true); + public DSByteBuffer putFloat(float v) { + return putFloat(v, true); } /** @@ -318,15 +272,15 @@ public void putFloat(float v) { * @param v The value to encode. * @param bigEndian Whether to encode in big or little endian byte ordering. */ - public void putFloat(float v, boolean bigEndian) { - putInt(Float.floatToIntBits(v), bigEndian); + public DSByteBuffer putFloat(float v, boolean bigEndian) { + return putInt(Float.floatToIntBits(v), bigEndian); } /** * Encodes the primitive into buffer using big endian encoding. */ - public void putInt(int v) { - putInt(v, true); + public DSByteBuffer putInt(int v) { + return putInt(v, true); } /** @@ -335,25 +289,24 @@ public void putInt(int v) { * @param v The value to encode. * @param bigEndian Whether to encode in big or little endian byte ordering. */ - public void putInt(int v, boolean bigEndian) { + public DSByteBuffer putInt(int v, boolean bigEndian) { if (bigEndian) { - put((byte) ((v >>> 24) & 0xFF), - (byte) ((v >>> 16) & 0xFF), - (byte) ((v >>> 8) & 0xFF), - (byte) ((v >>> 0) & 0xFF)); - } else { - put((byte) ((v >>> 0) & 0xFF), - (byte) ((v >>> 8) & 0xFF), - (byte) ((v >>> 16) & 0xFF), - (byte) ((v >>> 24) & 0xFF)); + return put((byte) ((v >>> 24) & 0xFF), + (byte) ((v >>> 16) & 0xFF), + (byte) ((v >>> 8) & 0xFF), + (byte) ((v >>> 0) & 0xFF)); } + return put((byte) ((v >>> 0) & 0xFF), + (byte) ((v >>> 8) & 0xFF), + (byte) ((v >>> 16) & 0xFF), + (byte) ((v >>> 24) & 0xFF)); } /** * Encodes the primitive into the buffer using big endian encoding. */ - public void putLong(long v) { - putLong(v, true); + public DSByteBuffer putLong(long v) { + return putLong(v, true); } /** @@ -362,33 +315,32 @@ public void putLong(long v) { * @param v The value to encode. * @param bigEndian Whether to encode in big or little endian byte ordering. */ - public void putLong(long v, boolean bigEndian) { + public DSByteBuffer putLong(long v, boolean bigEndian) { if (bigEndian) { - put((byte) (v >>> 56), - (byte) (v >>> 48), - (byte) (v >>> 40), - (byte) (v >>> 32), - (byte) (v >>> 24), - (byte) (v >>> 16), - (byte) (v >>> 8), - (byte) (v >>> 0)); - } else { - put((byte) (v >>> 0), - (byte) (v >>> 8), - (byte) (v >>> 16), - (byte) (v >>> 24), - (byte) (v >>> 32), - (byte) (v >>> 40), - (byte) (v >>> 48), - (byte) (v >>> 56)); + return put((byte) (v >>> 56), + (byte) (v >>> 48), + (byte) (v >>> 40), + (byte) (v >>> 32), + (byte) (v >>> 24), + (byte) (v >>> 16), + (byte) (v >>> 8), + (byte) (v >>> 0)); } + return put((byte) (v >>> 0), + (byte) (v >>> 8), + (byte) (v >>> 16), + (byte) (v >>> 24), + (byte) (v >>> 32), + (byte) (v >>> 40), + (byte) (v >>> 48), + (byte) (v >>> 56)); } /** * Encodes the primitive into the buffer using big endian. */ - public void putShort(short v) { - putShort(v, true); + public DSByteBuffer putShort(short v) { + return putShort(v, true); } /** @@ -397,12 +349,11 @@ public void putShort(short v) { * @param v The value to encode. * @param bigEndian Whether to encode in big or little endian byte ordering. */ - public void putShort(short v, boolean bigEndian) { + public DSByteBuffer putShort(short v, boolean bigEndian) { if (bigEndian) { - put((byte) ((v >>> 8) & 0xFF), (byte) ((v >>> 0) & 0xFF)); - } else { - put((byte) ((v >>> 0) & 0xFF), (byte) ((v >>> 8) & 0xFF)); + return put((byte) ((v >>> 8) & 0xFF), (byte) ((v >>> 0) & 0xFF)); } + return put((byte) ((v >>> 0) & 0xFF), (byte) ((v >>> 8) & 0xFF)); } /** @@ -418,6 +369,102 @@ public int read() { return ret; } + /** + * Overwrites byte in the internal buffer. + * + * @param dest The logical offset in the internal buffer to write the byte. + */ + public DSByteBuffer replace(int dest, byte b1) { + if (offset > 0) { + System.arraycopy(buffer, offset, buffer, 0, length); + offset = 0; + } + buffer[dest] = b1; + return this; + } + + /** + * Overwrites bytes in the internal buffer. + * + * @param dest The logical offset in the internal buffer to write the bytes. + */ + public DSByteBuffer replace(int dest, byte b1, byte b2) { + if (offset > 0) { + System.arraycopy(buffer, offset, buffer, 0, length); + offset = 0; + } + if ((dest + 2) > length) { + throw new IllegalArgumentException("Replace cannot grow the buffer"); + } + buffer[dest] = b1; + buffer[++dest] = b2; + return this; + } + + /** + * Overwrites bytes in the internal buffer. + * + * @param dest The logical offset in the internal buffer to write the bytes. + */ + public DSByteBuffer replace(int dest, byte b1, byte b2, byte b3, byte b4) { + if (offset > 0) { + System.arraycopy(buffer, offset, buffer, 0, length); + offset = 0; + } + if ((dest + 4) > length) { + throw new IllegalArgumentException("Replace cannot grow the buffer"); + } + buffer[dest] = b1; + buffer[++dest] = b2; + buffer[++dest] = b3; + buffer[++dest] = b4; + return this; + } + + /** + * Overwrites the primitive in the internal buffer. Does not change the buffer length or + * position. + * + * @param dest The logical offset in the internal buffer to write the bytes. + * @param v The value to encode. + * @param bigEndian Whether to encode in big or little endian byte ordering. + */ + public DSByteBuffer replaceInt(int dest, int v, boolean bigEndian) { + if (offset > 0) { + System.arraycopy(buffer, offset, buffer, 0, length); + offset = 0; + } + if (bigEndian) { + return replace(dest, (byte) ((v >>> 24) & 0xFF), + (byte) ((v >>> 16) & 0xFF), + (byte) ((v >>> 8) & 0xFF), + (byte) ((v >>> 0) & 0xFF)); + } + return replace(dest, (byte) ((v >>> 0) & 0xFF), + (byte) ((v >>> 8) & 0xFF), + (byte) ((v >>> 16) & 0xFF), + (byte) ((v >>> 24) & 0xFF)); + } + + /** + * Overwrites the primitive in the internal buffer. Does not change the buffer length or* + * position. + * + * @param dest The offset in the internal buffer to write the bytes. + * @param v The value to encode. + * @param bigEndian Whether to encode in big or little endian byte ordering. + */ + public DSByteBuffer replaceShort(int dest, short v, boolean bigEndian) { + if (offset > 0) { + System.arraycopy(buffer, offset, buffer, 0, length); + offset = 0; + } + if (bigEndian) { + return replace(dest, (byte) ((v >>> 8) & 0xFF), (byte) ((v >>> 0) & 0xFF)); + } + return replace(dest, (byte) ((v >>> 0) & 0xFF), (byte) ((v >>> 8) & 0xFF)); + } + /** * Push bytes from the internal buffer to the given buffer. * @@ -487,6 +534,22 @@ public void sendTo(OutputStream out) { } } + /** + * Skip forward some bytes, usually to be replaced later. + */ + public void skip(int len) { + int bufLen = buffer.length; + if ((len + length + offset) >= bufLen) { + if ((len + length) > bufLen) { + growBuffer(len + length); + } else { //offset must be > 0 + System.arraycopy(buffer, offset, buffer, 0, length); + offset = 0; + } + } + length += len; + } + /** * Returns a new array. */ diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/BaseMessage.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/AbstractMessage.java similarity index 90% rename from dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/BaseMessage.java rename to dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/AbstractMessage.java index 8fa9ec59..b5d4943c 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/BaseMessage.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/AbstractMessage.java @@ -8,7 +8,7 @@ * * @author Aaron Hansen */ -public class BaseMessage implements OutboundMessage { +public class AbstractMessage implements OutboundMessage { ///////////////////////////////////////////////////////////////// // Fields @@ -23,10 +23,10 @@ public class BaseMessage implements OutboundMessage { // Constructors ///////////////////////////////////////////////////////////////// - public BaseMessage() { + public AbstractMessage() { } - public BaseMessage(Integer requestId) { + public AbstractMessage(Integer requestId) { this.rid = requestId; } @@ -39,7 +39,7 @@ public String getPath() { } public String getMethod() { - return path; + return method; } public Integer getRequestId() { @@ -53,7 +53,7 @@ public String getStream() { /** * Looks for the path, method and request ID. */ - public BaseMessage parseRequest(DSMap map) { + public AbstractMessage parseRequest(DSMap map) { setPath(map.get("path", null)); setMethod(map.get("method", null)); setRequestId(map.get("rid", -1)); @@ -63,7 +63,7 @@ public BaseMessage parseRequest(DSMap map) { /** * Optional, null by default. */ - public BaseMessage setPath(String arg) { + public AbstractMessage setPath(String arg) { path = arg; return this; } @@ -71,7 +71,7 @@ public BaseMessage setPath(String arg) { /** * Optional, null by default. */ - public BaseMessage setMethod(String arg) { + public AbstractMessage setMethod(String arg) { method = arg; return this; } @@ -87,7 +87,7 @@ public OutboundMessage setRequestId(Integer rid) { /** * Optional, null by default. */ - public BaseMessage setStream(String arg) { + public AbstractMessage setStream(String arg) { stream = arg; return this; } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/CloseMessage.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/CloseMessage.java deleted file mode 100644 index 89ab6b8a..00000000 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/CloseMessage.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.acuity.iot.dsa.dslink.protocol.message; - -/** - * A close request or response. - * - * @author Aaron Hansen - */ -public class CloseMessage extends BaseMessage implements OutboundMessage { - - ///////////////////////////////////////////////////////////////// - // Fields - ///////////////////////////////////////////////////////////////// - - ///////////////////////////////////////////////////////////////// - // Constructors - ///////////////////////////////////////////////////////////////// - - public CloseMessage() { - setMethod("close"); - setStream("closed"); - } - - public CloseMessage(Integer rid) { - this(); - setRequestId(rid); - setStream("closed"); - } - - ///////////////////////////////////////////////////////////////// - // Methods - In alphabetical order by method name. - ///////////////////////////////////////////////////////////////// - - /** - * Calls the super implementation then writes the error object and closes the entire response - * object. - */ - public void write(MessageWriter writer) { - super.write(writer); - writer.getWriter().endMap(); - } - - ///////////////////////////////////////////////////////////////// - // Methods - Protected and in alphabetical order by method name. - ///////////////////////////////////////////////////////////////// - - ///////////////////////////////////////////////////////////////// - // Methods - Package and in alphabetical order by method name. - ///////////////////////////////////////////////////////////////// - - ///////////////////////////////////////////////////////////////// - // Methods - Private and in alphabetical order by method name. - ///////////////////////////////////////////////////////////////// - - ///////////////////////////////////////////////////////////////// - // Inner Classes - in alphabetical order by class name. - ///////////////////////////////////////////////////////////////// - - ///////////////////////////////////////////////////////////////// - // Facets - in alphabetical order by field name. - ///////////////////////////////////////////////////////////////// - - ///////////////////////////////////////////////////////////////// - // Initialization - ///////////////////////////////////////////////////////////////// - -} diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/ErrorResponse.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/ErrorResponse.java index 33689f00..b415113e 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/ErrorResponse.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/ErrorResponse.java @@ -14,7 +14,7 @@ * * @author Aaron Hansen */ -public class ErrorResponse extends BaseMessage implements OutboundMessage { +public class ErrorResponse extends AbstractMessage implements OutboundMessage { ///////////////////////////////////////////////////////////////// // Fields diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/CloseMessage.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/CloseMessage.java new file mode 100644 index 00000000..ce7f77f1 --- /dev/null +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/CloseMessage.java @@ -0,0 +1,38 @@ +package com.acuity.iot.dsa.dslink.protocol.protocol_v1; + +import com.acuity.iot.dsa.dslink.protocol.message.MessageWriter; +import com.acuity.iot.dsa.dslink.protocol.message.OutboundMessage; +import org.iot.dsa.io.DSIWriter; + +/** + * Responder uses to close streams without errors. + * + * @author Aaron Hansen + */ +public class CloseMessage implements OutboundMessage { + + private boolean method = false; + private Integer rid; + + public CloseMessage(Integer requestId) { + this.rid = requestId; + } + + public CloseMessage(Integer requestId, boolean method) { + this.rid = requestId; + this.method = method; + } + + @Override + public void write(MessageWriter writer) { + DSIWriter out = writer.getWriter(); + out.beginMap().key("rid").value(rid); + if (method) { + out.key("method").value("close"); + } else { + out.key("stream").value("closed"); + } + out.endMap(); + } + +} diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1Session.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1Session.java index 1ae3996e..838d995c 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1Session.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1Session.java @@ -37,7 +37,6 @@ public class DS1Session extends DSSession { static final String LAST_ACK_RECV = "Last Ack Recv"; static final String LAST_ACK_SENT = "Last Ack Sent"; - static final int MAX_MSG_ID = 2147483647; static final int MAX_MSG_IVL = 45000; /////////////////////////////////////////////////////////////////////////// @@ -48,8 +47,6 @@ public class DS1Session extends DSSession { private DSInfo lastAckSent = getInfo(LAST_ACK_SENT); private long lastMessageSent; private MessageWriter messageWriter; - private int nextAck = -1; - private int nextMsg = 1; private boolean requestsNext = false; private DS1Requester requester = new DS1Requester(this); private DS1Responder responder = new DS1Responder(this); @@ -80,16 +77,11 @@ public DS1Session(DS1LinkConnection connection) { protected void beginMessage() { DSIWriter out = getWriter(); out.beginMap(); - if (++nextMsg > MAX_MSG_ID) { - nextMsg = 1; - } - out.key("msg").value(nextMsg); - synchronized (this) { - if (nextAck > 0) { - out.key("ack").value(nextAck); - put(lastAckSent, DSInt.valueOf(nextAck)); - nextAck = -1; - } + out.key("msg").value(getNextMessageId()); + int nextAck = getNextAck(); + if (nextAck > 0) { + out.key("ack").value(nextAck); + put(lastAckSent, DSInt.valueOf(nextAck)); } } @@ -219,9 +211,6 @@ private boolean hasPingToSend() { * Override point, returns true if there are any pending acks or outbound messages queued up. */ protected boolean hasSomethingToSend() { - if (nextAck > 0) { - return true; - } if (hasPingToSend()) { return true; } @@ -310,7 +299,7 @@ protected void processEnvelope(DSIReader reader) { next = reader.next(); } while (next != END_MAP); if (sendAck && (msg >= 0)) { - sendAck(msg); + setNextAck(msg); } } @@ -387,14 +376,6 @@ private void send(boolean requests, long endTime) { } } - /** - * We need to send an ack to the broker. - */ - private synchronized void sendAck(int msg) { - nextAck = msg; - notifyOutgoing(); - } - /** * Returns true if the current message size has crossed a message size threshold. */ diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1Requester.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1Requester.java index d324c54d..187e6e10 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1Requester.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1Requester.java @@ -1,7 +1,7 @@ package com.acuity.iot.dsa.dslink.protocol.protocol_v1.requester; -import com.acuity.iot.dsa.dslink.protocol.message.CloseMessage; import com.acuity.iot.dsa.dslink.protocol.message.OutboundMessage; +import com.acuity.iot.dsa.dslink.protocol.protocol_v1.CloseMessage; import com.acuity.iot.dsa.dslink.protocol.protocol_v1.DS1Session; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -137,7 +137,7 @@ void removeRequest(Integer rid) { void sendClose(Integer rid) { requests.remove(rid); - sendRequest(new CloseMessage(rid)); + sendRequest(new CloseMessage(rid, true)); } void sendRequest(OutboundMessage res) { diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundList.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundList.java deleted file mode 100644 index 72487d90..00000000 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundList.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.acuity.iot.dsa.dslink.protocol.protocol_v1.responder; - -import com.acuity.iot.dsa.dslink.protocol.message.ErrorResponse; -import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundList; -import org.iot.dsa.node.DSMap; - -/** - * List implementation for a responder. - * - * @author Aaron Hansen - */ -class DS1InboundList extends DSInboundList { - - private DSMap request; - - @Override - protected ErrorResponse makeError(Throwable reason) { - ErrorResponse ret = new ErrorResponse(reason); - ret.parseRequest(request); - return ret; - } - - public DS1InboundList setRequest(DSMap request) { - this.request = request; - return this; - } - -} diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundRequest.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundRequest.java deleted file mode 100644 index d5070b16..00000000 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundRequest.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.acuity.iot.dsa.dslink.protocol.protocol_v1.responder; - -import com.acuity.iot.dsa.dslink.protocol.message.ErrorResponse; -import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundRequest; -import org.iot.dsa.dslink.responder.InboundRequest; -import org.iot.dsa.node.DSMap; - -/** - * Getters and setters common to most requests. - * - * @author Aaron Hansen - */ -class DS1InboundRequest extends DSInboundRequest implements InboundRequest { - - private DSMap request; - - public DSMap getRequest() { - return request; - } - - protected ErrorResponse makeError(Throwable reason) { - ErrorResponse ret = new ErrorResponse(reason); - ret.parseRequest(request); - return ret; - } - - public DS1InboundRequest setRequest(DSMap request) { - this.request = request; - return this; - } - - public DS1Responder getResponder() { - return (DS1Responder) super.getResponder(); - } - -} diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1Responder.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1Responder.java index f1260dd9..5c8a715c 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1Responder.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1Responder.java @@ -3,16 +3,22 @@ import com.acuity.iot.dsa.dslink.DSProtocolException; import com.acuity.iot.dsa.dslink.DSSession; import com.acuity.iot.dsa.dslink.protocol.DSStream; -import com.acuity.iot.dsa.dslink.protocol.message.CloseMessage; -import com.acuity.iot.dsa.dslink.protocol.message.ErrorResponse; +import com.acuity.iot.dsa.dslink.protocol.protocol_v1.CloseMessage; +import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundInvoke; +import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundList; +import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundRequest; +import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundSet; +import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundSubscriptions; import com.acuity.iot.dsa.dslink.protocol.responder.DSResponder; import java.util.Map; import org.iot.dsa.DSRuntime; +import org.iot.dsa.node.DSElement; import org.iot.dsa.node.DSList; import org.iot.dsa.node.DSMap; +import org.iot.dsa.security.DSPermission; /** - * Implements DSA 1.1.2 + * Implementation DSA v1. * * @author Aaron Hansen */ @@ -22,8 +28,7 @@ public class DS1Responder extends DSResponder { // Fields /////////////////////////////////////////////////////////////////////////// - private DS1InboundSubscriptions subscriptions = - new DS1InboundSubscriptions(this); + private DSInboundSubscriptions subscriptions = new DSInboundSubscriptions(this); ///////////////////////////////////////////////////////////////// // Methods - Constructors @@ -48,10 +53,6 @@ private String getPath(DSMap req) { return path; } - public DS1InboundSubscriptions getSubscriptions() { - return subscriptions; - } - /** * Process an individual request. */ @@ -59,12 +60,12 @@ public void handleRequest(final Integer rid, final DSMap map) { String method = map.get("method", null); try { if ((method == null) || method.isEmpty()) { - throwInvalidMethod(method, map); + sendInvalidMethod(rid, method); } switch (method.charAt(0)) { case 'c': //close if (!method.equals("close")) { - throwInvalidMethod(method, map); + sendInvalidMethod(rid, method); } final DSStream req = removeRequest(rid); if (req != null) { @@ -81,13 +82,13 @@ public void run() { break; case 'i': //invoke if (!method.equals("invoke")) { - throwInvalidMethod(method, map); + sendInvalidMethod(rid, method); } processInvoke(rid, map); break; case 'l': //list if (!method.equals("list")) { - throwInvalidMethod(method, map); + sendInvalidMethod(rid, method); } processList(rid, map); break; @@ -95,10 +96,9 @@ public void run() { if (method.equals("remove")) { //Does this even make sense in a link? severe("Remove method called"); - sendResponse( - new CloseMessage(rid).setMethod(null).setStream("closed")); + sendClose(rid); } else { - throwInvalidMethod(method, map); + sendInvalidMethod(rid, method); } break; case 's': //set, subscribe @@ -111,14 +111,14 @@ public void run() { processSubscribe(rid, map); } }); - sendResponse(new CloseMessage(rid).setMethod(null)); + sendClose(rid); } else { - throwInvalidMethod(method, map); + sendInvalidMethod(rid, method); } break; case 'u': if (!method.equals("unsubscribe")) { - throwInvalidMethod(method, map); + sendInvalidMethod(rid, method); } DSRuntime.run(new Runnable() { @Override @@ -126,18 +126,16 @@ public void run() { processUnsubscribe(rid, map); } }); - sendResponse(new CloseMessage(rid).setMethod(null)); + sendClose(rid); break; default: - throwInvalidMethod(method, map); + sendInvalidMethod(rid, method); } } catch (DSProtocolException x) { - sendResponse(new ErrorResponse(x).parseRequest(map)); + sendError(rid, x); } catch (Throwable x) { - DSProtocolException px = new DSProtocolException(x.getMessage()); - px.setDetail(x); - px.setType("serverError"); - sendResponse(new ErrorResponse(px).parseRequest(map)); + severe(getPath(), x); + sendError(rid, x); } } @@ -164,9 +162,10 @@ public void onDisconnect() { * Handles an invoke request. */ private void processInvoke(Integer rid, DSMap req) { - DS1InboundInvoke invokeImpl = new DS1InboundInvoke(req); - invokeImpl.setRequest(req) - .setPath(getPath(req)) + String permit = req.get("permit", "config"); + DSPermission permission = DSPermission.forString(permit); + DSInboundInvoke invokeImpl = new DSInboundInvoke(req.getMap("params"), permission); + invokeImpl.setPath(getPath(req)) .setSession(getSession()) .setRequestId(rid) .setLink(getLink()) @@ -179,9 +178,8 @@ private void processInvoke(Integer rid, DSMap req) { * Handles a list request. */ private void processList(Integer rid, DSMap req) { - DS1InboundList listImpl = new DS1InboundList(); - listImpl.setRequest(req) - .setPath(getPath(req)) + DSInboundList listImpl = new DSInboundList(); + listImpl.setPath(getPath(req)) .setSession(getSession()) .setRequestId(rid) .setLink(getLink()) @@ -194,9 +192,11 @@ private void processList(Integer rid, DSMap req) { * Handles a set request. */ private void processSet(Integer rid, DSMap req) { - DS1InboundSet setImpl = new DS1InboundSet(req); - setImpl.setRequest(req) - .setPath(getPath(req)) + String permit = req.get("permit", "config"); + DSPermission permission = DSPermission.forString(permit); + DSElement value = req.get("value"); + DSInboundSet setImpl = new DSInboundSet(value, permission); + setImpl.setPath(getPath(req)) .setSession(getSession()) .setRequestId(rid) .setLink(getLink()) @@ -242,19 +242,31 @@ private void processUnsubscribe(int rid, DSMap req) { sid = list.getInt(i); subscriptions.unsubscribe(sid); } catch (Exception x) { - fine(fine() ? "Unsubscribe: " + sid : null, x); + severe(fine() ? "Unsubscribe: " + sid : null, x); } } } } + public void sendClose(int rid) { + sendResponse(new CloseMessage(rid)); + } + + public void sendError(int rid, Throwable reason) { + sendResponse(new ErrorMessage(rid, reason)); + } + + public void sendError(DSInboundRequest req, Throwable reason) { + sendResponse(new ErrorMessage(req.getRequestId(), reason)); + } + /** * Used throughout processRequest. */ - private void throwInvalidMethod(String methodName, DSMap request) { - String msg = "Invalid method name " + methodName; - finest(finest() ? (msg + ": " + request.toString()) : null); - throw new DSProtocolException(msg).setType("invalidMethod"); + private void sendInvalidMethod(int rid, String methodName) { + String msg = "Invalid method: " + methodName; + severe(msg); + sendResponse(new ErrorMessage(rid, msg).setType("invalidMethod")); } } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/ErrorMessage.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/ErrorMessage.java new file mode 100644 index 00000000..8b71f3bb --- /dev/null +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/ErrorMessage.java @@ -0,0 +1,77 @@ +package com.acuity.iot.dsa.dslink.protocol.protocol_v1.responder; + +import com.acuity.iot.dsa.dslink.protocol.message.MessageWriter; +import com.acuity.iot.dsa.dslink.protocol.message.OutboundMessage; +import java.io.PrintWriter; +import java.io.StringWriter; +import org.iot.dsa.dslink.DSInvalidPathException; +import org.iot.dsa.dslink.DSPermissionException; +import org.iot.dsa.dslink.DSRequestException; +import org.iot.dsa.io.DSIWriter; + +/** + * Responder uses to close streams without errors. + * + * @author Aaron Hansen + */ +class ErrorMessage implements OutboundMessage { + + private static String SERVER_ERROR; + + private Integer rid; + private String message; + + private String type = SERVER_ERROR; + + public ErrorMessage(Integer requestId, String message) { + this.rid = requestId; + this.message = message; + } + + public ErrorMessage(Integer requestId, Throwable reason) { + this.rid = requestId; + this.message = toString(reason); + if (reason instanceof DSRequestException) { + if (reason instanceof DSInvalidPathException) { + setType("invalidPath"); + } else if (reason instanceof DSPermissionException) { + setType("permissionDenied"); + } else { + setType("invalidRequest"); + } + } else { + setType("serverError"); + } + } + + public ErrorMessage setType(String type) { + this.type = type; + return this; + } + + private String toString(Throwable arg) { + String msg = arg.getMessage(); + if ((msg != null) && (msg.length() > 0)) { + return msg; + } + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + arg.printStackTrace(pw); + pw.close(); + return sw.toString(); + } + + @Override + public void write(MessageWriter writer) { + DSIWriter out = writer.getWriter(); + out.beginMap() + .key("rid").value(rid) + .key("stream").value("closed"); + out.key("error").beginMap() + .key("type").value(type) + .key("msg").value(message) + .endMap(); + out.endMap(); + } + +} diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/CloseMessage.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/CloseMessage.java new file mode 100644 index 00000000..7c0f169b --- /dev/null +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/CloseMessage.java @@ -0,0 +1,37 @@ +package com.acuity.iot.dsa.dslink.protocol.protocol_v2; + +import com.acuity.iot.dsa.dslink.protocol.message.MessageWriter; +import com.acuity.iot.dsa.dslink.protocol.message.OutboundMessage; +import com.acuity.iot.dsa.dslink.protocol.protocol_v2.responder.DS2Responder; + +/** + * Responder uses to close streams without errors. + * + * @author Aaron Hansen + */ +public class CloseMessage implements MessageConstants, OutboundMessage { + + private byte method = MSG_CLOSE; + private DS2Responder responder; + private int rid; + + public CloseMessage(DS2Responder responder, int requestId) { + this.responder = responder; + this.rid = requestId; + } + + public CloseMessage(DS2Responder responder, int requestId, byte method) { + this.method = method; + this.responder = responder; + this.rid = requestId; + } + + @Override + public void write(MessageWriter writer) { + DS2MessageWriter out = (DS2MessageWriter) writer; + out.init(rid, responder.getSession().getNextAck()); + out.setMethod(method); + out.write(responder.getTransport()); + } + +} diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java index 17fa95cd..d148e3d9 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java @@ -1,5 +1,6 @@ package com.acuity.iot.dsa.dslink.protocol.protocol_v2; +import com.acuity.iot.dsa.dslink.io.DSByteBuffer; import com.acuity.iot.dsa.dslink.transport.DSBinaryTransport; import com.acuity.iot.dsa.dslink.transport.DSTransport; import com.acuity.iot.dsa.dslink.transport.SocketTransport; @@ -214,8 +215,6 @@ private void recvF3() throws IOException { } //TODO check for header status put(requesterAllowed, DSBool.valueOf(in.read() == 1)); - String sessionId = reader.readString(in); //TODO remove - int lastAck = DSBytes.readInt(in, false); //TODO remove String pathOnBroker = reader.readString(in); put(brokerPath, DSString.valueOf(pathOnBroker)); byte[] tmp = new byte[32]; @@ -229,7 +228,7 @@ private void sendF0() { DSKeys dsKeys = link.getKeys(); DS2MessageWriter writer = new DS2MessageWriter(); writer.setMethod((byte) 0xf0); - ByteBuffer buffer = writer.getBody(); + DSByteBuffer buffer = writer.getBody(); buffer.put((byte) 2).put((byte) 0); //dsa version writer.writeString(dsId, buffer); buffer.put(dsKeys.encodePublic()); @@ -240,15 +239,13 @@ private void sendF0() { private void sendF2() throws Exception { DS2MessageWriter writer = new DS2MessageWriter(); writer.setMethod((byte) 0xf2); - ByteBuffer buffer = writer.getBody(); + DSByteBuffer buffer = writer.getBody(); String token = getLink().getConfig().getToken(); if (token == null) { token = ""; } writer.writeString(token, buffer); buffer.put((byte) 0x01); //isResponder - writer.writeString("", buffer); //TODO remove - writer.writeIntLE(0, buffer); //TODO remove writer.writeString("", buffer); //blank server path byte[] sharedSecret = getLink().getKeys().generateSharedSecret( brokerPubKey.getElement().toBytes()); diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageReader.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageReader.java index 7b0aca08..e96fdec8 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageReader.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageReader.java @@ -7,6 +7,8 @@ import java.nio.charset.CharsetDecoder; import java.util.HashMap; import java.util.Map; +import org.iot.dsa.io.DSIReader; +import org.iot.dsa.io.msgpack.MsgpackReader; import org.iot.dsa.node.DSBytes; import org.iot.dsa.node.DSString; import org.iot.dsa.util.DSException; @@ -29,6 +31,7 @@ public class DS2MessageReader implements MessageConstants { private Map headers = new HashMap(); private InputStream input; private int method; + private DSIReader reader; private int requestId; private ByteBuffer strBuffer; private CharsetDecoder utf8decoder = DSString.UTF8.newDecoder(); @@ -50,6 +53,13 @@ public InputStream getBody() { return input; } + public DSIReader getBodyReader() { + if (reader == null) { + reader = new MsgpackReader(input); + } + return reader; + } + public int getBodyLength() { return bodyLength; } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageWriter.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageWriter.java index fce6282c..094c854d 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageWriter.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageWriter.java @@ -1,9 +1,9 @@ package com.acuity.iot.dsa.dslink.protocol.protocol_v2; +import com.acuity.iot.dsa.dslink.io.DSByteBuffer; import com.acuity.iot.dsa.dslink.transport.DSBinaryTransport; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.nio.CharBuffer; import java.nio.charset.CharsetEncoder; import org.iot.dsa.node.DSString; @@ -20,9 +20,9 @@ public class DS2MessageWriter implements MessageConstants { // Fields // ------ - private ByteBuffer body; + private DSByteBuffer body; private CharBuffer charBuffer; - private ByteBuffer header; + private DSByteBuffer header; private byte method; private ByteBuffer strBuffer; private CharsetEncoder utf8encoder; @@ -31,10 +31,8 @@ public class DS2MessageWriter implements MessageConstants { // ------------ public DS2MessageWriter() { - body = ByteBuffer.allocateDirect(MAX_HEADER); - //do not change endian-ness of the body since most bodies will be big endian msgpack. - header = ByteBuffer.allocateDirect(MAX_BODY); - header.order(ByteOrder.LITTLE_ENDIAN); + header = new DSByteBuffer(); + body = new DSByteBuffer(); utf8encoder = DSString.UTF8.newEncoder(); init(-1, -1); } @@ -64,7 +62,7 @@ public DS2MessageWriter addHeader(byte key, byte value) { */ public DS2MessageWriter addHeader(byte key, int value) { header.put(key); - header.putInt(value); + header.putInt(value, false); return this; } @@ -78,19 +76,19 @@ public DS2MessageWriter addHeader(byte key, String value) { } private void encodeHeaderLengths() { - int hlen = header.position(); - int blen = body.position(); - header.putInt(0, hlen + blen); - header.putShort(4, (short) hlen); - header.put(6, method); + int hlen = header.length(); + int blen = body.length(); + header.replaceInt(0, hlen + blen, false); + header.replaceShort(4, (short) hlen, false); + header.replace(6, method); } public int getBodyLength() { - return body.position(); + return body.length(); } public int getHeaderLength() { - return header.position(); + return header.length(); } /** @@ -142,7 +140,7 @@ private ByteBuffer getStringBuffer(int len) { return strBuffer; } - public ByteBuffer getBody() { + public DSByteBuffer getBody() { return body; } @@ -155,7 +153,7 @@ public ByteBuffer getBody() { public DS2MessageWriter init(int requestId, int ackId) { body.clear(); header.clear(); - header.position(7); + header.skip(7); if (requestId >= 0) { header.putInt(requestId); if (ackId >= 0) { @@ -174,17 +172,10 @@ public DS2MessageWriter setMethod(byte method) { * This is for testing, it encodes the full message. */ public byte[] toByteArray() { - int len = header.position() + body.position(); - ByteArrayOutputStream out = new ByteArrayOutputStream(len); + ByteArrayOutputStream out = new ByteArrayOutputStream(header.length() + body.length()); encodeHeaderLengths(); - header.flip(); - while (header.hasRemaining()) { - out.write(header.get()); - } - body.flip(); - while (body.hasRemaining()) { - out.write(body.get()); - } + header.sendTo(out); + body.sendTo(out); return out.toByteArray(); } @@ -193,95 +184,93 @@ public byte[] toByteArray() { */ public DS2MessageWriter write(DSBinaryTransport out) { encodeHeaderLengths(); - //TODO out.write(header, false); - //TODO out.write(body, true); + header.sendTo(out, false); + body.sendTo(out, true); return this; } /** * DSA 2.n encodes a string into the the given buffer. */ - public void writeString(CharSequence str, ByteBuffer buf) { + public void writeString(CharSequence str, DSByteBuffer buf) { CharBuffer chars = getCharBuffer(str); ByteBuffer strBuffer = getStringBuffer( chars.position() * (int) utf8encoder.maxBytesPerChar()); utf8encoder.encode(chars, strBuffer, false); - int len = strBuffer.position(); - writeShortLE((short) len, buf); - strBuffer.flip(); + buf.putShort((short) strBuffer.position(), false); buf.put(strBuffer); } /** * Writes a little endian integer to the buffer. + public void writeIntLE(int v, ByteBuffer buf) { + buf.put((byte) ((v >>> 0) & 0xFF)); + buf.put((byte) ((v >>> 8) & 0xFF)); + buf.put((byte) ((v >>> 16) & 0xFF)); + buf.put((byte) ((v >>> 32) & 0xFF)); + } */ - public void writeIntLE(int v, ByteBuffer buf) { - buf.put((byte) ((v >>> 0) & 0xFF)); - buf.put((byte) ((v >>> 8) & 0xFF)); - buf.put((byte) ((v >>> 16) & 0xFF)); - buf.put((byte) ((v >>> 32) & 0xFF)); - } /** * Writes a little endian integer to the buffer. + public void writeIntLE(int v, ByteBuffer buf, int idx) { + buf.put(idx, (byte) ((v >>> 0) & 0xFF)); + buf.put(++idx, (byte) ((v >>> 8) & 0xFF)); + buf.put(++idx, (byte) ((v >>> 16) & 0xFF)); + buf.put(++idx, (byte) ((v >>> 32) & 0xFF)); + } */ - public void writeIntLE(int v, ByteBuffer buf, int idx) { - buf.put(idx, (byte) ((v >>> 0) & 0xFF)); - buf.put(++idx, (byte) ((v >>> 8) & 0xFF)); - buf.put(++idx, (byte) ((v >>> 16) & 0xFF)); - buf.put(++idx, (byte) ((v >>> 32) & 0xFF)); - } /** * Writes a little endian short to the buffer. + public void writeShortLE(short v, ByteBuffer buf) { + buf.put((byte) ((v >>> 0) & 0xFF)); + buf.put((byte) ((v >>> 8) & 0xFF)); + } */ - public void writeShortLE(short v, ByteBuffer buf) { - buf.put((byte) ((v >>> 0) & 0xFF)); - buf.put((byte) ((v >>> 8) & 0xFF)); - } /** * Writes a little endian short to the buffer. + public void writeShortLE(short v, ByteBuffer buf, int idx) { + buf.put(idx, (byte) ((v >>> 0) & 0xFF)); + buf.put(++idx, (byte) ((v >>> 8) & 0xFF)); + } */ - public void writeShortLE(short v, ByteBuffer buf, int idx) { - buf.put(idx, (byte) ((v >>> 0) & 0xFF)); - buf.put(++idx, (byte) ((v >>> 8) & 0xFF)); - } /** * Writes an unsigned 16 bit little endian integer to the buffer. + public void writeU16LE(int v, ByteBuffer buf) { + buf.put((byte) ((v >>> 0) & 0xFF)); + buf.put((byte) ((v >>> 8) & 0xFF)); + } */ - public void writeU16LE(int v, ByteBuffer buf) { - buf.put((byte) ((v >>> 0) & 0xFF)); - buf.put((byte) ((v >>> 8) & 0xFF)); - } /** * Writes an unsigned 16 bit little endian integer to the buffer. + public void writeU16LE(int v, ByteBuffer buf, int idx) { + buf.put(idx, (byte) ((v >>> 0) & 0xFF)); + buf.put(++idx, (byte) ((v >>> 8) & 0xFF)); + } */ - public void writeU16LE(int v, ByteBuffer buf, int idx) { - buf.put(idx, (byte) ((v >>> 0) & 0xFF)); - buf.put(++idx, (byte) ((v >>> 8) & 0xFF)); - } /** * Writes an unsigned 32 bit little endian integer to the buffer. + public void writeU32LE(long v, ByteBuffer buf) { + buf.put((byte) ((v >>> 0) & 0xFF)); + buf.put((byte) ((v >>> 8) & 0xFF)); + buf.put((byte) ((v >>> 16) & 0xFF)); + buf.put((byte) ((v >>> 32) & 0xFF)); + } */ - public void writeU32LE(long v, ByteBuffer buf) { - buf.put((byte) ((v >>> 0) & 0xFF)); - buf.put((byte) ((v >>> 8) & 0xFF)); - buf.put((byte) ((v >>> 16) & 0xFF)); - buf.put((byte) ((v >>> 32) & 0xFF)); - } /** * Writes an unsigned 32 bit little endian integer to the buffer. + public void writeU32LE(long v, ByteBuffer buf, int idx) { + buf.put(idx, (byte) ((v >>> 0) & 0xFF)); + buf.put(++idx, (byte) ((v >>> 8) & 0xFF)); + buf.put(++idx, (byte) ((v >>> 16) & 0xFF)); + buf.put(++idx, (byte) ((v >>> 32) & 0xFF)); + } */ - public void writeU32LE(long v, ByteBuffer buf, int idx) { - buf.put(idx, (byte) ((v >>> 0) & 0xFF)); - buf.put(++idx, (byte) ((v >>> 8) & 0xFF)); - buf.put(++idx, (byte) ((v >>> 16) & 0xFF)); - buf.put(++idx, (byte) ((v >>> 32) & 0xFF)); - } } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java index af9df4ba..e2a38428 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java @@ -39,7 +39,7 @@ public class DS2Session extends DSSession implements MessageConstants { private int nextMsg = 1; private boolean requestsNext = false; private DS1Requester requester;// = new DS1Requester(this); - private DS2Responder responder = new DS2Responder(this); + private DS2Responder responder = null;//new DS2Responder(this); //todo ///////////////////////////////////////////////////////////////// // Constructors @@ -154,7 +154,7 @@ public void onConnectFail() { public void onDisconnect() { super.onDisconnect(); requester.onDisconnect(); - //responder.onDisconnect(); + responder.onDisconnect(); } /** diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundInvoke.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundInvoke.java new file mode 100644 index 00000000..d999786d --- /dev/null +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundInvoke.java @@ -0,0 +1,33 @@ +package com.acuity.iot.dsa.dslink.protocol.protocol_v2.responder; + +import com.acuity.iot.dsa.dslink.protocol.message.MessageWriter; +import com.acuity.iot.dsa.dslink.protocol.protocol_v2.DS2MessageWriter; +import com.acuity.iot.dsa.dslink.protocol.protocol_v2.MessageConstants; +import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundInvoke; +import com.acuity.iot.dsa.dslink.transport.DSBinaryTransport; +import org.iot.dsa.node.DSMap; +import org.iot.dsa.security.DSPermission; + +/** + * List implementation for a responder. + * + * @author Aaron Hansen + */ +class DS2InboundInvoke extends DSInboundInvoke implements MessageConstants { + + DS2InboundInvoke(DSMap parameters, DSPermission permission) { + super(parameters, permission); + } + + @Override + public void write(MessageWriter writer) { + //if has remaining multipart, send that + DS2MessageWriter out = (DS2MessageWriter) writer; + out.init(getRequestId(), getSession().getNextAck()); + out.setMethod((byte) MSG_INVOKE_RES); + super.write(writer); + out.write((DSBinaryTransport)getResponder().getTransport()); + //if has multipart + } + +} diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundList.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundList.java index a1f10ef9..f34880f9 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundList.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundList.java @@ -1,29 +1,27 @@ package com.acuity.iot.dsa.dslink.protocol.protocol_v2.responder; -import com.acuity.iot.dsa.dslink.protocol.message.ErrorResponse; import com.acuity.iot.dsa.dslink.protocol.message.MessageWriter; +import com.acuity.iot.dsa.dslink.protocol.protocol_v2.DS2MessageWriter; +import com.acuity.iot.dsa.dslink.protocol.protocol_v2.MessageConstants; import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundList; +import com.acuity.iot.dsa.dslink.transport.DSBinaryTransport; /** * List implementation for a responder. * * @author Aaron Hansen */ -class DS2InboundList extends DSInboundList { - - @Override - protected ErrorResponse makeError(Throwable reason) { - ErrorResponse ret = new ErrorResponse(reason); - //TODO - return ret; - } +class DS2InboundList extends DSInboundList implements MessageConstants { @Override public void write(MessageWriter writer) { - //Prep response - //if multipart message, send next part + //if has remaining multipart, send that + DS2MessageWriter out = (DS2MessageWriter) writer; + out.init(getRequestId(), getSession().getNextAck()); + out.setMethod((byte) MSG_LIST_RES); super.write(writer); - //get bytes determine if multi part message + out.write((DSBinaryTransport)getResponder().getTransport()); + //if has multipart } } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSet.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSet.java new file mode 100644 index 00000000..2e5a3ed0 --- /dev/null +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSet.java @@ -0,0 +1,23 @@ +package com.acuity.iot.dsa.dslink.protocol.protocol_v2.responder; + +import com.acuity.iot.dsa.dslink.protocol.protocol_v2.CloseMessage; +import com.acuity.iot.dsa.dslink.protocol.protocol_v2.MessageConstants; +import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundSet; +import com.acuity.iot.dsa.dslink.transport.DSBinaryTransport; +import org.iot.dsa.node.DSElement; +import org.iot.dsa.security.DSPermission; + +public class DS2InboundSet extends DSInboundSet implements MessageConstants { + + public DS2InboundSet(DSElement value, DSPermission permission) { + super(value, permission); + } + + @Override + protected void sendClose() { + getResponder().sendResponse(new CloseMessage((DS2Responder) getResponder(), + getRequestId(), + (byte)MSG_SET_RES)); + } + +} diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscription.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscription.java new file mode 100644 index 00000000..3f118570 --- /dev/null +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscription.java @@ -0,0 +1,58 @@ +package com.acuity.iot.dsa.dslink.protocol.protocol_v2.responder; + +import com.acuity.iot.dsa.dslink.io.DSByteBuffer; +import com.acuity.iot.dsa.dslink.protocol.message.MessageWriter; +import com.acuity.iot.dsa.dslink.protocol.protocol_v2.DS2MessageWriter; +import com.acuity.iot.dsa.dslink.protocol.protocol_v2.MessageConstants; +import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundSubscription; +import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundSubscriptions; +import com.acuity.iot.dsa.dslink.transport.DSBinaryTransport; +import org.iot.dsa.io.msgpack.MsgpackWriter; +import org.iot.dsa.time.DSTime; + +/** + * Subscribe implementation for the responder. + * + * @author Aaron Hansen + */ +public class DS2InboundSubscription extends DSInboundSubscription implements MessageConstants { + + /////////////////////////////////////////////////////////////////////////// + // Fields + /////////////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////////////// + // Constructors + /////////////////////////////////////////////////////////////////////////// + + protected DS2InboundSubscription(DSInboundSubscriptions manager, + Integer sid, String path, int qos) { + super(manager, sid, path, qos); + } + + /////////////////////////////////////////////////////////////////////////// + // Methods in alphabetical order + /////////////////////////////////////////////////////////////////////////// + + @Override + protected void write(Update update, MessageWriter writer, StringBuilder buf) { + DS2MessageWriter out = (DS2MessageWriter) writer; + out.init(getSubscriptionId(), getSession().getNextAck()); + out.setMethod((byte)MSG_SUBSCRIBE_RES); + DSByteBuffer byteBuffer = out.getBody(); + byteBuffer.skip(2); + MsgpackWriter msgpackWriter = new MsgpackWriter(byteBuffer); + msgpackWriter.beginMap(); + buf.setLength(0); + DSTime.encode(update.timestamp, true, buf); + msgpackWriter.key("timestamp").value(buf.toString()); + if (!update.quality.isOk()) { + msgpackWriter.key("status").value(update.quality.toElement()); + } + msgpackWriter.endMap(); + byteBuffer.replaceShort(0, (short) msgpackWriter.length(), false); + msgpackWriter.value(update.value.toElement()); + out.write((DSBinaryTransport)getResponder().getTransport()); + } + +} diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscriptions.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscriptions.java new file mode 100644 index 00000000..dee5cfea --- /dev/null +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscriptions.java @@ -0,0 +1,48 @@ +package com.acuity.iot.dsa.dslink.protocol.protocol_v2.responder; + +import com.acuity.iot.dsa.dslink.protocol.message.MessageWriter; +import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundSubscription; +import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundSubscriptions; +import com.acuity.iot.dsa.dslink.protocol.responder.DSResponder; + +/** + * Subscribe implementation for the responder. + * + * @author Aaron Hansen + */ +public class DS2InboundSubscriptions extends DSInboundSubscriptions { + + /////////////////////////////////////////////////////////////////////////// + // Constants + /////////////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////////////// + // Fields + /////////////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////////////// + // Constructors + /////////////////////////////////////////////////////////////////////////// + + public DS2InboundSubscriptions(DSResponder responder) { + super(responder); + } + + /////////////////////////////////////////////////////////////////////////// + // Methods in alphabetical order + /////////////////////////////////////////////////////////////////////////// + + @Override + protected DSInboundSubscription makeSubscription(Integer sid, String path, int qos) { + return new DS2InboundSubscription(this, sid, path, qos); + } + + @Override + protected void writeBegin(MessageWriter writer) { + } + + @Override + protected void writeEnd(MessageWriter writer) { + } + +} diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java index 6a27c645..8b24cf2f 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java @@ -1,28 +1,32 @@ package com.acuity.iot.dsa.dslink.protocol.protocol_v2.responder; +import com.acuity.iot.dsa.dslink.protocol.DSStream; +import com.acuity.iot.dsa.dslink.protocol.protocol_v2.CloseMessage; import com.acuity.iot.dsa.dslink.protocol.protocol_v2.DS2MessageReader; import com.acuity.iot.dsa.dslink.protocol.protocol_v2.DS2Session; import com.acuity.iot.dsa.dslink.protocol.protocol_v2.MessageConstants; +import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundRequest; +import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundSet; import com.acuity.iot.dsa.dslink.protocol.responder.DSResponder; +import com.acuity.iot.dsa.dslink.transport.DSBinaryTransport; +import java.util.Map; import org.iot.dsa.DSRuntime; +import org.iot.dsa.node.DSElement; +import org.iot.dsa.node.DSMap; +import org.iot.dsa.security.DSPermission; /** - * Implements DSA 1.1.2 + * Implementation for DSA v2. * * @author Aaron Hansen */ public class DS2Responder extends DSResponder implements MessageConstants { - /////////////////////////////////////////////////////////////////////////// - // Constants - /////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////// // Fields /////////////////////////////////////////////////////////////////////////// - //private DS2InboundSubscriptions subscriptions = - //new DS2InboundSubscriptions(this); + private DS2InboundSubscriptions subscriptions = new DS2InboundSubscriptions(this); ///////////////////////////////////////////////////////////////// // Methods - Constructors @@ -36,12 +40,20 @@ public DS2Responder(DS2Session session) { // Methods - In alphabetical order by method name. ///////////////////////////////////////////////////////////////// + public DSBinaryTransport getTransport() { + return (DSBinaryTransport) getConnection().getTransport(); + } + /** * Process an individual request. */ public void handleRequest(DS2MessageReader reader) { switch (reader.getMethod()) { + case MSG_CLOSE: + processClose(reader); + break; case MSG_INVOKE_REQ: + processInvoke(reader); break; case MSG_LIST_REQ: processList(reader); @@ -49,8 +61,10 @@ public void handleRequest(DS2MessageReader reader) { case MSG_OBSERVE_REQ: break; case MSG_SET_REQ: + processSet(reader); break; case MSG_SUBSCRIBE_REQ: + processSubscribe(reader); break; default: throw new IllegalArgumentException("Unexpected method: " + reader.getMethod()); @@ -63,34 +77,51 @@ public void onConnect() { public void onConnectFail() { } - /* public void onDisconnect() { finer(finer() ? "Close" : null); subscriptions.close(); - for (Map.Entry entry : inboundRequests.entrySet()) { + for (Map.Entry entry : getRequests().entrySet()) { try { entry.getValue().onClose(entry.getKey()); } catch (Exception x) { finer(finer() ? "Close" : null, x); } } - inboundRequests.clear(); + getRequests().clear(); } - */ /** * Handles an invoke request. - private void processInvoke(Integer rid, DSMap req) { - DS2InboundInvoke invokeImpl = new DS2InboundInvoke(req); - invokeImpl.setPath(getPath(req)) - .setSession(session) - .setRequestId(rid) - .setResponderImpl(responder) - .setResponder(this); - inboundRequests.put(rid, invokeImpl); - DSRuntime.run(invokeImpl); - } */ + private void processClose(DS2MessageReader msg) { + int rid = msg.getRequestId(); + DSStream stream = getRequests().get(rid); + if (stream != null) { + stream.onClose(rid); + } else { + subscriptions.unsubscribe(rid); + } + } + + /** + * Handles an invoke request. + */ + private void processInvoke(DS2MessageReader msg) { + int rid = msg.getRequestId(); + DSMap params = msg.getBodyReader().getMap(); + DSPermission perm = DSPermission.READ; + Object obj = msg.getHeader(MessageConstants.HDR_MAX_PERMISSION); + if (obj != null) { + perm = DSPermission.valueOf(obj.hashCode()); + } + DS2InboundInvoke invokeImpl = new DS2InboundInvoke(params, perm); + invokeImpl.setPath((String) msg.getHeader(HDR_TARGET_PATH)) + .setSession(getSession()) + .setRequestId(rid) + .setResponder(this); + putRequest(rid, invokeImpl); + DSRuntime.run(invokeImpl); + } /** * Handles a list request. @@ -109,68 +140,46 @@ private void processList(DS2MessageReader msg) { /** * Handles a set request. - private void processSet(Integer rid, DSMap req) { - DS2InboundSet setImpl = new DS2InboundSet(req); - setImpl.setPath(getPath(req)) - .setSession(session) - .setRequestId(rid) - .setResponderImpl(responder) - .setResponder(this); - DSRuntime.run(setImpl); - } */ + private void processSet(DS2MessageReader msg) { + int rid = msg.getRequestId(); + DSPermission perm = DSPermission.READ; + Object obj = msg.getHeader(MessageConstants.HDR_MAX_PERMISSION); + if (obj != null) { + perm = DSPermission.valueOf(obj.hashCode()); + } + DSElement value = msg.getBodyReader().getElement(); + DSInboundSet setImpl = new DSInboundSet(value, perm); + setImpl.setPath((String) msg.getHeader(HDR_TARGET_PATH)) + .setSession(getSession()) + .setRequestId(rid) + .setResponder(this); + DSRuntime.run(setImpl); + } /** * Handles a subscribe request. - private void processSubscribe(int rid, DSMap req) { - DSList list = req.getList("paths"); - if (list == null) { - return; - } - String path; - Integer sid; - Integer qos; - DSMap subscribe; - for (int i = 0, len = list.size(); i < len; i++) { - subscribe = list.getMap(i); - path = subscribe.getString("path"); - sid = subscribe.getInt("sid"); - qos = subscribe.get("qos", 0); - try { - subscriptions.subscribe(responder, sid, path, qos); - } catch (Exception x) { - //invalid paths are very common - fine(path, x); - } - } - } */ + private void processSubscribe(DS2MessageReader msg) { + Integer sid = msg.getRequestId(); + String path = (String) msg.getHeader(HDR_TARGET_PATH); + Integer qos = (Integer) msg.getHeader(MessageConstants.HDR_QOS); + if (qos == null) { + qos = Integer.valueOf(0); + } + //Integer queueSize = (Integer) msg.getHeader(MessageConstants.HDR_QUEUE_SIZE); + subscriptions.subscribe(sid, path, qos); + } - /** - * Handles an unsubscribe request. - * private void processUnsubscribe(int rid, DSMap req) { - * DSList list = req.getList("sids"); - * Integer sid = null; - * if (list != null) { - * for (int i = 0, len = list.size(); i < len; i++) { - * try { - * sid = list.getInt(i); - * subscriptions.unsubscribe(sid); - * } catch (Exception x) { - * fine(fine() ? "Unsubscribe: " + sid : null, x); - * } - * } - * } - * } - */ + @Override + public void sendClose(int rid) { + sendResponse(new CloseMessage(this, rid)); + } + + @Override + public void sendError(DSInboundRequest req, Throwable reason) { + sendResponse(new ErrorMessage(req, reason)); + } - /** - * Used throughout processRequest. - private void throwInvalidMethod(String methodName, DSMap request) { - String msg = "Invalid method name " + methodName; - finest(finest() ? (msg + ": " + request.toString()) : null); - throw new DSProtocolException(msg).setType("invalidMethod"); - } - */ } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/ErrorMessage.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/ErrorMessage.java new file mode 100644 index 00000000..c79d18ba --- /dev/null +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/ErrorMessage.java @@ -0,0 +1,70 @@ +package com.acuity.iot.dsa.dslink.protocol.protocol_v2.responder; + +import com.acuity.iot.dsa.dslink.protocol.message.MessageWriter; +import com.acuity.iot.dsa.dslink.protocol.message.OutboundMessage; +import com.acuity.iot.dsa.dslink.protocol.protocol_v2.DS2MessageWriter; +import com.acuity.iot.dsa.dslink.protocol.protocol_v2.MessageConstants; +import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundRequest; +import com.acuity.iot.dsa.dslink.transport.DSBinaryTransport; +import org.iot.dsa.dslink.DSInvalidPathException; +import org.iot.dsa.dslink.DSPermissionException; +import org.iot.dsa.dslink.DSRequestException; + +/** + * Responder uses to close streams without errors. + * + * @author Aaron Hansen + */ +class ErrorMessage implements MessageConstants, OutboundMessage { + + private DSInboundRequest req; + private Throwable reason; + + public ErrorMessage(DSInboundRequest req, Throwable reason) { + this.req = req; + this.reason = reason; + } + + /* + private String toString(Throwable arg) { + String msg = arg.getMessage(); + if ((msg != null) && (msg.length() > 0)) { + return msg; + } + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + arg.printStackTrace(pw); + pw.close(); + return sw.toString(); + } + */ + + @Override + public void write(MessageWriter writer) { + DS2MessageWriter out = (DS2MessageWriter) writer; + out.init(req.getRequestId(), req.getSession().getNextAck()); + if (req instanceof DS2InboundInvoke) { + out.setMethod((byte) MSG_INVOKE_RES); + } else if (req instanceof DS2InboundList) { + out.setMethod((byte) MSG_LIST_RES); + } else if (req instanceof DS2InboundSet) { + out.setMethod((byte) MSG_SET_RES); + } else { + out.setMethod((byte) MSG_CLOSE); + } + if (reason instanceof DSRequestException) { + if (reason instanceof DSInvalidPathException) { + out.addHeader(MessageConstants.HDR_STATUS, MessageConstants.STS_NOT_AVAILABLE); + } else if (reason instanceof DSPermissionException) { + out.addHeader(MessageConstants.HDR_STATUS, MessageConstants.STS_PERMISSION_DENIED); + } else { + out.addHeader(MessageConstants.HDR_STATUS, MessageConstants.STS_INVALID_MESSAGE); + } + } else { + //todo need server error + out.addHeader(MessageConstants.HDR_STATUS, MessageConstants.STS_INVALID_MESSAGE); + } + out.write((DSBinaryTransport) req.getResponder().getTransport()); + } + +} diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundInvoke.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundInvoke.java similarity index 85% rename from dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundInvoke.java rename to dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundInvoke.java index 7f75b4a6..f5643dc0 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundInvoke.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundInvoke.java @@ -1,7 +1,6 @@ -package com.acuity.iot.dsa.dslink.protocol.protocol_v1.responder; +package com.acuity.iot.dsa.dslink.protocol.responder; import com.acuity.iot.dsa.dslink.protocol.DSStream; -import com.acuity.iot.dsa.dslink.protocol.message.ErrorResponse; import com.acuity.iot.dsa.dslink.protocol.message.MessageWriter; import com.acuity.iot.dsa.dslink.protocol.message.OutboundMessage; import com.acuity.iot.dsa.dslink.protocol.message.RequestPath; @@ -27,7 +26,7 @@ * * @author Aaron Hansen */ -class DS1InboundInvoke extends DS1InboundRequest +public class DSInboundInvoke extends DSInboundRequest implements DSStream, InboundInvokeRequest, OutboundMessage, Runnable { /////////////////////////////////////////////////////////////////////////// @@ -58,11 +57,9 @@ class DS1InboundInvoke extends DS1InboundRequest // Constructors /////////////////////////////////////////////////////////////////////////// - DS1InboundInvoke(DSMap request) { - setRequest(request); - String permit = request.get("permit", "config"); - permission = DSPermission.forString(permit); - parameters = request.getMap("params"); + public DSInboundInvoke(DSMap parameters, DSPermission permission) { + this.parameters = parameters; + this.permission = permission; } /////////////////////////////////////////////////////////////////////////// @@ -163,36 +160,6 @@ private void enqueueResponse() { getResponder().sendResponse(this); } - /** - * Invokes the action and will then enqueueUpdate the outgoing response. - */ - public void run() { - try { - RequestPath path = new RequestPath(getPath(), getLink()); - if (path.isResponder()) { - DSIResponder responder = (DSIResponder) path.getTarget(); - setPath(path.getPath()); - result = responder.onInvoke(this); - } - DSInfo info = path.getInfo(); - if (!info.isAction()) { - throw new DSRequestException("Not an action " + path.getPath()); - } - //TODO verify incoming permission - DSAction action = info.getAction(); - result = action.invoke(info, this); - } catch (Exception x) { - severe(getPath(), x); - close(x); - return; - } - if (result == null) { - close(); - } else { - enqueueResponse(); - } - } - /** * Any parameters supplied by the requester for the invocation, or null. */ @@ -206,6 +173,13 @@ public DSPermission getPermission() { return permission; } + /** + * Returns "updates" for v1. + */ + protected String getRowsName() { + return "updates"; + } + @Override public void insert(int index, DSList[] rows) { enqueueUpdate(new Update(rows, index, -1, UpdateType.INSERT)); @@ -246,6 +220,36 @@ public void replace(int index, int len, DSList... rows) { enqueueUpdate(new Update(rows, index, len, UpdateType.REPLACE)); } + /** + * Invokes the action and will then enqueueUpdate the outgoing response. + */ + public void run() { + try { + RequestPath path = new RequestPath(getPath(), getLink()); + if (path.isResponder()) { + DSIResponder responder = (DSIResponder) path.getTarget(); + setPath(path.getPath()); + result = responder.onInvoke(this); + } + DSInfo info = path.getInfo(); + if (!info.isAction()) { + throw new DSRequestException("Not an action " + path.getPath()); + } + //TODO verify incoming permission + DSAction action = info.getAction(); + result = action.invoke(info, this); + } catch (Exception x) { + severe(getPath(), x); + close(x); + return; + } + if (result == null) { + close(); + } else { + enqueueResponse(); + } + } + @Override public void send(DSList row) { enqueueUpdate(new Update(row)); @@ -253,49 +257,65 @@ public void send(DSList row) { @Override public void write(MessageWriter writer) { - DSIWriter out = writer.getWriter(); enqueued = false; if (isClosed()) { return; } if (isClosePending() && (updateHead == null) && (closeReason != null)) { - ErrorResponse res = new ErrorResponse(closeReason); - res.parseRequest(getRequest()); - res.write(writer); + getResponder().sendError(this, closeReason); doClose(); return; } - out.beginMap(); - out.key("rid").value(getRequestId()); + writeBegin(writer); switch (state) { case STATE_INIT: - writeColumns(out); - writeInitialResults(out); + writeColumns(writer); + writeInitialResults(writer); break; case STATE_ROWS: - writeInitialResults(out); + writeInitialResults(writer); break; case STATE_CLOSE_PENDING: case STATE_UPDATES: - writeUpdates(out); + writeUpdates(writer); break; default: ; } if (isClosePending() && (updateHead == null)) { if (closeReason != null) { - ErrorResponse res = new ErrorResponse(closeReason); - res.parseRequest(getRequest()); - getResponder().sendResponse(res); + getResponder().sendError(this, closeReason); } else { - out.key("stream").value("closed"); + writeClose(writer); } doClose(); } - out.endMap(); + writeEnd(writer); + } + + /** + * Override point for v2, handles v1. + */ + protected void writeBegin(MessageWriter writer) { + writer.getWriter().beginMap().key("rid").value(getRequestId()); } - private void writeColumns(DSIWriter out) { + /** + * Override point for v2, handles v1. + */ + protected void writeClose(MessageWriter writer) { + writer.getWriter().key("stream").value("closed"); + } + + /** + * Override point for v2, handles v1. + */ + protected void writeEnd(MessageWriter writer) { + writer.getWriter().endMap(); + } + + private void writeColumns(MessageWriter writer) { + DSIWriter out = writer.getWriter(); if (result instanceof ActionValues) { out.key("columns").beginList(); Iterator it = result.getAction().getValueResults(); @@ -325,9 +345,10 @@ private void writeColumns(DSIWriter out) { } } - private void writeInitialResults(DSIWriter out) { + private void writeInitialResults(MessageWriter writer) { + DSIWriter out = writer.getWriter(); state = STATE_ROWS; - out.key("updates").beginList(); + out.key(getRowsName()).beginList(); if (result instanceof ActionValues) { out.beginList(); Iterator values = ((ActionValues) result).getValues(); @@ -342,7 +363,7 @@ private void writeInitialResults(DSIWriter out) { if (rows == null) { rows = ((ActionTable) result).getRows(); } - DS1Responder session = getResponder(); + DSResponder session = getResponder(); while (rows.hasNext()) { out.value(rows.next()); if (session.shouldEndMessage()) { @@ -352,18 +373,25 @@ private void writeInitialResults(DSIWriter out) { } } } - out.endList(); if ((result == null) || !result.getAction().getResultType().isOpen()) { - out.key("stream").value("closed"); + writeClose(writer); state = STATE_CLOSED; doClose(); } else { - out.key("stream").value("open"); + writeOpen(writer); state = STATE_UPDATES; } } - private void writeUpdates(DSIWriter out) { + /** + * Override point for v2, handles v1. + */ + protected void writeOpen(MessageWriter writer) { + writer.getWriter().key("stream").value("open"); + } + + private void writeUpdates(MessageWriter writer) { + DSIWriter out = writer.getWriter(); Update update = updateHead; //peak ahead if (update == null) { return; @@ -375,8 +403,8 @@ private void writeUpdates(DSIWriter out) { .key("meta").beginMap().endMap() .endMap(); } - out.key("updates").beginList(); - DS1Responder session = getResponder(); + out.key(getRowsName()).beginList(); + DSResponder responder = getResponder(); while (true) { update = dequeueUpdate(); if (update.rows != null) { @@ -389,7 +417,7 @@ private void writeUpdates(DSIWriter out) { if ((updateHead == null) || (updateHead.type != null)) { break; } - if (session.shouldEndMessage()) { + if (responder.shouldEndMessage()) { enqueueResponse(); break; } @@ -404,7 +432,7 @@ private void writeUpdates(DSIWriter out) { /** * Used to described more complex updates. */ - private enum UpdateType { + protected enum UpdateType { INSERT("insert"), REFRESH("refresh"), REPLACE("replace"); @@ -423,7 +451,7 @@ public String toString() { /** * Describes an update to be sent to the requester. */ - private static class Update { + protected static class Update { int beginIndex = -1; int endIndex = -1; diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java index 5121d996..4d9f1a0e 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java @@ -1,7 +1,6 @@ package com.acuity.iot.dsa.dslink.protocol.responder; import com.acuity.iot.dsa.dslink.protocol.DSStream; -import com.acuity.iot.dsa.dslink.protocol.message.ErrorResponse; import com.acuity.iot.dsa.dslink.protocol.message.MessageWriter; import com.acuity.iot.dsa.dslink.protocol.message.OutboundMessage; import com.acuity.iot.dsa.dslink.protocol.message.RequestPath; @@ -33,7 +32,7 @@ * * @author Aaron Hansen */ -public abstract class DSInboundList extends DSInboundRequest +public class DSInboundList extends DSInboundRequest implements DSISubscriber, DSStream, InboundListRequest, OutboundMessage, OutboundListResponse, Runnable { @@ -64,10 +63,6 @@ public abstract class DSInboundList extends DSInboundRequest private Update updateHead; private Update updateTail; - /////////////////////////////////////////////////////////////////////////// - // Constructors - /////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////// // Methods in alphabetical order /////////////////////////////////////////////////////////////////////////// @@ -570,8 +565,7 @@ public void write(MessageWriter writer) { return; } if (isClosePending() && (updateHead == null) && (closeReason != null)) { - ErrorResponse res = makeError(closeReason); - res.write(writer); + getResponder().sendError(this, closeReason); doClose(); return; } @@ -600,7 +594,7 @@ public void write(MessageWriter writer) { out.key("stream").value("open"); } else if (isClosePending() && (updateHead == null)) { if (closeReason != null) { - getResponder().sendResponse(makeError(closeReason)); + getResponder().sendError(this, closeReason); } else { out.key("stream").value("closed"); } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundRequest.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundRequest.java index 8ec29dbf..d855f45f 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundRequest.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundRequest.java @@ -47,8 +47,6 @@ public DSSession getSession() { return session; } - protected abstract ErrorResponse makeError(Throwable reason); - public DSInboundRequest setLink(DSLink link) { this.link = link; return this; diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundSet.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSet.java similarity index 71% rename from dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundSet.java rename to dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSet.java index 164120fa..fa59c09f 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundSet.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSet.java @@ -1,7 +1,5 @@ -package com.acuity.iot.dsa.dslink.protocol.protocol_v1.responder; +package com.acuity.iot.dsa.dslink.protocol.responder; -import com.acuity.iot.dsa.dslink.protocol.message.CloseMessage; -import com.acuity.iot.dsa.dslink.protocol.message.ErrorResponse; import com.acuity.iot.dsa.dslink.protocol.message.RequestPath; import org.iot.dsa.dslink.DSIResponder; import org.iot.dsa.dslink.DSRequestException; @@ -9,20 +7,17 @@ import org.iot.dsa.node.DSElement; import org.iot.dsa.node.DSIValue; import org.iot.dsa.node.DSInfo; -import org.iot.dsa.node.DSMap; import org.iot.dsa.node.DSNode; import org.iot.dsa.security.DSPermission; -class DS1InboundSet extends DS1InboundRequest implements InboundSetRequest, Runnable { +public class DSInboundSet extends DSInboundRequest implements InboundSetRequest, Runnable { private DSElement value; private DSPermission permission; - DS1InboundSet(DSMap request) { - setRequest(request); - String permit = request.get("permit", "config"); - permission = DSPermission.forString(permit); - value = request.get("value"); + public DSInboundSet(DSElement value, DSPermission permission) { + this.permission = permission; + this.value = value; } @Override @@ -67,14 +62,18 @@ public void run() { parent.onSet(info, current); } } - getResponder().sendResponse( - new CloseMessage(getRequestId()).setMethod(null).setStream("closed")); + sendClose(); } catch (Exception x) { severe(getPath(), x); - ErrorResponse err = new ErrorResponse(x); - err.parseRequest(getRequest()); - getResponder().sendResponse(err); + getResponder().sendError(this, x); } } + /** + * Override point for V2. + */ + protected void sendClose() { + getResponder().sendClose(getRequestId()); + } + } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundSubscription.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscription.java similarity index 79% rename from dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundSubscription.java rename to dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscription.java index 6547566f..7ab205d0 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundSubscription.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscription.java @@ -1,5 +1,6 @@ -package com.acuity.iot.dsa.dslink.protocol.protocol_v1.responder; +package com.acuity.iot.dsa.dslink.protocol.responder; +import com.acuity.iot.dsa.dslink.protocol.message.MessageWriter; import com.acuity.iot.dsa.dslink.protocol.message.RequestPath; import java.util.logging.Level; import java.util.logging.Logger; @@ -23,7 +24,7 @@ * * @author Aaron Hansen */ -class DS1InboundSubscription extends DS1InboundRequest +public class DSInboundSubscription extends DSInboundRequest implements DSISubscriber, InboundSubscribeRequest { /////////////////////////////////////////////////////////////////////////// @@ -33,7 +34,7 @@ class DS1InboundSubscription extends DS1InboundRequest private DSInfo child; private SubscriptionCloseHandler closeHandler; private boolean enqueued = false; - private DS1InboundSubscriptions manager; + private DSInboundSubscriptions manager; private DSNode node; private boolean open = true; private Integer sid; @@ -45,7 +46,7 @@ class DS1InboundSubscription extends DS1InboundRequest // Constructors /////////////////////////////////////////////////////////////////////////// - DS1InboundSubscription(DS1InboundSubscriptions manager, Integer sid, String path, int qos) { + protected DSInboundSubscription(DSInboundSubscriptions manager, Integer sid, String path, int qos) { this.manager = manager; this.sid = sid; setPath(path); @@ -66,7 +67,7 @@ public void close() { /** * Remove an update from the queue. */ - private synchronized Update dequeue() { + protected synchronized Update dequeue() { if (updateHead == null) { return null; } @@ -94,7 +95,7 @@ public Integer getSubscriptionId() { return sid; } - private void init() { + protected void init() { RequestPath path = new RequestPath(getPath(), getLink()); if (path.isResponder()) { DSIResponder responder = (DSIResponder) path.getTarget(); @@ -173,7 +174,7 @@ public void update(long timestamp, DSIValue value, DSStatus quality) { return; } finest(finest() ? "Update " + getPath() + " to " + value : null); - if (qos <= 1) { + if (qos == 0) { synchronized (this) { if (updateHead == null) { updateHead = updateTail = new Update(); @@ -201,12 +202,12 @@ public void update(long timestamp, DSIValue value, DSStatus quality) { manager.enqueue(this); } - DS1InboundSubscription setQos(int qos) { + protected DSInboundSubscription setQos(int qos) { this.qos = qos; return this; } - DS1InboundSubscription setSubscriptionId(Integer sid) { + protected DSInboundSubscription setSubscriptionId(Integer sid) { this.sid = sid; return this; } @@ -216,34 +217,25 @@ public String toString() { return "Subscription (" + getSubscriptionId() + ") " + getPath(); } + /** - * Encodes as many updates as possible. + * Encodes one or more updates. * - * @param out Where to encode. - * @param buf For encoding timestamps. + * @param writer Where to encode. + * @param buf For encoding timestamps. */ - void write(DSIWriter out, StringBuilder buf) { - //Don't check open state - forcefully closing will send an update - DS1Responder session = getResponder(); + protected void write(MessageWriter writer, StringBuilder buf) { + DSResponder session = getResponder(); Update update = dequeue(); while (update != null) { - out.beginMap(); - out.key("sid").value(getSubscriptionId()); - buf.setLength(0); - DSTime.encode(update.timestamp, true, buf); - out.key("ts").value(buf.toString()); - out.key("value").value(update.value.toElement()); - if ((update.quality != null) && !update.quality.isOk()) { - out.key("quality").value(update.quality.toString()); - } - out.endMap(); - if ((qos <= 1) || session.shouldEndMessage()) { + write(update, writer, buf); + if ((qos == 0) || session.shouldEndMessage()) { break; } } synchronized (this) { if (updateHead == null) { - if (qos <= 1) { + if (qos == 0) { //reuse instance updateHead = updateTail = update; } @@ -254,16 +246,38 @@ void write(DSIWriter out, StringBuilder buf) { manager.enqueue(this); } + /** + * Encode a single update. This is implemented for v1 and will need to be overridden for + * v2. + * + * @param update The udpate to write. + * @param writer Where to write. + * @param buf For encoding timestamps. + */ + protected void write(Update update, MessageWriter writer, StringBuilder buf) { + DSIWriter out = writer.getWriter(); + out.beginMap(); + out.key("sid").value(getSubscriptionId()); + buf.setLength(0); + DSTime.encode(update.timestamp, true, buf); + out.key("ts").value(buf.toString()); + out.key("value").value(update.value.toElement()); + if ((update.quality != null) && !update.quality.isOk()) { + out.key("quality").value(update.quality.toString()); + } + out.endMap(); + } + /////////////////////////////////////////////////////////////////////////// // Inner Classes /////////////////////////////////////////////////////////////////////////// - private class Update { + protected class Update { Update next; - long timestamp; - DSIValue value; - DSStatus quality; + public long timestamp; + public DSIValue value; + public DSStatus quality; Update set(long timestamp, DSIValue value, DSStatus quality) { this.timestamp = timestamp; diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundSubscriptions.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscriptions.java similarity index 63% rename from dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundSubscriptions.java rename to dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscriptions.java index f537372a..bebbcefb 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1InboundSubscriptions.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscriptions.java @@ -1,4 +1,4 @@ -package com.acuity.iot.dsa.dslink.protocol.protocol_v1.responder; +package com.acuity.iot.dsa.dslink.protocol.responder; import com.acuity.iot.dsa.dslink.protocol.message.MessageWriter; import com.acuity.iot.dsa.dslink.protocol.message.OutboundMessage; @@ -7,15 +7,14 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.logging.Logger; import org.iot.dsa.dslink.DSLink; -import org.iot.dsa.io.DSIWriter; -import org.iot.dsa.logging.DSLogger; +import org.iot.dsa.node.DSNode; /** * Subscribe implementation for the responder. * * @author Aaron Hansen */ -class DS1InboundSubscriptions extends DSLogger implements OutboundMessage { +public class DSInboundSubscriptions extends DSNode implements OutboundMessage { /////////////////////////////////////////////////////////////////////////// // Constants @@ -29,20 +28,20 @@ class DS1InboundSubscriptions extends DSLogger implements OutboundMessage { private boolean enqueued = false; private Logger logger; - private ConcurrentLinkedQueue outbound = - new ConcurrentLinkedQueue(); - private Map pathMap = - new ConcurrentHashMap(); - private DS1Responder responder; - private Map sidMap = - new ConcurrentHashMap(); + private ConcurrentLinkedQueue outbound = + new ConcurrentLinkedQueue(); + private Map pathMap = + new ConcurrentHashMap(); + private DSResponder responder; + private Map sidMap = + new ConcurrentHashMap(); private StringBuilder timestampBuffer = new StringBuilder(); /////////////////////////////////////////////////////////////////////////// // Constructors /////////////////////////////////////////////////////////////////////////// - DS1InboundSubscriptions(DS1Responder responder) { + public DSInboundSubscriptions(DSResponder responder) { this.responder = responder; } @@ -53,7 +52,7 @@ class DS1InboundSubscriptions extends DSLogger implements OutboundMessage { /** * Unsubscribes all. */ - void close() { + public void close() { for (Integer i : sidMap.keySet()) { unsubscribe(i); } @@ -62,7 +61,7 @@ void close() { /** * Add to the outbound queue if not already enqueued. */ - void enqueue(DS1InboundSubscription subscription) { + protected void enqueue(DSInboundSubscription subscription) { synchronized (this) { outbound.add(subscription); if (enqueued) { @@ -86,14 +85,25 @@ public Logger getLogger() { return logger; } + /** + * This returns a DSInboundSubscription for v1, this will be overridden for v2. + * + * @param sid Subscription ID. + * @param path Path being subscribed to. + * @param qos Qualityf of service. + */ + protected DSInboundSubscription makeSubscription(Integer sid, String path, int qos) { + return new DSInboundSubscription(this, sid, path, qos); + } + /** * Create or update a subscription. */ - void subscribe(Integer sid, String path, int qos) { + public void subscribe(Integer sid, String path, int qos) { finest(finest() ? "Subscribing " + path : null); - DS1InboundSubscription subscription = sidMap.get(sid); + DSInboundSubscription subscription = sidMap.get(sid); if (subscription == null) { - subscription = new DS1InboundSubscription(this, sid, path, qos); + subscription = makeSubscription(sid, path, qos); sidMap.put(sid, subscription); pathMap.put(path, subscription); } else if (!path.equals(subscription.getPath())) { @@ -108,8 +118,8 @@ void subscribe(Integer sid, String path, int qos) { /** * Remove the subscription and call onClose. */ - void unsubscribe(Integer sid) { - DS1InboundSubscription subscription = sidMap.remove(sid); + public void unsubscribe(Integer sid) { + DSInboundSubscription subscription = sidMap.remove(sid); if (subscription != null) { finest(finest() ? "Unsubscribing " + subscription.getPath() : null); pathMap.remove(subscription.getPath()); @@ -123,21 +133,16 @@ void unsubscribe(Integer sid) { @Override public void write(MessageWriter writer) { - DSIWriter out = writer.getWriter(); - out.beginMap(); - out.key("rid").value(ZERO); - out.key("updates").beginList(); - DS1InboundSubscription sub; + writeBegin(writer); + DSInboundSubscription sub; while (!responder.shouldEndMessage()) { sub = outbound.poll(); if (sub == null) { break; } - sub.write(out, timestampBuffer); + sub.write(writer, timestampBuffer); } - out.endList(); - out.endMap(); - timestampBuffer.setLength(0); + writeEnd(writer); synchronized (this) { if (outbound.isEmpty()) { enqueued = false; @@ -147,4 +152,23 @@ public void write(MessageWriter writer) { responder.sendResponse(this); } + /** + * Override point for v2. + */ + protected void writeBegin(MessageWriter writer) { + writer.getWriter() + .beginMap() + .key("rid").value(ZERO) + .key("updates").beginList(); + } + + /** + * Override point for v2. + */ + protected void writeEnd(MessageWriter writer) { + writer.getWriter() + .endList() + .endMap(); + } + } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSResponder.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSResponder.java index 0a4c61cf..fcc81d79 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSResponder.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSResponder.java @@ -3,6 +3,7 @@ import com.acuity.iot.dsa.dslink.DSSession; import com.acuity.iot.dsa.dslink.protocol.DSStream; import com.acuity.iot.dsa.dslink.protocol.message.OutboundMessage; +import com.acuity.iot.dsa.dslink.transport.DSTransport; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; @@ -31,7 +32,6 @@ public abstract class DSResponder extends DSNode { private Logger logger; private DSSession session; private DSResponder responder; - //private DS2InboundSubscriptions subscriptions = new DSInboundSubscriptions(this); ///////////////////////////////////////////////////////////////// // Methods - Constructors @@ -76,6 +76,10 @@ public DSSession getSession() { return session; } + public DSTransport getTransport() { + return getConnection().getTransport(); + } + public void onConnect() { } @@ -88,11 +92,14 @@ public void onDisconnect() { public DSStream putRequest(Integer rid, DSStream request) { return inboundRequests.put(rid, request); } - public DSStream removeRequest(Integer rid) { return inboundRequests.remove(rid); } + public abstract void sendClose(int rid); + + public abstract void sendError(DSInboundRequest req, Throwable reason); + public boolean shouldEndMessage() { return session.shouldEndMessage(); } diff --git a/dslink-core/src/main/java/org/iot/dsa/io/AbstractWriter.java b/dslink-core/src/main/java/org/iot/dsa/io/AbstractWriter.java index c8b35a2f..a634fb72 100644 --- a/dslink-core/src/main/java/org/iot/dsa/io/AbstractWriter.java +++ b/dslink-core/src/main/java/org/iot/dsa/io/AbstractWriter.java @@ -246,8 +246,8 @@ public AbstractWriter value(boolean arg) { switch (last) { case LAST_DONE: throw new IllegalStateException("Nesting error: " + arg); - case LAST_INIT: - throw new IllegalStateException("Not expecting value: " + arg); + //case LAST_INIT: + //throw new IllegalStateException("Not expecting value: " + arg); case LAST_VAL: case LAST_END: writeSeparator(); @@ -274,8 +274,8 @@ public AbstractWriter value(byte[] arg) { switch (last) { case LAST_DONE: throw new IllegalStateException("Nesting error"); - case LAST_INIT: - throw new IllegalStateException("Not expecting byte[] value"); + //case LAST_INIT: + //throw new IllegalStateException("Not expecting byte[] value"); case LAST_VAL: case LAST_END: writeSeparator(); @@ -302,8 +302,8 @@ public AbstractWriter value(double arg) { switch (last) { case LAST_DONE: throw new IllegalStateException("Nesting error: " + arg); - case LAST_INIT: - throw new IllegalStateException("Not expecting value: " + arg); + //case LAST_INIT: + //throw new IllegalStateException("Not expecting value: " + arg); case LAST_VAL: case LAST_END: writeSeparator(); @@ -330,8 +330,8 @@ public AbstractWriter value(int arg) { switch (last) { case LAST_DONE: throw new IllegalStateException("Nesting error: " + arg); - case LAST_INIT: - throw new IllegalStateException("Not expecting value: " + arg); + //case LAST_INIT: + //throw new IllegalStateException("Not expecting value: " + arg); case LAST_VAL: case LAST_END: writeSeparator(); @@ -358,8 +358,8 @@ public AbstractWriter value(long arg) { switch (last) { case LAST_DONE: throw new IllegalStateException("Nesting error: " + arg); - case LAST_INIT: - throw new IllegalStateException("Not expecting value: " + arg); + //case LAST_INIT: + //throw new IllegalStateException("Not expecting value: " + arg); case LAST_VAL: case LAST_END: writeSeparator(); @@ -386,8 +386,8 @@ public AbstractWriter value(String arg) { switch (last) { case LAST_DONE: throw new IllegalStateException("Nesting error: " + arg); - case LAST_INIT: - throw new IllegalStateException("Not expecting value: " + arg); + //case LAST_INIT: + //throw new IllegalStateException("Not expecting value: " + arg); case LAST_VAL: case LAST_END: writeSeparator(); diff --git a/dslink-core/src/main/java/org/iot/dsa/io/msgpack/MsgpackWriter.java b/dslink-core/src/main/java/org/iot/dsa/io/msgpack/MsgpackWriter.java index 449e9e4f..0f676ec1 100644 --- a/dslink-core/src/main/java/org/iot/dsa/io/msgpack/MsgpackWriter.java +++ b/dslink-core/src/main/java/org/iot/dsa/io/msgpack/MsgpackWriter.java @@ -31,6 +31,10 @@ public class MsgpackWriter extends AbstractWriter implements MsgpackConstants { public MsgpackWriter() { } + public MsgpackWriter(DSByteBuffer buffer) { + this.byteBuffer = buffer; + } + // Methods // ------- @@ -373,7 +377,7 @@ boolean isMap() { void writeSize() { if (offset >= 0) { - byteBuffer.overwriteShort(offset, (short) size, true); + byteBuffer.replaceShort(offset, (short) size, true); } } } diff --git a/dslink-core/src/main/java/org/iot/dsa/node/DSBytes.java b/dslink-core/src/main/java/org/iot/dsa/node/DSBytes.java index 68ad2af7..e46638d3 100644 --- a/dslink-core/src/main/java/org/iot/dsa/node/DSBytes.java +++ b/dslink-core/src/main/java/org/iot/dsa/node/DSBytes.java @@ -319,6 +319,9 @@ public DSBytes valueOf(DSElement arg) { if ((arg == null) || arg.isNull()) { return NULL; } + if (arg instanceof DSBytes) { + return (DSBytes) arg; + } if (arg instanceof DSString) { return valueOf(arg.toString()); } diff --git a/dslink-core/src/main/java/org/iot/dsa/security/DSPermission.java b/dslink-core/src/main/java/org/iot/dsa/security/DSPermission.java index 4c437189..3053fb46 100644 --- a/dslink-core/src/main/java/org/iot/dsa/security/DSPermission.java +++ b/dslink-core/src/main/java/org/iot/dsa/security/DSPermission.java @@ -8,10 +8,10 @@ public enum DSPermission { //Write permission NEVER = 0; - LIST("list", 1), - READ("read", 2), - WRITE("write", 3), - CONFIG("config", 4); + LIST("list", 0x1), + READ("read", 0x2), + WRITE("write", 0x3), + CONFIG("config", 0x4); private String display; private int level; @@ -41,18 +41,32 @@ public boolean isList() { return this == LIST; } + public static DSPermission forString(String str) { + for (DSPermission p : DSPermission.values()) { + if (p.toString().equalsIgnoreCase(str)) { + return p; + } + } + throw new IllegalArgumentException("Unknown permission: " + str); + } + @Override public String toString() { return display; } - public static DSPermission forString(String str) { - for (DSPermission p : DSPermission.values()) { - if (p.toString().equals(str)) { - return p; - } + public static DSPermission valueOf(int v2byte) { + switch (v2byte) { + case 0x10 : + return LIST; + case 0x20 : + return READ; + case 0x30 : + return WRITE; + case 0x40 : + return CONFIG; } - return null; + throw new IllegalArgumentException("Unknown permission: " + v2byte); } } diff --git a/dslink-core/src/test/java/org/iot/dsa/dslink/V2HandshakeTest.java b/dslink-core/src/test/java/org/iot/dsa/dslink/V2HandshakeTest.java index 2f6c26b6..42561540 100644 --- a/dslink-core/src/test/java/org/iot/dsa/dslink/V2HandshakeTest.java +++ b/dslink-core/src/test/java/org/iot/dsa/dslink/V2HandshakeTest.java @@ -1,5 +1,6 @@ package org.iot.dsa.dslink; +import com.acuity.iot.dsa.dslink.io.DSByteBuffer; import com.acuity.iot.dsa.dslink.protocol.protocol_v2.DS2MessageReader; import com.acuity.iot.dsa.dslink.protocol.protocol_v2.DS2MessageWriter; import java.io.ByteArrayInputStream; @@ -26,14 +27,6 @@ public class V2HandshakeTest { private final static char[] HEXCHARS = "0123456789abcdef".toCharArray(); - /////////////////////////////////////////////////////////////////////////// - // Fields - /////////////////////////////////////////////////////////////////////////// - - /////////////////////////////////////////////////////////////////////////// - // Constructors - /////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////// // Methods /////////////////////////////////////////////////////////////////////////// @@ -86,7 +79,7 @@ public void testF0() throws Exception { //construct the message DS2MessageWriter writer = new DS2MessageWriter(); writer.setMethod((byte) 0xf0); - ByteBuffer buffer = writer.getBody(); + DSByteBuffer buffer = writer.getBody(); buffer.put((byte) 2).put((byte) 0); //dsa version writer.writeString(dsId, buffer); buffer.put(dsKeys.encodePublic()); @@ -113,13 +106,13 @@ public void testF1() throws Exception { //construct the message DS2MessageWriter writer = new DS2MessageWriter(); writer.setMethod((byte) 0xf1); - ByteBuffer buffer = writer.getBody(); + DSByteBuffer buffer = writer.getBody(); //dsa version writer.writeString(dsId, buffer); byte[] publicKey = dsKeys.encodePublic(); buffer.put(publicKey); buffer.put(saltBytes); - int bodyLength = buffer.position(); + int bodyLength = buffer.length(); byte[] bytes = writer.toByteArray(); //what to test against String correctResult = "9c0000000700f1320062726f6b65722d67363735676153516f677a4d786a4a46764c374873436279533842304c79325f4162686b775f2d6734694904f9e64edcec5ea0a645bd034e46ff209dd9fb21d8aba74a5531dc6dcbea28d696c6c9386d924ebc2f48092a1d6c8b2ca907005cca7e8d2a58783b8a765d8eb29deccbc87e4b5ce2fe28308fd9f2a7baf3a87ff679a2f3e71d9181a67b7542122c" @@ -160,16 +153,14 @@ public void testF2() throws Exception { //construct the message DS2MessageWriter writer = new DS2MessageWriter(); writer.setMethod((byte) 0xf2); - ByteBuffer buffer = writer.getBody(); + DSByteBuffer buffer = writer.getBody(); writer.writeString("sample_token_string", buffer); buffer.put((byte) 0x01); //isResponder - writer.writeString("", buffer); //blank session string - writer.writeIntLE(0, buffer); //last ack writer.writeString("", buffer); //blank server path buffer.put(auth); byte[] tmp = writer.toByteArray(); //what to test against - String correctResult = "450000000700f2130073616d706c655f746f6b656e5f737472696e67010000000000000000f58c10e212a82bf327a020679c424fc63e852633a53253119df74114fac8b2ba" + String correctResult = "3f0000000700f2130073616d706c655f746f6b656e5f737472696e67010000f58c10e212a82bf327a020679c424fc63e852633a53253119df74114fac8b2ba" .toUpperCase(); byte[] correctBytes = toBytesFromHex(correctResult); Assert.assertArrayEquals(tmp, correctBytes); @@ -193,16 +184,14 @@ public void testF3() throws Exception { //construct the message DS2MessageWriter writer = new DS2MessageWriter(); writer.setMethod((byte) 0xf3); - ByteBuffer buffer = writer.getBody(); + DSByteBuffer buffer = writer.getBody(); buffer.put((byte) 1); // allow requester - writer.writeString("sampe-session-001", buffer); //client session id - writer.writeIntLE(0, buffer); //last ack id writer.writeString("/downstream/mlink1", buffer); buffer.put(auth); int bodyLength = writer.getBodyLength(); byte[] tmp = writer.toByteArray(); //what to test against - String correctResult = "530000000700f301110073616d70652d73657373696f6e2d3030310000000012002f646f776e73747265616d2f6d6c696e6b31e709059f1ebb84cfb8c34d53fdba7fbf20b1fe3dff8c343050d2b5c7c62be85a" + String correctResult = "3c0000000700f30112002f646f776e73747265616d2f6d6c696e6b31e709059f1ebb84cfb8c34d53fdba7fbf20b1fe3dff8c343050d2b5c7c62be85a" .toUpperCase(); byte[] correctBytes = toBytesFromHex(correctResult); Assert.assertTrue(Arrays.equals(tmp, correctBytes)); @@ -213,8 +202,6 @@ public void testF3() throws Exception { Assert.assertEquals(0xf3, reader.getMethod()); Assert.assertEquals(bodyLength, reader.getBodyLength()); Assert.assertEquals(in.read(), 1); //allow requester - Assert.assertEquals("sampe-session-001", reader.readString(in)); - Assert.assertEquals(DSBytes.readInt(in, false), 0); Assert.assertEquals("/downstream/mlink1", reader.readString(in)); tmp = new byte[auth.length]; in.read(tmp); @@ -229,12 +216,4 @@ public static String toHexFromBytes(byte[] bytes) { return DatatypeConverter.printHexBinary(bytes); } - /////////////////////////////////////////////////////////////////////////// - // Inner Classes - /////////////////////////////////////////////////////////////////////////// - - /////////////////////////////////////////////////////////////////////////// - // Initialization - /////////////////////////////////////////////////////////////////////////// - } From 99484e4f3acab06daaf5902fdd583dd189ece3b9 Mon Sep 17 00:00:00 2001 From: Aaron Date: Fri, 9 Feb 2018 16:43:44 -0800 Subject: [PATCH 02/11] Fix value results action. --- .../acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java index 4d9f1a0e..5d1f9610 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java @@ -288,7 +288,7 @@ private void encodeTargetAction(ApiObject object, DSIWriter out) { out.beginList().value("$columns"); if (e == null) { out.beginList(); - Iterator params = action.getParameters(); + Iterator params = action.getValueResults(); if (params != null) { DSMap param; while (params.hasNext()) { From 4b5d73eb71a1b5371f44b21c15ee920de2ca200b Mon Sep 17 00:00:00 2001 From: Aaron Date: Sat, 10 Feb 2018 11:54:48 -0800 Subject: [PATCH 03/11] DSSysNode --- .../protocol_v1/DS1ConnectionInit.java | 4 +- .../protocol_v1/DS1LinkConnection.java | 2 +- .../acuity/iot/dsa/dslink/test/TestLink.java | 1 - .../main/java/org/iot/dsa/dslink/DSLink.java | 60 ++----------- .../java/org/iot/dsa/dslink/DSLinkConfig.java | 80 ++++------------- .../org/iot/dsa/dslink/DSLinkConnection.java | 6 +- .../java/org/iot/dsa/dslink/DSMainNode.java | 14 +++ .../java/org/iot/dsa/dslink/DSSysNode.java | 86 +++++++++++++++++++ dslink-java-v2-test/dslink.json | 2 +- 9 files changed, 134 insertions(+), 121 deletions(-) create mode 100644 dslink-core/src/main/java/org/iot/dsa/dslink/DSSysNode.java diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1ConnectionInit.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1ConnectionInit.java index 524e98aa..18c53153 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1ConnectionInit.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1ConnectionInit.java @@ -245,8 +245,8 @@ void writeConnectionRequest(HttpURLConnection conn) throws Exception { out = new JsonWriter(conn.getOutputStream()); DSMap map = new DSMap(); map.put("publicKey", DSBase64.encodeUrl(link.getKeys().encodePublic())); - map.put("isRequester", link.getConfig().isRequester()); - map.put("isResponder", link.getConfig().isResponder()); + map.put("isRequester", link.getMain().isRequester()); + map.put("isResponder", link.getMain().isResponder()); map.put("linkData", new DSMap()); map.put("version", DSA_VERSION); DSList list = map.putList("formats"); diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1LinkConnection.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1LinkConnection.java index 6d0a29a1..ca9739f6 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1LinkConnection.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1LinkConnection.java @@ -173,7 +173,7 @@ protected void onRun() { @Override protected void onStable() { - this.link = (DSLink) getParent(); + this.link = getLink(); super.onStable(); } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/test/TestLink.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/test/TestLink.java index 33d6d8d5..a42ed398 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/test/TestLink.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/test/TestLink.java @@ -26,7 +26,6 @@ public TestLink(DSMainNode MainNode) { DSLinkConfig cfg = new DSLinkConfig(); cfg.setDslinkJson(new DSMap().put("configs", new DSMap())); cfg.setLinkName("dslink-java-testing"); - cfg.setRequester(true); cfg.setLogLevel(Level.FINEST); cfg.setConfig(DSLinkConfig.CFG_CONNECTION_TYPE, TestConnection.class.getName()); cfg.setConfig(DSLinkConfig.CFG_STABLE_DELAY, 1); diff --git a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLink.java b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLink.java index fe9b48fc..19b7c164 100644 --- a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLink.java +++ b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLink.java @@ -1,6 +1,5 @@ package org.iot.dsa.dslink; -import com.acuity.iot.dsa.dslink.protocol.protocol_v1.DS1LinkConnection; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -13,7 +12,6 @@ import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import org.iot.dsa.DSRuntime; import org.iot.dsa.io.NodeDecoder; import org.iot.dsa.io.NodeEncoder; import org.iot.dsa.io.json.JsonReader; @@ -21,9 +19,6 @@ import org.iot.dsa.logging.DSLogging; import org.iot.dsa.node.DSInfo; import org.iot.dsa.node.DSNode; -import org.iot.dsa.node.action.ActionInvocation; -import org.iot.dsa.node.action.ActionResult; -import org.iot.dsa.node.action.DSAction; import org.iot.dsa.security.DSKeys; import org.iot.dsa.time.DSTime; import org.iot.dsa.util.DSException; @@ -51,7 +46,6 @@ public class DSLink extends DSNode implements Runnable { /////////////////////////////////////////////////////////////////////////// static final String MAIN = "main"; - static final String SAVE = "Save"; static final String SYS = "sys"; /////////////////////////////////////////////////////////////////////////// @@ -59,14 +53,12 @@ public class DSLink extends DSNode implements Runnable { /////////////////////////////////////////////////////////////////////////// private DSLinkConfig config; - private DSLinkConnection connection; private String dsId; private DSKeys keys; private Logger logger; private DSInfo main = getInfo(MAIN); private String name; private Thread runThread; - private DSInfo save = getInfo(SAVE); private boolean saveEnabled = true; private DSInfo sys = getInfo(SYS); @@ -91,9 +83,8 @@ public DSLink() { */ @Override protected void declareDefaults() { - declareDefault(SAVE, new DSAction()).setAdmin(true); declareDefault(MAIN, new DSNode()); - declareDefault(SYS, new DSNode()).setTransient(true); + declareDefault(SYS, new DSSysNode()).setAdmin(true); } public DSLinkConfig getConfig() { @@ -101,7 +92,7 @@ public DSLinkConfig getConfig() { } public DSLinkConnection getConnection() { - return connection; + return getSys().getConnection(); } /** @@ -143,13 +134,14 @@ public Logger getLogger() { return logger; } - /** - * Returns the root of the node tree. - */ public DSMainNode getMain() { return (DSMainNode) main.getNode(); } + public DSSysNode getSys() { + return (DSSysNode) sys.getNode(); + } + /** * Configures a link instance including creating the appropriate connection. * @@ -160,28 +152,8 @@ protected DSLink init(DSLinkConfig config) { DSLogging.setDefaultLevel(config.getLogLevel()); name = config.getLinkName(); keys = config.getKeys(); - if (config.getLogFile() != null) { - logger = DSLogging.getLogger(name, config.getLogFile()); - } else { - logger = Logger.getLogger(name); - } - try { - String ver = config.getConfig(DSLinkConfig.CFG_PROTOCOL_VERSION, "1"); - if (ver.startsWith("1")) { - String type = config.getConfig(DSLinkConfig.CFG_CONNECTION_TYPE, null); - if (type != null) { - config(config() ? "Connection type: " + type : null); - connection = (DSLinkConnection) Class.forName(type).newInstance(); - } else { - connection = new DS1LinkConnection(); - } - } else { //2 - ; //TODO - } - put(sys, connection); - } catch (Exception x) { - DSException.throwRuntime(x); - } + logger = Logger.getLogger(name); + getSys().init(); return this; } @@ -245,22 +217,6 @@ public static void main(String[] args) { } } - /** - * Handles the save action. - */ - public ActionResult onInvoke(DSInfo actionInfo, ActionInvocation invocation) { - if (actionInfo == save) { - DSRuntime.run(new Runnable() { - @Override - public void run() { - save(); - } - }); - return null; - } - return super.onInvoke(actionInfo, invocation); - } - /** * Calls starts, waits the stableDelay, then calls stable. Does not return until this node is * stopped. diff --git a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConfig.java b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConfig.java index a03ac708..2522f5a4 100644 --- a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConfig.java +++ b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConfig.java @@ -21,15 +21,12 @@ public class DSLinkConfig { public static final String CFG_AUTH_TOKEN = "token"; public static final String CFG_BROKER_URL = "broker"; - public static final String CFG_CONNECTION_TYPE = "connectionType"; - public static final String CFG_IS_REQUESTER = "isRequester"; - public static final String CFG_IS_RESPONDER = "isResponder"; public static final String CFG_KEY_FILE = "key"; - public static final String CFG_LOG_FILE = "logFile"; public static final String CFG_LOG_LEVEL = "log"; - public static final String CFG_MAIN_TYPE = "mainType"; public static final String CFG_NODE_FILE = "nodes"; public static final String CFG_PROTOCOL_VERSION = "protocolVersion"; + + public static final String CFG_CONNECTION_TYPE = "connectionType"; public static final String CFG_READ_TIMEOUT = "readTimeout"; public static final String CFG_SAVE_INTERVAL = "saveInterval"; public static final String CFG_STABLE_DELAY = "stableDelay"; @@ -40,12 +37,11 @@ public class DSLinkConfig { /////////////////////////////////////////////////////////////////////////// private String brokerUri; - private String dsId; + private String dsaVersion; private DSMap dslinkJson; private boolean help = false; private DSKeys keys; private String linkName; - private File logFile; private Level logLevel; private File nodesFile; private String mainType; @@ -189,6 +185,13 @@ private DSMap getConfigMap(String name, boolean create) { return tmp; } + public String getDsaVersion() { + if (dsaVersion == null) { + dsaVersion = getDslinkJson().getString("dsa-version"); + } + return dsaVersion; + } + /** * If not set, this will attempt to open dslink.json in the working the process directory. */ @@ -236,16 +239,6 @@ public String getLinkName() { return linkName; } - public File getLogFile() { - if (logFile == null) { - String file = getConfig(CFG_LOG_FILE, null); - if (file != null) { - setLogFile(new File(file)); - } - } - return logFile; - } - /** * If not set, will attempt to use the getConfig in dslink.json but fall back to 'info' if * necessary. @@ -272,11 +265,14 @@ public File getNodesFile() { * The type of the root node. */ public String getMainType() { + if (mainType == null) { //legacy + mainType = getConfig("handler_class", null); + } + if (mainType == null) { //legacy + mainType = getConfig("mainType", null); + } if (mainType == null) { - mainType = getConfig(CFG_MAIN_TYPE, null); - if (mainType == null) { - throw new IllegalStateException("Missing mainType config."); - } + throw new IllegalStateException("Missing main node type in config."); } return mainType; } @@ -291,20 +287,6 @@ public String getToken() { return token; } - /** - * Looks for the isRequester config, false by default. - */ - public boolean isRequester() { - return getConfig(CFG_IS_REQUESTER, false); - } - - /** - * Looks for the isResponder config, true by default. - */ - public boolean isResponder() { - return getConfig(CFG_IS_RESPONDER, true); - } - /** * Parses command line args to set the internal state of this object. * @@ -349,8 +331,6 @@ private void processParsed(String key, String value) { setLogLevel(value); } else if (key.equals("--logging")) { setLogLevel(value); - } else if (key.equals("--logging-file")) { - setLogFile(new File(value)); } else if (key.equals("--key")) { setKeys(new File(value)); } else if (key.equals("--name")) { @@ -371,8 +351,6 @@ private void processParsed(String key, String value) { help = true; } else if (key.equals("-l")) { setLogLevel(value); - } else if (key.equals("-f")) { - setLogFile(new File(value)); } else if (key.equals("-k")) { setKeys(new File(value)); } else if (key.equals("-n")) { @@ -480,14 +458,6 @@ public DSLinkConfig setLinkName(String arg) { return this; } - /** - * Overrides dslink.json. - */ - public DSLinkConfig setLogFile(File arg) { - logFile = arg; - return setConfig(CFG_LOG_FILE, arg.getAbsolutePath()); - } - /** * Should be one of the following (case insensitive): all, finest, finer, fine, config, info, * warning, severe, off.

Overrides dslink.json. @@ -523,22 +493,6 @@ public DSLinkConfig setNodesFile(File file) { return setConfig(CFG_NODE_FILE, file.getAbsolutePath()); } - /** - * Overrides dslink.json. - */ - public DSLinkConfig setRequester(boolean isRequester) { - setConfig(CFG_IS_REQUESTER, isRequester); - return this; - } - - /** - * Overrides dslink.json. - */ - public DSLinkConfig setResponder(boolean isResponder) { - setConfig(CFG_IS_RESPONDER, isResponder); - return this; - } - /** * The type of the root node. */ diff --git a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConnection.java b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConnection.java index b882217e..9b770006 100644 --- a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConnection.java +++ b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConnection.java @@ -86,7 +86,7 @@ public String getConnectionId() { * The link using this connection. */ public DSLink getLink() { - return (DSLink) getParent(); + return (DSLink) getSys().getParent(); } @Override @@ -97,6 +97,10 @@ public Logger getLogger() { return logger; } + public DSSysNode getSys() { + return (DSSysNode) getParent(); + } + public abstract DSIRequester getRequester(); public abstract DSTransport getTransport(); diff --git a/dslink-core/src/main/java/org/iot/dsa/dslink/DSMainNode.java b/dslink-core/src/main/java/org/iot/dsa/dslink/DSMainNode.java index c24d70b6..0e3fbebf 100644 --- a/dslink-core/src/main/java/org/iot/dsa/dslink/DSMainNode.java +++ b/dslink-core/src/main/java/org/iot/dsa/dslink/DSMainNode.java @@ -28,6 +28,20 @@ public DSLink getLink() { return (DSLink) getParent(); } + /** + * Override point, returns true by default. + */ + public boolean isRequester() { + return true; + } + + /** + * Override point, returns true by default. + */ + public boolean isResponder() { + return true; + } + /** * The parent must be a DSLink instance. */ diff --git a/dslink-core/src/main/java/org/iot/dsa/dslink/DSSysNode.java b/dslink-core/src/main/java/org/iot/dsa/dslink/DSSysNode.java new file mode 100644 index 00000000..e1556509 --- /dev/null +++ b/dslink-core/src/main/java/org/iot/dsa/dslink/DSSysNode.java @@ -0,0 +1,86 @@ +package org.iot.dsa.dslink; + +import com.acuity.iot.dsa.dslink.protocol.protocol_v1.DS1LinkConnection; +import com.acuity.iot.dsa.dslink.protocol.protocol_v2.DS2LinkConnection; +import java.util.logging.Logger; +import org.iot.dsa.node.DSInfo; +import org.iot.dsa.node.DSMap; +import org.iot.dsa.node.DSNode; +import org.iot.dsa.node.action.ActionInvocation; +import org.iot.dsa.node.action.ActionResult; +import org.iot.dsa.node.action.DSAction; +import org.iot.dsa.util.DSException; + +/** + * The root of the system nodes. + * + * @author Aaron Hansen + */ +public class DSSysNode extends DSNode { + + static final String CONNECTION = "connection"; + static final String SAVE = "save"; + static final String STOP = "stop"; + + private DSInfo connection = getInfo(CONNECTION); + private DSInfo save = getInfo(SAVE); + private DSInfo stop = getInfo(STOP); + + @Override + protected void declareDefaults() { + declareDefault(CONNECTION, new DSNode()).setTransient(true); + declareDefault(SAVE, DSAction.DEFAULT); + declareDefault(STOP, DSAction.DEFAULT); + } + + public DSLinkConnection getConnection() { + return (DSLinkConnection) connection.getObject(); + } + + public DSLink getLink() { + return (DSLink) getParent(); + } + + void init() { + DSLinkConfig config = getLink().getConfig(); + try { + String ver = config.getConfig(DSLinkConfig.CFG_PROTOCOL_VERSION, "1"); + DSLinkConnection conn; + if (ver.startsWith("1")) { + String type = config.getConfig(DSLinkConfig.CFG_CONNECTION_TYPE, null); + if (type != null) { + config(config() ? "Connection type: " + type : null); + conn = (DSLinkConnection) Class.forName(type).newInstance(); + } else { + conn = new DS1LinkConnection(); + } + } else { //2 + conn = new DS2LinkConnection(); + } + put(connection, conn); + } catch (Exception x) { + DSException.throwRuntime(x); + } + } + + @Override + public ActionResult onInvoke(DSInfo action, ActionInvocation invocation) { + if (action == save) { + getLink().save(); + } else if (action == stop){ + getLink().shutdown(); + } else { + super.onInvoke(action, invocation); + } + return null; + } + + @Override + protected void validateParent(DSNode node) { + if (node instanceof DSLink) { + return; + } + throw new IllegalArgumentException("Invalid parent: " + node.getClass().getName()); + } + +} diff --git a/dslink-java-v2-test/dslink.json b/dslink-java-v2-test/dslink.json index 9a9fbcf3..153bb99c 100644 --- a/dslink-java-v2-test/dslink.json +++ b/dslink-java-v2-test/dslink.json @@ -4,7 +4,7 @@ "description": "Testing Link", "main": "bin/dslink-java-v2-test", "configs": { - "mainType": { + "handler_class": { "desc1": "*************** THIS IS REQUIRED AND MUST BE CHANGED ***************", "desc2": "Fully qualified class name of the root node of the link.", "type": "string", From b6b60ba4999abbf89cb279a762cd4cd759433631 Mon Sep 17 00:00:00 2001 From: Aaron Date: Mon, 12 Feb 2018 11:02:29 -0800 Subject: [PATCH 04/11] DSSysNode --- .../protocol_v2/DS2LinkConnection.java | 4 +- .../protocol_v2/DS2MessageReader.java | 1 + .../protocol_v2/DS2MessageWriter.java | 24 +++++--- .../protocol/protocol_v2/DS2Session.java | 59 +++++++++++-------- .../protocol/protocol_v2/PingMessage.java | 25 ++++++++ .../protocol_v2/responder/DS2Responder.java | 10 +++- .../dsa/dslink/transport/SocketTransport.java | 12 +++- .../main/java/org/iot/dsa/node/DSBytes.java | 1 + .../main/java/org/iot/dsa/node/DSPath.java | 26 +++++--- dslink-java-v2-test/dslink.json | 1 + 10 files changed, 118 insertions(+), 45 deletions(-) create mode 100644 dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/PingMessage.java diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java index d148e3d9..e8bc6f3a 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java @@ -132,14 +132,12 @@ protected void makeTransport() { } catch (Exception x) { DSException.throwRuntime(x); } - } else { + } else if (uri.startsWith("ds")) { transport = new SocketTransport(); } config(config() ? "Connection URL = " + uri : null); transport.setConnectionUrl(uri); transport.setConnection(this); - transport.setReadTimeout(getLink().getConfig().getConfig( - DSLinkConfig.CFG_READ_TIMEOUT, 60000)); } @Override diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageReader.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageReader.java index e96fdec8..eb539a5c 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageReader.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageReader.java @@ -160,6 +160,7 @@ public boolean isRequest() { case MSG_INVOKE_REQ: case MSG_LIST_REQ: case MSG_OBSERVE_REQ: + case MSG_SET_REQ: return true; } return false; diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageWriter.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageWriter.java index 094c854d..d2f76637 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageWriter.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageWriter.java @@ -1,11 +1,13 @@ package com.acuity.iot.dsa.dslink.protocol.protocol_v2; import com.acuity.iot.dsa.dslink.io.DSByteBuffer; +import com.acuity.iot.dsa.dslink.protocol.message.MessageWriter; import com.acuity.iot.dsa.dslink.transport.DSBinaryTransport; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharsetEncoder; +import org.iot.dsa.io.msgpack.MsgpackWriter; import org.iot.dsa.node.DSString; /** @@ -15,7 +17,7 @@ * * @author Aaron Hansen */ -public class DS2MessageWriter implements MessageConstants { +public class DS2MessageWriter implements MessageConstants, MessageWriter { // Fields // ------ @@ -26,6 +28,7 @@ public class DS2MessageWriter implements MessageConstants { private byte method; private ByteBuffer strBuffer; private CharsetEncoder utf8encoder; + private MsgpackWriter writer; // Constructors // ------------ @@ -33,6 +36,7 @@ public class DS2MessageWriter implements MessageConstants { public DS2MessageWriter() { header = new DSByteBuffer(); body = new DSByteBuffer(); + writer = new MsgpackWriter(body); utf8encoder = DSString.UTF8.newEncoder(); init(-1, -1); } @@ -83,12 +87,12 @@ private void encodeHeaderLengths() { header.replace(6, method); } - public int getBodyLength() { - return body.length(); + public DSByteBuffer getBody() { + return body; } - public int getHeaderLength() { - return header.length(); + public int getBodyLength() { + return body.length(); } /** @@ -117,6 +121,10 @@ private CharBuffer getCharBuffer(CharSequence arg) { return charBuffer; } + public int getHeaderLength() { + return header.length(); + } + /** * Called by writeString(), returns a bytebuffer for the given capacity ready for writing * (putting). Attempts to reuse the same buffer as much as possible. @@ -140,8 +148,9 @@ private ByteBuffer getStringBuffer(int len) { return strBuffer; } - public DSByteBuffer getBody() { - return body; + @Override + public MsgpackWriter getWriter() { + return writer; } /** @@ -152,6 +161,7 @@ public DSByteBuffer getBody() { */ public DS2MessageWriter init(int requestId, int ackId) { body.clear(); + writer.reset(); header.clear(); header.skip(7); if (requestId >= 0) { diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java index e2a38428..199d3cc1 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java @@ -1,9 +1,11 @@ package com.acuity.iot.dsa.dslink.protocol.protocol_v2; import com.acuity.iot.dsa.dslink.DSSession; +import com.acuity.iot.dsa.dslink.protocol.message.OutboundMessage; import com.acuity.iot.dsa.dslink.protocol.protocol_v1.requester.DS1Requester; import com.acuity.iot.dsa.dslink.protocol.protocol_v2.responder.DS2Responder; import com.acuity.iot.dsa.dslink.transport.DSBinaryTransport; +import com.acuity.iot.dsa.dslink.transport.DSTransport; import java.io.IOException; import org.iot.dsa.dslink.DSIRequester; import org.iot.dsa.node.DSInfo; @@ -22,9 +24,7 @@ public class DS2Session extends DSSession implements MessageConstants { static final int END_MSG_THRESHOLD = 48000; static final String LAST_ACK_RECV = "Last Ack Recv"; - static final String LAST_ACK_SENT = "Last Ack Sent"; - static final int MAX_MSG_ID = 2147483647; static final int MAX_MSG_IVL = 45000; /////////////////////////////////////////////////////////////////////////// @@ -32,14 +32,12 @@ public class DS2Session extends DSSession implements MessageConstants { /////////////////////////////////////////////////////////////////////////// private DSInfo lastAckRecv = getInfo(LAST_ACK_RECV); - private DSInfo lastAckSent = getInfo(LAST_ACK_SENT); private long lastMessageSent; private DS2MessageReader messageReader; - private int nextAck = -1; - private int nextMsg = 1; + private DS2MessageWriter messageWriter; private boolean requestsNext = false; private DS1Requester requester;// = new DS1Requester(this); - private DS2Responder responder = null;//new DS2Responder(this); //todo + private DS2Responder responder = new DS2Responder(this); ///////////////////////////////////////////////////////////////// // Constructors @@ -59,7 +57,6 @@ public DS2Session(DS2LinkConnection connection) { @Override protected void declareDefaults() { declareDefault(LAST_ACK_RECV, DSInt.NULL).setReadOnly(true); - declareDefault(LAST_ACK_SENT, DSInt.NULL).setReadOnly(true); } @Override @@ -81,20 +78,14 @@ protected void doRecvMessage() throws IOException { @Override protected void doSendMessage() { - /* DSTransport transport = getTransport(); - long endTime = System.currentTimeMillis() + 2000; requestsNext = !requestsNext; transport.beginMessage(); if (hasMessagesToSend()) { - send(requestsNext, endTime); - if ((System.currentTimeMillis() < endTime) && !shouldEndMessage()) { - send(!requestsNext, endTime); - } + send(requestsNext); } transport.endMessage(); lastMessageSent = System.currentTimeMillis(); - */ } @Override @@ -102,6 +93,13 @@ public DS2LinkConnection getConnection() { return (DS2LinkConnection) super.getConnection(); } + private DS2MessageWriter getMessageWriter() { + if (messageWriter == null) { + messageWriter = new DS2MessageWriter(); + } + return messageWriter; + } + @Override public DSIRequester getRequester() { return requester; @@ -119,9 +117,6 @@ private boolean hasPingToSend() { * Override point, returns true if there are any pending acks or outbound messages queued up. */ protected boolean hasSomethingToSend() { - if (nextAck > 0) { - return true; - } if (hasPingToSend()) { return true; } @@ -139,30 +134,46 @@ protected void onStable() { @Override public void onConnect() { super.onConnect(); - requester.onConnect(); + //requester.onConnect(); responder.onConnect(); } @Override public void onConnectFail() { super.onConnectFail(); - requester.onConnectFail(); + //requester.onConnectFail(); responder.onConnectFail(); } @Override public void onDisconnect() { super.onDisconnect(); - requester.onDisconnect(); + //requester.onDisconnect(); responder.onDisconnect(); } /** - * We need to send an ack to the broker. + * Send messages from one of the queues. + * + * @param requests Determines which queue to use; True for outgoing requests, false for + * responses. */ - private synchronized void sendAck(int msg) { - nextAck = msg; - notifyOutgoing(); + private void send(boolean requests) { + boolean hasSomething = false; + if (requests) { + hasSomething = hasOutgoingRequests(); + } else { + hasSomething = hasOutgoingResponses(); + } + OutboundMessage msg = null; + if (hasSomething) { + msg = requests ? dequeueOutgoingRequest() : dequeueOutgoingResponse(); + } else if (hasPingToSend()) { + msg = new PingMessage(this); + } + if (msg != null) { + msg.write(getMessageWriter()); + } } /** diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/PingMessage.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/PingMessage.java new file mode 100644 index 00000000..3980a35f --- /dev/null +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/PingMessage.java @@ -0,0 +1,25 @@ +package com.acuity.iot.dsa.dslink.protocol.protocol_v2; + +import com.acuity.iot.dsa.dslink.protocol.message.MessageWriter; +import com.acuity.iot.dsa.dslink.protocol.message.OutboundMessage; + +/** + * @author Aaron Hansen + */ +class PingMessage implements MessageConstants, OutboundMessage { + + private DS2Session session; + + public PingMessage(DS2Session session) { + this.session = session; + } + + @Override + public void write(MessageWriter writer) { + DS2MessageWriter out = (DS2MessageWriter) writer; + out.init(-1, session.getNextAck()); + out.setMethod((byte) MSG_PING); + out.write(session.getTransport()); + } + +} diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java index 8b24cf2f..8870adcf 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java @@ -1,5 +1,6 @@ package com.acuity.iot.dsa.dslink.protocol.protocol_v2.responder; +import com.acuity.iot.dsa.dslink.io.DSByteBuffer; import com.acuity.iot.dsa.dslink.protocol.DSStream; import com.acuity.iot.dsa.dslink.protocol.protocol_v2.CloseMessage; import com.acuity.iot.dsa.dslink.protocol.protocol_v2.DS2MessageReader; @@ -11,6 +12,7 @@ import com.acuity.iot.dsa.dslink.transport.DSBinaryTransport; import java.util.Map; import org.iot.dsa.DSRuntime; +import org.iot.dsa.node.DSBytes; import org.iot.dsa.node.DSElement; import org.iot.dsa.node.DSMap; import org.iot.dsa.security.DSPermission; @@ -78,13 +80,12 @@ public void onConnectFail() { } public void onDisconnect() { - finer(finer() ? "Close" : null); subscriptions.close(); for (Map.Entry entry : getRequests().entrySet()) { try { entry.getValue().onClose(entry.getKey()); } catch (Exception x) { - finer(finer() ? "Close" : null, x); + severe(getPath(), x); } } getRequests().clear(); @@ -148,6 +149,11 @@ private void processSet(DS2MessageReader msg) { if (obj != null) { perm = DSPermission.valueOf(obj.hashCode()); } + int metaLen = DSBytes.readShort(msg.getBody(), false); + if (metaLen > 0) { + //what to do with it? + msg.getBodyReader().getElement(); + } DSElement value = msg.getBodyReader().getElement(); DSInboundSet setImpl = new DSInboundSet(value, perm); setImpl.setPath((String) msg.getHeader(HDR_TARGET_PATH)) diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/SocketTransport.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/SocketTransport.java index 07b2297b..04f50097 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/SocketTransport.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/SocketTransport.java @@ -4,6 +4,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; +import javax.net.ssl.SSLSocketFactory; import org.iot.dsa.util.DSException; /** @@ -89,7 +90,16 @@ public DSTransport open() { throw new IllegalStateException("Already open"); } try { - socket = new Socket(getConnectionUrl(), 443); + String url = getConnectionUrl(); + if (url.startsWith("dss:")) { + url = url.substring(4); + socket = SSLSocketFactory.getDefault().createSocket(url, 4128); + } else if (url.startsWith("ds:")) { + url = url.substring(3); + socket = new Socket(url, 4120); + } else { + throw new IllegalArgumentException("Invalid broker URI: " + url); + } socket.setSoTimeout((int) getReadTimeout()); open = true; fine(fine() ? "SocketTransport open" : null); diff --git a/dslink-core/src/main/java/org/iot/dsa/node/DSBytes.java b/dslink-core/src/main/java/org/iot/dsa/node/DSBytes.java index e46638d3..7efdaa77 100644 --- a/dslink-core/src/main/java/org/iot/dsa/node/DSBytes.java +++ b/dslink-core/src/main/java/org/iot/dsa/node/DSBytes.java @@ -1,5 +1,6 @@ package org.iot.dsa.node; +import com.acuity.iot.dsa.dslink.io.DSByteBuffer; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; diff --git a/dslink-core/src/main/java/org/iot/dsa/node/DSPath.java b/dslink-core/src/main/java/org/iot/dsa/node/DSPath.java index 4d71aea2..ec3ce52d 100644 --- a/dslink-core/src/main/java/org/iot/dsa/node/DSPath.java +++ b/dslink-core/src/main/java/org/iot/dsa/node/DSPath.java @@ -165,14 +165,17 @@ public static boolean encodeName(String name, StringBuilder buf) { } boolean modified = false; int pathLength = name.length(); - CharArrayWriter charArrayWriter = new CharArrayWriter(); + CharArrayWriter charArrayWriter = null; char c; for (int i = 0; i < pathLength; ) { c = name.charAt(i); - if (!shouldEncode(c)) { - buf.append((char) c); + if (!shouldEncode(c, i)) { + buf.append(c); i++; } else { + if (charArrayWriter == null) { + charArrayWriter = new CharArrayWriter(); + } do { charArrayWriter.write(c); if (c >= 0xD800 && c <= 0xDBFF) { @@ -185,7 +188,7 @@ public static boolean encodeName(String name, StringBuilder buf) { } } i++; - } while (i < pathLength && shouldEncode((c = name.charAt(i)))); + } while (i < pathLength && shouldEncode((c = name.charAt(i)),i)); charArrayWriter.flush(); String str = new String(charArrayWriter.toCharArray()); byte[] bytes = str.getBytes(utf8); @@ -237,21 +240,28 @@ public String[] getPathElements() { /** * Returns true for characters that should be encoded. */ - public static boolean shouldEncode(int ch) { + public static boolean shouldEncode(int ch, int pos) { switch (ch) { case '.': case '/': case '\\': + case '\'': + case '"': case '?': case '*': - case ':': - case '"': + case '|': case '<': case '>': + case '=': + case ':': + case ';': case '%': return true; + case '@': + case '$': + return pos == 0; default: - return false; + return ch < 0x20; } } diff --git a/dslink-java-v2-test/dslink.json b/dslink-java-v2-test/dslink.json index 153bb99c..8fb4bb94 100644 --- a/dslink-java-v2-test/dslink.json +++ b/dslink-java-v2-test/dslink.json @@ -1,5 +1,6 @@ { "name": "dslink-java-v2-test", + "dsaVersion": "2.0", "version": "1.0.0", "description": "Testing Link", "main": "bin/dslink-java-v2-test", From 6026475ab09234c52010e8c671f484aec7ba51db Mon Sep 17 00:00:00 2001 From: Aaron Date: Mon, 12 Feb 2018 14:57:59 -0800 Subject: [PATCH 05/11] Fix responses to actions that return something. --- .../protocol_v2/DS2LinkConnection.java | 14 ++++- .../protocol/responder/DSInboundInvoke.java | 1 + .../dsa/dslink/transport/SocketTransport.java | 8 +-- .../java/org/iot/dsa/dslink/DSLinkConfig.java | 4 +- .../java/org/iot/dsa/dslink/DSSysNode.java | 2 +- .../iot/dsa/node/action/DSActionValues.java | 56 +++++++++++++++++++ dslink-java-v2-test/dslink.json | 15 +---- .../org/iot/dsa/dslink/test/MainNode.java | 10 ++++ 8 files changed, 88 insertions(+), 22 deletions(-) create mode 100644 dslink-core/src/main/java/org/iot/dsa/node/action/DSActionValues.java diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java index e8bc6f3a..d11d47d7 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java @@ -73,6 +73,7 @@ public void declareDefaults() { declareDefault(LAST_CONNECT_OK, DSDateTime.NULL).setTransient(true).setReadOnly(true); declareDefault(LAST_CONNECT_FAIL, DSDateTime.NULL).setTransient(true).setReadOnly(true); declareDefault(FAIL_CAUSE, DSString.NULL).setTransient(true).setReadOnly(true); + declareDefault(BROKER_URI, DSString.NULL).setTransient(true).setReadOnly(true); declareDefault(BROKER_PATH, DSString.NULL).setTransient(true).setReadOnly(true); declareDefault(BROKER_ID, DSString.NULL).setTransient(true).setReadOnly(true); declareDefault(BROKER_AUTH, DSBytes.NULL) @@ -195,12 +196,21 @@ private void recvF1() throws IOException { } //TODO check for header status put(brokerDsId, DSString.valueOf(reader.readString(in))); + System.out.println(brokerDsId.getElement().toString()); byte[] tmp = new byte[65]; - in.read(tmp); + int len = in.read(tmp); + if (len != 65) { + throw new IllegalStateException("Broker pub key not 65 bytes: " + len); + } put(BROKER_PUB_KEY, DSBytes.valueOf(tmp)); + System.out.println(brokerPubKey.getElement().toString()); tmp = new byte[32]; - in.read(tmp); + len = in.read(tmp); + if (len != 32) { + throw new IllegalStateException("Broker salt not 32 bytes: " + len); + } put(BROKER_SALT, DSBytes.valueOf(tmp)); + System.out.println(brokerSalt.getElement().toString()); } private void recvF3() throws IOException { diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundInvoke.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundInvoke.java index f5643dc0..b338268d 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundInvoke.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundInvoke.java @@ -373,6 +373,7 @@ private void writeInitialResults(MessageWriter writer) { } } } + out.endList(); if ((result == null) || !result.getAction().getResultType().isOpen()) { writeClose(writer); state = STATE_CLOSED; diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/SocketTransport.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/SocketTransport.java index 04f50097..12c5537a 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/SocketTransport.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/SocketTransport.java @@ -91,11 +91,11 @@ public DSTransport open() { } try { String url = getConnectionUrl(); - if (url.startsWith("dss:")) { - url = url.substring(4); + if (url.startsWith("dss://")) { + url = url.substring(6); socket = SSLSocketFactory.getDefault().createSocket(url, 4128); - } else if (url.startsWith("ds:")) { - url = url.substring(3); + } else if (url.startsWith("ds://")) { + url = url.substring(5); socket = new Socket(url, 4120); } else { throw new IllegalArgumentException("Invalid broker URI: " + url); diff --git a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConfig.java b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConfig.java index 2522f5a4..992e9572 100644 --- a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConfig.java +++ b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConfig.java @@ -24,7 +24,6 @@ public class DSLinkConfig { public static final String CFG_KEY_FILE = "key"; public static final String CFG_LOG_LEVEL = "log"; public static final String CFG_NODE_FILE = "nodes"; - public static final String CFG_PROTOCOL_VERSION = "protocolVersion"; public static final String CFG_CONNECTION_TYPE = "connectionType"; public static final String CFG_READ_TIMEOUT = "readTimeout"; @@ -66,6 +65,7 @@ public DSLinkConfig() { */ public DSLinkConfig(File workingDir) { this.workingDir = workingDir; + setDslinkJson(new File(workingDir, "dslink.json")); } /** @@ -73,6 +73,7 @@ public DSLinkConfig(File workingDir) { * character. */ public DSLinkConfig(String args) { + setDslinkJson(new File("dslink.json")); parse(args.split(" +")); } @@ -80,6 +81,7 @@ public DSLinkConfig(String args) { * Constructor for the arguments pass to a main method. */ public DSLinkConfig(String[] args) { + setDslinkJson(new File("dslink.json")); parse(args); } diff --git a/dslink-core/src/main/java/org/iot/dsa/dslink/DSSysNode.java b/dslink-core/src/main/java/org/iot/dsa/dslink/DSSysNode.java index e1556509..be255a67 100644 --- a/dslink-core/src/main/java/org/iot/dsa/dslink/DSSysNode.java +++ b/dslink-core/src/main/java/org/iot/dsa/dslink/DSSysNode.java @@ -44,7 +44,7 @@ public DSLink getLink() { void init() { DSLinkConfig config = getLink().getConfig(); try { - String ver = config.getConfig(DSLinkConfig.CFG_PROTOCOL_VERSION, "1"); + String ver = config.getDsaVersion(); DSLinkConnection conn; if (ver.startsWith("1")) { String type = config.getConfig(DSLinkConfig.CFG_CONNECTION_TYPE, null); diff --git a/dslink-core/src/main/java/org/iot/dsa/node/action/DSActionValues.java b/dslink-core/src/main/java/org/iot/dsa/node/action/DSActionValues.java new file mode 100644 index 00000000..6d6bc54f --- /dev/null +++ b/dslink-core/src/main/java/org/iot/dsa/node/action/DSActionValues.java @@ -0,0 +1,56 @@ +package org.iot.dsa.node.action; + +import java.util.ArrayList; +import java.util.Iterator; +import org.iot.dsa.node.DSIValue; + +/** + * This is a convenience implementation of ActionValues. It is for actions that return one or + * more values (not tables or streams). + * + * @author Aaron Hansen + */ +public class DSActionValues implements ActionValues { + + /////////////////////////////////////////////////////////////////////////// + // Fields + /////////////////////////////////////////////////////////////////////////// + + private DSAction action; + private ArrayList values = new ArrayList(); + + /////////////////////////////////////////////////////////////////////////// + // Constructors + /////////////////////////////////////////////////////////////////////////// + + public DSActionValues(DSAction action) { + this.action = action; + } + + /////////////////////////////////////////////////////////////////////////// + // Methods in alphabetical order + /////////////////////////////////////////////////////////////////////////// + + /** + * Values must be added in the same order they were defined in the action. + */ + public DSActionValues addResult(DSIValue value) { + values.add(value); + return this; + } + + public DSAction getAction() { + return action; + } + + public Iterator getValues() { + return values.iterator(); + } + + /** + * Does nothing. + */ + public void onClose() { + } + +} diff --git a/dslink-java-v2-test/dslink.json b/dslink-java-v2-test/dslink.json index 8fb4bb94..1232297a 100644 --- a/dslink-java-v2-test/dslink.json +++ b/dslink-java-v2-test/dslink.json @@ -1,27 +1,14 @@ { "name": "dslink-java-v2-test", - "dsaVersion": "2.0", "version": "1.0.0", + "dsa-version": "1.0", "description": "Testing Link", "main": "bin/dslink-java-v2-test", "configs": { "handler_class": { - "desc1": "*************** THIS IS REQUIRED AND MUST BE CHANGED ***************", - "desc2": "Fully qualified class name of the root node of the link.", "type": "string", "value": "org.iot.dsa.dslink.test.MainNode" }, - "broker": { - "desc1": "*************** OPTIONAL CONFIGS IN ALPHABETICAL ORDER ***************", - "desc2": "URL to broker.", - "type": "url", - "value": null - }, - "key": { - "desc": "Path to public/private key pair.", - "type": "path", - "value": ".key" - }, "log": { "desc": "debug, info, warn, error, none", "type": "enum", diff --git a/dslink-java-v2-test/src/main/java/org/iot/dsa/dslink/test/MainNode.java b/dslink-java-v2-test/src/main/java/org/iot/dsa/dslink/test/MainNode.java index d2f918d4..1f4f6e15 100644 --- a/dslink-java-v2-test/src/main/java/org/iot/dsa/dslink/test/MainNode.java +++ b/dslink-java-v2-test/src/main/java/org/iot/dsa/dslink/test/MainNode.java @@ -16,6 +16,7 @@ import org.iot.dsa.node.action.ActionInvocation; import org.iot.dsa.node.action.ActionResult; import org.iot.dsa.node.action.DSAction; +import org.iot.dsa.node.action.DSActionValues; /** * Link main class and node. @@ -37,6 +38,7 @@ public class MainNode extends DSMainNode implements Runnable { private DSInfo incrementingInt = getInfo("Incrementing Int"); private DSInfo reset = getInfo("Reset"); private DSInfo test = getInfo("Test"); + private DSInfo valuesAction = getInfo("Values Action"); private DSRuntime.Timer timer; /////////////////////////////////////////////////////////////////////////// @@ -59,6 +61,10 @@ protected void declareDefaults() { "My action description"); declareDefault("Reset", action); declareDefault("Test", DSAction.DEFAULT); + action = new DSAction(); + action.addValueResult("bool", DSBool.TRUE); + action.addValueResult("long", DSLong.valueOf(0)); + declareDefault("Values Action", action); } @Override @@ -77,6 +83,10 @@ public void run() { } }); return null; + } else if (actionInfo == this.valuesAction) { + return new DSActionValues(this.valuesAction.getAction()) + .addResult(DSBool.TRUE) + .addResult(DSLong.valueOf(1234)); } return super.onInvoke(actionInfo, invocation); } From 67bf46f66303bfd31e5b9270212a254070963ed2 Mon Sep 17 00:00:00 2001 From: Aaron Date: Mon, 12 Feb 2018 18:03:27 -0800 Subject: [PATCH 06/11] Fix dsa version determination logic. --- .../src/main/java/org/iot/dsa/dslink/DSLinkConfig.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConfig.java b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConfig.java index 992e9572..b3cfe729 100644 --- a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConfig.java +++ b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConfig.java @@ -188,8 +188,16 @@ private DSMap getConfigMap(String name, boolean create) { } public String getDsaVersion() { + String brokerUri = getBrokerUri(); + if (brokerUri != null) { + if (brokerUri.startsWith("http")) { + if (brokerUri.indexOf("/conn") > 0) { + return "1.0"; + } + } + } if (dsaVersion == null) { - dsaVersion = getDslinkJson().getString("dsa-version"); + dsaVersion = getDslinkJson().get("dsa-version", "2.0"); } return dsaVersion; } From bc17bd8d0d8b25db9e9a53c64456f001b199bc95 Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 13 Feb 2018 08:40:43 -0800 Subject: [PATCH 07/11] Fix connection handshake. --- .../protocol_v2/DS2LinkConnection.java | 16 ++++++--- .../org/iot/dsa/dslink/DSLinkConnection.java | 20 ++++------- .../main/java/org/iot/dsa/node/DSBytes.java | 34 ++++++++++++++++++- dslink-java-v2-test/dslink.json | 2 +- 4 files changed, 52 insertions(+), 20 deletions(-) diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java index d11d47d7..ed5f3f37 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java @@ -6,7 +6,6 @@ import com.acuity.iot.dsa.dslink.transport.SocketTransport; import java.io.IOException; import java.io.InputStream; -import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.SecureRandom; import org.iot.dsa.dslink.DSIRequester; @@ -144,11 +143,18 @@ protected void makeTransport() { @Override protected void onConnect() { transport.open(); - performHandshake(); + session.onConnect(); + try { + performHandshake(); + } catch (Exception x) { + session.onConnectFail(); + DSException.throwRuntime(x); + } } @Override protected void onDisconnect() { + session.onDisconnect(); put(STATUS, DSStatus.down); } @@ -196,21 +202,18 @@ private void recvF1() throws IOException { } //TODO check for header status put(brokerDsId, DSString.valueOf(reader.readString(in))); - System.out.println(brokerDsId.getElement().toString()); byte[] tmp = new byte[65]; int len = in.read(tmp); if (len != 65) { throw new IllegalStateException("Broker pub key not 65 bytes: " + len); } put(BROKER_PUB_KEY, DSBytes.valueOf(tmp)); - System.out.println(brokerPubKey.getElement().toString()); tmp = new byte[32]; len = in.read(tmp); if (len != 32) { throw new IllegalStateException("Broker salt not 32 bytes: " + len); } put(BROKER_SALT, DSBytes.valueOf(tmp)); - System.out.println(brokerSalt.getElement().toString()); } private void recvF3() throws IOException { @@ -258,12 +261,15 @@ private void sendF2() throws Exception { byte[] sharedSecret = getLink().getKeys().generateSharedSecret( brokerPubKey.getElement().toBytes()); byte[] tmp = brokerSalt.getElement().toBytes(); + byte[] authBytes = DSKeys.generateHmacSHA256Signature(tmp, sharedSecret); + /* byte[] authBytes = new byte[tmp.length + sharedSecret.length]; System.arraycopy(tmp, 0, authBytes, 0, tmp.length); System.arraycopy(sharedSecret, 0, authBytes, tmp.length, sharedSecret.length); MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); messageDigest.update(authBytes); authBytes = messageDigest.digest(); + */ buffer.put(authBytes); writer.write(transport); } diff --git a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConnection.java b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConnection.java index 9b770006..a19ebea3 100644 --- a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConnection.java +++ b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConnection.java @@ -3,7 +3,6 @@ import com.acuity.iot.dsa.dslink.transport.DSTransport; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; -import org.iot.dsa.DSRuntime; import org.iot.dsa.node.DSNode; import org.iot.dsa.time.DSTime; @@ -190,20 +189,15 @@ public void run() { severe(getPath(), x); continue; } - DSRuntime.runDelayed(new Runnable() { - @Override - public void run() { - if (listeners != null) { - for (Listener listener : listeners.keySet()) { - try { - listener.onConnect(DSLinkConnection.this); - } catch (Exception x) { - severe(listener.toString(), x); - } - } + if (listeners != null) { + for (Listener listener : listeners.keySet()) { + try { + listener.onConnect(DSLinkConnection.this); + } catch (Exception x) { + severe(listener.toString(), x); } } - }, 1000); + } try { onRun(); reconnectRate = 1000; diff --git a/dslink-core/src/main/java/org/iot/dsa/node/DSBytes.java b/dslink-core/src/main/java/org/iot/dsa/node/DSBytes.java index 7efdaa77..4217256e 100644 --- a/dslink-core/src/main/java/org/iot/dsa/node/DSBytes.java +++ b/dslink-core/src/main/java/org/iot/dsa/node/DSBytes.java @@ -1,6 +1,5 @@ package org.iot.dsa.node; -import com.acuity.iot.dsa.dslink.io.DSByteBuffer; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -18,6 +17,7 @@ public class DSBytes extends DSElement { // Constants // --------- + private final static char[] HEX = "0123456789ABCDEF".toCharArray(); public static final DSBytes NULL = new DSBytes(new byte[0]); private static final String PREFIX = "\u001Bbytes:"; @@ -62,6 +62,19 @@ public boolean equals(Object arg) { return false; } + /** + * Converts a hex string into a byte array. + */ + public static byte[] fromHex(CharSequence s) { + int len = s.length(); + byte[] ret = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + ret[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i + 1), 16)); + } + return ret; + } + /** * The raw bytes, do not modify. */ @@ -300,6 +313,25 @@ public byte[] toBytes() { return value; } + /** + * Converts the bytes into a hex string. + * + * @param bytes What to convert. + * @param buf Where to put the results, can be null. + * @return The buf parameter, or a new StringBuilder if the param was null. + */ + public static StringBuilder toHex(byte[] bytes, StringBuilder buf) { + if (buf == null) { + buf = new StringBuilder(); + } + for (int i = 0, len = bytes.length; i < len; i++) { + int val = bytes[i] & 0xFF; + buf.append(HEX[val >>> 4]); + buf.append(HEX[val & 0x0F]); + } + return buf; + } + @Override public String toString() { if (isNull()) { diff --git a/dslink-java-v2-test/dslink.json b/dslink-java-v2-test/dslink.json index 1232297a..ab07b557 100644 --- a/dslink-java-v2-test/dslink.json +++ b/dslink-java-v2-test/dslink.json @@ -1,7 +1,7 @@ { "name": "dslink-java-v2-test", "version": "1.0.0", - "dsa-version": "1.0", + "dsa-version": "2.0", "description": "Testing Link", "main": "bin/dslink-java-v2-test", "configs": { From 7bad72e570f8f86f524c7c1afdcbf79cc62390a4 Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 13 Feb 2018 08:50:46 -0800 Subject: [PATCH 08/11] Reformat code --- .../protocol/protocol_v2/DS2LinkConnection.java | 1 - .../protocol_v2/responder/DS2InboundInvoke.java | 2 +- .../protocol_v2/responder/DS2InboundList.java | 2 +- .../protocol_v2/responder/DS2InboundSet.java | 3 +-- .../responder/DS2InboundSubscription.java | 4 ++-- .../protocol/protocol_v2/responder/DS2Responder.java | 1 - .../dslink/protocol/responder/DSInboundRequest.java | 1 - .../protocol/responder/DSInboundSubscription.java | 3 ++- .../dsa/dslink/protocol/responder/DSResponder.java | 1 + .../src/main/java/org/iot/dsa/dslink/DSSysNode.java | 4 +--- .../src/main/java/org/iot/dsa/io/AbstractWriter.java | 12 ++++++------ .../src/main/java/org/iot/dsa/node/DSPath.java | 2 +- .../main/java/org/iot/dsa/security/DSPermission.java | 8 ++++---- .../java/org/iot/dsa/dslink/V2HandshakeTest.java | 2 -- 14 files changed, 20 insertions(+), 26 deletions(-) diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java index ed5f3f37..c0df72dc 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java @@ -6,7 +6,6 @@ import com.acuity.iot.dsa.dslink.transport.SocketTransport; import java.io.IOException; import java.io.InputStream; -import java.security.MessageDigest; import java.security.SecureRandom; import org.iot.dsa.dslink.DSIRequester; import org.iot.dsa.dslink.DSLink; diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundInvoke.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundInvoke.java index d999786d..979805aa 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundInvoke.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundInvoke.java @@ -26,7 +26,7 @@ public void write(MessageWriter writer) { out.init(getRequestId(), getSession().getNextAck()); out.setMethod((byte) MSG_INVOKE_RES); super.write(writer); - out.write((DSBinaryTransport)getResponder().getTransport()); + out.write((DSBinaryTransport) getResponder().getTransport()); //if has multipart } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundList.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundList.java index f34880f9..8fc7d6a9 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundList.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundList.java @@ -20,7 +20,7 @@ public void write(MessageWriter writer) { out.init(getRequestId(), getSession().getNextAck()); out.setMethod((byte) MSG_LIST_RES); super.write(writer); - out.write((DSBinaryTransport)getResponder().getTransport()); + out.write((DSBinaryTransport) getResponder().getTransport()); //if has multipart } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSet.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSet.java index 2e5a3ed0..3018cacb 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSet.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSet.java @@ -3,7 +3,6 @@ import com.acuity.iot.dsa.dslink.protocol.protocol_v2.CloseMessage; import com.acuity.iot.dsa.dslink.protocol.protocol_v2.MessageConstants; import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundSet; -import com.acuity.iot.dsa.dslink.transport.DSBinaryTransport; import org.iot.dsa.node.DSElement; import org.iot.dsa.security.DSPermission; @@ -17,7 +16,7 @@ public DS2InboundSet(DSElement value, DSPermission permission) { protected void sendClose() { getResponder().sendResponse(new CloseMessage((DS2Responder) getResponder(), getRequestId(), - (byte)MSG_SET_RES)); + (byte) MSG_SET_RES)); } } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscription.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscription.java index 3f118570..03ad10d5 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscription.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscription.java @@ -38,7 +38,7 @@ protected DS2InboundSubscription(DSInboundSubscriptions manager, protected void write(Update update, MessageWriter writer, StringBuilder buf) { DS2MessageWriter out = (DS2MessageWriter) writer; out.init(getSubscriptionId(), getSession().getNextAck()); - out.setMethod((byte)MSG_SUBSCRIBE_RES); + out.setMethod((byte) MSG_SUBSCRIBE_RES); DSByteBuffer byteBuffer = out.getBody(); byteBuffer.skip(2); MsgpackWriter msgpackWriter = new MsgpackWriter(byteBuffer); @@ -52,7 +52,7 @@ protected void write(Update update, MessageWriter writer, StringBuilder buf) { msgpackWriter.endMap(); byteBuffer.replaceShort(0, (short) msgpackWriter.length(), false); msgpackWriter.value(update.value.toElement()); - out.write((DSBinaryTransport)getResponder().getTransport()); + out.write((DSBinaryTransport) getResponder().getTransport()); } } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java index 8870adcf..9adc6b2a 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java @@ -1,6 +1,5 @@ package com.acuity.iot.dsa.dslink.protocol.protocol_v2.responder; -import com.acuity.iot.dsa.dslink.io.DSByteBuffer; import com.acuity.iot.dsa.dslink.protocol.DSStream; import com.acuity.iot.dsa.dslink.protocol.protocol_v2.CloseMessage; import com.acuity.iot.dsa.dslink.protocol.protocol_v2.DS2MessageReader; diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundRequest.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundRequest.java index d855f45f..827480b1 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundRequest.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundRequest.java @@ -1,7 +1,6 @@ package com.acuity.iot.dsa.dslink.protocol.responder; import com.acuity.iot.dsa.dslink.DSSession; -import com.acuity.iot.dsa.dslink.protocol.message.ErrorResponse; import org.iot.dsa.dslink.DSLink; import org.iot.dsa.dslink.responder.InboundRequest; import org.iot.dsa.logging.DSLogger; diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscription.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscription.java index 7ab205d0..afeda49d 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscription.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscription.java @@ -46,7 +46,8 @@ public class DSInboundSubscription extends DSInboundRequest // Constructors /////////////////////////////////////////////////////////////////////////// - protected DSInboundSubscription(DSInboundSubscriptions manager, Integer sid, String path, int qos) { + protected DSInboundSubscription(DSInboundSubscriptions manager, Integer sid, String path, + int qos) { this.manager = manager; this.sid = sid; setPath(path); diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSResponder.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSResponder.java index fcc81d79..62346b9a 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSResponder.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSResponder.java @@ -92,6 +92,7 @@ public void onDisconnect() { public DSStream putRequest(Integer rid, DSStream request) { return inboundRequests.put(rid, request); } + public DSStream removeRequest(Integer rid) { return inboundRequests.remove(rid); } diff --git a/dslink-core/src/main/java/org/iot/dsa/dslink/DSSysNode.java b/dslink-core/src/main/java/org/iot/dsa/dslink/DSSysNode.java index be255a67..95ecd083 100644 --- a/dslink-core/src/main/java/org/iot/dsa/dslink/DSSysNode.java +++ b/dslink-core/src/main/java/org/iot/dsa/dslink/DSSysNode.java @@ -2,9 +2,7 @@ import com.acuity.iot.dsa.dslink.protocol.protocol_v1.DS1LinkConnection; import com.acuity.iot.dsa.dslink.protocol.protocol_v2.DS2LinkConnection; -import java.util.logging.Logger; import org.iot.dsa.node.DSInfo; -import org.iot.dsa.node.DSMap; import org.iot.dsa.node.DSNode; import org.iot.dsa.node.action.ActionInvocation; import org.iot.dsa.node.action.ActionResult; @@ -67,7 +65,7 @@ void init() { public ActionResult onInvoke(DSInfo action, ActionInvocation invocation) { if (action == save) { getLink().save(); - } else if (action == stop){ + } else if (action == stop) { getLink().shutdown(); } else { super.onInvoke(action, invocation); diff --git a/dslink-core/src/main/java/org/iot/dsa/io/AbstractWriter.java b/dslink-core/src/main/java/org/iot/dsa/io/AbstractWriter.java index a634fb72..60cf295b 100644 --- a/dslink-core/src/main/java/org/iot/dsa/io/AbstractWriter.java +++ b/dslink-core/src/main/java/org/iot/dsa/io/AbstractWriter.java @@ -246,7 +246,7 @@ public AbstractWriter value(boolean arg) { switch (last) { case LAST_DONE: throw new IllegalStateException("Nesting error: " + arg); - //case LAST_INIT: + //case LAST_INIT: //throw new IllegalStateException("Not expecting value: " + arg); case LAST_VAL: case LAST_END: @@ -274,7 +274,7 @@ public AbstractWriter value(byte[] arg) { switch (last) { case LAST_DONE: throw new IllegalStateException("Nesting error"); - //case LAST_INIT: + //case LAST_INIT: //throw new IllegalStateException("Not expecting byte[] value"); case LAST_VAL: case LAST_END: @@ -302,7 +302,7 @@ public AbstractWriter value(double arg) { switch (last) { case LAST_DONE: throw new IllegalStateException("Nesting error: " + arg); - //case LAST_INIT: + //case LAST_INIT: //throw new IllegalStateException("Not expecting value: " + arg); case LAST_VAL: case LAST_END: @@ -330,7 +330,7 @@ public AbstractWriter value(int arg) { switch (last) { case LAST_DONE: throw new IllegalStateException("Nesting error: " + arg); - //case LAST_INIT: + //case LAST_INIT: //throw new IllegalStateException("Not expecting value: " + arg); case LAST_VAL: case LAST_END: @@ -358,7 +358,7 @@ public AbstractWriter value(long arg) { switch (last) { case LAST_DONE: throw new IllegalStateException("Nesting error: " + arg); - //case LAST_INIT: + //case LAST_INIT: //throw new IllegalStateException("Not expecting value: " + arg); case LAST_VAL: case LAST_END: @@ -386,7 +386,7 @@ public AbstractWriter value(String arg) { switch (last) { case LAST_DONE: throw new IllegalStateException("Nesting error: " + arg); - //case LAST_INIT: + //case LAST_INIT: //throw new IllegalStateException("Not expecting value: " + arg); case LAST_VAL: case LAST_END: diff --git a/dslink-core/src/main/java/org/iot/dsa/node/DSPath.java b/dslink-core/src/main/java/org/iot/dsa/node/DSPath.java index ec3ce52d..a33f094e 100644 --- a/dslink-core/src/main/java/org/iot/dsa/node/DSPath.java +++ b/dslink-core/src/main/java/org/iot/dsa/node/DSPath.java @@ -188,7 +188,7 @@ public static boolean encodeName(String name, StringBuilder buf) { } } i++; - } while (i < pathLength && shouldEncode((c = name.charAt(i)),i)); + } while (i < pathLength && shouldEncode((c = name.charAt(i)), i)); charArrayWriter.flush(); String str = new String(charArrayWriter.toCharArray()); byte[] bytes = str.getBytes(utf8); diff --git a/dslink-core/src/main/java/org/iot/dsa/security/DSPermission.java b/dslink-core/src/main/java/org/iot/dsa/security/DSPermission.java index 3053fb46..ab19a118 100644 --- a/dslink-core/src/main/java/org/iot/dsa/security/DSPermission.java +++ b/dslink-core/src/main/java/org/iot/dsa/security/DSPermission.java @@ -57,13 +57,13 @@ public String toString() { public static DSPermission valueOf(int v2byte) { switch (v2byte) { - case 0x10 : + case 0x10: return LIST; - case 0x20 : + case 0x20: return READ; - case 0x30 : + case 0x30: return WRITE; - case 0x40 : + case 0x40: return CONFIG; } throw new IllegalArgumentException("Unknown permission: " + v2byte); diff --git a/dslink-core/src/test/java/org/iot/dsa/dslink/V2HandshakeTest.java b/dslink-core/src/test/java/org/iot/dsa/dslink/V2HandshakeTest.java index 42561540..be8d166c 100644 --- a/dslink-core/src/test/java/org/iot/dsa/dslink/V2HandshakeTest.java +++ b/dslink-core/src/test/java/org/iot/dsa/dslink/V2HandshakeTest.java @@ -5,13 +5,11 @@ import com.acuity.iot.dsa.dslink.protocol.protocol_v2.DS2MessageWriter; import java.io.ByteArrayInputStream; import java.io.InputStream; -import java.nio.ByteBuffer; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; import java.util.Arrays; import javax.xml.bind.DatatypeConverter; -import org.iot.dsa.node.DSBytes; import org.iot.dsa.security.DSKeys; import org.junit.Assert; import org.junit.Test; From 1d381c81c76d102dab4315003af955ec86af80c4 Mon Sep 17 00:00:00 2001 From: Aaron Date: Sun, 18 Feb 2018 13:18:47 -0800 Subject: [PATCH 09/11] V2 Protocol fixes. --- .../com/acuity/iot/dsa/dslink/DSSession.java | 50 ++- .../iot/dsa/dslink/io/DSByteBuffer.java | 54 ++- .../dslink/protocol/message/RequestPath.java | 6 + .../protocol_v1/DS1ConnectionInit.java | 19 +- .../protocol_v1/DS1LinkConnection.java | 4 +- .../protocol/protocol_v1/DS1Session.java | 12 +- .../requester/DS1OutboundInvokeStub.java | 2 +- .../requester/DS1OutboundListStub.java | 2 +- .../requester/DS1OutboundStub.java | 4 +- .../requester/DS1OutboundSubscribeStub.java | 4 +- .../requester/DS1OutboundSubscribeStubs.java | 4 +- .../requester/DS1OutboundSubscriptions.java | 18 +- .../protocol_v1/requester/DS1Requester.java | 1 + .../protocol_v1/responder/DS1Responder.java | 12 +- .../protocol/protocol_v2/AckMessage.java | 26 ++ .../protocol_v2/DS2LinkConnection.java | 10 +- .../protocol/protocol_v2/DS2Message.java | 149 ++++++++ .../protocol_v2/DS2MessageReader.java | 84 +++-- .../protocol_v2/DS2MessageWriter.java | 137 +++---- .../protocol/protocol_v2/DS2Session.java | 109 +++++- .../protocol_v2/MessageConstants.java | 51 +-- .../protocol/protocol_v2/PingMessage.java | 2 +- .../protocol_v2/responder/DS2InboundList.java | 85 +++++ .../protocol_v2/responder/DS2Responder.java | 7 +- .../protocol_v2/responder/ErrorMessage.java | 11 +- .../protocol/responder/DSInboundInvoke.java | 6 +- .../protocol/responder/DSInboundList.java | 335 ++++++++++-------- .../protocol/responder/DSInboundRequest.java | 8 + .../protocol/responder/DSInboundSet.java | 2 +- .../responder/DSInboundSubscription.java | 13 +- .../responder/DSInboundSubscriptions.java | 15 +- .../protocol/responder/DSResponder.java | 17 +- .../iot/dsa/dslink/transport/DSTransport.java | 43 ++- .../dslink/transport/PushBinaryTransport.java | 4 +- .../dsa/dslink/transport/SocketTransport.java | 130 ++++++- .../main/java/org/iot/dsa/dslink/DSLink.java | 19 +- .../java/org/iot/dsa/dslink/DSLinkConfig.java | 30 +- .../org/iot/dsa/dslink/DSLinkConnection.java | 23 +- .../java/org/iot/dsa/dslink/DSMainNode.java | 16 +- .../java/org/iot/dsa/dslink/DSSysNode.java | 7 +- .../org/iot/dsa/io/msgpack/MsgpackWriter.java | 11 + .../org/iot/dsa/logging/AsyncLogHandler.java | 40 ++- .../java/org/iot/dsa/logging/DSILevels.java | 30 ++ .../java/org/iot/dsa/logging/DSLevel.java | 11 + .../java/org/iot/dsa/logging/DSLogger.java | 195 +++++----- .../dsa/logging/PrintStreamLogHandler.java | 26 +- .../main/java/org/iot/dsa/node/DSBytes.java | 18 +- .../main/java/org/iot/dsa/node/DSGroup.java | 4 + .../src/main/java/org/iot/dsa/node/DSMap.java | 2 +- .../main/java/org/iot/dsa/node/DSNode.java | 38 +- .../main/java/org/iot/dsa/node/DSPath.java | 57 ++- .../main/java/org/iot/dsa/time/DSTime.java | 9 + dslink-java-v2-test/dslink.json | 4 +- .../org/iot/dsa/dslink/test/MainNode.java | 1 + .../dsa/dslink/websocket/WsTextTransport.java | 12 +- 55 files changed, 1335 insertions(+), 654 deletions(-) create mode 100644 dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/AckMessage.java create mode 100644 dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Message.java create mode 100644 dslink-core/src/main/java/org/iot/dsa/logging/DSILevels.java create mode 100644 dslink-core/src/main/java/org/iot/dsa/logging/DSLevel.java diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/DSSession.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/DSSession.java index bf772421..ab1ea9f4 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/DSSession.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/DSSession.java @@ -4,7 +4,6 @@ import com.acuity.iot.dsa.dslink.transport.DSTransport; import java.util.LinkedList; import java.util.List; -import java.util.logging.Logger; import org.iot.dsa.dslink.DSIRequester; import org.iot.dsa.dslink.DSLinkConnection; import org.iot.dsa.node.DSNode; @@ -31,7 +30,6 @@ public abstract class DSSession extends DSNode { private int nextMessage = 1; private boolean connected = false; private DSLinkConnection connection; - private Logger logger; private Object outgoingMutex = new Object(); private List outgoingRequests = new LinkedList(); private List outgoingResponses = new LinkedList(); @@ -135,11 +133,8 @@ public DSLinkConnection getConnection() { } @Override - public Logger getLogger() { - if (logger == null) { - logger = Logger.getLogger(getConnection().getLink().getLinkName() + ".session"); - } - return logger; + protected String getLogName() { + return "Session"; } /** @@ -168,20 +163,8 @@ public DSTransport getTransport() { return getConnection().getTransport(); } - /** - * True if there are any outbound requests or responses queued up. - */ - protected final boolean hasMessagesToSend() { - if (nextAck > 0) { - return true; - } - if (!outgoingResponses.isEmpty()) { - return true; - } - if (!outgoingRequests.isEmpty()) { - return true; - } - return false; + protected boolean hasAckToSend() { + return nextAck > 0; } protected boolean hasOutgoingRequests() { @@ -196,7 +179,16 @@ protected boolean hasOutgoingResponses() { * Override point, this returns the result of hasMessagesToSend. */ protected boolean hasSomethingToSend() { - return hasMessagesToSend(); + if (nextAck > 0) { + return true; + } + if (!outgoingResponses.isEmpty()) { + return true; + } + if (!outgoingRequests.isEmpty()) { + return true; + } + return false; } protected boolean isConnected() { @@ -240,9 +232,11 @@ public void onDisconnect() { /** * Call for each incoming message id that needs to be acked. */ - protected synchronized void setNextAck(int nextAck) { - this.nextAck = nextAck; - notifyOutgoing(); + public synchronized void setNextAck(int nextAck) { + if (nextAck > 0) { + this.nextAck = nextAck; + notifyOutgoing(); + } } /** @@ -267,7 +261,7 @@ public void run() { getTransport().close(); if (connected) { connected = false; - severe(getPath(), x); + error(getPath(), x); } } } @@ -295,7 +289,7 @@ public void run() { try { outgoingMutex.wait(5000); } catch (InterruptedException x) { - fine(getPath(), x); + warn(getPath(), x); } continue; } @@ -306,7 +300,7 @@ public void run() { if (connected) { connected = false; getTransport().close(); - severe(getPath(), x); + error(getPath(), x); } } } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/io/DSByteBuffer.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/io/DSByteBuffer.java index 16e4dd91..be86e7df 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/io/DSByteBuffer.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/io/DSByteBuffer.java @@ -1,8 +1,12 @@ package com.acuity.iot.dsa.dslink.io; import com.acuity.iot.dsa.dslink.transport.DSBinaryTransport; +import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintStream; import java.nio.ByteBuffer; +import org.iot.dsa.node.DSBytes; import org.iot.dsa.util.DSException; /** @@ -11,7 +15,7 @@ * * @author Aaron Hansen */ -public class DSByteBuffer { +public class DSByteBuffer extends InputStream { /////////////////////////////////////////////////////////////////////////// // Fields @@ -71,6 +75,28 @@ public int length() { return length; } + /** + * Prints the current contents of the buffer, doesn't modify it in any way. + */ + public void print(PrintStream out, int cols) { + StringBuilder buf = new StringBuilder(); + int buflen; + for (int i = offset, len = offset + length; i < len; i++) { + buflen = buf.length(); + if ((buflen + 3) > cols) { + out.println(buf.toString()); + buf.setLength(0); + } + if (buflen > 0) { + buf.append(' '); + } + DSBytes.toHex(buffer[i], buf); + } + if (buf.length() > 0) { + out.println(buf.toString()); + } + } + /** * /** * Gets the bytes from the given buffer, which will be flipped, then cleared. @@ -242,6 +268,24 @@ public DSByteBuffer put(int dest, byte[] msg, int off, int len) { return this; } + public int put(InputStream in, int len) { + int count = 0; + try { + int ch; + while (count < len) { + ch = in.read(); + if (ch < 0) { + return count; + } + put((byte) ch); + count++; + } + } catch (IOException x) { + DSException.throwRuntime(x); + } + return count; + } + /** * Encodes the primitive into buffer using big endian encoding. */ @@ -430,10 +474,6 @@ public DSByteBuffer replace(int dest, byte b1, byte b2, byte b3, byte b4) { * @param bigEndian Whether to encode in big or little endian byte ordering. */ public DSByteBuffer replaceInt(int dest, int v, boolean bigEndian) { - if (offset > 0) { - System.arraycopy(buffer, offset, buffer, 0, length); - offset = 0; - } if (bigEndian) { return replace(dest, (byte) ((v >>> 24) & 0xFF), (byte) ((v >>> 16) & 0xFF), @@ -455,10 +495,6 @@ public DSByteBuffer replaceInt(int dest, int v, boolean bigEndian) { * @param bigEndian Whether to encode in big or little endian byte ordering. */ public DSByteBuffer replaceShort(int dest, short v, boolean bigEndian) { - if (offset > 0) { - System.arraycopy(buffer, offset, buffer, 0, length); - offset = 0; - } if (bigEndian) { return replace(dest, (byte) ((v >>> 8) & 0xFF), (byte) ((v >>> 0) & 0xFF)); } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/RequestPath.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/RequestPath.java index 62f302d7..9bf638cd 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/RequestPath.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/message/RequestPath.java @@ -30,6 +30,12 @@ public class RequestPath { /////////////////////////////////////////////////////////////////////////// public RequestPath(String path, DSNode root) { + if (root == null) { + throw new NullPointerException("Null root"); + } + if (path == null) { + path = ""; + } this.path = path; this.root = root; names = DSPath.decodePath(path); diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1ConnectionInit.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1ConnectionInit.java index 18c53153..f6bf9d8f 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1ConnectionInit.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1ConnectionInit.java @@ -6,7 +6,6 @@ import java.net.HttpURLConnection; import java.net.URL; import java.security.MessageDigest; -import java.util.logging.Logger; import org.iot.dsa.dslink.DSLink; import org.iot.dsa.dslink.DSLinkConfig; import org.iot.dsa.io.DSBase64; @@ -44,7 +43,6 @@ public class DS1ConnectionInit extends DSNode { private String brokerUri; private DS1LinkConnection connection; private DSLink link; - private Logger logger; private DSMap response; /////////////////////////////////////////////////////////////////////////// @@ -59,17 +57,6 @@ DSLink getLink() { return connection.getLink(); } - /** - * Uses the connection's logger. - */ - @Override - public Logger getLogger() { - if (logger == null) { - logger = connection.getLogger(); - } - return logger; - } - /** * Returns the brokers response to connection initialization. */ @@ -85,7 +72,7 @@ DSMap getResponse() { */ void initializeConnection() throws Exception { String uri = makeBrokerUrl(); - config(config() ? "Broker URI " + uri : null); + fine(fine() ? "Broker URI " + uri : null); HttpURLConnection conn = (HttpURLConnection) new URL(uri).openConnection(); conn.setDoInput(true); conn.setDoOutput(true); @@ -106,7 +93,7 @@ void initializeConnection() throws Exception { in = new JsonReader(conn.getInputStream(), "UTF-8"); response = in.getMap(); put(BROKER_RES, response).setReadOnly(true); - finest(finest() ? response : null); + trace(trace() ? response : null); } finally { if (in != null) { in.close(); @@ -255,7 +242,7 @@ void writeConnectionRequest(HttpURLConnection conn) throws Exception { } map.put("enableWebSocketCompression", false); put(BROKER_REQ, map).setReadOnly(true); - finest(finest() ? map.toString() : null); + trace(trace() ? map.toString() : null); out.value(map); } finally { if (out != null) { diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1LinkConnection.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1LinkConnection.java index ca9739f6..365f90ea 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1LinkConnection.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1LinkConnection.java @@ -113,13 +113,13 @@ protected DSTransport makeTransport(DS1ConnectionInit init) { DSException.throwRuntime(x); } String uri = init.makeWsUrl(wsUri); - config(config() ? "Connection URL = " + uri : null); + fine(fine() ? "Connection URL = " + uri : null); transport.setConnectionUrl(uri); transport.setConnection(this); transport.setReadTimeout(getLink().getConfig().getConfig( DSLinkConfig.CFG_READ_TIMEOUT, 60000)); setTransport(transport); - config(config() ? "Transport type: " + transport.getClass().getName() : null); + fine(fine() ? "Transport type: " + transport.getClass().getName() : null); return transport; } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1Session.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1Session.java index 838d995c..9e2bf088 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1Session.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1Session.java @@ -138,16 +138,16 @@ protected void doSendMessage() { DSIWriter writer = getWriter(); writer.reset(); requestsNext = !requestsNext; - transport.beginMessage(); + transport.beginSendMessage(); beginMessage(); - if (hasMessagesToSend()) { + if (this.hasSomethingToSend()) { send(requestsNext, endTime); if ((System.currentTimeMillis() < endTime) && !shouldEndMessage()) { send(!requestsNext, endTime); } } endMessage(); - transport.endMessage(); + transport.endSendMessage(); lastMessageSent = System.currentTimeMillis(); } @@ -286,14 +286,14 @@ protected void processEnvelope(DSIReader reader) { throw new IllegalStateException("Allowed not a boolean"); } if (reader.getBoolean()) { - config(config() ? "Requester allowed" : null); + fine(fine() ? "Requester allowed" : null); getConnection().setRequesterAllowed(); } } else if (key.equals("salt")) { if (reader.next() != Token.STRING) { throw new IllegalStateException("Salt not a string"); } - config(config() ? "Next salt: " + reader.getString() : null); + fine(fine() ? "Next salt: " + reader.getString() : null); getConnection().updateSalt(reader.getString()); } next = reader.next(); @@ -322,7 +322,7 @@ protected void processMessages(DSIReader reader, boolean areRequests) { req = reader.getMap(); rid = req.get("rid", -1); if (rid < 0) { - finest(finest() ? "No request ID: " + req.toString() : null); + trace(trace() ? "No request ID: " + req.toString() : null); throw new DSProtocolException("Response missing rid"); } if (areRequests) { diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundInvokeStub.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundInvokeStub.java index 2c82bd61..86171954 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundInvokeStub.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundInvokeStub.java @@ -94,7 +94,7 @@ protected void handleResponse(DSMap response) { handler.onUpdate(updates.get(i).toList()); } } catch (Exception x) { - getRequester().severe(getRequester().getPath(), x); + getRequester().error(getRequester().getPath(), x); } } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundListStub.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundListStub.java index ef211fff..a18786bd 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundListStub.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundListStub.java @@ -73,7 +73,7 @@ protected void handleResponse(DSMap response) { request.onInitialized(); } } catch (Exception x) { - getRequester().severe(getRequester().getPath(), x); + getRequester().error(getRequester().getPath(), x); } } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundStub.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundStub.java index 823f8e39..a047ef4f 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundStub.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundStub.java @@ -68,7 +68,7 @@ protected void handleClose() { try { getHandler().onClose(); } catch (Exception x) { - getRequester().severe(getRequester().getPath(), x); + getRequester().error(getRequester().getPath(), x); } getRequester().removeRequest(getRequestId()); } @@ -91,7 +91,7 @@ protected void handleError(DSElement details) { } getHandler().onError(type, msg, detail); } catch (Exception x) { - getRequester().severe(getRequester().getPath(), x); + getRequester().error(getRequester().getPath(), x); } } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundSubscribeStub.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundSubscribeStub.java index c7ab4780..a356c0ae 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundSubscribeStub.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundSubscribeStub.java @@ -56,7 +56,7 @@ public void closeStream() { try { request.onClose(); } catch (Exception x) { - stubs.getSubscriptions().severe(path, x); + stubs.getSubscriptions().error(path, x); } } @@ -84,7 +84,7 @@ public void process(DSDateTime ts, DSElement value, DSStatus status) { try { request.onUpdate(ts, value, status); } catch (Exception x) { - stubs.getSubscriptions().severe(path, x); + stubs.getSubscriptions().error(path, x); } } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundSubscribeStubs.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundSubscribeStubs.java index 4465c6cf..d1d2b0b2 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundSubscribeStubs.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundSubscribeStubs.java @@ -68,7 +68,7 @@ void add(DS1OutboundSubscribeStub stub) { try { stub.process(lastTs, lastValue, lastStatus); } catch (Exception x) { - subscriptions.severe(path, x); + subscriptions.error(path, x); } } } @@ -138,7 +138,7 @@ void process(DSDateTime ts, DSElement value, DSStatus status) { try { stub.process(ts, value, status); } catch (Exception x) { - subscriptions.severe(path, x); + subscriptions.error(path, x); } stub = stub.getNext(); } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundSubscriptions.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundSubscriptions.java index b43625ce..7bf2eb44 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundSubscriptions.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1OutboundSubscriptions.java @@ -8,7 +8,6 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.logging.Logger; import org.iot.dsa.dslink.requester.OutboundSubscribeHandler; import org.iot.dsa.io.DSIWriter; import org.iot.dsa.logging.DSLogger; @@ -37,7 +36,6 @@ class DS1OutboundSubscriptions extends DSLogger implements OutboundMessage { /////////////////////////////////////////////////////////////////////////// private boolean enqueued = false; - private Logger logger; private int nextSid = 1; private final ConcurrentLinkedQueue pendingSubscribe = new ConcurrentLinkedQueue(); @@ -61,14 +59,6 @@ class DS1OutboundSubscriptions extends DSLogger implements OutboundMessage { // Methods in alphabetical order /////////////////////////////////////////////////////////////////////////// - @Override - public Logger getLogger() { - if (logger == null) { - logger = requester.getLogger(); - } - return logger; - } - /** * This is already synchronized by the subscribe method. */ @@ -101,7 +91,7 @@ void processUpdate(DSElement updateElement) { DSList updateList = (DSList) updateElement; int cols = updateList.size(); if (cols < 3) { - finest(finest() ? "Update incomplete: " + updateList.toString() : null); + trace(trace() ? "Update incomplete: " + updateList.toString() : null); return; } sid = updateList.get(0, -1); @@ -118,12 +108,12 @@ void processUpdate(DSElement updateElement) { return; } if (sid < 0) { - finer(finer() ? "Update missing sid: " + updateElement.toString() : null); + debug(debug() ? "Update missing sid: " + updateElement.toString() : null); return; } DS1OutboundSubscribeStubs stub = sidMap.get(sid); if (stub == null) { - finer(finer() ? ("Unexpected subscription update sid=" + sid) : null); + debug(debug() ? ("Unexpected subscription update sid=" + sid) : null); return; } DSDateTime timestamp = null; @@ -183,7 +173,7 @@ OutboundSubscribeHandler subscribe(String path, int qos, OutboundSubscribeHandle try { req.onInit(path, stubs.getQos(), stub); } catch (Exception x) { - severe(path, x); + error(path, x); } sendMessage(); return req; diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1Requester.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1Requester.java index 187e6e10..0de3c72c 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1Requester.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/requester/DS1Requester.java @@ -87,6 +87,7 @@ public OutboundListHandler list(String path, OutboundListHandler req) { public void onConnect() { subscriptions.onConnect(); + session.setRequesterAllowed(); } public void onConnectFail() { diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1Responder.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1Responder.java index 5c8a715c..31bd1a55 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1Responder.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/responder/DS1Responder.java @@ -95,7 +95,7 @@ public void run() { case 'r': //remove if (method.equals("remove")) { //Does this even make sense in a link? - severe("Remove method called"); + error("Remove method called"); sendClose(rid); } else { sendInvalidMethod(rid, method); @@ -134,7 +134,7 @@ public void run() { } catch (DSProtocolException x) { sendError(rid, x); } catch (Throwable x) { - severe(getPath(), x); + error(getPath(), x); sendError(rid, x); } } @@ -146,13 +146,13 @@ public void onConnectFail() { } public void onDisconnect() { - finer(finer() ? "Close" : null); + debug(debug() ? "Close" : null); subscriptions.close(); for (Map.Entry entry : getRequests().entrySet()) { try { entry.getValue().onClose(entry.getKey()); } catch (Exception x) { - finer(finer() ? "Close" : null, x); + debug(debug() ? "Close" : null, x); } } getRequests().clear(); @@ -242,7 +242,7 @@ private void processUnsubscribe(int rid, DSMap req) { sid = list.getInt(i); subscriptions.unsubscribe(sid); } catch (Exception x) { - severe(fine() ? "Unsubscribe: " + sid : null, x); + error(fine() ? "Unsubscribe: " + sid : null, x); } } } @@ -265,7 +265,7 @@ public void sendError(DSInboundRequest req, Throwable reason) { */ private void sendInvalidMethod(int rid, String methodName) { String msg = "Invalid method: " + methodName; - severe(msg); + error(msg); sendResponse(new ErrorMessage(rid, msg).setType("invalidMethod")); } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/AckMessage.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/AckMessage.java new file mode 100644 index 00000000..37498505 --- /dev/null +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/AckMessage.java @@ -0,0 +1,26 @@ +package com.acuity.iot.dsa.dslink.protocol.protocol_v2; + +import com.acuity.iot.dsa.dslink.protocol.message.MessageWriter; +import com.acuity.iot.dsa.dslink.protocol.message.OutboundMessage; + +/** + * @author Aaron Hansen + */ +class AckMessage implements MessageConstants, OutboundMessage { + + private DS2Session session; + + public AckMessage(DS2Session session) { + this.session = session; + } + + @Override + public void write(MessageWriter writer) { + DS2MessageWriter out = (DS2MessageWriter) writer; + out.init(-1, -1); + out.setMethod(MessageConstants.MSG_ACK); + out.getBody().putInt(session.getNextAck(), false); + out.write(session.getTransport()); + } + +} diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java index c0df72dc..41e5a7b0 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2LinkConnection.java @@ -45,6 +45,7 @@ public class DS2LinkConnection extends DSLinkConnection { private static final String REQUESTER_ALLOWED = "Requester Allowed"; private static final String SESSION = "Status"; private static final String STATUS = "Status"; + private static final String TRANSPORT = "Transport"; /////////////////////////////////////////////////////////////////////////// // Fields @@ -134,7 +135,8 @@ protected void makeTransport() { } else if (uri.startsWith("ds")) { transport = new SocketTransport(); } - config(config() ? "Connection URL = " + uri : null); + put(TRANSPORT, transport); + fine(fine() ? "Connection URL = " + uri : null); transport.setConnectionUrl(uri); transport.setConnection(this); } @@ -224,7 +226,11 @@ private void recvF3() throws IOException { Integer.toHexString(reader.getMethod())); } //TODO check for header status - put(requesterAllowed, DSBool.valueOf(in.read() == 1)); + boolean allowed = in.read() == 1; + put(requesterAllowed, DSBool.valueOf(allowed)); + if (allowed) { + session.setRequesterAllowed(); + } String pathOnBroker = reader.readString(in); put(brokerPath, DSString.valueOf(pathOnBroker)); byte[] tmp = new byte[32]; diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Message.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Message.java new file mode 100644 index 00000000..da6df8ed --- /dev/null +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Message.java @@ -0,0 +1,149 @@ +package com.acuity.iot.dsa.dslink.protocol.protocol_v2; + +import org.iot.dsa.node.DSBytes; + +/** + * Methods common to the reader and writer. + * + * @author Aaron Hansen + */ +class DS2Message implements MessageConstants { + + private StringBuilder debug; + + public StringBuilder getDebug() { + return debug; + } + + protected StringBuilder debugMethod(int arg, StringBuilder buf) { + if (buf == null) { + buf = new StringBuilder(); + } + switch (arg & 0xFF) { + case MSG_SUBSCRIBE_REQ: + buf.append("Sub req"); + break; + case MSG_SUBSCRIBE_RES: + buf.append("Sub res"); + break; + case MSG_LIST_REQ: + buf.append("List req"); + break; + case MSG_LIST_RES: + buf.append("List res"); + break; + case MSG_INVOKE_REQ: + buf.append("Invoke req"); + break; + case MSG_INVOKE_RES: + buf.append("Invoke res"); + break; + case MSG_SET_REQ: + buf.append("Set req"); + break; + case MSG_SET_RES: + buf.append("Set res"); + break; + case MSG_OBSERVE_REQ: + buf.append("Obs req"); + break; + case MSG_CLOSE: + buf.append("Close"); + break; + case MSG_ACK: + buf.append("Ack"); + break; + case MSG_PING: + buf.append("Ping"); + break; + case MSG_HANDSHAKE_1: + buf.append("Handshake 1"); + break; + case MSG_HANDSHAKE_2: + buf.append("Handshake 2"); + break; + case MSG_HANDSHAKE_3: + buf.append("Handshake 3"); + break; + case MSG_HANDSHAKE_4: + buf.append("Handshake 4"); + break; + default: + buf.append("?? 0x"); + DSBytes.toHex((byte) arg, buf); + } + return buf; + } + + protected StringBuilder debugHeader(int arg, StringBuilder buf) { + if (buf == null) { + buf = new StringBuilder(); + } + switch (arg) { + case HDR_STATUS: + buf.append("Status"); + break; + case HDR_SEQ_ID: + buf.append("Seq ID"); + break; + case HDR_PAGE_ID: + buf.append("Page ID"); + break; + case HDR_ALIAS_COUNT: + buf.append("Alias Ct"); + break; + case HDR_PRIORITY: + buf.append("Priority"); + break; + case HDR_NO_STREAM: + buf.append("No Stream"); + break; + case HDR_QOS: + buf.append("QOS"); + break; + case HDR_QUEUE_SIZE: + buf.append("Queue Size"); + break; + case HDR_QUEUE_DURATION: + buf.append("Queue Duration"); + break; + case HDR_REFRESHED: + buf.append("Refreshed"); + break; + case HDR_PUB_PATH: + buf.append("Pub Path"); + break; + case HDR_SKIPPABLE: + buf.append("Skippable"); + break; + case HDR_MAX_PERMISSION: + buf.append("Max Perm"); + break; + case HDR_ATTRIBUTE_FIELD: + buf.append("Attr Field"); + break; + case HDR_PERMISSION_TOKEN: + buf.append("Perm Token"); + break; + case HDR_TARGET_PATH: + buf.append("Target Path"); + break; + case HDR_SOURCE_PATH: + buf.append("Sourth Path"); + break; + default: + buf.append("?? 0x"); + DSBytes.toHex((byte) arg, buf); + } + return buf; + } + + public boolean isDebug() { + return debug != null; + } + + public void setDebug(StringBuilder debug) { + this.debug = debug; + } + +} diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageReader.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageReader.java index eb539a5c..cbb4cd56 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageReader.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageReader.java @@ -20,7 +20,7 @@ * * @author Aaron Hansen */ -public class DS2MessageReader implements MessageConstants { +public class DS2MessageReader extends DS2Message { // Fields // ------ @@ -28,7 +28,7 @@ public class DS2MessageReader implements MessageConstants { private int ackId; private int bodyLength; private CharBuffer charBuffer; - private Map headers = new HashMap(); + private Map headers = new HashMap(); private InputStream input; private int method; private DSIReader reader; @@ -81,11 +81,11 @@ private CharBuffer getCharBuffer(int size) { return charBuffer; } - public Object getHeader(Byte key) { + public Object getHeader(Integer key) { return headers.get(key); } - public Object getHeader(Byte key, Object def) { + public Object getHeader(Integer key, Object def) { Object ret = headers.get(key); if (ret == null) { ret = def; @@ -93,7 +93,7 @@ public Object getHeader(Byte key, Object def) { return ret; } - public Map getHeaders() { + public Map getHeaders() { return headers; } @@ -139,14 +139,19 @@ public DS2MessageReader init(InputStream in) { hlen -= 7; requestId = -1; ackId = -1; - if (hlen > 4) { + if (hlen >= 4) { requestId = DSBytes.readInt(in, false); hlen -= 4; - if (hlen > 4) { + if (hlen >= 4) { ackId = DSBytes.readInt(in, false); hlen -= 4; } - parseDynamicHeaders(in, hlen); + if (hlen > 0) { + parseDynamicHeaders(in, hlen, headers); + } + } + if (isDebug()) { + debugSummary(); } } catch (IOException x) { DSException.throwRuntime(x); @@ -154,6 +159,14 @@ public DS2MessageReader init(InputStream in) { return this; } + public boolean isAck() { + return method == MSG_ACK; + } + + public boolean isPing() { + return method == MSG_PING; + } + public boolean isRequest() { switch (method) { case MSG_CLOSE: @@ -170,33 +183,34 @@ public boolean isResponse() { return (method & 0x80) != 0; } - private void parseDynamicHeaders(InputStream in, int len) throws IOException { - byte code; + void parseDynamicHeaders(InputStream in, int len, Map headers) + throws IOException { + int code; Object val; while (len > 0) { - code = (byte) in.read(); + code = in.read() & 0xFF; val = null; len--; switch (code) { - case 0: - case 8: - case 12: - case 32: + case 0x00: + case 0x08: + case 0x12: + case 0x32: val = in.read(); len--; break; - case 1: - case 2: - case 14: - case 15: + case 0x01: + case 0x02: + case 0x14: + case 0x15: val = DSBytes.readInt(in, false); len -= 4; break; - case 21: - case 41: - case 60: - case 80: - case 81: + case 0x21: + case 0x41: + case 0x60: + case 0x80: + case 0x81: short slen = DSBytes.readShort(in, false); len -= 2; len -= slen; @@ -209,6 +223,28 @@ private void parseDynamicHeaders(InputStream in, int len) throws IOException { } } + public void debugSummary() { + StringBuilder buf = getDebug(); + buf.append("RECV "); + debugMethod(getMethod(), buf); + if (requestId >= 0) { + buf.append(", ").append("Rid ").append(requestId); + } + if (ackId >= 0) { + buf.append(", ").append("Ack ").append(ackId); + } + Object target = headers.get(HDR_TARGET_PATH); + if (target != null) { + buf.append(", ").append(target); + } + for (Map.Entry e : headers.entrySet()) { + buf.append(", "); + debugHeader(e.getKey(), buf); + buf.append("="); + buf.append(e.getValue()); + } + } + /** * Decodes a DSA 2.n string. */ diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageWriter.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageWriter.java index d2f76637..451be7c2 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageWriter.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageWriter.java @@ -17,15 +17,17 @@ * * @author Aaron Hansen */ -public class DS2MessageWriter implements MessageConstants, MessageWriter { +public class DS2MessageWriter extends DS2Message implements MessageWriter { // Fields // ------ + private int ackId = -1; private DSByteBuffer body; private CharBuffer charBuffer; private DSByteBuffer header; - private byte method; + private int method; + private int requestId = -1; private ByteBuffer strBuffer; private CharsetEncoder utf8encoder; private MsgpackWriter writer; @@ -55,7 +57,7 @@ public DS2MessageWriter addHeader(byte key) { /** * Encodes the key value pair into the header buffer. */ - public DS2MessageWriter addHeader(byte key, byte value) { + public DS2MessageWriter addHeader(byte key, Byte value) { header.put(key); header.put(value); return this; @@ -79,12 +81,28 @@ public DS2MessageWriter addHeader(byte key, String value) { return this; } - private void encodeHeaderLengths() { + private void debugSummary() { + StringBuilder debug = getDebug(); + StringBuilder buf = debug; + boolean insert = buf.length() > 0; + if (insert) { + buf = new StringBuilder(); + } + buf.append("SEND "); + debugMethod(method, buf); + buf.append(", Rid ").append(requestId < 0 ? 0 : requestId); + buf.append(", Ack ").append(ackId < 0 ? 0 : ackId); + if (insert) { + debug.insert(0, buf); + } + } + + private void finishHeader() { int hlen = header.length(); int blen = body.length(); header.replaceInt(0, hlen + blen, false); header.replaceShort(4, (short) hlen, false); - header.replace(6, method); + header.replace(6, (byte) method); } public DSByteBuffer getBody() { @@ -157,23 +175,29 @@ public MsgpackWriter getWriter() { * Initialize a new message. * * @param requestId The request ID or -1 to omit. - * @param ackId -1 to omit, but can only be -1 when the requestId is also -1. + * @param ackId -1 to omit. */ public DS2MessageWriter init(int requestId, int ackId) { + this.requestId = requestId; + this.ackId = ackId; body.clear(); writer.reset(); header.clear(); header.skip(7); - if (requestId >= 0) { - header.putInt(requestId); - if (ackId >= 0) { - header.putInt(requestId); + if ((requestId >= 0) || (ackId >= 0)) { + if (requestId < 0) { + requestId = 0; + } + header.putInt(requestId, false); + if (ackId < 0) { + ackId = 0; } + header.putInt(ackId, false); } return this; } - public DS2MessageWriter setMethod(byte method) { + public DS2MessageWriter setMethod(int method) { this.method = method; return this; } @@ -183,7 +207,7 @@ public DS2MessageWriter setMethod(byte method) { */ public byte[] toByteArray() { ByteArrayOutputStream out = new ByteArrayOutputStream(header.length() + body.length()); - encodeHeaderLengths(); + finishHeader(); header.sendTo(out); body.sendTo(out); return out.toByteArray(); @@ -193,12 +217,27 @@ public byte[] toByteArray() { * Writes the message to the transport. */ public DS2MessageWriter write(DSBinaryTransport out) { - encodeHeaderLengths(); + finishHeader(); + if (isDebug()) { + debugSummary(); + } header.sendTo(out, false); body.sendTo(out, true); return this; } + /** + * DSA 2.n encodes a string into the body. + */ + public void writeString(CharSequence str) { + CharBuffer chars = getCharBuffer(str); + ByteBuffer strBuffer = getStringBuffer( + chars.position() * (int) utf8encoder.maxBytesPerChar()); + utf8encoder.encode(chars, strBuffer, false); + body.putShort((short) strBuffer.position(), false); + body.put(strBuffer); + } + /** * DSA 2.n encodes a string into the the given buffer. */ @@ -211,76 +250,4 @@ public void writeString(CharSequence str, DSByteBuffer buf) { buf.put(strBuffer); } - /** - * Writes a little endian integer to the buffer. - public void writeIntLE(int v, ByteBuffer buf) { - buf.put((byte) ((v >>> 0) & 0xFF)); - buf.put((byte) ((v >>> 8) & 0xFF)); - buf.put((byte) ((v >>> 16) & 0xFF)); - buf.put((byte) ((v >>> 32) & 0xFF)); - } - */ - - /** - * Writes a little endian integer to the buffer. - public void writeIntLE(int v, ByteBuffer buf, int idx) { - buf.put(idx, (byte) ((v >>> 0) & 0xFF)); - buf.put(++idx, (byte) ((v >>> 8) & 0xFF)); - buf.put(++idx, (byte) ((v >>> 16) & 0xFF)); - buf.put(++idx, (byte) ((v >>> 32) & 0xFF)); - } - */ - - /** - * Writes a little endian short to the buffer. - public void writeShortLE(short v, ByteBuffer buf) { - buf.put((byte) ((v >>> 0) & 0xFF)); - buf.put((byte) ((v >>> 8) & 0xFF)); - } - */ - - /** - * Writes a little endian short to the buffer. - public void writeShortLE(short v, ByteBuffer buf, int idx) { - buf.put(idx, (byte) ((v >>> 0) & 0xFF)); - buf.put(++idx, (byte) ((v >>> 8) & 0xFF)); - } - */ - - /** - * Writes an unsigned 16 bit little endian integer to the buffer. - public void writeU16LE(int v, ByteBuffer buf) { - buf.put((byte) ((v >>> 0) & 0xFF)); - buf.put((byte) ((v >>> 8) & 0xFF)); - } - */ - - /** - * Writes an unsigned 16 bit little endian integer to the buffer. - public void writeU16LE(int v, ByteBuffer buf, int idx) { - buf.put(idx, (byte) ((v >>> 0) & 0xFF)); - buf.put(++idx, (byte) ((v >>> 8) & 0xFF)); - } - */ - - /** - * Writes an unsigned 32 bit little endian integer to the buffer. - public void writeU32LE(long v, ByteBuffer buf) { - buf.put((byte) ((v >>> 0) & 0xFF)); - buf.put((byte) ((v >>> 8) & 0xFF)); - buf.put((byte) ((v >>> 16) & 0xFF)); - buf.put((byte) ((v >>> 32) & 0xFF)); - } - */ - - /** - * Writes an unsigned 32 bit little endian integer to the buffer. - public void writeU32LE(long v, ByteBuffer buf, int idx) { - buf.put(idx, (byte) ((v >>> 0) & 0xFF)); - buf.put(++idx, (byte) ((v >>> 8) & 0xFF)); - buf.put(++idx, (byte) ((v >>> 16) & 0xFF)); - buf.put(++idx, (byte) ((v >>> 32) & 0xFF)); - } - */ - } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java index 199d3cc1..791418fd 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java @@ -6,8 +6,8 @@ import com.acuity.iot.dsa.dslink.protocol.protocol_v2.responder.DS2Responder; import com.acuity.iot.dsa.dslink.transport.DSBinaryTransport; import com.acuity.iot.dsa.dslink.transport.DSTransport; -import java.io.IOException; import org.iot.dsa.dslink.DSIRequester; +import org.iot.dsa.node.DSBytes; import org.iot.dsa.node.DSInfo; import org.iot.dsa.node.DSInt; @@ -24,13 +24,18 @@ public class DS2Session extends DSSession implements MessageConstants { static final int END_MSG_THRESHOLD = 48000; static final String LAST_ACK_RECV = "Last Ack Recv"; - static final int MAX_MSG_IVL = 45000; /////////////////////////////////////////////////////////////////////////// // Fields /////////////////////////////////////////////////////////////////////////// + private boolean debugRecv = false; + private StringBuilder debugRecvTranport = new StringBuilder(); + private StringBuilder debugRecvMessage = new StringBuilder(); + private boolean debugSend = false; + private StringBuilder debugSendTranport = new StringBuilder(); + private StringBuilder debugSendMessage = new StringBuilder(); private DSInfo lastAckRecv = getInfo(LAST_ACK_RECV); private long lastMessageSent; private DS2MessageReader messageReader; @@ -60,32 +65,86 @@ protected void declareDefaults() { } @Override - protected void doRecvMessage() throws IOException { - if (messageReader == null) { - messageReader = new DS2MessageReader(); + protected void doRecvMessage() { + DSBinaryTransport transport = getTransport(); + DS2MessageReader reader = getMessageReader(); + boolean debug = debug(); + if (debug != debugRecv) { + if (debug) { + debugRecvMessage = new StringBuilder(); + debugRecvTranport = new StringBuilder(); + reader.setDebug(debugRecvMessage); + transport.setDebugIn(debugRecvTranport); + } else { + debugRecvMessage = null; + debugRecvTranport = null; + reader.setDebug(null); + transport.setDebugIn(null); + } + debugRecv = debug; + } + if (debug) { + debugRecvMessage.setLength(0); + debugRecvTranport.setLength(0); + debugRecvTranport.append("Bytes read\n"); } - messageReader.init(getTransport().getInput()); - int ack = messageReader.getAckId(); + transport.beginRecvMessage(); + reader.init(transport.getInput()); + int ack = reader.getAckId(); if (ack > 0) { put(lastAckRecv, DSInt.valueOf(ack)); } - if (messageReader.isRequest()) { - responder.handleRequest(messageReader); - } else if (messageReader.isResponse()) { - ;//requester.processResponse(messageReader); + if (reader.isRequest()) { + responder.handleRequest(reader); + setNextAck(reader.getRequestId()); + } else if (reader.isAck()) { + setNextAck(DSBytes.readInt(reader.getBody(), false)); + } else if (reader.isPing()) { + } else if (reader.isResponse()) { + ;//requester.processResponse(reader); + } + if (debug) { + debug(debugRecvMessage); + debug(debugRecvTranport); + debugRecvMessage.setLength(0); + debugRecvTranport.setLength(0); } } @Override protected void doSendMessage() { DSTransport transport = getTransport(); - requestsNext = !requestsNext; - transport.beginMessage(); - if (hasMessagesToSend()) { - send(requestsNext); + boolean debug = debug(); + if (debug != debugSend) { + if (debug) { + debugSendMessage = new StringBuilder(); + debugSendTranport = new StringBuilder(); + getMessageWriter().setDebug(debugSendMessage); + transport.setDebugOut(debugSendTranport); + } else { + debugSendMessage = null; + debugSendTranport = null; + getMessageWriter().setDebug(null); + transport.setDebugOut(null); + } + debugSend = debug; + } + if (debug) { + debugSendMessage.setLength(0); + debugSendTranport.setLength(0); + debugSendTranport.append("Bytes sent\n"); + } + if (this.hasSomethingToSend()) { + transport.beginSendMessage(); + boolean sent = send(requestsNext != requestsNext); //alternate reqs and resps + if (sent && debug) { + debug(debugSendMessage); + debug(debugSendTranport); + debugSendMessage.setLength(0); + debugSendTranport.setLength(0); + } + transport.endSendMessage(); } - transport.endMessage(); - lastMessageSent = System.currentTimeMillis(); } @Override @@ -93,6 +152,13 @@ public DS2LinkConnection getConnection() { return (DS2LinkConnection) super.getConnection(); } + private DS2MessageReader getMessageReader() { + if (messageReader == null) { + messageReader = new DS2MessageReader(); + } + return messageReader; + } + private DS2MessageWriter getMessageWriter() { if (messageWriter == null) { messageWriter = new DS2MessageWriter(); @@ -158,7 +224,7 @@ public void onDisconnect() { * @param requests Determines which queue to use; True for outgoing requests, false for * responses. */ - private void send(boolean requests) { + private boolean send(boolean requests) { boolean hasSomething = false; if (requests) { hasSomething = hasOutgoingRequests(); @@ -170,17 +236,22 @@ private void send(boolean requests) { msg = requests ? dequeueOutgoingRequest() : dequeueOutgoingResponse(); } else if (hasPingToSend()) { msg = new PingMessage(this); + } else if (hasAckToSend()) { + msg = new AckMessage(this); } if (msg != null) { + lastMessageSent = System.currentTimeMillis(); msg.write(getMessageWriter()); + return true; } + return false; } /** * Returns true if the current message size has crossed a message size threshold. */ public boolean shouldEndMessage() { - //TODO + //todo //return (messageWriter.length() + getTransport().messageSize()) > END_MSG_THRESHOLD; return getTransport().messageSize() > END_MSG_THRESHOLD; } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/MessageConstants.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/MessageConstants.java index e418193b..c3893e35 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/MessageConstants.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/MessageConstants.java @@ -12,37 +12,40 @@ public interface MessageConstants { int MAX_HEADER = 1024 * 48; int MAX_BODY = 16320; - Byte HDR_STATUS = 0x0; - Byte HDR_SEQ_ID = 0x01; - Byte HDR_PAGE_ID = 0x02; - Byte HDR_ALIAS_COUNT = 0x08; - Byte HDR_PRIORITY = 0x10; - Byte HDR_NO_STREAM = 0x11; - Byte HDR_QOS = 0x12; - Byte HDR_QUEUE_SIZE = 0x14; - Byte HDR_QUEUE_DURATION = 0x15; - Byte HDR_REFRESHED = 0x20; - Byte HDR_PUB_PATH = 0x21; - Byte HDR_SKIPPABLE = 0x30; - Byte HDR_MAX_PERMISSION = 0x32; - Byte HDR_ATTRIBUTE_FIELD = 0x41; - Byte HDR_PERMISSION_TOKEN = 0x60; - Byte HDR_TARGET_PATH = (byte) (0x80 & 0xFF); - Byte HDR_SOURCE_PATH = (byte) (0x81 & 0xFF); + int HDR_STATUS = 0x0; + int HDR_SEQ_ID = 0x01; + int HDR_PAGE_ID = 0x02; + int HDR_ALIAS_COUNT = 0x08; + int HDR_PRIORITY = 0x10; + int HDR_NO_STREAM = 0x11; + int HDR_QOS = 0x12; + int HDR_QUEUE_SIZE = 0x14; + int HDR_QUEUE_DURATION = 0x15; + int HDR_REFRESHED = 0x20; + int HDR_PUB_PATH = 0x21; + int HDR_SKIPPABLE = 0x30; + int HDR_MAX_PERMISSION = 0x32; + int HDR_ATTRIBUTE_FIELD = 0x41; + int HDR_PERMISSION_TOKEN = 0x60; + int HDR_TARGET_PATH = (0x80 & 0xFF); + int HDR_SOURCE_PATH = (0x81 & 0xFF); int MSG_SUBSCRIBE_REQ = 0x01; - int MSG_SUBSCRIBE_RES = 0x81; + int MSG_SUBSCRIBE_RES = (0x81 & 0xFF); int MSG_LIST_REQ = 0x02; - int MSG_LIST_RES = 0x82; + int MSG_LIST_RES = (0x82 & 0xFF); int MSG_INVOKE_REQ = 0x03; - int MSG_INVOKE_RES = 0x83; + int MSG_INVOKE_RES = (0x83 & 0xFF); int MSG_SET_REQ = 0x04; - int MSG_SET_RES = 0x84; + int MSG_SET_RES = (0x84 & 0xFF); int MSG_OBSERVE_REQ = 0x0A; - int MSG_OBSERVE_RES = 0x8A; int MSG_CLOSE = 0x0F; - int MSG_ACK = 0xF8; - int MSG_PING = 0xF9; + int MSG_ACK = (0xF8 & 0xFF); + int MSG_PING = (0xF9 & 0xFF); + int MSG_HANDSHAKE_1 = (0xF0 & 0xFF); + int MSG_HANDSHAKE_2 = (0xF1 & 0xFF); + int MSG_HANDSHAKE_3 = (0xF2 & 0xFF); + int MSG_HANDSHAKE_4 = (0xF3 & 0xFF); Byte STS_OK = 0; Byte STS_INITIALIZING = 0x01; diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/PingMessage.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/PingMessage.java index 3980a35f..766039ea 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/PingMessage.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/PingMessage.java @@ -18,7 +18,7 @@ public PingMessage(DS2Session session) { public void write(MessageWriter writer) { DS2MessageWriter out = (DS2MessageWriter) writer; out.init(-1, session.getNextAck()); - out.setMethod((byte) MSG_PING); + out.setMethod(MSG_PING); out.write(session.getTransport()); } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundList.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundList.java index 8fc7d6a9..a28f6e97 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundList.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundList.java @@ -1,10 +1,13 @@ package com.acuity.iot.dsa.dslink.protocol.protocol_v2.responder; +import com.acuity.iot.dsa.dslink.io.DSByteBuffer; import com.acuity.iot.dsa.dslink.protocol.message.MessageWriter; import com.acuity.iot.dsa.dslink.protocol.protocol_v2.DS2MessageWriter; import com.acuity.iot.dsa.dslink.protocol.protocol_v2.MessageConstants; import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundList; import com.acuity.iot.dsa.dslink.transport.DSBinaryTransport; +import org.iot.dsa.io.msgpack.MsgpackWriter; +import org.iot.dsa.node.DSElement; /** * List implementation for a responder. @@ -13,6 +16,88 @@ */ class DS2InboundList extends DSInboundList implements MessageConstants { + @Override + protected void beginMessage(MessageWriter writer) { + } + + @Override + protected void beginUpdates(MessageWriter writer) { + } + + @Override + protected void encode(String key, DSElement value, MessageWriter writer) { + DS2MessageWriter out = (DS2MessageWriter) writer; + if (out.isDebug()) { + out.getDebug().append('\n').append(key).append(" : ").append(value); + } + DSByteBuffer buf = out.getBody(); + MsgpackWriter mp = out.getWriter(); + buf.skip(2); + int start = buf.length(); + mp.writeUTF8(key); + int end = buf.length(); + buf.replaceShort(start - 2, (short) (end - start), false); + mp.reset(); + buf.skip(2); + start = buf.length(); + mp.value(value); + end = buf.length(); + buf.replaceShort(start - 2, (short) (end - start), false); + } + + @Override + protected void encode(String key, String value, MessageWriter writer) { + DS2MessageWriter out = (DS2MessageWriter) writer; + if (out.isDebug()) { + out.getDebug().append('\n').append(key).append(" : ").append(value); + } + DSByteBuffer buf = out.getBody(); + MsgpackWriter mp = out.getWriter(); + buf.skip(2); + int start = buf.length(); + mp.writeUTF8(key); + int end = buf.length(); + buf.replaceShort(start - 2, (short) (end - start), false); + out.getWriter().reset(); + buf.skip(2); + start = buf.length(); + out.getWriter().value(value); + end = buf.length(); + buf.replaceShort(start - 2, (short) (end - start), false); + } + + @Override + protected String encodeName(String name) { + return name; + } + + @Override + protected void encodeUpdate(Update update, MessageWriter writer) { + if (update.added) { + encodeChild(update.child, writer); + } else { + DS2MessageWriter out = (DS2MessageWriter) writer; + out.writeString(encodeName(update.child.getName())); + out.getBody().put((byte) 0, (byte) 0); + } + } + + @Override + protected void endMessage(MessageWriter writer, Boolean streamOpen) { + DS2MessageWriter out = (DS2MessageWriter) writer; + if (streamOpen == null) { + out.addHeader((byte) HDR_STATUS, MessageConstants.STS_INITIALIZING); + } else if (streamOpen) { + out.addHeader((byte) HDR_STATUS, MessageConstants.STS_OK); + } else { + out.addHeader((byte) HDR_STATUS, MessageConstants.STS_CLOSED); + } + } + + @Override + protected void endUpdates(MessageWriter writer) { + } + @Override public void write(MessageWriter writer) { //if has remaining multipart, send that diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java index 9adc6b2a..0373913d 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java @@ -45,6 +45,11 @@ public DSBinaryTransport getTransport() { return (DSBinaryTransport) getConnection().getTransport(); } + @Override + public boolean isV1() { + return false; + } + /** * Process an individual request. */ @@ -84,7 +89,7 @@ public void onDisconnect() { try { entry.getValue().onClose(entry.getKey()); } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); } } getRequests().clear(); diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/ErrorMessage.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/ErrorMessage.java index c79d18ba..4b18f0b7 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/ErrorMessage.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/ErrorMessage.java @@ -54,15 +54,18 @@ public void write(MessageWriter writer) { } if (reason instanceof DSRequestException) { if (reason instanceof DSInvalidPathException) { - out.addHeader(MessageConstants.HDR_STATUS, MessageConstants.STS_NOT_AVAILABLE); + out.addHeader((byte) MessageConstants.HDR_STATUS, + MessageConstants.STS_NOT_AVAILABLE); } else if (reason instanceof DSPermissionException) { - out.addHeader(MessageConstants.HDR_STATUS, MessageConstants.STS_PERMISSION_DENIED); + out.addHeader((byte) MessageConstants.HDR_STATUS, + MessageConstants.STS_PERMISSION_DENIED); } else { - out.addHeader(MessageConstants.HDR_STATUS, MessageConstants.STS_INVALID_MESSAGE); + out.addHeader((byte) MessageConstants.HDR_STATUS, + MessageConstants.STS_INVALID_MESSAGE); } } else { //todo need server error - out.addHeader(MessageConstants.HDR_STATUS, MessageConstants.STS_INVALID_MESSAGE); + out.addHeader((byte) MessageConstants.HDR_STATUS, MessageConstants.STS_INVALID_MESSAGE); } out.write((DSBinaryTransport) req.getResponder().getTransport()); } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundInvoke.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundInvoke.java index b338268d..0a4961a4 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundInvoke.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundInvoke.java @@ -122,7 +122,7 @@ public void run() { try { result.onClose(); } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); } } }); @@ -204,7 +204,7 @@ public void onClose(Integer requestId) { return; } state = STATE_CLOSED; - fine(finer() ? getPath() + " invoke closed" : null); + fine(debug() ? getPath() + " invoke closed" : null); synchronized (this) { updateHead = updateTail = null; } @@ -239,7 +239,7 @@ public void run() { DSAction action = info.getAction(); result = action.invoke(info, this); } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); close(x); return; } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java index 5d1f9610..e6636374 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java @@ -10,8 +10,8 @@ import org.iot.dsa.dslink.responder.ApiObject; import org.iot.dsa.dslink.responder.InboundListRequest; import org.iot.dsa.dslink.responder.OutboundListResponse; -import org.iot.dsa.io.DSIWriter; import org.iot.dsa.node.DSElement; +import org.iot.dsa.node.DSIEnum; import org.iot.dsa.node.DSIValue; import org.iot.dsa.node.DSInfo; import org.iot.dsa.node.DSList; @@ -20,6 +20,7 @@ import org.iot.dsa.node.DSMetadata; import org.iot.dsa.node.DSNode; import org.iot.dsa.node.DSPath; +import org.iot.dsa.node.DSValueType; import org.iot.dsa.node.action.ActionSpec; import org.iot.dsa.node.action.DSAction; import org.iot.dsa.node.event.DSIEvent; @@ -67,6 +68,20 @@ public class DSInboundList extends DSInboundRequest // Methods in alphabetical order /////////////////////////////////////////////////////////////////////////// + /** + * Override point for v2. + */ + protected void beginMessage(MessageWriter writer) { + writer.getWriter().beginMap().key("rid").value(getRequestId()); + } + + /** + * Override point for v2. + */ + protected void beginUpdates(MessageWriter writer) { + writer.getWriter().key("updates").beginList(); + } + @Override public void childAdded(ApiObject child) { if (!isClosed()) { @@ -133,77 +148,110 @@ public void run() { try { response.onClose(); } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); } } }); } - private void encodeChild(ApiObject child, DSIWriter out) { - out.beginList(); - String name = child.getName(); - String displayName = null; - if (DSPath.encodeName(name, cacheBuf)) { - displayName = name; - out.value(cacheBuf.toString()); - } else { - out.value(name); + /** + * Override point for v2. + */ + protected void encode(String key, DSElement value, MessageWriter writer) { + writer.getWriter().beginList().value(key).value(value).endList(); + } + + /** + * Override point for v2. + */ + protected void encode(String key, String value, MessageWriter writer) { + writer.getWriter().beginList().value(key).value(value).endList(); + } + + /** + * Override point for v2. + */ + protected void endMessage(MessageWriter writer, Boolean streamOpen) { + if (streamOpen != null) { + writer.getWriter().key("stream").value(streamOpen ? "open" : "closed"); } + writer.getWriter().endMap(); + } + + /** + * Override point for v2. + */ + protected void endUpdates(MessageWriter writer) { + writer.getWriter().endList(); + } + + /** + * Override point for v2. + */ + protected String encodeName(String name) { cacheBuf.setLength(0); - out.beginMap(); + if (DSPath.encodeNameV1(name, cacheBuf)) { + return cacheBuf.toString(); + } + return name; + } + + protected void encodeChild(ApiObject child, MessageWriter writer) { + String name = child.getName(); + String safe = encodeName(name); + DSMap map = new DSMap(); child.getMetadata(cacheMap.clear()); DSElement e = cacheMap.remove(DSMetadata.DISPLAY_NAME); if (e != null) { - out.key("$name").value(e); - } else if (displayName != null) { - out.key("$name").value(displayName); + map.put("$name", e); + } else if (!safe.equals(name)) { + map.put("$name", name); } e = cacheMap.remove("$is"); if (e != null) { - out.key("$is").value(e); + map.put("$is", e); } else { - out.key("$is").value("node"); + map.put("$is", "node"); } if (child.isAction()) { ActionSpec action = child.getAction(); e = cacheMap.remove("$invokable"); if (e != null) { - out.key("$invokable").value(e); + map.put("$invokable", e); } else { - out.key("$invokable").value(action.getPermission().toString()); + map.put("$invokable", action.getPermission().toString()); } } else if (child.isValue()) { - out.key("$type"); e = cacheMap.remove("$type"); if (e != null) { - out.value(e); + map.put("$type", e); } else { - encodeType(child.getValue(), cacheMeta, out); + map.put("$type", encodeType(child.getValue(), cacheMeta)); } if (!child.isReadOnly()) { e = cacheMap.remove("$writable"); if (e != null) { - out.key("$writable").value(e); + map.put("$writable", e); } else { - out.key("$writable").value(child.isAdmin() ? "config" : "write"); + map.put("$writable", child.isAdmin() ? "config" : "write"); } } } else if (child.isAdmin()) { e = cacheMap.remove("$permission"); if (e != null) { - out.key("$permission").value(e); + map.put("$permission", e); } else { - out.key("$permission").value("config"); + map.put("$permission", "config"); } } - out.endMap().endList(); + encode(safe, map, writer); cacheMap.clear(); } /** * Encode all the meta data about the root target of a list request. */ - private void encodeTarget(ApiObject object, DSIWriter out) { + private void encodeTarget(ApiObject object, MessageWriter writer) { if (object instanceof DSInfo) { DSMetadata.getMetadata((DSInfo) object, cacheMap.clear()); } else { @@ -211,42 +259,37 @@ private void encodeTarget(ApiObject object, DSIWriter out) { } DSElement e = cacheMap.remove("$is"); if (e == null) { - out.beginList().value("$is").value("node").endList(); + encode("$is", "node", writer); } else { - out.beginList().value("$is").value(e).endList(); + encode("$is", e, writer); } e = cacheMap.get("$name"); if (e == null) { - String safeName = object.getName(); - if (DSPath.encodeName(safeName, cacheBuf)) { - safeName = cacheBuf.toString(); - } - cacheBuf.setLength(0); - out.beginList().value("$name").value(safeName).endList(); + encode("$name", encodeName(object.getName()), writer); } else { - out.beginList().value("$name").value(e).endList(); + encode("$name", e, writer); } if (object.isAction()) { - encodeTargetAction(object, out); + encodeTargetAction(object, writer); } else if (object.isValue()) { - encodeTargetValue(object, out); + encodeTargetValue(object, writer); } else if (object.isAdmin()) { e = cacheMap.remove("$permission"); if (e == null) { - out.beginList().value("$permission").value("config").endList(); + encode("$permission", "config", writer); } else { - out.beginList().value("$permission").value(e).endList(); + encode("$permission", e, writer); } } - encodeTargetMetadata(cacheMap, out); + encodeTargetMetadata(cacheMap, writer); cacheMap.clear(); } /** * Called by encodeTarget for actions. */ - private void encodeTargetAction(ApiObject object, DSIWriter out) { + private void encodeTargetAction(ApiObject object, MessageWriter writer) { DSInfo info = null; if (object instanceof DSInfo) { info = (DSInfo) object; @@ -257,16 +300,14 @@ private void encodeTargetAction(ApiObject object, DSIWriter out) { dsAction = (DSAction) action; } DSElement e = cacheMap.remove("$invokable"); - out.beginList().value("$invokable"); if (e == null) { - out.value(action.getPermission().toString()).endList(); + encode("$invokable", action.getPermission().toString(), writer); } else { - out.value(e).endList(); + encode("$invokable", e, writer); } e = cacheMap.remove("params"); - out.beginList().value("$params"); if (e == null) { - out.beginList(); + DSList list = new DSList(); Iterator params = action.getParameters(); if (params != null) { DSMap param; @@ -275,124 +316,123 @@ private void encodeTargetAction(ApiObject object, DSIWriter out) { if (dsAction != null) { dsAction.prepareParameter(info, param); } - out.value(fixType(param)); + if (param.hasParent()) { + param = param.copy(); + } + list.add(param); } } - out.endList(); + encode("$params", list, writer); } else { - out.value(e); + encode("$params", e, writer); } - out.endList(); if (action.getResultType().isValues()) { e = cacheMap.remove("$columns"); - out.beginList().value("$columns"); if (e == null) { - out.beginList(); - Iterator params = action.getValueResults(); - if (params != null) { + DSList list = new DSList(); + Iterator cols = action.getValueResults(); + if (cols != null) { DSMap param; - while (params.hasNext()) { - param = params.next(); + while (cols.hasNext()) { + param = cols.next(); if (dsAction != null) { dsAction.prepareParameter(info, param); } - out.value(fixType(param)); + if (param.hasParent()) { + param = param.copy(); + } + list.add(param); } } - out.endList(); + encode("$columns", list, writer); } else { - out.value(e); + encode("$columns", e, writer); } - out.endList(); } e = cacheMap.remove("$result"); if (e != null) { - out.beginList().value("$result").value(e).endList(); + encode("$result", e, writer); } else if (!action.getResultType().isVoid()) { - out.beginList().value("$result").value(action.getResultType().toString()).endList(); + encode("$result", action.getResultType().toString(), writer); } } /** * Called by encodeTarget, encodes meta-data as configs. */ - private void encodeTargetMetadata(DSMap metadata, DSIWriter out) { - if (cacheMap.isEmpty()) { - return; - } + private void encodeTargetMetadata(DSMap metadata, MessageWriter writer) { Entry entry; String name; - for (int i = 0, len = cacheMap.size(); i < len; i++) { - entry = cacheMap.getEntry(i); - out.beginList(); + for (int i = 0, len = metadata.size(); i < len; i++) { + entry = metadata.getEntry(i); name = entry.getKey(); switch (name.charAt(0)) { case '$': case '@': - out.value(name); + break; default: - cacheBuf.append("@"); //TODO ? - DSPath.encodeName(name, cacheBuf); - out.value(cacheBuf.toString()); cacheBuf.setLength(0); + cacheBuf.append("@"); //TODO ? + cacheBuf.append(encodeName(name)); + name = cacheBuf.toString(); } - out.value(entry.getValue()); - out.endList(); + encode(name, entry.getValue(), writer); } } /** * Called by encodeTarget for values. */ - private void encodeTargetValue(ApiObject object, DSIWriter out) { + private void encodeTargetValue(ApiObject object, MessageWriter writer) { DSElement e = cacheMap.remove("$type"); - out.beginList(); - out.value("$type"); if (e != null) { - out.value(e); + encode("$type", e, writer); } else { - encodeType(object.getValue(), cacheMeta, out); + encode("$type", encodeType(object.getValue(), cacheMeta), writer); } - out.endList(); e = cacheMap.remove("$writable"); if (e != null) { - out.beginList() - .value("$writable") - .value(e) - .endList(); + encode("$writable", e, writer); } else if (!object.isReadOnly()) { - out.beginList() - .value("$writable") - .value(object.isAdmin() ? "config" : "write") - .endList(); + encode("$writable", object.isAdmin() ? "config" : "write", writer); } } - private void encodeType(DSIValue value, DSMetadata meta, DSIWriter out) { + private DSElement encodeType(DSIValue value, DSMetadata meta) { String type = meta.getType(); - if ((type == null) && (value != null)) { - meta.setType(value); + if (getResponder().isV1()) { + if ((type == null) && (value != null)) { + meta.setType(value); + } + } else { + if ((type == null) && (value != null)) { + if (value instanceof DSIEnum) { + meta.getMap().put(DSMetadata.TYPE, DSValueType.STRING.toString()); + } else { + meta.setType(value); + } + } } fixType(meta.getMap()); DSElement e = cacheMap.remove(DSMetadata.TYPE); if (e == null) { throw new IllegalArgumentException("Missing type"); } - out.value(e); + return e; } - private void encodeUpdate(Update update, DSIWriter out) { - if (!isOpen()) { - return; - } + /** + * Override point for v2. + */ + protected void encodeUpdate(Update update, MessageWriter writer) { if (update.added) { - encodeChild(update.child, out); + encodeChild(update.child, writer); } else { - out.beginMap(); - out.key("name").value(DSPath.encodeName(update.child.getName())); - out.key("change").value("remove"); - out.endMap(); + writer.getWriter().beginMap() + .key("name").value(encodeName(update.child.getName())) + .key("change").value("remove") + .endMap(); } } @@ -445,7 +485,11 @@ private DSMap fixType(DSMap arg) { cacheBuf.append(','); cacheBuf.append(range.get(1).toString()); cacheBuf.append(']'); - arg.put(DSMetadata.TYPE, cacheBuf.toString()); + if (getResponder().isV1()) { + arg.put(DSMetadata.TYPE, cacheBuf.toString()); + } else { + arg.put(DSMetadata.EDITOR, cacheBuf.toString()); + } } } else if ("enum".equals(type)) { DSList range = (DSList) arg.remove(DSMetadata.ENUM_RANGE); @@ -462,7 +506,11 @@ private DSMap fixType(DSMap arg) { cacheBuf.append(range.get(i).toString()); } cacheBuf.append(']'); - arg.put(DSMetadata.TYPE, cacheBuf.toString()); + if (getResponder().isV1()) { + arg.put(DSMetadata.TYPE, cacheBuf.toString()); + } else { + arg.put(DSMetadata.EDITOR, cacheBuf.toString()); + } } return arg; } @@ -516,6 +564,19 @@ public void onUnsubscribed(DSTopic topic, DSNode node, DSInfo child) { close(); } + @Override + public void onClose(Integer requestId) { + if (isClosed()) { + return; + } + state = STATE_CLOSED; + fine(debug() ? getPath() + " list closed" : null); + synchronized (this) { + updateHead = updateTail = null; + } + doClose(); + } + @Override public void run() { try { @@ -533,7 +594,7 @@ public void run() { response = this; } } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); close(x); return; } @@ -544,22 +605,8 @@ public void run() { } } - @Override - public void onClose(Integer requestId) { - if (isClosed()) { - return; - } - state = STATE_CLOSED; - fine(finer() ? getPath() + " list closed" : null); - synchronized (this) { - updateHead = updateTail = null; - } - doClose(); - } - @Override public void write(MessageWriter writer) { - DSIWriter out = writer.getWriter(); enqueued = false; if (isClosed()) { return; @@ -570,47 +617,44 @@ public void write(MessageWriter writer) { return; } int last = state; - out.beginMap(); - out.key("rid").value(getRequestId()); + beginMessage(writer); switch (state) { case STATE_INIT: - out.key("updates").beginList(); + beginUpdates(writer); writeInit(writer); break; case STATE_CHILDREN: - out.key("updates").beginList(); + beginUpdates(writer); writeChildren(writer); break; case STATE_CLOSE_PENDING: case STATE_UPDATES: - out.key("updates").beginList(); + beginUpdates(writer); writeUpdates(writer); break; - default: - ; } - out.endList(); + endUpdates(writer); if ((state != last) && (state == STATE_UPDATES)) { - out.key("stream").value("open"); + endMessage(writer, Boolean.TRUE); } else if (isClosePending() && (updateHead == null)) { if (closeReason != null) { getResponder().sendError(this, closeReason); } else { - out.key("stream").value("closed"); + endMessage(writer, Boolean.FALSE); } doClose(); + } else { + endMessage(writer, null); } - out.endMap(); } private void writeChildren(MessageWriter writer) { - DSIWriter out = writer.getWriter(); if (children != null) { ApiObject child; while (children.hasNext()) { child = children.next(); if (!child.isHidden()) { - encodeChild(child, out); + encodeChild(child, writer); } if (getResponder().shouldEndMessage()) { enqueueResponse(); @@ -623,9 +667,8 @@ private void writeChildren(MessageWriter writer) { } private void writeInit(MessageWriter writer) { - DSIWriter out = writer.getWriter(); ApiObject target = response.getTarget(); - encodeTarget(target, out); + encodeTarget(target, writer); if (target.hasChildren()) { state = STATE_CHILDREN; children = target.getChildren(); @@ -637,16 +680,18 @@ private void writeInit(MessageWriter writer) { } private void writeUpdates(MessageWriter writer) { - DSIWriter out = writer.getWriter(); DSResponder responder = getResponder(); - Update update = dequeue(); - while (update != null) { - encodeUpdate(update, out); + Update update; + while (isOpen()) { + update = dequeue(); + if (update == null) { + break; + } + encodeUpdate(update, writer); if (responder.shouldEndMessage()) { enqueueResponse(); break; } - update = dequeue(); } } @@ -654,11 +699,11 @@ private void writeUpdates(MessageWriter writer) { // Inner Classes /////////////////////////////////////////////////////////////////////////// - private static class Update { + protected static class Update { - boolean added; - ApiObject child; - Update next; + public boolean added; + public ApiObject child; + public Update next; Update(ApiObject child, boolean added) { this.child = child; diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundRequest.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundRequest.java index 827480b1..d5f46471 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundRequest.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundRequest.java @@ -27,9 +27,17 @@ public abstract class DSInboundRequest extends DSLogger implements InboundReques /////////////////////////////////////////////////////////////////////////// public DSLink getLink() { + if (link == null) { + link = responder.getLink(); + } return link; } + @Override + protected String getLogName() { + return getClass().getSimpleName(); + } + public String getPath() { return path; } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSet.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSet.java index fa59c09f..a0c87616 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSet.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSet.java @@ -64,7 +64,7 @@ public void run() { } sendClose(); } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); getResponder().sendError(this, x); } } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscription.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscription.java index afeda49d..dd10c6b5 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscription.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscription.java @@ -2,8 +2,6 @@ import com.acuity.iot.dsa.dslink.protocol.message.MessageWriter; import com.acuity.iot.dsa.dslink.protocol.message.RequestPath; -import java.util.logging.Level; -import java.util.logging.Logger; import org.iot.dsa.dslink.DSIResponder; import org.iot.dsa.dslink.responder.InboundSubscribeRequest; import org.iot.dsa.dslink.responder.SubscriptionCloseHandler; @@ -83,11 +81,6 @@ protected synchronized Update dequeue() { return ret; } - @Override - public Logger getLogger() { - return manager.getLogger(); - } - /** * Unique subscription id for this path. */ @@ -134,14 +127,14 @@ void onClose() { closeHandler.onClose(getSubscriptionId()); } } catch (Exception x) { - getLogger().log(Level.WARNING, toString(), x); + manager.warn(manager.getPath(), x); } try { if (node != null) { node.unsubscribe(DSNode.VALUE_TOPIC, child, this); } } catch (Exception x) { - getLogger().log(Level.WARNING, toString(), x); + manager.warn(manager.getPath(), x); } } @@ -174,7 +167,7 @@ public void update(long timestamp, DSIValue value, DSStatus quality) { if (!open) { return; } - finest(finest() ? "Update " + getPath() + " to " + value : null); + trace(trace() ? "Update " + getPath() + " to " + value : null); if (qos == 0) { synchronized (this) { if (updateHead == null) { diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscriptions.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscriptions.java index bebbcefb..46eddfe1 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscriptions.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscriptions.java @@ -5,7 +5,6 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.logging.Logger; import org.iot.dsa.dslink.DSLink; import org.iot.dsa.node.DSNode; @@ -27,7 +26,6 @@ public class DSInboundSubscriptions extends DSNode implements OutboundMessage { /////////////////////////////////////////////////////////////////////////// private boolean enqueued = false; - private Logger logger; private ConcurrentLinkedQueue outbound = new ConcurrentLinkedQueue(); private Map pathMap = @@ -76,15 +74,6 @@ DSLink getLink() { return responder.getConnection().getLink(); } - @Override - public Logger getLogger() { - if (logger == null) { - logger = Logger.getLogger(responder.getConnection().getLink().getLinkName() - + ".responderSubscriptions"); - } - return logger; - } - /** * This returns a DSInboundSubscription for v1, this will be overridden for v2. * @@ -100,7 +89,7 @@ protected DSInboundSubscription makeSubscription(Integer sid, String path, int q * Create or update a subscription. */ public void subscribe(Integer sid, String path, int qos) { - finest(finest() ? "Subscribing " + path : null); + trace(trace() ? "Subscribing " + path : null); DSInboundSubscription subscription = sidMap.get(sid); if (subscription == null) { subscription = makeSubscription(sid, path, qos); @@ -121,7 +110,7 @@ public void subscribe(Integer sid, String path, int qos) { public void unsubscribe(Integer sid) { DSInboundSubscription subscription = sidMap.remove(sid); if (subscription != null) { - finest(finest() ? "Unsubscribing " + subscription.getPath() : null); + trace(trace() ? "Unsubscribing " + subscription.getPath() : null); pathMap.remove(subscription.getPath()); try { subscription.onClose(); diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSResponder.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSResponder.java index 62346b9a..836bd3f5 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSResponder.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSResponder.java @@ -6,7 +6,6 @@ import com.acuity.iot.dsa.dslink.transport.DSTransport; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Logger; import org.iot.dsa.dslink.DSLink; import org.iot.dsa.dslink.DSLinkConnection; import org.iot.dsa.node.DSNode; @@ -29,7 +28,6 @@ public abstract class DSResponder extends DSNode { private DSLinkConnection connection; private ConcurrentHashMap inboundRequests = new ConcurrentHashMap(); private DSLink link; - private Logger logger; private DSSession session; private DSResponder responder; @@ -60,12 +58,8 @@ public DSLink getLink() { } @Override - public Logger getLogger() { - if (logger == null) { - logger = Logger.getLogger( - getConnection().getLink().getLinkName() + ".responder"); - } - return logger; + protected String getLogName() { + return getClass().getSimpleName(); } public Map getRequests() { @@ -80,6 +74,13 @@ public DSTransport getTransport() { return getConnection().getTransport(); } + /** + * V2 override point, this returns true. + */ + public boolean isV1() { + return true; + } + public void onConnect() { } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/DSTransport.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/DSTransport.java index 7c0fa2fe..5b4aa836 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/DSTransport.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/DSTransport.java @@ -1,6 +1,5 @@ package com.acuity.iot.dsa.dslink.transport; -import java.util.logging.Logger; import org.iot.dsa.dslink.DSLinkConnection; import org.iot.dsa.node.DSLong; import org.iot.dsa.node.DSNode; @@ -26,18 +25,28 @@ public abstract class DSTransport extends DSNode { /////////////////////////////////////////////////////////////////////////// private DSLinkConnection connection; - private Logger logger; + private StringBuilder debugIn; + private StringBuilder debugOut; /////////////////////////////////////////////////////////////////////////// // Methods /////////////////////////////////////////////////////////////////////////// + /** + * Called at the start of a new inbound message. + * + * @return This + */ + public DSTransport beginRecvMessage() { + return this; + } + /** * Called at the start of a new outbound message. * * @return This */ - public abstract DSTransport beginMessage(); + public abstract DSTransport beginSendMessage(); /** * Close the actual connection and clean up resources. Calling when already closed will have no @@ -58,7 +67,7 @@ protected void declareDefaults() { * * @return This */ - public abstract DSTransport endMessage(); + public abstract DSTransport endSendMessage(); public DSLinkConnection getConnection() { return connection; @@ -68,14 +77,20 @@ public String getConnectionUrl() { return String.valueOf(get(CONNECTION_URL)); } + public StringBuilder getDebugIn() { + return debugIn; + } + + public StringBuilder getDebugOut() { + return debugOut; + } + @Override - public Logger getLogger() { - if (logger == null) { - logger = Logger.getLogger(getConnection().getLink().getLinkName() + ".transport"); - } - return logger; + protected String getLogName() { + return getClass().getSimpleName(); } + public long getReadTimeout() { return ((DSLong) get(READ_TIMEOUT)).toLong(); } @@ -112,6 +127,16 @@ public DSTransport setConnectionUrl(String url) { return this; } + public DSTransport setDebugIn(StringBuilder buf) { + this.debugIn = buf; + return this; + } + + public DSTransport setDebugOut(StringBuilder buf) { + this.debugOut = buf; + return this; + } + /** * The number of millis the input stream will block on a read before throwing a DSIoException. * diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/PushBinaryTransport.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/PushBinaryTransport.java index 8a0bd074..6ee794c8 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/PushBinaryTransport.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/PushBinaryTransport.java @@ -27,7 +27,7 @@ public abstract class PushBinaryTransport extends DSBinaryTransport { ///////////////////////////////////////////////////////////////// @Override - public DSTransport beginMessage() { + public DSTransport beginSendMessage() { return this; } @@ -46,7 +46,7 @@ public DSTransport close() { } @Override - public DSTransport endMessage() { + public DSTransport endSendMessage() { return this; } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/SocketTransport.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/SocketTransport.java index 12c5537a..08944a84 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/SocketTransport.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/transport/SocketTransport.java @@ -4,7 +4,9 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; +import java.net.URI; import javax.net.ssl.SSLSocketFactory; +import org.iot.dsa.node.DSBytes; import org.iot.dsa.util.DSException; /** @@ -18,6 +20,10 @@ public class SocketTransport extends DSBinaryTransport { // Fields /////////////////////////////////////////////////////////////////////////// + private static final int DEBUG_COLS = 30; + + private int debugInSize; + private int debugOutSize; private int messageSize; private OutputStream out; private boolean open = false; @@ -28,7 +34,14 @@ public class SocketTransport extends DSBinaryTransport { ///////////////////////////////////////////////////////////////// @Override - public DSTransport beginMessage() { + public DSTransport beginRecvMessage() { + debugInSize = 0; + return this; + } + + @Override + public DSTransport beginSendMessage() { + debugOutSize = 0; return this; } @@ -42,14 +55,14 @@ public DSTransport close() { out.close(); } } catch (Exception x) { - finer(getConnection().getConnectionId(), x); + debug(getConnection().getConnectionId(), x); } try { if (socket != null) { socket.close(); } } catch (Exception x) { - finer(getConnection().getConnectionId(), x); + debug(getConnection().getConnectionId(), x); } out = null; socket = null; @@ -58,18 +71,22 @@ public DSTransport close() { } @Override - public DSTransport endMessage() { + public DSTransport endSendMessage() { return this; } @Override public InputStream getInput() { + InputStream ret = null; try { - return socket.getInputStream(); + ret = socket.getInputStream(); + if (getDebugIn() != null) { + ret = new DebugInputStream(ret); + } } catch (IOException x) { DSException.throwRuntime(x); } - return null; //unreachable + return ret; } @Override @@ -91,12 +108,20 @@ public DSTransport open() { } try { String url = getConnectionUrl(); - if (url.startsWith("dss://")) { - url = url.substring(6); - socket = SSLSocketFactory.getDefault().createSocket(url, 4128); - } else if (url.startsWith("ds://")) { - url = url.substring(5); - socket = new Socket(url, 4120); + URI uri = new URI(url); + String scheme = uri.getScheme(); + String host = uri.getHost(); + int port = uri.getPort(); + if ("dss".equals(scheme)) { + if (port < 0) { + port = 4128; + } + socket = SSLSocketFactory.getDefault().createSocket(host, port); + } else if ("ds".equals(scheme)) { + if (port < 0) { + port = 4120; + } + socket = new Socket(host, port); } else { throw new IllegalArgumentException("Invalid broker URI: " + url); } @@ -114,6 +139,19 @@ public DSTransport open() { */ public void write(byte[] buf, int off, int len, boolean isLast) { try { + StringBuilder debug = getDebugOut(); + if (debug != null) { + for (int i = off, j = off + len; i < j; i++) { + if (debugOutSize > 0) { + debug.append(' '); + } + DSBytes.toHex(buf[i], debug); + if (++debugOutSize == DEBUG_COLS) { + debugOutSize = 0; + debug.append('\n'); + } + } + } messageSize += len; if (out == null) { out = socket.getOutputStream(); @@ -124,4 +162,72 @@ public void write(byte[] buf, int off, int len, boolean isLast) { } } + // Inner Classes + // ------------- + + private class DebugInputStream extends InputStream { + + private InputStream inner; + + public DebugInputStream(InputStream inner) { + this.inner = inner; + } + + @Override + public int read() throws IOException { + StringBuilder debug = getDebugIn(); + int ch = inner.read(); + if (debug != null) { + if (debugInSize > 0) { + debug.append(' '); + } + DSBytes.toHex((byte) ch, debug); + if (++debugInSize == DEBUG_COLS) { + debugInSize = 0; + debug.append('\n'); + } + } + return ch; + } + + @Override + public int read(byte[] buf) throws IOException { + int ret = inner.read(buf); + StringBuilder debug = getDebugOut(); + if ((debug != null) && (ret > 0)) { + for (int i = 0; i < ret; i++) { + if (debugInSize > 0) { + debug.append(' '); + } + DSBytes.toHex(buf[i], debug); + if (++debugInSize == DEBUG_COLS) { + debugInSize = 0; + debug.append('\n'); + } + } + } + return ret; + } + + @Override + public int read(byte[] buf, int off, int len) throws IOException { + int ret = inner.read(buf, off, len); + StringBuilder debug = getDebugOut(); + if ((debug != null) && (ret > 0)) { + for (int i = off, j = off + ret; i < j; i++) { + if (debugInSize > 0) { + debug.append(' '); + } + DSBytes.toHex(buf[i], debug); + if (++debugInSize == DEBUG_COLS) { + debugInSize = 0; + debug.append('\n'); + } + } + } + return ret; + } + + } //DebugInputStream + } diff --git a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLink.java b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLink.java index 19b7c164..2a7655e7 100644 --- a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLink.java +++ b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLink.java @@ -55,7 +55,6 @@ public class DSLink extends DSNode implements Runnable { private DSLinkConfig config; private String dsId; private DSKeys keys; - private Logger logger; private DSInfo main = getInfo(MAIN); private String name; private Thread runThread; @@ -126,12 +125,9 @@ public String getLinkName() { return name; } - /** - * The logger as defined in dslink.json. - */ @Override - public Logger getLogger() { - return logger; + protected String getLogName() { + return getClass().getSimpleName(); } public DSMainNode getMain() { @@ -152,7 +148,6 @@ protected DSLink init(DSLinkConfig config) { DSLogging.setDefaultLevel(config.getLogLevel()); name = config.getLinkName(); keys = config.getKeys(); - logger = Logger.getLogger(name); getSys().init(); return this; } @@ -189,7 +184,7 @@ public static DSLink load(DSLinkConfig config) { if (type == null) { throw new IllegalStateException("Config missing the main node type"); } - ret.config("Main type: " + type); + ret.fine("Main type: " + type); try { DSNode node = (DSNode) Class.forName(type).newInstance(); ret.put(MAIN, node); @@ -270,7 +265,7 @@ public void run() { } } } catch (Exception x) { - severe(getLinkName(), x); + error(getLinkName(), x); stop(); DSException.throwRuntime(x); } @@ -349,21 +344,21 @@ public void save() { time = System.currentTimeMillis() - time; info("Node database saved: " + time + "ms"); } catch (Exception x) { - severe("Saving node database", x); + error("Saving node database", x); } try { if (in != null) { in.close(); } } catch (IOException x) { - severe("Closing input", x); + error("Closing input", x); } try { if (zos != null) { zos.close(); } } catch (IOException x) { - severe("Closing output", x); + error("Closing output", x); } } diff --git a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConfig.java b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConfig.java index b3cfe729..c73b6294 100644 --- a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConfig.java +++ b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConfig.java @@ -3,6 +3,7 @@ import java.io.File; import java.util.logging.Level; import org.iot.dsa.io.json.JsonReader; +import org.iot.dsa.logging.DSILevels; import org.iot.dsa.node.DSMap; import org.iot.dsa.security.DSKeys; @@ -13,7 +14,7 @@ * * @author Aaron Hansen */ -public class DSLinkConfig { +public class DSLinkConfig implements DSILevels { /////////////////////////////////////////////////////////////////////////// // Constants @@ -474,16 +475,31 @@ public DSLinkConfig setLinkName(String arg) { */ public DSLinkConfig setLogLevel(String level) { level = level.toUpperCase(); - if (level.equals("DEBUG")) { - level = "FINEST"; + if (level.equals("TRACE")) { + logLevel = trace; + } else if (level.equals("DEBUG")) { + logLevel = debug; + } else if (level.equals("FINE")) { + logLevel = fine; + } else if (level.equals("CONFIG")) { + logLevel = fine; + } else if (level.equals("WARN")) { + logLevel = warn; + } else if (level.equals("INFO")) { + logLevel = info; } else if (level.equals("ERROR")) { - level = "WARNING"; + logLevel = error; + } else if (level.equals("ADMIN")) { + logLevel = admin; } else if (level.equals("CRITICAL")) { - level = "SEVERE"; + logLevel = fatal; + } else if (level.equals("FATAL")) { + logLevel = fatal; } else if (level.equals("NONE")) { - level = "OFF"; + logLevel = off; + } else { + logLevel = Level.parse(level); } - logLevel = Level.parse(level); return setConfig(CFG_LOG_LEVEL, level); } diff --git a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConnection.java b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConnection.java index a19ebea3..77df64b3 100644 --- a/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConnection.java +++ b/dslink-core/src/main/java/org/iot/dsa/dslink/DSLinkConnection.java @@ -2,7 +2,6 @@ import com.acuity.iot.dsa.dslink.transport.DSTransport; import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Logger; import org.iot.dsa.node.DSNode; import org.iot.dsa.time.DSTime; @@ -22,7 +21,6 @@ public abstract class DSLinkConnection extends DSNode { private boolean connected = false; private String connectionId; private ConcurrentHashMap listeners; - private Logger logger; // Methods // ------- @@ -42,7 +40,7 @@ public void addListener(Listener listener) { try { listener.onConnect(this); } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); } } } @@ -89,11 +87,8 @@ public DSLink getLink() { } @Override - public Logger getLogger() { - if (logger == null) { - logger = Logger.getLogger(getLink().getLinkName() + ".connection"); - } - return logger; + protected String getLogName() { + return getClass().getSimpleName(); } public DSSysNode getSys() { @@ -179,14 +174,14 @@ public void run() { try { onInitialize(); } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); continue; } try { onConnect(); connected = true; } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); continue; } if (listeners != null) { @@ -194,7 +189,7 @@ public void run() { try { listener.onConnect(DSLinkConnection.this); } catch (Exception x) { - severe(listener.toString(), x); + error(listener.toString(), x); } } } @@ -203,19 +198,19 @@ public void run() { reconnectRate = 1000; } catch (Throwable x) { reconnectRate = Math.min(reconnectRate * 2, DSTime.MILLIS_MINUTE); - severe(getConnectionId(), x); + error(getConnectionId(), x); } try { onDisconnect(); } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); } if (listeners != null) { for (Listener listener : listeners.keySet()) { try { listener.onDisconnect(DSLinkConnection.this); } catch (Exception x) { - severe(listener.toString(), x); + error(listener.toString(), x); } } } diff --git a/dslink-core/src/main/java/org/iot/dsa/dslink/DSMainNode.java b/dslink-core/src/main/java/org/iot/dsa/dslink/DSMainNode.java index 0e3fbebf..96144177 100644 --- a/dslink-core/src/main/java/org/iot/dsa/dslink/DSMainNode.java +++ b/dslink-core/src/main/java/org/iot/dsa/dslink/DSMainNode.java @@ -1,6 +1,5 @@ package org.iot.dsa.dslink; -import java.util.logging.Logger; import org.iot.dsa.node.DSNode; /** @@ -11,16 +10,6 @@ */ public class DSMainNode extends DSNode { - /** - * Creates a child logger of the link. - */ - protected Logger getLogger(String name) { - if (name.startsWith(".")) { - return Logger.getLogger(getLink().getLinkName() + name); - } - return Logger.getLogger(getLink().getLinkName() + '.' + name); - } - /** * The parent link or null. */ @@ -28,6 +17,11 @@ public DSLink getLink() { return (DSLink) getParent(); } + @Override + protected String getLogName() { + return "Main"; + } + /** * Override point, returns true by default. */ diff --git a/dslink-core/src/main/java/org/iot/dsa/dslink/DSSysNode.java b/dslink-core/src/main/java/org/iot/dsa/dslink/DSSysNode.java index 95ecd083..876cbcc2 100644 --- a/dslink-core/src/main/java/org/iot/dsa/dslink/DSSysNode.java +++ b/dslink-core/src/main/java/org/iot/dsa/dslink/DSSysNode.java @@ -39,6 +39,11 @@ public DSLink getLink() { return (DSLink) getParent(); } + @Override + protected String getLogName() { + return "Sys"; + } + void init() { DSLinkConfig config = getLink().getConfig(); try { @@ -47,7 +52,7 @@ void init() { if (ver.startsWith("1")) { String type = config.getConfig(DSLinkConfig.CFG_CONNECTION_TYPE, null); if (type != null) { - config(config() ? "Connection type: " + type : null); + fine(fine() ? "Connection type: " + type : null); conn = (DSLinkConnection) Class.forName(type).newInstance(); } else { conn = new DS1LinkConnection(); diff --git a/dslink-core/src/main/java/org/iot/dsa/io/msgpack/MsgpackWriter.java b/dslink-core/src/main/java/org/iot/dsa/io/msgpack/MsgpackWriter.java index 0f676ec1..dc11699b 100644 --- a/dslink-core/src/main/java/org/iot/dsa/io/msgpack/MsgpackWriter.java +++ b/dslink-core/src/main/java/org/iot/dsa/io/msgpack/MsgpackWriter.java @@ -344,6 +344,17 @@ public void writeTo(OutputStream out) { } } + /** + * Writes the UTF8 bytes to the underlying buffer. + */ + public void writeUTF8(CharSequence arg) { + CharBuffer chars = getCharBuffer(arg); + ByteBuffer strBuffer = getStringBuffer(chars.position() * (int) encoder.maxBytesPerChar()); + encoder.encode(chars, strBuffer, false); + byteBuffer.put(strBuffer); + encoder.reset(); + } + @Override protected void writeValue(CharSequence arg) throws IOException { if (frame != null) { diff --git a/dslink-core/src/main/java/org/iot/dsa/logging/AsyncLogHandler.java b/dslink-core/src/main/java/org/iot/dsa/logging/AsyncLogHandler.java index 090f122b..ad960169 100644 --- a/dslink-core/src/main/java/org/iot/dsa/logging/AsyncLogHandler.java +++ b/dslink-core/src/main/java/org/iot/dsa/logging/AsyncLogHandler.java @@ -15,7 +15,7 @@ * * @author Aaron Hansen */ -public abstract class AsyncLogHandler extends Handler { +public abstract class AsyncLogHandler extends Handler implements DSILevels { /////////////////////////////////////////////////////////////////////////// // Constants @@ -199,6 +199,29 @@ protected void start() { } } + private String toString(Level level) { + switch (level.intValue()) { + case TRACE: //finest + return "Trace"; + case DEBUG: //finer + return "Debug"; + case FINE: //fine + case CONFIG: //config + return "Fine "; + case WARN: //custom + return "Warn "; + case INFO: //info + return "Info "; + case ERROR: //warn + return "Error"; + case ADMIN: //custom + return "Admin"; + case FATAL: //severe + return "Fatal"; + } + return level.getLocalizedName(); + } + /** * Formats and writes the logging record the underlying stream. */ @@ -208,11 +231,11 @@ protected void write(LogRecord record) { out.println(formatter.format(record)); return; } + // severity + builder.append(toString(record.getLevel())).append(' '); // timestamp calendar.setTimeInMillis(record.getMillis()); DSTime.encodeForLogs(calendar, builder); - // severity - builder.append(' ').append(record.getLevel().getLocalizedName()); // log name String name = record.getLoggerName(); if ((name != null) && !name.isEmpty()) { @@ -220,27 +243,26 @@ protected void write(LogRecord record) { builder.append(record.getLoggerName()); builder.append(']'); } else { - builder.append(" [default]"); + builder.append(" [Default]"); } // class if (record.getSourceClassName() != null) { - builder.append(" - "); builder.append(record.getSourceClassName()); + builder.append(" - "); } // method if (record.getSourceMethodName() != null) { - builder.append(" - "); builder.append(record.getSourceMethodName()); + builder.append(" - "); } // message String msg = record.getMessage(); if ((msg != null) && (msg.length() > 0)) { Object[] params = record.getParameters(); - if (params != null) { + if ((params != null) && (params.length > 0)) { msg = String.format(msg, params); } - builder.append(" - "); - builder.append(msg); + builder.append(' ').append(msg); } out.println(builder.toString()); builder.setLength(0); diff --git a/dslink-core/src/main/java/org/iot/dsa/logging/DSILevels.java b/dslink-core/src/main/java/org/iot/dsa/logging/DSILevels.java new file mode 100644 index 00000000..687d73d9 --- /dev/null +++ b/dslink-core/src/main/java/org/iot/dsa/logging/DSILevels.java @@ -0,0 +1,30 @@ +package org.iot.dsa.logging; + +import java.util.logging.Level; + +public interface DSILevels { + + int TRACE = 300; //finest + int DEBUG = 400; //finer + int FINE = 500; //fine + int CONFIG = 700; //config + int WARN = 750; //custom + int INFO = 800; //info + int ERROR = 900; //warning + int ADMIN = 950; //custom + int FATAL = 1000; //severe + + Level all = Level.ALL; + Level trace = Level.FINEST; + Level debug = Level.FINER; + Level fine = Level.FINE; + Level warn = new DSLevel("Warn", WARN); + Level info = Level.INFO; + Level error = Level.WARNING; + Level admin = new DSLevel("Admin", ADMIN); + Level fatal = Level.SEVERE; + Level off = Level.OFF; + +} + + diff --git a/dslink-core/src/main/java/org/iot/dsa/logging/DSLevel.java b/dslink-core/src/main/java/org/iot/dsa/logging/DSLevel.java new file mode 100644 index 00000000..84b8d8c6 --- /dev/null +++ b/dslink-core/src/main/java/org/iot/dsa/logging/DSLevel.java @@ -0,0 +1,11 @@ +package org.iot.dsa.logging; + +import java.util.logging.Level; + +class DSLevel extends Level { + + DSLevel(String name, int value) { + super(name, value); + } + +} diff --git a/dslink-core/src/main/java/org/iot/dsa/logging/DSLogger.java b/dslink-core/src/main/java/org/iot/dsa/logging/DSLogger.java index 539b8bd8..5b8157b7 100644 --- a/dslink-core/src/main/java/org/iot/dsa/logging/DSLogger.java +++ b/dslink-core/src/main/java/org/iot/dsa/logging/DSLogger.java @@ -1,17 +1,19 @@ package org.iot.dsa.logging; -import static java.util.logging.Level.CONFIG; -import static java.util.logging.Level.FINE; -import static java.util.logging.Level.FINER; -import static java.util.logging.Level.FINEST; -import static java.util.logging.Level.INFO; -import static java.util.logging.Level.SEVERE; -import static java.util.logging.Level.WARNING; - import java.util.logging.Logger; /** - * Adds a convenience layer to Java Util Logging. + * Adds an abstraction layer on Java Util Logging for two purposes: + * + *

    + * + *
  • To use the DSA levels. + * + *
  • So JUL can be replaced with other logging frameworks if desired. + * + *
+ * + * This has methods that enables conditional logging (most efficient) using ternary statements. * *

* @@ -20,9 +22,8 @@ *

  *
  * {@code
- * if (myLogger.isLoggable(Level.FINE) {
- *   myLogger.fine(someMessage());
- * }
+ * if (myLogger.isLoggable(Level.INFO))
+ *     myLogger.info(someMessage());
  * }
  *
  * 
@@ -32,184 +33,218 @@ *
  *
  * {@code
- * fine(fine() ? someMessage() : null);
+ * info(info() ? someMessage() : null);
  * }
  *
  * 
* *

* - * Level Guidelines: + * DSA defines levels differently than JUL, however, all JUL levels will be mapped / formatted + * for DSA. Level guidelines: * *

    * - *
  • finest = verbose or trace + *
  • trace() = DSA trace = JUL finest + * + *
  • debug() = DSA debug = JUL finer * - *
  • finer = debug + *
  • fine() = DSA info = JUL fine, config * - *
  • fine = common or frequent event + *
  • warn() = DSA warn = Custom level * - *
  • config = configuration info + *
  • info() = DSA sys = JUL info * - *
  • info = major lifecycle event + *
  • error() = DSA error = JUL warning * - *
  • warn = unusual and infrequent, but not critical + *
  • admin() = DSA admin = Custom level * - *
  • severe = critical / fatal error or event + *
  • fatal() = DSA fatal = JUL severe * *
* * @author Aaron Hansen */ -public class DSLogger { +public class DSLogger implements DSILevels { + + private Logger logger; + + /** + * Override point, returns the console logger by default. + */ + private Logger getLogger() { + if (logger == null) { + logger = Logger.getLogger(getLogName()); + } + return logger; + } + + /** + * Override point, returns the simple class name by default. + */ + protected String getLogName() { + return getClass().getSimpleName(); + } ///////////////////////////////////////////////////////////////// - // Methods - In alphabetical order by method name. + // Logging methods, ordered from lowest severity to highest ///////////////////////////////////////////////////////////////// /** * True if the level is loggable. */ - public boolean config() { - return getLogger().isLoggable(CONFIG); + public boolean trace() { + return getLogger().isLoggable(trace); } - public void config(Object msg) { - getLogger().log(CONFIG, string(msg)); + /** + * Log a trace or verbose event. + */ + public void trace(Object msg) { + getLogger().log(trace, string(msg)); } /** - * Override point, returns the console logger by default. + * Log a trace or verbose event. */ - public Logger getLogger() { - return DSLogging.getDefaultLogger(); + public void trace(Object msg, Throwable x) { + getLogger().log(trace, string(msg), x); } /** * True if the level is loggable. */ - public boolean fine() { - return getLogger().isLoggable(FINE); + public boolean debug() { + return getLogger().isLoggable(debug); } /** - * Log a frequent event. + * Log a debug event. */ - public void fine(Object msg) { - getLogger().log(FINE, string(msg)); + public void debug(Object msg) { + getLogger().log(debug, string(msg)); } /** - * Log a frequent event. + * Log a debug event. */ - public void fine(Object msg, Throwable x) { - getLogger().log(FINE, string(msg), x); + public void debug(Object msg, Throwable x) { + getLogger().log(debug, string(msg), x); } /** * True if the level is loggable. */ - public boolean finer() { - return getLogger().isLoggable(FINER); + public boolean fine() { + return getLogger().isLoggable(fine); } /** - * Log a debug event. + * Log a frequent event. */ - public void finer(Object msg) { - getLogger().log(FINER, string(msg)); + public void fine(Object msg) { + getLogger().log(fine, string(msg)); } /** - * Log a debug event. + * Log a frequent event. */ - public void finer(Object msg, Throwable x) { - getLogger().log(FINER, string(msg), x); + public void fine(Object msg, Throwable x) { + getLogger().log(fine, string(msg), x); } /** * True if the level is loggable. */ - public boolean finest() { - return getLogger().isLoggable(FINEST); + public boolean warn() { + return getLogger().isLoggable(warn); } /** - * Log a trace or verbose event. + * Log an unusual but not critical event. */ - public void finest(Object msg) { - getLogger().log(FINEST, string(msg)); + public void warn(Object msg) { + getLogger().log(warn, string(msg)); } /** - * Log a trace or verbose event. + * Log an unusual but not critical event. */ - public void finest(Object msg, Throwable x) { - getLogger().log(FINEST, string(msg), x); + public void warn(Object msg, Throwable x) { + getLogger().log(warn, string(msg), x); } /** * True if the level is loggable. */ public boolean info() { - return getLogger().isLoggable(INFO); + return getLogger().isLoggable(info); } /** * Log an infrequent major lifecycle event. */ public void info(Object msg) { - getLogger().log(INFO, string(msg)); + getLogger().log(info, string(msg)); } /** * Log an infrequent major lifecycle event. */ public void info(Object msg, Throwable x) { - getLogger().log(INFO, string(msg), x); + getLogger().log(info, string(msg), x); } /** * True if the level is loggable. */ - public boolean severe() { - return getLogger().isLoggable(SEVERE); + public boolean error() { + return getLogger().isLoggable(warn); } - public void severe(Object msg) { - getLogger().log(SEVERE, string(msg)); + public void error(Object msg) { + getLogger().log(warn, string(msg)); } - public void severe(Object msg, Throwable x) { - getLogger().log(SEVERE, string(msg), x); - } - - private String string(Object obj) { - if (obj == null) { - return null; - } - return obj.toString(); + public void error(Object msg, Throwable x) { + getLogger().log(warn, string(msg), x); } /** * True if the level is loggable. */ - public boolean warn() { - return getLogger().isLoggable(WARNING); + public boolean admin() { + return getLogger().isLoggable(admin); } - /** - * Log an unusual but not critical event. - */ - public void warn(Object msg) { - getLogger().log(WARNING, string(msg)); + public void admin(Object msg) { + getLogger().log(admin, string(msg)); + } + + public void admin(Object msg, Throwable x) { + getLogger().log(admin, string(msg), x); } /** - * Log an unusual but not critical event. + * True if the level is loggable. */ - public void warn(Object msg, Throwable x) { - getLogger().log(WARNING, string(msg), x); + public boolean fatal() { + return getLogger().isLoggable(fatal); + } + + public void fatal(Object msg) { + getLogger().log(fatal, string(msg)); + } + + public void fatal(Object msg, Throwable x) { + getLogger().log(fatal, string(msg), x); + } + + private String string(Object obj) { + if (obj == null) { + return null; + } + return obj.toString(); } diff --git a/dslink-core/src/main/java/org/iot/dsa/logging/PrintStreamLogHandler.java b/dslink-core/src/main/java/org/iot/dsa/logging/PrintStreamLogHandler.java index fc78c62d..7509e594 100644 --- a/dslink-core/src/main/java/org/iot/dsa/logging/PrintStreamLogHandler.java +++ b/dslink-core/src/main/java/org/iot/dsa/logging/PrintStreamLogHandler.java @@ -9,41 +9,17 @@ */ public class PrintStreamLogHandler extends AsyncLogHandler { - /////////////////////////////////////////////////////////////////////////// - // Constants - /////////////////////////////////////////////////////////////////////////// - - /////////////////////////////////////////////////////////////////////////// - // Fields - /////////////////////////////////////////////////////////////////////////// - private String name; - /////////////////////////////////////////////////////////////////////////// - // Constructors - /////////////////////////////////////////////////////////////////////////// - public PrintStreamLogHandler(String name, PrintStream out) { this.name = name; setOut(out); start(); } - /////////////////////////////////////////////////////////////////////////// - // Methods - /////////////////////////////////////////////////////////////////////////// - @Override public String getThreadName() { return name; } - /////////////////////////////////////////////////////////////////////////// - // Inner Classes - /////////////////////////////////////////////////////////////////////////// - - /////////////////////////////////////////////////////////////////////////// - // Initialization - /////////////////////////////////////////////////////////////////////////// - -} //class +} diff --git a/dslink-core/src/main/java/org/iot/dsa/node/DSBytes.java b/dslink-core/src/main/java/org/iot/dsa/node/DSBytes.java index 4217256e..ebfeb42d 100644 --- a/dslink-core/src/main/java/org/iot/dsa/node/DSBytes.java +++ b/dslink-core/src/main/java/org/iot/dsa/node/DSBytes.java @@ -313,6 +313,22 @@ public byte[] toBytes() { return value; } + /** + * Converts the bytes into a hex string. + * + * @param val What to convert. + * @param buf Where to put the results, can be null. + * @return The buf parameter, or a new StringBuilder if the param was null. + */ + public static StringBuilder toHex(byte val, StringBuilder buf) { + if (buf == null) { + buf = new StringBuilder(); + } + buf.append(HEX[(val >>> 4) & 0x0F]); + buf.append(HEX[val & 0x0F]); + return buf; + } + /** * Converts the bytes into a hex string. * @@ -326,7 +342,7 @@ public static StringBuilder toHex(byte[] bytes, StringBuilder buf) { } for (int i = 0, len = bytes.length; i < len; i++) { int val = bytes[i] & 0xFF; - buf.append(HEX[val >>> 4]); + buf.append(HEX[val >>> 4] & 0x0F); buf.append(HEX[val & 0x0F]); } return buf; diff --git a/dslink-core/src/main/java/org/iot/dsa/node/DSGroup.java b/dslink-core/src/main/java/org/iot/dsa/node/DSGroup.java index 4881fa36..ef2cc10c 100644 --- a/dslink-core/src/main/java/org/iot/dsa/node/DSGroup.java +++ b/dslink-core/src/main/java/org/iot/dsa/node/DSGroup.java @@ -57,6 +57,10 @@ public int hashCode() { return hashCode; } + public boolean hasParent() { + return parent != null; + } + /** * Returns the item at index 0. * diff --git a/dslink-core/src/main/java/org/iot/dsa/node/DSMap.java b/dslink-core/src/main/java/org/iot/dsa/node/DSMap.java index 628b4b70..e47de7b8 100644 --- a/dslink-core/src/main/java/org/iot/dsa/node/DSMap.java +++ b/dslink-core/src/main/java/org/iot/dsa/node/DSMap.java @@ -58,7 +58,7 @@ public boolean contains(String key) { } @Override - public DSElement copy() { + public DSMap copy() { DSMap ret = new DSMap(); for (int i = 0, len = size(); i < len; i++) { ret.put(getKey(i), get(i).copy()); diff --git a/dslink-core/src/main/java/org/iot/dsa/node/DSNode.java b/dslink-core/src/main/java/org/iot/dsa/node/DSNode.java index 7de04331..ec555486 100644 --- a/dslink-core/src/main/java/org/iot/dsa/node/DSNode.java +++ b/dslink-core/src/main/java/org/iot/dsa/node/DSNode.java @@ -3,11 +3,9 @@ import java.util.Iterator; import java.util.Map; import java.util.TreeMap; -import java.util.logging.Logger; import org.iot.dsa.dslink.DSIResponder; import org.iot.dsa.dslink.responder.InboundSetRequest; import org.iot.dsa.logging.DSLogger; -import org.iot.dsa.logging.DSLogging; import org.iot.dsa.node.action.ActionInvocation; import org.iot.dsa.node.action.ActionResult; import org.iot.dsa.node.event.DSIEvent; @@ -228,7 +226,7 @@ void add(final DSInfo info) { try { onChildAdded(info); } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); } fire(INFO_TOPIC, DSInfoTopic.Event.CHILD_ADDED, info); } @@ -419,7 +417,7 @@ protected void fire(DSTopic topic, DSIEvent event, DSInfo child, Object... param try { sub.subscriber.onEvent(topic, event, this, child, params); } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); } } sub = sub.next; @@ -536,17 +534,13 @@ public DSInfo getLastInfo() { return lastChild; } - /** - * Ascends the tree until a logger is found. If overriding, call super.getLogger and set the - * result as the parent logger of your new logger. - */ @Override - public Logger getLogger() { + protected String getLogName() { DSNode parent = getParent(); if (parent != null) { - return parent.getLogger(); + return parent.getLogName(); } - return DSLogging.getDefaultLogger(); + return super.getLogName(); } /** @@ -1012,7 +1006,7 @@ public DSNode put(DSInfo info, DSIObject object) { try { onChildChanged(info); } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); } fire(VALUE_TOPIC, DSValueTopic.Event.CHILD_CHANGED, info); } @@ -1064,7 +1058,7 @@ public DSNode remove(DSInfo info) { try { onChildRemoved(info); } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); } fire(INFO_TOPIC, DSInfoTopic.Event.CHILD_REMOVED, info); Subscription sub = subscription; @@ -1141,7 +1135,7 @@ public final void stable() { try { onStable(); } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); } } @@ -1165,7 +1159,7 @@ public final void start() { try { onStarted(); } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); } } @@ -1246,20 +1240,20 @@ public void subscribe(DSTopic topic, DSInfo child, DSISubscriber subscriber) { try { onSubscribe(topic, child, subscriber); } catch (Exception x) { - severe(getParent(), x); + error(getParent(), x); } if (count == 0) { try { onSubscribed(topic, child); } catch (Exception x) { - severe(getParent(), x); + error(getParent(), x); } } if (firstSubscription) { try { onSubscribed(); } catch (Exception x) { - severe(getParent(), x); + error(getParent(), x); } } } @@ -1305,20 +1299,20 @@ public void unsubscribe(DSTopic topic, DSInfo child, DSISubscriber subscriber) { try { onUnsubscribe(topic, child, subscriber); } catch (Exception x) { - severe(getParent(), x); + error(getParent(), x); } if (count == 0) { try { onUnsubscribed(topic, child); } catch (Exception x) { - severe(getParent(), x); + error(getParent(), x); } } if (subscription == null) { try { onUnsubscribed(); } catch (Exception x) { - severe(getParent(), x); + error(getParent(), x); } } removed.unsubscribe(); @@ -1446,7 +1440,7 @@ void unsubscribe() { try { subscriber.onUnsubscribed(topic, DSNode.this, child); } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); } } diff --git a/dslink-core/src/main/java/org/iot/dsa/node/DSPath.java b/dslink-core/src/main/java/org/iot/dsa/node/DSPath.java index a33f094e..4aa611d5 100644 --- a/dslink-core/src/main/java/org/iot/dsa/node/DSPath.java +++ b/dslink-core/src/main/java/org/iot/dsa/node/DSPath.java @@ -92,6 +92,9 @@ public static String decodePathName(String pathName) { * Splits the path and decodes each individual name. */ public static String[] decodePath(String path) { + if (path == null) { + return new String[0]; + } String[] elems = splitPath(path); for (int i = 0, len = elems.length; i < len; i++) { elems[i] = decodePathName(elems[i]); @@ -145,7 +148,7 @@ public static String encodePath(DSNode node) { */ public static String encodeName(String name) { StringBuilder builder = new StringBuilder(); - if (encodeName(name, builder)) { + if (encodeName(name, builder, false)) { return builder.toString(); } return name; @@ -160,6 +163,28 @@ public static String encodeName(String name) { * @return True if the name was modified in any way. */ public static boolean encodeName(String name, StringBuilder buf) { + return encodeName(name, buf, false); + } + + /** + * Encodes a DSA v1 name for use outside of a path. + * + * @return True if the name was modified in any way. + */ + public static boolean encodeNameV1(String name, StringBuilder buf) { + return encodeName(name, buf, true); + } + + /** + * Encodes a name. + * + * @param name The raw un-encoded name. + * @param buf When to put the encoded name. Characters will be put into the buf no matter + * what. + * @param v1 True if for v1. + * @return True if the name was modified in any way. + */ + private static boolean encodeName(String name, StringBuilder buf, boolean v1) { if (name == null) { return false; } @@ -167,9 +192,11 @@ public static boolean encodeName(String name, StringBuilder buf) { int pathLength = name.length(); CharArrayWriter charArrayWriter = null; char c; + boolean encode = false; for (int i = 0; i < pathLength; ) { c = name.charAt(i); - if (!shouldEncode(c, i)) { + encode = v1 ? shouldEncodeV1(c, i) : shouldEncode(c, i); + if (!encode) { buf.append(c); i++; } else { @@ -240,7 +267,7 @@ public String[] getPathElements() { /** * Returns true for characters that should be encoded. */ - public static boolean shouldEncode(int ch, int pos) { + private static boolean shouldEncode(int ch, int pos) { switch (ch) { case '.': case '/': @@ -265,6 +292,22 @@ public static boolean shouldEncode(int ch, int pos) { } } + /** + * Returns true for characters that should be encoded. + */ + private static boolean shouldEncodeV1(int ch, int pos) { + switch (ch) { + case '/': + case '%': + return true; + case '@': + case '$': + return pos == 0; + default: + return ch < 0x20; + } + } + /** * Splits the path, but does not decode any names. The difference between this and * String.split() is that String.split() will return an empty string at index 0 if the path @@ -296,12 +339,4 @@ public static String[] splitPath(String path) { return path.split("/"); } - /////////////////////////////////////////////////////////////////////////// - // Inner Classes - /////////////////////////////////////////////////////////////////////////// - - /////////////////////////////////////////////////////////////////////////// - // Initialization - /////////////////////////////////////////////////////////////////////////// - } diff --git a/dslink-core/src/main/java/org/iot/dsa/time/DSTime.java b/dslink-core/src/main/java/org/iot/dsa/time/DSTime.java index 21a05fec..18d7ba43 100644 --- a/dslink-core/src/main/java/org/iot/dsa/time/DSTime.java +++ b/dslink-core/src/main/java/org/iot/dsa/time/DSTime.java @@ -715,6 +715,15 @@ public static StringBuilder encodeForLogs(Calendar calendar, StringBuilder buf) buf.append('0'); } buf.append(tmp); + buf.append(tmp).append(':'); + tmp = calendar.get(Calendar.MILLISECOND); + if (tmp < 100) { + buf.append('0'); + } + if (tmp < 10) { + buf.append('0'); + } + buf.append(tmp); return buf; } diff --git a/dslink-java-v2-test/dslink.json b/dslink-java-v2-test/dslink.json index ab07b557..85f264f1 100644 --- a/dslink-java-v2-test/dslink.json +++ b/dslink-java-v2-test/dslink.json @@ -10,9 +10,9 @@ "value": "org.iot.dsa.dslink.test.MainNode" }, "log": { - "desc": "debug, info, warn, error, none", + "desc": "all, trace, debug, info, warn, sys, error, admin, fatal, none", "type": "enum", - "value": "info" + "value": "debug" }, "token": { "desc": "Authentication token for the broker.", diff --git a/dslink-java-v2-test/src/main/java/org/iot/dsa/dslink/test/MainNode.java b/dslink-java-v2-test/src/main/java/org/iot/dsa/dslink/test/MainNode.java index 1f4f6e15..bb4ffc70 100644 --- a/dslink-java-v2-test/src/main/java/org/iot/dsa/dslink/test/MainNode.java +++ b/dslink-java-v2-test/src/main/java/org/iot/dsa/dslink/test/MainNode.java @@ -65,6 +65,7 @@ protected void declareDefaults() { action.addValueResult("bool", DSBool.TRUE); action.addValueResult("long", DSLong.valueOf(0)); declareDefault("Values Action", action); + declareDefault("T./,<>?;:'\"[%]{/}bc", DSString.valueOf("abc")).setTransient(true); } @Override diff --git a/dslink-websocket-standalone/src/main/java/org/iot/dsa/dslink/websocket/WsTextTransport.java b/dslink-websocket-standalone/src/main/java/org/iot/dsa/dslink/websocket/WsTextTransport.java index cc7f3d86..e84f5f51 100644 --- a/dslink-websocket-standalone/src/main/java/org/iot/dsa/dslink/websocket/WsTextTransport.java +++ b/dslink-websocket-standalone/src/main/java/org/iot/dsa/dslink/websocket/WsTextTransport.java @@ -46,7 +46,7 @@ public class WsTextTransport extends DSTextTransport { ///////////////////////////////////////////////////////////////// @Override - public DSTransport beginMessage() { + public DSTransport beginSendMessage() { messageSize = 0; return this; } @@ -62,7 +62,7 @@ public DSTransport close() { session.close(); } } catch (Exception x) { - finer(getConnection().getConnectionId(), x); + debug(getConnection().getConnectionId(), x); } session = null; buffer.close(); @@ -70,7 +70,7 @@ public DSTransport close() { } @Override - public DSTransport endMessage() { + public DSTransport endSendMessage() { write("", true); messageSize = 0; return this; @@ -112,14 +112,14 @@ public void onClose(Session session, CloseReason reason) { public void onError(Session session, Throwable err) { if (open) { open = false; - severe(getConnectionUrl(), err); + error(getConnectionUrl(), err); buffer.close(DSException.makeRuntime(err)); } } @OnMessage public void onMessage(Session session, String msgPart, boolean isLast) { - finest(finest() ? "Recv: " + msgPart : null); + trace(trace() ? "Recv: " + msgPart : null); buffer.put(msgPart); } @@ -164,7 +164,7 @@ public void write(String text, boolean isLast) { int len = text.length(); messageSize += len; if (len > 0) { - finest(finest() ? "Send: " + text : null); + trace(trace() ? "Send: " + text : null); } basic.sendText(text, isLast); } catch (IOException x) { From c42552913ced2708d2cf3ee1045dfad5b3a1d72e Mon Sep 17 00:00:00 2001 From: Aaron Date: Mon, 19 Feb 2018 13:17:46 -0800 Subject: [PATCH 10/11] V2 Protocol fixes. --- .../protocol_v2/DS2MessageReader.java | 41 +++++++++---------- .../protocol/protocol_v2/DS2Session.java | 9 ++-- .../responder/DS2InboundInvoke.java | 18 ++++++++ .../responder/DS2InboundSubscription.java | 29 +++++++------ .../responder/DS2InboundSubscriptions.java | 5 ++- .../protocol_v2/responder/DS2Responder.java | 20 +++++++-- .../protocol/responder/DSInboundInvoke.java | 11 ++++- .../protocol/responder/DSInboundList.java | 26 +++++++++--- .../responder/DSInboundSubscription.java | 18 +++++--- .../responder/DSInboundSubscriptions.java | 12 +++++- .../org/iot/dsa/dslink/test/MainNode.java | 9 +++- 11 files changed, 139 insertions(+), 59 deletions(-) diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageReader.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageReader.java index cbb4cd56..b5c2db8d 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageReader.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2MessageReader.java @@ -45,6 +45,24 @@ public DS2MessageReader() { // Methods // ------- + public void debugSummary() { + StringBuilder buf = getDebug(); + buf.append("RECV "); + debugMethod(getMethod(), buf); + if (requestId >= 0) { + buf.append(", ").append("Rid ").append(requestId); + } + if (ackId >= 0) { + buf.append(", ").append("Ack ").append(ackId); + } + for (Map.Entry e : headers.entrySet()) { + buf.append(", "); + debugHeader(e.getKey(), buf); + buf.append("="); + buf.append(e.getValue()); + } + } + public int getAckId() { return ackId; } @@ -173,6 +191,7 @@ public boolean isRequest() { case MSG_INVOKE_REQ: case MSG_LIST_REQ: case MSG_OBSERVE_REQ: + case MSG_SUBSCRIBE_REQ: case MSG_SET_REQ: return true; } @@ -223,28 +242,6 @@ void parseDynamicHeaders(InputStream in, int len, Map headers) } } - public void debugSummary() { - StringBuilder buf = getDebug(); - buf.append("RECV "); - debugMethod(getMethod(), buf); - if (requestId >= 0) { - buf.append(", ").append("Rid ").append(requestId); - } - if (ackId >= 0) { - buf.append(", ").append("Ack ").append(ackId); - } - Object target = headers.get(HDR_TARGET_PATH); - if (target != null) { - buf.append(", ").append(target); - } - for (Map.Entry e : headers.entrySet()) { - buf.append(", "); - debugHeader(e.getKey(), buf); - buf.append("="); - buf.append(e.getValue()); - } - } - /** * Decodes a DSA 2.n string. */ diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java index 791418fd..8551497c 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java @@ -22,7 +22,7 @@ public class DS2Session extends DSSession implements MessageConstants { // Constants /////////////////////////////////////////////////////////////////////////// - static final int END_MSG_THRESHOLD = 48000; + static final int END_MSG_THRESHOLD = 48 * 1024; static final String LAST_ACK_RECV = "Last Ack Recv"; static final int MAX_MSG_IVL = 45000; @@ -251,9 +251,10 @@ private boolean send(boolean requests) { * Returns true if the current message size has crossed a message size threshold. */ public boolean shouldEndMessage() { - //todo - //return (messageWriter.length() + getTransport().messageSize()) > END_MSG_THRESHOLD; - return getTransport().messageSize() > END_MSG_THRESHOLD; + if (getMessageWriter().getBodyLength() > END_MSG_THRESHOLD) { + return true; + } + return (System.currentTimeMillis() - lastMessageSent) > 1000; } } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundInvoke.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundInvoke.java index 979805aa..e4566d6d 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundInvoke.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundInvoke.java @@ -30,4 +30,22 @@ public void write(MessageWriter writer) { //if has multipart } + @Override + protected void writeBegin(MessageWriter writer) { + writer.getWriter().beginMap(); + } + + @Override + protected void writeClose(MessageWriter writer) { + DS2MessageWriter out = (DS2MessageWriter) writer; + out.addHeader((byte) HDR_STATUS, MessageConstants.STS_CLOSED); + } + + @Override + protected void writeOpen(MessageWriter writer) { + DS2MessageWriter out = (DS2MessageWriter) writer; + out.addHeader((byte) HDR_STATUS, MessageConstants.STS_OK); + } + + } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscription.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscription.java index 03ad10d5..76734d1c 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscription.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscription.java @@ -7,7 +7,7 @@ import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundSubscription; import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundSubscriptions; import com.acuity.iot.dsa.dslink.transport.DSBinaryTransport; -import org.iot.dsa.io.msgpack.MsgpackWriter; +import org.iot.dsa.io.DSIWriter; import org.iot.dsa.time.DSTime; /** @@ -36,23 +36,26 @@ protected DS2InboundSubscription(DSInboundSubscriptions manager, @Override protected void write(Update update, MessageWriter writer, StringBuilder buf) { - DS2MessageWriter out = (DS2MessageWriter) writer; - out.init(getSubscriptionId(), getSession().getNextAck()); - out.setMethod((byte) MSG_SUBSCRIBE_RES); - DSByteBuffer byteBuffer = out.getBody(); + DS2MessageWriter messageWriter = (DS2MessageWriter) writer; + messageWriter.init(getSubscriptionId(), getSession().getNextAck()); + messageWriter.setMethod((byte) MSG_SUBSCRIBE_RES); + DSIWriter dsiWriter = messageWriter.getWriter(); + DSByteBuffer byteBuffer = messageWriter.getBody(); byteBuffer.skip(2); - MsgpackWriter msgpackWriter = new MsgpackWriter(byteBuffer); - msgpackWriter.beginMap(); + int start = byteBuffer.length(); + dsiWriter.beginMap(); buf.setLength(0); DSTime.encode(update.timestamp, true, buf); - msgpackWriter.key("timestamp").value(buf.toString()); + dsiWriter.key("timestamp").value(buf.toString()); if (!update.quality.isOk()) { - msgpackWriter.key("status").value(update.quality.toElement()); + dsiWriter.key("status").value(update.quality.toElement()); } - msgpackWriter.endMap(); - byteBuffer.replaceShort(0, (short) msgpackWriter.length(), false); - msgpackWriter.value(update.value.toElement()); - out.write((DSBinaryTransport) getResponder().getTransport()); + dsiWriter.endMap(); + int end = byteBuffer.length(); + byteBuffer.replaceShort(start - 2, (short) (end - start), false); + dsiWriter.reset(); + dsiWriter.value(update.value.toElement()); + messageWriter.write((DSBinaryTransport) getResponder().getTransport()); } } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscriptions.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscriptions.java index dee5cfea..169f40f4 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscriptions.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscriptions.java @@ -34,7 +34,10 @@ public DS2InboundSubscriptions(DSResponder responder) { @Override protected DSInboundSubscription makeSubscription(Integer sid, String path, int qos) { - return new DS2InboundSubscription(this, sid, path, qos); + DSInboundSubscription ret = new DS2InboundSubscription(this, sid, path, qos); + ret.setResponder(getResponder()); + ret.setSession(getResponder().getSession()); + return ret; } @Override diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java index 0373913d..b462d966 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2Responder.java @@ -7,6 +7,7 @@ import com.acuity.iot.dsa.dslink.protocol.protocol_v2.MessageConstants; import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundRequest; import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundSet; +import com.acuity.iot.dsa.dslink.protocol.responder.DSInboundSubscription; import com.acuity.iot.dsa.dslink.protocol.responder.DSResponder; import com.acuity.iot.dsa.dslink.transport.DSBinaryTransport; import java.util.Map; @@ -113,14 +114,19 @@ private void processClose(DS2MessageReader msg) { */ private void processInvoke(DS2MessageReader msg) { int rid = msg.getRequestId(); - DSMap params = msg.getBodyReader().getMap(); + DSMap params = null; + if (msg.getBodyLength() > 0) { + params = msg.getBodyReader().getMap(); + } DSPermission perm = DSPermission.READ; Object obj = msg.getHeader(MessageConstants.HDR_MAX_PERMISSION); if (obj != null) { perm = DSPermission.valueOf(obj.hashCode()); } + boolean stream = msg.getHeader(MessageConstants.HDR_NO_STREAM) == null; DS2InboundInvoke invokeImpl = new DS2InboundInvoke(params, perm); - invokeImpl.setPath((String) msg.getHeader(HDR_TARGET_PATH)) + invokeImpl.setStream(stream) + .setPath((String) msg.getHeader(HDR_TARGET_PATH)) .setSession(getSession()) .setRequestId(rid) .setResponder(this); @@ -134,8 +140,10 @@ private void processInvoke(DS2MessageReader msg) { private void processList(DS2MessageReader msg) { int rid = msg.getRequestId(); String path = (String) msg.getHeader(HDR_TARGET_PATH); + boolean stream = msg.getHeader(MessageConstants.HDR_NO_STREAM) == null; DS2InboundList listImpl = new DS2InboundList(); - listImpl.setPath(path) + listImpl.setStream(stream) + .setPath(path) .setSession(getSession()) .setRequestId(rid) .setResponder(this); @@ -172,13 +180,17 @@ private void processSet(DS2MessageReader msg) { */ private void processSubscribe(DS2MessageReader msg) { Integer sid = msg.getRequestId(); + //todo if no stream String path = (String) msg.getHeader(HDR_TARGET_PATH); Integer qos = (Integer) msg.getHeader(MessageConstants.HDR_QOS); if (qos == null) { qos = Integer.valueOf(0); } //Integer queueSize = (Integer) msg.getHeader(MessageConstants.HDR_QUEUE_SIZE); - subscriptions.subscribe(sid, path, qos); + DSInboundSubscription sub = subscriptions.subscribe(sid, path, qos); + if (msg.getHeader(MessageConstants.HDR_NO_STREAM) != null) { + sub.setCloseAfterUpdate(true); + } } @Override diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundInvoke.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundInvoke.java index 0a4961a4..38f55b65 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundInvoke.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundInvoke.java @@ -49,6 +49,7 @@ public class DSInboundInvoke extends DSInboundRequest private DSPermission permission; private ActionResult result; private Iterator rows; + private boolean stream = true; private int state = STATE_INIT; private Update updateHead; private Update updateTail; @@ -255,6 +256,14 @@ public void send(DSList row) { enqueueUpdate(new Update(row)); } + /** + * For v2 only, set to false to auto close the stream after sending the initial state. + */ + public DSInboundInvoke setStream(boolean stream) { + this.stream = stream; + return this; + } + @Override public void write(MessageWriter writer) { enqueued = false; @@ -374,7 +383,7 @@ private void writeInitialResults(MessageWriter writer) { } } out.endList(); - if ((result == null) || !result.getAction().getResultType().isOpen()) { + if ((result == null) || !result.getAction().getResultType().isOpen() || !stream) { writeClose(writer); state = STATE_CLOSED; doClose(); diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java index e6636374..f7d92244 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java @@ -61,6 +61,7 @@ public class DSInboundList extends DSInboundRequest private OutboundListResponse response; private boolean enqueued = false; private int state = STATE_INIT; + private boolean stream = true; private Update updateHead; private Update updateTail; @@ -372,7 +373,7 @@ private void encodeTargetMetadata(DSMap metadata, MessageWriter writer) { break; default: cacheBuf.setLength(0); - cacheBuf.append("@"); //TODO ? + cacheBuf.append("$"); cacheBuf.append(encodeName(name)); name = cacheBuf.toString(); @@ -491,13 +492,13 @@ private DSMap fixType(DSMap arg) { arg.put(DSMetadata.EDITOR, cacheBuf.toString()); } } - } else if ("enum".equals(type)) { + } else if ("enum".equals(type) || "string".equals(type)) { DSList range = (DSList) arg.remove(DSMetadata.ENUM_RANGE); if (range == null) { return arg; } cacheBuf.setLength(0); - cacheBuf.append(type); + cacheBuf.append("enum"); cacheBuf.append('['); for (int i = 0, len = range.size(); i < len; i++) { if (i > 0) { @@ -605,6 +606,14 @@ public void run() { } } + /** + * V2 only, set to false to auto close after sending the initial state. + */ + public DSInboundList setStream(boolean stream) { + this.stream = stream; + return this; + } + @Override public void write(MessageWriter writer) { enqueued = false; @@ -634,8 +643,15 @@ public void write(MessageWriter writer) { break; } endUpdates(writer); - if ((state != last) && (state == STATE_UPDATES)) { - endMessage(writer, Boolean.TRUE); + if (state != last) { + if (state == STATE_UPDATES) { + if (stream) { + endMessage(writer, Boolean.TRUE); + } else { + endMessage(writer, Boolean.FALSE); + doClose(); + } + } } else if (isClosePending() && (updateHead == null)) { if (closeReason != null) { getResponder().sendError(this, closeReason); diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscription.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscription.java index dd10c6b5..932e096c 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscription.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscription.java @@ -30,6 +30,7 @@ public class DSInboundSubscription extends DSInboundRequest /////////////////////////////////////////////////////////////////////////// private DSInfo child; + private boolean closeAfterUpdate = false; private SubscriptionCloseHandler closeHandler; private boolean enqueued = false; private DSInboundSubscriptions manager; @@ -113,7 +114,14 @@ protected void init() { } /** - * Called by DSSubcriptions no matter how closed. + * For v2 only. + */ + public boolean isCloseAfterUpdate() { + return closeAfterUpdate; + } + + /** + * Called no matter how closed. */ void onClose() { synchronized (this) { @@ -196,13 +204,13 @@ public void update(long timestamp, DSIValue value, DSStatus quality) { manager.enqueue(this); } - protected DSInboundSubscription setQos(int qos) { - this.qos = qos; + public DSInboundSubscription setCloseAfterUpdate(boolean closeAfterUpdate) { + this.closeAfterUpdate = closeAfterUpdate; return this; } - protected DSInboundSubscription setSubscriptionId(Integer sid) { - this.sid = sid; + protected DSInboundSubscription setQos(int qos) { + this.qos = qos; return this; } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscriptions.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscriptions.java index 46eddfe1..6e567d02 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscriptions.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundSubscriptions.java @@ -74,6 +74,10 @@ DSLink getLink() { return responder.getConnection().getLink(); } + public DSResponder getResponder() { + return responder; + } + /** * This returns a DSInboundSubscription for v1, this will be overridden for v2. * @@ -88,7 +92,7 @@ protected DSInboundSubscription makeSubscription(Integer sid, String path, int q /** * Create or update a subscription. */ - public void subscribe(Integer sid, String path, int qos) { + public DSInboundSubscription subscribe(Integer sid, String path, int qos) { trace(trace() ? "Subscribing " + path : null); DSInboundSubscription subscription = sidMap.get(sid); if (subscription == null) { @@ -97,11 +101,12 @@ public void subscribe(Integer sid, String path, int qos) { pathMap.put(path, subscription); } else if (!path.equals(subscription.getPath())) { unsubscribe(sid); - subscribe(sid, path, qos); + return subscribe(sid, path, qos); } else { subscription.setQos(qos); //TODO refresh subscription, align w/v2 } + return subscription; } /** @@ -130,6 +135,9 @@ public void write(MessageWriter writer) { break; } sub.write(writer, timestampBuffer); + if (sub.isCloseAfterUpdate()) { + unsubscribe(sub.getSubscriptionId()); + } } writeEnd(writer); synchronized (this) { diff --git a/dslink-java-v2-test/src/main/java/org/iot/dsa/dslink/test/MainNode.java b/dslink-java-v2-test/src/main/java/org/iot/dsa/dslink/test/MainNode.java index bb4ffc70..ec9c486c 100644 --- a/dslink-java-v2-test/src/main/java/org/iot/dsa/dslink/test/MainNode.java +++ b/dslink-java-v2-test/src/main/java/org/iot/dsa/dslink/test/MainNode.java @@ -11,6 +11,7 @@ import org.iot.dsa.node.DSJavaEnum; import org.iot.dsa.node.DSList; import org.iot.dsa.node.DSLong; +import org.iot.dsa.node.DSMap; import org.iot.dsa.node.DSNode; import org.iot.dsa.node.DSString; import org.iot.dsa.node.action.ActionInvocation; @@ -72,8 +73,12 @@ protected void declareDefaults() { public ActionResult onInvoke(DSInfo actionInfo, ActionInvocation invocation) { if (actionInfo == this.reset) { put(incrementingInt, DSElement.make(0)); - DSElement arg = invocation.getParameters().get("Arg"); - put("Message", arg); + DSMap map = invocation.getParameters(); + DSElement arg = null; + if (map != null) { + arg = invocation.getParameters().get("Arg"); + put("Message", arg); + } clear(); return null; } else if (actionInfo == this.test) { From 9c5b1f32d7f6ce8402d7156e91a275348d440e7c Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 20 Feb 2018 15:15:43 -0800 Subject: [PATCH 11/11] V2 Protocol fixes. --- .../com/acuity/iot/dsa/dslink/DSSession.java | 21 ++++++++++++++++++ .../protocol_v1/DS1LinkConnection.java | 10 --------- .../protocol/protocol_v2/DS2Session.java | 22 +++++++++++++++---- .../protocol_v2/MessageConstants.java | 2 ++ .../protocol_v2/responder/ErrorMessage.java | 19 +++------------- .../protocol/responder/DSInboundList.java | 6 +++-- dslink-java-v2-test/dslink.json | 2 +- 7 files changed, 49 insertions(+), 33 deletions(-) diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/DSSession.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/DSSession.java index ab1ea9f4..469322b5 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/DSSession.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/DSSession.java @@ -2,6 +2,7 @@ import com.acuity.iot.dsa.dslink.protocol.message.OutboundMessage; import com.acuity.iot.dsa.dslink.transport.DSTransport; +import java.io.IOException; import java.util.LinkedList; import java.util.List; import org.iot.dsa.dslink.DSIRequester; @@ -21,11 +22,14 @@ public abstract class DSSession extends DSNode { /////////////////////////////////////////////////////////////////////////// private static final int MAX_MSG_ID = 2147483647; + private static final long MSG_TIMEOUT = 60000; /////////////////////////////////////////////////////////////////////////// // Fields /////////////////////////////////////////////////////////////////////////// + private long lastRecv; + private long lastSend; private int nextAck = -1; private int nextMessage = 1; private boolean connected = false; @@ -253,10 +257,13 @@ public void setRequesterAllowed() { * implementation. A separate thread is spun off to manage writing. */ public void run() { + lastRecv = lastSend = System.currentTimeMillis(); new WriteThread(getConnection().getLink().getLinkName() + " Writer").start(); while (connected) { try { + verifyLastSend(); doRecvMessage(); + lastRecv = System.currentTimeMillis(); } catch (Exception x) { getTransport().close(); if (connected) { @@ -267,6 +274,18 @@ public void run() { } } + private void verifyLastRead() throws IOException { + if ((System.currentTimeMillis() - lastRecv) > MSG_TIMEOUT) { + throw new IOException("No message received in " + MSG_TIMEOUT + "ms"); + } + } + + private void verifyLastSend() throws IOException { + if ((System.currentTimeMillis() - lastSend) > MSG_TIMEOUT) { + throw new IOException("No message sent in " + MSG_TIMEOUT + "ms"); + } + } + /////////////////////////////////////////////////////////////////////////// // Inner Classes /////////////////////////////////////////////////////////////////////////// @@ -284,6 +303,7 @@ private class WriteThread extends Thread { public void run() { try { while (connected) { + verifyLastRead(); synchronized (outgoingMutex) { if (!hasSomethingToSend()) { try { @@ -295,6 +315,7 @@ public void run() { } } doSendMessage(); + lastSend = System.currentTimeMillis(); } } catch (Exception x) { if (connected) { diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1LinkConnection.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1LinkConnection.java index 365f90ea..df4789a4 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1LinkConnection.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1LinkConnection.java @@ -191,16 +191,6 @@ protected void setTransport(DSTransport transport) { writer = new MsgpackWriter() { @Override public void onComplete() { - /* How to debug - try { - MsgpackReader reader = new MsgpackReader( - new ByteArrayInputStream(byteBuffer.array()); - System.out.println(reader.getMap()); - reader.close(); - } catch (Exception x) { - x.printStackTrace(); - } - */ writeTo(trans); } }; diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java index 8551497c..4ecd6e00 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/DS2Session.java @@ -31,11 +31,11 @@ public class DS2Session extends DSSession implements MessageConstants { /////////////////////////////////////////////////////////////////////////// private boolean debugRecv = false; - private StringBuilder debugRecvTranport = new StringBuilder(); - private StringBuilder debugRecvMessage = new StringBuilder(); + private StringBuilder debugRecvTranport = null; + private StringBuilder debugRecvMessage = null; private boolean debugSend = false; - private StringBuilder debugSendTranport = new StringBuilder(); - private StringBuilder debugSendMessage = new StringBuilder(); + private StringBuilder debugSendTranport = null; + private StringBuilder debugSendMessage = null; private DSInfo lastAckRecv = getInfo(LAST_ACK_RECV); private long lastMessageSent; private DS2MessageReader messageReader; @@ -200,6 +200,20 @@ protected void onStable() { @Override public void onConnect() { super.onConnect(); + messageReader = null; + messageWriter = null; + if (debugRecv) { + debugRecvMessage = new StringBuilder(); + debugRecvTranport = new StringBuilder(); + getMessageReader().setDebug(debugRecvMessage); + getTransport().setDebugIn(debugRecvTranport); + } + if (debugSend) { + debugSendMessage = new StringBuilder(); + debugSendTranport = new StringBuilder(); + getMessageWriter().setDebug(debugSendMessage); + getTransport().setDebugOut(debugSendTranport); + } //requester.onConnect(); responder.onConnect(); } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/MessageConstants.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/MessageConstants.java index c3893e35..1c666775 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/MessageConstants.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/MessageConstants.java @@ -15,6 +15,7 @@ public interface MessageConstants { int HDR_STATUS = 0x0; int HDR_SEQ_ID = 0x01; int HDR_PAGE_ID = 0x02; + int HDR_ERROR_DETAIL = 0x05; int HDR_ALIAS_COUNT = 0x08; int HDR_PRIORITY = 0x10; int HDR_NO_STREAM = 0x11; @@ -58,6 +59,7 @@ public interface MessageConstants { Byte STS_INVALID_MESSAGE = 0x44; Byte STS_INVALID_PARAMETER = 0x45; Byte STS_BUSY = 0x48; + Byte STS_INTERNAL_ERR = 0x50; Byte STS_ALIAS_LOOP = 0x61; Byte STS_INVALID_AUTH = (byte) (0xF9 & 0xFF); diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/ErrorMessage.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/ErrorMessage.java index 4b18f0b7..7d7a228c 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/ErrorMessage.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/ErrorMessage.java @@ -9,6 +9,7 @@ import org.iot.dsa.dslink.DSInvalidPathException; import org.iot.dsa.dslink.DSPermissionException; import org.iot.dsa.dslink.DSRequestException; +import org.iot.dsa.util.DSException; /** * Responder uses to close streams without errors. @@ -25,20 +26,6 @@ public ErrorMessage(DSInboundRequest req, Throwable reason) { this.reason = reason; } - /* - private String toString(Throwable arg) { - String msg = arg.getMessage(); - if ((msg != null) && (msg.length() > 0)) { - return msg; - } - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - arg.printStackTrace(pw); - pw.close(); - return sw.toString(); - } - */ - @Override public void write(MessageWriter writer) { DS2MessageWriter out = (DS2MessageWriter) writer; @@ -64,9 +51,9 @@ public void write(MessageWriter writer) { MessageConstants.STS_INVALID_MESSAGE); } } else { - //todo need server error - out.addHeader((byte) MessageConstants.HDR_STATUS, MessageConstants.STS_INVALID_MESSAGE); + out.addHeader((byte) MessageConstants.HDR_STATUS, MessageConstants.STS_INTERNAL_ERR); } + out.addHeader((byte) HDR_ERROR_DETAIL, DSException.makeMessage(reason)); out.write((DSBinaryTransport) req.getResponder().getTransport()); } diff --git a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java index f7d92244..5f940c50 100644 --- a/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundList.java @@ -314,6 +314,7 @@ private void encodeTargetAction(ApiObject object, MessageWriter writer) { DSMap param; while (params.hasNext()) { param = params.next(); + fixRange(param); if (dsAction != null) { dsAction.prepareParameter(info, param); } @@ -336,6 +337,7 @@ private void encodeTargetAction(ApiObject object, MessageWriter writer) { DSMap param; while (cols.hasNext()) { param = cols.next(); + fixRange(param); if (dsAction != null) { dsAction.prepareParameter(info, param); } @@ -415,7 +417,7 @@ private DSElement encodeType(DSIValue value, DSMetadata meta) { } } } - fixType(meta.getMap()); + fixRange(meta.getMap()); DSElement e = cacheMap.remove(DSMetadata.TYPE); if (e == null) { throw new IllegalArgumentException("Missing type"); @@ -472,7 +474,7 @@ private void enqueueResponse() { /** * Combines boolean and enum ranges into the type name. */ - private DSMap fixType(DSMap arg) { + private DSMap fixRange(DSMap arg) { String type = arg.getString(DSMetadata.TYPE); if ("bool".equals(type)) { DSList range = (DSList) arg.remove(DSMetadata.BOOLEAN_RANGE); diff --git a/dslink-java-v2-test/dslink.json b/dslink-java-v2-test/dslink.json index 85f264f1..56c5ff13 100644 --- a/dslink-java-v2-test/dslink.json +++ b/dslink-java-v2-test/dslink.json @@ -10,7 +10,7 @@ "value": "org.iot.dsa.dslink.test.MainNode" }, "log": { - "desc": "all, trace, debug, info, warn, sys, error, admin, fatal, none", + "desc": "all, trace, debug, fine, warn, info, error, admin, fatal, none", "type": "enum", "value": "debug" },