Package ch.ethz.inf.vs.californium.network.stack

Source Code of ch.ethz.inf.vs.californium.network.stack.BlockwiseLayer$TimeoutForwarder

package ch.ethz.inf.vs.californium.network.stack;

import java.util.logging.Logger;

import ch.ethz.inf.vs.californium.coap.BlockOption;
import ch.ethz.inf.vs.californium.coap.CoAP.Code;
import ch.ethz.inf.vs.californium.coap.CoAP.ResponseCode;
import ch.ethz.inf.vs.californium.coap.CoAP.Type;
import ch.ethz.inf.vs.californium.coap.EmptyMessage;
import ch.ethz.inf.vs.californium.coap.Message;
import ch.ethz.inf.vs.californium.coap.MessageObserverAdapter;
import ch.ethz.inf.vs.californium.coap.OptionSet;
import ch.ethz.inf.vs.californium.coap.Request;
import ch.ethz.inf.vs.californium.coap.Response;
import ch.ethz.inf.vs.californium.network.Exchange;
import ch.ethz.inf.vs.californium.network.config.NetworkConfig;
import ch.ethz.inf.vs.californium.network.config.NetworkConfigDefaults;
import ch.ethz.inf.vs.californium.network.config.NetworkConfigObserverAdapter;

public class BlockwiseLayer extends AbstractLayer {

  /** The logger. */
  protected final static Logger LOGGER = Logger.getLogger(BlockwiseLayer.class.getCanonicalName());
 
  // TODO: Size Option. Include only in first block.
  // TODO: DoS: server should have max allowed blocks/bytes/time to allocate.
  // TODO: Random access for Cf servers: The draft still needs to specify a reaction to "overshoot"
  // TODO: Blockwise with separate response or NONs. Not yet mentioned in draft.
  // TODO: How should our client deal with a server that handles blocks non-atomic?
  // TODO: Forward cancellation and timeouts of a request to its blocks.
 
  /**
   * What if a request contains a Block2 option with size 128 but the response
   * is only 10 bytes long? Should we still add the block2 option to the
   * response? Currently, we do.
   * <p>
   * The draft needs to specify whether it is allowed to use separate
   * responses or NONs. Otherwise, I do not know whether I should allow (or
   * prevent) the resource to use it. Currently, we do not prevent it but I am
   * not sure what would happen if a resource used accept() or NONs.
   * <p>
   * What is the client supposed to do when it asks the server for block x but
   * receives a wrong block? The client cannot send a 4.08 (Request Entity
   * Incomplete). Should it reject it? Currently, we reject it and cancel the
   * request.
   * <p>
   * In a blockwise transfer of a response to a POST request, the draft should
   * mention whether the client should always include all options in each
   * request for the next block or not. The response is already produced at
   * the server, thus, there is no point in receiving them again. The draft
   * only states that the payload should be empty. Currently we always send
   * all options in each request (just in case) (except observe which is not
   * allowed).
   * <p>
   * When an observe notification is being sent blockwise, it is not clear
   * whether we are allowed to include the observe option in each response
   * block. In the draft, the observe option is left out but it would be
   * easier for us if we were allowed to include it. The policy which options
   * should be included in which block is not clear to me anyway. ETag is
   * always included, observe only in the first block, what about the others?
   * Currently, I send observe only in the first block so that it exactly
   * matches the example in the draft.
   */
 
  private int maxMsgSize;
  private int defaultBlockSize;
 
  public BlockwiseLayer(NetworkConfig config) {
    this.maxMsgSize = config.getInt(NetworkConfigDefaults.MAX_MESSAGE_SIZE);
    this.defaultBlockSize = config.getInt(NetworkConfigDefaults.DEFAULT_BLOCK_SIZE);
    LOGGER.config("Blockwise14 layer uses MAX_MESSAGE_SIZE: "+maxMsgSize+" and DEFAULT_BLOCK_SIZE:"+defaultBlockSize);
   
    config.addConfigObserver(new NetworkConfigObserverAdapter() {
      @Override
      public void changed(String key, int value) {
        if (NetworkConfigDefaults.MAX_MESSAGE_SIZE.equals(key))
          maxMsgSize = value;
        if (NetworkConfigDefaults.DEFAULT_BLOCK_SIZE.equals(key))
          defaultBlockSize = value;
      }
    });
  }
 
