All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.named_data.jndn.sync.ChronoSync2013 Maven / Gradle / Ivy

Go to download

jNDN is a new implementation of a Named Data Networking client library written in Java. It is wire format compatible with the new NDN-TLV encoding, with NDNx and PARC's CCNx.

There is a newer version: 0.25
Show newest version
/**
 * Copyright (C) 2014-2017 Regents of the University of California.
 * @author: Jeff Thompson 
 * Derived from ChronoChat-js by Qiuhan Ding and Wentao Shang.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 * A copy of the GNU Lesser General Public License is in the file COPYING.
 */

package net.named_data.jndn.sync;

import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.named_data.jndn.Data;
import net.named_data.jndn.Face;
import net.named_data.jndn.Interest;
import net.named_data.jndn.InterestFilter;
import net.named_data.jndn.Name;
import net.named_data.jndn.OnData;
import net.named_data.jndn.OnInterestCallback;
import net.named_data.jndn.OnRegisterFailed;
import net.named_data.jndn.OnTimeout;
import net.named_data.jndn.security.KeyChain;
import net.named_data.jndn.security.SecurityException;
import net.named_data.jndn.util.Blob;
import net.named_data.jndn.util.MemoryContentCache;

/**
 * ChronoSync2013 implements the NDN ChronoSync protocol as described in the
 * 2013 paper "Let's ChronoSync: Decentralized Dataset State Synchronization in
 * Named Data Networking". http://named-data.net/publications/chronosync .
 * @note The support for ChronoSync is experimental and the API is not finalized.
 * See the API docs for more detail at
 * http://named-data.net/doc/ndn-ccl-api/chrono-sync2013.html .
 */
public class ChronoSync2013 implements OnInterestCallback, OnData, OnTimeout {
  // Use ArrayList without generics so it works with older Java compilers.
  public interface OnReceivedSyncState {
    void onReceivedSyncState(List syncStates /* of SyncState */, boolean isRecovery);
  }

  public interface OnInitialized {
    void onInitialized();
  }

    /**
   * Create a new ChronoSync2013 to communicate using the given face. Initialize
   * the digest log with a digest of "00" and and empty content. Register the
   * applicationBroadcastPrefix to receive interests for sync state messages and
   * express an interest for the initial root digest "00".
   * @note Your application must call processEvents. Since processEvents
   * modifies the internal ChronoSync data structures, your application should
   * make sure that it calls processEvents in the same thread as this
   * constructor (which also modifies the data structures).
   * @param onReceivedSyncState When ChronoSync receives a sync state message,
   * this calls onReceivedSyncState.onReceivedSyncState(syncStates, isRecovery)
   * where syncStates is the
   * list of SyncState messages and isRecovery is true if this is the initial
   * list of SyncState messages or from a recovery interest. (For example, if
   * isRecovery is true, a chat application would not want to re-display all
   * the associated chat messages.) The callback should send interests to fetch
   * the application data for the sequence numbers in the sync state.
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param onInitialized This calls onInitialized.onInitialized() when the
   * first sync data is received (or the interest times out because there are no
   * other publishers yet).
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param applicationDataPrefix The prefix used by this application instance
   * for application data. For example, "/my/local/prefix/ndnchat4/0K4wChff2v".
   * This is used when sending a sync message for a new sequence number.
   * In the sync message, this uses applicationDataPrefix.toUri().
   * @param applicationBroadcastPrefix The broadcast name prefix including the
   * application name. For example, "/ndn/broadcast/ChronoChat-0.3/ndnchat1".
   * This makes a copy of the name.
   * @param sessionNo The session number used with the applicationDataPrefix in
   * sync state messages.
   * @param face The Face for calling registerPrefix and expressInterest. The
   * Face object must remain valid for the life of this ChronoSync2013 object.
   * @param keyChain To sign a data packet containing a sync state message, this
   * calls keyChain.sign(data, certificateName).
   * @param certificateName The certificate name of the key to use for signing a
   * data packet containing a sync state message.
   * @param syncLifetime The interest lifetime in milliseconds for sending
   * sync interests.
   * @param onRegisterFailed If failed to register the prefix to receive
   * interests for the applicationBroadcastPrefix, this calls
   * onRegisterFailed.onRegisterFailed(applicationBroadcastPrefix).
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   */
  public ChronoSync2013
    (OnReceivedSyncState onReceivedSyncState, OnInitialized onInitialized,
     Name applicationDataPrefix, Name applicationBroadcastPrefix, long sessionNo,
     Face face, KeyChain keyChain, Name certificateName, double syncLifetime,
     OnRegisterFailed onRegisterFailed) throws IOException, SecurityException
  {
    onReceivedSyncState_ = onReceivedSyncState;
    onInitialized_ = onInitialized;
    applicationDataPrefixUri_ = applicationDataPrefix.toUri();
    applicationBroadcastPrefix_ = new Name(applicationBroadcastPrefix);
    sessionNo_ = sessionNo;
    face_ = face;
    keyChain_ = keyChain;
    certificateName_ = new Name(certificateName);
    syncLifetime_ = syncLifetime;
    contentCache_ = new MemoryContentCache(face);

    SyncStateProto.SyncStateMsg emptyContent =
      SyncStateProto.SyncStateMsg.newBuilder().build();
    digestLog_.add(new DigestLogEntry("00", emptyContent.getSsList()));

    // Register the prefix with the contentCache_ and use our own onInterest
    //   as the onDataNotFound fallback.
    contentCache_.registerPrefix
      (applicationBroadcastPrefix_, onRegisterFailed, this);

    Interest interest = new Interest(applicationBroadcastPrefix_);
    interest.getName().append("00");
    interest.setInterestLifetimeMilliseconds(1000);
    face.expressInterest(interest, this, this.new InitialTimeout());
    logger_.log(Level.FINE, "initial sync expressed");
    logger_.log(Level.FINE, interest.getName().toUri());
  }

