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

Source Code of ch.ethz.inf.vs.californium.network.Matcher$ExchangeObserverImpl

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

import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

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.Request;
import ch.ethz.inf.vs.californium.coap.Response;
import ch.ethz.inf.vs.californium.network.Exchange.KeyMID;
import ch.ethz.inf.vs.californium.network.Exchange.KeyToken;
import ch.ethz.inf.vs.californium.network.Exchange.KeyUri;
import ch.ethz.inf.vs.californium.network.Exchange.Origin;
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.deduplication.Deduplicator;
import ch.ethz.inf.vs.californium.network.deduplication.DeduplicatorFactory;

public class Matcher {

  private final static Logger LOGGER = Logger.getLogger(Matcher.class.getCanonicalName());
 
  private boolean started;
  private ExchangeObserver exchangeObserver = new ExchangeObserverImpl();
 
  /** The executor. */
  private ScheduledExecutorService executor;
 
  // TODO: Make per endpoint
  private AtomicInteger currendMID;
 
  private ConcurrentHashMap<KeyMID, Exchange> exchangesByMID; // Outgoing
  private ConcurrentHashMap<KeyToken, Exchange> exchangesByToken;
 
  private ConcurrentHashMap<KeyUri, Exchange> ongoingExchanges; // for blockwise
 
  // TODO: Multicast Exchanges: should not be removed from deduplicator
  private Deduplicator deduplicator;
  // Idea: Only store acks/rsts and not the whole exchange. Responses should be sent CON.
 
  public Matcher(NetworkConfig config) {
    this.started = false;
    this.exchangesByMID = new ConcurrentHashMap<KeyMID, Exchange>();
    this.exchangesByToken = new ConcurrentHashMap<KeyToken, Exchange>();
    this.ongoingExchanges = new ConcurrentHashMap<KeyUri, Exchange>();

    DeduplicatorFactory factory = DeduplicatorFactory.getDeduplicatorFactory();
    this.deduplicator = factory.createDeduplicator(config);
   
    if (config.getBoolean(NetworkConfigDefaults.USE_RANDOM_MID_START))
      currendMID = new AtomicInteger(new Random().nextInt(1<<16));
    else currendMID = new AtomicInteger(0);
  }
 
  public synchronized void start() {
    if (started) return;
    else started = true;
    if (executor == null)
      throw new IllegalStateException("Matcher has no executor to schedule exchnage removal");
    deduplicator.start();
  }
 
  public synchronized void stop() {
    if (!started) return;
    else started = false;
    deduplicator.stop();
    clear();
  }
 
  public synchronized void setExecutor(ScheduledExecutorService executor) {
    deduplicator.setExecutor(executor);
    this.executor = executor;
  }
 
  public void sendRequest(Exchange exchange, Request request) {
    if (request.getMID() == Message.NONE)
      request.setMID(currendMID.getAndIncrement()%(1<<16));

    /*
     * The request is a CON or NCON and must be prepared for these responses
     * - CON  => ACK/RST/ACK+response/CON+response/NCON+response
     * - NCON => RST/CON+response/NCON+response
     * If this request goes lost, we do not get anything back.
     */
   
    KeyMID idByMID = new KeyMID(request.getMID(),
        request.getDestination().getAddress(), request.getDestinationPort());
    KeyToken idByTok = new KeyToken(request.getToken(),
        request.getDestination().getAddress(), request.getDestinationPort());
   
    exchange.setObserver(exchangeObserver);
   
    LOGGER.fine("Stored open request by "+idByMID+", "+idByTok);
   
    exchangesByMID.put(idByMID, exchange);
    exchangesByToken.put(idByTok, exchange);
  }