  @Override
  public void sendRequest(Exchange exchange, Request request) {
    if (requiresBlockwise(request)) {
      // This must be a large POST or PUT request
      LOGGER.fine("Request payload "+request.getPayloadSize()+"/"+maxMsgSize+" requires Blockwise");
      BlockwiseStatus status = findRequestBlockStatus(exchange, request);
     
      Request block = getNextRequestBlock(request, status);
     
      exchange.setRequestBlockStatus(status);
      exchange.setCurrentRequest(block);
      super.sendRequest(exchange, block);
     
    } else {
      exchange.setCurrentRequest(request);
      super.sendRequest(exchange, request);
    }
  }

  @Override
  public void receiveRequest(Exchange exchange, Request request) {
    if (request.getOptions().hasBlock1()) {
      // This must be a large POST or PUT request
      BlockOption block1 = request.getOptions().getBlock1();
      LOGGER.fine("Request contains block1 option "+block1);
     
      BlockwiseStatus status = findRequestBlockStatus(exchange, request);
      if (block1.getNum() == 0 && status.getCurrentNum() > 0) {
        // reset the blockwise transfer
        LOGGER.finer("Block1 num is 0, the client has restarted the blockwise transfer. Reset status.");
        status = new BlockwiseStatus(request.getOptions().getContentFormat());
        exchange.setRequestBlockStatus(status);
      }
     
      if (block1.getNum() == status.getCurrentNum()) {
       
        if (request.getOptions().getContentFormat()==status.getContentFormat()) {
          status.addBlock(request.getPayload());
        } else {
          Response error = Response.createPiggybackedResponse(request, ResponseCode.REQUEST_ENTITY_INCOMPLETE);
          error.getOptions().setBlock1(block1.getSzx(), block1.isM(), block1.getNum());
          error.setPayload("Changed Content-Format");
          request.setAcknowledged(true);
          exchange.setCurrentResponse(error);
          super.sendResponse(exchange, error);
          return;
        }
       
        status.setCurrentNum(status.getCurrentNum() + 1);
        if ( block1.isM() ) {
          LOGGER.finest("There are more blocks to come. Acknowledge this block.");
         
          if (request.isConfirmable()) {
            Response piggybacked = Response.createPiggybackedResponse(request, ResponseCode.CONTINUE);
            piggybacked.getOptions().setBlock1(block1.getSzx(), true, block1.getNum());
            piggybacked.setLast(false);
            request.setAcknowledged(true);
            exchange.setCurrentResponse(piggybacked);
            super.sendResponse(exchange, piggybacked);
          }
          // do not assemble and deliver the request yet
         
        } else {
          LOGGER.finer("This was the last block. Deliver request");
         
          // Remember block to acknowledge. TODO: We might make this a boolean flag in status.
          exchange.setBlock1ToAck(block1);
         
          // Block2 early negotiation
          earlyBlock2Negotiation(exchange, request);
         
          // Assemble and deliver
          Request assembled = new Request(request.getCode()); // getAssembledRequest(status, request);
          assembleMessage(status, assembled, request);
//          assembled.setAcknowledged(true); // TODO: prevents accept from sending ACK. Maybe the resource uses separate...
          exchange.setRequest(assembled);
          super.receiveRequest(exchange, assembled);
        }
       
      } else {
        // ERROR, wrong number, Incomplete
        LOGGER.warning("Wrong block number. Expected "+status.getCurrentNum()+" but received "+block1.getNum()+". Respond with 4.08 (Request Entity Incomplete)");
        Response error = Response.createPiggybackedResponse(request, ResponseCode.REQUEST_ENTITY_INCOMPLETE);
        error.getOptions().setBlock1(block1.getSzx(), block1.isM(), block1.getNum());
        error.setPayload("Wrong block number");
        request.setAcknowledged(true);
        exchange.setCurrentResponse(error);
        super.sendResponse(exchange, error);
      }
     
    } else if (exchange.getResponse()!=null && request.getOptions().hasBlock2()) {
      // The response has already been generated and the client just wants
      // the next block of it
      BlockOption block2 = request.getOptions().getBlock2();
      Response response = exchange.getResponse();
      BlockwiseStatus status = findResponseBlockStatus(exchange, response);
      status.setCurrentNum(block2.getNum());
      status.setCurrentSzx(block2.getSzx());
     
      Response block = getNextResponseBlock(response, status);
      block.setToken(request.getToken());
      block.getOptions().removeObserve();
     
      if (status.isComplete()) {
        // clean up blockwise status
        LOGGER.severe("Ongoing is complete "+status);
        exchange.setResponseBlockStatus(null);
      } else {
        LOGGER.severe("Ongoing is continuing "+status);
      }
     
      exchange.setCurrentResponse(block);
      super.sendResponse(exchange, block);
     
    } else {
      earlyBlock2Negotiation(exchange, request);

      exchange.setRequest(request);
      super.receiveRequest(exchange, request);
    }
  }

