[IOT-1616] CSM signaling supported in CoAP D2S communication
authoryeonghun.nam <yeonghun.nam@samsung.com>
Mon, 12 Dec 2016 06:47:24 +0000 (15:47 +0900)
committerJee Hyeok Kim <jihyeok13.kim@samsung.com>
Wed, 18 Jan 2017 11:52:17 +0000 (11:52 +0000)
- 7.01 CSM implementation (based on draft-ietf-core-coap-tcp-tls-05)
- 4.02 bad option response when unrecognized critical option is included in the message
- unrecognized request method throws 4.05 Method Not Allowed, and unrecognized response throws 4.00 Bad Request

Change-Id: I1fe6815f93f373086b00bd1381d86c9bee86c656
Signed-off-by: yeonghun.nam <yeonghun.nam@samsung.com>
Reviewed-on: https://gerrit.iotivity.org/gerrit/15459
Tested-by: jenkins-iotivity <jenkins-iotivity@opendaylight.org>
Reviewed-by: Jee Hyeok Kim <jihyeok13.kim@samsung.com>
(cherry picked from commit 8e85103153f9e624e7206b3afd7fa1d5db67bcf4)
Reviewed-on: https://gerrit.iotivity.org/gerrit/16091
Tested-by: jenkins-iotivity <jenkins@iotivity.org>
13 files changed:
cloud/interface/src/main/java/org/iotivity/cloud/ciserver/DeviceServerSystem.java
cloud/interface/src/test/java/org/iotivity/cloud/ciserver/DeviceServerSystemTest.java
cloud/stack/src/main/java/org/iotivity/cloud/base/exception/ServerException.java
cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/ISignaling.java [new file with mode: 0644]
cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/Message.java
cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/MessageBuilder.java
cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapDecoder.java
cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapLogHandler.java
cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapMessage.java
cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapRequest.java
cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapResponse.java
cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapSignaling.java [new file with mode: 0644]
cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/enums/SignalingMethod.java [new file with mode: 0644]

index 4571b80..cc449e0 100644 (file)
@@ -32,14 +32,17 @@ import org.iotivity.cloud.base.device.Device;
 import org.iotivity.cloud.base.device.IRequestChannel;
 import org.iotivity.cloud.base.exception.ClientException;
 import org.iotivity.cloud.base.exception.ServerException;
+import org.iotivity.cloud.base.exception.ServerException.BadOptionException;
 import org.iotivity.cloud.base.exception.ServerException.BadRequestException;
 import org.iotivity.cloud.base.exception.ServerException.UnAuthorizedException;
 import org.iotivity.cloud.base.protocols.MessageBuilder;
 import org.iotivity.cloud.base.protocols.coap.CoapRequest;
 import org.iotivity.cloud.base.protocols.coap.CoapResponse;
+import org.iotivity.cloud.base.protocols.coap.CoapSignaling;
 import org.iotivity.cloud.base.protocols.enums.ContentFormat;
 import org.iotivity.cloud.base.protocols.enums.RequestMethod;
 import org.iotivity.cloud.base.protocols.enums.ResponseStatus;
+import org.iotivity.cloud.base.protocols.enums.SignalingMethod;
 import org.iotivity.cloud.base.server.CoapServer;
 import org.iotivity.cloud.base.server.HttpServer;
 import org.iotivity.cloud.base.server.Server;