  /**
   * A SyncState holds the values of a sync state message which is passed to the
   * onReceivedSyncState callback which was given to the ChronoSyn2013
   * constructor. Note: this has the same info as the Protobuf class
   * SyncStateProto.SyncState, but we make a separate class so that we don't need the
   * Protobuf definition in the ChronoSync API.
   */
  public static class SyncState {
    public SyncState
      (String dataPrefixUri, long sessionNo, long sequenceNo,
       Blob applicationInfo)
    {
      dataPrefixUri_ = dataPrefixUri;
      sessionNo_ = sessionNo;
      sequenceNo_ = sequenceNo;
      applicationInfo_ = applicationInfo;
    }

    /**
     * Get the application data prefix for this sync state message.
     * @return The application data prefix as a Name URI string.
     */
    public final String
    getDataPrefix() { return dataPrefixUri_; }

    /**
     * Get the session number associated with the application data prefix for
     * this sync state message.
     * @return The session number.
     */
    public final long
    getSessionNo() { return sessionNo_; }

    /**
     * Get the sequence number for this sync state message.
     * @return The sequence number.
     */
    public final long
    getSequenceNo() { return sequenceNo_; }

    /**
     * Get the application info which was included when the sender published
     * the next sequence number.
     * @return The applicationInfo Blob. If the sender did not provide any,
     * return an isNull Blob.
     */
    public final Blob
    getApplicationInfo() { return applicationInfo_; }

    private final String dataPrefixUri_;
    private final long sessionNo_;
    private final long sequenceNo_;
    private final Blob applicationInfo_;
  }

  /**
   * A PrefixAndSessionNo holds a user's data prefix and session number (used to
   * return a list from getProducerPrefixes).
   */
  public static class PrefixAndSessionNo {
    public PrefixAndSessionNo(String dataPrefixUri, long sessionNo)
    {
      dataPrefixUri_ = dataPrefixUri;
      sessionNo_ = sessionNo;
    }

    /**
     * Get the application data prefix.
     * @return The application data prefix as a Name URI string.
     */
    public final String
    getDataPrefix() { return dataPrefixUri_; }

    /**
     * Get the session number associated with the application data prefix.
     * @return The session number.
     */
    public final long
    getSessionNo() { return sessionNo_; }

    private final String dataPrefixUri_;
    private final long sessionNo_;
  }

  /**
   * Get a copy of the current list of producer data prefixes, and the
   * associated session number. You can use these in getProducerSequenceNo().
   * This includes the prefix for this user.
   * @return A copy of the list of each producer prefix and session number.
   */
  public final List
  getProducerPrefixes()
  {
    List prefixes = new ArrayList();

    for (int i = 0; i < digestTree_.size(); ++i) {
      DigestTree.Node node = digestTree_.get(i);
      prefixes.add
        (new PrefixAndSessionNo(node.getDataPrefix(), node.getSessionNo()));
    }
    return prefixes;
  }

