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

net.named_data.jndn.sync.DigestTree 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 java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.named_data.jndn.util.Common;

public class DigestTree {
  public DigestTree()
  {
    root_ = "00";
  }

  public static class Node {
    /**
     * Create a new DigestTree.Node with the given fields and compute the digest.
     * @param dataPrefix The data prefix. This is encoded as UTF-8 to digest.
     * @param sessionNo The session number.
     * @param sequenceNo The sequence number.
     */
    public Node(String dataPrefix, long sessionNo, long sequenceNo)
    {
      dataPrefix_ = dataPrefix;
      sessionNo_ = sessionNo;
      sequenceNo_ = sequenceNo;
      recomputeDigest();
    }

    public final String
    getDataPrefix() { return dataPrefix_; }

    public final long
    getSessionNo() { return sessionNo_; }

    public final long
    getSequenceNo() { return sequenceNo_; }

    /**
     * Get the digest.
     * @return The digest as a hex string.
     */
    public final String
    getDigest() { return digest_; }


    /**
     * Set the sequence number and recompute the digest.
     * @param sequenceNo The new sequence number.
     */
    public final void
    setSequenceNo(long sequenceNo)
    {
      sequenceNo_ = sequenceNo;
      recomputeDigest();
    }

    /**
     * Compare this Node with node2 first comparing dataPrefix_ then sessionNo_.
     * @param node2 The other Node to compare.
     * @return True if this node is less than node2.
     */
    public final boolean
    lessThan(Node node2)
    {
      // We compare the Unicode strings which is OK because it has the same sort
      // order as the UTF-8 encoding: http://en.wikipedia.org/wiki/UTF-8#Advantages
      // "Sorting a set of UTF-8 encoded strings as strings of unsigned bytes
      // yields the same order as sorting the corresponding Unicode strings
      // lexicographically by codepoint."
      int prefixComparison = dataPrefix_.compareTo(node2.dataPrefix_);
      if (prefixComparison != 0)
        return prefixComparison < 0;

      return sessionNo_ < node2.sessionNo_;
    }

    /**
     * Digest the fields and set digest_ to the hex digest.
     */
    private void
    recomputeDigest()
    {
      MessageDigest sha256;
      try {
        sha256 = MessageDigest.getInstance("SHA-256");
      }
      catch (NoSuchAlgorithmException exception) {
        // Don't expect this to happen.
        throw new Error
          ("MessageDigest: SHA-256 is not supported: " + exception.getMessage());
      }

      byte[] number = new byte[4];
      // Debug: sync-state-proto.proto defines seq and session as uint64, but
      //   the original ChronoChat-js only digests 32 bits.
      int32ToLittleEndian((int)sessionNo_, number);
      sha256.update(number);
      int32ToLittleEndian((int)sequenceNo_, number);
      sha256.update(number);
      byte[] sequenceDigest = sha256.digest();

      sha256.reset();
      try {
        sha256.update(dataPrefix_.getBytes("UTF-8"));
      } catch (UnsupportedEncodingException ex) {
        // We don't expect this to happen.
        throw new Error("UTF-8 encoder not supported: " + ex.getMessage());
      }
      byte[] nameDigest = sha256.digest();

      sha256.reset();
      sha256.update(nameDigest);
      sha256.update(sequenceDigest);
      byte[] nodeDigest = sha256.digest();
      digest_ = Common.toHex(nodeDigest);
    }

    private static void
    int32ToLittleEndian(int value, byte[] result)
    {
      for (int i = 0; i < 4; i++) {
        result[i] = (byte)(value & 0xff);
        value >>= 8;
       }
    }

    private final String dataPrefix_;
    private final long sessionNo_;
    private long sequenceNo_;
    private String digest_;
  }