@@ -50,6 +53,7 @@ import org.iotivity.cloud.util.Log;
 import io.netty.channel.ChannelDuplexHandler;
 import io.netty.channel.ChannelHandler.Sharable;
 import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.channel.ChannelPromise;
 
 /**
@@ -60,9 +64,10 @@ import io.netty.channel.ChannelPromise;
 
 public class DeviceServerSystem extends ServerSystem {
 
-    private Cbor<HashMap<String, Object>> mCbor     = new Cbor<HashMap<String, Object>>();
+    private Cbor<HashMap<String, Object>>                 mCbor     = new Cbor<HashMap<String, Object>>();
+    private HashMap<ChannelHandlerContext, CoapSignaling> mCsmMap   = new HashMap<>();
 
-    IRequestChannel                       mRDServer = null;
+    IRequestChannel                                       mRDServer = null;
 
     public DeviceServerSystem() {
         mRDServer = ConnectorPool.getConnection("rd");
@@ -239,6 +244,7 @@ public class DeviceServerSystem extends ServerSystem {
         public void channelInactive(ChannelHandlerContext ctx)
                 throws ClientException {
             Device device = ctx.channel().attr(keyDevice).get();
+
             // Some cases, this event occurs after new device connected using
             // same di.
             // So compare actual value, and remove if same.
@@ -290,7 +296,6 @@ public class DeviceServerSystem extends ServerSystem {
         @Override
         public void write(ChannelHandlerContext ctx, Object msg,
                 ChannelPromise promise) {
-
             try {
 
                 if (!(msg instanceof CoapResponse)) {
@@ -299,6 +304,7 @@ public class DeviceServerSystem extends ServerSystem {
                 }
                 // This is CoapResponse
                 // Once the response is valid, add this to deviceList
+
                 CoapResponse response = (CoapResponse) msg;
 
                 switch (response.getUriPath()) {
@@ -343,7 +349,6 @@ public class DeviceServerSystem extends ServerSystem {
 
         @Override
         public void channelRead(ChannelHandlerContext ctx, Object msg) {
-
             try {
                 if (!(msg instanceof CoapRequest)) {
                     throw new BadRequestException(
@@ -421,9 +426,108 @@ public class DeviceServerSystem extends ServerSystem {
         }
     }
 
+    @Sharable
+    class CoapSignalingHandler extends ChannelInboundHandlerAdapter {
+
+        @Override
+        public void channelInactive(ChannelHandlerContext ctx)
+                throws Exception {
+            // delete csm information from the map
+            mCsmMap.remove(ctx);
+            ctx.fireChannelInactive();
+        }
+
+        @Override
+        public void channelActive(ChannelHandlerContext ctx) {
+            Device device = ctx.channel().attr(keyDevice).get();
+            mDevicePool.addDevice(device);
+            device.onConnected();
+            // Authenticated device connected
+            // Actual channel active should decided after authentication.
+            CoapSignaling signaling = (CoapSignaling) MessageBuilder
+                    .createSignaling(SignalingMethod.CSM);
+            signaling.setCsmMaxMessageSize(4294967295L);
+            ctx.writeAndFlush(signaling);
+        }
+
+        @Override
+        public void channelRead(ChannelHandlerContext ctx, Object msg) {
+            try {
+                if (msg instanceof CoapSignaling) {
+                    CoapSignaling signaling = (CoapSignaling) msg;
+                    switch (signaling.getSignalingMethod()) {
+                        case CSM:
+                            // get existing CSM from the map
+                            CoapSignaling existingCsm = mCsmMap.get(ctx);
+                            if (existingCsm == null) {
+                                existingCsm = signaling;
+                            } else {
+                                // replace and cumulate CSM options
+                                existingCsm.setCsmBlockWiseTransfer(
+                                        signaling.getCsmBlockWiseTransfer());
+                                existingCsm.setCsmMaxMessageSize(
+                                        signaling.getCsmMaxMessageSize());
+                                existingCsm.setCsmServerName(
+                                        signaling.getCsmServerName());
+                            }
+                            mCsmMap.put(ctx, existingCsm);
+                            break;
+                        case PING:
+                            // TODO process PING signaling option
+                            break;
+                        case PONG:
+                            // TODO process PONG signaling option
+                            break;
+                        case RELEASE:
+                        case ABORT:
+                            mCsmMap.remove(ctx);
+                            ctx.close();
+                            break;
+                        default:
+                            throw new BadOptionException(
+                                    "unsupported CoAP Signaling option");
+                    }
+
+                    ctx.fireChannelRead(msg);
+                } else {
+                    ctx.fireChannelRead(msg);
+                    // TODO annotated codes must be removed to follow
+                    // the CSM specification of draft-ietf-core-coap-tcp-tls-05
+
+                    // if (mCsmMap.get(ctx) != null) {
+                    // ctx.fireChannelRead(msg);
+                    // } else {
+                    // // send ABORT signaling and close the connection
+                    // ctx.writeAndFlush(MessageBuilder.createSignaling(
+                    // SignalingMethod.ABORT,
+                    // new String(
+                    // "Capability and Settings message (CSM) is not received
+                    // yet")
+                    // .getBytes()));
+                    // ctx.close();
+                    // }
+                }
+            } catch (Throwable t) {
+                ResponseStatus responseStatus = t instanceof ServerException
+                        ? ((ServerException) t).getErrorResponse()
+                        : ResponseStatus.BAD_OPTION;
+                if (msg instanceof CoapRequest) {
+                    ctx.writeAndFlush(MessageBuilder
+                            .createResponse((CoapRequest) msg, responseStatus));
+                } else if (msg instanceof CoapSignaling) {
+                    ctx.writeAndFlush(MessageBuilder.createSignalingResponse(
+                            (CoapSignaling) msg, responseStatus));
+                }
+                Log.f(ctx.channel(), t);
+            }
+        }
+
+    }
+
     @Override
     public void addServer(Server server) {
         if (server instanceof CoapServer) {
+            server.addHandler(new CoapSignalingHandler());
             server.addHandler(new CoapAuthHandler());
         }
 
index cdfb1e5..8ab871e 100644 (file)
@@ -36,15 +36,19 @@ import org.iotivity.cloud.base.device.Device;
 import org.iotivity.cloud.base.device.IRequestChannel;
 import org.iotivity.cloud.base.protocols.IRequest;
 import org.iotivity.cloud.base.protocols.IResponse;
+import org.iotivity.cloud.base.protocols.ISignaling;
 import org.iotivity.cloud.base.protocols.MessageBuilder;
 import org.iotivity.cloud.base.protocols.coap.CoapRequest;
 import org.iotivity.cloud.base.protocols.coap.CoapResponse;
+import org.iotivity.cloud.base.protocols.coap.CoapSignaling;
 import org.iotivity.cloud.base.protocols.enums.ContentFormat;
 import org.iotivity.cloud.base.protocols.enums.RequestMethod;
 import org.iotivity.cloud.base.protocols.enums.ResponseStatus;
+import org.iotivity.cloud.base.protocols.enums.SignalingMethod;
 import org.iotivity.cloud.base.server.CoapServer;
 import org.iotivity.cloud.base.server.HttpServer;
 import org.iotivity.cloud.ciserver.DeviceServerSystem.CoapDevicePool;
+import org.iotivity.cloud.ciserver.DeviceServerSystem.CoapSignalingHandler;
 import org.iotivity.cloud.ciserver.resources.proxy.account.Account;
 import org.iotivity.cloud.ciserver.resources.proxy.mq.MessageQueue;
 import org.iotivity.cloud.ciserver.resources.proxy.rd.ResourceDirectory;
@@ -69,6 +73,7 @@ import io.netty.util.Attribute;
 
 public class DeviceServerSystemTest {
     private ChannelHandlerContext                   mCtx                  = null;
+    private ChannelHandlerContext                   mCtxSignal            = null;
     private String                                  mDi                   = "B371C481-38E6-4D47-8320-7688D8A5B58C";
     private String                                  mUserId               = "testuser";
     private String                                  mAccessToken          = "1689c70ffa245effc563017fee36d250";
@@ -78,6 +83,7 @@ public class DeviceServerSystemTest {
             CoapDevice.class);
     private IResponse                               mRes                  = null;
     private IRequest                                mReq                  = null;
+    private ISignaling                              mSig                  = null;
     final CountDownLatch                            mLatch                = new CountDownLatch(
             1);
     private Cbor<HashMap<Object, Object>>           mCbor                 = new Cbor<>();
@@ -90,6 +96,8 @@ public class DeviceServerSystemTest {
     private DeviceServerSystem.CoapLifecycleHandler mCoapLifecycleHandler = mDeviceServerSystem.new CoapLifecycleHandler();
     @InjectMocks
     private DeviceServerSystem.CoapAuthHandler      mCoapAuthHandler      = mDeviceServerSystem.new CoapAuthHandler();
+    @InjectMocks
+    private CoapSignalingHandler                    mCoapSignalingHandler = mDeviceServerSystem.new CoapSignalingHandler();
 
     @Before
     public void setUp() throws Exception {
@@ -98,6 +106,7 @@ public class DeviceServerSystemTest {
         mRes = null;
         mReq = null;
         mCtx = mock(ChannelHandlerContext.class);
+        mCtxSignal = mock(ChannelHandlerContext.class);
         Cbor<HashMap<Object, Object>> cbor = new Cbor<>();
         Channel channel = mock(Channel.class);
         Attribute<Device> attribute = mock(Attribute.class);
@@ -130,6 +139,31 @@ public class DeviceServerSystemTest {
 
         Mockito.doAnswer(new Answer<Object>() {
             @Override
+            public CoapSignaling answer(InvocationOnMock invocation)
+                    throws Throwable {
+                Object[] args = invocation.getArguments();
+                CoapSignaling signaling = (CoapSignaling) args[0];
+                System.out.println("\t----------payload : "
+                        + signaling.getPayloadString());
+                System.out.println("\t----------signaling method : "
+                        + signaling.getSignalingMethod());
+                if (signaling.getSignalingMethod()
+                        .equals(SignalingMethod.CSM)) {
+                    System.out.println("\t----------CSM Blockwise Transfer : "
+                            + signaling.getCsmBlockWiseTransfer());
+                    System.out.println("\t----------CSM Max Message Size : "
+                            + signaling.getCsmMaxMessageSize());
+                    System.out.println("\t----------CSM Server Name : "
+                            + signaling.getCsmServerName());
+                }
+                mSig = signaling;
+                mLatch.countDown();
+                return null;
+            }
+        }).when(mCtxSignal).fireChannelRead(Mockito.any());
+
+        Mockito.doAnswer(new Answer<Object>() {
+            @Override
             public CoapResponse answer(InvocationOnMock invocation)
                     throws Throwable {
                 Object[] args = invocation.getArguments();
@@ -367,6 +401,20 @@ public class DeviceServerSystemTest {
     }
 
     @Test
+    public void coapAuthHandlerCSMSignaling() throws Exception {
+
+        System.out.println(
+                "\t--------------coapAuthHandler coapAuthHandlerCSMSignaling Test------------");
+        ISignaling signal = MessageBuilder.createSignaling(SignalingMethod.CSM);
+        CoapSignaling signaling = (CoapSignaling) signal;
+        signaling.setCsmBlockWiseTransfer(true);
+        signaling.setCsmMaxMessageSize(1124);
+        signaling.setCsmServerName("default_server_name");
+        mCoapSignalingHandler.channelRead(mCtxSignal, signal);
+        assertEquals(mSig.getSignalingMethod(), SignalingMethod.CSM);
+    }
+
+    @Test
     public void CoapLifecycleHandlerChannelReadRequest()
             throws InterruptedException {
         System.out.println(
index 5d0309f..fb33b1e 100644 (file)
@@ -32,6 +32,18 @@ public class ServerException extends RuntimeException {
         }
     }
 
+    public static class BadOptionException extends ServerException {
+        private static final long serialVersionUID = -1133293352997486233L;
+
+        public BadOptionException() {
+            super(ResponseStatus.BAD_OPTION);
+        }
+
+        public BadOptionException(String msg) {
+            super(ResponseStatus.BAD_OPTION, msg);
+        }
+    }
+
     public static class NotFoundException extends ServerException {
         private static final long serialVersionUID = 775328915430229701L;
 
@@ -68,6 +80,18 @@ public class ServerException extends RuntimeException {
         }
     }
 
+    public static class MethodNotAllowedException extends ServerException {
+        private static final long serialVersionUID = -3491771205478082847L;
+
+        public MethodNotAllowedException() {
+            super(ResponseStatus.METHOD_NOT_ALLOWED);
+        }
+
+        public MethodNotAllowedException(String msg) {
+            super(ResponseStatus.METHOD_NOT_ALLOWED, msg);
+        }
+    }
+
     public static class PreconditionFailedException extends ServerException {
         private static final long serialVersionUID = -4139595328143251734L;
 
diff --git a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/ISignaling.java b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/ISignaling.java
new file mode 100644 (file)
index 0000000..d28df13
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * //******************************************************************
+ * //
+ * // Copyright 2016 Samsung Electronics All Rights Reserved.
+ * //
+ * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+ * //
+ * // Licensed under the Apache License, Version 2.0 (the "License");
+ * // you may not use this file except in compliance with the License.
+ * // You may obtain a copy of the License at
+ * //
+ * //      http://www.apache.org/licenses/LICENSE-2.0
+ * //
+ * // Unless required by applicable law or agreed to in writing, software
+ * // distributed under the License is distributed on an "AS IS" BASIS,
+ * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * // See the License for the specific language governing permissions and
+ * // limitations under the License.
+ * //
+ * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+ */
+package org.iotivity.cloud.base.protocols;
+
+import org.iotivity.cloud.base.protocols.enums.SignalingMethod;
+
+public interface ISignaling {
+
+    public SignalingMethod getSignalingMethod();
+
+    public int getPayloadSize();
+
+    public byte[] getPayload();
+}
index 9e2a503..b234c7a 100644 (file)
@@ -27,7 +27,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.StringTokenizer;
 