  /**
   * Get the current sequence number in the digest tree for the given
   * producer dataPrefix and sessionNo.
   * @param dataPrefix The producer data prefix as a Name URI string.
   * @param sessionNo The producer session number.
   * @return The current producer sequence number, or -1 if the producer
   * namePrefix and sessionNo are not in the digest tree.
   */
  public final long
  getProducerSequenceNo(String dataPrefix, long sessionNo)
  {
    int index = digestTree_.find(dataPrefix, sessionNo);
    if (index < 0)
      return -1;
    else
      return digestTree_.get(index).getSequenceNo();
  }

  /**
   * Increment the sequence number, create a sync message with the new
   * sequence number and publish a data packet where the name is
   * the applicationBroadcastPrefix + the root digest of the current digest
   * tree. Then add the sync message to the digest tree and digest log which
   * creates a new root digest. Finally, express an interest for the next sync
   * update with the name applicationBroadcastPrefix + the new root digest.
   * After this, your application should publish the content for the new
   * sequence number. You can get the new sequence number with getSequenceNo().
   * @param applicationInfo (optional) This appends applicationInfo to the
   * content of the sync messages. This same info is provided to the receiving
   * application in the SyncState state object provided to the
   * onReceivedSyncState callback.
   * @note Your application must call processEvents. Since processEvents
   * modifies the internal ChronoSync data structures, your application should
   * make sure that it calls processEvents in the same thread as
   * publishNextSequenceNo() (which also modifies the data structures).
   */
  public final void
  publishNextSequenceNo(Blob applicationInfo) throws IOException, SecurityException
  {
    ++sequenceNo_;

    SyncStateProto.SyncStateMsg.Builder builder =
      SyncStateProto.SyncStateMsg.newBuilder();
    builder.addSsBuilder()
      .setName(applicationDataPrefixUri_)
      .setType(SyncStateProto.SyncState.ActionType.UPDATE)
      .getSeqnoBuilder().setSeq(sequenceNo_)
                        .setSession(sessionNo_);
    if (!applicationInfo.isNull() && applicationInfo.size() > 0)
      builder.getSsBuilder(0).setApplicationInfo
        (ByteString.copyFrom(applicationInfo.buf()));
    SyncStateProto.SyncStateMsg syncMessage = builder.build();

    broadcastSyncState(digestTree_.getRoot(), syncMessage);

    if (!update(syncMessage.getSsList()))
      // Since we incremented the sequence number, we expect there to be a
      //   new digest log entry.
      throw new Error
        ("ChronoSync: update did not create a new digest log entry");

    // TODO: Should we have an option to not express an interest if this is the
    //   final publish of the session?
    Interest interest = new Interest(applicationBroadcastPrefix_);
    interest.getName().append(digestTree_.getRoot());
    interest.setInterestLifetimeMilliseconds(syncLifetime_);
    face_.expressInterest(interest, this, this);
  }

  /**
   * Increment the sequence number, create a sync message with the new
   * sequence number and publish a data packet where the name is
   * the applicationBroadcastPrefix + the root digest of the current digest
   * tree. Then add the sync message to the digest tree and digest log which
   * creates a new root digest. Finally, express an interest for the next sync
   * update with the name applicationBroadcastPrefix + the new root digest.
   * After this, your application should publish the content for the new
   * sequence number. You can get the new sequence number with getSequenceNo().
   * @note Your application must call processEvents. Since processEvents
   * modifies the internal ChronoSync data structures, your application should
   * make sure that it calls processEvents in the same thread as
   * publishNextSequenceNo() (which also modifies the data structures).
   */
  public final void
  publishNextSequenceNo() throws IOException, SecurityException
  {
    publishNextSequenceNo(new Blob());
  }

  /**
   * Get the sequence number of the latest data published by this application
   * instance.
   * @return The sequence number.
   */
  public final long
  getSequenceNo() { return sequenceNo_; }

  private static class DigestLogEntry {
    public DigestLogEntry(String digest, List data)
    {
      digest_ = digest;
      // Copy.
      data_ = new ArrayList(data);
    }

