Android - Hive2Hive/Hive2Hive GitHub Wiki

To answer your first question: Yes Hive2Hive (above version 1.2.0) works on Android. However, some key points need to be regarded.

NAT Traversal

Since mobile phones can be behind the NAT of the cellular network provider, NAT traversal using relay peers needs to be set up. This means, there need to be well-known relay peers in the network. To start relay peers, include the following dependency in your Hive2Hive client:

<dependency>
	<groupId>net.tomp2p</groupId>
	<artifactId>tomp2p-nat</artifactId>
	<version>...</version>
	<scope>compile</scope>
</dependency>

...

<repository>
	<id>tomp2p.net</id>
	<name>TomP2P Repository</name>
	<url>http://tomp2p.net/dev/mvn/</url>
</repository>

Then start a relay peer using an already connected Hive2Hive node:

new PeerBuilderNAT(h2hNode.getPeer().peer()).start();

Voilà, peers behind NAT devices / firewalls (like mobile phones) can now connect to this relay peer as follows:

PeerNAT peerNat = new PeerBuilderNAT(h2hNode.getPeer().peer()).start();
RelayClientConfig config = new TCPRelayClientConfig();
FutureRelayNAT futureRelayNAT = peerNat.startRelay(config, relayPeerAddress).awaitUninterruptibly();

Energy Consumption

As you can see, we used TCPRelayClientConfig above. This opens a steady TCP connection between the relay peer and the mobile phone to push messages through the NAT. In cellular networks, this connection drains much battery because it needs to be maintained using heartbeats. This prevents the cellular network adapter from going to sleep mode.

As a solution, you can use the Google Cloud Messaging service with a buffer to omit this TCP connection. It slows down requests because a latency of at least 20s is added (GCM restrictions). However, if the quota of mobile peers in the DHT is low, this may not reduce overall performance because of the replication.

To use Google Cloud Messaging, include the following additional artifact in your client:

<dependency>
	<groupId>net.tomp2p</groupId>
	<artifactId>tomp2p-android</artifactId>
	<version>...</version>
	<scope>compile</scope>
</dependency>

At the relay side, you need to add the GCM ability separately:

MessageBufferConfiguration bufferConfiguration = new MessageBufferConfiguration().bufferAgeLimit(20 * 1000);
AndroidRelayServerConfig androidServer = new AndroidRelayServerConfig(gcmAuthenticationKey, 5, bufferConfiguration);
new PeerBuilderNAT(h2hNode.getPeer().peer()).addRelayServerConfiguration(RelayType.ANDROID, androidServer).start();

The gcmAuthenticationKey is the API key to use Google Cloud Messaging. It can be obtained at the Google Developers Console.

At the Android side, you need to configure that you want to use the relay with GCM instead of an open TCP connection. First include the same android dependency as above, then use the following code instead of the open TCP connection variant:

RelayClientConfig config = new AndroidRelayClientConfig(gcmRegistrationID);
FutureRelayNAT futureRelayNAT = peerNat.startRelay(config, relayPeerAddress).awaitUninterruptibly();

The gcmRegistrationID is the sender ID, which can also be obtained at the Google Developers Console, together with the authentication key.

Encryption

Hive2Hive uses the BouncyCastle library for encryption / decryption by default. Android has adopted a (cropped and crappy) version of BouncyCastle as the default security provider. An optimized variant of BC for Android is SpongyCastle. To use SpongyCastle instead of BouncyCastle in Hive2Hive, you need to include the following library in you Android project:

compile 'com.madgag.spongycastle:prov:{version}'

Then, add these classes:

public class SpongyCastleEncryption extends H2HDefaultEncryption {

	public SpongyCastleEncryption(IH2HSerialize serializer) {
		super(serializer, "SC", new SCStrongAESEncryption());

		// install the SC provider instead of the BC provider
		if (Security.getProvider("SC") == null) {
			Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
		}
	}
}

and

public class SCStrongAESEncryption implements IStrongAESEncryption {

	@Override
	public byte[] encryptStrongAES(byte[] data, SecretKey key, byte[] initVector) throws GeneralSecurityException {
		try {
			return processAESCipher(true, data, key, initVector);
		} catch (DataLengthException | IllegalStateException | InvalidCipherTextException e) {
			throw new GeneralSecurityException("Cannot encrypt the data with AES 256bit", e);
		}
	}

	@Override
	public byte[] decryptStrongAES(byte[] data, SecretKey key, byte[] initVector) throws GeneralSecurityException {
		try {
			return processAESCipher(false, data, key, initVector);
		} catch (DataLengthException | IllegalStateException | InvalidCipherTextException e) {
			throw new GeneralSecurityException("Cannot decrypt the data with AES 256bit", e);
		}
	}

	private static byte[] processAESCipher(boolean encrypt, byte[] data, SecretKey key, byte[] initVector)
			throws DataLengthException, IllegalStateException, InvalidCipherTextException {
		// seat up engine, block cipher mode and padding
		AESEngine aesEngine = new AESEngine();
		CBCBlockCipher cbc = new CBCBlockCipher(aesEngine);
		PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(cbc);

		// apply parameters
		CipherParameters parameters = new ParametersWithIV(new KeyParameter(key.getEncoded()), initVector);
		cipher.init(encrypt, parameters);

		// process ciphering
		byte[] output = new byte[cipher.getOutputSize(data.length)];

		int bytesProcessed1 = cipher.processBytes(data, 0, data.length, output, 0);
		int bytesProcessed2 = cipher.doFinal(output, bytesProcessed1);
		byte[] result = new byte[bytesProcessed1 + bytesProcessed2];
		System.arraycopy(output, 0, result, 0, result.length);
		return result;
	}
}

The first class SpongyCastleEncryption overrides the use of BC with SC. The second class is optional and enables AES encryption with keys larger than 128 bit. If you need at most 128bit, SCStrongAESEncryption is not required.

Use these implementations when you create the peer:

IH2HNode h2hNode = H2HNode.createNode(FileConfiguration.createDefault(), new SpongyCastleEncryption(serializer), serializer);

Serialization

Finally, there are some serialization issues with Android. The first one is the use of SpongyCastle instead of BouncyCastle. A serialized SpongyCastle class cannot be simply deserialized into a BouncyCastle class because of the different name space. Therefore the default Java serializer cannot be used and would lead to an exception as soon as the first key(pair) is sent over the network. Instead, use the optimized FST serializer. To prevent namespace conflicts, indices instead of class names are sent over the network. To map the correct index to the correct key implementation, an implementation of ISecurityClassProvider for the used security provider must be provided. If you use SpongyCastle (as explained above), the following implementation at the Android device can be used:

public class SCSecurityClassProvider implements ISecurityClassProvider {
	public static final String SECURITY_PROVIDER = BouncyCastleProvider.PROVIDER_NAME;

	@Override
	public String getSecurityProvider() {
		return SECURITY_PROVIDER;
	}

	@Override
	public Class<? extends RSAPublicKey> getRSAPublicKeyClass() {
		return BCRSAPublicKey.class;
	}

	@Override
	public Class<? extends RSAPrivateKey> getRSAPrivateKeyClass() {
		return BCRSAPrivateKey.class;
	}

	@Override
	public Class<? extends RSAPrivateCrtKey> getRSAPrivateCrtKeyClass() {
		return BCRSAPrivateCrtKey.class;
	}
}

The implementation of the BouncyCastle provider used at "normal" peers is already in Hive2Hive and is automatically used when you use the FSTSerializer together with BouncyCastle. The FST serializer is the default serializer, thus no action at the "normal" peer is required. At the Android peer, the SpongyCastle security provider classes need to be injected:

IH2HSerialize serializer = new FSTSerializer(false, new SCSecurityClassProvider());
IH2HNode h2hNode = H2HNode.createNode(FileConfiguration.createDefault(), new SpongyCastleEncryption(serializer), serializer);
⚠️ **GitHub.com Fallback** ⚠️