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..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,9 +2,9 @@ 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 java.util.logging.Logger; import org.iot.dsa.dslink.DSIRequester; import org.iot.dsa.dslink.DSLinkConnection; import org.iot.dsa.node.DSNode; @@ -17,13 +17,23 @@ */ public abstract class DSSession extends DSNode { + /////////////////////////////////////////////////////////////////////////// + // Constants + /////////////////////////////////////////////////////////////////////////// + + 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; private DSLinkConnection connection; - private Logger logger; private Object outgoingMutex = new Object(); private List outgoingRequests = new LinkedList(); private List outgoingResponses = new LinkedList(); @@ -127,11 +137,28 @@ public DSLinkConnection getConnection() { } @Override - public Logger getLogger() { - if (logger == null) { - logger = Logger.getLogger(getConnection().getLink().getLinkName() + ".session"); + protected String getLogName() { + return "Session"; + } + + /** + * 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 logger; + return ret; } public abstract DSIRequester getRequester(); @@ -140,17 +167,8 @@ public DSTransport getTransport() { return getConnection().getTransport(); } - /** - * True if there are any outbound requests or responses queued up. - */ - protected final boolean hasMessagesToSend() { - if (!outgoingResponses.isEmpty()) { - return true; - } - if (!outgoingRequests.isEmpty()) { - return true; - } - return false; + protected boolean hasAckToSend() { + return nextAck > 0; } protected boolean hasOutgoingRequests() { @@ -165,7 +183,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() { @@ -206,6 +233,16 @@ public void onDisconnect() { } } + /** + * Call for each incoming message id that needs to be acked. + */ + public synchronized void setNextAck(int nextAck) { + if (nextAck > 0) { + this.nextAck = nextAck; + notifyOutgoing(); + } + } + /** * Called when the broker signifies that requests are allowed. */ @@ -220,20 +257,35 @@ 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) { connected = false; - severe(getPath(), x); + error(getPath(), x); } } } } + 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 /////////////////////////////////////////////////////////////////////////// @@ -251,23 +303,25 @@ private class WriteThread extends Thread { public void run() { try { while (connected) { + verifyLastRead(); synchronized (outgoingMutex) { if (!hasSomethingToSend()) { try { outgoingMutex.wait(5000); } catch (InterruptedException x) { - fine(getPath(), x); + warn(getPath(), x); } continue; } } doSendMessage(); + lastSend = System.currentTimeMillis(); } } catch (Exception x) { 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 deb2963f..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 @@ -44,9 +48,10 @@ public int available() { return length; } - public void clear() { + public DSByteBuffer clear() { length = 0; offset = 0; + return this; } /** @@ -71,85 +76,53 @@ public int length() { } /** - * Overwrites bytes in the internal buffer, does not change the current length or position. + * Prints the current contents of the buffer, doesn't modify it in any way. */ - 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)); + 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); } - } - - /** - * 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)); + if (buf.length() > 0) { + out.println(buf.toString()); } } /** + * /** * 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 +135,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 +156,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 +179,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 +207,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 +215,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 +226,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 +238,7 @@ public void put(byte[] msg, int off, int len) { } System.arraycopy(msg, off, buffer, length + offset, len); length += len; + return this; } /** @@ -270,7 +249,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 +265,32 @@ public void put(int dest, byte[] msg, int off, int len) { if ((dest + len) > length) { length += 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. */ - public void putDouble(double v) { - putDouble(v, true); + public DSByteBuffer putDouble(double v) { + return putDouble(v, true); } /** @@ -301,15 +299,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 +316,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 +333,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 +359,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 +393,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 +413,94 @@ 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 (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 (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 +570,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/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/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/DS1ConnectionInit.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1ConnectionInit.java index 524e98aa..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(); @@ -245,8 +232,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"); @@ -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 6d0a29a1..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 @@ -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; } @@ -173,7 +173,7 @@ protected void onRun() { @Override protected void onStable() { - this.link = (DSLink) getParent(); + this.link = getLink(); super.onStable(); } @@ -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_v1/DS1Session.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v1/DS1Session.java index 1ae3996e..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 @@ -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)); } } @@ -146,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(); } @@ -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; } @@ -297,20 +286,20 @@ 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(); } while (next != END_MAP); if (sendAck && (msg >= 0)) { - sendAck(msg); + setNextAck(msg); } } @@ -333,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) { @@ -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/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 d324c54d..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 @@ -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; @@ -87,6 +87,7 @@ public OutboundListHandler list(String path, OutboundListHandler req) { public void onConnect() { subscriptions.onConnect(); + session.setRequesterAllowed(); } public void onConnectFail() { @@ -137,7 +138,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..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 @@ -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,24 +82,23 @@ 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; case 'r': //remove if (method.equals("remove")) { //Does this even make sense in a link? - severe("Remove method called"); - sendResponse( - new CloseMessage(rid).setMethod(null).setStream("closed")); + error("Remove method called"); + 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)); + error(getPath(), x); + sendError(rid, x); } } @@ -148,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(); @@ -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); + error(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; + error(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/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/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..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 @@ -1,12 +1,11 @@ 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; 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; import org.iot.dsa.dslink.DSLink; @@ -46,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 @@ -72,6 +72,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) @@ -131,24 +132,30 @@ protected void makeTransport() { } catch (Exception x) { DSException.throwRuntime(x); } - } else { + } 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); - transport.setReadTimeout(getLink().getConfig().getConfig( - DSLinkConfig.CFG_READ_TIMEOUT, 60000)); } @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); } @@ -197,10 +204,16 @@ private void recvF1() throws IOException { //TODO check for header status put(brokerDsId, DSString.valueOf(reader.readString(in))); 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)); 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)); } @@ -213,9 +226,11 @@ private void recvF3() throws IOException { Integer.toHexString(reader.getMethod())); } //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 + 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]; @@ -229,7 +244,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,25 +255,26 @@ 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()); 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/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 7b0aca08..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 @@ -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; @@ -18,7 +20,7 @@ * * @author Aaron Hansen */ -public class DS2MessageReader implements MessageConstants { +public class DS2MessageReader extends DS2Message { // Fields // ------ @@ -26,9 +28,10 @@ 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; private int requestId; private ByteBuffer strBuffer; private CharsetDecoder utf8decoder = DSString.UTF8.newDecoder(); @@ -42,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; } @@ -50,6 +71,13 @@ public InputStream getBody() { return input; } + public DSIReader getBodyReader() { + if (reader == null) { + reader = new MsgpackReader(input); + } + return reader; + } + public int getBodyLength() { return bodyLength; } @@ -71,11 +99,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; @@ -83,7 +111,7 @@ public Object getHeader(Byte key, Object def) { return ret; } - public Map getHeaders() { + public Map getHeaders() { return headers; } @@ -129,14 +157,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); @@ -144,12 +177,22 @@ 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: case MSG_INVOKE_REQ: case MSG_LIST_REQ: case MSG_OBSERVE_REQ: + case MSG_SUBSCRIBE_REQ: + case MSG_SET_REQ: return true; } return false; @@ -159,33 +202,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; 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..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 @@ -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.ByteOrder; import java.nio.CharBuffer; import java.nio.charset.CharsetEncoder; +import org.iot.dsa.io.msgpack.MsgpackWriter; import org.iot.dsa.node.DSString; /** @@ -15,26 +17,28 @@ * * @author Aaron Hansen */ -public class DS2MessageWriter implements MessageConstants { +public class DS2MessageWriter extends DS2Message implements MessageWriter { // Fields // ------ - private ByteBuffer body; + private int ackId = -1; + private DSByteBuffer body; private CharBuffer charBuffer; - private ByteBuffer header; - private byte method; + private DSByteBuffer header; + private int method; + private int requestId = -1; private ByteBuffer strBuffer; private CharsetEncoder utf8encoder; + private MsgpackWriter writer; // Constructors // ------------ 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(); + writer = new MsgpackWriter(body); utf8encoder = DSString.UTF8.newEncoder(); init(-1, -1); } @@ -53,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; @@ -64,7 +68,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; } @@ -77,20 +81,36 @@ public DS2MessageWriter addHeader(byte key, String value) { return this; } - 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); + 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); + } } - public int getBodyLength() { - return body.position(); + 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, (byte) method); } - public int getHeaderLength() { - return header.position(); + public DSByteBuffer getBody() { + return body; + } + + public int getBodyLength() { + return body.length(); } /** @@ -119,6 +139,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. @@ -142,30 +166,38 @@ private ByteBuffer getStringBuffer(int len) { return strBuffer; } - public ByteBuffer getBody() { - return body; + @Override + public MsgpackWriter getWriter() { + return writer; } /** * 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.position(7); - if (requestId >= 0) { - header.putInt(requestId); - if (ackId >= 0) { - header.putInt(requestId); + header.skip(7); + 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; } @@ -174,17 +206,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); - encodeHeaderLengths(); - header.flip(); - while (header.hasRemaining()) { - out.write(header.get()); - } - body.flip(); - while (body.hasRemaining()) { - out.write(body.get()); - } + ByteArrayOutputStream out = new ByteArrayOutputStream(header.length() + body.length()); + finishHeader(); + header.sendTo(out); + body.sendTo(out); return out.toByteArray(); } @@ -192,96 +217,37 @@ public byte[] toByteArray() { * Writes the message to the transport. */ public DS2MessageWriter write(DSBinaryTransport out) { - encodeHeaderLengths(); - //TODO out.write(header, false); - //TODO out.write(body, true); + finishHeader(); + if (isDebug()) { + debugSummary(); + } + header.sendTo(out, false); + body.sendTo(out, true); return this; } /** - * DSA 2.n encodes a string into the the given buffer. + * DSA 2.n encodes a string into the body. */ - public void writeString(CharSequence str, ByteBuffer buf) { + public void writeString(CharSequence str) { 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.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)); + body.putShort((short) strBuffer.position(), false); + body.put(strBuffer); } /** - * 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. + * DSA 2.n encodes a string into the the given 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 writeString(CharSequence str, DSByteBuffer buf) { + CharBuffer chars = getCharBuffer(str); + ByteBuffer strBuffer = getStringBuffer( + chars.position() * (int) utf8encoder.maxBytesPerChar()); + utf8encoder.encode(chars, strBuffer, false); + buf.putShort((short) strBuffer.position(), false); + buf.put(strBuffer); } } 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..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 @@ -1,11 +1,13 @@ 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 java.io.IOException; +import com.acuity.iot.dsa.dslink.transport.DSTransport; import org.iot.dsa.dslink.DSIRequester; +import org.iot.dsa.node.DSBytes; import org.iot.dsa.node.DSInfo; import org.iot.dsa.node.DSInt; @@ -20,23 +22,24 @@ 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 String LAST_ACK_SENT = "Last Ack Sent"; - - static final int MAX_MSG_ID = 2147483647; static final int MAX_MSG_IVL = 45000; /////////////////////////////////////////////////////////////////////////// // Fields /////////////////////////////////////////////////////////////////////////// + private boolean debugRecv = false; + private StringBuilder debugRecvTranport = null; + private StringBuilder debugRecvMessage = null; + private boolean debugSend = false; + private StringBuilder debugSendTranport = null; + private StringBuilder debugSendMessage = null; 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 = new DS2Responder(this); @@ -59,42 +62,89 @@ 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 - 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; } - messageReader.init(getTransport().getInput()); - int ack = messageReader.getAckId(); + if (debug) { + debugRecvMessage.setLength(0); + debugRecvTranport.setLength(0); + debugRecvTranport.append("Bytes read\n"); + } + 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(); - long endTime = System.currentTimeMillis() + 2000; - requestsNext = !requestsNext; - transport.beginMessage(); - if (hasMessagesToSend()) { - send(requestsNext, endTime); - if ((System.currentTimeMillis() < endTime) && !shouldEndMessage()) { - send(!requestsNext, endTime); + 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 @@ -102,6 +152,20 @@ 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(); + } + return messageWriter; + } + @Override public DSIRequester getRequester() { return requester; @@ -119,9 +183,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,39 +200,75 @@ protected void onStable() { @Override public void onConnect() { super.onConnect(); - requester.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(); } @Override public void onConnectFail() { super.onConnectFail(); - requester.onConnectFail(); + //requester.onConnectFail(); responder.onConnectFail(); } @Override public void onDisconnect() { super.onDisconnect(); - requester.onDisconnect(); - //responder.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 boolean 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); + } 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 - //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/MessageConstants.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/MessageConstants.java index e418193b..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 @@ -12,37 +12,41 @@ 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_ERROR_DETAIL = 0x05; + 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; @@ -55,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/PingMessage.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/PingMessage.java new file mode 100644 index 00000000..766039ea --- /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(MSG_PING); + out.write(session.getTransport()); + } + +} 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..e4566d6d --- /dev/null +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundInvoke.java @@ -0,0 +1,51 @@ +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 + } + + @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/DS2InboundList.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundList.java index a1f10ef9..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,29 +1,112 @@ 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.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. * * @author Aaron Hansen */ -class DS2InboundList extends DSInboundList { +class DS2InboundList extends DSInboundList implements MessageConstants { @Override - protected ErrorResponse makeError(Throwable reason) { - ErrorResponse ret = new ErrorResponse(reason); - //TODO - return ret; + 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) { - //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..3018cacb --- /dev/null +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSet.java @@ -0,0 +1,22 @@ +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 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..76734d1c --- /dev/null +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscription.java @@ -0,0 +1,61 @@ +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.DSIWriter; +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 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); + int start = byteBuffer.length(); + dsiWriter.beginMap(); + buf.setLength(0); + DSTime.encode(update.timestamp, true, buf); + dsiWriter.key("timestamp").value(buf.toString()); + if (!update.quality.isOk()) { + dsiWriter.key("status").value(update.quality.toElement()); + } + 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 new file mode 100644 index 00000000..169f40f4 --- /dev/null +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/DS2InboundSubscriptions.java @@ -0,0 +1,51 @@ +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) { + DSInboundSubscription ret = new DS2InboundSubscription(this, sid, path, qos); + ret.setResponder(getResponder()); + ret.setSession(getResponder().getSession()); + return ret; + } + + @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..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 @@ -1,28 +1,34 @@ 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.DSInboundSubscription; 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.DSBytes; +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 +42,25 @@ public DS2Responder(DS2Session session) { // Methods - In alphabetical order by method name. ///////////////////////////////////////////////////////////////// + public DSBinaryTransport getTransport() { + return (DSBinaryTransport) getConnection().getTransport(); + } + + @Override + public boolean isV1() { + return false; + } + /** * 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 +68,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 +84,55 @@ 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); + error(getPath(), 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 = 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.setStream(stream) + .setPath((String) msg.getHeader(HDR_TARGET_PATH)) + .setSession(getSession()) + .setRequestId(rid) + .setResponder(this); + putRequest(rid, invokeImpl); + DSRuntime.run(invokeImpl); + } /** * Handles a list request. @@ -98,8 +140,10 @@ private void processInvoke(Integer rid, DSMap req) { 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); @@ -109,68 +153,55 @@ 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()); + } + 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)) + .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(); + //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); + DSInboundSubscription sub = subscriptions.subscribe(sid, path, qos); + if (msg.getHeader(MessageConstants.HDR_NO_STREAM) != null) { + sub.setCloseAfterUpdate(true); + } + } - /** - * 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..7d7a228c --- /dev/null +++ b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/protocol_v2/responder/ErrorMessage.java @@ -0,0 +1,60 @@ +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; +import org.iot.dsa.util.DSException; + +/** + * 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; + } + + @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((byte) MessageConstants.HDR_STATUS, + MessageConstants.STS_NOT_AVAILABLE); + } else if (reason instanceof DSPermissionException) { + out.addHeader((byte) MessageConstants.HDR_STATUS, + MessageConstants.STS_PERMISSION_DENIED); + } else { + out.addHeader((byte) MessageConstants.HDR_STATUS, + MessageConstants.STS_INVALID_MESSAGE); + } + } else { + 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/protocol_v1/responder/DS1InboundInvoke.java b/dslink-core/src/main/java/com/acuity/iot/dsa/dslink/protocol/responder/DSInboundInvoke.java similarity index 83% 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..38f55b65 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 { /////////////////////////////////////////////////////////////////////////// @@ -50,6 +49,7 @@ class DS1InboundInvoke extends DS1InboundRequest private DSPermission permission; private ActionResult result; private Iterator rows; + private boolean stream = true; private int state = STATE_INIT; private Update updateHead; private Update updateTail; @@ -58,11 +58,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; } /////////////////////////////////////////////////////////////////////////// @@ -125,7 +123,7 @@ public void run() { try { result.onClose(); } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); } } }); @@ -163,36 +161,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 +174,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)); @@ -230,7 +205,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; } @@ -246,56 +221,110 @@ 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) { + error(getPath(), x); + close(x); + return; + } + if (result == null) { + close(); + } else { + enqueueResponse(); + } + } + @Override 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) { - 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 +354,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 +372,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()) { @@ -353,17 +383,25 @@ private void writeInitialResults(DSIWriter out) { } } out.endList(); - if ((result == null) || !result.getAction().getResultType().isOpen()) { - out.key("stream").value("closed"); + if ((result == null) || !result.getAction().getResultType().isOpen() || !stream) { + 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 +413,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 +427,7 @@ private void writeUpdates(DSIWriter out) { if ((updateHead == null) || (updateHead.type != null)) { break; } - if (session.shouldEndMessage()) { + if (responder.shouldEndMessage()) { enqueueResponse(); break; } @@ -404,7 +442,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 +461,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..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 @@ -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; @@ -11,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; @@ -21,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; @@ -33,7 +33,7 @@ * * @author Aaron Hansen */ -public abstract class DSInboundList extends DSInboundRequest +public class DSInboundList extends DSInboundRequest implements DSISubscriber, DSStream, InboundListRequest, OutboundMessage, OutboundListResponse, Runnable { @@ -61,17 +61,28 @@ public abstract 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; - /////////////////////////////////////////////////////////////////////////// - // Constructors - /////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////// // 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()) { @@ -138,77 +149,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 { @@ -216,42 +260,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; @@ -262,142 +301,141 @@ 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; while (params.hasNext()) { param = params.next(); + fixRange(param); 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.getParameters(); - 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(); + fixRange(param); 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("$"); + 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()); + fixRange(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(); } } @@ -436,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); @@ -450,15 +488,19 @@ 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)) { + } 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) { @@ -467,7 +509,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; } @@ -521,6 +567,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 { @@ -538,7 +597,7 @@ public void run() { response = this; } } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); close(x); return; } @@ -549,74 +608,71 @@ 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(); + /** + * 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) { - DSIWriter out = writer.getWriter(); enqueued = false; if (isClosed()) { return; } if (isClosePending() && (updateHead == null) && (closeReason != null)) { - ErrorResponse res = makeError(closeReason); - res.write(writer); + getResponder().sendError(this, closeReason); doClose(); 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(); - if ((state != last) && (state == STATE_UPDATES)) { - out.key("stream").value("open"); + endUpdates(writer); + 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().sendResponse(makeError(closeReason)); + 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(); @@ -629,9 +685,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(); @@ -643,16 +698,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(); } } @@ -660,11 +717,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 8ec29dbf..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 @@ -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; @@ -28,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; } @@ -47,8 +54,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 70% 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..a0c87616 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); + error(getPath(), x); + 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 73% 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..932e096c 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,8 +1,7 @@ -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; import org.iot.dsa.dslink.DSIResponder; import org.iot.dsa.dslink.responder.InboundSubscribeRequest; import org.iot.dsa.dslink.responder.SubscriptionCloseHandler; @@ -23,7 +22,7 @@ * * @author Aaron Hansen */ -class DS1InboundSubscription extends DS1InboundRequest +public class DSInboundSubscription extends DSInboundRequest implements DSISubscriber, InboundSubscribeRequest { /////////////////////////////////////////////////////////////////////////// @@ -31,9 +30,10 @@ class DS1InboundSubscription extends DS1InboundRequest /////////////////////////////////////////////////////////////////////////// private DSInfo child; + private boolean closeAfterUpdate = false; 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 +45,8 @@ 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; } @@ -81,11 +82,6 @@ private synchronized Update dequeue() { return ret; } - @Override - public Logger getLogger() { - return manager.getLogger(); - } - /** * Unique subscription id for this path. */ @@ -94,7 +90,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(); @@ -118,7 +114,14 @@ private 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) { @@ -132,14 +135,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); } } @@ -172,8 +175,8 @@ public void update(long timestamp, DSIValue value, DSStatus quality) { if (!open) { return; } - finest(finest() ? "Update " + getPath() + " to " + value : null); - if (qos <= 1) { + trace(trace() ? "Update " + getPath() + " to " + value : null); + if (qos == 0) { synchronized (this) { if (updateHead == null) { updateHead = updateTail = new Update(); @@ -201,13 +204,13 @@ public void update(long timestamp, DSIValue value, DSStatus quality) { manager.enqueue(this); } - DS1InboundSubscription setQos(int qos) { - this.qos = qos; + public DSInboundSubscription setCloseAfterUpdate(boolean closeAfterUpdate) { + this.closeAfterUpdate = closeAfterUpdate; return this; } - DS1InboundSubscription setSubscriptionId(Integer sid) { - this.sid = sid; + protected DSInboundSubscription setQos(int qos) { + this.qos = qos; return this; } @@ -216,34 +219,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 +248,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 55% 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..6e567d02 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,21 +1,19 @@ -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; 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.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 @@ -28,21 +26,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 +50,7 @@ class DS1InboundSubscriptions extends DSLogger implements OutboundMessage { /** * Unsubscribes all. */ - void close() { + public void close() { for (Integer i : sidMap.keySet()) { unsubscribe(i); } @@ -62,7 +59,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) { @@ -77,41 +74,48 @@ DSLink getLink() { return responder.getConnection().getLink(); } - @Override - public Logger getLogger() { - if (logger == null) { - logger = Logger.getLogger(responder.getConnection().getLink().getLinkName() - + ".responderSubscriptions"); - } - return logger; + public DSResponder getResponder() { + return responder; + } + + /** + * 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) { - finest(finest() ? "Subscribing " + path : null); - DS1InboundSubscription subscription = sidMap.get(sid); + public DSInboundSubscription subscribe(Integer sid, String path, int qos) { + trace(trace() ? "Subscribing " + path : null); + 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())) { unsubscribe(sid); - subscribe(sid, path, qos); + return subscribe(sid, path, qos); } else { subscription.setQos(qos); //TODO refresh subscription, align w/v2 } + return subscription; } /** * 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); + trace(trace() ? "Unsubscribing " + subscription.getPath() : null); pathMap.remove(subscription.getPath()); try { subscription.onClose(); @@ -123,21 +127,19 @@ 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); + if (sub.isCloseAfterUpdate()) { + unsubscribe(sub.getSubscriptionId()); + } } - out.endList(); - out.endMap(); - timestampBuffer.setLength(0); + writeEnd(writer); synchronized (this) { if (outbound.isEmpty()) { enqueued = false; @@ -147,4 +149,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..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 @@ -3,9 +3,9 @@ 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; import org.iot.dsa.dslink.DSLink; import org.iot.dsa.dslink.DSLinkConnection; import org.iot.dsa.node.DSNode; @@ -28,10 +28,8 @@ 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; - //private DS2InboundSubscriptions subscriptions = new DSInboundSubscriptions(this); ///////////////////////////////////////////////////////////////// // Methods - Constructors @@ -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() { @@ -76,6 +70,17 @@ public DSSession getSession() { return session; } + public DSTransport getTransport() { + return getConnection().getTransport(); + } + + /** + * V2 override point, this returns true. + */ + public boolean isV1() { + return true; + } + public void onConnect() { } @@ -93,6 +98,10 @@ 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/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/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 07b2297b..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,6 +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; /** @@ -17,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; @@ -27,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; } @@ -41,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; @@ -57,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 @@ -89,7 +107,24 @@ public DSTransport open() { throw new IllegalStateException("Already open"); } try { - socket = new Socket(getConnectionUrl(), 443); + String url = getConnectionUrl(); + 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); + } socket.setSoTimeout((int) getReadTimeout()); open = true; fine(fine() ? "SocketTransport open" : null); @@ -104,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(); @@ -114,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 fe9b48fc..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 @@ -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,11 @@ 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 +82,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 +91,7 @@ public DSLinkConfig getConfig() { } public DSLinkConnection getConnection() { - return connection; + return getSys().getConnection(); } /** @@ -135,21 +125,19 @@ public String getLinkName() { return name; } - /** - * The logger as defined in dslink.json. - */ @Override - public Logger getLogger() { - return logger; + protected String getLogName() { + return getClass().getSimpleName(); } - /** - * 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 +148,7 @@ 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); - } + getSys().init(); return this; } @@ -217,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); @@ -245,22 +212,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. @@ -314,7 +265,7 @@ public void run() { } } } catch (Exception x) { - severe(getLinkName(), x); + error(getLinkName(), x); stop(); DSException.throwRuntime(x); } @@ -393,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 a03ac708..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 @@ -21,15 +22,11 @@ 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; @@ -70,6 +66,7 @@ public DSLinkConfig() { */ public DSLinkConfig(File workingDir) { this.workingDir = workingDir; + setDslinkJson(new File(workingDir, "dslink.json")); } /** @@ -77,6 +74,7 @@ public DSLinkConfig(File workingDir) { * character. */ public DSLinkConfig(String args) { + setDslinkJson(new File("dslink.json")); parse(args.split(" +")); } @@ -84,6 +82,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); } @@ -189,6 +188,21 @@ private DSMap getConfigMap(String name, boolean create) { return tmp; } + 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().get("dsa-version", "2.0"); + } + return dsaVersion; + } + /** * If not set, this will attempt to open dslink.json in the working the process directory. */ @@ -236,16 +250,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 +276,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 +298,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 +342,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 +362,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,30 +469,37 @@ 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. */ 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); } @@ -523,22 +519,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..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,8 +2,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; @@ -23,7 +21,6 @@ public abstract class DSLinkConnection extends DSNode { private boolean connected = false; private String connectionId; private ConcurrentHashMap listeners; - private Logger logger; // Methods // ------- @@ -43,7 +40,7 @@ public void addListener(Listener listener) { try { listener.onConnect(this); } catch (Exception x) { - severe(getPath(), x); + error(getPath(), x); } } } @@ -86,15 +83,16 @@ public String getConnectionId() { * The link using this connection. */ public DSLink getLink() { - return (DSLink) getParent(); + return (DSLink) getSys().getParent(); } @Override - public Logger getLogger() { - if (logger == null) { - logger = Logger.getLogger(getLink().getLinkName() + ".connection"); - } - return logger; + protected String getLogName() { + return getClass().getSimpleName(); + } + + public DSSysNode getSys() { + return (DSSysNode) getParent(); } public abstract DSIRequester getRequester(); @@ -176,48 +174,43 @@ 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; } - 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) { + error(listener.toString(), x); } } - }, 1000); + } try { onRun(); 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 c24d70b6..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; /** @@ -12,20 +11,29 @@ public class DSMainNode extends DSNode { /** - * Creates a child logger of the link. + * The parent link or null. */ - protected Logger getLogger(String name) { - if (name.startsWith(".")) { - return Logger.getLogger(getLink().getLinkName() + name); - } - return Logger.getLogger(getLink().getLinkName() + '.' + name); + public DSLink getLink() { + return (DSLink) getParent(); + } + + @Override + protected String getLogName() { + return "Main"; } /** - * The parent link or null. + * Override point, returns true by default. */ - public DSLink getLink() { - return (DSLink) getParent(); + public boolean isRequester() { + return true; + } + + /** + * Override point, returns true by default. + */ + public boolean isResponder() { + return true; } /** 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..876cbcc2 --- /dev/null +++ b/dslink-core/src/main/java/org/iot/dsa/dslink/DSSysNode.java @@ -0,0 +1,89 @@ +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 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.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(); + } + + @Override + protected String getLogName() { + return "Sys"; + } + + void init() { + DSLinkConfig config = getLink().getConfig(); + try { + String ver = config.getDsaVersion(); + DSLinkConnection conn; + if (ver.startsWith("1")) { + String type = config.getConfig(DSLinkConfig.CFG_CONNECTION_TYPE, null); + if (type != null) { + fine(fine() ? "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-core/src/main/java/org/iot/dsa/io/AbstractWriter.java b/dslink-core/src/main/java/org/iot/dsa/io/AbstractWriter.java index c8b35a2f..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,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..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 @@ -31,6 +31,10 @@ public class MsgpackWriter extends AbstractWriter implements MsgpackConstants { public MsgpackWriter() { } + public MsgpackWriter(DSByteBuffer buffer) { + this.byteBuffer = buffer; + } + // Methods // ------- @@ -340,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) { @@ -373,7 +388,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/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 68ad2af7..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 @@ -17,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:"; @@ -61,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. */ @@ -299,6 +313,41 @@ 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. + * + * @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] & 0x0F); + buf.append(HEX[val & 0x0F]); + } + return buf; + } + @Override public String toString() { if (isNull()) { @@ -319,6 +368,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/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 4d71aea2..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,19 +163,46 @@ 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; } boolean modified = false; int pathLength = name.length(); - CharArrayWriter charArrayWriter = new CharArrayWriter(); + CharArrayWriter charArrayWriter = null; char c; + boolean encode = false; for (int i = 0; i < pathLength; ) { c = name.charAt(i); - if (!shouldEncode(c)) { - buf.append((char) c); + encode = v1 ? shouldEncodeV1(c, i) : shouldEncode(c, i); + if (!encode) { + buf.append(c); i++; } else { + if (charArrayWriter == null) { + charArrayWriter = new CharArrayWriter(); + } do { charArrayWriter.write(c); if (c >= 0xD800 && c <= 0xDBFF) { @@ -185,7 +215,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 +267,44 @@ public String[] getPathElements() { /** * Returns true for characters that should be encoded. */ - public static boolean shouldEncode(int ch) { + private 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; + } + } + + /** + * 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; } } @@ -286,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/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-core/src/main/java/org/iot/dsa/security/DSPermission.java b/dslink-core/src/main/java/org/iot/dsa/security/DSPermission.java index 4c437189..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 @@ -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/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-core/src/test/java/org/iot/dsa/dslink/V2HandshakeTest.java b/dslink-core/src/test/java/org/iot/dsa/dslink/V2HandshakeTest.java index 2f6c26b6..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 @@ -1,16 +1,15 @@ 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; 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; @@ -26,14 +25,6 @@ public class V2HandshakeTest { private final static char[] HEXCHARS = "0123456789abcdef".toCharArray(); - /////////////////////////////////////////////////////////////////////////// - // Fields - /////////////////////////////////////////////////////////////////////////// - - /////////////////////////////////////////////////////////////////////////// - // Constructors - /////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////// // Methods /////////////////////////////////////////////////////////////////////////// @@ -86,7 +77,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 +104,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 +151,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 +182,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 +200,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 +214,4 @@ public static String toHexFromBytes(byte[] bytes) { return DatatypeConverter.printHexBinary(bytes); } - /////////////////////////////////////////////////////////////////////////// - // Inner Classes - /////////////////////////////////////////////////////////////////////////// - - /////////////////////////////////////////////////////////////////////////// - // Initialization - /////////////////////////////////////////////////////////////////////////// - } diff --git a/dslink-java-v2-test/dslink.json b/dslink-java-v2-test/dslink.json index 9a9fbcf3..56c5ff13 100644 --- a/dslink-java-v2-test/dslink.json +++ b/dslink-java-v2-test/dslink.json @@ -1,30 +1,18 @@ { "name": "dslink-java-v2-test", "version": "1.0.0", + "dsa-version": "2.0", "description": "Testing Link", "main": "bin/dslink-java-v2-test", "configs": { - "mainType": { - "desc1": "*************** THIS IS REQUIRED AND MUST BE CHANGED ***************", - "desc2": "Fully qualified class name of the root node of the link.", + "handler_class": { "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", + "desc": "all, trace, debug, fine, warn, info, 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 d2f918d4..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,11 +11,13 @@ 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; 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 +39,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,14 +62,23 @@ 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); + declareDefault("T./,<>?;:'\"[%]{/}bc", DSString.valueOf("abc")).setTransient(true); } @Override 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) { @@ -77,6 +89,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); } 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) {