  /**
   * Update the digest tree and recompute the root digest.  If the combination
   * of dataPrefix and sessionNo already exists in the tree then update its
   * sequenceNo (only if the given sequenceNo is newer), otherwise add a new node.
   * @param dataPrefix The name prefix. This is encoded as UTF-8 to digest.
   * @param sessionNo The session number.
   * @param sequenceNo The new sequence number.
   * @return True if the digest tree is updated, false if not (because the
   * given sequenceNo is not newer than the existing sequence number).
   */
  public final boolean
  update(String dataPrefix, long sessionNo, long sequenceNo)
  {
    int nodeIndex = find(dataPrefix, sessionNo);
    Logger.getLogger(DigestTree.class.getName()).log(Level.FINE,
      "{0}, {1}",  new Object[]{dataPrefix, sessionNo});
    Logger.getLogger(DigestTree.class.getName()).log(Level.FINE,
      "DigestTree.update session {0}, nodeIndex {1}", new Object[]{sessionNo, nodeIndex});
    if (nodeIndex >= 0) {
      // Only update to a  newer status.
      if (digestNode_.get(nodeIndex).getSequenceNo() < sequenceNo)
        digestNode_.get(nodeIndex).setSequenceNo(sequenceNo);
      else
        return false;
    }
    else {
      Logger.getLogger(DigestTree.class.getName()).log(Level.FINE,
        "new comer {0}, session {1}, sequence {2}", new Object[]{dataPrefix, sessionNo, sequenceNo});
      // Insert into digestnode_ sorted.
      Node temp = new Node(dataPrefix, sessionNo, sequenceNo);
      // Find the index of the first node where it is not less than temp.
      int i = 0;
      while (i < digestNode_.size()) {
        if (!digestNode_.get(i).lessThan(temp))
          break;
        ++i;
      }
      digestNode_.add(i, temp);
    }

    recomputeRoot();
    return true;
  }

  public final int
  find(String dataPrefix, long sessionNo)
  {
    for (int i = 0; i < digestNode_.size(); ++i) {
      if (digestNode_.get(i).getDataPrefix().equals(dataPrefix) &&
          digestNode_.get(i).getSessionNo() == sessionNo)
        return i;
    }

    return -1;
  }

  public final int
  size() { return digestNode_.size(); }

  public final Node
  get(int i) { return digestNode_.get(i); }

  /**
   * Get the root digest.
   * @return The root digest as a hex string.
   */
  public final String
  getRoot() { return root_; }

  /**
   * Convert the hex character to an integer from 0 to 15, or -1 if not a hex character.
   */
  private static int
  fromHexChar(char c)
  {
    if (c >= '0' && c <= '9')
      return (int)c - (int)'0';
    else if (c >= 'A' && c <= 'F')
      return (int)c - (int)'A' + 10;
    else if (c >= 'a' && c <= 'f')
      return (int)c - (int)'a' + 10;
    else
      return -1;
  }

  /**
   * Convert the hex string to bytes and call messageDigest.update.
   * @param messageDigest The MessageDigest to update.
   * @param hex The hex string.
   */
  private static void
  updateHex(MessageDigest messageDigest, String hex)
  {
    byte[] data = new byte[hex.length() / 2];
    for (int i = 0; i < data.length; ++i)
      data[i] = (byte)((16 * fromHexChar(hex.charAt(2 * i)) +
                        fromHexChar(hex.charAt(2 * i + 1))) & 0xff);

    messageDigest.update(data);
  }

  /**
   * Set root_ to the digest of all digests in digestnode_. This sets root_
   * to the hex value of the digest.
   */
  private void
  recomputeRoot()
  {
    MessageDigest sha256;
    try {
      sha256 = MessageDigest.getInstance("SHA-256");
    }
    catch (NoSuchAlgorithmException exception) {
      // Don't expect this to happen.
      throw new Error
        ("MessageDigest: SHA-256 is not supported: " + exception.getMessage());
    }

    for (int i = 0; i < digestNode_.size(); ++i)
      updateHex(sha256, digestNode_.get(i).getDigest());
    byte[] digestRoot = sha256.digest();
    root_ = Common.toHex(digestRoot);
    Logger.getLogger(DigestTree.class.getName()).log(Level.FINE,
      "update root to: {0}", root_);
  }

  private final ArrayList digestNode_ = new ArrayList();
  private String root_;
  // This is to force an import of net.named_data.jndn.util.
  private static Common dummyCommon_ = new Common();
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy