Android Open Source - BLEMeshChat B L E Protocol






From Project

Back to project page BLEMeshChat.

License

The source code is released under:

GNU General Public License

If you think the Android project BLEMeshChat listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package pro.dbro.ble.protocol;
//  w  w  w . j  a  v a  2s.  c  om
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Date;

import pro.dbro.ble.crypto.SodiumShaker;
import pro.dbro.ble.data.model.DataUtil;

/**
 * Created by davidbrodsky on 10/14/14.
 */
public class BLEProtocol implements Protocol {
    public static final String TAG = "ChatProtocol";

    // <editor-fold desc="Public API">
    /** Bluetooth LE Mesh Chat Protocol Version */
    public static final byte VERSION = 0x01;

    /** Identity */
    public static final int MESSAGE_RESPONSE_LENGTH    = 309;  // bytes
    public static final int IDENTITY_RESPONSE_LENGTH   = 140;  // bytes
    public static final int MESSAGE_BODY_LENGTH        = 140;  // bytes
    public static final int ALIAS_LENGTH               = 35;   // bytes

    private static final ByteBuffer sTimeStampBuffer = ByteBuffer.allocate(Long.SIZE / 8);

    static {
        sTimeStampBuffer.order(ByteOrder.LITTLE_ENDIAN);
    }

    /** Outgoing
     *
     * Create raw transmission data from protocol Objects
    */

    @Nullable
    public byte[] serializeIdentity(@NonNull OwnedIdentityPacket ownedIdentity) {
        // Protocol version 1
        //[[version=1][timestamp=8][sender_public_key=32][display_name=35]][signature=64]
        try {
            byte[] identity = new byte[IDENTITY_RESPONSE_LENGTH];
            int writeIndex = 0;
            writeIndex += addVersionToBuffer(identity, writeIndex);
            writeIndex += addTimestampToBuffer(identity, writeIndex);
            writeIndex += addPublicKeyToBuffer(ownedIdentity.publicKey, identity, writeIndex);
            writeIndex += addAliasToBuffer(ownedIdentity.alias, identity, writeIndex);
            writeIndex += addSignatureToBuffer(ownedIdentity.secretKey, identity, writeIndex);

            if (writeIndex != IDENTITY_RESPONSE_LENGTH)
                throw new IllegalStateException("Generated Identity does not match expected length");

            return identity;
        } catch (UnsupportedEncodingException e) {
            Log.e(TAG, "Failed to generate Identity response. Are there invalid UTF-8 characters in the user alias?");
            e.printStackTrace();
        }
        return null;
    }

    public MessagePacket serializeMessage(@NonNull OwnedIdentityPacket ownedIdentity, String body) {
        // Protocol version 1
        //[[version=1][timestamp=8][sender_public_key=32][message=140][reply_signature=64]][signature=64]
        try {
            byte[] message = new byte[MESSAGE_RESPONSE_LENGTH];
            int writeIndex = 0;
            writeIndex += addVersionToBuffer(message, writeIndex);
            writeIndex += addTimestampToBuffer(message, writeIndex);
            writeIndex += addPublicKeyToBuffer(ownedIdentity.publicKey, message, writeIndex);
            writeIndex += addMessageBodyToBuffer(body, message, writeIndex);
            writeIndex += 64; // Empty reply_signature
            writeIndex += addSignatureToBuffer(ownedIdentity.secretKey, message, writeIndex);

            if (writeIndex != MESSAGE_RESPONSE_LENGTH)
                throw new IllegalStateException("Generated Message does not match expected length");

            return deserializeMessageWithIdentity(message, ownedIdentity);
        } catch (UnsupportedEncodingException e) {
            Log.e(TAG, "Failed to generate Identity response. Are there invalid UTF-8 characters in the user alias?");
            e.printStackTrace();
        }
        return null;
    }

    /** Incoming
     *
     * Produce protocol Objects from raw transmission data
     */