  @Override
  public void sendResponse(Exchange exchange, Response response) {
    BlockOption block1 = exchange.getBlock1ToAck();
    if (block1 != null)
      exchange.setBlock1ToAck(null);
   
    if (requireBlockwise(exchange, response)) {
      // This must be a large response to a GET or POST request (PUT?)
      LOGGER.fine("Response payload "+response.getPayloadSize()+"/"+maxMsgSize+" requires Blockwise");
     
      BlockwiseStatus status = findResponseBlockStatus(exchange, response);
     
      Response block = getNextResponseBlock(response, status);
      block.setType(response.getType()); // This is only true for the first block
      if (block1 != null) // in case we still have to ack the last block1
        block.getOptions().setBlock1(block1);
      if (block.getToken() == null)
        block.setToken(exchange.getRequest().getToken());
     
      if (response.getOptions().hasObserve()) {
        // the ACK for the first block should acknowledge the whole notification
        exchange.setCurrentResponse(response);
      } else {
        exchange.setCurrentResponse(block);
      }
      super.sendResponse(exchange, block);
     
    } else {
      if (block1 != null) response.getOptions().setBlock1(block1);
      exchange.setCurrentResponse(response);
      super.sendResponse(exchange, response);
    }
  }
 
  @Override
  public void receiveResponse(Exchange exchange, Response response) {
    if (!response.getOptions().hasBlock1() && !response.getOptions().hasBlock2()) {
      // There is no block1 or block2 option, therefore it is a normal response
      exchange.setResponse(response);
      super.receiveResponse(exchange, response);
      return;
    }
   
    if (response.getOptions().hasBlock1()) {
      // TODO: What if request has not been sent blockwise (server error)
      BlockOption block1 = response.getOptions().getBlock1();
      LOGGER.finer("Response acknowledges block "+block1);
     
      BlockwiseStatus status = exchange.getRequestBlockStatus();
      if (! status.isComplete()) {
        // TODO: the response code should be CONTINUE. Otherwise deliver
        // Send next block
        int currentSize = 1 << (4 + status.getCurrentSzx());
        int nextNum = status.getCurrentNum() + currentSize / block1.getSize();
        LOGGER.finer("Send next block num = "+nextNum);
        status.setCurrentNum(nextNum);
        status.setCurrentSzx(block1.getSzx());
        Request nextBlock = getNextRequestBlock(exchange.getRequest(), status);
        if (nextBlock.getToken() == null)
          nextBlock.setToken(response.getToken()); // reuse same token
        exchange.setCurrentRequest(nextBlock);
        super.sendRequest(exchange, nextBlock);
        // do not deliver response
       
      } else if (!response.getOptions().hasBlock2()) {
        // All request block have been acknowledged and we receive a piggy-backed
        // response that needs no blockwise transfer. Thus, deliver it.
        super.receiveResponse(exchange, response);
      } else {
        LOGGER.fine("Response has Block2 option and is therefore sent blockwise");
      }
    }
   
    if (response.getOptions().hasBlock2()) {
      BlockOption block2 = response.getOptions().getBlock2();
      BlockwiseStatus status = findResponseBlockStatus(exchange, response);
     
      if (block2.getNum() == status.getCurrentNum()) {
        // We got the block we expected :-)
        status.addBlock(response.getPayload());
        if (response.getOptions().hasObserve())
          status.setObserve(response.getOptions().getObserve());
       
        if (block2.isM()) {
          LOGGER.finer("Request the next response block");
          // TODO: If this is a notification, do we have to use
          // another token now?

          Request request = exchange.getRequest();
          int num = block2.getNum() + 1;
          int szx = block2.getSzx();
          boolean m = false;
          Request block = new Request(request.getCode());
          block.setOptions(new OptionSet(request.getOptions()));
          block.setDestination(request.getDestination());
          block.setDestinationPort(request.getDestinationPort());
         
          block.setType(request.getType()); // NON could make sense over SMS or similar transports
          block.getOptions().setBlock2(szx, m, num);
          status.setCurrentNum(num);
         
          // to make it easier for Observe, we do not re-use the Token
//          if (!response.getOptions().hasObserve()) {
//            block.setToken(request.getToken());
//          }
         
          // make sure not to use Observe for block retrieval
          block.getOptions().removeObserve();
         
          exchange.setCurrentRequest(block);
          super.sendRequest(exchange, block);
         
        } else {
          LOGGER.finer("We have received all "+status.getBlockCount()+" blocks of the response. Assemble and deliver");
          Response assembled = new Response(response.getCode());
          assembleMessage(status, assembled, response);
          assembled.setType(response.getType());
         
          // Check if this response is a notification
          int observe = status.getObserve();
          if (observe != BlockwiseStatus.NO_OBSERVE) {
            assembled.getOptions().setObserve(observe);
            // This is necessary for notifications that are sent blockwise:
            // Reset block number AND container with all blocks
            exchange.setResponseBlockStatus(null);
          }
         
          LOGGER.fine("Assembled response: "+assembled);
          exchange.setResponse(assembled);
          super.receiveResponse(exchange, assembled);
        }
       
      } else {
        // ERROR, wrong block number (server error)
        // TODO: This scenario is not specified in the draft.
        // Currently, we reject it and cancel the request.
        LOGGER.warning("Wrong block number. Expected "+status.getCurrentNum()+" but received "+block2.getNum()+". Reject response; exchange has failed.");
        if (response.getType()==Type.CON) {
          EmptyMessage rst = EmptyMessage.newRST(response);
          super.sendEmptyMessage(exchange, rst);
        }
        exchange.getRequest().cancel();
      }
    }
  }