  public void sendResponse(Exchange exchange, Response response) {
    if (response.getMID() == Message.NONE)
      response.setMID(currendMID.getAndIncrement()%(1<<16));
   
    /*
     * The response is a CON or NON or ACK and must be prepared for these
     * - CON  => ACK/RST // we only care to stop retransmission
     * - NCON => RST // we don't care
     * - ACK  => nothing!
     * If this response goes lost, we must be prepared to get the same
     * CON/NCON request with same MID again. We then find the corresponding
     * exchange and the retransmissionlayer resends this response.
     */
   
    if (response.getDestination() == null)
      throw new NullPointerException("Response has no destination address set");
    if (response.getDestinationPort() == 0)
      throw new NullPointerException("Response hsa no destination port set");
   
    // Insert CON and NON to match ACKs and RSTs to the exchange
    KeyMID idByMID = new KeyMID(response.getMID(),
        response.getDestination().getAddress(), response.getDestinationPort());
    exchangesByMID.put(idByMID, exchange);
   
    if (response.getOptions().hasBlock2()) {
      Request request = exchange.getRequest();
      KeyUri idByUri = new KeyUri(request.getURI(),
          response.getDestination().getAddress(), response.getDestinationPort());
      if (exchange.getResponseBlockStatus()!=null && !response.getOptions().hasObserve()) {
        // Remember ongoing blockwise GET requests
        LOGGER.fine("Ongoing Block2 started, storing "+idByUri + "\nOngoing " + request + "\nOngoing " + response);
        ongoingExchanges.put(idByUri, exchange);
      } else {
        LOGGER.fine("Ongoing Block2 completed, cleaning up "+idByUri + "\nOngoing " + request + "\nOngoing " + response);
        ongoingExchanges.remove(idByUri);
      }
    }
   
    if (response.getType() == Type.ACK || response.getType() == Type.NON) {
      // Since this is an ACK or NON, the exchange is over with sending this response.
      if (response.isLast()) {
        exchange.setComplete();
      }
    } // else this is a CON and we need to wait for the ACK or RST
  }

  public void sendEmptyMessage(Exchange exchange, EmptyMessage message) {
   
    if (message.getType() == Type.RST && exchange != null) {
      // We have rejected the request or response
      exchange.setComplete();
    }
   
    /*
     * We do not expect any response for an empty message
     */
    if (message.getMID() == Message.NONE)
      LOGGER.severe("Empy message "+ message+" has no MID // debugging");
  }

  public Exchange receiveRequest(Request request) {
    /*
     * This request could be
     *  - Complete origin request => deliver with new exchange
     *  - One origin block        => deliver with ongoing exchange
     *  - Complete duplicate request or one duplicate block (because client got no ACK)
     *      =>
     *     if ACK got lost => resend ACK
     *     if ACK+response got lost => resend ACK+response
     *     if nothing has been sent yet => do nothing
     * (Retransmission is supposed to be done by the retransm. layer)
     */
   
    KeyMID idByMID = new KeyMID(request.getMID(), request.getSource().getAddress(), request.getSourcePort());
   
    /*
     * The differentiation between the case where there is a Block1 or
     * Block2 option and the case where there is none has the advantage that
     * all exchanges that do not need blockwise transfer have simpler and
     * faster code than exchanges with blockwise transfer.
     */
    if (!request.getOptions().hasBlock1() && !request.getOptions().hasBlock2()) {

      Exchange exchange = new Exchange(request, Origin.REMOTE);
      Exchange previous = deduplicator.findPrevious(idByMID, exchange);
      if (previous == null) {
        exchange.setObserver(exchangeObserver);
        return exchange;
       
      } else {
        LOGGER.info("Message is a duplicate, ignore: "+request);
        request.setDuplicate(true);
        return previous;
      }
     
    } else {
     
      KeyUri idByUri = new KeyUri(request.getURI(),
          request.getSource().getAddress(), request.getSourcePort());
     
      LOGGER.fine("Lookup ongoing exchange for "+idByUri);
      Exchange ongoing = ongoingExchanges.get(idByUri);
      if (ongoing != null) {
       
        Exchange prev = deduplicator.findPrevious(idByMID, ongoing);
        if (prev != null) {
          LOGGER.info("Message is a duplicate: "+request);
          request.setDuplicate(true);
        }
        return ongoing;
   
      } else {
        // We have no ongoing exchange for that request block.
        /*
         * Note the difficulty of the following code: The first message
         * of a blockwise transfer might arrive twice due to a
         * retransmission. The new Exchange must be inserted in both the
         * hash map 'ongoing' and the deduplicator. They must agree on
         * which exchange they store!
         */
       
        Exchange exchange = new Exchange(request, Origin.REMOTE);
        Exchange previous = deduplicator.findPrevious(idByMID, exchange);
        LOGGER.fine("New ongoing exchange for remote Block1 request with key "+idByUri);
        if (previous == null) {
          exchange.setObserver(exchangeObserver);
          ongoingExchanges.put(idByUri, exchange);
          return exchange;
        } else {
          LOGGER.info("Message is a duplicate: "+request);
          request.setDuplicate(true);
          return previous;
        }
      } // if ongoing
    } // if blockwise
  }