    @Nullable
    public IdentityPacket deserializeIdentity(@NonNull byte[] identity) {
        if (identity.length != IDENTITY_RESPONSE_LENGTH)
            throw new IllegalArgumentException(String.format("Identity response is %d bytes. Expect %d", identity.length, IDENTITY_RESPONSE_LENGTH));

        // Protocol version 1
        //[[version=1][timestamp=8][sender_public_key=32][display_name=35]][signature=64]
        try {
            int readIndex     = 0;
            byte[] timestamp  = new byte[Long.SIZE / 8];
            byte[] public_key = new byte[SodiumShaker.crypto_sign_PUBLICKEYBYTES];
            byte[] alias      = new byte[ALIAS_LENGTH];
            byte[] signature  = new byte[SodiumShaker.crypto_sign_BYTES];
            byte[] signedData = new byte[IDENTITY_RESPONSE_LENGTH - SodiumShaker.crypto_sign_BYTES];

            readIndex += assertBufferVersion(identity, readIndex);
            readIndex += getBytesFromBuffer(identity, timestamp, readIndex);
            readIndex += getBytesFromBuffer(identity, public_key, readIndex);
            readIndex += getBytesFromBuffer(identity, alias, readIndex);
            readIndex += getBytesFromBuffer(identity, signature, readIndex);

            System.arraycopy(identity, 0, signedData, 0, signedData.length);
            boolean validSignature = SodiumShaker.verifySignature(public_key, signature, signedData);
            if (!validSignature)
                throw new IllegalStateException("Identity signature does not match content!");

            return new IdentityPacket(public_key, new String(alias, "UTF-8"), getDateFromTimestampBuffer(timestamp), identity);
        } catch (UnsupportedEncodingException e) {
            Log.e(TAG, "Failed to generate Identity response. Are there invalid UTF-8 characters in the user alias?");
            e.printStackTrace();
        }
        return null;
    }

    @Nullable
    public MessagePacket deserializeMessageWithIdentity(@NonNull byte[] message, @NonNull IdentityPacket identity) {
        if (message.length != MESSAGE_RESPONSE_LENGTH)
            throw new IllegalArgumentException(String.format("Message response is illegal length. Got %d expected %d", message.length, MESSAGE_RESPONSE_LENGTH));

        // TODO Don't duplicate this code between de
        MessagePacket messageWithoutIdentity = deserializeMessage(message);
        MessagePacket messageWithIdentity = null;

        if (messageWithoutIdentity != null) {
            messageWithIdentity = MessagePacket.attachIdentityToMessage(messageWithoutIdentity, identity);
        }
        return messageWithIdentity;
    }

    @Nullable
    public MessagePacket deserializeMessage(@NonNull byte[] message) {
        if (message.length != MESSAGE_RESPONSE_LENGTH)
            throw new IllegalArgumentException(String.format("Message response is illegal length. Got %d expected %d", message.length, MESSAGE_RESPONSE_LENGTH));

        // Protocol version 1
        //[[version=1][timestamp=8][sender_public_key=32][message=140][reply_signature=64]][signature=64]
        try {
            int readIndex          = 0;
            byte[] timestamp       = new byte[Long.SIZE / 8];
            byte[] public_key      = new byte[SodiumShaker.crypto_sign_PUBLICKEYBYTES];
            byte[] body            = new byte[MESSAGE_BODY_LENGTH];
            byte[] signature       = new byte[SodiumShaker.crypto_sign_BYTES];
            byte[] replySignature  = new byte[SodiumShaker.crypto_sign_BYTES];
            byte[] signedData      = new byte[MESSAGE_RESPONSE_LENGTH - SodiumShaker.crypto_sign_BYTES];

            readIndex += assertBufferVersion(message, readIndex);
            readIndex += getBytesFromBuffer(message, timestamp, readIndex);
            readIndex += getBytesFromBuffer(message, public_key, readIndex);
            readIndex += getBytesFromBuffer(message, body, readIndex);
            readIndex += getBytesFromBuffer(message, replySignature, readIndex);
            readIndex += getBytesFromBuffer(message, signature, readIndex);

            System.arraycopy(message, 0, signedData, 0, signedData.length);
            boolean validSignature = SodiumShaker.verifySignature(public_key, signature, signedData);
            if (!validSignature)
                throw new IllegalStateException("Message signature does not match content!");

            return new MessagePacket(public_key, signature, replySignature, getDateFromTimestampBuffer(timestamp), new String(body, "UTF-8"), message);
        } catch (UnsupportedEncodingException e) {
            Log.e(TAG, "Failed to generate Identity response. Are there invalid UTF-8 characters in the user alias?");
            e.printStackTrace();
        }
        return null;
    }


    // </editor-fold desc="Public API">

    // <editor-fold desc="Private API">

    private static int addVersionToBuffer(@NonNull byte[] input, int offset) {
        int bytesToWrite = 1;
        assertBufferLength(input, offset + bytesToWrite);

        input[offset] = VERSION;
        return bytesToWrite;
    }

    private static int getVersionFromBuffer(@NonNull byte[] input, @NonNull byte[] version, int offset) {
        int bytesToRead = 1;
        assertBufferLength(input, offset + bytesToRead);
        version[0] = input[offset];
        return bytesToRead;
    }

    private static int addTimestampToBuffer(@NonNull byte[] input, int offset) {
        synchronized (sTimeStampBuffer) {
            int bytesToWrite = Long.SIZE / 8;
            assertBufferLength(input, offset + bytesToWrite);

            long unixTime64 = System.currentTimeMillis();
            sTimeStampBuffer.rewind();
            sTimeStampBuffer.putLong(unixTime64);
            System.arraycopy(sTimeStampBuffer.array(), 0, input, offset, bytesToWrite);
            return bytesToWrite;
        }
    }

    private static int addPublicKeyToBuffer(@NonNull byte[] public_key, @NonNull byte[] input, int offset) {
        int bytesToWrite = public_key.length;
        assertBufferLength(input, offset + bytesToWrite);

        System.arraycopy(public_key, 0, input, offset, bytesToWrite);
        return bytesToWrite;
    }

    private static int addAliasToBuffer(@NonNull String alias, @NonNull byte[] input, int offset) throws UnsupportedEncodingException {
        int bytesToWrite = ALIAS_LENGTH;
        assertBufferLength(input, offset + bytesToWrite);

        byte[] aliasAsBytes = alias.getBytes("UTF-8");
        byte[] paddedAliasAsBytes = new byte[ALIAS_LENGTH];

        truncateOrPadTextBuffer(aliasAsBytes, paddedAliasAsBytes);

        System.arraycopy(paddedAliasAsBytes, 0, input, offset, bytesToWrite);
        return bytesToWrite;
    }

    private static int addMessageBodyToBuffer(@NonNull String body, @NonNull byte[] input, int offset) throws UnsupportedEncodingException {
        int bytesToWrite = MESSAGE_BODY_LENGTH;
        assertBufferLength(input, offset + bytesToWrite);

        byte[] bodyAsBytes = body.getBytes("UTF-8");
        byte[] paddedBodyAsBytes = new byte[MESSAGE_BODY_LENGTH];

        truncateOrPadTextBuffer(bodyAsBytes, paddedBodyAsBytes);

        System.arraycopy(paddedBodyAsBytes, 0, input, offset, bytesToWrite);
        return bytesToWrite;
    }

    private static int getBytesFromBuffer(@NonNull byte[] input, @NonNull byte[] output, int offset) {
        int bytesToRead = output.length;
        assertBufferLength(input, offset + bytesToRead);

        System.arraycopy(input, offset, output, 0, bytesToRead);
        return bytesToRead;
    }

    /**
     * Generate signature for input from the first byte until the offset byte. Append signature to input after offset byte.
     */
    private static int addSignatureToBuffer(@NonNull byte[] secret_key, @NonNull byte[] input, int offset) {
        int bytesToWrite = SodiumShaker.crypto_sign_BYTES;
        assertBufferLength(input, offset + bytesToWrite);

        byte[] signature = SodiumShaker.generateSignatureForMessage(secret_key, input, offset);

        System.arraycopy(signature, 0, input, offset, bytesToWrite);
        return bytesToWrite;
    }

    /** Utility */