    public final String
    getDigest() { return digest_; }

    /**
     * Get the data.
     * @return The data as a list of SyncStateProto.SyncState.
     */
    List // of SyncStateProto.SyncState
    getData() { return data_; }

    private final String digest_;
  // Use List without generics so it works with older Java compilers.
    List data_; // of SyncStateProto.SyncState
  }

  /**
   * Unregister callbacks so that this does not respond to interests anymore.
   * If you will discard this ChronoSync2013 object while your application is
   * still running, you should call shutdown() first.  After calling this, you
   * should not call publishNextSequenceNo() again since the behavior will be
   * undefined.
   * @note Because this modifies internal ChronoSync data structures, your
   * application should make sure that it calls processEvents in the same
   * thread as shutdown() (which also modifies the data structures).
   */
  public final void
  shutdown()
  {
    enabled_ = false;
    contentCache_.unregisterAll();
  }

  /**
   * Make a data packet with the syncMessage and with name
   * applicationBroadcastPrefix_ + digest. Sign and send.
   * @param digest The root digest as a hex string for the data packet name.
   * @param syncMessage The SyncStateMsg which updates the digest tree state
   * with the given digest.
   */
  private void
  broadcastSyncState(String digest, SyncStateProto.SyncStateMsg syncMessage)
    throws SecurityException
  {
    Data data = new Data(applicationBroadcastPrefix_);
    data.getName().append(digest);
    data.setContent(new Blob(syncMessage.toByteArray(), false));
    keyChain_.sign(data, certificateName_);
    contentCache_.add(data);
  }

  /**
   * Update the digest tree with the messages in content. If the digest tree
   * root is not in the digest log, also add a log entry with the content.
   * @param content The list of SyncStateProto.SyncState.
   * @return True if added a digest log entry (because the updated digest
   * tree root was not in the log), false if didn't add a log entry.
   */
  private boolean
  update(List content)
  {
    for (int i = 0; i < content.size(); ++i) {
      SyncStateProto.SyncState syncState = (SyncStateProto.SyncState)content.get(i);

      if (syncState.getType().equals
           (SyncStateProto.SyncState.ActionType.UPDATE)) {
        if (digestTree_.update
            (syncState.getName(), syncState.getSeqno().getSession(),
             syncState.getSeqno().getSeq())) {
          // The digest tree was updated.
          if (applicationDataPrefixUri_.equals(syncState.getName()))
            sequenceNo_ = syncState.getSeqno().getSeq();
        }
      }
    }

    if (logFind(digestTree_.getRoot()) == -1) {
      digestLog_.add(new DigestLogEntry(digestTree_.getRoot(), content));
      return true;
    }
    else
      return false;
  }

  // Search the digest log by digest.
  private int
  logFind(String digest)
  {
    for (int i = 0; i < digestLog_.size(); ++i) {
      if (digest.equals(((DigestLogEntry)digestLog_.get(i)).getDigest()))
        return i;
    }

    return -1;
  }

  /**
   * Process the sync interest from the applicationBroadcastPrefix. If we can't
   * satisfy the interest, add it to the pending interest table in the
   * contentCache_ so that a future call to add may satisfy it.
   * (Do not call this. It is only public to implement the interface.)
   */
  public final void
  onInterest
    (Name prefix, Interest interest, Face face, long interestFilterId,
     InterestFilter filter)
  {
    if (!enabled_)
      // Ignore callbacks after the application calls shutdown().
      return;

    // Search if the digest already exists in the digest log.
    logger_.log(Level.FINE, "Sync Interest received in callback.");
    logger_.log(Level.FINE, interest.getName().toUri());

    String syncDigest = interest.getName().get
      (applicationBroadcastPrefix_.size()).toEscapedString();
    if (interest.getName().size() == applicationBroadcastPrefix_.size() + 2)
      // Assume this is a recovery interest.
      syncDigest = interest.getName().get
        (applicationBroadcastPrefix_.size() + 1).toEscapedString();
    logger_.log(Level.FINE, "syncDigest: {0}", syncDigest);
    if (interest.getName().size() == applicationBroadcastPrefix_.size() + 2 ||
        syncDigest.equals("00"))
      // Recovery interest or newcomer interest.
      processRecoveryInterest(interest, syncDigest, face);
    else {
      contentCache_.storePendingInterest(interest, face);

      if (!syncDigest.equals(digestTree_.getRoot())) {
        int index = logFind(syncDigest);
        if (index == -1) {
          // To see whether there is any data packet coming back, wait 2 seconds
          // using the Interest timeout mechanism.
          // TODO: Are we sure using a "/local/timeout" interest is the best future call approach?
          Interest timeout = new Interest(new Name("/local/timeout"));
          timeout.setInterestLifetimeMilliseconds(2000);
          try {
            face_.expressInterest
              (timeout, DummyOnData.onData_,
               this.new JudgeRecovery(syncDigest, face));
          } catch (IOException ex) {
            logger_.log(Level.SEVERE, null, ex);
            return;
          }
          logger_.log(Level.FINE, "set timer recover");
        }
        else {
          try {
            // common interest processing
            processSyncInterest(index, syncDigest, face);
          } catch (SecurityException ex) {
            logger_.log(Level.SEVERE, null, ex);
          }
        }
      }
    }
  }