  @Override
  public void sendEmptyMessage(Exchange exchange, EmptyMessage message) {
    super.sendEmptyMessage(exchange, message);
  }

  @Override
  public void receiveEmptyMessage(Exchange exchange, EmptyMessage message) {
    super.receiveEmptyMessage(exchange, message);
  }
 
  /////////// HELPER METHODS //////////
 
  private void earlyBlock2Negotiation(Exchange exchange, Request request) {
    // Call this method when a request has completely arrived (might have
    // been sent in one piece without blockwise).
    if (request.getOptions().hasBlock2()) {
      BlockOption block2 = request.getOptions().getBlock2();
      LOGGER.fine("Request demands blockwise transfer of response with option "+block2+". Create and set new block2 status");
      BlockwiseStatus status2 = new BlockwiseStatus(request.getOptions().getContentFormat(), block2.getNum(), block2.getSzx());
      exchange.setResponseBlockStatus(status2);
    }
  }
 
  private BlockwiseStatus findRequestBlockStatus(Exchange exchange, Request request) {
    // NOTICE: This method is used by sendRequest and receiveRequest. Be
    // careful, making changes to the status in here.
    BlockwiseStatus status = exchange.getRequestBlockStatus();
    if (status == null) {
      status = new BlockwiseStatus(request.getOptions().getContentFormat());
      status.setCurrentSzx( computeSZX(defaultBlockSize) );
      exchange.setRequestBlockStatus(status);
      LOGGER.finer("There is no assembler status yet. Create and set new block1 status: "+status);
    }
    return status;
  }
 
  private BlockwiseStatus findResponseBlockStatus(Exchange exchange, Response response) {
    // NOTICE: This method is used by sendResponse and receiveResponse. Be
    // careful, making changes to the status in here.
    BlockwiseStatus status = exchange.getResponseBlockStatus();
    if (status == null) {
      status = new BlockwiseStatus(response.getOptions().getContentFormat());
      status.setCurrentSzx( computeSZX(defaultBlockSize) );
      exchange.setResponseBlockStatus(status);
      LOGGER.finer("There is no blockwise status yet. Create and set new block2 status: "+status);
    } else {
      LOGGER.finer("Current blockwise status: "+status);
    }
    return status;
  }
 
