Commit 5136b59f authored by Björn Butzin's avatar Björn Butzin
Browse files

Merge branch 'master' of https://gitlab.amd.e-technik.uni-rostock.de/bjoern.konieczek/jcoap-draft18

Conflicts:
	ws4d-jcoap/src/org/ws4d/coap/connection/BasicCoapClientChannel.java
	ws4d-jcoap/src/org/ws4d/coap/connection/BasicCoapServerChannel.java
	ws4d-jcoap/src/org/ws4d/coap/connection/BasicCoapSocketHandler.java
	ws4d-jcoap/src/org/ws4d/coap/messages/CoapResponseCode.java
parents cfd3e9aa 599c5957
/* Copyright 2011 University of Rostock
/* Copyright [2011] [University of Rostock]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -15,8 +15,10 @@
package org.ws4d.coap.connection;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import org.ws4d.coap.interfaces.CoapChannel;
import org.ws4d.coap.interfaces.CoapMessage;
......@@ -28,16 +30,18 @@ import org.ws4d.coap.interfaces.CoapSocketHandler;
import org.ws4d.coap.messages.BasicCoapRequest;
import org.ws4d.coap.messages.BasicCoapResponse;
import org.ws4d.coap.messages.CoapBlockOption;
import org.ws4d.coap.messages.CoapBlockOption.CoapBlockSize;
import org.ws4d.coap.messages.CoapEmptyMessage;
import org.ws4d.coap.messages.CoapMediaType;
import org.ws4d.coap.messages.CoapPacketType;
import org.ws4d.coap.messages.CoapRequestCode;
import org.ws4d.coap.messages.CoapResponseCode;
import org.ws4d.coap.messages.CoapBlockOption.CoapBlockSize;
/**
* @author Bjoern Konieczek <bjoern.konieczek@uni-rostock.de>
* @author Christian Lerche <christian.lerche@uni-rostock.de>
*/
public class BasicCoapServerChannel extends BasicCoapChannel implements CoapServerChannel{
CoapServer server = null;
CoapResponse lastResponse;
......@@ -98,21 +102,22 @@ public class BasicCoapServerChannel extends BasicCoapChannel implements CoapServ
response = createResponse(request, CoapResponseCode.Content_205 );
response.setBlock2( newBlock );
response.setPayload( blockContext.getNextPayload(newBlock) );
// System.out.println("Sending Block Number: " + newBlock.getNumber()+"; Payload: " + new String(response.getPayload()) );
sendMessage(response);
if( blockContext.isFinished() ) {
blockContext = null;
return;
}
return;
}
}
message.setPayload( blockContext.getPayload() );
}
}
if( blockContext == null || (blockContext.getFirstRequest().getRequestCode() != CoapRequestCode.GET && blockContext.isFinished() ) ) {
CoapChannel channel = request.getChannel();
blockContext = null;
if( blockContext != null ){
request.setPayload( blockContext.getPayload() );
blockContext = null;
}
/* TODO make this cast safe */
server.onRequest((CoapServerChannel) channel, request);
}
......@@ -234,14 +239,15 @@ public class BasicCoapServerChannel extends BasicCoapChannel implements CoapServ
private class ServerBlockContext{
private ArrayList<Byte> payload;
// private FastTable<Byte> payload;
private ByteArrayOutputStream incomingStream;
private ByteArrayInputStream outgoingStream;
boolean finished = false;
boolean context = false; //false=receiving; true=sending
boolean sending = false; //false=receiving; true=sending
CoapBlockSize blockSize; //null means no block option
int blockNumber;
int maxBlockNumber;
CoapRequest request;
CoapResponse response;
/** Create BlockContext for GET requests. This is done automatically, if the sent GET request or the obtained response
* contain a Block2-Option.
......@@ -250,10 +256,10 @@ public class BasicCoapServerChannel extends BasicCoapChannel implements CoapServ
* @param maxBlocksize Indicates the maximum block size supported by the client
*/
public ServerBlockContext(CoapBlockOption blockOption, CoapBlockSize maxBlocksize) {
this.incomingStream = new ByteArrayOutputStream();
this.outgoingStream = null;
/* determine the right blocksize (min of remote and max)*/
this.payload = new ArrayList<Byte>();
// this.payload = new FastTable<Byte>();
if (maxBlocksize == null){
blockSize = blockOption.getBlockSize();
} else {
......@@ -265,7 +271,8 @@ public class BasicCoapServerChannel extends BasicCoapChannel implements CoapServ
blockSize = maxBlocksize;
}
}
context=false;
this.blockNumber = blockOption.getNumber();
this.sending=false;
}
/** Create BlockContext for POST or PUT requests. Is only called by addBlockContext().
......@@ -274,27 +281,32 @@ public class BasicCoapServerChannel extends BasicCoapChannel implements CoapServ
* @param payload The whole payload, that should be transferred
*/
public ServerBlockContext(CoapBlockSize maxBlocksize, byte[] payload ){
this.outgoingStream = new ByteArrayInputStream( payload );
this.incomingStream = null;
this.blockSize = maxBlocksize;
this.payload = new ArrayList<Byte>();
// this.payload = new FastTable<Byte>();
for(int i = 0; i < payload.length; i++ ){
this.payload.add( payload[i] );
}
this.blockNumber = 0;
this.maxBlockNumber = this.payload.size() / this.blockSize.getSize() - 1;
if( this.payload.size()%this.blockSize.getSize() > 0 )
this.blockNumber = 0;
this.maxBlockNumber = payload.length / this.blockSize.getSize() - 1;
if( payload.length%this.blockSize.getSize() > 0 )
this.maxBlockNumber++;
context = true;
this.sending = true;
}
public byte[] getPayload() {
byte[] tmp = new byte[ this.payload.size() ];
for( int i = 0; i < this.payload.size(); i++) {
tmp[i] = this.payload.get(i);
}
return tmp;
if (!this.sending ) {
try{
this.incomingStream.close();
} catch(IOException e){
e.printStackTrace();
}
return this.incomingStream.toByteArray();
} else if( this.outgoingStream != null ) {
byte[] payload = new byte[ this.outgoingStream.available() ];
outgoingStream.read(payload, 0, this.outgoingStream.available());
return payload;
} else
return null;
}
/** Adds the new obtained data block to the complete payload, in the case of blockwise GET requests.
......@@ -306,17 +318,15 @@ public class BasicCoapServerChannel extends BasicCoapChannel implements CoapServ
*/
public boolean addBlock(CoapMessage msg, CoapBlockOption block){
int number = block.getNumber();
int blockLength = msg.getPayloadLength();
if( number * blockSize.getSize() > payload.size()) {
return false;
} else if( number*blockSize.getSize()+blockLength <= payload.size() ) {
if( number > this.blockNumber )
return false;
}
for(int i = 0; i < msg.getPayloadLength(); i++ ){
this.payload.add( msg.getPayload()[i] );
// this.payload.addLast( msg.getPayload()[i] );
}
this.blockNumber++;
try{
this.incomingStream.write( msg.getPayload() );
} catch( IOException e) {
System.err.println("ERROR: Cannot write data block to input buffer!");
}
if( block.isLast() ) {
finished = true;
}
......@@ -329,8 +339,8 @@ public class BasicCoapServerChannel extends BasicCoapChannel implements CoapServ
* @return BlockOption to indicate the next block, that should be send (POST or PUT) or received (GET)
*/
public CoapBlockOption getNextBlock() {
if( !context ) {
blockNumber = payload.size() / blockSize.getSize(); //ignore the rest (no rest should be there)
if( !sending ) {
blockNumber++; //ignore the rest (no rest should be there)
return new CoapBlockOption( blockNumber, false, blockSize);
}
else {
......@@ -348,24 +358,18 @@ public class BasicCoapServerChannel extends BasicCoapChannel implements CoapServ
* @return The next part of the payload
*/
public byte[] getNextPayload(CoapBlockOption block){
int number = block.getNumber();
int size = blockSize.getSize();
Byte[] tmp;
byte[] payloadBlock;
if( number == maxBlockNumber ) {
//payloadBlock = payload.substring(number * size, payload.length() );
tmp = new Byte[ this.payload.size() - (number*size)];
this.payload.subList( number * size , this.payload.size() ).toArray(tmp);
payloadBlock = new byte[ block.getBlockSize().getSize() ];
this.outgoingStream.read(payloadBlock, 0, this.outgoingStream.available() );
finished = true;
} else {
//payloadBlock = payload.substring(number * size, number * size + size);
tmp = new Byte[ block.getBlockSize().getSize() ];
this.payload.subList(number * size, number * size + size ).toArray( tmp );
}
payloadBlock = new byte[ tmp.length ];
for( int i = 0; i < payloadBlock.length; i++ )
payloadBlock[i] = tmp[i];
payloadBlock = new byte[ block.getBlockSize().getSize() ];
this.outgoingStream.read(payloadBlock, 0, block.getBlockSize().getSize());
}
return payloadBlock;
}
......@@ -384,3 +388,4 @@ public class BasicCoapServerChannel extends BasicCoapChannel implements CoapServ
}
}
/* Copyright 2011 University of Rostock
/* Copyright [2011] [University of Rostock]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -18,10 +18,15 @@ package org.ws4d.coap.connection;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.StandardSocketOptions;
//import java.net.StandardProtocolFamily;
//import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.PriorityQueue;
......@@ -47,6 +52,7 @@ import org.ws4d.coap.tools.TimeoutHashMap;
* @author Christian Lerche <christian.lerche@uni-rostock.de>
* @author Nico Laum <nico.laum@uni-rostock.de>
*/
public class BasicCoapSocketHandler implements CoapSocketHandler {
/* the socket handler has its own logger
* TODO: implement different socket handler for client and server channels */
......@@ -71,31 +77,29 @@ public class BasicCoapSocketHandler implements CoapSocketHandler {
this.channelManager = channelManager;
//StandardProtocolFamily test = StandardProtocolFamily.INET;StandardProtocolFamily.INET
dgramChannel = DatagramChannel.open();
// boolean found = false;
// Enumeration<NetworkInterface> Interfaces = NetworkInterface.getNetworkInterfaces();
// NetworkInterface NetworkAdapter = null;
// while( !found ){
// NetworkAdapter = Interfaces.nextElement();
// if( NetworkAdapter.isUp() && !NetworkAdapter.isLoopback() ) {
// found = true;
// }
// }
boolean found = false;
Enumeration<NetworkInterface> Interfaces = NetworkInterface.getNetworkInterfaces();
NetworkInterface NetworkAdapter = null;
while( !found ){
NetworkAdapter = Interfaces.nextElement();
if( NetworkAdapter.isUp() && !NetworkAdapter.isLoopback() ) {
found = true;
}
}
//
// if( NetworkAdapter != null){
// }
//dgramChannel.setOption(StandardSocketOptions.IP_MULTICAST_IF, NetworkAdapter);
// else {
// System.err.println("ERROR: No suitable Network Interface");
// System.exit(-1);
// }
if( NetworkAdapter != null){
dgramChannel.setOption(StandardSocketOptions.IP_MULTICAST_IF, NetworkAdapter);
} else {
System.err.println("ERROR: No suitable Network Interface");
System.exit(-1);
}
//dgramChannel.socket().connect(InetAddress.getByName("0.0.0.0"), port);
dgramChannel.socket().bind(new InetSocketAddress(port)); //port can be 0, then a free port is chosen
this.localPort = dgramChannel.socket().getLocalPort();
dgramChannel.configureBlocking(false);
//dgramChannel.join( InetAddress.getByName("224.0.0.1"), NetworkAdapter );
dgramChannel.join( InetAddress.getByName("224.0.0.1"), NetworkAdapter );
workerThread = new WorkerThread();
workerThread.start();
......@@ -108,16 +112,16 @@ public class BasicCoapSocketHandler implements CoapSocketHandler {
protected class WorkerThread extends Thread {
Selector selector = null;
/** contains all received message keys of a remote (message id generated by the remote) to detect duplications */
/* contains all received message keys of a remote (message id generated by the remote) to detect duplications */
TimeoutHashMap<MessageKey, Boolean> duplicateRemoteMap = new TimeoutHashMap<MessageKey, Boolean>(CoapMessage.ACK_RST_RETRANS_TIMEOUT_MS);
/** contains all received message keys of the host (message id generated by the host) to detect duplications */
/* contains all received message keys of the host (message id generated by the host) to detect duplications */
TimeoutHashMap<Integer, Boolean> duplicateHostMap = new TimeoutHashMap<Integer, Boolean>(CoapMessage.ACK_RST_RETRANS_TIMEOUT_MS);
/** contains all messages that (possibly) needs to be retransmitted (ACK, RST)*/
/* contains all messages that (possibly) needs to be retransmitted (ACK, RST)*/
TimeoutHashMap<MessageKey, CoapMessage> retransMsgMap = new TimeoutHashMap<MessageKey, CoapMessage>(CoapMessage.ACK_RST_RETRANS_TIMEOUT_MS);
/** contains all messages that are not confirmed yet (CON),
/* contains all messages that are not confirmed yet (CON),
* MessageID is always generated by Host and therefore unique */
TimeoutHashMap<Integer, CoapMessage> timeoutConMsgMap = new TimeoutHashMap<Integer, CoapMessage>(CoapMessage.ACK_RST_RETRANS_TIMEOUT_MS);
/** this queue handles the timeout objects in the right order*/
/* this queue handles the timeout objects in the right order*/
private PriorityQueue<TimeoutObject<Integer>> timeoutQueue = new PriorityQueue<TimeoutObject<Integer>>();
public ConcurrentLinkedQueue<CoapMessage> sendBuffer = new ConcurrentLinkedQueue<CoapMessage>();
......@@ -198,9 +202,6 @@ public class BasicCoapSocketHandler implements CoapSocketHandler {
selector.select(waitFor);
} catch (IOException e) {
// TODO Auto-generated catch block
// IOException - If an I/O error occurs
// ClosedSelectorException - If this selector is closed
// IllegalArgumentException - If the value of the timeout argument is negative
e.printStackTrace();
}
}
......@@ -220,12 +221,6 @@ public class BasicCoapSocketHandler implements CoapSocketHandler {
}
}
/**
* Handles an incoming message. If the message is an acknowledgment, a resets or a duplicate, this will will be handled directly.
* Otherwise the message will be passed to the corresponding {@link CoapServerChannel}.
* @param buffer The buffer containing the datagram.
* @param addr The datagram's source address
*/
private void handleIncommingMessage(ByteBuffer buffer, InetSocketAddress addr) {
CoapMessage msg;
try {
......@@ -240,9 +235,9 @@ public class BasicCoapSocketHandler implements CoapSocketHandler {
int msgId = msg.getMessageID();
MessageKey msgKey = new MessageKey(msgId, addr.getAddress(), addr.getPort());
/* --- INCOMING REQUEST: This is an incoming client request with a message key generated by the remote client*/
if (msg.isRequest()){
/* --- INCOMING REQUEST: This is an incoming client request with a message key generated by the remote client*/
if (packetType == CoapPacketType.ACK || packetType == CoapPacketType.RST){
logger.warn("Invalid Packet Type: Request can not be in a ACK or a RST packet");
return;
......@@ -277,10 +272,10 @@ public class BasicCoapSocketHandler implements CoapSocketHandler {
msg.setChannel(channel);
channel.handleMessage(msg);
return;
/* --- INCOMING RESPONSE: This is an incoming server response (message ID generated by host)
* or a separate server response (message ID generated by remote)*/
} else if (msg.isResponse()){
/* --- INCOMING RESPONSE: This is an incoming server response (message ID generated by host)
* or a separate server response (message ID generated by remote)*/
if (packetType == CoapPacketType.RST){
logger.warn("Invalid Packet Type: RST packet must be empty");
......@@ -348,14 +343,14 @@ public class BasicCoapSocketHandler implements CoapSocketHandler {
} else if (msg.isEmpty()){
if (packetType == CoapPacketType.CON || packetType == CoapPacketType.NON){
/* TODO: is this always true? Could also be ACK or RST?*/
/* TODO: is this always true? */
logger.warn("Invalid Packet Type: CON or NON packets cannot be empty");
return;
}
/* ACK or RST, Message Id was generated by the host*/
/* drop duplicate responses */
if (isHostDuplicate(msgId)){
/* drop duplicate responses */
return;
}
......@@ -375,15 +370,14 @@ public class BasicCoapSocketHandler implements CoapSocketHandler {
}
msg.setChannel(channel);
/* separate response ACK */
if (packetType == CoapPacketType.ACK ){
/* separate response ACK */
channel.handleMessage(msg);
return;
}
/* connection closed by remote */
if (packetType == CoapPacketType.RST ){
/* connection closed by remote */
channel.handleMessage(msg);
return;
}
......@@ -393,10 +387,6 @@ public class BasicCoapSocketHandler implements CoapSocketHandler {
}
}
/**
*
* @return Milliseconds until the next timeout expires. POLLING_INTERVALL if there is no timeout to wait for.
*/
private long handleTimeouts(){
long nextTimeout = POLLING_INTERVALL;
......
/* Copyright 2015 University of Rostock
*
* 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.ws4d.coap.messages;
/**
* This Enumeration contains all response codes available for CoAP. <br>
* See rfc7252 - 5.9. "Response Code Definitions" for further details.
* @author Bjoern Konieczek <bjoern.konieczek@uni-rostock.de>
* @author Christian Lerche <christian.lerche@uni-rostock.de>
* @author Bjrn Butzin <bjoern.butzin@uni-rostock.de>
*/
public enum CoapResponseCode {
//Success 2.xx
Created_201(65),
Deleted_202(66),
Valid_203(67),
Changed_204(68),
Content_205(69),
//FIXME: Where is it defined? NOT part of rfc7252! Missing in parse & toString function
Continue_231(95),
//Client Error 4.xx
Bad_Request_400(128),
Unauthorized_401(129),
Bad_Option_402(130),
Forbidden_403(131),
Not_Found_404(132),
Method_Not_Allowed_405(133),
Not_Acceptable_406(134),
Method_Not_Allowed_405(133),
Precondition_Failed_412(140),
Request_Entity_To_Large_413(141),
Unsupported_Media_Type_415(143),
//Server Error 5.xx
Internal_Server_Error_500(160),
Not_Implemented_501(161),
Bad_Gateway_502(162),
Service_Unavailable_503(163),
Gateway_Timeout_504(164),
Proxying_Not_Supported_505(165),
//additional, NOT part of rfc7252
Proxying_Not_Supported_505(165),
UNKNOWN(-1);
private int code;
private CoapResponseCode(int code) {
this.code = code;
}
/**
* @return The codeValue of the ENUM element.
*/
public int getValue() {
return code;
}
/**
* @param codeValue the code for the response code.
* @return The ENUM element matching the codeValue. <br>
* UNKNOWN, if the codeValue doesn't match any ENUM element.
* @throws IllegalArgumentException if codeValue is out of range.
*/
public static CoapResponseCode parseResponseCode(int codeValue) {
switch (codeValue) {
/* 32..63: reserved */
////Success 2.xx
/* 64 is not used anymore */
// case 64:
// this.code = ResponseCode.OK_200;
// break;
case 65: return Created_201;
case 66: return Deleted_202;
case 67: return Valid_203;
case 68: return Changed_204;
case 69: return Content_205;
//Client Error 4.xx
case 95: return Continue_231;
case 128: return Bad_Request_400;
case 129: return Unauthorized_401;
case 130: return Bad_Option_402;
case 131: return Forbidden_403;
case 132: return Not_Found_404;
case 133: return Method_Not_Allowed_405;
case 134: return Not_Acceptable_406;
case 140: return Precondition_Failed_412;
case 141: return Request_Entity_To_Large_413;
case 143: return Unsupported_Media_Type_415;
//Server Error 5.xx
case 160: return Internal_Server_Error_500;
case 161: return Not_Implemented_501;
case 162: return Bad_Gateway_502;
......@@ -113,38 +72,59 @@ public enum CoapResponseCode {
}
}
//TODO can be removed as it is the same as the super implementation
// @Override
// public String toString() {
// switch (this) {
//
// //Success 2.xx
// case Created_201: return "Created_201";
// case Deleted_202: return "Deleted_202";
// case Valid_203: return "Valid_203";
// case Changed_204: return "Changed_204";
// case Content_205: return "Content_205";
//
// //Client Error 4.xx
// case Bad_Request_400: return "Bad_Request_400";
// case Unauthorized_401: return "Unauthorized_401";
// case Bad_Option_402: return "Bad_Option_402";
// case Forbidden_403: return "Forbidden_403";
// case Not_Found_404: return "Not_Found_404";
// case Method_Not_Allowed_405: return "Method_Not_Allowed_405";
// case Precondition_Failed_412: return "Precondition_Failed_412";
// case Not_Acceptable_406: return "Not_Acceptable_406";
// case Request_Entity_To_Large_413: return "Request_Entity_To_Large_413";
// case Unsupported_Media_Type_415: return "Unsupported_Media_Type_415";
//
// //Server Error 5.xx
// case Internal_Server_Error_500:return "Internal_Server_Error_500";
// case Not_Implemented_501: return "Not_Implemented_501";
// case Bad_Gateway_502: return "Bad_Gateway_502";
// case Service_Unavailable_503: return "Service_Unavailable_503";
// case Gateway_Timeout_504: return "Gateway_Timeout_504";
// case Proxying_Not_Supported_505: return "Proxying_Not_Supported_505";
// default: return "Unknown_Response_Code";
// }
// }
public int getValue() {
return code;
}
@Override
public String toString() {
switch (this) {
case Created_201:
return "Created_201";
case Deleted_202:
return "Deleted_202";
case Valid_203:
return "Valid_203";
case Continue_231:
return "Continue_231";
case Changed_204:
return "Changed_204";
case Content_205:
return "Content_205";
case Bad_Request_400:
return "Bad_Request_400";
case Unauthorized_401:
return "Unauthorized_401";
case Bad_Option_402:
return "Bad_Option_402";
case Forbidden_403:
return "Forbidden_403";
case Not_Found_404:
return "Not_Found_404";
case Method_Not_Allowed_405:
return "Method_Not_Allowed_405";