  // Process Sync Data.
  // (Do not call this. It is only public to implement the interface.)
  public final void
  onData(Interest interest, Data data)
  {
    if (!enabled_)
      // Ignore callbacks after the application calls shutdown().
      return;

    logger_.log(Level.FINE, "Sync ContentObject received in callback");
    logger_.log(Level.FINE, "name: {0}", data.getName().toUri());
    SyncStateProto.SyncStateMsg tempContent;
    try {
      tempContent = SyncStateProto.SyncStateMsg.parseFrom(data.getContent().getImmutableArray());
    } catch (InvalidProtocolBufferException ex) {
      logger_.log(Level.SEVERE, null, ex);
      return;
    }
    List content = tempContent.getSsList();
    boolean isRecovery;
    if (digestTree_.getRoot().equals("00")) {
      isRecovery = true;
      try {
        //processing initial sync data
        initialOndata(content);
      } catch (SecurityException ex) {
        logger_.log(Level.SEVERE, null, ex);
        return;
      }
    }
    else {
      update(content);
      if (interest.getName().size() == applicationBroadcastPrefix_.size() + 2)
        // Assume this is a recovery interest.
        isRecovery = true;
      else
        isRecovery = false;
    }

    // Send the interests to fetch the application data.
    ArrayList syncStates = new ArrayList();
    for (int i = 0; i < content.size(); ++i) {
      SyncStateProto.SyncState syncState = (SyncStateProto.SyncState)content.get(i);

      // Only report UPDATE sync states.
      if (syncState.getType().equals
           (SyncStateProto.SyncState.ActionType.UPDATE)) {
        Blob applicationInfo;
        if (syncState.hasApplicationInfo() &&
            syncState.getApplicationInfo().size() > 0)
          applicationInfo = new Blob
            (syncState.getApplicationInfo().asReadOnlyByteBuffer(), true);
        else
          applicationInfo = new Blob();

        syncStates.add(new SyncState
          (syncState.getName(), syncState.getSeqno().getSession(),
           syncState.getSeqno().getSeq(), applicationInfo));
      }
    }
    try {
      onReceivedSyncState_.onReceivedSyncState(syncStates, isRecovery);
    } catch (Throwable ex) {
      logger_.log(Level.SEVERE, "Error in onReceivedSyncState", ex);
    }

    Name name = new Name(applicationBroadcastPrefix_);
    name.append(digestTree_.getRoot());
    Interest syncInterest = new Interest(name);
    syncInterest.setInterestLifetimeMilliseconds(syncLifetime_);
    try {
      face_.expressInterest(syncInterest, this, this);
    } catch (IOException ex) {
      logger_.log(Level.SEVERE, null, ex);
      return;
    }
    logger_.log(Level.FINE, "Syncinterest expressed:");
    logger_.log(Level.FINE, name.toUri());
  }