-public abstract class Message implements IRequest, IResponse {
+public abstract class Message implements IRequest, IResponse, ISignaling {
     protected List<byte[]> uri_path  = null;
     protected List<byte[]> uri_query = null;
     protected byte[]       payload   = null;
index 5a703b7..26b7dfe 100644 (file)
@@ -23,9 +23,11 @@ package org.iotivity.cloud.base.protocols;
 
 import org.iotivity.cloud.base.protocols.coap.CoapRequest;
 import org.iotivity.cloud.base.protocols.coap.CoapResponse;
+import org.iotivity.cloud.base.protocols.coap.CoapSignaling;
 import org.iotivity.cloud.base.protocols.enums.ContentFormat;
 import org.iotivity.cloud.base.protocols.enums.RequestMethod;
 import org.iotivity.cloud.base.protocols.enums.ResponseStatus;
+import org.iotivity.cloud.base.protocols.enums.SignalingMethod;
 
 public class MessageBuilder {
 
@@ -35,6 +37,29 @@ public class MessageBuilder {
                 null);
     }
 
+    public static IResponse createSignalingResponse(ISignaling signaling,
+            ResponseStatus responseStatus) {
+        return createSignalingResponse(signaling, responseStatus, null);
+    }
+
+    public static IResponse createSignalingResponse(ISignaling signaling,
+            ResponseStatus responseStatus, byte[] payload) {
+        IResponse response = null;
+
+        if (signaling instanceof CoapSignaling) {
+            CoapSignaling coapSignaling = (CoapSignaling) signaling;
+            CoapResponse coapResponse = new CoapResponse(responseStatus);
+            coapResponse.setToken(coapSignaling.getToken());
+            if (payload != null) {
+                coapResponse.setPayload(payload);
+            }
+
+            response = coapResponse;
+        }
+
+        return response;
+    }
+
     public static IResponse createResponse(IRequest request,
             ResponseStatus responseStatus, ContentFormat format,
             byte[] payload) {
@@ -75,6 +100,26 @@ public class MessageBuilder {
                 ContentFormat.NO_CONTENT, null);
     }
 
+    public static ISignaling createSignaling(SignalingMethod signalingMethod,
+            byte[] diagnosticPayload) {
+
+        CoapSignaling coapSignaling = null;
+
+        coapSignaling = new CoapSignaling(signalingMethod);
+        // TODO: Random token generation required
+        coapSignaling.setToken("tmptoken".getBytes());
+
+        if (diagnosticPayload != null) {
+            coapSignaling.setPayload(diagnosticPayload);
+        }
+
+        return coapSignaling;
+    }
+
+    public static ISignaling createSignaling(SignalingMethod signalingMethod) {
+        return createSignaling(signalingMethod, null);
+    }
+
     public static IRequest createRequest(RequestMethod requestMethod,
             String uriPath, String uriQuery, ContentFormat contentFormat,
             byte[] payload) {
index cf384ea..9b9d5b0 100644 (file)
@@ -23,6 +23,11 @@ package org.iotivity.cloud.base.protocols.coap;
 
 import java.util.List;
 
+import org.iotivity.cloud.base.exception.ServerException;
+import org.iotivity.cloud.base.protocols.MessageBuilder;
+import org.iotivity.cloud.base.protocols.enums.ResponseStatus;
+import org.iotivity.cloud.util.Log;
+
 import io.netty.buffer.ByteBuf;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.ByteToMessageDecoder;
@@ -41,113 +46,124 @@ public class CoapDecoder extends ByteToMessageDecoder {
 
     @Override
     protected void decode(ChannelHandlerContext ctx, ByteBuf in,
-            List<Object> out) throws Exception {
-
-        // TODO: need exceptional case handling
-        while (in.isReadable(bufferToRead)) {
-
-            switch (nextState) {
-                case SHIM_HEADER:
-                    int shimHeader = in.readByte();
-                    bufferToRead = (shimHeader >>> 4) & 0x0F;
-                    tokenLength = (shimHeader) & 0x0F;
-                    switch (bufferToRead) {
-                        case 13:
-                            bufferToRead = 1;
-                            nextState = ParsingState.OPTION_PAYLOAD_LENGTH;
-                            break;
-                        case 14:
-                            bufferToRead = 2;
-                            nextState = ParsingState.OPTION_PAYLOAD_LENGTH;
-                            break;
-                        case 15:
-                            bufferToRead = 4;
-                            nextState = ParsingState.OPTION_PAYLOAD_LENGTH;
-                            break;
-                        default:
-                            optionPayloadLength = bufferToRead;
-                            bufferToRead += 1 + tokenLength; // code + tkl
-                            nextState = ParsingState.CODE_TOKEN_OPTION;
-                            break;
-                    }
-                    break;
-
-                case OPTION_PAYLOAD_LENGTH:
-                    switch (bufferToRead) {
-                        case 1:
-                            optionPayloadLength = 13 + (in.readByte() & 0xFF);
-                            break;
-
-                        case 2:
-                            optionPayloadLength = 269
-                                    + (((in.readByte() & 0xFF) << 8)
-                                            + (in.readByte() & 0xFF));
-                            break;
-
-                        case 4:
-                            optionPayloadLength = 65805
-                                    + (((in.readByte() & 0xFF) << 24)
-                                            + ((in.readByte() & 0xFF) << 16)
-                                            + ((in.readByte() & 0xFF) << 8)
-                                            + (in.readByte() & 0xFF));
-                            break;
-                    }
-                    nextState = ParsingState.CODE_TOKEN_OPTION;
-                    bufferToRead = 1 + tokenLength + optionPayloadLength; // code
-                                                                          // +
-                                                                          // tkl
-                    break;
-
-                case CODE_TOKEN_OPTION:
-                    int code = in.readByte() & 0xFF;
-
-                    if (code <= 31) {
-                        partialMsg = new CoapRequest(code);
-                    } else {
-                        partialMsg = new CoapResponse(code);
-                    }
-
-                    if (tokenLength > 0) {
-                        byte[] token = new byte[tokenLength];
-                        in.readBytes(token);
-                        partialMsg.setToken(token);
-                    }
-
-                    if (optionPayloadLength > 0) {
-                        int optionLen = parseOptions(partialMsg, in,
-                                optionPayloadLength);
-                        if (optionPayloadLength > optionLen) {
-                            nextState = ParsingState.PAYLOAD;
-                            bufferToRead = optionPayloadLength - optionLen;
-                            continue;
+            List<Object> out) {
+        try {
+
+            // TODO: need exceptional case handling
+            while (in.isReadable(bufferToRead)) {
+
+                switch (nextState) {
+                    case SHIM_HEADER:
+                        int shimHeader = in.readByte();
+                        bufferToRead = (shimHeader >>> 4) & 0x0F;
+                        tokenLength = (shimHeader) & 0x0F;
+                        switch (bufferToRead) {
+                            case 13:
+                                bufferToRead = 1;
+                                nextState = ParsingState.OPTION_PAYLOAD_LENGTH;
+                                break;
+                            case 14:
+                                bufferToRead = 2;
+                                nextState = ParsingState.OPTION_PAYLOAD_LENGTH;
+                                break;
+                            case 15:
+                                bufferToRead = 4;
+                                nextState = ParsingState.OPTION_PAYLOAD_LENGTH;
+                                break;
+                            default:
+                                optionPayloadLength = bufferToRead;
+                                bufferToRead += 1 + tokenLength; // code + tkl
+                                nextState = ParsingState.CODE_TOKEN_OPTION;
+                                break;
+                        }
+                        break;
+
+                    case OPTION_PAYLOAD_LENGTH:
+                        switch (bufferToRead) {
+                            case 1:
+                                optionPayloadLength = 13
+                                        + (in.readByte() & 0xFF);
+                                break;
+
+                            case 2:
+                                optionPayloadLength = 269
+                                        + (((in.readByte() & 0xFF) << 8)
+                                                + (in.readByte() & 0xFF));
+                                break;
+
+                            case 4:
+                                optionPayloadLength = 65805
+                                        + (((in.readByte() & 0xFF) << 24)
+                                                + ((in.readByte() & 0xFF) << 16)
+                                                + ((in.readByte() & 0xFF) << 8)
+                                                + (in.readByte() & 0xFF));
+                                break;
+                        }
+                        nextState = ParsingState.CODE_TOKEN_OPTION;
+                        bufferToRead = 1 + tokenLength + optionPayloadLength; // code
+                                                                              // +
+                                                                              // tkl
+                        break;
+
+                    case CODE_TOKEN_OPTION:
+                        int code = in.readByte() & 0xFF;
+                        if (code <= 31) {
+                            partialMsg = new CoapRequest(code);
+                        } else if (code > 224) {
+                            partialMsg = new CoapSignaling(code);
+                        } else {
+                            partialMsg = new CoapResponse(code);
+                        }
+
+                        if (tokenLength > 0) {
+                            byte[] token = new byte[tokenLength];
+                            in.readBytes(token);
+                            partialMsg.setToken(token);
+                        }
+
+                        if (optionPayloadLength > 0) {
+                            int optionLen = parseOptions(partialMsg, in,
+                                    optionPayloadLength);
+                            if (optionPayloadLength > optionLen) {
+                                nextState = ParsingState.PAYLOAD;
+                                bufferToRead = optionPayloadLength - optionLen;
+                                continue;
+                            }
                         }
-                    }
 
-                    nextState = ParsingState.FINISH;
-                    bufferToRead = 0;
+                        nextState = ParsingState.FINISH;
+                        bufferToRead = 0;
 
-                    break;
+                        break;
 
-                case PAYLOAD:
-                    byte[] payload = new byte[bufferToRead];
-                    in.readBytes(payload);
-                    partialMsg.setPayload(payload);
-                    nextState = ParsingState.FINISH;
-                    bufferToRead = 0;
-                    break;
+                    case PAYLOAD:
+                        byte[] payload = new byte[bufferToRead];
+                        in.readBytes(payload);
+                        partialMsg.setPayload(payload);
+                        nextState = ParsingState.FINISH;
+                        bufferToRead = 0;
+                        break;
 
-                case FINISH:
-                    nextState = ParsingState.SHIM_HEADER;
-                    bufferToRead = 1;
-                    out.add(partialMsg);
-                    break;
+                    case FINISH:
+                        nextState = ParsingState.SHIM_HEADER;
+                        bufferToRead = 1;
+                        out.add(partialMsg);
+                        break;
 
-                default:
-                    break;
+                    default:
+                        break;
+                }
             }
+            in.discardReadBytes();
+        } catch (Throwable t) {
+            ResponseStatus responseStatus = t instanceof ServerException
+                    ? ((ServerException) t).getErrorResponse()
+                    : ResponseStatus.INTERNAL_SERVER_ERROR;
+            Log.f(ctx.channel(), t);
+            ctx.writeAndFlush(
+                    MessageBuilder.createResponse(partialMsg, responseStatus));
+            ctx.close();
         }
-
-        in.discardReadBytes();
     }
 
     private int parseOptions(CoapMessage coapMessage, ByteBuf byteBuf,
index c6343f0..748d1a5 100644 (file)
@@ -60,17 +60,7 @@ public class CoapLogHandler extends ChannelDuplexHandler {
     public void write(ChannelHandlerContext ctx, Object msg,
             ChannelPromise promise) {
 
-        String log = null;
-
-        if (msg instanceof CoapRequest) {
-            log = composeCoapRequest(
-                    ctx.channel().id().asLongText().substring(26),
-                    (CoapRequest) msg);
-        } else {
-            log = composeCoapResponse(
-                    ctx.channel().id().asLongText().substring(26),
-                    (CoapResponse) msg);
-        }
+        String log = getCoapLog(ctx, msg);
 
         Log.v(log);
 
@@ -81,21 +71,70 @@ public class CoapLogHandler extends ChannelDuplexHandler {
     public void channelRead(ChannelHandlerContext ctx, Object msg)
             throws Exception {
 
-        String log = null;
+        String log = getCoapLog(ctx, msg);
 
+        Log.v(log);
+
+        ctx.fireChannelRead(msg);
+
+    }
+
+    private String getCoapLog(ChannelHandlerContext ctx, Object msg) {
         if (msg instanceof CoapRequest) {
-            log = composeCoapRequest(
+            return composeCoapRequest(
                     ctx.channel().id().asLongText().substring(26),
                     (CoapRequest) msg);
+        } else if (msg instanceof CoapSignaling) {
+            return composeCoapSignaling(
+                    ctx.channel().id().asLongText().substring(26),
+                    (CoapSignaling) msg);
         } else {
-            log = composeCoapResponse(
+            return composeCoapResponse(
                     ctx.channel().id().asLongText().substring(26),
                     (CoapResponse) msg);
         }
+    }
 
-        Log.v(log);
+    private String composeCoapSignaling(String channelId,
+            CoapSignaling signaling) {
+        StringBuilder strBuilder = new StringBuilder();
 
-        ctx.fireChannelRead(msg);
+        strBuilder.append(channelId);
+        strBuilder.append(" " + signaling.getTokenString());
+
+        switch (signaling.getSignalingMethod()) {
+            case CSM:
+                strBuilder.append(" 7.01 CSM");
+                strBuilder.append(" SERVER-NAME:");
+                strBuilder.append(signaling.getCsmServerName());
+                strBuilder.append(" MAX-MESSAGE-SIZE:");
+                strBuilder.append(signaling.getCsmMaxMessageSize());
+                strBuilder.append(" BLOCK-WISE-TRANSFER:");
+                strBuilder.append(signaling.getCsmBlockWiseTransfer());
+                break;
+            case PING:
+                strBuilder.append(" 7.02 PING");
+                break;
+            case PONG:
+                strBuilder.append(" 7.03 PONG");
+                break;
+            case RELEASE:
+                strBuilder.append(" 7.04 RELEASE");
+                break;
+            case ABORT:
+                strBuilder.append(" 7.05 ABORT");
+                break;
+            default:
+                break;
+        }
+
+        if (signaling.getPayloadSize() > 0) {
+            strBuilder.append(" SZ:" + signaling.getPayloadSize() + " P:"
+                    + new String(signaling.getPayload(), 0,
+                            signaling.getPayloadSize() > MAX_LOGLEN ? MAX_LOGLEN
+                                    : signaling.getPayloadSize()));
+        }
+        return strBuilder.toString();
     }
 
     private String composeCoapRequest(String channelId, CoapRequest request) {
@@ -136,10 +175,12 @@ public class CoapLogHandler extends ChannelDuplexHandler {
         }
 
         if (request.getPayloadSize() > 0) {
-            strBuilder.append(" SZ:" + request.getPayloadSize() + " P:"
-                    + new String(request.getPayload(), 0,
-                            request.getPayloadSize() > MAX_LOGLEN ? MAX_LOGLEN
-                                    : request.getPayloadSize()));
+            strBuilder
+                    .append(" SZ:" + request.getPayloadSize() + " P:"
+                            + new String(request.getPayload(), 0,
+                                    request.getPayloadSize() > MAX_LOGLEN
+                                            ? MAX_LOGLEN
+                                            : request.getPayloadSize()));
         }
 
         return strBuilder.toString();
@@ -236,10 +277,12 @@ public class CoapLogHandler extends ChannelDuplexHandler {
         }
 
         if (response.getPayloadSize() > 0) {
-            strBuilder.append(" SZ:" + response.getPayloadSize() + " P:"
-                    + new String(response.getPayload(), 0,
-                            response.getPayloadSize() > MAX_LOGLEN ? MAX_LOGLEN
-                                    : response.getPayloadSize()));
+            strBuilder
+                    .append(" SZ:" + response.getPayloadSize() + " P:"
+                            + new String(response.getPayload(), 0,
+                                    response.getPayloadSize() > MAX_LOGLEN
+                                            ? MAX_LOGLEN
+                                            : response.getPayloadSize()));
         }
 
         return strBuilder.toString();
index 283b681..c91cb42 100644 (file)
@@ -27,6 +27,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
+import org.iotivity.cloud.base.exception.ServerException.BadOptionException;
 import org.iotivity.cloud.base.protocols.Message;
 import org.iotivity.cloud.base.protocols.enums.ContentFormat;
 import org.iotivity.cloud.base.protocols.enums.Observe;
@@ -157,6 +158,13 @@ public abstract class CoapMessage extends Message {
             case 6:
                 mObserve = Bytes.bytesToInt(value);
                 break;
+            default: {
+                if (optnum % 2 == 1) {
+                    throw new BadOptionException(
+                            "unrecognized critical option # " + optnum
+                                    + " received");
+                }
+            }
         }
     }
 
index 4f0ff7b..d2cf668 100644 (file)
  */
 package org.iotivity.cloud.base.protocols.coap;
 
+import org.iotivity.cloud.base.exception.ServerException.MethodNotAllowedException;
 import org.iotivity.cloud.base.protocols.enums.RequestMethod;
 import org.iotivity.cloud.base.protocols.enums.ResponseStatus;
+import org.iotivity.cloud.base.protocols.enums.SignalingMethod;
 
 public class CoapRequest extends CoapMessage {
     private RequestMethod mRequestMethod;
@@ -46,7 +48,9 @@ public class CoapRequest extends CoapMessage {
                 mRequestMethod = RequestMethod.DELETE;
                 break;
             default:
-                throw new IllegalArgumentException("Invalid CoapRequest code");
+                // unrecognized or unsupported Method Code MUST generate
+                // a 4.05 (Method Not Allowed) piggybacked response. (RFC7252)
+                throw new MethodNotAllowedException("Invalid CoapRequest code");
         }
     }
 
@@ -78,4 +82,10 @@ public class CoapRequest extends CoapMessage {
     public ResponseStatus getStatus() {
         return ResponseStatus.METHOD_NOT_ALLOWED;
     }
+
+    // This request object does not support signaling status
+    @Override
+    public SignalingMethod getSignalingMethod() {
+        return null;
+    }
 }
\ No newline at end of file
index cfab952..8f6e3d1 100644 (file)
@@ -23,6 +23,7 @@ package org.iotivity.cloud.base.protocols.coap;
 
 import org.iotivity.cloud.base.protocols.enums.RequestMethod;
 import org.iotivity.cloud.base.protocols.enums.ResponseStatus;
+import org.iotivity.cloud.base.protocols.enums.SignalingMethod;
 
 public class CoapResponse extends CoapMessage {
     private ResponseStatus mResponseStatus;
@@ -97,7 +98,9 @@ public class CoapResponse extends CoapMessage {
                 mResponseStatus = ResponseStatus.PROXY_NOT_SUPPORTED;
                 break;
             default:
-                throw new IllegalArgumentException("Invalid CoapResponse code");
+                // unrecognized response code is treated as being equivalent to
+                // the generic response code of 4.00 (RFC7252)
+                mResponseStatus = ResponseStatus.BAD_REQUEST;
         }
     }
 
@@ -163,4 +166,10 @@ public class CoapResponse extends CoapMessage {
     public ResponseStatus getStatus() {
         return mResponseStatus;
     }
+
+    // This response object does not support signaling method
+    @Override
+    public SignalingMethod getSignalingMethod() {
+        return null;
+    }
 }
diff --git a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapSignaling.java b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapSignaling.java
new file mode 100644 (file)
index 0000000..f7e4a2a
--- /dev/null
@@ -0,0 +1,325 @@
+/*
+ * //******************************************************************
+ * //
+ * // Copyright 2016 Samsung Electronics All Rights Reserved.
+ * //
+ * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+ * //
+ * // Licensed under the Apache License, Version 2.0 (the "License");
+ * // you may not use this file except in compliance with the License.
+ * // You may obtain a copy of the License at
+ * //
+ * //      http://www.apache.org/licenses/LICENSE-2.0
+ * //
+ * // Unless required by applicable law or agreed to in writing, software
+ * // distributed under the License is distributed on an "AS IS" BASIS,
+ * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * // See the License for the specific language governing permissions and
+ * // limitations under the License.
+ * //
+ * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+ */
+package org.iotivity.cloud.base.protocols.coap;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.iotivity.cloud.base.exception.ServerException.BadOptionException;
+import org.iotivity.cloud.base.protocols.enums.RequestMethod;
+import org.iotivity.cloud.base.protocols.enums.ResponseStatus;
+import org.iotivity.cloud.base.protocols.enums.SignalingMethod;
+
+public class CoapSignaling extends CoapMessage {
+    private SignalingMethod mSignalingMethod;
+
+    // Option fields (for CSM signaling)
+    protected byte[]        server_name         = null;  // CSM option 1
+    protected byte[]        max_message_size    = null;  // CSM option 2
+    protected boolean       block_wise_tranfer  = false; // CSM option 4
+
+    // Option fields (for PING signaling and PONG signaling)
+    protected boolean       custody             = false; // PING / PONG option 2
+
+    // Option fields (for Release signaling)
+    protected boolean       bad_server_name     = false; // Release option 2
+    protected byte[]        alternative_address = null;  // Release option 4
+    protected byte[]        hold_off            = null;  // Release option 6
+
+    // Option fields (for Abort signaling)
+    protected byte[]        bad_csm_option      = null;  // Abort option 2
+
+    public CoapSignaling(SignalingMethod signalingMethod) {
+        mSignalingMethod = signalingMethod;
+    }
+
+    public CoapSignaling(int code) {
+        switch (code) {
+            case 225: // code 7.01
+                mSignalingMethod = SignalingMethod.CSM;
+                break;
+            case 226: // code 7.02
+                mSignalingMethod = SignalingMethod.PING;
+                break;
+            case 227: // code 7.03
+                mSignalingMethod = SignalingMethod.PONG;
+                break;
+            case 228: // code 7.04
+                mSignalingMethod = SignalingMethod.RELEASE;
+                break;
+            case 229: // code 7.05
+                mSignalingMethod = SignalingMethod.ABORT;
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Unsupported CoAP signaling code " + code);
+        }
+    }
+
+    @Override
+    public int getCode() {
+        switch (mSignalingMethod) {
+            case CSM: // code 7.01
+                return 225;
+            case PING: // code 7.02
+                return 226;
+            case PONG: // code 7.03
+                return 227;
+            case RELEASE: // code 7.04
+                return 228;
+            case ABORT: // code 7.05
+                return 229;
+            default:
+                break;
+        }
+        return 0;
+    }
+
+    public SignalingMethod getSignalingMethod() {
+        return mSignalingMethod;
+    }
+
+    @Override
+    public void addOption(int optnum, byte[] value) {
+        switch (mSignalingMethod) {
+            case CSM: // code 7.01
+                addCsmOption(optnum, value);
+                break;
+            case PING: // code 7.02
+            case PONG: // code 7.03
+                addPingPongOption(optnum, value);
+                break;
+            case RELEASE: // code 7.04
+                addReleaseOption(optnum, value);
+                break;
+            case ABORT: // code 7.05
+                addAbortOption(optnum, value);
+                break;
+            default:
+                break;
+        }
+    }
+
+    private void addCsmOption(int optnum, byte[] value) {
+        switch (optnum) {
+            // SERVER_NAME
+            case 1:
+                server_name = value;
+                break;
+            // MAX_MESSAGE_SIZE
+            case 2:
+                max_message_size = value;
+                break;
+            // BLOCK_WISE_TRANSFER
+            case 4:
+                block_wise_tranfer = true;
+                break;
+            default: {
+                if (optnum % 2 == 1) {
+                    throw new BadOptionException(
+                            "unrecognized critical option # " + optnum
+                                    + " received");
+                }
+            }
+        }
+    }
+
+    private void addPingPongOption(int optnum, byte[] value) {
+        switch (optnum) {
+            // CUSTODY
+            case 2:
+                custody = true;
+                break;
+            default: {
+                if (optnum % 2 == 1) {
+                    throw new BadOptionException(
+                            "unrecognized critical option # " + optnum
+                                    + " received");
+                }
+            }
+        }
+    }
+
+    private void addReleaseOption(int optnum, byte[] value) {
+        switch (optnum) {
+            // BAD_SERVER_NAME
+            case 2:
+                bad_server_name = true;
+                break;
+            // ALTERNATIVE_ADDRESS
+            case 4:
+                alternative_address = value;
+                break;
+            // HOLD_OFF
+            case 6:
+                hold_off = value;
+                break;
+            default: {
+                if (optnum % 2 == 1) {
+                    throw new BadOptionException(
+                            "unrecognized critical option # " + optnum
+                                    + " received");
+                }
+            }
+        }
+    }
+
+    private void addAbortOption(int optnum, byte[] value) {
+        switch (optnum) {
+            // BAD_CSM_OPTION
+            case 2:
+                bad_csm_option = value;
+                break;
+            default: {
+                if (optnum % 2 == 1) {
+                    throw new BadOptionException(
+                            "unrecognized critical option # " + optnum
+                                    + " received");
+                }
+            }
+        }
+    }
+
+    public void setCsmServerName(String serverName) {
+        addOption(1, serverName.getBytes(StandardCharsets.UTF_8));
+    }
+
+    public void setCsmMaxMessageSize(long maxMessageSize) {
+        ByteBuffer buf = ByteBuffer.wrap(new byte[4]);
+        max_message_size = buf.putInt(0, (int) maxMessageSize).array();
+    }
+
+    public void setCsmBlockWiseTransfer(boolean blockWiseTransferOption) {
+        block_wise_tranfer = blockWiseTransferOption;
+    }
+
+    public String getCsmServerName() {
+        if (server_name == null)
+            return "";
+        return new String(server_name, Charset.forName("UTF-8"));
+    }
+
+    public long getCsmMaxMessageSize() {
+        return unsignedIntToLong(max_message_size);
+    }
+
+    public boolean getCsmBlockWiseTransfer() {
+        return block_wise_tranfer;
+    }
+
+    @Override
+    public List<byte[]> getOption(int optnum) {
+        switch (mSignalingMethod) {
+            case CSM: // code 7.01
+                return getCsmOption(optnum);
+            case PING: // code 7.02
+            case PONG: // code 7.03
+                return getPingPongOption(optnum);
+            case RELEASE: // code 7.04
+                return getReleaseOption(optnum);
+            case ABORT: // code 7.05
+                return getAbortOption(optnum);
+            default:
+                break;
+        }
+        return null;
+    }
+
+    public List<byte[]> getCsmOption(int optnum) {
+        switch (optnum) {
+            // SERVER_NAME
+            case 1:
+                return server_name != null ? Arrays.asList(server_name) : null;
+            // MAX_MESSAGE_SIZE
+            case 2:
+                return max_message_size != null
+                        ? Arrays.asList(max_message_size) : null;
+            // BLOCK_WISE_TRANSFER
+            case 4:
+                return block_wise_tranfer == true ? new ArrayList<byte[]>()
+                        : null;
+        }
+        return null;
+    }
+
+    public List<byte[]> getPingPongOption(int optnum) {
+        switch (optnum) {
+            // CUSTODY
+            case 2:
+                return custody == true ? new ArrayList<byte[]>() : null;
+        }
+        return null;
+    }
+
+    public List<byte[]> getReleaseOption(int optnum) {
+        switch (optnum) {
+            // BAD_SERVER_NAME
+            case 2:
+                return bad_server_name == true ? new ArrayList<byte[]>() : null;
+            // ALTERNATIVE_ADDRESS
+            case 4:
+                return alternative_address != null
+                        ? Arrays.asList(alternative_address) : null;
+            // HOLD_OFF
+            case 6:
+                return hold_off != null ? Arrays.asList(hold_off) : null;
+        }
+        return null;
+    }
+
+    public List<byte[]> getAbortOption(int optnum) {
+        switch (optnum) {
+            // BAD_CSM_OPTION
+            case 2:
+                return bad_csm_option != null ? Arrays.asList(bad_csm_option)
+                        : null;
+        }
+        return null;
+    }
+
+    // This signal object does not support request status
+    @Override
+    public RequestMethod getMethod() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    // This signal object does not support response status
+    @Override
+    public ResponseStatus getStatus() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    private static final long unsignedIntToLong(byte[] b) {
+        long value = 0;
+        for (byte data : b) {
+            value <<= 8;
+            value += data & 0xFF;
+        }
+        return value;
+    }
+}
diff --git a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/enums/SignalingMethod.java b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/enums/SignalingMethod.java
new file mode 100644 (file)
index 0000000..72428f4
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * //******************************************************************
+ * //
+ * // Copyright 2016 Samsung Electronics All Rights Reserved.
+ * //
+ * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+ * //
+ * // Licensed under the Apache License, Version 2.0 (the "License");
+ * // you may not use this file except in compliance with the License.
+ * // You may obtain a copy of the License at
+ * //
+ * //      http://www.apache.org/licenses/LICENSE-2.0
+ * //
+ * // Unless required by applicable law or agreed to in writing, software
+ * // distributed under the License is distributed on an "AS IS" BASIS,
+ * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * // See the License for the specific language governing permissions and
+ * // limitations under the License.
+ * //
+ * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+ */
+package org.iotivity.cloud.base.protocols.enums;
+
+public enum SignalingMethod {
+    CSM, PING, PONG, RELEASE, ABORT
+}