  public Exchange receiveResponse(Response response) {
   
    /*
     * This response could be
     * - The first CON/NCON/ACK+response => deliver
     * - Retransmitted CON (because client got no ACK)
     *     => resend ACK
     */

    KeyMID idByMID = new KeyMID(response.getMID(),
        response.getSource().getAddress(), response.getSourcePort());
   
    KeyToken idByTok = new KeyToken(response.getToken(),
        response.getSource().getAddress(), response.getSourcePort());
   
    Exchange exchange = exchangesByToken.get(idByTok);
   
    if (exchange != null) {
      // There is an exchange with the given token
     
      Exchange prev = deduplicator.findPrevious(idByMID, exchange);
      if (prev != null) { // (and thus it holds: prev == exchange)
        LOGGER.fine("Duplicate response "+response);
        response.setDuplicate(true);
      } else {
        LOGGER.fine("Exchange got reply: Cleaning up "+idByMID);
        exchangesByMID.remove(idByMID);
      }
     
      if (response.getType() == Type.ACK && exchange.getCurrentRequest().getMID() != response.getMID()) {
        // The token matches but not the MID. This is a response for an older exchange
        LOGGER.warning("Token matches but not MID: Expected "+exchange.getCurrentRequest().getMID()+" but was "+response.getMID());
        // ignore response
        return null;
      } else {
        // this is a separate response that we can deliver
        return exchange;
      }
     
    } else {
      // There is no exchange with the given token.
      if (response.getType() != Type.ACK) {
        LOGGER.info("Response with unknown Token "+idByTok+": Rejecting "+response);
        // This is a totally unexpected response.
        EmptyMessage rst = EmptyMessage.newRST(response);
        sendEmptyMessage(exchange, rst);
      }
      // ignore response
      return null;
    }
  }

  public Exchange receiveEmptyMessage(EmptyMessage message) {
   
    KeyMID idByMID = new KeyMID(message.getMID(),
        message.getSource().getAddress(), message.getSourcePort());
   
    Exchange exchange = exchangesByMID.get(idByMID);
   
    if (exchange != null) {
      LOGGER.fine("Exchange got reply: Cleaning up "+idByMID);
      exchangesByMID.remove(idByMID);
      return exchange;
    } else {
      LOGGER.info("Matcher received empty message that does not match any exchange: "+message);
      // ignore message;
      return null;
    } // else, this is an ACK for an unknown exchange and we ignore it
  }
 
  public void clear() {
    this.exchangesByMID.clear();
    this.exchangesByToken.clear();
    this.ongoingExchanges.clear();
    deduplicator.clear();
  }
 
  private class ExchangeObserverImpl implements ExchangeObserver {

    @Override
    public void completed(Exchange exchange) {
     
      /*
       * Logging in this method leads to significant performance loss.
       * Uncomment logging code only for debugging purposes.
       */
     
      if (exchange.getOrigin() == Origin.LOCAL) {
        // this endpoint created the Exchange by issuing a request
        Request request = exchange.getRequest();
        KeyToken idByTok = new KeyToken(exchange.getCurrentRequest().getToken(), request.getDestination().getAddress(), request.getDestinationPort());
        KeyMID idByMID = new KeyMID(request.getMID(), request.getDestination().getAddress(), request.getDestinationPort());
       
//        LOGGER.fine("Exchange completed: Cleaning up "+idByTok);
        exchangesByToken.remove(idByTok);
        // in case an empty ACK was lost
        exchangesByMID.remove(idByMID);
     
      } else {
        // this endpoint created the Exchange to respond a request
        Request request = exchange.getCurrentRequest();
        if (request != null) {
          // TODO: We can optimize this and only do it, when the request really had blockwise transfer
          KeyUri uriKey = new KeyUri(request.getURI(),
              request.getSource().getAddress(), request.getSourcePort());
//          LOGGER.warning("++++++++++++++++++Remote ongoing completed, cleaning up "+uriKey);
          ongoingExchanges.remove(uriKey);
        }
        // TODO: What if the request is only a block?
        // TODO: This should only happen if the transfer was blockwise

        Response response = exchange.getResponse();
        if (response != null) {
          // only response MIDs are stored for ACK and RST, no reponse Tokens
          KeyMID midKey = new KeyMID(response.getMID(),
              response.getDestination().getAddress(), response.getDestinationPort());
//          LOGGER.warning("++++++++++++++++++Remote ongoing completed, cleaning up "+midKey);
          exchangesByMID.remove(midKey);
        }
      }
    }
   
  }
 
}
TOP

Related Classes of ch.ethz.inf.vs.californium.network.Matcher$ExchangeObserverImpl

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.