/* 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. * 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.connection; import java.io.ByteArrayOutputStream; import java.net.InetAddress; import org.ws4d.coap.interfaces.CoapClient; import org.ws4d.coap.interfaces.CoapClientChannel; import org.ws4d.coap.interfaces.CoapMessage; import org.ws4d.coap.interfaces.CoapRequest; import org.ws4d.coap.interfaces.CoapResponse; 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.CoapPacketType; import org.ws4d.coap.messages.CoapRequestCode; /** * @author Christian Lerche <christian.lerche@uni-rostock.de> */ public class BasicCoapClientChannel extends BasicCoapChannel implements CoapClientChannel { CoapClient client = null; ClientBlockContext blockContext = null; CoapRequest lastRequest = null; Object trigger = null; public BasicCoapClientChannel(CoapSocketHandler socketHandler, CoapClient client, InetAddress remoteAddress, int remotePort) { super(socketHandler, remoteAddress, remotePort); this.client = client; } public void close() { socketHandler.removeClientChannel(this); } public void handleMessage(CoapMessage message) { if (message.isRequest()){ /* this is a client channel, no requests allowed */ message.getChannel().sendMessage(new CoapEmptyMessage(CoapPacketType.RST, message.getMessageID())); return; } if (message.isEmpty() && message.getPacketType() == CoapPacketType.ACK){ /* this is the ACK of a separate response */ //TODO: implement a handler or listener, that informs a client when a sep. resp. ack was received return; } if (message.getPacketType() == CoapPacketType.CON) { /* this is a separate response */ /* send ACK */ this.sendMessage(new CoapEmptyMessage(CoapPacketType.ACK, message.getMessageID())); } /* check for blockwise transfer */ CoapBlockOption block2 = message.getBlock2(); if (blockContext == null && block2 != null){ /* initiate blockwise transfer */ blockContext = new ClientBlockContext(block2, maxReceiveBlocksize); blockContext.setFirstRequest(lastRequest); blockContext.setFirstResponse((CoapResponse) message); } if (blockContext!= null){ /*blocking option*/ if (!blockContext.addBlock(message, block2)){ /*this was not a correct block*/ /* TODO: implement either a RST or ignore this packet */ } if (!blockContext.isFinished()){ /* TODO: implement a counter to avoid an infinity req/resp loop: * if the same block is received more than x times -> rst the connection * implement maxPayloadSize to avoid an infinity payload */ CoapBlockOption newBlock = blockContext.getNextBlock(); if (lastRequest == null){ /*TODO: this should never happen*/ System.out.println("ERROR: client channel: lastRequest == null"); } else { /* create a new request for the next block */ BasicCoapRequest request = new BasicCoapRequest(lastRequest.getPacketType(), lastRequest.getRequestCode(), channelManager.getNewMessageID()); request.copyHeaderOptions((BasicCoapRequest) blockContext.getFirstRequest()); request.setBlock2(newBlock); sendMessage(request); } /* TODO: implement handler, inform the client that a block (but not the complete message) was received*/ return; } /* blockwise transfer finished */ message.setPayload(blockContext.getPayload()); /* TODO: give the payload separately and leave the original message as they is*/ } /* normal or separate response */ client.onResponse(this, (BasicCoapResponse) message); } public void lostConnection(boolean notReachable, boolean resetByServer) { client.onConnectionFailed(this, notReachable, resetByServer); } public BasicCoapRequest createRequest(boolean reliable, CoapRequestCode requestCode) { BasicCoapRequest msg = new BasicCoapRequest( reliable ? CoapPacketType.CON : CoapPacketType.NON, requestCode, channelManager.getNewMessageID()); msg.setChannel(this); return msg; } @Override public void sendMessage(CoapMessage msg) { super.sendMessage(msg); //TODO: check lastRequest = (CoapRequest) msg; } // public DefaultCoapClientChannel(CoapChannelManager channelManager) { // super(channelManager); // } // // @Override // public void connect(String remoteHost, int remotePort) { // socket = null; // if (remoteHost!=null && remotePort!=-1) { // try { // socket = new DatagramSocket(); // } catch (SocketException e) { // e.printStackTrace(); // } // } // // try { // InetAddress address = InetAddress.getByName(remoteHost); // socket.connect(address, remotePort); // super.establish(socket); // } catch (UnknownHostException e) { // e.printStackTrace(); // } // } private class ClientBlockContext{ ByteArrayOutputStream payload = new ByteArrayOutputStream(); boolean finished = false; CoapBlockSize blockSize; //null means no block option CoapRequest request; CoapResponse response; public ClientBlockContext(CoapBlockOption blockOption, CoapBlockSize maxBlocksize) { /* determine the right blocksize (min of remote and max)*/ if (maxBlocksize == null){ blockSize = blockOption.getBlockSize(); } else { int max = maxBlocksize.getSize(); int remote = blockOption.getBlockSize().getSize(); if (remote < max){ blockSize = blockOption.getBlockSize(); } else { blockSize = maxBlocksize; } } } public byte[] getPayload() { return payload.toByteArray(); } public boolean addBlock(CoapMessage msg, CoapBlockOption block){ int blockPos = block.getBytePosition(); int blockLength = msg.getPayloadLength(); int bufSize = payload.size(); /*TODO: check if payload length = blocksize (except for the last block)*/ if (blockPos > bufSize){ /* data is missing before this block */ return false; } else if ((blockPos + blockLength) <= bufSize){ /* data already received */ return false; } int offset = bufSize - blockPos; payload.write(msg.getPayload(), offset, blockLength - offset); if (block.isLast()){ /* was this the last block */ finished = true; } return true; } public CoapBlockOption getNextBlock() { int num = payload.size() / blockSize.getSize(); //ignore the rest (no rest should be there) return new CoapBlockOption(num, false, blockSize); } public boolean isFinished() { return finished; } public CoapRequest getFirstRequest() { return request; } public void setFirstRequest(CoapRequest request) { this.request = request; } public CoapResponse getFirstResponse() { return response; } public void setFirstResponse(CoapResponse response) { this.response = response; } } public void setTrigger(Object o) { trigger = o; } public Object getTrigger() { return trigger; } }