  // Initial sync interest timeout, which means there are no other publishers yet.
  // We make this an inner class because onTimeout is already used for syncTimeout.
  private class InitialTimeout implements OnTimeout {
    public final void
    onTimeout(Interest interest)
    {
      if (!enabled_)
        // Ignore callbacks after the application calls shutdown().
        return;

      logger_.log(Level.FINE, "initial sync timeout");
      logger_.log(Level.FINE, "no other people");
      ++sequenceNo_;
      if (sequenceNo_ != 0)
        // Since there were no other users, we expect sequence no 0.
        throw new Error
          ("ChronoSync: sequenceNo_ is not the expected value of 0 for first use.");

      SyncStateProto.SyncStateMsg.Builder builder =
        SyncStateProto.SyncStateMsg.newBuilder();
      builder.addSsBuilder()
        .setName(applicationDataPrefixUri_)
        .setType(SyncStateProto.SyncState.ActionType.UPDATE)
        .getSeqnoBuilder().setSeq(sequenceNo_)
                          .setSession(sessionNo_);
      SyncStateProto.SyncStateMsg tempContent = builder.build();
      update(tempContent.getSsList());

      try {
        onInitialized_.onInitialized();
      } catch (Throwable ex) {
        logger_.log(Level.SEVERE, null, ex);
      }

      Name name = new Name(applicationBroadcastPrefix_);
      name.append(digestTree_.getRoot());
      Interest retryInterest = new Interest(name);
      retryInterest.setInterestLifetimeMilliseconds(syncLifetime_);
      try {
        face_.expressInterest(retryInterest, ChronoSync2013.this, ChronoSync2013.this);
      } catch (IOException ex) {
        logger_.log(Level.SEVERE, null, ex);
        return;
      }
      logger_.log(Level.FINE, "Syncinterest expressed:");
      logger_.log(Level.FINE, name.toUri());
    }
  }

  private void
  processRecoveryInterest(Interest interest, String syncDigest, Face face)
  {
    logger_.log(Level.FINE, "processRecoveryInterest");
    if (logFind(syncDigest) != -1) {
      SyncStateProto.SyncStateMsg.Builder builder =
        SyncStateProto.SyncStateMsg.newBuilder();
      for (int i = 0; i < digestTree_.size(); ++i) {
        builder.addSsBuilder()
          .setName(digestTree_.get(i).getDataPrefix())
          .setType(SyncStateProto.SyncState.ActionType.UPDATE)
          .getSeqnoBuilder().setSeq(digestTree_.get(i).getSequenceNo())
                            .setSession(digestTree_.get(i).getSessionNo());
      }
      SyncStateProto.SyncStateMsg tempContent = builder.build();

      if (tempContent.getSsCount() != 0) {
        byte[] array = tempContent.toByteArray();
        Data data = new Data(interest.getName());
        data.setContent(new Blob(array, false));
        if (interest.getName().get(-1).toEscapedString().equals("00"))
          // Limit the lifetime of replies to interest for "00" since they can be different.
          data.getMetaInfo().setFreshnessPeriod(1000);

        try {
          keyChain_.sign(data, certificateName_);
        } catch (SecurityException ex) {
          logger_.log(Level.SEVERE, null, ex);
          return;
        }
        try {
          face.putData(data);
        } catch (IOException ex) {
          logger_.log(Level.SEVERE, ex.getMessage());
          return;
        }
        logger_.log(Level.FINE, "send recovery data back");
        logger_.log(Level.FINE, interest.getName().toUri());
      }
    }
  }

