If you are using Eclipse then these steps will setup a working version of TomP2P in Eclipse.
<buildSpec>
</buildSpec>
<natures>
</natures>
with
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
Then right click on “TomP2P” -> “Maven” -> “Update Project Configuration...” -> ok. Now you are ready to go.
TomP2P can send direct messages in an RPC-style to other peers. There are two types of send() functions defined in TomP2P: sending objects and sending raw data. While the first serializes the object to a byte array, the later one does not and should be used if there is alreay a byte array.
For the usage of ChannelBuffer, please refer to this http://docs.jboss.org/netty/3.2/api/org/jboss/netty/buffer/class-use/ChannelBuffer.html. Basically, to create a ChannelBuffer from a byte[], you can use e.g., ChannelBuffers.wrappedBuffer(byte[] array) as described http://docs.jboss.org/netty/3.2/api/org/jboss/netty/buffer/ChannelBuffers.html.
The following example shows how to send objects that can be serialized to an other peer.
Peer p1 = null;
Peer p2 = null;
try
{
p1 = new Peer(new Number160(rnd));
p1.listen(4001, 4001);
p2 = new Peer(new Number160(rnd));
p2.listen(4002, 4002);
//attach reply handler
p2.setObjectDataReply(new ObjectDataReply()
{
@Override
public Object reply(PeerAddress sender, Object request) throws Exception
{
System.out.println("request ["+request+"]");
return "world";
}
});
FutureData futureData=p1.send(p2.getPeerAddress(), "hello");
futureData.awaitUninterruptibly();
System.out.println("reply ["+futureData.getObject()+"]");
}
finally
{
p1.shutdown();
p2.shutdown();
}
The output of this example is:
request [hello]
reply [world]
TomP2P has been tested and used on Anroid in a couple of academic projects. To install Android, please read the Quick Start. For any kind of development it is useful to use logcat for the error messages.
Although, the Android API is very similar to Java 1.5, you need to do some extra work.
System.setProperty("java.net.preferIPv6Addresses", "false"); before using TomP2P<uses-permission android:name="android.permission.INTERNET"/>.The following example code has been tested on the latest Android 4.0.3 with TomP2P 4.0.7. Please note that the current TomP2P does not work with Android 2.3 or 2.2:
public class TestP2P extends Activity {
final private static Random rnd = new Random(42L);
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
System.setProperty("java.net.preferIPv6Addresses", "false");
Peer master = null;
try
{
master = new Peer(new Number160(rnd));
Bindings bindings=new Bindings();
bindings.addProtocol(Protocol.IPv4);
master.listen(4001, 4001, bindings);
Peer[] nodes = createAndAttachNodes(master, 10);
bootstrap(master, nodes);
examplePutGet(nodes);
exampleAddGet(nodes);
}
catch (Throwable e)
{
Log.wtf("tomp2p", e);
e.printStackTrace();
}
finally
{
master.shutdown();
}
}
private static void bootstrap(Peer master, Peer[] nodes)
{
List<FutureBootstrap> futures = new ArrayList<FutureBootstrap>();
for (int i = 1; i < nodes.length; i++)
{
FutureBootstrap tmp = nodes[i].bootstrap(master.getPeerAddress());
futures.add(tmp);
}
for (FutureBootstrap future : futures)
future.awaitUninterruptibly();
}
public static void examplePutGet(Peer[] nodes) throws IOException
{
Number160 nr = new Number160(rnd);
String toStore = "hallo";
Data data = new Data(toStore.getBytes());
FutureDHT futureDHT = nodes[3].put(nr, data);
futureDHT.awaitUninterruptibly();
System.out.println("stored: " + toStore + " (" + futureDHT.isSuccess() + ")");
futureDHT = nodes[7].get(nr);
futureDHT.awaitUninterruptibly();
//there are easier ways to get it, but this is for demonstration
Data d1=futureDHT.getRawData().values().iterator().next().values().iterator().next();
System.out.println("got: "
+ new String(d1.getData(),d1.getOffset(),d1.getLength()) + " (" + futureDHT.isSuccess() + ")");
}
private static void exampleAddGet(Peer[] nodes) throws IOException
{
Number160 nr = new Number160(rnd);
String toStore1 = "hallo1";
String toStore2 = "hallo2";
Data data1 = new Data(toStore1.getBytes());
Data data2 = new Data(toStore2.getBytes());
FutureDHT futureDHT = nodes[3].add(nr, data1);
futureDHT.awaitUninterruptibly();
System.out.println("added: " + toStore1 + " (" + futureDHT.isSuccess() + ")");
futureDHT = nodes[5].add(nr, data2);
futureDHT.awaitUninterruptibly();
System.out.println("added: " + toStore2 + " (" + futureDHT.isSuccess() + ")");
futureDHT = nodes[7].getAll(nr);
futureDHT.awaitUninterruptibly();
System.out.println("size " + futureDHT.getData().size());
Iterator<Data> iterator = futureDHT.getData().values().iterator();
Data d1=iterator.next();
Data d2=iterator.next();
System.out.println("got: " + new String(d1.getData(),d1.getOffset(),d1.getLength()) + " ("
+ futureDHT.isSuccess() + ")");
System.out.println("got: " + new String(d2.getData(),d2.getOffset(),d2.getLength()) + " ("
+ futureDHT.isSuccess() + ")");
}
private static Peer[] createAndAttachNodes(Peer master, int nr) throws Exception
{
Peer[] nodes = new Peer[nr];
nodes[0] = master;
for (int i = 1; i < nr; i++)
{
nodes[i] = new Peer(new Number160(rnd));
nodes[i].listen(master);
}
return nodes;
}
}
The output of this example is for examplePutGet
| stored: hallo (true) |
| got: hallo (true) |
and for exampleAddGet
| added: hallo2 (true) |
| size 2 |
| got: hallo2 (true) |
| got: hallo1 (true) |
The Eclispe workspace for TomP2P_Android is available for download: TomP2P-4.0.7_Android.zip (works with Android 4.0.3). An older version (works with Android 2.3) can be found here TomP2P-3.2.9_Android.zip.
your-path-to-android-sdk/tools/emulator -avd TomP2P-15 and your-path-to-android-sdk/tools/emulator -avd TomP2P-15-1). The port redirection is set up by connecting to the device (telnet localhost 5554 and telnet localhost 5556) and run the commands redir add udp:5001:4001 and redir add tcp:5001:4001 for the emulator on port 5554 and redir add udp:6001:4001 / redir add tcp:6001:4001 for the emulator listening on port 5556. Now with the test application TestP2P, you should be able to connect from one emulator to the other by using the IP 10.0.2.2. The screenshot shows how the input (GUI) and output (console) should look like. There is also a thread @stackoverflow.
Each DHT operation in TomP2P can be configured. All configurations extend ConfigurationBase, which lets you set the content key (default 0), the domain (default “P2P domain”), the routing configuration and if the message should be signed. Reasonable defaults are created when using the Configurations class. The routing configuration lets you configure in particular:
Furthermore, each DHT configuration has a RequestP2PConfiguration, which configures the direct calls, such as get or put. The P2P configuration lets you configure in particular:
get or put operation.ConfigurationStore lets you configure:
Some of the options of ConfigurationRemove are the same as of ConfigurationStore. The differences are:
Some of the options of ConfigurationDirect are the same as of ConfigurationStore / ConfigurationRemove. The differences are:
Since multiple results can be returned, an evaluation of the results is necessary. Such a result evaluation needs to implement EvaluatingSchemeDHT. The default scheme is a majority voting. Furthermore, the retrieval can specify a public key to get data from a specify peer only.
The tracker configuration lets you configure:
Similar to the DHT get operation, an evaluation of the results is necessary. The flag expectAttachement has to be set if the caller is interested in the attachment.
Security features are typically needed in uncontrolled environments, e.g., the Internet. The built-in security features of TomP2P (>= 3.1.10) are signature-based. Encryption-based security is not built-in, but a user can encrypt its data if necessary. Thus, the following sections explain how to use the signature-based security features. For more information about signing and encryption in P2P, please see Secure routing for structured peer-to-peer overlay networks.
Two types of signatures exist in TomP2P: message signatures and data signatures. While the message signature signs the complete message including header (IP and peerID), the data signature only signs the data object.
Any data objects can be signed. For example the following object Data data=new Data("content"); is signed with data.signAndSetPublicKey(keyPair);. Thus, the data can be signed with a different keys as the message signature. As the name signAndSetPublicKey suggests, the data object is signed and the public key is attached (self-signature). A recipient of this data object can verify if the data object is signed with public key provided and compare the received public key with already stored public keys. A public key is stored on first contact and any subsequent storage, removal, etc. can be verified if its the same peer.
Data signatures are typically used in the Internet with active replication, where peers move around data objects. A message signature would not work in such a situation, since a message signature always includes the IP and the peerID of the sender, and this can change with active replication.
Message signatures sign the complete message. Messages that can be signed are remove, add, store, move, get, and copy messages. These signatures are used to protect entries, excpet the get message. An example scenario, where such a protection is necessary, is in a tracker, where peers announces its availability of a file. For non-protected entries, anyone could overwrite those entries making an attack easy. Thus, the protection works that the entry can only be modified by the original author. Data and message signatures have been separated since signed data objects that should replace signed messages would require additional mechanisms such as message type indication, or peerID identification, etc.
With signed messages, domains and entries can be protected. Protected means, that nobody else can overwrite it.
The following modes and methods exist for domains and entries:
The following modes and methods exist for entries in unprotected domains:
The following modes exist for entries in a protected domain:
If a protected domain has no entries, i.e., they all expired, the domain gets unprotected. The following table describes those modes and methods.
| NO_*_MASTER or MASTER_*_PUBLIC_KEY | This mode, in both domain and entry specifies, if a hash of the public key of a signed message can overwrite a currently preset value. NO_MASTER cannot overwrite, while MASTER_PUBLIC_KEY can, even if the domain is protected. |
|---|---|
| DOMAIN_PROTECTION_ALL or DOMAIN_PROTECTION_NONE | This modes either sets all domains, which can be protected by any peer, or none. |
| removeDomainProtection(x) | This methods removes domains, which can be protected. The opposite addDomainProtection does not make much sense here, because every peer has to specify it and is publicly known, which makes it a perfect target for malicious peers. |
| ENTRY_PROTECTION_ALL or ENTRY_PROTECTION_NONE | This mode turns of or on protection for entries. |
These combinations are possible:
hash(public key) == domain.These modes are set by default, which should be fine for most applications.
protectionDomainMode = ProtectionMode.MASTER_PUBLIC_KEY;protectionDomainEnable = ProtectionEnable.ALL;protectionEntryMode = ProtectionMode.MASTER_PUBLIC_KEY;protectionEntryEnable = ProtectionEnable.ALLprotectionEntryInDomain = ProtectionEntryInDomain.ENTRY_LEAVE;If other values should be set, e.g., disable domain protection, use setProtection(protectionDomainEnable, protectionDomainMode, protectionEntryEnable, protectionEntryMode, protectionEntryInDomain).
A peer can protect a domain as follows:
ConfigurationStore cs1 = Configurations.defaultStoreConfiguration();
cs1.setProtectDomain(true);
cs1.setDomain(new Number160(11));
FutureDHT fdht1 = peer_xy.put(locationKey, new Data("test1"), cs1);
The domain with the number 11 is now protected and only peer_xy can use this domain for data storage for any content key if the master public key mode is not set. If it is set, the peer with the hash of the public key equals 11, can overtake this domain. Domains can also be set to never be protected with storage.removeDomainProtection(new Number160(11));. Also, a public key with this hash cannot protect this domain if the domain is set to unprotectable. The entry protection works similar:
ConfigurationStore cs1 = Configurations.defaultStoreConfiguration();
cs1.setContentKey(new Number160(12))
Data data=new Data("test1");
data.setProtectedEntry(true);
FutureDHT fdht1 = peer_xy.put(locationKey, new Data("test1"), cs1);
The entry with the key 12 can only be changed by the peer_xy if the master content key mode is not set.
A tracker has the built in modes: NO_DOMAIN_MASTER, DOMAIN_PROTECTION_NONE, ENTRY_PROTECTION_NONE, MASTER_ENTRY_PUBLIC_KEY, which means that if the peer decides to protect its entry on the tracker, the key has to be the hash of its public key and the add message has to be signed. This can be done by calling code ConfigurationTrackerStore.setSign(true);.
In TomP2P, there are indirect and direct replication mechanisms available. The direct replication can be described as peers constantly publishing their content. This means that a single peer is responsible for its content and periodically republishes the content. If this peer stops doing so, the content will eventually time out and gets removed. The information about responsibilities need to be stored along with the data in the Storage class. The indirect replication can be described as peers publishing content for others. The peer closest to a location ID is considered as the responsible peer and replicates data if necessary.
The direct replication can be turned on by setting ConfigurationStore.setRefreshSeconds(int refreshSeconds) to a value greater than 0. This tells TomP2P to refresh the entry every refreshSeconds seconds. The information what content has to be refreshed is stored in the Storage class, which can be either memory or disk-based. The add and put methods are similar with respect to the direct replication except that add calculates a hash of the data on the sender, while put does not. The time-to-live (TTL) in combination with storage tells all other peers (except the sender) to invalidate the entry after this time, if no refresh has been performed. The sender never expires “its” content unless the sender calls remove.
The TTL and refreshSeconds for the remove and copy operation has a different meaning. The refresh tells the sender to remove the entry every refreshSeconds until TTL has been reached. These values are used on the sender only.
The tracker does only direct replication and never active replication. It has a fixed timing, which can be set with TrackerStorageMemory.setTrackerTimoutMillis(TTL). for the TrackerStorage and its TTL * 0.75 for the refresh interval.
The following example executes 5 times put
ConfigurationStore cs=Configurations.defaultStoreConfiguration();
cs.setRefreshSeconds(2); // greater than 0
cs.setFutureCreate(new FutureChain<FutureDHT>()
{
@Override
public void repeated(FutureDHT future)
{
System.err.println("chain...");
}
});
FutureDHT fdht=nodes[1].put(nodes[50].getPeerID(), new Data("test"), cs);
Utils.sleep(9*1000);
//outputs five times chain...
Since version 3.2, TomP2P supports indirect replication. This feature can be enabled for put/get storage with Peer.setDefaultStorageReplication() and for tracker storage Peer.setDefaultTrackerReplication(). Indirect replication is triggered by two events:
Routing is always performed for the built-in add/put/get methods in TomP2P. If a user wants to use its own commands together with the routing, the user can either use send(key, ...), which calls send(peer, ...) in the end. With this approach the user has to define what to reply with setRawDataReply() or setObjectDataReply() depending on whether an object is used or a ChannelBuffer. The second approach is to extend StorageMemory, e.g., with MyStorageMemory, and intercept the calls for a specific domain. The following example code shows such a custom behavior.
public class MyStorageMemory extends StorageMemory
{
public Collection<Number160> put(Number160 locationKey, Number160 domainKey, PublicKey publicKey, Map<Number160, Data> contentMap, boolean putIfAbsent, boolean domainProtection)
{
if (PEER_VOTE.equals(domainKey))
{
//here we could get an int from the data and sum it up
}
else
super.put(locationKey, domainKey, peerAddress, publicKey, contentMap, putIfAbsent, domainProtection);
}
...
}
The second approach is recommended. For setting up the storage memory it is important to do this after the listen(...) method call.
Since version 3.2.7, TomP2P supports port forwarding detection and port forwarding settings via UPNP and NAT-PMP. Such a port forwarding is important if peers are behind NAT to be reachable. If a peer is behind a NAT without port forwarding, the peer cannot participate in the P2P network.
TomP2P can detect if there is already an existing port forwarding rule in place. First the peer behind the NAT needs to conact a well-known peer (typically the bootstrap peer). This peer reports back how the peer is seen behind the NAT. With this information the peer behind the NAT can guess how the port forwarding rule might look like. It is a guess, since the peer assumes that the internal and external port is the same. In cases where internal and external ports is not the same, there is the option to set the external port manually.
With UPNP and NAT-PMP, TomP2P can set the port forwarding rules on the router automatically. This happens during the port forwarding detection. As soon as TomP2P knows how other peers see a peer, and the peer detects that it is behind a NAT, it tries to set the port forwarding. The configuration in TomP2P for the port forwarding allows to enable or disable it with P2PConfiguration.setBehindFirewall(). Since the default behavior to announced the external IP changed in 3.2.7, a peer needs to discover its external IP address before it can participate (boostrap) in the network:
Peer peer = new Peer(new Number160(r));
peer.listen(4000, 4000);
PeerAddress pa = new PeerAddress(Number160.ZERO, boostrap IP, 4000, 4000);
peer.getP2PConfiguration().setBehindFirewall(true);
//find out how the other peers see me
FutureDiscover fd = peer.discover(pa);
fd.awaitUninterruptibly();
if (fd.isSuccess()) {
System.out.println("found that my outside address is "+ fd.getPeerAddress());
} else {
System.out.println("failed " + fd.getFailedReason());
}
peer.boostrap(...);