    /**
     * Truncates or pads input to fit precisely inside output.
     * After this call output will contain the truncated or padded input
     */
    private static void truncateOrPadTextBuffer(byte[] input, byte[] output) {
        System.arraycopy(input, 0, output, 0, Math.min(input.length, output.length));
        if (input.length < output.length) {
            for (int x = input.length; x < output.length; x++) {
                output[x] = 0x20; // UTF-8 space
            }
        }
    }

    private static void assertBufferLength(byte[] input, int minimumLength) {
        if (input.length < minimumLength)
            throw new IllegalArgumentException(String.format("Operation requires input buffer length %d. Actual: %d", minimumLength, input.length));
    }

    private static int assertBufferVersion(byte[] input, int offset) {
        byte[] version = new byte[1];
        getVersionFromBuffer(input, version, offset);

        if (version[0] != VERSION)
            throw new IllegalStateException(String.format("Response is for an unknown protocol version. Got %d. Expected %d", version[0], VERSION));
        return 1;
    }

    @Nullable
    private static Date getDateFromTimestampBuffer(byte[] timestamp) {
        synchronized (sTimeStampBuffer) {
            sTimeStampBuffer.clear();
            sTimeStampBuffer.put(timestamp);
            sTimeStampBuffer.rewind();
            // TODO: Test if flip needed
            return new Date(sTimeStampBuffer.getLong());
        }
    }

    // </editor-fold desc="Private API">
}




Java Source Code List

im.delight.android.identicons.AsymmetricIdenticon.java
im.delight.android.identicons.Identicon.java
im.delight.android.identicons.SymmetricIdenticon.java
pro.dbro.ble.ActivityRecevingMessagesIndicator.java
pro.dbro.ble.ChatAppTest.java
pro.dbro.ble.ChatApp.java
pro.dbro.ble.ChatService.java
pro.dbro.ble.crypto.KeyPair.java
pro.dbro.ble.crypto.SodiumShaker.java
pro.dbro.ble.data.ContentProviderStore.java
pro.dbro.ble.data.DataStore.java
pro.dbro.ble.data.model.ChatContentProvider.java
pro.dbro.ble.data.model.ChatDatabase.java
pro.dbro.ble.data.model.CursorModel.java
pro.dbro.ble.data.model.DataUtil.java
pro.dbro.ble.data.model.IdentityDeliveryTable.java
pro.dbro.ble.data.model.MessageCollection.java
pro.dbro.ble.data.model.MessageDeliveryTable.java
pro.dbro.ble.data.model.MessageTable.java
pro.dbro.ble.data.model.Message.java
pro.dbro.ble.data.model.PeerTable.java
pro.dbro.ble.data.model.Peer.java
pro.dbro.ble.protocol.BLEProtocol.java
pro.dbro.ble.protocol.IdentityPacket.java
pro.dbro.ble.protocol.MessagePacket.java
pro.dbro.ble.protocol.OwnedIdentityPacket.java
pro.dbro.ble.protocol.Protocol.java
pro.dbro.ble.transport.ConnectionGovernor.java
pro.dbro.ble.transport.ConnectionListener.java
pro.dbro.ble.transport.Transport.java
pro.dbro.ble.transport.ble.BLECentralConnection.java
pro.dbro.ble.transport.ble.BLECentralRequest.java
pro.dbro.ble.transport.ble.BLECentral.java
pro.dbro.ble.transport.ble.BLEPeripheralResponse.java
pro.dbro.ble.transport.ble.BLEPeripheral.java
pro.dbro.ble.transport.ble.BLETransport.java
pro.dbro.ble.transport.ble.BLEUtil.java
pro.dbro.ble.transport.ble.GATT.java
pro.dbro.ble.ui.Notification.java
pro.dbro.ble.ui.activities.LogConsumer.java
pro.dbro.ble.ui.activities.MainActivity.java
pro.dbro.ble.ui.activities.Util.java
pro.dbro.ble.ui.adapter.CursorFilter.java
pro.dbro.ble.ui.adapter.MessageAdapter.java
pro.dbro.ble.ui.adapter.PeerAdapter.java
pro.dbro.ble.ui.adapter.RecyclerViewCursorAdapter.java
pro.dbro.ble.ui.fragment.MessageListFragment.java
pro.dbro.ble.util.RandomString.java