From 3c0ccaa7d5dfe45dbc6cba5191d0fd63650c693b Mon Sep 17 00:00:00 2001 From: "yeonghun.nam" Date: Mon, 12 Dec 2016 15:47:24 +0900 Subject: [PATCH] [IOT-1616] CSM signaling supported in CoAP D2S communication - 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 Reviewed-on: https://gerrit.iotivity.org/gerrit/15459 Tested-by: jenkins-iotivity Reviewed-by: Jee Hyeok Kim (cherry picked from commit 8e85103153f9e624e7206b3afd7fa1d5db67bcf4) Reviewed-on: https://gerrit.iotivity.org/gerrit/16091 Tested-by: jenkins-iotivity --- .../cloud/ciserver/DeviceServerSystem.java | 112 ++++++- .../cloud/ciserver/DeviceServerSystemTest.java | 48 +++ .../cloud/base/exception/ServerException.java | 24 ++ .../iotivity/cloud/base/protocols/ISignaling.java | 33 +++ .../org/iotivity/cloud/base/protocols/Message.java | 2 +- .../cloud/base/protocols/MessageBuilder.java | 45 +++ .../cloud/base/protocols/coap/CoapDecoder.java | 214 +++++++------- .../cloud/base/protocols/coap/CoapLogHandler.java | 91 ++++-- .../cloud/base/protocols/coap/CoapMessage.java | 8 + .../cloud/base/protocols/coap/CoapRequest.java | 12 +- .../cloud/base/protocols/coap/CoapResponse.java | 11 +- .../cloud/base/protocols/coap/CoapSignaling.java | 325 +++++++++++++++++++++ .../base/protocols/enums/SignalingMethod.java | 26 ++ 13 files changed, 821 insertions(+), 130 deletions(-) create mode 100644 cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/ISignaling.java create mode 100644 cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapSignaling.java create mode 100644 cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/enums/SignalingMethod.java diff --git a/cloud/interface/src/main/java/org/iotivity/cloud/ciserver/DeviceServerSystem.java b/cloud/interface/src/main/java/org/iotivity/cloud/ciserver/DeviceServerSystem.java index 4571b80..cc449e0 100644 --- a/cloud/interface/src/main/java/org/iotivity/cloud/ciserver/DeviceServerSystem.java +++ b/cloud/interface/src/main/java/org/iotivity/cloud/ciserver/DeviceServerSystem.java @@ -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> mCbor = new Cbor>(); + private Cbor> mCbor = new Cbor>(); + private HashMap 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()); } diff --git a/cloud/interface/src/test/java/org/iotivity/cloud/ciserver/DeviceServerSystemTest.java b/cloud/interface/src/test/java/org/iotivity/cloud/ciserver/DeviceServerSystemTest.java index cdfb1e5..8ab871e 100644 --- a/cloud/interface/src/test/java/org/iotivity/cloud/ciserver/DeviceServerSystemTest.java +++ b/cloud/interface/src/test/java/org/iotivity/cloud/ciserver/DeviceServerSystemTest.java @@ -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> 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> cbor = new Cbor<>(); Channel channel = mock(Channel.class); Attribute attribute = mock(Attribute.class); @@ -130,6 +139,31 @@ public class DeviceServerSystemTest { Mockito.doAnswer(new Answer() { @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() { + @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( diff --git a/cloud/stack/src/main/java/org/iotivity/cloud/base/exception/ServerException.java b/cloud/stack/src/main/java/org/iotivity/cloud/base/exception/ServerException.java index 5d0309f..fb33b1e 100644 --- a/cloud/stack/src/main/java/org/iotivity/cloud/base/exception/ServerException.java +++ b/cloud/stack/src/main/java/org/iotivity/cloud/base/exception/ServerException.java @@ -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 index 0000000..d28df13 --- /dev/null +++ b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/ISignaling.java @@ -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(); +} diff --git a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/Message.java b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/Message.java index 9e2a503..b234c7a 100644 --- a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/Message.java +++ b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/Message.java @@ -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 uri_path = null; protected List uri_query = null; protected byte[] payload = null; diff --git a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/MessageBuilder.java b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/MessageBuilder.java index 5a703b7..26b7dfe 100644 --- a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/MessageBuilder.java +++ b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/MessageBuilder.java @@ -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) { diff --git a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapDecoder.java b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapDecoder.java index cf384ea..9b9d5b0 100644 --- a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapDecoder.java +++ b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapDecoder.java @@ -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 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 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, diff --git a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapLogHandler.java b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapLogHandler.java index c6343f0..748d1a5 100644 --- a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapLogHandler.java +++ b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapLogHandler.java @@ -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(); diff --git a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapMessage.java b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapMessage.java index 283b681..c91cb42 100644 --- a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapMessage.java +++ b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapMessage.java @@ -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"); + } + } } } diff --git a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapRequest.java b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapRequest.java index 4f0ff7b..d2cf668 100644 --- a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapRequest.java +++ b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapRequest.java @@ -21,8 +21,10 @@ */ 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 diff --git a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapResponse.java b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapResponse.java index cfab952..8f6e3d1 100644 --- a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapResponse.java +++ b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapResponse.java @@ -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 index 0000000..f7e4a2a --- /dev/null +++ b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapSignaling.java @@ -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 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 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() + : null; + } + return null; + } + + public List getPingPongOption(int optnum) { + switch (optnum) { + // CUSTODY + case 2: + return custody == true ? new ArrayList() : null; + } + return null; + } + + public List getReleaseOption(int optnum) { + switch (optnum) { + // BAD_SERVER_NAME + case 2: + return bad_server_name == true ? new ArrayList() : 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 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 index 0000000..72428f4 --- /dev/null +++ b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/enums/SignalingMethod.java @@ -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 +} -- 2.7.4