10 |
--------------------------------------------------------------------------------
/android-demo/res/values-sw720dp-land/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 | 128dp
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/crypto/HashNamePrivateKey.java:
--------------------------------------------------------------------------------
1 | package org.telehash.crypto;
2 |
3 | import org.telehash.core.CipherSetIdentifier;
4 | import org.telehash.core.TelehashException;
5 |
6 | public interface HashNamePrivateKey {
7 | public CipherSetIdentifier getCipherSetIdentifier();
8 | public byte[] getEncoded() throws TelehashException;
9 | }
10 |
--------------------------------------------------------------------------------
/android-demo/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/crypto/HashNamePublicKey.java:
--------------------------------------------------------------------------------
1 | package org.telehash.crypto;
2 |
3 | import org.telehash.core.CipherSetIdentifier;
4 | import org.telehash.core.TelehashException;
5 |
6 | public interface HashNamePublicKey {
7 | public CipherSetIdentifier getCipherSetIdentifier();
8 | public byte[] getEncoded() throws TelehashException;
9 | public byte[] getFingerprint();
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/PlaceholderNode.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | public class PlaceholderNode extends Node {
4 | public PlaceholderNode(final HashName hashName) {
5 | super(hashName);
6 | }
7 |
8 | @Override
9 | public String toString() {
10 | String hashName = mHashName.getShortHash();
11 | return "Node["+hashName+"]";
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/android-demo/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/json/README-telehash.txt:
--------------------------------------------------------------------------------
1 |
2 | This directory contains a copy of the org.json JSON library, as
3 | retrieved from the following GitHub repository on April 30, 2014:
4 |
5 | https://github.com/douglascrockford/JSON-java.git
6 |
7 | commit: 48d31b7f5c8e43321e4b2143a8a795c366ace6d9
8 |
9 | The "org.json" package names have been changed to "org.telehash.json" to
10 | avoid conflicts with the older "org.json" package found on Android
11 | devices.
12 |
13 |
--------------------------------------------------------------------------------
/src/test/java/org/telehash/test/network/NetworkSimulator.java:
--------------------------------------------------------------------------------
1 | package org.telehash.test.network;
2 |
3 | import org.telehash.network.Network;
4 |
5 | public class NetworkSimulator {
6 |
7 | private Router mRouter = new Router();
8 |
9 | public Network createNode(String addressString, int port) {
10 | return new FakeNetworkImpl(mRouter, addressString);
11 | }
12 |
13 | public void waitForQuiescence(long time) {
14 | mRouter.waitForQuiescence(time);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/json/zip/None.java:
--------------------------------------------------------------------------------
1 | package org.telehash.json.zip;
2 |
3 | /**
4 | * None is an interface that makes the constant none (short for
5 | * negative one or long for -1) available to any class that implements it.
6 | * The none value is used to stand for an integer that is not an integer,
7 | * such as the negative result of a search.
8 | */
9 | public interface None {
10 | /**
11 | * Negative One.
12 | */
13 | public static final int none = -1;
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/CompletionHandler.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | /**
4 | * This is similar to the Java 7 java.nio.channels.CompletionHandler interface,
5 | * although this asynchronous callback interface is really just a wrapper around
6 | * the select/dispatch reactor pattern, so doesn't offer any proactor-like
7 | * scalability advantages.
8 | */
9 | public interface CompletionHandler {
10 | void completed(V result, Object attachment);
11 | void failed(Throwable exc, Object attachment);
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/SeedNode.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | import org.telehash.crypto.HashNamePublicKey;
4 | import org.telehash.network.Path;
5 |
6 | import java.util.Collection;
7 | import java.util.SortedMap;
8 |
9 | public class SeedNode extends FullNode {
10 | public SeedNode(
11 | FingerprintSet fingerprints,
12 | SortedMap publicKeys,
13 | Collection paths
14 | ) {
15 | super(fingerprints, publicKeys, paths);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/network/Reactor.java:
--------------------------------------------------------------------------------
1 | package org.telehash.network;
2 |
3 | import java.io.IOException;
4 |
5 | public interface Reactor {
6 | void setDatagramHandler(DatagramHandler datagramHandler);
7 | void setMessageHandler(MessageHandler messageHandler);
8 | void start() throws IOException;
9 | void stop();
10 | void close() throws IOException;
11 | void wakeup();
12 | void select(long timeout) throws IOException;
13 | void sendDatagram(Datagram datagram);
14 | void sendMessage(Message message);
15 | }
16 |
--------------------------------------------------------------------------------
/android-demo/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-19
15 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/TelehashException.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | /**
4 | * This is the base class for all Telehash exceptions. This class may be
5 | * instantiated directly to indicate an error condition, or extended to create a
6 | * specialized exception.
7 | */
8 | @SuppressWarnings("serial")
9 | public class TelehashException extends Exception {
10 | public TelehashException(String message) {
11 | super(message);
12 | }
13 | public TelehashException(Throwable e) {
14 | super(e);
15 | }
16 | public TelehashException(String message, Throwable e) {
17 | super(message, e);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/dht/NodeDistanceComparator.java:
--------------------------------------------------------------------------------
1 | package org.telehash.dht;
2 |
3 | import org.telehash.core.HashName;
4 | import org.telehash.core.Node;
5 |
6 | import java.math.BigInteger;
7 | import java.util.Comparator;
8 |
9 | class NodeDistanceComparator implements Comparator {
10 | private HashName mTargetHashName;
11 | public NodeDistanceComparator(HashName targetHashName) {
12 | mTargetHashName = targetHashName;
13 | }
14 | @Override
15 | public int compare(Node a, Node b) {
16 | BigInteger da = mTargetHashName.distance(a.getHashName());
17 | BigInteger db = mTargetHashName.distance(b.getHashName());
18 | return da.compareTo(db);
19 | }
20 | }
--------------------------------------------------------------------------------
/android-demo/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/json/JSONString.java:
--------------------------------------------------------------------------------
1 | package org.telehash.json;
2 | /**
3 | * The JSONString interface allows a toJSONString()
4 | * method so that a class can change the behavior of
5 | * JSONObject.toString(), JSONArray.toString(),
6 | * and JSONWriter.value(Object). The
7 | * toJSONString method will be used instead of the default behavior
8 | * of using the Object's toString() method and quoting the result.
9 | */
10 | public interface JSONString {
11 | /**
12 | * The toJSONString method allows a class to produce its own JSON
13 | * serialization.
14 | *
15 | * @return A strictly syntactically correct JSON text.
16 | */
17 | public String toJSONString();
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/network/Datagram.java:
--------------------------------------------------------------------------------
1 | package org.telehash.network;
2 |
3 | public class Datagram {
4 | private byte[] mBytes;
5 | private Path mSource;
6 | private Path mDestination;
7 |
8 | public Datagram(byte[] bytes, Path source, Path destination) {
9 | mBytes = bytes;
10 | mSource = source;
11 | mDestination = destination;
12 | }
13 |
14 | public byte[] getBytes() {
15 | return mBytes;
16 | }
17 |
18 | public Path getSource() {
19 | return mSource;
20 | }
21 |
22 | public void setSource(Path source) {
23 | mSource = source;
24 | }
25 |
26 | public Path getDestination() {
27 | return mDestination;
28 | }
29 |
30 | public void setDestination(Path destination) {
31 | mDestination = destination;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/test/java/org/telehash/test/mesh/Mesh.java:
--------------------------------------------------------------------------------
1 | package org.telehash.test.mesh;
2 |
3 | import org.telehash.test.network.NetworkSimulator;
4 |
5 | import java.util.List;
6 |
7 | public class Mesh {
8 | private List mInstances;
9 | private NetworkSimulator mNetworkSimulator;
10 |
11 | public Mesh(List instances, NetworkSimulator networkSimulator) {
12 | mInstances = instances;
13 | mNetworkSimulator = networkSimulator;
14 | }
15 |
16 | public List getInstances() {
17 | return mInstances;
18 | }
19 |
20 | public NetworkSimulator getNetworkSimulator() {
21 | return mNetworkSimulator;
22 | }
23 |
24 | public void waitForQuiescence(long time) {
25 | mNetworkSimulator.waitForQuiescence(time);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/CounterTrigger.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | /**
4 | * Execute a runnable when a signal count reaches a specified limit.
5 | */
6 | public class CounterTrigger {
7 |
8 | private Runnable mRunnable;
9 | private int mCount = 0;
10 | private int mLimit = 0;
11 |
12 | public CounterTrigger(Runnable runnable) {
13 | mRunnable = runnable;
14 | }
15 |
16 | public void signal() {
17 | mCount++;
18 | if (mCount == mLimit) {
19 | if (mRunnable != null) {
20 | mRunnable.run();
21 | }
22 | }
23 | }
24 |
25 | public void setLimit(int limit) {
26 | mLimit = limit;
27 | if (mCount == limit) {
28 | if (mRunnable != null) {
29 | mRunnable.run();
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/crypto/set2a/HashNameKeyPairImpl.java:
--------------------------------------------------------------------------------
1 | package org.telehash.crypto.set2a;
2 |
3 | import org.telehash.crypto.HashNameKeyPair;
4 | import org.telehash.crypto.HashNamePrivateKey;
5 | import org.telehash.crypto.HashNamePublicKey;
6 |
7 | public class HashNameKeyPairImpl implements HashNameKeyPair {
8 |
9 | private HashNamePublicKeyImpl mPublicKey;
10 | private HashNamePrivateKeyImpl mPrivateKey;
11 |
12 | public HashNameKeyPairImpl(HashNamePublicKeyImpl publicKey, HashNamePrivateKeyImpl privateKey) {
13 | mPublicKey = publicKey;
14 | mPrivateKey = privateKey;
15 | }
16 |
17 | @Override
18 | public HashNamePublicKey getPublicKey() {
19 | return mPublicKey;
20 | }
21 |
22 | @Override
23 | public HashNamePrivateKey getPrivateKey() {
24 | return mPrivateKey;
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/android-demo/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
22 | #-keep class org.spongycastle.**
23 | -dontwarn org.spongycastle.jce.provider.X509LDAPCertStoreSpi
24 | -dontwarn org.spongycastle.x509.util.LDAPStoreHelper
25 |
--------------------------------------------------------------------------------
/android-demo/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
17 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/test/java/org/telehash/test/util/EchoChannelHandler.java:
--------------------------------------------------------------------------------
1 | package org.telehash.test.util;
2 |
3 | import org.telehash.core.Channel;
4 | import org.telehash.core.ChannelHandler;
5 | import org.telehash.core.ChannelPacket;
6 | import org.telehash.core.Log;
7 | import org.telehash.core.TelehashException;
8 |
9 | public class EchoChannelHandler implements ChannelHandler {
10 |
11 | public static final String TYPE = "echo";
12 |
13 | @Override
14 | public void handleOpen(Channel channel) {
15 | Log.i("echo channel handler: channel opened");
16 | }
17 |
18 | @Override
19 | public void handleIncoming(Channel channel, ChannelPacket channelPacket) {
20 | Log.i("echo channel handler: received "+channelPacket.getBody().length+" bytes.");
21 | try {
22 | channel.send(channelPacket.getBody());
23 | } catch (TelehashException e) {
24 | Log.e("echo channel handler: error sending response", e);
25 | }
26 | }
27 |
28 | @Override
29 | public void handleError(Channel channel, Throwable error) {
30 | Log.i("echo channel handler: channel error", error);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/crypto/set2a/LinePrivateKeyImpl.java:
--------------------------------------------------------------------------------
1 | package org.telehash.crypto.set2a;
2 |
3 | import org.spongycastle.crypto.params.ECDomainParameters;
4 | import org.spongycastle.crypto.params.ECPrivateKeyParameters;
5 | import org.telehash.core.CipherSetIdentifier;
6 | import org.telehash.core.TelehashException;
7 | import org.telehash.core.Util;
8 | import org.telehash.crypto.LinePrivateKey;
9 |
10 | import java.math.BigInteger;
11 |
12 | public class LinePrivateKeyImpl implements LinePrivateKey {
13 |
14 | private ECPrivateKeyParameters mKey;
15 |
16 | public LinePrivateKeyImpl(ECPrivateKeyParameters privateKey) {
17 | mKey = privateKey;
18 | }
19 |
20 | public LinePrivateKeyImpl(
21 | byte[] buffer,
22 | ECDomainParameters domainParameters
23 | ) throws TelehashException {
24 | BigInteger d = new BigInteger(Util.bytesToHex(buffer), 16);
25 | mKey = new ECPrivateKeyParameters(d, domainParameters);
26 | }
27 |
28 | @Override
29 | public CipherSetIdentifier getCipherSetIdentifier() {
30 | return CipherSet2aImpl.CIPHER_SET_ID;
31 | }
32 |
33 | public ECPrivateKeyParameters getKey() {
34 | return mKey;
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/json/JSONException.java:
--------------------------------------------------------------------------------
1 | package org.telehash.json;
2 |
3 | /**
4 | * The JSONException is thrown by the JSON.org classes when things are amiss.
5 | *
6 | * @author JSON.org
7 | * @version 2013-02-10
8 | */
9 | public class JSONException extends RuntimeException {
10 | private static final long serialVersionUID = 0;
11 | private Throwable cause;
12 |
13 | /**
14 | * Constructs a JSONException with an explanatory message.
15 | *
16 | * @param message
17 | * Detail about the reason for the exception.
18 | */
19 | public JSONException(String message) {
20 | super(message);
21 | }
22 |
23 | /**
24 | * Constructs a new JSONException with the specified cause.
25 | */
26 | public JSONException(Throwable cause) {
27 | super(cause.getMessage());
28 | this.cause = cause;
29 | }
30 |
31 | /**
32 | * Returns the cause of this exception or null if the cause is nonexistent
33 | * or unknown.
34 | *
35 | * @returns the cause of this exception or null if the cause is nonexistent
36 | * or unknown.
37 | */
38 | public Throwable getCause() {
39 | return this.cause;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/json/zip/BitReader.java:
--------------------------------------------------------------------------------
1 | package org.telehash.json.zip;
2 |
3 | import java.io.IOException;
4 |
5 | public interface BitReader {
6 | /**
7 | * Read one bit.
8 | *
9 | * @return true if it is a 1 bit.
10 | */
11 | public boolean bit() throws IOException;
12 |
13 | /**
14 | * Returns the number of bits that have been read from this bitreader.
15 | *
16 | * @return The number of bits read so far.
17 | */
18 | public long nrBits();
19 |
20 | /**
21 | * Check that the rest of the block has been padded with zeroes.
22 | *
23 | * @param factor
24 | * The size in bits of the block to pad. This will typically be
25 | * 8, 16, 32, 64, 128, 256, etc.
26 | * @return true if the block was zero padded, or false if the the padding
27 | * contained any one bits.
28 | * @throws IOException
29 | */
30 | public boolean pad(int factor) throws IOException;
31 |
32 | /**
33 | * Read some bits.
34 | *
35 | * @param width
36 | * The number of bits to read. (0..32)
37 | * @throws IOException
38 | * @return the bits
39 | */
40 | public int read(int width) throws IOException;
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/crypto/set2a/LineKeyPairImpl.java:
--------------------------------------------------------------------------------
1 | package org.telehash.crypto.set2a;
2 |
3 | import org.spongycastle.crypto.params.ECPrivateKeyParameters;
4 | import org.spongycastle.crypto.params.ECPublicKeyParameters;
5 | import org.telehash.core.TelehashException;
6 | import org.telehash.crypto.LineKeyPair;
7 | import org.telehash.crypto.LinePrivateKey;
8 | import org.telehash.crypto.LinePublicKey;
9 |
10 | public class LineKeyPairImpl implements LineKeyPair {
11 | LinePublicKeyImpl mPublicKey;
12 | LinePrivateKeyImpl mPrivateKey;
13 |
14 | public LineKeyPairImpl(
15 | ECPublicKeyParameters publicKey,
16 | ECPrivateKeyParameters privateKey
17 | ) throws TelehashException {
18 | mPublicKey = new LinePublicKeyImpl(publicKey);
19 | mPrivateKey = new LinePrivateKeyImpl(privateKey);
20 | }
21 |
22 | public LineKeyPairImpl(
23 | LinePublicKey publicKey,
24 | LinePrivateKey privateKey
25 | ) throws TelehashException {
26 | mPublicKey = (LinePublicKeyImpl)publicKey;
27 | mPrivateKey = (LinePrivateKeyImpl)privateKey;
28 | }
29 |
30 | @Override
31 | public LinePrivateKey getPrivateKey() {
32 | return mPrivateKey;
33 | }
34 | @Override
35 | public LinePublicKey getPublicKey() {
36 | return mPublicKey;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/network/Network.java:
--------------------------------------------------------------------------------
1 | package org.telehash.network;
2 |
3 | import org.telehash.core.TelehashException;
4 |
5 | /**
6 | * This interface contains methods that may be used to perform the network
7 | * operations needed by Telehash. Concrete implementations suitable for specific
8 | * platforms and/or specific network technologies may be developed, and
9 | * applications are free to extend these implementations or provide their own.
10 | */
11 | public interface Network {
12 | /**
13 | * Parse a string representing a network address.
14 | *
15 | * @param addressString
16 | * The path string to parse.
17 | * @return The network path object.
18 | * @throws TelehashException
19 | * If a problem occurred while parsing the path.
20 | */
21 | public Path parsePath(String addressString, int port) throws TelehashException;
22 |
23 | /**
24 | * Get preferred local path
25 | * TODO: This will certainly change... we need to support multiple network interfaces!
26 | */
27 | public Path getPreferredLocalPath() throws TelehashException;
28 |
29 | /**
30 | * Provision a new reactor i/o engine listening on the specified port.
31 | *
32 | * @param port The IP port on which to listen.
33 | * @return The reactor.
34 | */
35 | public Reactor createReactor(int port);
36 | }
37 |
--------------------------------------------------------------------------------
/android-demo/build.gradle:
--------------------------------------------------------------------------------
1 | //////////////////////////////////////////////////////////////////////
2 | //
3 | // Telehash Android Demo
4 | //
5 | //////////////////////////////////////////////////////////////////////
6 |
7 | //
8 | // fetch and load the android gradle plugin.
9 | // the plugin is documented here:
10 | // http://tools.android.com/tech-docs/new-build-system/user-guide
11 | //
12 | buildscript {
13 | repositories {
14 | mavenCentral()
15 | }
16 | dependencies {
17 | classpath 'com.android.tools.build:gradle:0.10.0'
18 | }
19 | }
20 | apply plugin: 'android'
21 |
22 | repositories {
23 | mavenCentral()
24 | }
25 |
26 | dependencies {
27 | compile group: 'com.madgag.spongycastle', name: 'core', version: '1.50.0.0'
28 | compile group: 'com.madgag.spongycastle', name: 'prov', version: '1.50.0.0'
29 | compile project(':')
30 | }
31 |
32 | android {
33 | // use old-style directory layout
34 | sourceSets {
35 | main {
36 | manifest.srcFile 'AndroidManifest.xml'
37 | java.srcDirs = ['src']
38 | resources.srcDirs = ['src']
39 | aidl.srcDirs = ['src']
40 | renderscript.srcDirs = ['src']
41 | res.srcDirs = ['res']
42 | assets.srcDirs = ['assets']
43 | }
44 | }
45 |
46 | compileSdkVersion 19
47 | buildToolsVersion "19.0.0"
48 | lintOptions {
49 | disable 'InvalidPackage'
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/android-demo/src/org/telehash/androiddemo/LogFragment.java:
--------------------------------------------------------------------------------
1 | package org.telehash.androiddemo;
2 |
3 | import android.app.Fragment;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.ScrollView;
9 |
10 | import org.telehash.core.LogEntry;
11 |
12 | public class LogFragment extends Fragment {
13 |
14 | private ScrollView mScrollView;
15 | private LogView mLogView;
16 | private TelehashService mService = null;
17 |
18 | public void setService(TelehashService service) {
19 | mService = service;
20 | }
21 |
22 | @Override
23 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
24 | Bundle savedInstanceState){
25 | mScrollView = new ScrollView(this.getActivity());
26 | mLogView = new LogView(this.getActivity());
27 | if (mService != null) {
28 | mLogView.setText(mService.getLogger().render());
29 | }
30 | mScrollView.addView(mLogView);
31 | return mScrollView;
32 | }
33 |
34 | @Override
35 | public void onResume() {
36 | super.onResume();
37 | if (mService != null) {
38 | mLogView.setText(mService.getLogger().render());
39 | }
40 | }
41 |
42 | public void showEntry(LogEntry entry) {
43 | String text = AndroidLogger.renderEntry(entry);
44 | mLogView.append(text);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/LogEntry.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | import org.telehash.core.Log.Category;
4 | import org.telehash.core.Log.Level;
5 |
6 | public class LogEntry {
7 |
8 | private static long sStartTime = System.nanoTime();
9 |
10 | private Telehash mTelehash;
11 | private Category mCategory;
12 | private Level mLevel;
13 | private String mMessage;
14 | private Throwable mError;
15 | private long mTime;
16 |
17 | public LogEntry(Category category, Level level, String message) {
18 | mTelehash = Telehash.get();
19 | mCategory = category;
20 | mLevel = level;
21 | mMessage = message;
22 | mTime = System.nanoTime() - sStartTime;
23 | }
24 |
25 | public LogEntry(Category category, Level level, String message, Throwable error) {
26 | mTelehash = Telehash.get();
27 | mCategory = category;
28 | mLevel = level;
29 | mMessage = message;
30 | mError = error;
31 | mTime = System.nanoTime() - sStartTime;
32 | }
33 |
34 | public Telehash getTelehash() {
35 | return mTelehash;
36 | }
37 |
38 | public Category getCategory() {
39 | return mCategory;
40 | }
41 |
42 | public Level getLevel() {
43 | return mLevel;
44 | }
45 |
46 | public String getMessage() {
47 | return mMessage;
48 | }
49 |
50 | public Throwable getError() {
51 | return mError;
52 | }
53 |
54 | public long getTime() {
55 | return mTime;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/misc/stringinator.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 | ######################################################################
3 | #
4 | # This is a utility for "stringifying" a text file so that it may
5 | # be included as a string literal within a Java source file.
6 | #
7 | # I use this to stringify a seeds.json file for inclusion in the
8 | # unit tests.
9 | #
10 | # - David Simmons, 2014-04-16
11 | #
12 | ######################################################################
13 |
14 | use strict;
15 | use warnings;
16 |
17 | our $LINE_LENGTH = 88;
18 | our $NORMAL_INDENT = 8;
19 | our $CONTINUATION_INDENT = 8;
20 |
21 | sub out {
22 | my ($_,$continuation,$eol) = @_;
23 | my $indent;
24 | my $newline;
25 | if ($continuation) {
26 | $indent = " "x($NORMAL_INDENT+$CONTINUATION_INDENT);
27 | } else {
28 | $indent = " "x($NORMAL_INDENT);
29 | }
30 | if ($eol) {
31 | $newline = "\\n";
32 | } else {
33 | $newline = "";
34 | }
35 | print $indent."\"$_".$newline."\"+\n";
36 | }
37 |
38 | while (<>) {
39 | chomp;
40 | s/\"/\\\"/g;
41 |
42 | my @lines;
43 | my $ll = $LINE_LENGTH;
44 | while ($_) {
45 | my $line = substr($_,0,$ll);
46 | if (length($_)>=$ll) {
47 | $_ = substr($_,$ll);
48 | } else {
49 | $_ = "";
50 | }
51 | push(@lines, $line);
52 | $ll = $LINE_LENGTH - $CONTINUATION_INDENT;
53 | }
54 |
55 | for (my $i=0; $i0), ($i==(scalar(@lines)-1));
57 | }
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/LineIdentifier.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | import java.util.Arrays;
4 |
5 | /**
6 | * Wrap a binary line identifier. This is needed so we can establish a sensible
7 | * Java object identity and use a line identifier as a key in HashMap.
8 | */
9 | public class LineIdentifier {
10 | public static final int SIZE = 16;
11 |
12 | private byte[] mBuffer;
13 |
14 | public LineIdentifier(byte[] buffer) {
15 | if (buffer == null || buffer.length != SIZE) {
16 | throw new IllegalArgumentException("invalid line id");
17 | }
18 | mBuffer = buffer;
19 | }
20 |
21 | public static LineIdentifier generate() {
22 | return new LineIdentifier(
23 | Telehash.get().getCrypto().getRandomBytes(SIZE)
24 | );
25 | }
26 |
27 | public byte[] getBytes() {
28 | return mBuffer;
29 | }
30 |
31 | public String asHex() {
32 | return Util.bytesToHex(mBuffer);
33 | }
34 |
35 | @Override
36 | public String toString() {
37 | return asHex();
38 | }
39 |
40 | // Java identity
41 |
42 | @Override
43 | public boolean equals(Object other) {
44 | if (other != null &&
45 | other instanceof LineIdentifier &&
46 | Arrays.equals(((LineIdentifier)other).mBuffer, mBuffer)) {
47 | return true;
48 | } else {
49 | return false;
50 | }
51 | }
52 |
53 | @Override
54 | public int hashCode() {
55 | return Arrays.hashCode(mBuffer);
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/json/zip/BitWriter.java:
--------------------------------------------------------------------------------
1 | package org.telehash.json.zip;
2 |
3 | import java.io.IOException;
4 |
5 | /**
6 | * A bitwriter is a an interface that allows for doing output at the bit level.
7 | * Most IO interfaces only allow for writing at the byte level or higher.
8 | */
9 | public interface BitWriter {
10 | /**
11 | * Returns the number of bits that have been written to this bitwriter.
12 | */
13 | public long nrBits();
14 |
15 | /**
16 | * Write a 1 bit.
17 | *
18 | * @throws IOException
19 | */
20 | public void one() throws IOException;
21 |
22 | /**
23 | * Pad the rest of the block with zeros and flush.
24 | *
25 | * @param factor
26 | * The size in bits of the block to pad. This will typically be
27 | * 8, 16, 32, 64, 128, 256, etc.
28 | * @return true if the block was zero padded, or false if the the padding
29 | * contains any one bits.
30 | * @throws IOException
31 | */
32 | public void pad(int factor) throws IOException;
33 |
34 | /**
35 | * Write some bits. Up to 32 bits can be written at a time.
36 | *
37 | * @param bits
38 | * The bits to be written.
39 | * @param width
40 | * The number of bits to write. (0..32)
41 | * @throws IOException
42 | */
43 | public void write(int bits, int width) throws IOException;
44 |
45 | /**
46 | * Write a 0 bit.
47 | *
48 | * @throws IOException
49 | */
50 | public void zero() throws IOException;
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/ChannelIdentifier.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 |
4 | /**
5 | * Wrap a binary channel identifier. This is needed so we can establish a
6 | * sensible Java object identity and use a channel identifier as a key in
7 | * HashMap.
8 | */
9 | public class ChannelIdentifier {
10 | public static final long MAX_CHANNEL_ID = (1L<<32)-1;
11 |
12 | private long mId;
13 |
14 | public ChannelIdentifier(long id) {
15 | if (id <= 0 || id > MAX_CHANNEL_ID) {
16 | throw new IllegalArgumentException("invalid line id");
17 | }
18 | mId = id;
19 | }
20 |
21 | public ChannelIdentifier(String s) {
22 | mId = Long.parseLong(s);
23 | if (mId <= 0 || mId > MAX_CHANNEL_ID) {
24 | throw new IllegalArgumentException("invalid line id");
25 | }
26 | }
27 |
28 | public long toLong() {
29 | return mId;
30 | }
31 |
32 | @Override
33 | public String toString() {
34 | return Long.toString(mId);
35 | }
36 |
37 | // Java identity
38 |
39 | @Override
40 | public int hashCode() {
41 | final int prime = 31;
42 | int result = 1;
43 | result = prime * result + (int) (mId ^ (mId >>> 32));
44 | return result;
45 | }
46 |
47 | @Override
48 | public boolean equals(Object obj) {
49 | if (this == obj)
50 | return true;
51 | if (obj == null)
52 | return false;
53 | if (getClass() != obj.getClass())
54 | return false;
55 | ChannelIdentifier other = (ChannelIdentifier) obj;
56 | if (mId != other.mId)
57 | return false;
58 | return true;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/test/java/org/telehash/test/network/Router.java:
--------------------------------------------------------------------------------
1 | package org.telehash.test.network;
2 |
3 | import org.telehash.core.Log;
4 | import org.telehash.network.Datagram;
5 | import org.telehash.network.DatagramHandler;
6 | import org.telehash.network.InetPath;
7 |
8 | import java.util.HashMap;
9 | import java.util.Map;
10 |
11 | public class Router {
12 | private long lastDatagramTime = 0L;
13 |
14 | private Map mNetworkMap =
15 | new HashMap();
16 |
17 | public void registerNetwork(FakeNetworkImpl network) {
18 | mNetworkMap.put(network.getPath(), network);
19 | }
20 |
21 | public void sendDatagram(Datagram datagram) {
22 | InetPath destination = new InetPath(((InetPath)datagram.getDestination()).getAddress(), 0);
23 | DatagramHandler handler = mNetworkMap.get(destination);
24 | if (handler != null) {
25 | synchronized (this) {
26 | lastDatagramTime = System.nanoTime();
27 | }
28 | handler.handleDatagram(datagram);
29 | }
30 | }
31 |
32 | public void waitForQuiescence(long time) {
33 | time = time * 1000000; // convert ms to ns
34 | long start = System.nanoTime();
35 | long now;
36 | do {
37 | final long diff;
38 | synchronized (this) {
39 | now = System.nanoTime();
40 | diff = now - lastDatagramTime;
41 | }
42 | Log.i("QUI: time since last datagram: "+diff+" ns (wanting "+time+" ns)");
43 | if (diff >= time) {
44 | break;
45 | } else {
46 | try {
47 | Thread.sleep(diff/1000000);
48 | } catch (InterruptedException e) {
49 | }
50 | }
51 | } while (true);
52 | Log.i(String.format(
53 | "Paused %7.3fs while waiting for network quiescence.",
54 | (now - start) / 1000000000.0));
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/CipherSetIdentifier.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 |
4 | /**
5 | * Wrap a cipher set identifier.
6 | */
7 | public class CipherSetIdentifier implements Comparable {
8 | public static final int SIZE = 1;
9 | private final short mId;
10 |
11 | public CipherSetIdentifier(int id) {
12 | mId = (short) id;
13 | }
14 |
15 | public CipherSetIdentifier(short id) {
16 | mId = id;
17 | }
18 |
19 | public CipherSetIdentifier(byte id) {
20 | mId = (short)(id & 0xFF);
21 | }
22 |
23 | public CipherSetIdentifier(String s) {
24 | byte[] buffer = Util.hexToBytes(s);
25 | if (buffer != null && buffer.length == 1) {
26 | mId = buffer[0];
27 | } else {
28 | throw new IllegalArgumentException("bad cipherset id");
29 | }
30 | }
31 |
32 | public byte getByte() {
33 | return (byte) mId;
34 | }
35 | public byte[] getBytes() {
36 | byte[] buffer = new byte[1];
37 | buffer[0] = (byte)mId;
38 | return buffer;
39 | }
40 |
41 | public String asHex() {
42 | return Util.bytesToHex(getBytes());
43 | }
44 |
45 | @Override
46 | public String toString() {
47 | return asHex();
48 | }
49 |
50 | // Java identity
51 |
52 | @Override
53 | public boolean equals(Object other) {
54 | if (other != null &&
55 | other instanceof CipherSetIdentifier &&
56 | ((CipherSetIdentifier)other).mId == mId) {
57 | return true;
58 | } else {
59 | return false;
60 | }
61 | }
62 |
63 | @Override
64 | public int hashCode() {
65 | return mId;
66 | }
67 |
68 | @Override
69 | public int compareTo(CipherSetIdentifier other) {
70 | if (other == null) {
71 | return +1;
72 | }
73 | if (this.mId > other.mId) {
74 | return +1;
75 | } else if (this.mId < other.mId) {
76 | return -1;
77 | } else {
78 | return 0;
79 | }
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/json/zip/PostMortem.java:
--------------------------------------------------------------------------------
1 | package org.telehash.json.zip;
2 |
3 | /*
4 | Copyright (c) 2013 JSON.org
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | The Software shall be used for Good, not Evil.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | /**
28 | * The PostMortem interface allows for testing the internal state of JSONzip
29 | * processors. Testing that JSONzip can compress an object and reproduce a
30 | * corresponding object is not sufficient. Complete testing requires that the
31 | * same internal data structures were constructed on both ends. If those
32 | * structures are not equivalent, then it is likely that the implementations
33 | * are not correct, even if convention tests are passed.
34 | *
35 | * PostMortem allows for testing of deep structures without breaking
36 | * encapsulation.
37 | */
38 | public interface PostMortem {
39 | /**
40 | * Determine if two objects are equivalent.
41 | *
42 | * @param pm
43 | * Another object of the same type.
44 | * @return true if they match.
45 | */
46 | public boolean postMortem(PostMortem pm);
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/Timeout.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | import java.lang.ref.WeakReference;
4 |
5 | public class Timeout implements Runnable {
6 |
7 | private Scheduler mScheduler;
8 | private WeakReference mListener;
9 | private long mDelay;
10 | private Scheduler.Task mTask;
11 |
12 | public Timeout(Scheduler scheduler, OnTimeoutListener listener, long delay) {
13 | mScheduler = scheduler;
14 | mListener = new WeakReference(listener);
15 | mDelay = 0;
16 | mTask = null;
17 | setDelay(delay);
18 | }
19 |
20 | @Override
21 | public String toString() {
22 | return "Timeout[delay="+mDelay+"/task="+mTask+"]{"+hashCode()+"}";
23 | }
24 |
25 | public void setDelay(long delay) {
26 | if (delay > 0) {
27 | if (mTask != null) {
28 | mScheduler.updateTask(mTask, null, delay);
29 | } else {
30 | mTask = mScheduler.addTask(this, delay);
31 | }
32 | } else {
33 | if (mTask != null) {
34 | mScheduler.removeTask(mTask);
35 | mTask = null;
36 | }
37 | }
38 | mDelay = delay;
39 | }
40 |
41 | public long getDelay() {
42 | return mDelay;
43 | }
44 |
45 | /**
46 | * Reset (and re-start) the timer with the previously established delay.
47 | */
48 | public void reset() {
49 | if (mDelay > 0) {
50 | if (mTask != null) {
51 | mScheduler.updateTask(mTask, null, mDelay);
52 | } else {
53 | mTask = mScheduler.addTask(this, mDelay);
54 | }
55 | }
56 | }
57 |
58 | public void cancel() {
59 | if (mTask != null) {
60 | mScheduler.removeTask(mTask);
61 | mTask = null;
62 | }
63 | }
64 |
65 | @Override
66 | public void run() {
67 | OnTimeoutListener listener = mListener.get();
68 | if (listener != null) {
69 | listener.handleTimeout();
70 | } else {
71 | Log.e("timeout lost reference to listener // "+this);
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/sample/java/org/telehash/sample/BasicSeed.java:
--------------------------------------------------------------------------------
1 | package org.telehash.sample;
2 |
3 | import org.telehash.core.LocalNode;
4 | import org.telehash.core.Switch;
5 | import org.telehash.core.Telehash;
6 | import org.telehash.core.TelehashException;
7 | import org.telehash.storage.Storage;
8 | import org.telehash.storage.impl.StorageImpl;
9 |
10 | import java.io.FileNotFoundException;
11 |
12 | public class BasicSeed {
13 |
14 | private static final String LOCALNODE_BASE_FILENAME = "telehash-seed";
15 | private static final int PORT = 5001;
16 |
17 | public static final void main(String[] args) {
18 |
19 | Storage storage = new StorageImpl();
20 |
21 | // load or create a local node
22 | LocalNode localNode;
23 | try {
24 | localNode = storage.readLocalNode(LOCALNODE_BASE_FILENAME);
25 | } catch (TelehashException e) {
26 | if (e.getCause() instanceof FileNotFoundException) {
27 | // no local node found -- create a new one.
28 | try {
29 | localNode = Telehash.get().getCrypto().generateLocalNode();
30 | storage.writeLocalNode(localNode, LOCALNODE_BASE_FILENAME);
31 | } catch (TelehashException e1) {
32 | e1.printStackTrace();
33 | return;
34 | }
35 | } else {
36 | e.printStackTrace();
37 | return;
38 | }
39 | }
40 |
41 | // launch the switch
42 | Telehash telehash = new Telehash(localNode);
43 | Switch telehashSwitch = new Switch(telehash, null, PORT);
44 | telehash.setSwitch(telehashSwitch);
45 | try {
46 | telehashSwitch.start();
47 | } catch (TelehashException e) {
48 | e.printStackTrace();
49 | return;
50 | }
51 |
52 | // allow the switch to run for one hour
53 | try {
54 | Thread.sleep(3600 * 1000);
55 | } catch (InterruptedException e) {
56 | // TODO Auto-generated catch block
57 | e.printStackTrace();
58 | }
59 |
60 | // stop the switch
61 | telehashSwitch.stop();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/Telehash.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | import org.telehash.crypto.Crypto;
4 | import org.telehash.crypto.impl.CryptoImpl;
5 | import org.telehash.network.Network;
6 | import org.telehash.network.impl.NetworkImpl;
7 | import org.telehash.storage.Storage;
8 | import org.telehash.storage.impl.StorageImpl;
9 |
10 | public class Telehash {
11 |
12 | private Crypto mCrypto;
13 | private Storage mStorage;
14 | private Network mNetwork;
15 | private LocalNode mLocalNode;
16 | private Switch mSwitch;
17 |
18 | public Telehash() {
19 | mCrypto = new CryptoImpl();
20 | mStorage = new StorageImpl();
21 | mNetwork = new NetworkImpl();
22 | mLocalNode = null;
23 | mSwitch = null;
24 | }
25 |
26 | public Telehash(LocalNode localNode) {
27 | mCrypto = new CryptoImpl();
28 | mStorage = new StorageImpl();
29 | mNetwork = new NetworkImpl();
30 | mLocalNode = localNode;
31 | mSwitch = null;
32 | }
33 |
34 | public Telehash(LocalNode localNode, Crypto crypto, Storage storage, Network network) {
35 | mLocalNode = localNode;
36 | mCrypto = crypto;
37 | mStorage = storage;
38 | mNetwork = network;
39 | mSwitch = null;
40 | }
41 |
42 | public Crypto getCrypto() {
43 | return mCrypto;
44 | }
45 |
46 | public Storage getStorage() {
47 | return mStorage;
48 | }
49 |
50 | public Network getNetwork() {
51 | return mNetwork;
52 | }
53 |
54 | public void setLocalNode(LocalNode localNode) {
55 | mLocalNode = localNode;
56 | }
57 | public LocalNode getLocalNode() {
58 | return mLocalNode;
59 | }
60 |
61 | public void setSwitch(Switch telehashSwitch) {
62 | mSwitch = telehashSwitch;
63 | }
64 | public Switch getSwitch() {
65 | return mSwitch;
66 | }
67 |
68 | private static ThreadLocal sThreadLocal = new ThreadLocal();
69 |
70 | public static Telehash get() {
71 | Telehash telehash = sThreadLocal.get();
72 | if (telehash == null) {
73 | telehash = new Telehash();
74 | sThreadLocal.set(telehash);
75 | }
76 | return telehash;
77 | }
78 |
79 | public void setThreadLocal() {
80 | sThreadLocal.set(this);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/FullNode.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | import org.telehash.crypto.HashNamePublicKey;
4 | import org.telehash.network.Path;
5 |
6 | import java.util.Collection;
7 | import java.util.SortedMap;
8 |
9 | public abstract class FullNode extends PeerNode {
10 |
11 | // must be fully populated
12 | protected SortedMap mPublicKeys;
13 |
14 | protected FullNode(
15 | SortedMap publicKeys,
16 | Collection paths
17 | ) {
18 | super(
19 | HashName.calculateHashName(publicKeys),
20 | FingerprintSet.fromPublicKeys(publicKeys),
21 | determineActiveCipherSetAndKey(publicKeys),
22 | paths
23 | );
24 | mPublicKeys = publicKeys;
25 | }
26 |
27 | /**
28 | * Create a new FullNode with the specified fingerprints and public
29 | * keys. This is useful if we are reading a seeds.json, and thus may
30 | * need to represent a node with pre-cooked fingerprints that we may
31 | * not be able to reproduce (if we don't support all the cipher sets
32 | * of this node!)
33 | *
34 | * @param fingerprints
35 | * @param publicKeys
36 | * @param paths
37 | */
38 | protected FullNode(
39 | FingerprintSet fingerprints,
40 | SortedMap publicKeys,
41 | Collection paths
42 | ) {
43 | super(
44 | fingerprints.getHashName(),
45 | fingerprints,
46 | determineActiveCipherSetAndKey(publicKeys),
47 | paths
48 | );
49 | mPublicKeys = publicKeys;
50 | }
51 |
52 | public SortedMap getPublicKeys() {
53 | return mPublicKeys;
54 | }
55 |
56 | public HashNamePublicKey getPublicKey(CipherSetIdentifier csid) {
57 | if (mPublicKeys == null) {
58 | return null;
59 | }
60 | return mPublicKeys.get(csid);
61 | }
62 |
63 | private static Active determineActiveCipherSetAndKey(
64 | SortedMap publicKeyMap
65 | ) {
66 | Active active = new Active();
67 | active.cipherSetIdentifier = bestCipherSetIdentifier(publicKeyMap.keySet());
68 | active.publicKey = publicKeyMap.get(active.cipherSetIdentifier);
69 | return active;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/json/README:
--------------------------------------------------------------------------------
1 | JSON in Java [package org.json]
2 |
3 | Douglas Crockford
4 | douglas@crockford.com
5 |
6 | 2011-02-02
7 |
8 |
9 | JSON is a light-weight, language independent, data interchange format.
10 | See http://www.JSON.org/
11 |
12 | The files in this package implement JSON encoders/decoders in Java.
13 | It also includes the capability to convert between JSON and XML, HTTP
14 | headers, Cookies, and CDL.
15 |
16 | This is a reference implementation. There is a large number of JSON packages
17 | in Java. Perhaps someday the Java community will standardize on one. Until
18 | then, choose carefully.
19 |
20 | The license includes this restriction: "The software shall be used for good,
21 | not evil." If your conscience cannot live with that, then choose a different
22 | package.
23 |
24 | The package compiles on Java 1.2 thru Java 1.4.
25 |
26 |
27 | JSONObject.java: The JSONObject can parse text from a String or a JSONTokener
28 | to produce a map-like object. The object provides methods for manipulating its
29 | contents, and for producing a JSON compliant object serialization.
30 |
31 | JSONArray.java: The JSONObject can parse text from a String or a JSONTokener
32 | to produce a vector-like object. The object provides methods for manipulating
33 | its contents, and for producing a JSON compliant array serialization.
34 |
35 | JSONTokener.java: The JSONTokener breaks a text into a sequence of individual
36 | tokens. It can be constructed from a String, Reader, or InputStream.
37 |
38 | JSONException.java: The JSONException is the standard exception type thrown
39 | by this package.
40 |
41 |
42 | JSONString.java: The JSONString interface requires a toJSONString method,
43 | allowing an object to provide its own serialization.
44 |
45 | JSONStringer.java: The JSONStringer provides a convenient facility for
46 | building JSON strings.
47 |
48 | JSONWriter.java: The JSONWriter provides a convenient facility for building
49 | JSON text through a writer.
50 |
51 |
52 | CDL.java: CDL provides support for converting between JSON and comma
53 | delimited lists.
54 |
55 | Cookie.java: Cookie provides support for converting between JSON and cookies.
56 |
57 | CookieList.java: CookieList provides support for converting between JSON and
58 | cookie lists.
59 |
60 | HTTP.java: HTTP provides support for converting between JSON and HTTP headers.
61 |
62 | HTTPTokener.java: HTTPTokener extends JSONTokener for parsing HTTP headers.
63 |
64 | XML.java: XML provides support for converting between JSON and XML.
65 |
66 | JSONML.java: JSONML provides support for converting between JSONML and XML.
67 |
68 | XMLTokener.java: XMLTokener extends JSONTokener for parsing XML text.
69 |
--------------------------------------------------------------------------------
/src/test/java/org/telehash/test/mesh/ThreeLevelMeshTest.java:
--------------------------------------------------------------------------------
1 | package org.telehash.test.mesh;
2 |
3 | import static org.junit.Assert.assertEquals;
4 | import static org.junit.Assert.assertTrue;
5 |
6 | import org.junit.After;
7 | import org.junit.Before;
8 | import org.junit.Test;
9 | import org.telehash.core.CompletionHandler;
10 | import org.telehash.core.Line;
11 | import org.telehash.core.Log;
12 | import org.telehash.core.PlaceholderNode;
13 |
14 | import java.util.List;
15 | import java.util.Set;
16 |
17 | public class ThreeLevelMeshTest {
18 |
19 | private static final int NUM_NODES = 5;
20 | private static final int NODE_A = 3;
21 | private static final int NODE_B = 4;
22 |
23 | private List mNodes;
24 |
25 | @Before
26 | public void setUp() throws Exception {
27 | mNodes = TelehashTestInstance.createThreeLevelTopology();
28 | assertEquals(mNodes.size(), NUM_NODES);
29 | }
30 |
31 | @After
32 | public void tearDown() throws Exception {
33 | for (TelehashTestInstance node : mNodes) {
34 | node.stop();
35 | }
36 | }
37 |
38 | @Test
39 | public void testOpenLine() throws Exception {
40 | TelehashTestInstance src = mNodes.get(NODE_A);
41 | TelehashTestInstance dst = mNodes.get(NODE_B);
42 | PlaceholderNode dstNode = new PlaceholderNode(dst.getNode().getHashName());
43 |
44 | src.getSwitch().getLineManager().openLine(
45 | dstNode,
46 | false,
47 | new CompletionHandler() {
48 | @Override
49 | public void failed(Throwable exc, Object attachment) {
50 | Log.i("line open failed");
51 | }
52 | @Override
53 | public void completed(Line result, Object attachment) {
54 | Log.i("line open success");
55 | }
56 | },
57 | null
58 | );
59 |
60 | // TODO: signal failure/success/timeout via Object.notify().
61 | Thread.sleep(1000);
62 |
63 | // assure src has a line open to dst.
64 | assertLineOpen(src, dst);
65 | assertLineOpen(dst, src);
66 | }
67 |
68 | protected void assertLineOpen(TelehashTestInstance a, TelehashTestInstance b) {
69 | // assure A has a line open to B.
70 | boolean found = false;
71 | Set aLines = a.getSwitch().getLineManager().getLines();
72 | for (Line line : aLines) {
73 | if (line.getRemoteNode().equals(b.getNode())) {
74 | found = true;
75 | }
76 | }
77 | assertTrue(found);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/json/HTTPTokener.java:
--------------------------------------------------------------------------------
1 | package org.telehash.json;
2 |
3 | /*
4 | Copyright (c) 2002 JSON.org
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | The Software shall be used for Good, not Evil.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | /**
28 | * The HTTPTokener extends the JSONTokener to provide additional methods
29 | * for the parsing of HTTP headers.
30 | * @author JSON.org
31 | * @version 2012-11-13
32 | */
33 | public class HTTPTokener extends JSONTokener {
34 |
35 | /**
36 | * Construct an HTTPTokener from a string.
37 | * @param string A source string.
38 | */
39 | public HTTPTokener(String string) {
40 | super(string);
41 | }
42 |
43 |
44 | /**
45 | * Get the next token or string. This is used in parsing HTTP headers.
46 | * @throws JSONException
47 | * @return A String.
48 | */
49 | public String nextToken() throws JSONException {
50 | char c;
51 | char q;
52 | StringBuffer sb = new StringBuffer();
53 | do {
54 | c = next();
55 | } while (Character.isWhitespace(c));
56 | if (c == '"' || c == '\'') {
57 | q = c;
58 | for (;;) {
59 | c = next();
60 | if (c < ' ') {
61 | throw syntaxError("Unterminated string.");
62 | }
63 | if (c == q) {
64 | return sb.toString();
65 | }
66 | sb.append(c);
67 | }
68 | }
69 | for (;;) {
70 | if (c == 0 || Character.isWhitespace(c)) {
71 | return sb.toString();
72 | }
73 | sb.append(c);
74 | c = next();
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/Flag.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | /**
4 | * This class is a utility for blocking execution until another thread signals
5 | * its readiness (or signals an error).
6 | */
7 | public class Flag {
8 | private static final int NANOSECONDS_IN_MILLISECOND = 1000000;
9 | private boolean mFlagged = false;
10 | private boolean mTimeoutOccurred = false;
11 | private Throwable mError = null;
12 |
13 | /**
14 | * Wait until another thread signals its readiness (or error).
15 | * @return An error, if the other thread indicated such.
16 | */
17 | public Throwable waitForSignal() {
18 | synchronized (this) {
19 | while (mFlagged == false) {
20 | try {
21 | this.wait();
22 | } catch (InterruptedException e) {
23 | }
24 | }
25 | }
26 | return mError;
27 | }
28 |
29 | /**
30 | * Wait until another thread signals its readiness (or error), or until the
31 | * specified time elapses.
32 | *
33 | * @return An error, if the other thread indicated such.
34 | */
35 | public Throwable waitForSignal(int timeout) {
36 | long now = System.nanoTime();
37 | long stopTime = now + (long)timeout*NANOSECONDS_IN_MILLISECOND;
38 | synchronized (this) {
39 | while (mFlagged == false && now <= stopTime) {
40 | int remainingTime = (int)((stopTime-now)/NANOSECONDS_IN_MILLISECOND);
41 | try {
42 | this.wait(remainingTime);
43 | } catch (InterruptedException e) {
44 | }
45 | now = System.nanoTime();
46 | }
47 | if (mFlagged == false) {
48 | mTimeoutOccurred = true;
49 | }
50 | }
51 | return mError;
52 | }
53 |
54 | /**
55 | * Signal readiness to the blocked thread.
56 | */
57 | public void signal() {
58 | synchronized (this) {
59 | mFlagged = true;
60 | this.notify();
61 | }
62 | }
63 |
64 | /**
65 | * Reset to the original state.
66 | */
67 | public void reset() {
68 | synchronized (this) {
69 | mFlagged = false;
70 | mError = null;
71 | mTimeoutOccurred = false;
72 | }
73 | }
74 |
75 | /**
76 | * Signal an error to the blocked thread.
77 | * @param e The error to signal.
78 | */
79 | public void signalError(Throwable e) {
80 | mError = e;
81 | signal();
82 | }
83 |
84 | /**
85 | * After waitForSignal(int) returns, this method will return true if the
86 | * wait timed out.
87 | *
88 | * @return
89 | */
90 | public boolean timeoutOccurred() {
91 | return mTimeoutOccurred;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/android-demo/src/org/telehash/androiddemo/AndroidLogger.java:
--------------------------------------------------------------------------------
1 | package org.telehash.androiddemo;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 | import android.os.Message;
6 |
7 | import org.telehash.core.LogEntry;
8 | import org.telehash.core.LogListener;
9 |
10 | import java.io.PrintWriter;
11 | import java.io.StringWriter;
12 | import java.util.LinkedList;
13 |
14 | public class AndroidLogger implements LogListener {
15 |
16 | private static final int MAX_ENTRIES = 1000;
17 |
18 | private LinkedList mRingBuffer = new LinkedList();
19 |
20 | @Override
21 | public void onLogEvent(LogEntry entry) {
22 | synchronized (this) {
23 | mRingBuffer.add(entry);
24 | if (mRingBuffer.size() > MAX_ENTRIES) {
25 | mRingBuffer.removeFirst();
26 | }
27 | showLogEntry(entry);
28 | }
29 | }
30 |
31 | public static String renderEntry(LogEntry entry) {
32 | StringBuilder output = new StringBuilder();
33 | renderEntry(entry, output);
34 | return output.toString();
35 | }
36 |
37 | public static void renderEntry(LogEntry entry, StringBuilder output) {
38 | String text = entry.getMessage();
39 | for (String line : text.split("\n")) {
40 | if (! line.isEmpty()) {
41 | output.append(line+"\n");
42 | }
43 | }
44 |
45 | Throwable error = entry.getError();
46 | if (error != null) {
47 | StringWriter errors = new StringWriter();
48 | error.printStackTrace(new PrintWriter(errors));
49 | for (String line : errors.toString().split("\n")) {
50 | line.trim();
51 | output.append(line+"\n");
52 | }
53 | }
54 | }
55 |
56 | public String render() {
57 | StringBuilder output = new StringBuilder();
58 |
59 | synchronized (this) {
60 | for (LogEntry entry : mRingBuffer) {
61 | renderEntry(entry, output);
62 | }
63 | }
64 |
65 | return output.toString();
66 | }
67 |
68 | private LogFragment mLogFragment = null;
69 | public void setLogFragment(LogFragment logFragment) {
70 | mLogFragment = logFragment;
71 | }
72 |
73 | private Handler mHandler = null;
74 | public void showLogEntry(LogEntry entry) {
75 | if (mHandler == null) {
76 | mHandler = new Handler(Looper.getMainLooper()) {
77 | @Override
78 | public void handleMessage(Message msg) {
79 | if (mLogFragment != null) {
80 | mLogFragment.showEntry((LogEntry)msg.obj);
81 | }
82 | }
83 | };
84 | }
85 | Message msg = Message.obtain(mHandler, 0, entry);
86 | mHandler.sendMessage(msg);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/crypto/set2a/LinePublicKeyImpl.java:
--------------------------------------------------------------------------------
1 | package org.telehash.crypto.set2a;
2 |
3 | import org.spongycastle.crypto.params.ECDomainParameters;
4 | import org.spongycastle.crypto.params.ECPublicKeyParameters;
5 | import org.spongycastle.math.ec.ECPoint;
6 | import org.spongycastle.util.BigIntegers;
7 | import org.telehash.core.CipherSetIdentifier;
8 | import org.telehash.core.TelehashException;
9 | import org.telehash.crypto.LinePublicKey;
10 |
11 | import java.math.BigInteger;
12 | import java.util.Arrays;
13 |
14 | public class LinePublicKeyImpl implements LinePublicKey {
15 |
16 | private ECPublicKeyParameters mKey;
17 |
18 | public LinePublicKeyImpl(ECPublicKeyParameters publicKey) {
19 | mKey = publicKey;
20 | }
21 |
22 | public LinePublicKeyImpl(
23 | byte[] buffer,
24 | ECDomainParameters domainParameters
25 | ) throws TelehashException {
26 | // expect the public key in ANSI X9.63 format,
27 | // with the "04" identifier prefix byte removed
28 | if (buffer.length != 64) {
29 | throw new TelehashException("bad ANSI X9.63 (sans prefix) EC key encoding");
30 | }
31 | byte[] xBytes = new byte[32+1];
32 | byte[] yBytes = new byte[32+1];
33 | // assure the leading byte is zero, to indicate a positive value
34 | xBytes[0] = 0;
35 | yBytes[0] = 0;
36 | // copy
37 | System.arraycopy(buffer, 0, xBytes, 1, 32);
38 | System.arraycopy(buffer, 32, yBytes, 1, 32);
39 | BigInteger x = new BigInteger(xBytes);
40 | BigInteger y = new BigInteger(yBytes);
41 |
42 | ECPoint q = domainParameters.getCurve().createPoint(x, y, false);
43 | mKey = new ECPublicKeyParameters(q, domainParameters);
44 | }
45 |
46 | @Override
47 | public CipherSetIdentifier getCipherSetIdentifier() {
48 | return CipherSet2aImpl.CIPHER_SET_ID;
49 | }
50 |
51 | @Override
52 | public byte[] getEncoded() {
53 | // return the public key in ANSI X9.63 format,
54 | // with the "04" identifier prefix byte removed
55 | ECPoint qPoint = mKey.getQ();
56 |
57 | byte[] xBytes = BigIntegers.asUnsignedByteArray(32, qPoint.getX().toBigInteger());
58 | byte[] yBytes = BigIntegers.asUnsignedByteArray(32, qPoint.getY().toBigInteger());
59 | byte[] buffer = new byte[64];
60 | System.arraycopy(xBytes, 0, buffer, 0, xBytes.length);
61 | System.arraycopy(yBytes, 0, buffer, xBytes.length, yBytes.length);
62 |
63 | return buffer;
64 | }
65 |
66 | public ECPublicKeyParameters getKey() {
67 | return mKey;
68 | }
69 |
70 | @Override
71 | public boolean equals(Object other) {
72 | if (! (other instanceof LinePublicKey)) {
73 | return false;
74 | }
75 | LinePublicKey otherKey = (LinePublicKey)other;
76 | return Arrays.equals(this.getEncoded(), otherKey.getEncoded());
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/json/Property.java:
--------------------------------------------------------------------------------
1 | package org.telehash.json;
2 |
3 | /*
4 | Copyright (c) 2002 JSON.org
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | The Software shall be used for Good, not Evil.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | import java.util.Enumeration;
28 | import java.util.Iterator;
29 | import java.util.Properties;
30 |
31 | /**
32 | * Converts a Property file data into JSONObject and back.
33 | * @author JSON.org
34 | * @version 2013-05-23
35 | */
36 | public class Property {
37 | /**
38 | * Converts a property file object into a JSONObject. The property file object is a table of name value pairs.
39 | * @param properties java.util.Properties
40 | * @return JSONObject
41 | * @throws JSONException
42 | */
43 | public static JSONObject toJSONObject(java.util.Properties properties) throws JSONException {
44 | JSONObject jo = new JSONObject();
45 | if (properties != null && !properties.isEmpty()) {
46 | Enumeration enumProperties = properties.propertyNames();
47 | while(enumProperties.hasMoreElements()) {
48 | String name = (String)enumProperties.nextElement();
49 | jo.put(name, properties.getProperty(name));
50 | }
51 | }
52 | return jo;
53 |
54 | }
55 |
56 | /**
57 | * Converts the JSONObject into a property file object.
58 | * @param jo JSONObject
59 | * @return java.util.Properties
60 | * @throws JSONException
61 | */
62 | public static Properties toProperties(JSONObject jo) throws JSONException {
63 | Properties properties = new Properties();
64 | if (jo != null) {
65 | Iterator keys = jo.keys();
66 |
67 | while (keys.hasNext()) {
68 | String name = keys.next().toString();
69 | properties.put(name, jo.getString(name));
70 | }
71 | }
72 | return properties;
73 | }
74 | }
--------------------------------------------------------------------------------
/src/main/java/org/telehash/storage/Storage.java:
--------------------------------------------------------------------------------
1 | package org.telehash.storage;
2 |
3 | import org.telehash.core.LocalNode;
4 | import org.telehash.core.SeedNode;
5 | import org.telehash.core.TelehashException;
6 |
7 | import java.util.Set;
8 |
9 | /**
10 | * This interface contains methods that may be used to read and write Telehash
11 | * local node keys and seed cache information. Concrete implementations suitable
12 | * for specific platforms may be developed, and applications are free to extend
13 | * these implementations or provide their own.
14 | */
15 | public interface Storage {
16 | /**
17 | * Read the local Telehash node keys from files named using the specified
18 | * base filename.
19 | *
20 | * @param localNodeBaseFilename
21 | * The base filename, e.g. "localnode".
22 | * @return The read and parsed Telehash local node.
23 | * @throws TelehashException
24 | * If a problem happened while reading and parsing the local node.
25 | */
26 | public LocalNode readLocalNode(String localNodeBaseFilename) throws TelehashException;
27 |
28 | /**
29 | * Read the local Telehash node keys from files named using the default
30 | * base filename.
31 | *
32 | * @return The read and parsed Telehash local node.
33 | * @throws TelehashException
34 | * If a problem happened while reading and parsing the local node.
35 | */
36 | public LocalNode readLocalNode() throws TelehashException;
37 |
38 | /**
39 | * Write the local Telehash node keys into files named using the specified
40 | * base filename.
41 | *
42 | * @param localNode
43 | * The local node to write.
44 | * @param localNodeBaseFilename
45 | * The base filename, e.g. "localnode".
46 | * @throws TelehashException
47 | * If a problem happened while writing the local node.
48 | */
49 | public void writeLocalNode(LocalNode localNode, String localNodeBaseFilename)
50 | throws TelehashException;
51 |
52 | /**
53 | * Write the local Telehash local node (RSA key pair) into files named using
54 | * the default base filename.
55 | *
56 | * @param localNode
57 | * The local node to write.
58 | * @throws TelehashException
59 | * If a problem happened while writing the local node.
60 | */
61 | public void writeLocalNode(LocalNode localNode) throws TelehashException;
62 |
63 | /**
64 | * Read the local seed cache to obtain a set of nodes that may be used to
65 | * bootstrap the switch onto the Telehash network.
66 | *
67 | * @param seedsFilename
68 | * The filename of the JSON-encoded list of seed nodes.
69 | * @return A set of seed nodes.
70 | * @throws TelehashException
71 | * If a problem happened while reading and parsing the seeds.
72 | */
73 | public Set readSeeds(String seedsFilename) throws TelehashException;
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/json/zip/Keep.java:
--------------------------------------------------------------------------------
1 | package org.telehash.json.zip;
2 |
3 |
4 | /*
5 | Copyright (c) 2013 JSON.org
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | The Software shall be used for Good, not Evil.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 | SOFTWARE.
26 | */
27 |
28 | /**
29 | * A keep is a data structure that associates strings (or substrings) with
30 | * numbers. This allows the sending of small integers instead of strings.
31 | *
32 | * @author JSON.org
33 | * @version 2013-04-18
34 | */
35 | abstract class Keep implements None, PostMortem {
36 | protected int capacity;
37 | protected int length;
38 | protected int power;
39 | protected long[] uses;
40 |
41 | public Keep(int bits) {
42 | this.capacity = JSONzip.twos[bits];
43 | this.length = 0;
44 | this.power = 0;
45 | this.uses = new long[this.capacity];
46 | }
47 |
48 | /**
49 | * When an item ages, its use count is reduced by at least half.
50 | *
51 | * @param use
52 | * The current use count of an item.
53 | * @return The new use count for that item.
54 | */
55 | public static long age(long use) {
56 | return use >= 32 ? 16 : use / 2;
57 | }
58 |
59 | /**
60 | * Return the number of bits required to contain an integer based on the
61 | * current length of the keep. As the keep fills up, the number of bits
62 | * required to identify one of its items goes up.
63 | */
64 | public int bitsize() {
65 | while (JSONzip.twos[this.power] < this.length) {
66 | this.power += 1;
67 | }
68 | return this.power;
69 | }
70 |
71 | /**
72 | * Increase the usage count on an integer value.
73 | */
74 | public void tick(int integer) {
75 | this.uses[integer] += 1;
76 | }
77 |
78 | /**
79 | * Get the value associated with an integer.
80 | * @param integer The number of an item in the keep.
81 | * @return The value.
82 | */
83 | abstract public Object value(int integer);
84 | }
85 |
--------------------------------------------------------------------------------
/doc/lineopen.tex:
--------------------------------------------------------------------------------
1 | \documentclass{article}
2 |
3 | \usepackage[latin1]{inputenc}
4 | \usepackage{tikz}
5 | \usetikzlibrary{shapes,arrows,positioning,calc}
6 | \begin{document}
7 | \pagestyle{empty}
8 |
9 | % Define block styles
10 | \tikzstyle{decision} = [diamond, aspect=1.8, draw, fill=blue!10,
11 | text width=12em,
12 | minimum width=20em,
13 | text badly centered, inner sep=0pt]
14 | \tikzstyle{start} = [rectangle, draw, fill=green!10,
15 | text width=12em, text centered, rounded corners=12, minimum height=4em]
16 | \tikzstyle{stop} = [rectangle, draw, fill=green!10,
17 | text width=12em, text centered, rounded corners=12, minimum height=4em]
18 | \tikzstyle{block} = [rectangle, draw, fill=blue!10,
19 | text width=12em, text centered, minimum height=4em]
20 | \tikzstyle{line} = [draw, -latex']
21 | \tikzstyle{cloud} = [draw, ellipse,fill=red!20, node distance=3cm,
22 | minimum height=2em]
23 |
24 | \begin{figure}
25 | \centering
26 | \begin{tikzpicture}[scale=0.8, transform shape, node distance = 2em, auto]
27 | % Place nodes
28 | \node [start] (start) {\tt{\mbox{openLine(hashName)}}};
29 | \node [decision, below=of start] (islineopen) {Is there an existing line open to this node?};
30 | \node [decision, below=of islineopen] (haspath) {Is the provided node a seed? (i.e. we have full path and public key information.)};
31 | \node [block, right=of haspath] (openline) {Open a line to the node directly.};
32 | \node [block, below=of openline] (waitforremote2) {Wait for the remote node's {\tt open} response.};
33 | \node [decision, below=of haspath] (isintable) {Is this hash name in the routing table?};
34 | \node [block, right=of isintable] (nodelookup) {Perform node lookup to find the node.};
35 | \node [block, below=of isintable] (peerconnect) {UDP hole-punch if needed, and ask the referring node to introduce us via \tt{peer/connect}.};
36 | \node [block, below=of peerconnect] (waitforremote1) {Wait for the remote node's {\tt open} packet.};
37 | \node [block, below=of waitforremote1] (sendopenresponse) {Send our {\tt open} packet in response.};
38 | \node [stop, below=of sendopenresponse] (lineisopen) {Line is open.};
39 | % Draw edges
40 | \path [line] (start) -- (islineopen);
41 | \path [line] (islineopen) -- node [near start] {no} (haspath);
42 | \path [line] (haspath) -- node [near start] {yes} (openline);
43 | \path [line] (haspath) -- node [near start] {no} (isintable);
44 | \path [line] (openline) -- (waitforremote2);
45 | \path [line] (isintable) -- node [near start] {no} (nodelookup);
46 | \path [line] (isintable) -- node [near start] {yes} (peerconnect);
47 | \path [line] (nodelookup) |- (peerconnect);
48 | \path [line] (peerconnect) -- (waitforremote1);
49 | \path [line] (waitforremote1) -- (sendopenresponse);
50 | \path [line] (sendopenresponse) -- (lineisopen);
51 | \path [line] (waitforremote2.east) -- ++(0.5,0) |- (lineisopen.east);
52 | \path [line] (islineopen.west) -- node [near start] {yes} ++(-1.2,0) |- (lineisopen.west);
53 | \end{tikzpicture}
54 | \caption{Steps to open a Telehash line.}
55 | \end{figure}
56 |
57 | \end{document}
58 |
59 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/LocalNode.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | import org.telehash.crypto.HashNameKeyPair;
4 | import org.telehash.crypto.HashNamePrivateKey;
5 | import org.telehash.crypto.HashNamePublicKey;
6 |
7 | import java.util.Map;
8 | import java.util.SortedMap;
9 | import java.util.TreeMap;
10 |
11 | /**
12 | * An object of this class represents the parameters of the local Telehash node.
13 | */
14 | public class LocalNode extends FullNode {
15 | private SortedMap mPrivateKeys;
16 |
17 | /**
18 | * Create a LocalNode object based on the provided RSA key pair.
19 | * @param keyPair
20 | */
21 | public LocalNode(SortedMap keyPairs) {
22 | super(extractPublicKeys(keyPairs), /* paths */ null);
23 | mPrivateKeys = extractPrivateKeys(keyPairs);
24 | }
25 |
26 | private static SortedMap extractPublicKeys(
27 | SortedMap keyPairs
28 | ) {
29 | SortedMap publicKeys =
30 | new TreeMap();
31 | for (Map.Entry entry : keyPairs.entrySet()) {
32 | publicKeys.put(entry.getKey(), entry.getValue().getPublicKey());
33 | }
34 | return publicKeys;
35 | }
36 |
37 | private static SortedMap extractPrivateKeys(
38 | SortedMap keyPairs
39 | ) {
40 | SortedMap privateKeys =
41 | new TreeMap();
42 | for (Map.Entry entry : keyPairs.entrySet()) {
43 | privateKeys.put(entry.getKey(), entry.getValue().getPrivateKey());
44 | }
45 | return privateKeys;
46 | }
47 |
48 | /**
49 | * Return a map of all hashname key pairs (keyed by cipher set id).
50 | *
51 | * @return A map of all hashname key pairs.
52 | */
53 | public SortedMap getHashNameKeyPairs() {
54 | SortedMap keyPairs =
55 | new TreeMap();
56 | for (Map.Entry entry : mPrivateKeys.entrySet()) {
57 | keyPairs.put(
58 | entry.getKey(),
59 | Telehash.get().getCrypto().createHashNameKeyPair(
60 | mPublicKeys.get(entry.getKey()),
61 | entry.getValue()
62 | )
63 | );
64 | }
65 | return keyPairs;
66 | }
67 |
68 | /**
69 | * Return the hashname private key for the indicated cipher set.
70 | *
71 | * @param csid The cipher set id.
72 | * @return The hashname private key.
73 | */
74 | public HashNamePrivateKey getPrivateKey(CipherSetIdentifier csid) {
75 | return mPrivateKeys.get(csid);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/network/Path.java:
--------------------------------------------------------------------------------
1 | package org.telehash.network;
2 |
3 | import org.telehash.json.JSONArray;
4 | import org.telehash.json.JSONException;
5 | import org.telehash.json.JSONObject;
6 | import org.telehash.core.TelehashException;
7 |
8 | import java.net.InetSocketAddress;
9 | import java.net.SocketAddress;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | public abstract class Path implements Comparable {
14 | public static final String IPV4_TYPE = "ipv4";
15 | public static final String IPV6_TYPE = "ipv6";
16 | public static final String TYPE_KEY = "type";
17 |
18 | public abstract String getType();
19 | public abstract JSONObject toJSONObject();
20 |
21 | static public Path parsePath(String json) throws TelehashException {
22 | JSONObject jsonObject;
23 | try {
24 | jsonObject = new JSONObject(json);
25 | } catch (JSONException e) {
26 | throw new TelehashException(e);
27 | }
28 | return parsePath(jsonObject);
29 | }
30 |
31 | static public Path parsePath(JSONObject path) throws TelehashException {
32 | if (path == null) {
33 | return null;
34 | }
35 | String type = path.getString("type");
36 | if (type == null) {
37 | return null;
38 | }
39 | if (type.equals(IPV4_TYPE) || type.equals(IPV6_TYPE)) {
40 | return InetPath.parsePath(path);
41 | } else {
42 | return null;
43 | }
44 | }
45 |
46 | static public List parsePathArray(JSONArray array) throws TelehashException {
47 | List paths = new ArrayList();
48 | if (array == null) {
49 | return null;
50 | }
51 |
52 | for (int i=0; i mReactorMap = new HashMap();
24 | private InetPath mPath;
25 |
26 | public FakeNetworkImpl(Router router, String addressString) {
27 | mRouter = router;
28 | try {
29 | mPath = (InetPath)parsePath(addressString, 0);
30 | } catch (TelehashException e) {
31 | throw new RuntimeException(e);
32 | }
33 | router.registerNetwork(this);
34 | }
35 |
36 | /** intentionally package-private */
37 | InetPath getPath() {
38 | return mPath;
39 | }
40 |
41 | /** intentionally package-private */
42 | Router getRouter() {
43 | return mRouter;
44 | }
45 |
46 | /**
47 | * Parse a string representing a network address.
48 | *
49 | * TODO: we shouldn't need this... why is "see" hard-coded for IP addressing in the protocol?
50 | *
51 | * @param addressString
52 | * The path string to parse.
53 | * @return The network path object.
54 | * @throws TelehashException
55 | * If a problem occurred while parsing the path.
56 | */
57 | @Override
58 | public Path parsePath(String addressString, int port) throws TelehashException {
59 | InetAddress address;
60 | try {
61 | address = InetAddress.getByName(addressString);
62 | } catch (UnknownHostException e) {
63 | throw new TelehashException("invalid address or unknown host in path");
64 | }
65 | return new InetPath(address, port);
66 | }
67 |
68 | /**
69 | * Get preferred local path
70 | * TODO: This will certainly change... we need to support multiple network interfaces!
71 | */
72 | @Override
73 | public Path getPreferredLocalPath() throws TelehashException {
74 | return mPath;
75 | }
76 |
77 | /**
78 | * Provision a new reactor i/o engine listening on the specified port.
79 | *
80 | * @param port The IP port on which to listen.
81 | * @return The reactor.
82 | */
83 | @Override
84 | public Reactor createReactor(int port) {
85 | FakeReactorImpl reactor = new FakeReactorImpl(this, port);
86 | mReactorMap.put(port, reactor);
87 | return reactor;
88 | }
89 |
90 | @Override
91 | public void handleDatagram(Datagram datagram) {
92 | InetPath path = (InetPath)datagram.getDestination();
93 | if (mReactorMap.containsKey(path.getPort())) {
94 | mReactorMap.get(path.getPort()).handleDatagram(datagram);
95 | }
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/src/test/java/org/telehash/test/mesh/LargeScaleMeshTest.java:
--------------------------------------------------------------------------------
1 | package org.telehash.test.mesh;
2 |
3 | import static org.junit.Assert.assertEquals;
4 | import static org.junit.Assert.assertTrue;
5 |
6 | import org.junit.After;
7 | import org.junit.Before;
8 | import org.junit.Ignore;
9 | import org.junit.Test;
10 | import org.telehash.core.Channel;
11 | import org.telehash.core.ChannelHandler;
12 | import org.telehash.core.ChannelPacket;
13 | import org.telehash.core.Line;
14 | import org.telehash.core.Log;
15 | import org.telehash.core.Node;
16 | import org.telehash.core.PlaceholderNode;
17 |
18 | import java.util.List;
19 | import java.util.Set;
20 |
21 | @Ignore
22 | public class LargeScaleMeshTest {
23 |
24 | // depth 4 = 15 nodes
25 | // depth 5 = 31 nodes
26 | // depth 6 = 63 nodes
27 | private static final int TREE_DEPTH = 5;
28 | private static final int NUM_NODES = (1< mNodes;
34 |
35 | @Before
36 | public void setUp() throws Exception {
37 | mMesh = TelehashTestInstance.createLargeScaleTopology(TREE_DEPTH);
38 | mNodes = mMesh.getInstances();
39 | assertEquals(mNodes.size(), NUM_NODES);
40 | }
41 |
42 | @After
43 | public void tearDown() throws Exception {
44 | for (TelehashTestInstance node : mNodes) {
45 | node.stop();
46 | }
47 | }
48 |
49 | @Test
50 | public void testOpenLine() throws Exception {
51 | TelehashTestInstance src = mNodes.get(NODE_A);
52 | TelehashTestInstance dst = mNodes.get(NODE_B);
53 | Node destinationNode = new PlaceholderNode(dst.getNode().getHashName());
54 |
55 | Log.i("TEST: request channel open from "+src.getNode()+" to "+destinationNode);
56 | src.getSwitch().openChannel(destinationNode, "test", new ChannelHandler() {
57 | @Override
58 | public void handleError(Channel channel, Throwable error) {
59 | Log.e("channel open error:",error);
60 | }
61 | @Override
62 | public void handleIncoming(Channel channel,
63 | ChannelPacket channelPacket) {
64 | Log.i("incoming channel data: "+channelPacket);
65 | }
66 | @Override
67 | public void handleOpen(Channel channel) {
68 | Log.i("channel open success: "+channel);
69 | }
70 | });
71 |
72 | // TODO: signal failure/success/timeout via Object.notify().
73 | Thread.sleep(1000);
74 |
75 | // assure src has a line open to dst.
76 | Log.i("TEST: assert line open from "+src.getNode()+" to "+dst.getNode());
77 | assertLineOpen(src, dst);
78 | assertLineOpen(dst, src);
79 | }
80 |
81 | protected void assertLineOpen(TelehashTestInstance a, TelehashTestInstance b) {
82 | // assure A has a line open to B.
83 | boolean found = false;
84 | Set aLines = a.getSwitch().getLineManager().getLines();
85 | for (Line line : aLines) {
86 | if (line.getRemoteNode().equals(b.getNode())) {
87 | found = true;
88 | }
89 | }
90 | assertTrue(found);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/network/impl/NetworkImpl.java:
--------------------------------------------------------------------------------
1 | package org.telehash.network.impl;
2 |
3 | import org.telehash.core.TelehashException;
4 | import org.telehash.network.InetPath;
5 | import org.telehash.network.Network;
6 | import org.telehash.network.Path;
7 | import org.telehash.network.Reactor;
8 |
9 | import java.net.InetAddress;
10 | import java.net.NetworkInterface;
11 | import java.net.SocketException;
12 | import java.net.UnknownHostException;
13 | import java.util.Enumeration;
14 |
15 | /**
16 | * This class contains implementations for the network operations needed by
17 | * Telehash.
18 | */
19 | public class NetworkImpl implements Network {
20 |
21 | /**
22 | * Parse a string representing a network address.
23 | *
24 | * TODO: we shouldn't need this... why is "see" hard-coded for IP addressing in the protocol?
25 | *
26 | * @param addressString
27 | * The path string to parse.
28 | * @return The network path object.
29 | * @throws TelehashException
30 | * If a problem occurred while parsing the path.
31 | */
32 | @Override
33 | public Path parsePath(String addressString, int port) throws TelehashException {
34 | InetAddress address;
35 | try {
36 | address = InetAddress.getByName(addressString);
37 | } catch (UnknownHostException e) {
38 | throw new TelehashException("invalid address or unknown host in path");
39 | }
40 | return new InetPath(address, port);
41 | }
42 |
43 | /**
44 | * Get preferred local path
45 | * TODO: This will certainly change... we need to support multiple network interfaces!
46 | */
47 | @Override
48 | public Path getPreferredLocalPath() throws TelehashException {
49 | Enumeration networkInterfaces;
50 | try {
51 | networkInterfaces = NetworkInterface.getNetworkInterfaces();
52 | } catch (SocketException e) {
53 | throw new TelehashException(e);
54 | }
55 | while (networkInterfaces.hasMoreElements()) {
56 | NetworkInterface networkInterface = networkInterfaces.nextElement();
57 | Enumeration inetAddresses = networkInterface.getInetAddresses();
58 | while (inetAddresses.hasMoreElements()) {
59 | InetAddress inetAddress = inetAddresses.nextElement();
60 | if (inetAddress.isLoopbackAddress() || inetAddress.isLinkLocalAddress()) {
61 | continue;
62 | }
63 |
64 | // TODO: restrict to ipv4 for now, but must eventually support ipv6.
65 | // (the whole idea of a "preferred" network interface is temporary, anyway --
66 | // eventually all non-localhost addresses will be used, both IPv4 and IPv6.
67 | if (inetAddress.getAddress().length != 4) {
68 | continue;
69 | }
70 |
71 | return new InetPath(inetAddress, 0);
72 | }
73 | }
74 | return null;
75 | }
76 |
77 | /**
78 | * Provision a new reactor i/o engine listening on the specified port.
79 | *
80 | * @param port The IP port on which to listen.
81 | * @return The reactor.
82 | */
83 | @Override
84 | public Reactor createReactor(int port) {
85 | return new ReactorImpl(port);
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/Node.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | /**
4 | * This abstract class represents a Telehash node, including its
5 | * hashname and any public keys, fingerprints, and network paths we may
6 | * be aware of.
7 | *
8 | *
9 | * The data associated with a node varies throughout the Telehash
10 | * protocol and the operation of the switch, due to various steps having
11 | * incomplete information about the possible fields: private keys,
12 | * public keys, fingerprints, hashname, supported cipher sets, and
13 | * network paths. This information may be represented by various
14 | * subclasses:
15 | *
16 | *
17 | *
18 | *
A PlaceholderNode object only contains a hashname, and is used to
19 | * refer to nodes before a public key and compatible cipher set has been
20 | * determined. When connecting to a PlaceholderNode, a DHT node lookup
21 | * must be performed to discover a common peer willing to introduce us
22 | * via peer/connect.
23 | *
A SeeNode object represents a "see" line provided to us in
24 | * response to a node lookup. This object contains a single public key
25 | * corresponding to the best cipher set that the common peer has
26 | * determined we should use, and a reference to a referring PeerNode
27 | * which may be used to introduce us via peer/connect. The SeeNode
28 | * object may also contain a single network path for hole punching.
29 | *
PeerNode objects are the most commonly used node representations
30 | * in Telehash. A PeerNode object represents a peer for whom we have
31 | * enough information to directly communicate with -- a set of
32 | * fingerprints, a valid public key in the best mutually supported
33 | * cipher set, and one or more network paths.
34 | *
The FullNode abstract class represents a node for whom we have
35 | * full knowledge of its supported cipher sets and all available public
36 | * keys. FullNode subclasses are used to represent the local node and
37 | * any seeds we've read from a seeds.json file.
38 | *
A SeedNode is a node obtained from a seeds.json file. We have
39 | * full public key and network path information about these nodes.
40 | *
A switch owns a single LocalNode object which contains the public
41 | * and private key pairs for all supported cipher sets, from which its
42 | * fingerprints, hashname, and supported cipher sets may be derived. A
43 | * switch may have partial knowledge of its network paths as obtained by
44 | * enumerating the local network interfaces, which may be augmented
45 | * later if a different public network path (e.g. at a NAT router) is
46 | * discovered.
47 | */
48 | public abstract class Node {
49 |
50 | protected final HashName mHashName;
51 |
52 | protected Node(final HashName hashName) {
53 | mHashName = hashName;
54 | }
55 |
56 | public HashName getHashName() {
57 | return mHashName;
58 | }
59 |
60 | // Java identity
61 |
62 | @Override
63 | public boolean equals(Object other) {
64 | if ( other != null &&
65 | (other instanceof Node && ((Node)other).getHashName().equals(mHashName)) ||
66 | (other instanceof HashName && ((HashName)other).equals(mHashName))
67 | )
68 | {
69 | return true;
70 | } else {
71 | return false;
72 | }
73 | }
74 |
75 | @Override
76 | public int hashCode() {
77 | return mHashName.hashCode();
78 | }
79 | }
--------------------------------------------------------------------------------
/src/main/java/org/telehash/json/JSONStringer.java:
--------------------------------------------------------------------------------
1 | package org.telehash.json;
2 |
3 | /*
4 | Copyright (c) 2006 JSON.org
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | The Software shall be used for Good, not Evil.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | import java.io.StringWriter;
28 |
29 | /**
30 | * JSONStringer provides a quick and convenient way of producing JSON text.
31 | * The texts produced strictly conform to JSON syntax rules. No whitespace is
32 | * added, so the results are ready for transmission or storage. Each instance of
33 | * JSONStringer can produce one JSON text.
34 | *
35 | * A JSONStringer instance provides a value method for appending
36 | * values to the
37 | * text, and a key
38 | * method for adding keys before values in objects. There are array
39 | * and endArray methods that make and bound array values, and
40 | * object and endObject methods which make and bound
41 | * object values. All of these methods return the JSONWriter instance,
42 | * permitting cascade style. For example,
51 | * The first method called must be array or object.
52 | * There are no methods for adding commas or colons. JSONStringer adds them for
53 | * you. Objects and arrays can be nested up to 20 levels deep.
54 | *
55 | * This can sometimes be easier than using a JSONObject to build a string.
56 | * @author JSON.org
57 | * @version 2008-09-18
58 | */
59 | public class JSONStringer extends JSONWriter {
60 | /**
61 | * Make a fresh JSONStringer. It can be used to build one JSON text.
62 | */
63 | public JSONStringer() {
64 | super(new StringWriter());
65 | }
66 |
67 | /**
68 | * Return the JSON text. This method is used to obtain the product of the
69 | * JSONStringer instance. It will return null if there was a
70 | * problem in the construction of the JSON text (such as the calls to
71 | * array were not properly balanced with calls to
72 | * endArray).
73 | * @return The JSON text.
74 | */
75 | public String toString() {
76 | return this.mode == 'd' ? this.writer.toString() : null;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/json/CookieList.java:
--------------------------------------------------------------------------------
1 | package org.telehash.json;
2 |
3 | /*
4 | Copyright (c) 2002 JSON.org
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | The Software shall be used for Good, not Evil.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | import java.util.Iterator;
28 |
29 | /**
30 | * Convert a web browser cookie list string to a JSONObject and back.
31 | * @author JSON.org
32 | * @version 2010-12-24
33 | */
34 | public class CookieList {
35 |
36 | /**
37 | * Convert a cookie list into a JSONObject. A cookie list is a sequence
38 | * of name/value pairs. The names are separated from the values by '='.
39 | * The pairs are separated by ';'. The names and the values
40 | * will be unescaped, possibly converting '+' and '%' sequences.
41 | *
42 | * To add a cookie to a cooklist,
43 | * cookielistJSONObject.put(cookieJSONObject.getString("name"),
44 | * cookieJSONObject.getString("value"));
45 | * @param string A cookie list string
46 | * @return A JSONObject
47 | * @throws JSONException
48 | */
49 | public static JSONObject toJSONObject(String string) throws JSONException {
50 | JSONObject jo = new JSONObject();
51 | JSONTokener x = new JSONTokener(string);
52 | while (x.more()) {
53 | String name = Cookie.unescape(x.nextTo('='));
54 | x.next('=');
55 | jo.put(name, Cookie.unescape(x.nextTo(';')));
56 | x.next();
57 | }
58 | return jo;
59 | }
60 |
61 |
62 | /**
63 | * Convert a JSONObject into a cookie list. A cookie list is a sequence
64 | * of name/value pairs. The names are separated from the values by '='.
65 | * The pairs are separated by ';'. The characters '%', '+', '=', and ';'
66 | * in the names and values are replaced by "%hh".
67 | * @param jo A JSONObject
68 | * @return A cookie list string
69 | * @throws JSONException
70 | */
71 | public static String toString(JSONObject jo) throws JSONException {
72 | boolean b = false;
73 | Iterator keys = jo.keys();
74 | String string;
75 | StringBuffer sb = new StringBuffer();
76 | while (keys.hasNext()) {
77 | string = keys.next().toString();
78 | if (!jo.isNull(string)) {
79 | if (b) {
80 | sb.append(';');
81 | }
82 | sb.append(Cookie.escape(string));
83 | sb.append("=");
84 | sb.append(Cookie.escape(jo.getString(string)));
85 | b = true;
86 | }
87 | }
88 | return sb.toString();
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/test/java/org/telehash/test/NetworkTest.java:
--------------------------------------------------------------------------------
1 | package org.telehash.test;
2 |
3 | import static org.junit.Assert.assertArrayEquals;
4 | import static org.junit.Assert.assertNotNull;
5 | import static org.junit.Assert.assertTrue;
6 | import static org.junit.Assert.fail;
7 |
8 | import org.junit.After;
9 | import org.junit.Before;
10 | import org.junit.Test;
11 | import org.telehash.core.TelehashException;
12 | import org.telehash.network.InetPath;
13 | import org.telehash.network.Path;
14 |
15 | import java.net.InetAddress;
16 |
17 | public class NetworkTest {
18 |
19 | @Before
20 | public void setUp() throws Exception {
21 | }
22 |
23 | @After
24 | public void tearDown() throws Exception {
25 | }
26 |
27 | class ParsePathTest {
28 | String string;
29 | byte[] address;
30 | int port;
31 | public ParsePathTest(String string, byte[] address, int port) {
32 | this.string = string;
33 | this.address = address;
34 | this.port = port;
35 | }
36 | public ParsePathTest(String string) {
37 | // represent an invalid string
38 | this.string = string;
39 | this.address = null;
40 | this.port = 0;
41 | }
42 | public void test() throws Exception {
43 | // parse
44 | Path path;
45 | try {
46 | path = Path.parsePath(string);
47 | } catch (TelehashException e) {
48 | if (this.address == null) {
49 | // failure expected.
50 | return;
51 | } else {
52 | throw e;
53 | }
54 | }
55 | if (this.address == null) {
56 | fail("parse failure expected but didn't happen.");
57 | }
58 |
59 | // basic tests
60 | assertNotNull(path);
61 | assertTrue(path instanceof InetPath);
62 | InetPath inetPath = (InetPath)path;
63 | InetAddress inetAddress = inetPath.getAddress();
64 | assertNotNull(inetAddress);
65 | assertTrue(inetPath.getPort() > 0);
66 |
67 | // accuracy tests
68 | assertArrayEquals(inetAddress.getAddress(), address);
69 | assertTrue(inetPath.getPort() == port);
70 | }
71 | };
72 |
73 | ParsePathTest[] parsePathTests = new ParsePathTest[] {
74 | new ParsePathTest(
75 | "{\"type\":\"ipv4\", \"ip\": \"10.0.0.1\", \"port\": 4242}",
76 | new byte[]{10,0,0,1}, 4242
77 | ),
78 | new ParsePathTest(
79 | "{\"type\":\"ipv4\", \"ip\": \"192.168.1.100\", \"port\": 512}",
80 | new byte[]{(byte)192,(byte)168,1,100}, 512
81 | ),
82 | new ParsePathTest(
83 | "{\"type\":\"ipv6\", \"ip\": \"2001:0db8:85a3:0000:0000:8a2e:0370:7334\","+
84 | " \"port\": 1234}",
85 | new byte[]{
86 | 0x20, 0x01, 0x0d, (byte)0xb8, (byte)0x85, (byte)0xa3, 0x00, 0x00,
87 | 0x00, 0x00, (byte)0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34
88 | },
89 | 1234
90 | ),
91 | new ParsePathTest(
92 | "{\"type\":\"ipv6\", \"ip\": \"2001::1\", \"port\": 2345}",
93 | new byte[]{
94 | 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
95 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
96 | },
97 | 2345
98 | ),
99 | };
100 |
101 | @Test
102 | public void testParsePath() throws Exception {
103 | for (ParsePathTest test : parsePathTests) {
104 | test.test();
105 | }
106 | }
107 |
108 | }
109 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/Log.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | import java.util.ArrayList;
4 | import java.util.LinkedList;
5 | import java.util.List;
6 |
7 | public class Log {
8 |
9 | public enum Category {
10 | UNKNOWN
11 | };
12 |
13 | public enum Level {
14 | DEBUG,
15 | VERBOSE,
16 | INFO,
17 | WARNING,
18 | ERROR
19 | };
20 |
21 | private static Object sLock = new Object();
22 | private static List sLogListeners = new ArrayList();
23 | private static boolean mInitialized = false;
24 |
25 | private static ThreadLocal> sBuffer =
26 | new ThreadLocal>();
27 |
28 | public static void d(String msg, Object... args) {
29 | println(Level.DEBUG, msg, args);
30 | }
31 | public static void v(String msg, Object... args) {
32 | println(Level.VERBOSE, msg, args);
33 | }
34 | public static void i(String msg, Object... args) {
35 | println(Level.INFO, msg, args);
36 | }
37 | public static void w(String msg, Object... args) {
38 | println(Level.WARNING, msg, args);
39 | }
40 | public static void e(String msg, Object... args) {
41 | println(Level.ERROR, msg, args);
42 | }
43 |
44 | public static void setLogListener(LogListener logListener) {
45 | sLogListeners.clear();
46 | sLogListeners.add(logListener);
47 | }
48 |
49 | public static void addLogListener(LogListener logListener) {
50 | sLogListeners.add(logListener);
51 | }
52 |
53 | private static void println(Level level, String msg, Object... args) {
54 | // first-call initialization
55 | if (mInitialized == false) {
56 | if (sLogListeners.isEmpty()) {
57 | sLogListeners.add(new StandardLogger());
58 | }
59 | mInitialized = true;
60 | }
61 |
62 | if (msg == null || msg.isEmpty()) {
63 | return;
64 | }
65 | String text = String.format(msg, args);
66 | LogEntry entry;
67 | entry = new LogEntry(Category.UNKNOWN, level, text);
68 | if (args.length > 0 && (args[args.length-1] instanceof Throwable)) {
69 | Throwable throwable = (Throwable)args[args.length - 1];
70 | entry = new LogEntry(Category.UNKNOWN, level, text, throwable);
71 | } else {
72 | entry = new LogEntry(Category.UNKNOWN, level, text);
73 | }
74 |
75 | synchronized (sLock) {
76 | LinkedList buffer = sBuffer.get();
77 | if (buffer != null) {
78 | buffer.add(entry);
79 | } else {
80 | for (LogListener listener : sLogListeners) {
81 | listener.onLogEvent(entry);
82 | }
83 | }
84 | }
85 | }
86 |
87 | public static void buffer() {
88 | synchronized (sLock) {
89 | LinkedList buffer = sBuffer.get();
90 | if (buffer != null) {
91 | throw new IllegalStateException("buffer() called twice without flush().");
92 | }
93 | sBuffer.set(new LinkedList());
94 | }
95 | }
96 |
97 | public static void flush() {
98 | synchronized (sLock) {
99 | LinkedList buffer = sBuffer.get();
100 | if (buffer == null) {
101 | throw new IllegalStateException("flush() called without buffer().");
102 | }
103 | for (LogEntry entry : buffer) {
104 | for (LogListener listener : sLogListeners) {
105 | listener.onLogEvent(entry);
106 | }
107 | }
108 | sBuffer.remove();
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/android-demo/src/org/telehash/androiddemo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package org.telehash.androiddemo;
2 |
3 | import android.app.ActionBar;
4 | import android.app.ActionBar.Tab;
5 | import android.app.Activity;
6 | import android.app.Fragment;
7 | import android.app.FragmentTransaction;
8 | import android.content.ComponentName;
9 | import android.content.Context;
10 | import android.content.Intent;
11 | import android.content.ServiceConnection;
12 | import android.os.Bundle;
13 | import android.os.IBinder;
14 | import android.view.Menu;
15 |
16 | import org.telehash.androiddemo.TelehashService.TelehashBinder;
17 |
18 | public class MainActivity extends Activity {
19 |
20 | private ActionBar.Tab mLogTab;
21 | private ActionBar.Tab mLineTab;
22 | private LogFragment mLogFragment = new LogFragment();
23 | private Fragment mLineFragment = new LineFragment();
24 |
25 | private TelehashService mService;
26 | private boolean mBound = false;
27 |
28 | /** Defines callbacks for service binding, passed to bindService() */
29 | private ServiceConnection mConnection = new ServiceConnection() {
30 | @Override
31 | public void onServiceConnected(ComponentName className, IBinder service) {
32 | // We've bound to LocalService, cast the IBinder and get LocalService instance
33 | TelehashBinder binder = (TelehashBinder) service;
34 | mService = binder.getService();
35 | mBound = true;
36 | mService.getLogger().setLogFragment(mLogFragment);
37 | }
38 |
39 | @Override
40 | public void onServiceDisconnected(ComponentName arg0) {
41 | if (mService != null) {
42 | mService.getLogger().setLogFragment(null);
43 | }
44 | mBound = false;
45 | mService = null;
46 | }
47 | };
48 |
49 | public class MyTabListener implements ActionBar.TabListener {
50 | Fragment fragment;
51 |
52 | public MyTabListener(Fragment fragment) {
53 | this.fragment = fragment;
54 | }
55 |
56 | @Override
57 | public void onTabSelected(Tab tab, FragmentTransaction ft) {
58 | ft.replace(R.id.fragment_container, fragment);
59 | }
60 |
61 | @Override
62 | public void onTabUnselected(Tab tab, FragmentTransaction ft) {
63 | ft.remove(fragment);
64 | }
65 |
66 | @Override
67 | public void onTabReselected(Tab tab, FragmentTransaction ft) {
68 | // nothing done here
69 | }
70 | }
71 |
72 | @Override
73 | protected void onCreate(Bundle savedInstanceState) {
74 | super.onCreate(savedInstanceState);
75 | setContentView(R.layout.activity_main);
76 |
77 | ActionBar actionBar = getActionBar();
78 | actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
79 |
80 | mLogTab = actionBar.newTab().setText("log");
81 | mLineTab = actionBar.newTab().setText("lines");
82 |
83 | mLogTab.setTabListener(new MyTabListener(mLogFragment));
84 | mLineTab.setTabListener(new MyTabListener(mLineFragment));
85 |
86 | actionBar.addTab(mLogTab);
87 | actionBar.addTab(mLineTab);
88 | }
89 |
90 | @Override
91 | public boolean onCreateOptionsMenu(Menu menu) {
92 | // Inflate the menu; this adds items to the action bar if it is
93 | // present.
94 | getMenuInflater().inflate(R.menu.main, menu);
95 | return true;
96 | }
97 |
98 | @Override
99 | protected void onStart() {
100 | super.onStart();
101 | // Bind to LocalService
102 | Intent intent = new Intent(this, TelehashService.class);
103 | bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
104 | }
105 |
106 | @Override
107 | protected void onStop() {
108 | super.onStop();
109 | if (mBound) {
110 | unbindService(mConnection);
111 | mBound = false;
112 | }
113 | }
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/Channel.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | import java.util.Map;
4 |
5 | public class Channel implements OnTimeoutListener {
6 | private ChannelIdentifier mChannelIdentifier;
7 | private String mType;
8 | private ChannelHandler mChannelHandler;
9 |
10 | private Telehash mTelehash;
11 | private Line mLine;
12 | private boolean mSentFirstPacket = false;
13 | private Timeout mTimeout;
14 |
15 | public Channel(Telehash telehash, Line line, String type) {
16 | mTelehash = telehash;
17 | mLine = line;
18 | mType = type;
19 | mChannelIdentifier = line.getNextChannelId();
20 | mTimeout = telehash.getSwitch().getTimeout(this, 0);
21 | }
22 |
23 | public Channel(Telehash telehash, Line line, ChannelIdentifier channelIdentifer, String type) {
24 | mTelehash = telehash;
25 | mLine = line;
26 | mChannelIdentifier = channelIdentifer;
27 | mType = type;
28 | mTimeout = telehash.getSwitch().getTimeout(this, 0);
29 | }
30 |
31 | public void setLine(Line line) {
32 | mLine = line;
33 | }
34 |
35 | public Line getLine() {
36 | return mLine;
37 | }
38 |
39 | public PeerNode getRemoteNode() {
40 | return mLine.getRemotePeerNode();
41 | }
42 |
43 | public ChannelIdentifier getChannelIdentifier() {
44 | return mChannelIdentifier;
45 | }
46 |
47 | public void setChannelIdentifier(ChannelIdentifier channelIdentifier) {
48 | mChannelIdentifier = channelIdentifier;
49 | }
50 |
51 | public String getType() {
52 | return mType;
53 | }
54 |
55 | public void setType(String type) {
56 | mType = type;
57 | }
58 |
59 | public ChannelHandler getChannelHandler() {
60 | return mChannelHandler;
61 | }
62 |
63 | public void setChannelHandler(ChannelHandler channelHandler) {
64 | mChannelHandler = channelHandler;
65 | }
66 |
67 | public void setTimeout(long timeout) {
68 | mTimeout.setDelay(timeout);
69 | }
70 |
71 | public long getTimeout() {
72 | return mTimeout.getDelay();
73 | }
74 |
75 | public void receive(ChannelPacket channelPacket) {
76 | mTimeout.reset();
77 | mChannelHandler.handleIncoming(this, channelPacket);
78 | }
79 |
80 | public void send(byte[] body) throws TelehashException {
81 | send(body, null, false);
82 | }
83 |
84 | public void send(byte[] body, Map fields, boolean end) throws TelehashException {
85 | ChannelPacket channelPacket = new ChannelPacket();
86 | channelPacket.setChannelIdentifier(mChannelIdentifier);
87 | if (! mSentFirstPacket) {
88 | // "type" is only sent for the first packet in a channel
89 | channelPacket.setType(mType);
90 | mSentFirstPacket = true;
91 | }
92 | if (fields != null) {
93 | for (Map.Entry field : fields.entrySet()) {
94 | channelPacket.put(field.getKey(), field.getValue());
95 | }
96 | }
97 | if (end) {
98 | channelPacket.put("end", true);
99 | // TODO: remove from Line's channel tracking
100 | }
101 | channelPacket.setBody(body);
102 | Log.i("outgoing: "+mLine+" "+this+" "+channelPacket);
103 | mTelehash.getSwitch().getLineManager().sendLinePacket(
104 | mLine,
105 | channelPacket,
106 | null,
107 | null
108 | );
109 |
110 | mTimeout.reset();
111 | }
112 |
113 | public void close() throws TelehashException {
114 | send(null, null, true);
115 | }
116 |
117 | @Override
118 | public void handleTimeout() {
119 | mChannelHandler.handleError(this, new TelehashException("timeout"));
120 | mTimeout.cancel();
121 | // TODO: close channel / dereference from switch
122 | }
123 |
124 | @Override
125 | public String toString() {
126 | return mLine.getRemoteNode().getHashName().getShortHash()+":"+mChannelIdentifier+"/"+mType;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/crypto/set2a/HashNamePrivateKeyImpl.java:
--------------------------------------------------------------------------------
1 | package org.telehash.crypto.set2a;
2 |
3 | import org.spongycastle.asn1.ASN1Encodable;
4 | import org.spongycastle.asn1.ASN1InputStream;
5 | import org.spongycastle.asn1.ASN1Integer;
6 | import org.spongycastle.asn1.ASN1Primitive;
7 | import org.spongycastle.asn1.ASN1Sequence;
8 | import org.spongycastle.crypto.params.AsymmetricKeyParameter;
9 | import org.spongycastle.crypto.params.RSAPrivateCrtKeyParameters;
10 | import org.telehash.core.CipherSetIdentifier;
11 | import org.telehash.core.TelehashException;
12 | import org.telehash.crypto.HashNamePrivateKey;
13 |
14 | import java.io.ByteArrayInputStream;
15 | import java.io.IOException;
16 | import java.math.BigInteger;
17 |
18 | public class HashNamePrivateKeyImpl implements HashNamePrivateKey {
19 |
20 | RSAPrivateCrtKeyParameters mKey;
21 |
22 | public HashNamePrivateKeyImpl(RSAPrivateCrtKeyParameters key) {
23 | mKey = key;
24 | }
25 |
26 | public HashNamePrivateKeyImpl(byte[] derBuffer) throws TelehashException {
27 | try {
28 | ASN1InputStream asn1InputStream =
29 | new ASN1InputStream(new ByteArrayInputStream(derBuffer));
30 | ASN1Primitive toplevelObject = asn1InputStream.readObject();
31 | asn1InputStream.close();
32 | if (! (toplevelObject instanceof ASN1Sequence)) {
33 | throw new TelehashException("ASN.1 toplevel object not sequence");
34 | }
35 | ASN1Sequence sequence = (ASN1Sequence)toplevelObject;
36 | if (getIntegerFromSequence(sequence, 0).compareTo(BigInteger.ZERO) != 0) {
37 | throw new TelehashException("only PKCS#1v1.5 (version=0) structures supported.");
38 | }
39 |
40 | mKey = new RSAPrivateCrtKeyParameters(
41 | getIntegerFromSequence(sequence, 1),
42 | getIntegerFromSequence(sequence, 2),
43 | getIntegerFromSequence(sequence, 3),
44 | getIntegerFromSequence(sequence, 4),
45 | getIntegerFromSequence(sequence, 5),
46 | getIntegerFromSequence(sequence, 6),
47 | getIntegerFromSequence(sequence, 7),
48 | getIntegerFromSequence(sequence, 8)
49 | );
50 | } catch (IOException e) {
51 | throw new TelehashException(e);
52 | }
53 | }
54 |
55 | public AsymmetricKeyParameter getKey() {
56 | return mKey;
57 | }
58 |
59 | @Override
60 | public CipherSetIdentifier getCipherSetIdentifier() {
61 | return CipherSet2aImpl.CIPHER_SET_ID;
62 | }
63 |
64 | @Override
65 | public byte[] getEncoded() throws TelehashException {
66 | org.spongycastle.asn1.pkcs.RSAPrivateKey asn1Key;
67 | asn1Key =
68 | new org.spongycastle.asn1.pkcs.RSAPrivateKey(
69 | mKey.getModulus(),
70 | mKey.getPublicExponent(),
71 | mKey.getExponent(),
72 | mKey.getP(),
73 | mKey.getQ(),
74 | mKey.getDP(),
75 | mKey.getDQ(),
76 | mKey.getQInv()
77 | );
78 |
79 | try {
80 | return asn1Key.getEncoded("DER");
81 | } catch (IOException e) {
82 | throw new TelehashException(e);
83 | }
84 | }
85 |
86 | /**
87 | * Helper method to extract a BigInteger from an ASN1 sequence.
88 | *
89 | * @param sequence An ASN1 sequence
90 | * @param index The index of the sequence from which we fetch the ASN1Integer value.
91 | * @return The integer value.
92 | * @throws TelehashException
93 | */
94 | private BigInteger getIntegerFromSequence(
95 | ASN1Sequence sequence,
96 | int index
97 | ) throws TelehashException {
98 | ASN1Encodable encodable = sequence.getObjectAt(index);
99 | if (!(encodable instanceof ASN1Integer)) {
100 | throw new TelehashException("error parsing ASN.1: expected integer");
101 | }
102 | return ((ASN1Integer)encodable).getValue();
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/test/java/org/telehash/test/network/FakeReactorImpl.java:
--------------------------------------------------------------------------------
1 | package org.telehash.test.network;
2 |
3 | import org.telehash.network.Datagram;
4 | import org.telehash.network.DatagramHandler;
5 | import org.telehash.network.InetPath;
6 | import org.telehash.network.Message;
7 | import org.telehash.network.MessageHandler;
8 | import org.telehash.network.Reactor;
9 |
10 | import java.io.IOException;
11 | import java.util.LinkedList;
12 | import java.util.Queue;
13 |
14 | public class FakeReactorImpl implements Reactor {
15 |
16 | private FakeNetworkImpl mNetwork;
17 | private InetPath mPath;
18 |
19 | private int mPort;
20 | private DatagramHandler mDatagramHandler;
21 | private MessageHandler mMessageHandler;
22 | private Queue mWriteQueue = new LinkedList();
23 | private Queue mReadQueue = new LinkedList();
24 | private Queue mMessageQueue = new LinkedList();
25 | private Object mLock = new Object();
26 |
27 | /**
28 | * Construct a new ReactorImpl.
29 | *
30 | * This constructor is intentionally package-private.
31 | */
32 | FakeReactorImpl(FakeNetworkImpl network, int port) {
33 | mNetwork = network;
34 | mPort = port;
35 | mPath = new InetPath(network.getPath().getAddress(), mPort);
36 | }
37 |
38 | void handleDatagram(Datagram datagram) {
39 | synchronized (mLock) {
40 | mReadQueue.offer(datagram);
41 | }
42 | wakeup();
43 | }
44 |
45 | @Override
46 | public void setDatagramHandler(DatagramHandler datagramHandler) {
47 | mDatagramHandler = datagramHandler;
48 | }
49 |
50 | @Override
51 | public void setMessageHandler(MessageHandler messageHandler) {
52 | mMessageHandler = messageHandler;
53 | }
54 |
55 | @Override
56 | public void start() throws IOException {
57 | }
58 |
59 | @Override
60 | public void stop() {
61 | wakeup();
62 | }
63 |
64 | @Override
65 | public void close() throws IOException {
66 | }
67 |
68 | @Override
69 | public void wakeup() {
70 | synchronized (mLock) {
71 | mLock.notifyAll();
72 | }
73 | }
74 |
75 | @Override
76 | public void select(long timeout) throws IOException {
77 | Datagram writeDatagram;
78 | Datagram readDatagram;
79 | Message message;
80 |
81 | synchronized (mLock) {
82 | // select
83 | if (mWriteQueue.isEmpty() &&
84 | mReadQueue.isEmpty() &&
85 | mMessageQueue.isEmpty() &&
86 | timeout != -1) {
87 | try {
88 | mLock.wait(timeout);
89 | } catch (InterruptedException e) {
90 | e.printStackTrace();
91 | }
92 | }
93 |
94 | writeDatagram = mWriteQueue.poll();
95 | readDatagram = mReadQueue.poll();
96 | message = mMessageQueue.poll();
97 | }
98 |
99 | // dispatch
100 | if (writeDatagram != null) {
101 | mNetwork.getRouter().sendDatagram(writeDatagram);
102 | }
103 | if (readDatagram != null) {
104 | if (mDatagramHandler != null) {
105 | mDatagramHandler.handleDatagram(readDatagram);
106 | }
107 | }
108 | if (message != null) {
109 | if (mMessageHandler != null) {
110 | mMessageHandler.handleMessage(message);
111 | }
112 | }
113 | }
114 |
115 | /**
116 | * Send a datagram. This is potentially called from an outside thread.
117 | *
118 | * @param datagram
119 | */
120 | @Override
121 | public void sendDatagram(Datagram datagram) {
122 | synchronized (mLock) {
123 | datagram.setSource(mPath);
124 | mWriteQueue.offer(datagram);
125 | wakeup();
126 | }
127 | }
128 |
129 | @Override
130 | public void sendMessage(Message message) {
131 | synchronized (mLock) {
132 | mMessageQueue.offer(message);
133 | wakeup();
134 | }
135 | }
136 |
137 | }
138 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/network/InetPath.java:
--------------------------------------------------------------------------------
1 | package org.telehash.network;
2 |
3 | import org.telehash.json.JSONObject;
4 | import org.telehash.core.TelehashException;
5 |
6 | import java.net.Inet4Address;
7 | import java.net.Inet6Address;
8 | import java.net.InetAddress;
9 | import java.net.UnknownHostException;
10 |
11 | public class InetPath extends Path {
12 | private static final String IP_ADDRESS_KEY = "ip";
13 | private static final String PORT_KEY = "port";
14 |
15 | private InetAddress mAddress;
16 | private int mPort;
17 |
18 | public InetPath(InetAddress address, int port) {
19 | mAddress = address;
20 | mPort = port;
21 | }
22 |
23 | public InetAddress getAddress() {
24 | return mAddress;
25 | }
26 | public int getPort() {
27 | return mPort;
28 | }
29 |
30 | public String getAddressString() {
31 | return mAddress.getHostAddress();
32 | }
33 |
34 | @Override
35 | public String getType() {
36 | if (mAddress instanceof Inet4Address) {
37 | return IPV4_TYPE;
38 | } else if (mAddress instanceof Inet6Address) {
39 | return IPV6_TYPE;
40 | } else {
41 | return "ip-unknown";
42 | }
43 | }
44 |
45 | @Override
46 | public JSONObject toJSONObject() {
47 | JSONObject json = new JSONObject();
48 | json.put(TYPE_KEY, getType());
49 | json.put(IP_ADDRESS_KEY, mAddress.getHostAddress());
50 | json.put(PORT_KEY, mPort);
51 | return json;
52 | }
53 |
54 | @Override
55 | public String toString() {
56 | return getType()+":" + mAddress.getHostAddress() + "/" + mPort;
57 | }
58 |
59 | static public InetPath parsePath(JSONObject path) throws TelehashException {
60 | if (path == null) {
61 | return null;
62 | }
63 | String type = path.getString(TYPE_KEY);
64 | if (type == null || type.isEmpty()) {
65 | return null;
66 | }
67 |
68 | String ipString = (String) path.get(IP_ADDRESS_KEY);
69 | if (ipString == null || ipString.isEmpty()) {
70 | return null;
71 | }
72 | int port = ((Number)path.get(PORT_KEY)).intValue();
73 | InetAddress address;
74 | try {
75 | // TODO: is this safe?
76 | address = InetAddress.getByName(ipString);
77 | } catch (UnknownHostException e) {
78 | throw new TelehashException(e);
79 | }
80 |
81 | // validation
82 | if ( (! type.equals(IPV4_TYPE)) && (! type.equals(IPV6_TYPE)) ) {
83 | throw new TelehashException("unknown internet path type \""+type+"\".");
84 | }
85 | if ( (!(address instanceof Inet4Address)) && (!(address instanceof Inet6Address)) ) {
86 | throw new TelehashException("path does not represent a valid address");
87 | }
88 | if ( (address instanceof Inet4Address && (! type.equals(IPV4_TYPE))) ||
89 | (address instanceof Inet6Address && (! type.equals(IPV6_TYPE))) ) {
90 | throw new TelehashException(
91 | "address \""+ipString+"\" is not suitable for type \""+type+"\"."
92 | );
93 | }
94 |
95 | return new InetPath(address, port);
96 | }
97 |
98 | @Override
99 | public int hashCode() {
100 | final int prime = 31;
101 | int result = 1;
102 | result = prime * result + ((mAddress == null) ? 0 : mAddress.hashCode());
103 | result = prime * result + mPort;
104 | return result;
105 | }
106 |
107 | @Override
108 | public boolean equals(Object obj) {
109 | if (this == obj)
110 | return true;
111 | if (obj == null)
112 | return false;
113 | if (getClass() != obj.getClass())
114 | return false;
115 | InetPath other = (InetPath) obj;
116 | if (mAddress == null) {
117 | if (other.mAddress != null)
118 | return false;
119 | } else if (!mAddress.equals(other.mAddress))
120 | return false;
121 | if (mPort != other.mPort)
122 | return false;
123 | return true;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/crypto/Crypto.java:
--------------------------------------------------------------------------------
1 | package org.telehash.crypto;
2 |
3 | import org.telehash.core.CipherSetIdentifier;
4 | import org.telehash.core.LocalNode;
5 | import org.telehash.core.TelehashException;
6 |
7 | import java.util.NavigableSet;
8 | import java.util.Set;
9 |
10 | /**
11 | * This interface contains the basic cryptographic functions required by
12 | * Telehash. Concrete implementations suitable for specific platforms may be
13 | * developed, and applications are free to extend these implementations or
14 | * provide their own.
15 | */
16 | public interface Crypto {
17 |
18 | /**
19 | * Return the cipher set associated with the provided cipher set id.
20 | * @param cipherSetId
21 | * @return The cipher set implementation, or null if no cipher set
22 | * matches the id.
23 | */
24 | public CipherSet getCipherSet(CipherSetIdentifier cipherSetId);
25 |
26 | /**
27 | * Return the set of all supported cipher sets.
28 | * @return The set of cipher sets.
29 | */
30 | public Set getAllCipherSets();
31 |
32 | /**
33 | * Return the set of all supported cipher sets ids.
34 | * @return The set of cipher set identifiers.
35 | */
36 | public NavigableSet getAllCipherSetsIds();
37 |
38 | /**
39 | * Generate a cryptographically secure pseudo-random array of byte values.
40 | *
41 | * @param size The number of random bytes to produce.
42 | * @return The array of random byte values.
43 | */
44 | public byte[] getRandomBytes(int size);
45 |
46 | /**
47 | * Return a SHA-256 digest of the provided byte buffer.
48 | *
49 | * @param buffer The buffer to digest.
50 | * @return A 32-byte array representing the digest.
51 | */
52 | public byte[] sha256Digest(byte[] buffer);
53 |
54 | /**
55 | * Return a SHA-256 digest of the provided UTF-8 string.
56 | *
57 | * @param string The string to digest.
58 | * @return A 32-byte array representing the digest.
59 | */
60 | public byte[] sha256Digest(String string);
61 |
62 | /**
63 | * Generate a fresh local node (i.e., public and private key pair) for a
64 | * newly provisioned Telehash node.
65 | *
66 | * @return The new local node.
67 | * @throws TelehashException
68 | */
69 | public LocalNode generateLocalNode() throws TelehashException;
70 |
71 | /**
72 | * Encrypt data with an RSA public key
73 | * @throws TelehashException
74 | */
75 | public byte[] encryptRSAOAEP(HashNamePublicKey key, byte[] clearText) throws TelehashException;
76 |
77 | /**
78 | * Decrypt data with an RSA private key
79 | * @throws TelehashException
80 | */
81 | public byte[] decryptRSAOAEP(HashNamePrivateKey key, byte[] buffer) throws TelehashException;
82 |
83 | /**
84 | * Sign a data buffer with an RSA private key using the SHA-256 digest, and
85 | * PKCSv1.5 padding.
86 | *
87 | * @throws TelehashException
88 | */
89 | public byte[] signRSA(HashNamePrivateKey key, byte[] buffer) throws TelehashException;
90 |
91 | /**
92 | * Verify the signature of a data buffer with an RSA private key using the
93 | * SHA-256 digest, and PKCSv1.5 padding.
94 | *
95 | * @return true if the signature is valid; false otherwise.
96 | * @throws TelehashException
97 | */
98 | public boolean verifyRSA(
99 | HashNamePublicKey key,
100 | byte[] buffer,
101 | byte[] signature
102 | ) throws TelehashException;
103 |
104 | /**
105 | * Create a new ECKeyPair from the provided public and private key.
106 | * @param privateKey
107 | * @param publicKey
108 | * @return The newly created ECKeyPair object.
109 | */
110 | public LineKeyPair createECKeyPair(
111 | LinePublicKey publicKey,
112 | LinePrivateKey privateKey
113 | ) throws TelehashException;
114 |
115 | /**
116 | * Create a new HashNameKeyPair from the provided public and private key.
117 | * @param privateKey
118 | * @param publicKey
119 | * @return The newly created HashNameKeyPair object.
120 | */
121 | public HashNameKeyPair createHashNameKeyPair(
122 | HashNamePublicKey publicKey,
123 | HashNamePrivateKey privateKey
124 | );
125 | }
126 |
--------------------------------------------------------------------------------
/src/test/java/org/telehash/test/mesh/StarMeshTest.java:
--------------------------------------------------------------------------------
1 | package org.telehash.test.mesh;
2 |
3 | import static org.junit.Assert.assertTrue;
4 |
5 | import org.junit.After;
6 | import org.junit.Before;
7 | import org.junit.Test;
8 | import org.telehash.core.Channel;
9 | import org.telehash.core.ChannelHandler;
10 | import org.telehash.core.ChannelPacket;
11 | import org.telehash.core.CompletionHandler;
12 | import org.telehash.core.Line;
13 | import org.telehash.core.Log;
14 | import org.telehash.core.TelehashException;
15 | import org.telehash.dht.DHT;
16 |
17 | import java.util.HashMap;
18 | import java.util.List;
19 | import java.util.Map;
20 | import java.util.Set;
21 |
22 | public class StarMeshTest {
23 | private static final int NUM_NODES = 3;
24 | private static final int NODE_SEED = 0;
25 | private static final int NODE_A = 1;
26 | private static final int NODE_B = 2;
27 |
28 | private List mNodes;
29 |
30 | @Before
31 | public void setUp() throws Exception {
32 | mNodes = TelehashTestInstance.createStarTopology(NUM_NODES);
33 | }
34 |
35 | @After
36 | public void tearDown() throws Exception {
37 | for (TelehashTestInstance node : mNodes) {
38 | node.stop();
39 | }
40 | }
41 |
42 | @Test
43 | public void testOpenLine() throws Exception {
44 | Log.i("testOpenLine()");
45 | TelehashTestInstance src = mNodes.get(NODE_A);
46 | TelehashTestInstance dst = mNodes.get(NODE_B);
47 |
48 | src.getSwitch().getLineManager().openLine(
49 | dst.getNode(),
50 | false,
51 | new CompletionHandler() {
52 | @Override
53 | public void failed(Throwable exc, Object attachment) {
54 | Log.i("line open failed");
55 | }
56 | @Override
57 | public void completed(Line result, Object attachment) {
58 | Log.i("line open success");
59 | }
60 | },
61 | null
62 | );
63 |
64 | // TODO: signal failure/success/timeout via Object.notify().
65 | Thread.sleep(1000);
66 |
67 | // assure src has a line open to dst.
68 | assertLineOpen(src, dst);
69 | assertLineOpen(dst, src);
70 | }
71 |
72 | @Test
73 | public void testPeerConnect() throws Exception {
74 | final TelehashTestInstance seed = mNodes.get(NODE_SEED);
75 | final TelehashTestInstance src = mNodes.get(NODE_A);
76 | final TelehashTestInstance dst = mNodes.get(NODE_B);
77 | Log.i("OPEN "+src.getNode()+" -> "+dst.getNode());
78 |
79 | src.getSwitch().openChannel(seed.getNode(), DHT.PEER_TYPE, new ChannelHandler() {
80 | @Override
81 | public void handleError(Channel channel, Throwable error) {
82 | Log.i("cannot open peer channel");
83 | }
84 | @Override
85 | public void handleIncoming(Channel channel,
86 | ChannelPacket channelPacket) {
87 | Log.i("expected silence, but received on channel: "+channelPacket);
88 | }
89 | @Override
90 | public void handleOpen(Channel channel) {
91 | Map fields = new HashMap();
92 | fields.put(DHT.PEER_KEY, dst.getNode().getHashName().asHex());
93 | try {
94 | channel.send(null, fields, false);
95 | } catch (TelehashException e) {
96 | // TODO Auto-generated catch block
97 | e.printStackTrace();
98 | }
99 | }
100 | });
101 |
102 | // TODO: signal failure/success/timeout via Object.notify().
103 | Thread.sleep(1000);
104 |
105 | // assure src has a line open to dst.
106 | assertLineOpen(src, dst);
107 | assertLineOpen(dst, src);
108 | }
109 |
110 | protected void assertLineOpen(TelehashTestInstance a, TelehashTestInstance b) {
111 | // assure A has a line open to B.
112 | boolean found = false;
113 | Set aLines = a.getSwitch().getLineManager().getLines();
114 | for (Line line : aLines) {
115 | if (line.getRemoteNode().equals(b.getNode())) {
116 | found = true;
117 | }
118 | }
119 | assertTrue(found);
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/HashName.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | import org.telehash.crypto.HashNamePublicKey;
4 |
5 | import java.math.BigInteger;
6 | import java.util.Arrays;
7 | import java.util.Map;
8 | import java.util.SortedMap;
9 | import java.util.TreeMap;
10 |
11 | /**
12 | * Wrap a hash name. This is needed so we can establish a sensible
13 | * Java object identity and use a hash name as a key in HashMap.
14 | */
15 | public class HashName implements Comparable {
16 | public static final int SIZE = 32;
17 |
18 | private byte[] mBuffer;
19 |
20 | public HashName(byte[] buffer) {
21 | if (buffer == null || buffer.length != SIZE) {
22 | throw new IllegalArgumentException("invalid hash name");
23 | }
24 | mBuffer = buffer;
25 | }
26 |
27 | public BigInteger distance(HashName other) {
28 | BigInteger a = new BigInteger(1, mBuffer);
29 | BigInteger b = new BigInteger(1, other.mBuffer);
30 | return (a.xor(b));
31 | }
32 |
33 | /**
34 | * Return the hashspace distance magnitude between this hashname and the
35 | * specified hashname. This is defined as the binary logarithm of the xor of
36 | * the two hashnames (or -1, if the hashnames are identical). This
37 | * distance magnitude metric is suitable for use as an index into an array
38 | * of buckets. (Unless the returned value is -1 indicating the hashnames are
39 | * the same, in which case nothing should be stored in a bucket.)
40 | *
41 | * The returned value will always be between -1 and 255, inclusive.
42 | *
43 | * @param A
44 | * The first hashname.
45 | * @param other
46 | * The second hashname.
47 | * @return The distance, or -1 if the hashnames are identical.
48 | */
49 | public int distanceMagnitude(HashName other) {
50 | // opportunities for optimization abound.
51 | // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
52 |
53 | if (this == null || other == null) {
54 | throw new IllegalArgumentException("invalid hashname");
55 | }
56 | byte[] ba = this.getBytes();
57 | byte[] bb = other.getBytes();
58 | for (int i=0; i publicKeys
82 | ) {
83 | // compose the hash name
84 | SortedMap fingerprintMap =
85 | new TreeMap();
86 | for (Map.Entry entry : publicKeys.entrySet()) {
87 | fingerprintMap.put(entry.getKey(), entry.getValue().getFingerprint());
88 | }
89 | FingerprintSet fingerprints = new FingerprintSet(fingerprintMap);
90 | return fingerprints.getHashName();
91 | }
92 |
93 | @Override
94 | public String toString() {
95 | return asHex();
96 | }
97 |
98 | public String getShortHash() {
99 | return toString().substring(0, 8);
100 | }
101 |
102 | // Java identity
103 |
104 | @Override
105 | public boolean equals(Object other) {
106 | if (other != null &&
107 | (other instanceof HashName && Arrays.equals(((HashName)other).mBuffer, mBuffer)) ||
108 | (other instanceof Node && Arrays.equals(((Node)other).getHashName().mBuffer, mBuffer))
109 | ) {
110 | return true;
111 | } else {
112 | return false;
113 | }
114 | }
115 |
116 | @Override
117 | public int hashCode() {
118 | return Arrays.hashCode(mBuffer);
119 | }
120 |
121 | @Override
122 | public int compareTo(HashName other) {
123 | BigInteger a = new BigInteger(1, mBuffer);
124 | BigInteger b = new BigInteger(1, other.mBuffer);
125 | return a.compareTo(b);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/sample/java/org/telehash/sample/BasicNode.java:
--------------------------------------------------------------------------------
1 | package org.telehash.sample;
2 |
3 | import org.telehash.core.CipherSetIdentifier;
4 | import org.telehash.core.LocalNode;
5 | import org.telehash.core.SeedNode;
6 | import org.telehash.core.Switch;
7 | import org.telehash.core.Telehash;
8 | import org.telehash.core.TelehashException;
9 | import org.telehash.core.Util;
10 | import org.telehash.crypto.HashNamePublicKey;
11 | import org.telehash.storage.Storage;
12 | import org.telehash.storage.impl.StorageImpl;
13 |
14 | import java.io.FileNotFoundException;
15 | import java.util.Map;
16 | import java.util.Set;
17 |
18 | public class BasicNode {
19 |
20 | private static final String LOCALNODE_BASE_FILENAME = "telehash-node";
21 | private static final int PORT = 42424;
22 |
23 | public static final void main(String[] args) {
24 |
25 | Storage storage = new StorageImpl();
26 |
27 | // load or create a local node
28 | LocalNode localNode;
29 | try {
30 | localNode = storage.readLocalNode(LOCALNODE_BASE_FILENAME);
31 | } catch (TelehashException e) {
32 | if (e.getCause() instanceof FileNotFoundException) {
33 | // no local node found -- create a new one.
34 | try {
35 | localNode = Telehash.get().getCrypto().generateLocalNode();
36 | storage.writeLocalNode(localNode, LOCALNODE_BASE_FILENAME);
37 | } catch (TelehashException e1) {
38 | e1.printStackTrace();
39 | return;
40 | }
41 | } else {
42 | e.printStackTrace();
43 | return;
44 | }
45 | }
46 |
47 | System.out.println("my hash name: "+localNode.getHashName());
48 |
49 | Set seeds = null;
50 | try {
51 | seeds = storage.readSeeds("seeds.json");
52 | } catch (TelehashException e2) {
53 | // TODO Auto-generated catch block
54 | e2.printStackTrace();
55 | }
56 |
57 | // debug seeds
58 | System.out.println("seeds:");
59 | for (SeedNode seed : seeds) {
60 | System.out.println(" hn " + seed.getHashName());
61 | for (Map.Entry entry : seed
62 | .getFingerprints().entrySet()) {
63 | System.out.println(" cs" + entry.getKey() + " fingerprint: "
64 | + Util.bytesToHex(entry.getValue()));
65 | }
66 | for (CipherSetIdentifier csid : seed.getCipherSetIds()) {
67 | System.out.println(" cs " + csid);
68 | try {
69 | HashNamePublicKey publicKey = seed.getPublicKey(csid);
70 | if (publicKey != null) {
71 | System.out.println(" pub: "
72 | + Util.base64Encode(seed.getPublicKey(csid)
73 | .getEncoded()));
74 | System.out.println(" fpr: "
75 | + Util.bytesToHex(seed.getPublicKey(csid)
76 | .getFingerprint()));
77 | }
78 | } catch (TelehashException e) {
79 | // TODO Auto-generated catch block
80 | e.printStackTrace();
81 | }
82 | }
83 | }
84 |
85 | // launch the switch
86 | final Telehash telehash = new Telehash(localNode);
87 | final Switch telehashSwitch = new Switch(telehash, seeds, PORT);
88 | telehash.setSwitch(telehashSwitch);
89 | try {
90 | telehashSwitch.start();
91 | } catch (TelehashException e) {
92 | e.printStackTrace();
93 | return;
94 | }
95 |
96 | try {
97 | System.out.println("preferred local path: "+
98 | telehash.getNetwork().getPreferredLocalPath());
99 | } catch (TelehashException e1) {
100 | // TODO Auto-generated catch block
101 | e1.printStackTrace();
102 | }
103 |
104 | // send packet
105 | System.out.println("node sending packet to seed.");
106 |
107 | // sleep 4 hours...
108 | try {
109 | Thread.sleep(4 * 60 * 60 * 1000);
110 | } catch (InterruptedException e) {
111 | // TODO Auto-generated catch block
112 | e.printStackTrace();
113 | }
114 |
115 | // stop the switch
116 | telehashSwitch.stop();
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/test/java/org/telehash/test/UtilTest.java:
--------------------------------------------------------------------------------
1 | package org.telehash.test;
2 | import static org.junit.Assert.assertArrayEquals;
3 | import static org.junit.Assert.assertEquals;
4 | import static org.junit.Assert.assertNotNull;
5 | import static org.junit.Assert.assertNull;
6 |
7 | import org.junit.After;
8 | import org.junit.Before;
9 | import org.junit.Test;
10 | import org.telehash.core.Util;
11 |
12 |
13 | public class UtilTest {
14 |
15 | private static final String TEST_HEX_STRING = "00FF7f8081abab";
16 | private static final byte[] TEST_BYTES = {
17 | 0x00, (byte)0xFF, 0x7F, (byte)0x80, (byte)0x81, (byte)0xAB, (byte)0xAB
18 | };
19 |
20 | @Before
21 | public void setUp() throws Exception {
22 | }
23 |
24 | @After
25 | public void tearDown() throws Exception {
26 | }
27 |
28 | @Test
29 | public void testHexToBytes() {
30 | byte[] buffer = Util.hexToBytes(TEST_HEX_STRING);
31 | assertArrayEquals(buffer, TEST_BYTES);
32 | }
33 |
34 | @Test
35 | public void testBytesToHex() {
36 | String hex = Util.bytesToHex(TEST_BYTES);
37 | assertEquals(hex, TEST_HEX_STRING.toLowerCase());
38 | }
39 |
40 | private static final String[][] BASE64_TESTS = {
41 | {"", ""},
42 | {"a", "YQ=="},
43 | {"ab", "YWI="},
44 | {"abc", "YWJj"},
45 | {"abcd", "YWJjZA=="},
46 | {"abcde", "YWJjZGU="},
47 | {"abcdef", "YWJjZGVm"},
48 | {"abcdefg", "YWJjZGVmZw=="},
49 | { "This is a test. This is only a test.",
50 | "VGhpcyBpcyBhIHRlc3QuICBUaGlzIGlzIG9ubHkgYSB0ZXN0Lg=="
51 | }
52 | };
53 | private static final Object[][] BASE64_BINARY_TESTS = {
54 | {new byte[] {}, ""},
55 | {new byte[] {0x01}, "AQ=="},
56 | {new byte[] {0x01, 0x02}, "AQI="},
57 | {new byte[] {0x01, 0x02, 0x03}, "AQID"},
58 | {new byte[] {0x01, 0x02, 0x03, 0x04}, "AQIDBA=="},
59 | {new byte[] {(byte)0x81}, "gQ=="},
60 | {new byte[] {(byte)0x81, (byte)0x82}, "gYI="},
61 | {new byte[] {(byte)0x81, (byte)0x82, (byte)0x83}, "gYKD"},
62 | {new byte[] {(byte)0x81, (byte)0x82, (byte)0x83, (byte)0x84}, "gYKDhA=="},
63 | {new byte[] {(byte)0xFF}, "/w=="},
64 | {new byte[] {(byte)0xFF, (byte)0xFF}, "//8="},
65 | {new byte[] {(byte)0xFF, (byte)0xFF, (byte)0xFF}, "////"},
66 | {new byte[] {(byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF}, "/////w=="},
67 | };
68 | private static final Object[][] BASE64_DECODE_TESTS = {
69 | // invalid character
70 | { "ABCDE?FG", null },
71 | // no padding - needs 1 char of padding
72 | { "YWJjZGU", "abcde" },
73 | // no padding - needs 2 chars of padding
74 | {
75 | "VGhpcyBpcyBhIHRlc3QuICBUaGlzIGlzIG9ubHkgYSB0ZXN0Lg",
76 | "This is a test. This is only a test."
77 | }
78 | };
79 |
80 | @Test
81 | public void testBase64EncodeDecode() throws Exception {
82 | for (String[] test : BASE64_TESTS) {
83 | String message = test[0];
84 | String expectedEncoding = test[1];
85 |
86 | String encoding = Util.base64Encode(message.getBytes("UTF-8"));
87 | assertEquals(expectedEncoding, encoding);
88 | byte[] decodedBytes = Util.base64Decode(encoding);
89 | assertNotNull(decodedBytes);
90 | String decoding = new String(decodedBytes, "UTF-8");
91 | assertEquals(message, decoding);
92 | }
93 |
94 | for (Object[] test : BASE64_BINARY_TESTS) {
95 | byte[] message = (byte[]) test[0];
96 | String expectedEncoding = (String) test[1];
97 |
98 | String encoding = Util.base64Encode(message);
99 | assertEquals(expectedEncoding, encoding);
100 | byte[] decoding = Util.base64Decode(encoding);
101 | assertNotNull(decoding);
102 | assertArrayEquals(message, decoding);
103 | }
104 |
105 | for (Object[] test : BASE64_DECODE_TESTS) {
106 | String base64 = (String)test[0];
107 | byte[] expectedDecoding;
108 | if (test[1] instanceof String) {
109 | expectedDecoding = ((String)test[1]).getBytes("UTF-8");
110 | } else {
111 | expectedDecoding = (byte[])test[1];
112 | }
113 |
114 | byte[] decoding = Util.base64Decode(base64);
115 | if (expectedDecoding == null) {
116 | assertNull(decoding);
117 | } else {
118 | assertArrayEquals(expectedDecoding, decoding);
119 | }
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/test/java/org/telehash/test/DHTTest.java:
--------------------------------------------------------------------------------
1 | package org.telehash.test;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import org.junit.After;
6 | import org.junit.Before;
7 | import org.junit.Test;
8 | import org.telehash.core.HashName;
9 | import org.telehash.core.Util;
10 | import org.telehash.crypto.Crypto;
11 | import org.telehash.crypto.impl.CryptoImpl;
12 | import org.telehash.dht.DHT;
13 |
14 | public class DHTTest {
15 |
16 | @Before
17 | public void setUp() throws Exception {
18 | }
19 |
20 | @After
21 | public void tearDown() throws Exception {
22 | }
23 |
24 | private class DistanceTest {
25 | private HashName mOrigin;
26 | private HashName mRemote;
27 | private int mDistance;
28 | public DistanceTest(byte[] originBytes, byte[] remoteBytes, int distance) {
29 | mOrigin = new HashName(Util.fixedSizeBytes(originBytes, HashName.SIZE));
30 | mRemote = new HashName(Util.fixedSizeBytes(remoteBytes, HashName.SIZE));
31 | mDistance = distance;
32 | }
33 | public DistanceTest(int[] originBytes, int[] remoteBytes, int distance) {
34 | mOrigin = new HashName(padBytes(originBytes));
35 | mRemote = new HashName(padBytes(remoteBytes));
36 | mDistance = distance;
37 | }
38 | private byte[] padBytes(int[] ints) {
39 | byte[] bytes = new byte[ints.length];
40 | for (int i=0; i "+mRemote+" = ");
47 | int measuredDistance = mOrigin.distanceMagnitude(mRemote);
48 | System.out.println(measuredDistance+" (expect: "+mDistance+")");
49 | assertEquals(mDistance, measuredDistance);
50 | }
51 | }
52 | DistanceTest[] mDistanceTests = new DistanceTest[] {
53 | new DistanceTest(
54 | new byte[] {1,2,3},
55 | new byte[] {1,2,3},
56 | -1 // indicates identical hashnames
57 | ),
58 | new DistanceTest(
59 | new byte[] {0},
60 | new byte[] {1},
61 | 0
62 | ),
63 | new DistanceTest(
64 | new byte[] {1},
65 | new byte[] {2},
66 | 1
67 | ),
68 | new DistanceTest(
69 | new byte[] {2},
70 | new byte[] {3},
71 | 0
72 | ),
73 | new DistanceTest(
74 | new int[] {0xFF},
75 | new int[] {0xFE},
76 | 0
77 | ),
78 | new DistanceTest(
79 | new int[] {0xFF},
80 | new int[] {0xF0},
81 | 3
82 | ),
83 | new DistanceTest(
84 | new int[] {0xFF, 0xFF},
85 | new int[] {0xFF, 0xF0},
86 | 3
87 | ),
88 | new DistanceTest(
89 | new int[] {0xFF, 0xFF},
90 | new int[] {0x00, 0xFF},
91 | 15
92 | ),
93 | new DistanceTest(
94 | new int[] {0xFF, 0xFF},
95 | new int[] {0x0F, 0xFF},
96 | 15
97 | ),
98 | new DistanceTest(
99 | new int[] {0x1F, 0xFF},
100 | new int[] {0x0F, 0xFF},
101 | 12
102 | ),
103 | };
104 |
105 | @Test
106 | public void testDistance() throws Exception {
107 | for (DistanceTest test : mDistanceTests) {
108 | test.test();
109 | }
110 | }
111 |
112 | private static final int NUM_ORIGINS = 16;
113 | private static final int NUM_RANDOMS = 16;
114 |
115 | @Test
116 | public void testRandomHashName() throws Exception {
117 | Crypto crypto = new CryptoImpl();
118 | for (int x=0; x sColorMap = new HashMap();
35 |
36 | private static final boolean DEFAULT_ENABLE_COLOR = false;
37 | private static boolean sEnableColor = DEFAULT_ENABLE_COLOR;
38 |
39 | private static final String TMP_DIRECTORY = "/tmp";
40 | private static final String LOG_PATH = "/tmp/telehash.log";
41 | private static Set sLogStreams = new HashSet();
42 | static {
43 | sLogStreams.add(System.out);
44 | // on systems with a /tmp, store a copy of the log in /tmp/telehash.log
45 | // TODO: this is temporary, for early-stage development.
46 | if (new File(TMP_DIRECTORY).exists()) {
47 | try {
48 | sLogStreams.add(new PrintStream(LOG_PATH));
49 | } catch (FileNotFoundException e) {
50 | e.printStackTrace();
51 | }
52 | }
53 | }
54 |
55 | public static void setEnableColor(boolean enableColor) {
56 | sEnableColor = enableColor;
57 | }
58 |
59 | @Override
60 | public void onLogEvent(LogEntry entry) {
61 | if (entry == null) {
62 | return;
63 | }
64 | LocalNode localNode = entry.getTelehash().getLocalNode();
65 |
66 | String tag;
67 | if (localNode == null) {
68 | tag = "[ ] ";
69 | } else {
70 | byte[] hashName = localNode.getHashName().getBytes();
71 | int a = hashName[0] & 0xFF;
72 | int b = hashName[1] & 0xFF;
73 | int c = hashName[2] & 0xFF;
74 | int d = hashName[3] & 0xFF;
75 | tag = String.format("[%02x%02x%02x%02x] ", a,b,c,d);
76 | }
77 |
78 | String timestamp = String.format("%07.3f", (entry.getTime()/1000000000.0f));
79 | tag = timestamp + " " + tag;
80 |
81 | String color;
82 | String endColor;
83 | if (sEnableColor && localNode != null) {
84 | HashName hashName = localNode.getHashName();
85 | color = sColorMap.get(hashName);
86 | if (color == null) {
87 | color = COLORS[Math.abs(hashName.hashCode()) % COLORS.length];
88 | sColorMap.put(hashName, color);
89 | }
90 | endColor = COLOR_RESET;
91 | } else {
92 | color = "";
93 | endColor = "";
94 | }
95 |
96 | StringBuilder output = new StringBuilder();
97 | String text = entry.getMessage();
98 | for (String line : text.split("\n")) {
99 | if (! line.isEmpty()) {
100 | output.append(color+tag+line+endColor+"\n");
101 | }
102 | }
103 |
104 | Throwable error = entry.getError();
105 | if (error != null) {
106 | StringWriter errors = new StringWriter();
107 | error.printStackTrace(new PrintWriter(errors));
108 | for (String line : errors.toString().split("\n")) {
109 | line.trim();
110 | output.append(color+tag+line+endColor+"\n");
111 | }
112 | }
113 |
114 | String finalOutput = output.toString();
115 | for (PrintStream stream : sLogStreams) {
116 | stream.print(finalOutput);
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/json/zip/BitOutputStream.java:
--------------------------------------------------------------------------------
1 | package org.telehash.json.zip;
2 |
3 | import java.io.IOException;
4 | import java.io.OutputStream;
5 |
6 | /*
7 | Copyright (c) 2013 JSON.org
8 |
9 | Permission is hereby granted, free of charge, to any person obtaining a copy
10 | of this software and associated documentation files (the "Software"), to deal
11 | in the Software without restriction, including without limitation the rights
12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | copies of the Software, and to permit persons to whom the Software is
14 | furnished to do so, subject to the following conditions:
15 |
16 | The above copyright notice and this permission notice shall be included in all
17 | copies or substantial portions of the Software.
18 |
19 | The Software shall be used for Good, not Evil.
20 |
21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 | SOFTWARE.
28 | */
29 |
30 | /**
31 | * This is a big endian bit writer. It writes its bits to an OutputStream.
32 | *
33 | * @version 2013-04-18
34 | *
35 | */
36 | public class BitOutputStream implements BitWriter {
37 |
38 | /**
39 | * The number of bits written.
40 | */
41 | private long nrBits = 0;
42 |
43 | /**
44 | * The destination of the bits.
45 | */
46 | private OutputStream out;
47 |
48 | /**
49 | * Holder of bits not yet written.
50 | */
51 | private int unwritten;
52 |
53 | /**
54 | * The number of unused bits in this.unwritten.
55 | */
56 | private int vacant = 8;
57 |
58 | /**
59 | * Use an OutputStream to produce a BitWriter. The BitWriter will send its
60 | * bits to the OutputStream as each byte is filled.
61 | *
62 | * @param out
63 | * An Output Stream
64 | */
65 | public BitOutputStream(OutputStream out) {
66 | this.out = out;
67 | }
68 |
69 | /**
70 | * Returns the number of bits that have been written to this
71 | * bitOutputStream. This may include bits that have not yet been written
72 | * to the underlying outputStream.
73 | */
74 | public long nrBits() {
75 | return this.nrBits;
76 | }
77 |
78 | /**
79 | * Write a 1 bit.
80 | *
81 | * @throws IOException
82 | */
83 | public void one() throws IOException {
84 | write(1, 1);
85 | }
86 |
87 | /**
88 | * Pad the rest of the block with zeroes and flush. pad(8) flushes the last
89 | * unfinished byte. The underlying OutputStream will be flushed.
90 | *
91 | * @param factor
92 | * The size of the block to pad. This will typically be 8, 16,
93 | * 32, 64, 128, 256, etc.
94 | * @return this
95 | * @throws IOException
96 | */
97 | public void pad(int factor) throws IOException {
98 | int padding = factor - (int) (nrBits % factor);
99 | int excess = padding & 7;
100 | if (excess > 0) {
101 | this.write(0, excess);
102 | padding -= excess;
103 | }
104 | while (padding > 0) {
105 | this.write(0, 8);
106 | padding -= 8;
107 | }
108 | this.out.flush();
109 | }
110 |
111 | /**
112 | * Write some bits. Up to 32 bits can be written at a time.
113 | *
114 | * @param bits
115 | * The bits to be written.
116 | * @param width
117 | * The number of bits to write. (0..32)
118 | * @throws IOException
119 | */
120 | public void write(int bits, int width) throws IOException {
121 | if (bits == 0 && width == 0) {
122 | return;
123 | }
124 | if (width <= 0 || width > 32) {
125 | throw new IOException("Bad write width.");
126 | }
127 | while (width > 0) {
128 | int actual = width;
129 | if (actual > this.vacant) {
130 | actual = this.vacant;
131 | }
132 | this.unwritten |= ((bits >>> (width - actual)) &
133 | BitInputStream.mask[actual]) << (this.vacant - actual);
134 | width -= actual;
135 | nrBits += actual;
136 | this.vacant -= actual;
137 | if (this.vacant == 0) {
138 | this.out.write(this.unwritten);
139 | this.unwritten = 0;
140 | this.vacant = 8;
141 | }
142 | }
143 | }
144 |
145 | /**
146 | * Write a 0 bit.
147 | *
148 | * @throws IOException
149 | */
150 | public void zero() throws IOException {
151 | write(0, 1);
152 |
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/crypto/set2a/RSAUtils.java:
--------------------------------------------------------------------------------
1 | package org.telehash.crypto.set2a;
2 |
3 | import org.spongycastle.crypto.util.PublicKeyFactory;
4 | import org.spongycastle.util.io.pem.PemObject;
5 | import org.spongycastle.util.io.pem.PemReader;
6 | import org.telehash.core.TelehashException;
7 | import org.telehash.core.Util;
8 | import org.telehash.crypto.HashNamePrivateKey;
9 | import org.telehash.crypto.HashNamePublicKey;
10 |
11 | import java.io.BufferedReader;
12 | import java.io.FileReader;
13 | import java.io.FileWriter;
14 | import java.io.IOException;
15 | import java.io.StringReader;
16 |
17 | public class RSAUtils {
18 |
19 | private static final String RSA_PRIVATE_KEY_PEM_TYPE = "RSA PRIVATE KEY";
20 | private static final String RSA_PUBLIC_KEY_PEM_TYPE = "PUBLIC KEY";
21 |
22 | /**
23 | * Parse a PEM-formatted RSA public key
24 | *
25 | * @param pem The PEM string.
26 | * @return The key.
27 | * @throws TelehashException If a problem occurs while reading the file.
28 | */
29 | public static HashNamePublicKey parseRSAPublicKeyFromStorage(
30 | String pem
31 | ) throws TelehashException {
32 | try {
33 | PemReader pemReader = new PemReader(new StringReader(pem));
34 | PemObject pemObject = pemReader.readPemObject();
35 | pemReader.close();
36 | if (pemObject == null) {
37 | throw new TelehashException("cannot parse RSA public key PEM file.");
38 | }
39 | if (! pemObject.getType().equals(RSA_PUBLIC_KEY_PEM_TYPE)) {
40 | throw new TelehashException(
41 | "RSA public key PEM file of incorrect type \"" +
42 | pemObject.getType() + "\""
43 | );
44 | }
45 | return new HashNamePublicKeyImpl(PublicKeyFactory.createKey(pemObject.getContent()));
46 | } catch (IOException e) {
47 | throw new TelehashException(e);
48 | }
49 | }
50 |
51 | /**
52 | * Read an RSA public key from a file.
53 | *
54 | * @param filename The filename of the file containing the PEM-formatted key.
55 | * @return The key.
56 | * @throws TelehashException If a problem occurs while reading the file.
57 | */
58 | public static HashNamePublicKey readRSAPublicKeyFromFile(
59 | String filename
60 | ) throws TelehashException {
61 | try {
62 | BufferedReader reader = new BufferedReader(new FileReader(filename));
63 | String line = reader.readLine();
64 | reader.close();
65 | return new HashNamePublicKeyImpl(Util.base64Decode(line));
66 | } catch (IOException e) {
67 | throw new TelehashException(e);
68 | }
69 | }
70 |
71 | /**
72 | * Read an RSA private key from a file.
73 | *
74 | * @param filename The filename of the file containing the PEM-formatted key.
75 | * @return The key.
76 | * @throws TelehashException If a problem occurs while reading the file.
77 | */
78 | public static HashNamePrivateKey readRSAPrivateKeyFromFile(
79 | String filename
80 | ) throws TelehashException {
81 | try {
82 | BufferedReader reader = new BufferedReader(new FileReader(filename));
83 | String line = reader.readLine();
84 | reader.close();
85 | return new HashNamePrivateKeyImpl(Util.base64Decode(line));
86 | } catch (IOException e) {
87 | throw new TelehashException(e);
88 | }
89 | }
90 |
91 | /**
92 | * Write an RSA public key to a file.
93 | *
94 | * @param filename The filename of the file to write.
95 | * @param key The key to write.
96 | * @throws IOException If a problem occurs while reading the file.
97 | */
98 | public static void writeRSAPublicKeyToFile(
99 | String filename,
100 | HashNamePublicKey key
101 | ) throws TelehashException {
102 | try {
103 | FileWriter fileWriter = new FileWriter(filename);
104 | fileWriter.write(Util.base64Encode(key.getEncoded()));
105 | fileWriter.write("\n");
106 | fileWriter.close();
107 | } catch (IOException e) {
108 | throw new TelehashException(e);
109 | }
110 | }
111 |
112 | /**
113 | * Write an RSA private key to a file.
114 | *
115 | * @param filename The filename of the file to write.
116 | * @param key The key to write.
117 | * @throws IOException If a problem occurs while reading the file.
118 | */
119 | public static void writeRSAPrivateKeyToFile(
120 | String filename,
121 | HashNamePrivateKey key
122 | ) throws TelehashException {
123 | try {
124 | FileWriter fileWriter = new FileWriter(filename);
125 | fileWriter.write(Util.base64Encode(key.getEncoded()));
126 | fileWriter.write("\n");
127 | fileWriter.close();
128 | } catch (IOException e) {
129 | throw new TelehashException(e);
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/PeerNode.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | import org.telehash.crypto.HashNamePublicKey;
4 | import org.telehash.network.InetPath;
5 | import org.telehash.network.Path;
6 |
7 | import java.util.Collection;
8 | import java.util.NavigableSet;
9 | import java.util.Set;
10 | import java.util.SortedSet;
11 | import java.util.TreeSet;
12 |
13 | public class PeerNode extends Node {
14 |
15 | private FingerprintSet mFingerprints; // only populate in full
16 | private final HashNamePublicKey mActivePublicKey; // optional
17 | private final CipherSetIdentifier mActiveCipherSetIdentifier;
18 | private SortedSet mPaths = new TreeSet();
19 |
20 | protected static class Active {
21 | public CipherSetIdentifier cipherSetIdentifier;
22 | public HashNamePublicKey publicKey;
23 | };
24 |
25 | public PeerNode(HashName hashName, CipherSetIdentifier csid,
26 | HashNamePublicKey publicKey, Collection paths) {
27 | super(hashName);
28 | mFingerprints = null;
29 | mActiveCipherSetIdentifier = csid;
30 | mActivePublicKey = publicKey;
31 | mPaths.addAll(paths);
32 | }
33 |
34 | protected PeerNode(
35 | HashName hashName,
36 | FingerprintSet fingerprints,
37 | Active active,
38 | Collection paths
39 | ) {
40 | super(hashName);
41 | mFingerprints = fingerprints;
42 | mActiveCipherSetIdentifier = active.cipherSetIdentifier;
43 | mActivePublicKey = active.publicKey;
44 | if (paths != null) {
45 | mPaths.addAll(paths);
46 | }
47 | }
48 |
49 | public PeerNode(HashName hashName, FingerprintSet fingerprints, Collection paths)
50 | throws TelehashException {
51 | super(hashName);
52 | mFingerprints = fingerprints;
53 | mActivePublicKey = null;
54 | mActiveCipherSetIdentifier = null;
55 | mPaths.addAll(paths);
56 | }
57 |
58 | public PeerNode(HashName hashName, Collection paths) throws TelehashException {
59 | super(hashName);
60 | mFingerprints = null;
61 | mActivePublicKey = null;
62 | mActiveCipherSetIdentifier = null;
63 | mPaths.addAll(paths);
64 | }
65 |
66 | public FingerprintSet getFingerprints() {
67 | return mFingerprints;
68 | }
69 |
70 | public HashNamePublicKey getActivePublicKey() {
71 | return mActivePublicKey;
72 | }
73 |
74 | public CipherSetIdentifier getActiveCipherSetIdentifier() {
75 | return mActiveCipherSetIdentifier;
76 | }
77 |
78 | public Set getCipherSetIds() {
79 | if (mFingerprints != null) {
80 | return mFingerprints.keySet();
81 | } else {
82 | return null;
83 | }
84 | }
85 |
86 | @Deprecated
87 | public Path getPath() {
88 | // if there is only one path, return it.
89 | if (mPaths.size() <= 1) {
90 | return mPaths.first();
91 | }
92 | // return the first non-RFC1918 internet address, if available.
93 | for (Path path : mPaths) {
94 | if (path instanceof InetPath) {
95 | if (! ((InetPath)path).getAddress().isSiteLocalAddress()) {
96 | return path;
97 | }
98 | }
99 | }
100 | // otherwise, return the first address.
101 | return mPaths.first();
102 | }
103 |
104 | public SortedSet getPaths() {
105 | return mPaths;
106 | }
107 |
108 | public void setPaths(Collection extends Path> paths) {
109 | mPaths.clear();
110 | mPaths.addAll(paths);
111 | }
112 |
113 | public void updateFingerprints(FingerprintSet fingerprints) {
114 | if (mFingerprints == null) {
115 | mFingerprints = fingerprints;
116 | } else {
117 | if (! mFingerprints.equals(fingerprints)) {
118 | throw new IllegalStateException(
119 | "attempt to change the existing fingerprint set of a node."
120 | );
121 | }
122 | }
123 | }
124 |
125 | public static CipherSetIdentifier bestCipherSet(PeerNode a, PeerNode b) {
126 | if (a == null || b == null) {
127 | throw new IllegalArgumentException("null peernode provided");
128 | }
129 | return FingerprintSet.bestCipherSet(a.mFingerprints, b.mFingerprints);
130 | }
131 |
132 | protected static CipherSetIdentifier bestCipherSetIdentifier(
133 | Set theirSet
134 | ) {
135 | NavigableSet theirs =
136 | new TreeSet(theirSet);
137 | NavigableSet ours =
138 | Telehash.get().getCrypto().getAllCipherSetsIds();
139 | for (CipherSetIdentifier csid : ours.descendingSet()) {
140 | if (theirs.contains(csid)) {
141 | return csid;
142 | }
143 | }
144 | return null;
145 | }
146 |
147 | @Override
148 | public String toString() {
149 | String hashName = mHashName.getShortHash();
150 | return "PeerNode["+hashName+"]"+mPaths;
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/android-demo/telehash.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
119 |
--------------------------------------------------------------------------------
/src/test/java/org/telehash/test/StorageTest.java:
--------------------------------------------------------------------------------
1 | package org.telehash.test;
2 |
3 | import static org.junit.Assert.assertEquals;
4 | import static org.junit.Assert.assertNotNull;
5 |
6 | import org.junit.After;
7 | import org.junit.Before;
8 | import org.junit.Test;
9 | import org.telehash.core.SeedNode;
10 | import org.telehash.network.InetPath;
11 | import org.telehash.storage.Storage;
12 | import org.telehash.storage.impl.StorageImpl;
13 |
14 | import java.io.File;
15 | import java.io.FileOutputStream;
16 | import java.util.Set;
17 |
18 | public class StorageTest {
19 |
20 | // Use this JSON structure to test the parsing of seeds.
21 | // This seeds file was taken from
22 | // https://github.com/quartzjer/telehash-seeds/blob/master/seeds.json
23 | // and Java-stringified using the "stringinator.pl" tool found
24 | // elsewhere in this repository. The network paths have been
25 | // changed so that port numbers equal 9000 + the last octet of the
26 | // IPv4 address (for validation purposes).
27 | private static final String SEEDS_JSON =
28 | "{\n"+
29 | " \"89a4cbc6c27eb913c1bcaf06bac2d8b872c7cbef626b35b6d7eaf993590d37de\": {\n"+
30 | " \"admin\":\"http://github.com/quartzjer\",\n"+
31 | " \"paths\": [{\n"+
32 | " \"type\": \"ipv4\",\n"+
33 | " \"ip\": \"208.68.164.253\",\n"+
34 | " \"port\": 9253\n"+
35 | " }, {\n"+
36 | " \"type\": \"ipv6\",\n"+
37 | " \"ip\": \"2605:da00:5222:5269:230:48ff:fe35:6572\",\n"+
38 | " \"port\": 9253\n"+
39 | " }, {\n"+
40 | " \"type\": \"http\",\n"+
41 | " \"http\": \"http://208.68.164.253:42424\"\n"+
42 | " }],\n"+
43 | " \"parts\": {\n"+
44 | " \"2a\": \"beb07e8864786e1d3d70b0f537e96fb719ca2bbb4a2a3791ca45e215e2f67c9a\",\n"+
45 | " \"1a\": \"6c0da502755941a463454e9d478b16bbe4738e67\"\n"+
46 | " },\n"+
47 | " \"keys\": {\n"+
48 | " \"2a\": \"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvJlhpi2pZZRrnf+bmnnRRAQHfzMz"+
49 | "DwOV+s+JzamyL0X9wwJK8m2DHCFcpJQSLFIzv3v+e102+pZlIWAU6vvO5s6J60C+9UwQoKj9L3cxUL/X"+
50 | "mEBjAnbwfs+61HGSjf8yS8Uon/0oDXxssJyJjnrzAJT7K5G+Nqf5N5IJiEfhYkfa9wkhWR4fU1ZiC3PZ"+
51 | "ZoMrGurGxWAwIs4No2LlBkZXUtAC31jISWODBAahGcaRjPgHFVaDxQ+gEQFse0Aa0mWA7KCRiSbBC89B"+
52 | "rx837AREWFa7t14UAi01BLcPSkbAIbIv1SmM+E3k7HdN6rXTjz2h7Na5DOCS4z1LgujXW460WQIDAQAB"+
53 | "\",\n"+
54 | " \"1a\": \"hhzKwBDYADu6gQdDrP2AgQJmAE5iIbEqpPmLLZI9w72rLTCCqob9sw==\"\n"+
55 | " },\n"+
56 | " \"bridge\": true\n"+
57 | " },\n"+
58 | " \"f50f423ce7f94fe98cdd09268c7e57001aed300b23020840a84a881c76739471\": {\n"+
59 | " \"admin\":\"http://github.com/quartzjer\",\n"+
60 | " \"paths\": [{\n"+
61 | " \"type\": \"ipv4\",\n"+
62 | " \"ip\": \"208.126.199.195\",\n"+
63 | " \"port\": 9195\n"+
64 | " }, {\n"+
65 | " \"type\": \"ipv6\",\n"+
66 | " \"ip\": \"2001:470:c0a6:3::10\",\n"+
67 | " \"port\": 9195\n"+
68 | " }, {\n"+
69 | " \"type\": \"http\",\n"+
70 | " \"http\": \"http://208.126.199.195:42424\"\n"+
71 | " }],\n"+
72 | " \"parts\": {\n"+
73 | " \"2a\": \"8a5235d7cebb82d48a945e7c4b301efed40503d50ea1063464fe839b12278d93\",\n"+
74 | " \"1a\": \"b3c9341ff5d11670c1e1c918ad51631b1251448a\"\n"+
75 | " },\n"+
76 | " \"keys\": {\n"+
77 | " \"2a\": \"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5mWOu3o0chHcpcxPYX43fD6DTWGk"+
78 | "Cj09QaWerHbTX1Gua5eW8VdPOM/Ki21WEY2xcBa55/s37hIRP1XZveFiWgIXft9g/L+1AsF56cO0ZGnH"+
79 | "hrp5Wabrt+L5mVuWg2VcSAUQ/gdoSLmDRTdOc0ruzroIN4a4Wnfk6rwvFYq/LfTj2w5cBD3ziVts4XSi"+
80 | "cX9fnmWElrTKfGLWyC6W5ICbLZ0BmTt9CZLbdNmotuYqQkPXPJ0wccsWAuI8yjbmU2my+F+vakWbGFvM"+
81 | "SCBlLlQLXMTnLbLtgnwgeujTHiJaB0Iycw5Q9FS0RiQ0QeFqUvmMX9BezKfayq2hHjcob58WbwIDAQAB"+
82 | "\",\n"+
83 | " \"1a\": \"idT0VmmEmSdDF1eMrajIVHP0XZ/8Udgeas1Zxy0va5tD/KP393Ri3w==\"\n"+
84 | " },\n"+
85 | " \"bridge\": true\n"+
86 | " }\n"+
87 | "}\n";
88 | private static final int NUM_SEEDS = 2;
89 |
90 | private Storage mStorage;
91 |
92 | @Before
93 | public void setUp() throws Exception {
94 | mStorage = new StorageImpl();
95 | }
96 |
97 | @After
98 | public void tearDown() throws Exception {
99 | }
100 |
101 | @Test
102 | public void testParseSeeds() throws Exception {
103 | // create JSON seeds file
104 | File temp = File.createTempFile("seeds", "json");
105 | FileOutputStream fos = new FileOutputStream(temp);
106 | fos.write(SEEDS_JSON.getBytes("UTF-8"));
107 | fos.close();
108 |
109 | Set seeds = mStorage.readSeeds(temp.getAbsolutePath());
110 | assertNotNull(seeds);
111 | assertEquals(seeds.size(), NUM_SEEDS);
112 | for (SeedNode seed : seeds) {
113 | int port = ((InetPath)seed.getPath()).getPort();
114 | int lowerOctet = ((InetPath)seed.getPath()).getAddress().getAddress()[3]&0xFF;
115 | assertEquals(lowerOctet, port-9000);
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/LinePacket.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | import org.telehash.crypto.Crypto;
4 | import org.telehash.network.Path;
5 |
6 | /**
7 | * A Telehash "line" packet is used to exchange data between two Telehash nodes
8 | * that have established a shared secret via open packets.
9 | *
10 | *
11 | * A line packet consists of the following components, in roughly the order in
12 | * which they should be unpacked:
13 | *
14 | *
15 | *
16 | *
The line identifier
17 | *
A random initialization vector (IV) used for the AES encryption of the inner packet.
18 | *
An embedded "inner packet" containing arbitrary data. This inner packet
19 | * is AES-CTR encrypted using a key derived from the SHA-256 hash of shared secret,
20 | * the outgoing line id, and the incoming line id.
21 | *
22 | */
23 | public class LinePacket extends Packet {
24 |
25 | private static final String LINE_IDENTIFIER_KEY = "line";
26 | private static final String IV_KEY = "iv";
27 | private static final int IV_SIZE = 16;
28 |
29 | private Line mLine;
30 | private ChannelPacket mChannelPacket;
31 |
32 | public LinePacket(Line line) {
33 | mLine = line;
34 | mDestinationNode = (PeerNode)line.getRemoteNode();
35 | }
36 |
37 | public LinePacket(Line line, ChannelPacket channelPacket) {
38 | mLine = line;
39 | mChannelPacket = channelPacket;
40 | // TODO: this is wrong if we are parsing an incoming packet --
41 | // line.getRemoteNode() should be the *source* not the *destination*!
42 | mDestinationNode = line.getRemotePeerNode();
43 | }
44 |
45 | // accessor methods
46 |
47 | public void setLine(Line line) {
48 | mLine = line;
49 | }
50 | public Line getLine() {
51 | return mLine;
52 | }
53 |
54 | public void setChannelPacket(ChannelPacket channelPacket) {
55 | mChannelPacket = channelPacket;
56 | }
57 | public ChannelPacket getChannelPacket() {
58 | return mChannelPacket;
59 | }
60 |
61 | /**
62 | * Render the open packet into its final form.
63 | *
64 | * @return The rendered open packet as a byte array.
65 | */
66 | @Override
67 | public byte[] render() throws TelehashException {
68 | Crypto crypto = Telehash.get().getCrypto();
69 |
70 | // serialize the channel packet
71 | if (mChannelPacket == null) {
72 | mChannelPacket = new ChannelPacket();
73 | }
74 | byte[] channelPlaintext = mChannelPacket.render();
75 |
76 | // regard the line id
77 | if (mLine == null) {
78 | throw new TelehashException("null line");
79 | }
80 | if (mLine.getOutgoingLineIdentifier() == null) {
81 | throw new TelehashException("null line id");
82 | }
83 | byte[] lineBytes = mLine.getOutgoingLineIdentifier().getBytes();
84 | if (lineBytes.length != LineIdentifier.SIZE) {
85 | throw new TelehashException("line id must be exactly 16 bytes");
86 | }
87 |
88 | // cipherset processing of inner packet
89 | byte[] inner = mLine.getCipherSet().renderLineInnerPacket(mLine, channelPlaintext);
90 |
91 | byte[] headerLengthPrefix = new byte[] {0,0};
92 | byte[] packet = Util.concatenateByteArrays(headerLengthPrefix, lineBytes, inner);
93 | return packet;
94 | }
95 |
96 | public static LinePacket parse(
97 | Telehash telehash,
98 | SplitPacket splitPacket,
99 | Path path
100 | ) throws TelehashException {
101 | Crypto crypto = telehash.getCrypto();
102 |
103 | if (splitPacket.headerLength != 0 ||
104 | splitPacket.json != null ||
105 | splitPacket.body == null ||
106 | splitPacket.body.length < LineIdentifier.SIZE
107 | ) {
108 | throw new TelehashException("invalid line packet format");
109 | }
110 |
111 | // extract the line id
112 | byte[] lineIdBytes = new byte[LineIdentifier.SIZE];
113 | System.arraycopy(splitPacket.body, 0, lineIdBytes, 0, LineIdentifier.SIZE);
114 | LineIdentifier lineIdentifier = new LineIdentifier(lineIdBytes);
115 |
116 | // confirm that the line id is valid
117 | Line line = telehash.getSwitch().getLineManager().getLine(lineIdentifier);
118 | if (line == null) {
119 | throw new TelehashException("unknown line id: "+lineIdentifier);
120 | }
121 |
122 | // extract the inner packet
123 | int innerPacketSize = splitPacket.body.length - LineIdentifier.SIZE;
124 | byte[] innerPacket = new byte[innerPacketSize];
125 | System.arraycopy(splitPacket.body, LineIdentifier.SIZE, innerPacket, 0, innerPacketSize);
126 |
127 | // cipherset processing of inner packet
128 | byte[] channelPlaintext = line.getCipherSet().parseLineInnerPacket(line, innerPacket);
129 |
130 | // parse the embedded channel packet
131 | ChannelPacket channelPacket = ChannelPacket.parse(telehash, channelPlaintext, path);
132 | channelPacket.setSourceNode(line.getRemotePeerNode());
133 |
134 | return new LinePacket(line, channelPacket);
135 | }
136 |
137 | @Override
138 | public String toString() {
139 | String s = mLine.toString();
140 | if (mSourceNode != null) {
141 | s += " <"+mSourceNode;
142 | }
143 | if (mDestinationNode != null) {
144 | s += " >"+mDestinationNode;
145 | }
146 | return s;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/core/Scheduler.java:
--------------------------------------------------------------------------------
1 | package org.telehash.core;
2 |
3 | import java.util.HashSet;
4 | import java.util.Iterator;
5 | import java.util.Set;
6 | import java.util.SortedSet;
7 | import java.util.TreeSet;
8 |
9 | public class Scheduler {
10 |
11 | private static final int NANOSECONDS_IN_MILLISECOND = 1000000;
12 |
13 | public static class Task implements Comparable {
14 | private Runnable mRunnable;
15 | private long mTime;
16 |
17 | public Task(Runnable runnable, long time) {
18 | mRunnable = runnable;
19 | mTime = time;
20 | }
21 |
22 | @Override
23 | public String toString() {
24 | return "Task["+mRunnable.hashCode()+"/"+mTime+"]";
25 | }
26 |
27 | @Override
28 | public int hashCode() {
29 | final int prime = 31;
30 | int result = 1;
31 | result = prime * result + ((mRunnable == null) ? 0 : mRunnable.hashCode());
32 | result = prime * result + (int) (mTime ^ (mTime >>> 32));
33 | return result;
34 | }
35 | @Override
36 | public boolean equals(Object obj) {
37 | if (this == obj)
38 | return true;
39 | if (obj == null)
40 | return false;
41 | if (getClass() != obj.getClass())
42 | return false;
43 | Task other = (Task) obj;
44 | if (mRunnable == null) {
45 | if (other.mRunnable != null)
46 | return false;
47 | } else if (!mRunnable.equals(other.mRunnable))
48 | return false;
49 | if (mTime != other.mTime)
50 | return false;
51 | return true;
52 | }
53 |
54 | @Override
55 | public int compareTo(Task other) {
56 | if (mTime < other.mTime) {
57 | return -1;
58 | } else if (mTime > other.mTime) {
59 | return +1;
60 | } else if (mRunnable.hashCode() < other.mRunnable.hashCode()) {
61 | return -1;
62 | } else if (mRunnable.hashCode() > other.mRunnable.hashCode()) {
63 | return +1;
64 | } else {
65 | return 0;
66 | }
67 | }
68 | }
69 | private SortedSet mTasks = new TreeSet();
70 |
71 | /**
72 | * Schedule a new task to be executed after a delay.
73 | * @param runnable
74 | * @param delay The delay in milliseconds.
75 | */
76 | public Task addTask(Runnable runnable, long delay) {
77 | Task task = new Task(runnable, System.nanoTime() + delay*NANOSECONDS_IN_MILLISECOND);
78 | mTasks.add(task);
79 | return task;
80 | }
81 |
82 | /**
83 | * Remove the specified task from the scheduler.
84 | *
85 | * @param task
86 | */
87 | public void removeTask(Task task) {
88 | mTasks.remove(task);
89 | }
90 |
91 | /**
92 | * Updated an existing task to use a new delay and/or runnable.
93 | *
94 | * @param runnable
95 | * The runnable to run at the specified time, or null if the
96 | * runnable should not be updated.
97 | * @param delay
98 | * The delay in milliseconds, or -1 if the delay should not be
99 | * updated.
100 | */
101 | public void updateTask(Task task, Runnable runnable, long delay) {
102 | mTasks.remove(task);
103 | if (runnable != null) {
104 | task.mRunnable = runnable;
105 | }
106 | if (delay != -1) {
107 | task.mTime = System.nanoTime() + delay*NANOSECONDS_IN_MILLISECOND;
108 | }
109 | mTasks.add(task);
110 | }
111 |
112 | /**
113 | * Run all tasks that are ready for execution.
114 | */
115 | public void runTasks() {
116 | long time = System.nanoTime();
117 |
118 | // iterate over a copy of the task list, since otherwise
119 | // the called runnable may add a task and cause us to
120 | // receive a ConcurrentModificationException.
121 | //
122 | // TODO: adding all the tasks into a separate sorted tree is a lot
123 | // of work to do for every iteration of the switch's select loop.
124 | // find a better way.
125 | Set tasks = new TreeSet(mTasks);
126 |
127 | Iterator iterator = tasks.iterator();
128 | Set removalSet = new HashSet();
129 | while (iterator.hasNext()) {
130 | Task task = iterator.next();
131 | if (task.mTime > time) {
132 | break;
133 | }
134 | task.mRunnable.run();
135 | removalSet.add(task);
136 | }
137 | mTasks.removeAll(removalSet);
138 | }
139 |
140 | /**
141 | * Return the number of milliseconds to the next scheduled task,
142 | * 0 if no upcoming tasks are scheduled, or -1 if tasks are ready
143 | * for immediate execution.
144 | *
145 | * @return
146 | */
147 | public long getNextTaskTime() {
148 | if (mTasks.isEmpty()) {
149 | return 0;
150 | }
151 | long nextTaskTime = (mTasks.first().mTime - System.nanoTime())/NANOSECONDS_IN_MILLISECOND;
152 | if (nextTaskTime <= 0) {
153 | return -1;
154 | } else {
155 | return nextTaskTime;
156 | }
157 | }
158 |
159 | public void dump() {
160 | Log.i("SCHEDULER TASKS:");
161 | for (Task task : mTasks) {
162 | Log.i(" "+task);
163 | }
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/main/java/org/telehash/dht/NodeSeekRequest.java:
--------------------------------------------------------------------------------
1 | package org.telehash.dht;
2 |
3 | import org.telehash.json.JSONArray;
4 | import org.telehash.json.JSONException;
5 | import org.telehash.core.Channel;
6 | import org.telehash.core.ChannelHandler;
7 | import org.telehash.core.ChannelPacket;
8 | import org.telehash.core.HashName;
9 | import org.telehash.core.Log;
10 | import org.telehash.core.Node;
11 | import org.telehash.core.PeerNode;
12 | import org.telehash.core.SeeNode;
13 | import org.telehash.core.Telehash;
14 | import org.telehash.core.TelehashException;
15 | import org.telehash.core.Util;
16 |
17 | import java.util.HashMap;
18 | import java.util.HashSet;
19 | import java.util.Map;
20 | import java.util.Set;
21 |
22 | /**
23 | * Handle a single seek/see transaction.
24 | */
25 | public class NodeSeekRequest {
26 |
27 | private static final String SEEK_TYPE = "seek";
28 | private static final String SEEK_KEY = "seek";
29 | private static final String SEE_KEY = "see";
30 |
31 | public static interface Handler {
32 | void handleError(NodeSeekRequest seek, Throwable e);
33 | void handleCompletion(NodeSeekRequest seek);
34 | }
35 |
36 | private Telehash mTelehash;
37 | private Node mQueryNode;
38 | private HashName mTargetHashName;
39 | private Handler mHandler;
40 | private Set mResultNodes;
41 |
42 | public NodeSeekRequest(
43 | Telehash telehash,
44 | Node queryNode,
45 | HashName targetHashName,
46 | Handler handler
47 | ) {
48 | mTelehash = telehash;
49 | mQueryNode = queryNode;
50 | mTargetHashName = targetHashName;
51 | mHandler = handler;
52 | Log.i("NodeSeekRequest="+this+" mQueryNode="+mQueryNode+" mTargetHashName="+mTargetHashName);
53 | }
54 |
55 | public Set getResultNodes() {
56 | return mResultNodes;
57 | }
58 |
59 | public void start() {
60 | Log.i("open seek channel to node: "+mQueryNode);
61 | mTelehash.getSwitch().openChannel(mQueryNode, SEEK_TYPE, new ChannelHandler() {
62 | @Override
63 | public void handleError(Channel channel, Throwable error) {
64 | Log.i("seek channel error: "+error.getMessage());
65 | fail(error);
66 | }
67 | @Override
68 | public void handleIncoming(Channel channel, ChannelPacket channelPacket) {
69 | Log.i("seek channel incoming");
70 | parseResult(channelPacket);
71 | }
72 | @Override
73 | public void handleOpen(Channel channel) {
74 | Log.i("seek channel open");
75 | Map fields = new HashMap();
76 |
77 | // To protect the user's privacy, only provide enough of the target hashname
78 | // to get useful results -- the distance to the query node plus one bytes.
79 | HashName localHashName = mTelehash.getLocalNode().getHashName();
80 | byte[] target;
81 | if (! mTargetHashName.equals(localHashName)) {
82 | int prefixLength = mQueryNode.getHashName().distanceMagnitude(localHashName)+1;
83 | if (prefixLength > HashName.SIZE) {
84 | prefixLength = HashName.SIZE;
85 | }
86 | target = new byte[prefixLength];
87 | System.arraycopy(mTargetHashName.getBytes(), 0, target, 0, prefixLength);
88 | } else {
89 | target = mTargetHashName.getBytes();
90 | }
91 |
92 | fields.put(SEEK_KEY, Util.bytesToHex(target));
93 | try {
94 | channel.send(null, fields, false);
95 | } catch (TelehashException e) {
96 | fail(e);
97 | return;
98 | }
99 | }
100 | });
101 | }
102 |
103 | private void parseResult(ChannelPacket channelPacket) {
104 | Object seeObject = channelPacket.get(SEE_KEY);
105 | if (! (seeObject instanceof JSONArray)) {
106 | fail(new TelehashException("'see' object not an array"));
107 | return;
108 | }
109 | JSONArray seeNodes = (JSONArray)seeObject;
110 |
111 | mResultNodes = new HashSet();
112 | for (int i=0; i";
148 | }
149 | }
150 |
--------------------------------------------------------------------------------