  private Request getNextRequestBlock(Request request, BlockwiseStatus status) {
    int num = status.getCurrentNum();
    int szx = status.getCurrentSzx();
    Request block = new Request(request.getCode());
    block.setOptions(new OptionSet(request.getOptions()));
    block.setDestination(request.getDestination());
    block.setDestinationPort(request.getDestinationPort());
    block.setToken(request.getToken());
    block.setType(Type.CON);
   
    int currentSize = 1 << (4 + szx);
    int from = num * currentSize;
    int to = Math.min((num + 1) * currentSize, request.getPayloadSize());
    int length = to - from;
    byte[] blockPayload = new byte[length];
    System.arraycopy(request.getPayload(), from, blockPayload, 0, length);
    block.setPayload(blockPayload);
   
    boolean m = (to < request.getPayloadSize());
    block.getOptions().setBlock1(szx, m, num);
   
    status.setComplete(!m);
    return block;
  }
 
  private Response getNextResponseBlock(Response response, BlockwiseStatus status) {
    int szx = status.getCurrentSzx();
    int num = status.getCurrentNum();
    Response block = new Response(response.getCode());
//    block.setType(response.getType()); // NO! First block has type from origin response, all other depend on current request
    block.setDestination(response.getDestination());
    block.setDestinationPort(response.getDestinationPort());
    block.setToken(response.getToken());
    block.setOptions(new OptionSet(response.getOptions()));
    block.addMessageObserver(new TimeoutForwarder(response));
   
    if (response.getPayloadSize() > 0) {
      int currentSize = 1 << (4 + szx);
      int from = num * currentSize;
      int to = Math.min((num + 1) * currentSize, response.getPayloadSize());
      int length = to - from;
      byte[] blockPayload = new byte[length];
      System.arraycopy(response.getPayload(), from, blockPayload, 0, length);
      block.setPayload(blockPayload);
     
      boolean m = (to < response.getPayloadSize());
      block.getOptions().setBlock2(szx, m, num);
      block.setLast(!m);
     
      status.setComplete(!m);
    } else {
      block.getOptions().setBlock2(szx, false, 0);
      block.setLast(true);
      status.setComplete(true);
    }
    return block;
  }
 
  private void assembleMessage(BlockwiseStatus status, Message message, Message last) {
    // The assembled request will contain the options of the last block
    message.setMID(last.getMID());
    message.setSource(last.getSource());
    message.setSourcePort(last.getSourcePort());
    message.setToken(last.getToken());
    message.setType(last.getType());
    message.setOptions(new OptionSet(last.getOptions()));
   
    int length = 0;
    for (byte[] block:status.getBlocks())
      length += block.length;
   
    byte[] payload = new byte[length];
    int offset = 0;
    for (byte[] block:status.getBlocks()) {
      System.arraycopy(block, 0, payload, offset, block.length);
      offset += block.length;
    }
   
    message.setPayload(payload);
  }
 
  private boolean requiresBlockwise(Request request) {
    if (request.getCode() == Code.PUT || request.getCode() == Code.POST) {
      return request.getPayloadSize() > maxMsgSize;
    } else return false;
  }
 
  private boolean requireBlockwise(Exchange exchange, Response response) {
    return response.getPayloadSize() > maxMsgSize
        || exchange.getResponseBlockStatus() != null;
  }
 
  /**
   * Encodes a block size into a 3-bit SZX value as specified by
   * draft-ietf-core-block-03, section-2.1:
   *
   * 16 bytes = 2^4 --> 0
   * ...
   * 1024 bytes = 2^10 -> 6
   *
   */
  private int computeSZX(int blockSize) {
    return (int)(Math.log(blockSize)/Math.log(2)) - 4;
  }
 
  // When a timeout occurs for a block it has to be forwarded to the origin
  // response.
  public static class TimeoutForwarder extends MessageObserverAdapter {
   
    private Message message;
   
    public TimeoutForwarder(Message message) {
      this.message = message;
    }
   
    @Override
    public void onTimeout() {
      message.setTimedOut(true);
    }
  }
}
TOP

Related Classes of ch.ethz.inf.vs.californium.network.stack.BlockwiseLayer$TimeoutForwarder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.