├── doc ├── lineopen.pdf ├── Makefile └── lineopen.tex ├── .gitignore ├── android-demo ├── telehash.png ├── libs │ └── android-support-v4.jar ├── res │ ├── drawable-hdpi │ │ └── telehash.png │ ├── drawable-mdpi │ │ └── telehash.png │ ├── drawable-xhdpi │ │ └── telehash.png │ ├── drawable-xxhdpi │ │ └── telehash.png │ ├── values-sw600dp │ │ └── dimens.xml │ ├── values │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── layout │ │ └── activity_main.xml │ ├── menu │ │ └── main.xml │ ├── values-sw720dp-land │ │ └── dimens.xml │ ├── values-v11 │ │ └── styles.xml │ └── values-v14 │ │ └── styles.xml ├── src │ └── org │ │ └── telehash │ │ └── androiddemo │ │ ├── LineFragment.java │ │ ├── LogView.java │ │ ├── LogFragment.java │ │ ├── AndroidLogger.java │ │ └── MainActivity.java ├── project.properties ├── proguard-project.txt ├── AndroidManifest.xml ├── build.gradle └── telehash.svg ├── src ├── main │ └── java │ │ └── org │ │ └── telehash │ │ ├── network │ │ ├── Message.java │ │ ├── MessageHandler.java │ │ ├── DatagramHandler.java │ │ ├── Reactor.java │ │ ├── Datagram.java │ │ ├── Network.java │ │ ├── Path.java │ │ ├── impl │ │ │ └── NetworkImpl.java │ │ └── InetPath.java │ │ ├── json │ │ ├── zip │ │ │ ├── README │ │ │ ├── None.java │ │ │ ├── BitReader.java │ │ │ ├── BitWriter.java │ │ │ ├── PostMortem.java │ │ │ ├── Keep.java │ │ │ └── BitOutputStream.java │ │ ├── README-telehash.txt │ │ ├── JSONString.java │ │ ├── JSONException.java │ │ ├── README │ │ ├── HTTPTokener.java │ │ ├── Property.java │ │ ├── JSONStringer.java │ │ └── CookieList.java │ │ ├── core │ │ ├── LogListener.java │ │ ├── OnTimeoutListener.java │ │ ├── ChannelHandler.java │ │ ├── PlaceholderNode.java │ │ ├── CompletionHandler.java │ │ ├── SeedNode.java │ │ ├── TelehashException.java │ │ ├── CounterTrigger.java │ │ ├── LogEntry.java │ │ ├── LineIdentifier.java │ │ ├── ChannelIdentifier.java │ │ ├── CipherSetIdentifier.java │ │ ├── Timeout.java │ │ ├── Telehash.java │ │ ├── FullNode.java │ │ ├── Flag.java │ │ ├── LocalNode.java │ │ ├── SeeNode.java │ │ ├── Node.java │ │ ├── Log.java │ │ ├── Channel.java │ │ ├── HashName.java │ │ ├── StandardLogger.java │ │ ├── PeerNode.java │ │ ├── LinePacket.java │ │ └── Scheduler.java │ │ ├── crypto │ │ ├── LineKeyPair.java │ │ ├── HashNameKeyPair.java │ │ ├── LinePrivateKey.java │ │ ├── LinePublicKey.java │ │ ├── HashNamePrivateKey.java │ │ ├── HashNamePublicKey.java │ │ ├── set2a │ │ │ ├── HashNameKeyPairImpl.java │ │ │ ├── LinePrivateKeyImpl.java │ │ │ ├── LineKeyPairImpl.java │ │ │ ├── LinePublicKeyImpl.java │ │ │ ├── HashNamePrivateKeyImpl.java │ │ │ └── RSAUtils.java │ │ └── Crypto.java │ │ ├── dht │ │ ├── NodeDistanceComparator.java │ │ └── NodeSeekRequest.java │ │ └── storage │ │ └── Storage.java ├── test │ └── java │ │ └── org │ │ └── telehash │ │ └── test │ │ ├── network │ │ ├── NetworkSimulator.java │ │ ├── Router.java │ │ ├── FakeNetworkImpl.java │ │ └── FakeReactorImpl.java │ │ ├── mesh │ │ ├── Mesh.java │ │ ├── ThreeLevelMeshTest.java │ │ ├── LargeScaleMeshTest.java │ │ └── StarMeshTest.java │ │ ├── util │ │ └── EchoChannelHandler.java │ │ ├── NetworkTest.java │ │ ├── UtilTest.java │ │ ├── DHTTest.java │ │ └── StorageTest.java └── sample │ └── java │ └── org │ └── telehash │ └── sample │ ├── BasicSeed.java │ └── BasicNode.java ├── settings.gradle └── misc └── stringinator.pl /doc/lineopen.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telehash/telehash-java/HEAD/doc/lineopen.pdf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | *.swp 3 | build/ 4 | .classpath 5 | .project 6 | .settings/ 7 | nodes/ 8 | bin/ 9 | -------------------------------------------------------------------------------- /android-demo/telehash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telehash/telehash-java/HEAD/android-demo/telehash.png -------------------------------------------------------------------------------- /src/main/java/org/telehash/network/Message.java: -------------------------------------------------------------------------------- 1 | package org.telehash.network; 2 | 3 | public class Message { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /android-demo/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telehash/telehash-java/HEAD/android-demo/libs/android-support-v4.jar -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: lineopen.pdf 3 | 4 | %.pdf : %.tex 5 | pdflatex lineopen.tex 6 | 7 | clean: 8 | rm -f *.aux *.log 9 | 10 | -------------------------------------------------------------------------------- /android-demo/res/drawable-hdpi/telehash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telehash/telehash-java/HEAD/android-demo/res/drawable-hdpi/telehash.png -------------------------------------------------------------------------------- /android-demo/res/drawable-mdpi/telehash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telehash/telehash-java/HEAD/android-demo/res/drawable-mdpi/telehash.png -------------------------------------------------------------------------------- /android-demo/res/drawable-xhdpi/telehash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telehash/telehash-java/HEAD/android-demo/res/drawable-xhdpi/telehash.png -------------------------------------------------------------------------------- /android-demo/res/drawable-xxhdpi/telehash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telehash/telehash-java/HEAD/android-demo/res/drawable-xxhdpi/telehash.png -------------------------------------------------------------------------------- /src/main/java/org/telehash/json/zip/README: -------------------------------------------------------------------------------- 1 | FOR EVALUATION PURPOSES ONLY. THIS PACKAGE HAS NOT BEEN TESTED ADEQUATELY FOR 2 | PRODUCTION USE. 3 | -------------------------------------------------------------------------------- /src/main/java/org/telehash/core/LogListener.java: -------------------------------------------------------------------------------- 1 | package org.telehash.core; 2 | 3 | public interface LogListener { 4 | void onLogEvent(LogEntry entry); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/org/telehash/core/OnTimeoutListener.java: -------------------------------------------------------------------------------- 1 | package org.telehash.core; 2 | 3 | public interface OnTimeoutListener { 4 | void handleTimeout(); 5 | } 6 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | 2 | rootProject.name = 'telehash-java' 3 | 4 | // To enable building the Android demo app, uncomment the following line: 5 | //include 'android-demo' 6 | 7 | -------------------------------------------------------------------------------- /src/main/java/org/telehash/network/MessageHandler.java: -------------------------------------------------------------------------------- 1 | package org.telehash.network; 2 | 3 | public interface MessageHandler { 4 | void handleMessage(Message message); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/org/telehash/network/DatagramHandler.java: -------------------------------------------------------------------------------- 1 | package org.telehash.network; 2 | 3 | public interface DatagramHandler { 4 | void handleDatagram(Datagram datagram); 5 | } 6 | -------------------------------------------------------------------------------- /android-demo/src/org/telehash/androiddemo/LineFragment.java: -------------------------------------------------------------------------------- 1 | package org.telehash.androiddemo; 2 | 3 | import android.app.Fragment; 4 | 5 | public class LineFragment extends Fragment { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/telehash/crypto/LineKeyPair.java: -------------------------------------------------------------------------------- 1 | package org.telehash.crypto; 2 | 3 | public interface LineKeyPair { 4 | public LinePrivateKey getPrivateKey(); 5 | public LinePublicKey getPublicKey(); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/org/telehash/crypto/HashNameKeyPair.java: -------------------------------------------------------------------------------- 1 | package org.telehash.crypto; 2 | 3 | public interface HashNameKeyPair { 4 | public HashNamePrivateKey getPrivateKey(); 5 | public HashNamePublicKey getPublicKey(); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/org/telehash/crypto/LinePrivateKey.java: -------------------------------------------------------------------------------- 1 | package org.telehash.crypto; 2 | 3 | import org.telehash.core.CipherSetIdentifier; 4 | 5 | public interface LinePrivateKey { 6 | public CipherSetIdentifier getCipherSetIdentifier(); 7 | } 8 | -------------------------------------------------------------------------------- /android-demo/res/values-sw600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android-demo/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16dp 5 | 16dp 6 | 7 | 8 | -------------------------------------------------------------------------------- /android-demo/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Telehash 5 | Settings 6 | Hello world! 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/java/org/telehash/crypto/LinePublicKey.java: -------------------------------------------------------------------------------- 1 | package org.telehash.crypto; 2 | 3 | import org.telehash.core.CipherSetIdentifier; 4 | 5 | public interface LinePublicKey { 6 | public CipherSetIdentifier getCipherSetIdentifier(); 7 | public byte[] getEncoded(); 8 | } 9 | -------------------------------------------------------------------------------- /android-demo/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main/java/org/telehash/core/ChannelHandler.java: -------------------------------------------------------------------------------- 1 | package org.telehash.core; 2 | 3 | public interface ChannelHandler { 4 | void handleError(Channel channel, Throwable error); 5 | void handleIncoming(Channel channel, ChannelPacket channelPacket); 6 | void handleOpen(Channel channel); 7 | } 8 | -------------------------------------------------------------------------------- /android-demo/src/org/telehash/androiddemo/LogView.java: -------------------------------------------------------------------------------- 1 | package org.telehash.androiddemo; 2 | 3 | import android.content.Context; 4 | import android.widget.TextView; 5 | 6 | public class LogView extends TextView { 7 | 8 | public LogView(Context context) { 9 | super(context); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /android-demo/res/menu/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 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 | *
  1. 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.
  2. 23 | *
  3. 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.
  4. 29 | *
  5. 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.
  6. 34 | *
  7. 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.
  8. 38 | *
  9. A SeedNode is a node obtained from a seeds.json file. We have 39 | * full public key and network path information about these nodes.
  10. 40 | *
  11. 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.
  12. 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,

    43 |  * myString = new JSONStringer()
    44 |  *     .object()
    45 |  *         .key("JSON")
    46 |  *         .value("Hello, World!")
    47 |  *     .endObject()
    48 |  *     .toString();
    which produces the string
    49 |  * {"JSON":"Hello, World!"}
    50 | *

    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 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 | 21 | 23 | 25 | 29 | 33 | 34 | 35 | 57 | 59 | 60 | 62 | image/svg+xml 63 | 65 | 66 | 67 | 68 | 69 | 75 | 82 | 83 | 89 | 94 | 105 | # 117 | 118 | 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 | *
    1. The line identifier
    2. 17 | *
    3. A random initialization vector (IV) used for the AES encryption of the inner packet.
    4. 18 | *
    5. 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.
    6. 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 | --------------------------------------------------------------------------------