  /**
   * Common interest processing, using digest log to find the difference after
   * syncDigest. Return true if sent a data packet to satisfy the interest,
   * otherwise false.
   */
  private boolean
  processSyncInterest(int index, String syncDigest, Face face) throws SecurityException
  {
    ArrayList nameList = new ArrayList(); // of String
    ArrayList sequenceNoList = new ArrayList();  // of long
    ArrayList sessionNoList = new ArrayList(); // of long
    for (int j = index + 1; j < digestLog_.size(); ++j) {
      List temp = ((DigestLogEntry)digestLog_.get(j)).getData(); // of SyncStateProto.SyncState.
      for (int i = 0; i < temp.size(); ++i) {
        SyncStateProto.SyncState syncState = (SyncStateProto.SyncState)temp.get(i);
        if (!syncState.getType().equals
             (SyncStateProto.SyncState.ActionType.UPDATE))
          continue;

        if (digestTree_.find
            (syncState.getName(), syncState.getSeqno().getSession()) != -1) {
          int n = -1;
          for (int k = 0; k < nameList.size(); ++k) {
            if (((String)nameList.get(k)).equals(syncState.getName())) {
              n = k;
              break;
            }
          }
          if (n == -1) {
            nameList.add(syncState.getName());
            sequenceNoList.add(syncState.getSeqno().getSeq());
            sessionNoList.add(syncState.getSeqno().getSession());
          }
          else {
            sequenceNoList.set(n, syncState.getSeqno().getSeq());
            sessionNoList.set(n, syncState.getSeqno().getSession());
          }
        }
      }
    }

    SyncStateProto.SyncStateMsg.Builder builder =
      SyncStateProto.SyncStateMsg.newBuilder();
    for (int i = 0; i < nameList.size(); ++i) {
      builder.addSsBuilder()
        .setName((String)nameList.get(i))
        .setType(SyncStateProto.SyncState.ActionType.UPDATE)
        .getSeqnoBuilder().setSeq((long)(Long)sequenceNoList.get(i))
                          .setSession((long)(Long)sessionNoList.get(i));
    }
    SyncStateProto.SyncStateMsg tempContent = builder.build();

    boolean sent = false;
    if (tempContent.getSsCount() != 0) {
      Name name = new Name(applicationBroadcastPrefix_);
      name.append(syncDigest);
      byte[] array = tempContent.toByteArray();
      Data data = new Data(name);
      data.setContent(new Blob(array, false));
      keyChain_.sign(data, certificateName_);

      try {
        face.putData(data);
      } catch (IOException ex) {
        logger_.log(Level.SEVERE, ex.getMessage());
        return false;
      }

      sent = true;
      logger_.log(Level.FINE, "Sync Data send");
      logger_.log(Level.FINE, name.toUri());
    }

    return sent;
  }

  // Send Recovery Interest.
  private void
  sendRecovery(String syncDigest) throws IOException
  {
    logger_.log(Level.FINE, "unknown digest: ");
    Name name = new Name(applicationBroadcastPrefix_);
    name.append("recovery").append(syncDigest);
    Interest interest = new Interest(name);
    interest.setInterestLifetimeMilliseconds(syncLifetime_);
    face_.expressInterest(interest, this, this);
    logger_.log(Level.FINE, "Recovery Syncinterest expressed:");
    logger_.log(Level.FINE, name.toUri());
  }

  // This is called by onInterest after a timeout to check if a recovery is needed.
  // We make this an inner class because onTimeout is already used for syncTimeout.
  private class JudgeRecovery implements OnTimeout {
    public JudgeRecovery(String syncDigest, Face face)
    {
      syncDigest_ = syncDigest;
      face_ = face;
    }

    public final void
    onTimeout(Interest interest)
    {
      if (!enabled_)
        // Ignore callbacks after the application calls shutdown().
        return;

      int index2 = logFind(syncDigest_);
      if (index2 != -1) {
        if (!syncDigest_.equals(digestTree_.getRoot())) {
          try {
            processSyncInterest(index2, syncDigest_, face_);
          } catch (SecurityException ex) {
            logger_.log(Level.SEVERE, null, ex);
            return;
          }
        }
      }
      else {
        try {
          sendRecovery(syncDigest_);
        } catch (IOException ex) {
          logger_.log(Level.SEVERE, null, ex);
          return;
        }
      }
    }

    private final String syncDigest_;
    private final Face face_;
  }

  // Sync interest time out, if the interest is the static one send again.
  // This is called "syncTimeout" in NDN-CPP, etc. but here it is "onTimeout"
  // so that we can make ChronoSync2013 implement OnTimeout.
  // (Do not call this. It is only public to implement the interface.)
  public void
  onTimeout(Interest interest)
  {
    if (!enabled_)
      // Ignore callbacks after the application calls shutdown().
      return;

    logger_.log(Level.FINE, "Sync Interest time out.");
    logger_.log(Level.FINE, "Sync Interest name: {0}", interest.getName().toUri());
    String component = interest.getName().get(4).toEscapedString();
    if (component.equals(digestTree_.getRoot())) {
      Name name = new Name(interest.getName());
      Interest retryInterest = new Interest(interest.getName());
      retryInterest.setInterestLifetimeMilliseconds(syncLifetime_);
      try {
        face_.expressInterest(retryInterest, this, this);
      } catch (IOException ex) {
        logger_.log(Level.SEVERE, null, ex);
        return;
      }
      logger_.log(Level.FINE, "Syncinterest expressed:");
      logger_.log(Level.FINE, name.toUri());
    }
  }

  // Process initial data which usually includes all other publisher's info, and
  // send back the new comer's own info.
  private void
  initialOndata(List content) throws SecurityException
  {
    // The user is a new comer and receive data of all other people in the group.
    update(content);
    String digest = digestTree_.getRoot();
    for (int i = 0; i < content.size(); ++i) {
      SyncStateProto.SyncState syncState =
        (SyncStateProto.SyncState)content.get(i);
      if (syncState.getName().equals(applicationDataPrefixUri_) &&
          syncState.getSeqno().getSession() == sessionNo_) {
        // If the user was an old comer, after add the static log he needs to
        // increase his sequence number by 1.
        SyncStateProto.SyncStateMsg.Builder builder =
          SyncStateProto.SyncStateMsg.newBuilder();
        builder.addSsBuilder()
          .setName(applicationDataPrefixUri_)
          .setType(SyncStateProto.SyncState.ActionType.UPDATE)
          .getSeqnoBuilder().setSeq(syncState.getSeqno().getSeq() + 1)
                            .setSession(sessionNo_);
        SyncStateProto.SyncStateMsg tempContent = builder.build();

        if (update(tempContent.getSsList())) {
          try {
            onInitialized_.onInitialized();
          } catch (Throwable ex) {
            logger_.log(Level.SEVERE, null, ex);
          }
        }
      }
    }

    SyncStateProto.SyncStateMsg tempContent2;
    if (sequenceNo_ >= 0) {
      // Send the data packet with the new sequence number back.
      SyncStateProto.SyncStateMsg.Builder builder =
        SyncStateProto.SyncStateMsg.newBuilder();
      builder.addSsBuilder()
        .setName(applicationDataPrefixUri_)
        .setType(SyncStateProto.SyncState.ActionType.UPDATE)
        .getSeqnoBuilder().setSeq(sequenceNo_)
                          .setSession(sessionNo_);
      tempContent2 = builder.build();
    }
    else {
      SyncStateProto.SyncStateMsg.Builder builder =
        SyncStateProto.SyncStateMsg.newBuilder();
      builder.addSsBuilder()
        .setName(applicationDataPrefixUri_)
        .setType(SyncStateProto.SyncState.ActionType.UPDATE)
        .getSeqnoBuilder().setSeq(0)
                          .setSession(sessionNo_);
      tempContent2 = builder.build();
    }

    broadcastSyncState(digest, tempContent2);

    if (digestTree_.find(applicationDataPrefixUri_, sessionNo_) == -1) {
      // the user hasn't put himself in the digest tree.
      logger_.log(Level.FINE, "initial state");
      ++sequenceNo_;
      SyncStateProto.SyncStateMsg.Builder builder =
        SyncStateProto.SyncStateMsg.newBuilder();
      builder.addSsBuilder()
        .setName(applicationDataPrefixUri_)
        .setType(SyncStateProto.SyncState.ActionType.UPDATE)
        .getSeqnoBuilder().setSeq(sequenceNo_)
                          .setSession(sessionNo_);
      SyncStateProto.SyncStateMsg tempContent = builder.build();

      if (update(tempContent.getSsList())) {
        try {
          onInitialized_.onInitialized();
        } catch (Throwable ex) {
          logger_.log(Level.SEVERE, null, ex);
        }
      }
    }
  }

  // This is a do-nothing onData for using expressInterest for timeouts.
  // This should never be called.
  private static class DummyOnData implements OnData {
    public final void
    onData(Interest interest, Data data) {}

    public final static OnData onData_ = new DummyOnData();
  }

  Face face_;
  KeyChain keyChain_;
  Name certificateName_;
  double syncLifetime_;
  OnReceivedSyncState onReceivedSyncState_;
  OnInitialized onInitialized_;
  // Use ArrayList without generics so it works with older Java compilers.
  ArrayList digestLog_ = new ArrayList(); // of DigestLogEntry
  DigestTree digestTree_ = new DigestTree();
  String applicationDataPrefixUri_;
  Name applicationBroadcastPrefix_;
  long sessionNo_;
  long sequenceNo_ = -1;
  MemoryContentCache contentCache_;
  boolean enabled_ = true;
  private static final Logger logger_ = Logger.getLogger(ChronoSync2013.class.getName());
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy