From 834f3ca72405a31f06aa2baa6836f2aa474948b5 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Fri, 12 Dec 2014 00:13:22 +0200 Subject: [PATCH] Moved remaining messages to binary encoding, removed msgpack dependency. This seems to have added some random disconnects, but there doesn't seem to be a reason for this in the code. --- PROTOCOL.md | 112 ++++++++++-- app/build.gradle | 25 +-- app/proguard-rules.pro | 7 +- .../nutomic/ensichat/aodvv2/AddressTest.scala | 7 +- .../ensichat/aodvv2/MessageHeaderTest.scala | 35 ++-- .../nutomic/ensichat/aodvv2/MessageTest.scala | 77 ++++++++ .../aodvv2/ResultAddContactTest.scala | 18 ++ .../ensichat/messages/CryptoTest.scala | 20 ++- .../ensichat/messages/MessageTest.scala | 50 ------ .../nutomic/ensichat/util/DatabaseTest.scala | 22 +-- app/src/main/res/xml/settings.xml | 2 +- .../activities/AddContactsActivity.scala | 41 ++--- .../ensichat/aodvv2/ConnectionInfo.scala | 13 +- .../nutomic/ensichat/aodvv2/CryptoData.scala | 67 +++++++ .../com/nutomic/ensichat/aodvv2/Data.scala | 37 ---- .../ensichat/aodvv2/EncryptedBody.scala | 15 ++ .../com/nutomic/ensichat/aodvv2/Message.scala | 50 ++++++ .../nutomic/ensichat/aodvv2/MessageBody.scala | 7 +- .../ensichat/aodvv2/MessageHeader.scala | 18 +- .../ensichat/aodvv2/RequestAddContact.scala | 34 ++++ .../ensichat/aodvv2/ResultAddContact.scala | 41 +++++ .../com/nutomic/ensichat/aodvv2/Text.scala | 50 ++++++ .../ensichat/bluetooth/ChatService.scala | 115 ++++++------ .../ensichat/bluetooth/ConnectThread.scala | 2 +- .../ensichat/bluetooth/ListenThread.scala | 1 + .../ensichat/bluetooth/TransferThread.scala | 31 ++-- .../ensichat/fragments/ChatFragment.scala | 18 +- .../nutomic/ensichat/messages/Crypto.scala | 100 ++++------- .../nutomic/ensichat/messages/Message.scala | 122 ------------- .../messages/RequestAddContactMessage.scala | 30 ---- .../messages/ResultAddContactMessage.scala | 37 ---- .../ensichat/messages/TextMessage.scala | 32 ---- .../nutomic/ensichat/util/BufferUtils.scala | 5 +- .../com/nutomic/ensichat/util/Database.scala | 41 ++--- .../ensichat/util/MessagesAdapter.scala | 11 +- build.gradle | 2 +- gradle/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 51017 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + gradle/gradlew | 164 ++++++++++++++++++ gradle/gradlew.bat | 90 ++++++++++ gradle/local.properties | 11 ++ gradle/wrapper/gradle-wrapper.jar | Bin 49896 -> 51017 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- 43 files changed, 984 insertions(+), 585 deletions(-) create mode 100644 app/src/androidTest/scala/com/nutomic/ensichat/aodvv2/MessageTest.scala create mode 100644 app/src/androidTest/scala/com/nutomic/ensichat/aodvv2/ResultAddContactTest.scala delete mode 100644 app/src/androidTest/scala/com/nutomic/ensichat/messages/MessageTest.scala create mode 100644 app/src/main/scala/com/nutomic/ensichat/aodvv2/CryptoData.scala delete mode 100644 app/src/main/scala/com/nutomic/ensichat/aodvv2/Data.scala create mode 100644 app/src/main/scala/com/nutomic/ensichat/aodvv2/EncryptedBody.scala create mode 100644 app/src/main/scala/com/nutomic/ensichat/aodvv2/Message.scala create mode 100644 app/src/main/scala/com/nutomic/ensichat/aodvv2/RequestAddContact.scala create mode 100644 app/src/main/scala/com/nutomic/ensichat/aodvv2/ResultAddContact.scala create mode 100644 app/src/main/scala/com/nutomic/ensichat/aodvv2/Text.scala delete mode 100644 app/src/main/scala/com/nutomic/ensichat/messages/Message.scala delete mode 100644 app/src/main/scala/com/nutomic/ensichat/messages/RequestAddContactMessage.scala delete mode 100644 app/src/main/scala/com/nutomic/ensichat/messages/ResultAddContactMessage.scala delete mode 100644 app/src/main/scala/com/nutomic/ensichat/messages/TextMessage.scala create mode 100644 gradle/gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/gradle/wrapper/gradle-wrapper.properties create mode 100755 gradle/gradlew create mode 100644 gradle/gradlew.bat create mode 100644 gradle/local.properties diff --git a/PROTOCOL.md b/PROTOCOL.md index 5a6ff41..b7f9dc4 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -7,17 +7,17 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. -A node is a single device implementing this protocol. Each node has -exactl one node address. +A _node_ is a single device implementing this protocol. Each node has +exactly one node address based on its RSA key pair. -A node address consists of 32 bytes and is the SHA-256 hash of the +A _node address_ consists of 32 bytes and is the SHA-256 hash of the node's public key. -The broadcast address is +The _broadcast address_ is `0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF` (i.e. all bits set). -The null address is +The _null address_ is `0x0000000000000000000000000000000000000000000000000000000000000000` (i.e. no bits set). @@ -29,6 +29,27 @@ nodes MUST NOT connect to a node with either address. Messages -------- +All messages are signed using RSASSA-PKCS1-v1_5. All messages except +ConnectionInfo are encrypted using AES/CBC/PKCS5Padding, after which +the AES key is wrapped with the recipient's public RSA key. + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + / / + \ Header (variable length) \ + / / + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + / / + \ Encryption Data (variable length) \ + / / + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + / / + \ Body (variable length) \ + / / + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + ### Header Every message starts with one 32 bit word indicating the message @@ -54,6 +75,8 @@ header is in network byte order, i.e. big endian. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence Number | Metric | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Body Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Ver specifies the protocol version number. This is currently 0. A message with unknown version number MUST be ignored. The connection @@ -83,17 +106,50 @@ Sequence number is the sequence number of either the source or target node for this message, depending on type. +### Encryption Data + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Signature Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + / / + \ Signature (variable length) \ + / / + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Encryption Key Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + / / + \ Encryption Key (variable length) \ + / / + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Encryption key is the symmetric key that was used to encrypt the message +body. + +Signature is the cryptographic signature over the (unencrypted) message +header and message body. + + + ConnectionInfo (Type = 0) --------- After successfully connecting to a node via Bluetooth, public keys -must be exchanged. Each node MUST send this as the first message over +are exchanged. Each node MUST send this as the first message over the connection. Hop Limit MUST be 1 for this message type (i.e. it must never be forwarded). Origin Address and Target Address MUST be set to all zeros, and MUST be ignored by the receiving node. A receiving node SHOULD store the key in permanent storage if it -hasn't already stored it earlier. This key is to be used for message +hasn't already stored it earlier. However, a node MAY decide to +delete these stored keys in a least-recently-used order to adhere +to storage limitations. If a key has been deleted, messages to +that node can only be sent once a new ConnectionInfo message +for it has been received. + + +This key is to be used for message encryption when communicating with the sending node. 0 1 2 3 @@ -114,20 +170,46 @@ After this message has been received, communication with normal messages may start. -### Data (Data Transfer, Type = 255) +### RequestAddContact (Type = 4) + +Sent when a user wants to add another node as a contact. After this, +a ResultAddContact message should be returned. 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Length | + | Reserved | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +### ResultAddContact (Type = 5) + +Sent as response to a RequestAddContact message. + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |A| Reserved | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Accepted bit (A) is true if the user accepts the new contact, false +otherwise. Nodes should only add another node as a contact if both +users agreed. + +### Text (Type = 6) + +A simple chat message. + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Reserved | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Text Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ / / - \ Data (variable length) \ + \ Text (variable length) \ / / +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -Length is the number of bytes in data. - -Data is any binary data that should be transported. - -This message type is deprecated. \ No newline at end of file +Text the string to be transferred, encoded as UTF-8. diff --git a/app/build.gradle b/app/build.gradle index 451f567..60c490c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,27 +6,19 @@ buildscript { mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:0.14.4" classpath "jp.leafytree.gradle:gradle-android-scala-plugin:1.3.1" } } dependencies { - compile "com.android.support:support-v4:21.0.0" + compile "com.android.support:support-v4:21.0.2" // For `flat` debug config, as `flatProvided` is unknown. provided "org.scala-lang:scala-library:2.11.4" debugCompile "org.scala-lang:scala-library:2.11.4" releaseCompile "org.scala-lang:scala-library:2.11.4" - compile("org.msgpack:msgpack-scala_2.11:0.6.11") { - transitive = false; - } - compile('org.msgpack:msgpack:0.6.11') { - transitive = false; - } compile 'com.google.guava:guava:18.0' } - android { compileSdkVersion 21 buildToolsVersion "21.1.1" @@ -40,13 +32,8 @@ android { } sourceSets { - main { - scala.srcDir "src/main/scala" - } - - androidTest { - scala.srcDir "src/androidTest/scala" - } + main.scala.srcDir "src/main/scala" + androidTest.scala.srcDir "src/androidTest/scala" } buildTypes { @@ -68,11 +55,13 @@ android { // Needed to rename `app-thin.apk` to `app-debug.apk` (because Android Studio doesn't let us // specify a different apk name). +/* applicationVariants.all { variant -> def apk = variant.outputFile; def newName = apk.name.replace("app-thin", "app-debug"); variant.outputFile = new File(apk.parentFile, newName); } +*/ // Avoid duplicate file errors during packaging. packagingOptions { @@ -82,3 +71,7 @@ android { exclude 'META-INF/NOTICE' } } + +tasks.withType(ScalaCompile) { + scalaCompileOptions.useCompileDaemon = true +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 3d4b47b..218d085 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,11 +18,12 @@ -dontpreverify -dontwarn scala.** -keep class !scala*.** { *; } --ignorewarnings # Avoid crash when invoking String.toInt (see https://issues.scala-lang.org/browse/SI-5397). -keep class scala.collection.SeqLike { public protected *; } -# Suppress warnings caused by msgpack (code works fine anyway). --dontwarn +# Disable warnings for Guava annotations. +-dontwarn javax.annotation.** +-dontwarn javax.inject.** +-dontwarn sun.misc.Unsafe diff --git a/app/src/androidTest/scala/com/nutomic/ensichat/aodvv2/AddressTest.scala b/app/src/androidTest/scala/com/nutomic/ensichat/aodvv2/AddressTest.scala index f464598..b350a40 100644 --- a/app/src/androidTest/scala/com/nutomic/ensichat/aodvv2/AddressTest.scala +++ b/app/src/androidTest/scala/com/nutomic/ensichat/aodvv2/AddressTest.scala @@ -14,14 +14,17 @@ object AddressTest { val a4 = new Address("4444459893F8810C4024CFC951374AABA1F4DE6347A3D7D8E44918AD1FF2BA36") - val a1Binary: Array[Byte] = Array(-91, 27, 116, 71, 94, -26, 34, -61, -55, 36, -37, 20, 118, 104, -8, 94, 2, 76, -96, -76, 76, -95, 70, -75, -29, -45, -61, 26, 84, -77, 76, 30).map(_.toByte) + val a1Binary: Array[Byte] = Array(-91, 27, 116, 71, 94, -26, 34, -61, -55, 36, -37, 20, 118, 104, + -8, 94, 2, 76, -96, -76, 76, -95, 70, -75, -29, -45, -61, 26, 84, -77, 76, 30).map(_.toByte) + + val Addresses = Set(a1, a2, a3, a4, Address.Broadcast, Address.Null) } class AddressTest extends AndroidTestCase { def testEncode(): Unit = { - Set(Address.Broadcast, Address.Null, a1, a2, a3, a4).foreach{a => + Addresses.foreach{a => val base32 = a.toString val read = new Address(base32) assertEquals(a, read) diff --git a/app/src/androidTest/scala/com/nutomic/ensichat/aodvv2/MessageHeaderTest.scala b/app/src/androidTest/scala/com/nutomic/ensichat/aodvv2/MessageHeaderTest.scala index 49310d5..bfdf560 100644 --- a/app/src/androidTest/scala/com/nutomic/ensichat/aodvv2/MessageHeaderTest.scala +++ b/app/src/androidTest/scala/com/nutomic/ensichat/aodvv2/MessageHeaderTest.scala @@ -1,30 +1,41 @@ package com.nutomic.ensichat.aodvv2 -import java.util.Date +import java.util.GregorianCalendar import android.test.AndroidTestCase -import junit.framework.Assert +import com.nutomic.ensichat.aodvv2.MessageHeaderTest._ +import junit.framework.Assert._ object MessageHeaderTest { - val h1 = new MessageHeader(Data.Type, MessageHeader.DefaultHopLimit, new Date(), AddressTest.a3, - AddressTest.a4, 456, 123) + val h1 = new MessageHeader(Text.Type, MessageHeader.DefaultHopLimit, AddressTest.a1, + AddressTest.a2, 1234, 0, new GregorianCalendar(1970, 1, 1).getTime, 567, 8) - val h2 = new MessageHeader(0xfff, 0, new Date(0xffffffff), Address.Null, Address.Broadcast, 0, - 0xff) + val h2 = new MessageHeader(Text.Type, 0, AddressTest.a1, AddressTest.a3, 8765, 234, + new GregorianCalendar(2014, 6, 10).getTime, 0, 0xff) - val h3 = new MessageHeader(0xfff, 0xff, new Date(0), Address.Broadcast, Address.Null, 0xffff, 0) + val h3 = new MessageHeader(Text.Type, 0xff, AddressTest.a4, AddressTest.a2, 0, 56, + new GregorianCalendar(2020, 11, 11).getTime, 0xffff, 0) + + val h4 = new MessageHeader(0xfff, 0, Address.Null, Address.Broadcast, 0, 0xff, + new GregorianCalendar(1990, 1, 1).getTime, 0, 0xff) + + val h5 = new MessageHeader(ConnectionInfo.Type, 0xff, Address.Broadcast, Address.Null, 0xffff, 0, + new GregorianCalendar(2035, 12, 31).getTime, 0xffff, 0) + + val headers = Set(h1, h2, h3, h4, h5) } class MessageHeaderTest extends AndroidTestCase { def testSerialize(): Unit = { - val ci = ConnectionInfoTest.generateCi(getContext) - val bytes = MessageHeaderTest.h1.write(ci) - val header = MessageHeader.read(bytes) - Assert.assertEquals(MessageHeaderTest.h1, header) - Assert.assertEquals(bytes.length, header.Length) + headers.foreach{h => + val bytes = h.write(0) + val header = MessageHeader.read(bytes) + assertEquals(h, header) + assertEquals(bytes.length, header.Length) + } } } \ No newline at end of file diff --git a/app/src/androidTest/scala/com/nutomic/ensichat/aodvv2/MessageTest.scala b/app/src/androidTest/scala/com/nutomic/ensichat/aodvv2/MessageTest.scala new file mode 100644 index 0000000..fa8f8b3 --- /dev/null +++ b/app/src/androidTest/scala/com/nutomic/ensichat/aodvv2/MessageTest.scala @@ -0,0 +1,77 @@ +package com.nutomic.ensichat.aodvv2 + +import java.io.ByteArrayInputStream +import java.util.GregorianCalendar + +import android.test.AndroidTestCase +import com.nutomic.ensichat.aodvv2.MessageHeaderTest._ +import com.nutomic.ensichat.aodvv2.MessageTest._ +import com.nutomic.ensichat.messages.Crypto +import junit.framework.Assert._ + +import scala.collection.immutable.TreeSet + +object MessageTest { + + val m1 = new Message(h1, new Text("first")) + + val m2 = new Message(h2, new Text("second")) + + val m3 = new Message(h3, new Text("third")) + + val messages = Set(m1, m2, m3) + +} + +class MessageTest extends AndroidTestCase { + + lazy val Crypto: Crypto = new Crypto(getContext) + + override def setUp(): Unit = { + super.setUp() + if (!Crypto.localKeysExist) { + Crypto.generateLocalKeys() + } + } + + def testOrder(): Unit = { + var messages = new TreeSet[Message]()(Message.Ordering) + messages += m1 + messages += m2 + assertEquals(m1, messages.firstKey) + + messages = new TreeSet[Message]()(Message.Ordering) + messages += m2 + messages += m3 + assertEquals(m2, messages.firstKey) + } + + def testSerializeSigned(): Unit = { + val header = new MessageHeader(ConnectionInfo.Type, 0xff, AddressTest.a4, AddressTest.a2, 0, 56, + new GregorianCalendar(2020, 11, 11).getTime, 0xffff, 0) + val m = new Message(header, ConnectionInfoTest.generateCi(getContext)) + + val signed = Crypto.sign(m) + val bytes = signed.write + val read = Message.read(new ByteArrayInputStream(bytes)) + + assertEquals(signed, read) + assertTrue(Crypto.verify(read, Crypto.getLocalPublicKey)) + } + + def testSerializeEncrypted(): Unit = { + messages.foreach{ m => + val signed = Crypto.sign(m) + val encrypted = Crypto.encrypt(signed, Crypto.getLocalPublicKey) + val bytes = encrypted.write + + val read = Message.read(new ByteArrayInputStream(bytes)) + assertEquals(encrypted.Crypto, read.Crypto) + val decrypted = Crypto.decrypt(read) + assertEquals(m.Header, decrypted.Header) + assertEquals(m.Body, decrypted.Body) + assertTrue(Crypto.verify(decrypted, Crypto.getLocalPublicKey)) + } + } + +} \ No newline at end of file diff --git a/app/src/androidTest/scala/com/nutomic/ensichat/aodvv2/ResultAddContactTest.scala b/app/src/androidTest/scala/com/nutomic/ensichat/aodvv2/ResultAddContactTest.scala new file mode 100644 index 0000000..92ac748 --- /dev/null +++ b/app/src/androidTest/scala/com/nutomic/ensichat/aodvv2/ResultAddContactTest.scala @@ -0,0 +1,18 @@ +package com.nutomic.ensichat.aodvv2 + +import android.test.AndroidTestCase +import junit.framework.Assert._ + + +class ResultAddContactTest extends AndroidTestCase { + + def testWriteRead(): Unit = { + Array(true, false).foreach { a => + val rac = new ResultAddContact(a) + val bytes = rac.write + val read = ResultAddContact.read(bytes) + assertEquals(a, read.Accepted) + } + } + +} \ No newline at end of file diff --git a/app/src/androidTest/scala/com/nutomic/ensichat/messages/CryptoTest.scala b/app/src/androidTest/scala/com/nutomic/ensichat/messages/CryptoTest.scala index f97dc51..0b67fd6 100644 --- a/app/src/androidTest/scala/com/nutomic/ensichat/messages/CryptoTest.scala +++ b/app/src/androidTest/scala/com/nutomic/ensichat/messages/CryptoTest.scala @@ -1,7 +1,7 @@ package com.nutomic.ensichat.messages import android.test.AndroidTestCase -import com.nutomic.ensichat.messages.MessageTest._ +import com.nutomic.ensichat.aodvv2.MessageTest._ import junit.framework.Assert._ class CryptoTest extends AndroidTestCase { @@ -16,15 +16,21 @@ class CryptoTest extends AndroidTestCase { } def testSignVerify(): Unit = { - val sig = Crypto.calculateSignature(m1) - assertTrue(Crypto.isValidSignature(m1, sig, Crypto.getLocalPublicKey)) + messages.foreach { m => + val signed = Crypto.sign(m) + assertTrue(Crypto.verify(signed, Crypto.getLocalPublicKey)) + assertEquals(m.Header, signed.Header) + assertEquals(m.Body, signed.Body) + } } def testEncryptDecrypt(): Unit = { - val (encrypted, key) = - Crypto.encrypt(null, MessageTest.m1.write(Array[Byte]()), Crypto.getLocalPublicKey) - val decrypted = Crypto.decrypt(encrypted, key) - assertEquals(MessageTest.m1, Message.read(decrypted)._1) + messages.foreach{ m => + val encrypted = Crypto.encrypt(Crypto.sign(m), Crypto.getLocalPublicKey) + val decrypted = Crypto.decrypt(encrypted) + assertEquals(m.Body, decrypted.Body) + assertEquals(m.Header, encrypted.Header) + } } } diff --git a/app/src/androidTest/scala/com/nutomic/ensichat/messages/MessageTest.scala b/app/src/androidTest/scala/com/nutomic/ensichat/messages/MessageTest.scala deleted file mode 100644 index bc09824..0000000 --- a/app/src/androidTest/scala/com/nutomic/ensichat/messages/MessageTest.scala +++ /dev/null @@ -1,50 +0,0 @@ -package com.nutomic.ensichat.messages - -import java.io.{PipedInputStream, PipedOutputStream} -import java.util.GregorianCalendar - -import android.test.AndroidTestCase -import com.nutomic.ensichat.aodvv2.AddressTest -import com.nutomic.ensichat.messages.MessageTest._ -import junit.framework.Assert._ - -import scala.collection.immutable.TreeSet - -object MessageTest { - - val m1 = new TextMessage(AddressTest.a1, AddressTest.a2, - new GregorianCalendar(2014, 10, 29).getTime, "first") - - val m2 = new TextMessage(AddressTest.a1, AddressTest.a3, - new GregorianCalendar(2014, 10, 30).getTime, "second") - - val m3 = new TextMessage(AddressTest.a4, AddressTest.a2, - new GregorianCalendar(2014, 10, 31).getTime, "third") - -} - -class MessageTest extends AndroidTestCase { - - def testSerialize(): Unit = { - Set(m1, m2, m3).foreach { m => - val pis = new PipedInputStream() - val pos = new PipedOutputStream(pis) - val bytes = m.write(Array[Byte]()) - val (msg, _) = Message.read(bytes) - assertEquals(m, msg) - } - } - - def testOrder(): Unit = { - var messages = new TreeSet[Message]()(Message.Ordering) - messages += MessageTest.m1 - messages += MessageTest.m2 - assertEquals(MessageTest.m1, messages.firstKey) - - messages = new TreeSet[Message]()(Message.Ordering) - messages += MessageTest.m2 - messages += MessageTest.m3 - assertEquals(MessageTest.m2, messages.firstKey) - } - -} diff --git a/app/src/androidTest/scala/com/nutomic/ensichat/util/DatabaseTest.scala b/app/src/androidTest/scala/com/nutomic/ensichat/util/DatabaseTest.scala index ecec1c9..853eded 100644 --- a/app/src/androidTest/scala/com/nutomic/ensichat/util/DatabaseTest.scala +++ b/app/src/androidTest/scala/com/nutomic/ensichat/util/DatabaseTest.scala @@ -9,7 +9,7 @@ import android.database.sqlite.SQLiteDatabase import android.test.AndroidTestCase import android.test.mock.MockContext import com.nutomic.ensichat.aodvv2.AddressTest -import com.nutomic.ensichat.messages.MessageTest +import com.nutomic.ensichat.aodvv2.MessageTest._ import junit.framework.Assert._ class DatabaseTest extends AndroidTestCase { @@ -27,9 +27,9 @@ class DatabaseTest extends AndroidTestCase { private lazy val Database = new Database(new TestContext(getContext)) override def setUp(): Unit = { - Database.addMessage(MessageTest.m1) - Database.addMessage(MessageTest.m2) - Database.addMessage(MessageTest.m3) + Database.addMessage(m1) + Database.addMessage(m2) + Database.addMessage(m3) } override def tearDown(): Unit = { @@ -38,22 +38,22 @@ class DatabaseTest extends AndroidTestCase { } def testMessageCount(): Unit = { - val msg1 = Database.getMessages(MessageTest.m1.sender, 1) + val msg1 = Database.getMessages(m1.Header.Origin, 1) assertEquals(1, msg1.size) - val msg2 = Database.getMessages(MessageTest.m1.sender, 3) + val msg2 = Database.getMessages(m1.Header.Origin, 3) assertEquals(2, msg2.size) } def testMessageOrder(): Unit = { - val msg = Database.getMessages(MessageTest.m1.receiver, 1) - assertTrue(msg.contains(MessageTest.m3)) + val msg = Database.getMessages(m1.Header.Target, 1) + assertTrue(msg.contains(m3)) } def testMessageSelect(): Unit = { - val msg = Database.getMessages(MessageTest.m1.receiver, 2) - assertTrue(msg.contains(MessageTest.m1)) - assertTrue(msg.contains(MessageTest.m3)) + val msg = Database.getMessages(m1.Header.Target, 2) + assertTrue(msg.contains(m1)) + assertTrue(msg.contains(m3)) } def testAddContact(): Unit = { diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml index 363cb03..70712bb 100644 --- a/app/src/main/res/xml/settings.xml +++ b/app/src/main/res/xml/settings.xml @@ -5,7 +5,7 @@ diff --git a/app/src/main/scala/com/nutomic/ensichat/activities/AddContactsActivity.scala b/app/src/main/scala/com/nutomic/ensichat/activities/AddContactsActivity.scala index 8934500..ebe57d7 100644 --- a/app/src/main/scala/com/nutomic/ensichat/activities/AddContactsActivity.scala +++ b/app/src/main/scala/com/nutomic/ensichat/activities/AddContactsActivity.scala @@ -1,7 +1,5 @@ package com.nutomic.ensichat.activities -import java.util.Date - import android.app.AlertDialog import android.content.DialogInterface.OnClickListener import android.content.{Context, DialogInterface} @@ -12,10 +10,10 @@ import android.view._ import android.widget.AdapterView.OnItemClickListener import android.widget._ import com.nutomic.ensichat.R -import com.nutomic.ensichat.aodvv2.Address +import com.nutomic.ensichat.aodvv2.{Address, Message, RequestAddContact, ResultAddContact} import com.nutomic.ensichat.bluetooth.ChatService import com.nutomic.ensichat.bluetooth.ChatService.OnMessageReceivedListener -import com.nutomic.ensichat.messages.{Crypto, Message, RequestAddContactMessage, ResultAddContactMessage} +import com.nutomic.ensichat.messages.Crypto import com.nutomic.ensichat.util.{DevicesAdapter, IdenticonGenerator} import scala.collection.SortedSet @@ -92,7 +90,7 @@ class AddContactsActivity extends EnsiChatActivity with ChatService.OnNearbyCont return } - service.send(new RequestAddContactMessage(Crypto.getLocalAddress, address, new Date())) + service.sendTo(address, new RequestAddContact()) addDeviceDialog(address) } @@ -108,12 +106,10 @@ class AddContactsActivity extends EnsiChatActivity with ChatService.OnNearbyCont currentlyAdding += (address -> new AddContactInfo(currentlyAdding(address).localConfirmed, true)) addContactIfBothConfirmed(address) - service.send( - new ResultAddContactMessage(Crypto.getLocalAddress, address, new Date(), true)) + service.sendTo(address, new ResultAddContact(true)) case DialogInterface.BUTTON_NEGATIVE => // Local user denied adding contact, send info to other device. - service.send( - new ResultAddContactMessage(Crypto.getLocalAddress, address, new Date(), false)) + service.sendTo(address, new ResultAddContact(false)) } } @@ -137,27 +133,28 @@ class AddContactsActivity extends EnsiChatActivity with ChatService.OnNearbyCont } /** - * Handles incoming [[RequestAddContactMessage]] and [[ResultAddContactMessage]] messages. + * Handles incoming [[RequestAddContact]] and [[ResultAddContact]] messages. * * These are only handled here and require user action, so contacts can only be added if * the user is in this activity. */ override def onMessageReceived(messages: SortedSet[Message]): Unit = { - messages.filter(_.receiver == Crypto.getLocalAddress) + messages.filter(_.Header.Target == Crypto.getLocalAddress) .foreach{ - case m: RequestAddContactMessage => - Log.i(Tag, "Remote device " + m.sender + " wants to add us as a contact, showing dialog") - addDeviceDialog(m.sender) - case m: ResultAddContactMessage => - if (m.Accepted) { - Log.i(Tag, "Remote device " + m.sender + " accepted us as a contact, updating state") - currentlyAdding += (m.sender -> - new AddContactInfo(true, currentlyAdding(m.sender).remoteConfirmed)) - addContactIfBothConfirmed(m.sender) + case m if m.Body.isInstanceOf[RequestAddContact] => + Log.i(Tag, "Remote device " + m.Header.Origin + " wants to add us as a contact, showing dialog") + addDeviceDialog(m.Header.Origin) + case m if m.Body.isInstanceOf[ResultAddContact] => + val origin = m.Header.Origin + if (m.Body.asInstanceOf[ResultAddContact].Accepted) { + Log.i(Tag, "Remote device " + origin + " accepted us as a contact, updating state") + currentlyAdding += (origin -> + new AddContactInfo(true, currentlyAdding(origin).remoteConfirmed)) + addContactIfBothConfirmed(origin) } else { - Log.i(Tag, "Remote device " + m.sender + " denied us as a contact, showing toast") + Log.i(Tag, "Remote device " + origin + " denied us as a contact, showing toast") Toast.makeText(this, R.string.contact_not_added, Toast.LENGTH_LONG).show() - currentlyAdding -= m.sender + currentlyAdding -= origin } case _ => } diff --git a/app/src/main/scala/com/nutomic/ensichat/aodvv2/ConnectionInfo.scala b/app/src/main/scala/com/nutomic/ensichat/aodvv2/ConnectionInfo.scala index 83d6669..ecd55e9 100644 --- a/app/src/main/scala/com/nutomic/ensichat/aodvv2/ConnectionInfo.scala +++ b/app/src/main/scala/com/nutomic/ensichat/aodvv2/ConnectionInfo.scala @@ -34,11 +34,22 @@ object ConnectionInfo { */ class ConnectionInfo(val key: PublicKey) extends MessageBody { + override def Type = ConnectionInfo.Type + override def write: Array[Byte] = { - val b = ByteBuffer.allocate(4 + key.getEncoded.length) + val b = ByteBuffer.allocate(length) BufferUtils.putUnsignedInt(b, key.getEncoded.length) b.put(key.getEncoded) b.array() } + override def equals(a: Any): Boolean = a match { + case o: ConnectionInfo => key == o.key + case _ => false + } + + override def toString = "ConnectionInfo(key=" + key + ")" + + override def length = 4 + key.getEncoded.length + } diff --git a/app/src/main/scala/com/nutomic/ensichat/aodvv2/CryptoData.scala b/app/src/main/scala/com/nutomic/ensichat/aodvv2/CryptoData.scala new file mode 100644 index 0000000..3172e39 --- /dev/null +++ b/app/src/main/scala/com/nutomic/ensichat/aodvv2/CryptoData.scala @@ -0,0 +1,67 @@ +package com.nutomic.ensichat.aodvv2 + +import java.nio.ByteBuffer +import java.util.Arrays + +import com.nutomic.ensichat.util.BufferUtils + +object CryptoData { + + /** + * Constructs [[CryptoData]] instance from byte array. + */ + def read(array: Array[Byte]): (CryptoData, Array[Byte]) = { + val b = ByteBuffer.wrap(array) + val signatureLength = BufferUtils.getUnsignedInt(b).toInt + val signature = new Array[Byte](signatureLength) + b.get(signature, 0, signatureLength) + + val keyLength = BufferUtils.getUnsignedInt(b).toInt + val key = + if (keyLength != 0) { + val key = new Array[Byte](keyLength) + b.get(key, 0, keyLength) + Some(key) + } + else None + + val remaining = new Array[Byte](b.remaining()) + b.get(remaining, 0, b.remaining()) + (new CryptoData(Some(signature), key), remaining) + + } + +} + +/** + * Holds the signature and (optional) key that are stored in a message. + */ +class CryptoData(val Signature: Option[Array[Byte]], val Key: Option[Array[Byte]]) { + + override def equals(a: Any): Boolean = a match { + case o: CryptoData => + Arrays.equals(Signature.orNull, o.Signature.orNull) && Arrays.equals(Key.orNull, o.Key.orNull) + case _ => false + } + + /** + * Writes this object into a new byte array. + * @return + */ + def write: Array[Byte] = { + val b = ByteBuffer.allocate(length) + BufferUtils.putUnsignedInt(b, Signature.get.length) + b.put(Signature.get) + BufferUtils.putUnsignedInt(b, keyLength) + if (Key.nonEmpty) b.put(Key.get) + b.array() + } + + def length = 8 + Signature.get.length + keyLength + + private def keyLength = if (Key.isDefined) Key.get.length else 0 + + override def toString = "CryptoData(Signature.length=" + Signature.foreach(_.length) + + ", Key.length=" + Key.foreach(_.length) + ")" + +} \ No newline at end of file diff --git a/app/src/main/scala/com/nutomic/ensichat/aodvv2/Data.scala b/app/src/main/scala/com/nutomic/ensichat/aodvv2/Data.scala deleted file mode 100644 index eadaac9..0000000 --- a/app/src/main/scala/com/nutomic/ensichat/aodvv2/Data.scala +++ /dev/null @@ -1,37 +0,0 @@ -package com.nutomic.ensichat.aodvv2 - -import java.nio.ByteBuffer - -import com.nutomic.ensichat.util.BufferUtils - -object Data { - - val Type = 255 - - /** - * Constructs [[Data]] object from byte array. - */ - def read(array: Array[Byte]): Data = { - val b = ByteBuffer.wrap(array) - val length = BufferUtils.getUnsignedInt(b).toInt - val data = new Array[Byte](length) - b.get(data, 0, length) - new Data(data) - } - -} - -/** - * Container for [[com.nutomic.ensichat.messages.Message]] objects. - */ -@Deprecated -class Data(val data: Array[Byte]) extends MessageBody { - - override def write: Array[Byte] = { - val b = ByteBuffer.allocate(4 + data.length) - BufferUtils.putUnsignedInt(b, data.length) - b.put(data) - b.array() - } - -} diff --git a/app/src/main/scala/com/nutomic/ensichat/aodvv2/EncryptedBody.scala b/app/src/main/scala/com/nutomic/ensichat/aodvv2/EncryptedBody.scala new file mode 100644 index 0000000..956f558 --- /dev/null +++ b/app/src/main/scala/com/nutomic/ensichat/aodvv2/EncryptedBody.scala @@ -0,0 +1,15 @@ +package com.nutomic.ensichat.aodvv2 + +/** + * Represents the data in an encrypted message body. + */ +class EncryptedBody(val Data: Array[Byte]) extends MessageBody { + + override def Type = -1 + + def write = Data + + override def toString = "EncryptedBody(Data.length=" + Data.length + ")" + + override def length = Data.length +} diff --git a/app/src/main/scala/com/nutomic/ensichat/aodvv2/Message.scala b/app/src/main/scala/com/nutomic/ensichat/aodvv2/Message.scala new file mode 100644 index 0000000..d40e66d --- /dev/null +++ b/app/src/main/scala/com/nutomic/ensichat/aodvv2/Message.scala @@ -0,0 +1,50 @@ +package com.nutomic.ensichat.aodvv2 + +import java.io.InputStream + +object Message { + + /** + * Orders messages by date, oldest messages first. + */ + val Ordering = new Ordering[Message] { + override def compare(m1: Message, m2: Message) = m1.Header.Time.compareTo(m2.Header.Time) + } + + def read(stream: InputStream): Message = { + val headerBytes = new Array[Byte](MessageHeader.Length) + stream.read(headerBytes, 0, MessageHeader.Length) + val header = MessageHeader.read(headerBytes) + + val contentLength = (header.Length - MessageHeader.Length).toInt + val contentBytes = new Array[Byte](contentLength) + stream.read(contentBytes, 0, contentLength) + + val (crypto, remaining) = CryptoData.read(contentBytes) + + val body = + header.MessageType match { + case ConnectionInfo.Type => ConnectionInfo.read(remaining) + case _ => new EncryptedBody(remaining) + } + + new Message(header, crypto, body) + } + +} + +class Message(val Header: MessageHeader, val Crypto: CryptoData, val Body: MessageBody) { + + def this(header: MessageHeader, body: MessageBody) = + this(header, new CryptoData(None, None), body) + + def write = Header.write(Body.length + Crypto.length) ++ Crypto.write ++ Body.write + + override def toString = "Message(Header=" + Header + ", Body=" + Body + ", Crypto=" + Crypto + ")" + + override def equals(a: Any): Boolean = a match { + case o: Message => Header == o.Header && Body == o.Body && Crypto == o.Crypto + case _ => false + } + +} \ No newline at end of file diff --git a/app/src/main/scala/com/nutomic/ensichat/aodvv2/MessageBody.scala b/app/src/main/scala/com/nutomic/ensichat/aodvv2/MessageBody.scala index 8850994..2564489 100644 --- a/app/src/main/scala/com/nutomic/ensichat/aodvv2/MessageBody.scala +++ b/app/src/main/scala/com/nutomic/ensichat/aodvv2/MessageBody.scala @@ -1,14 +1,19 @@ package com.nutomic.ensichat.aodvv2 +import android.util.Log + /** * Holds the actual message content. */ abstract class MessageBody { + def Type: Int + /** * Writes the message contents to a byte array. - * @return */ def write: Array[Byte] + def length: Int + } diff --git a/app/src/main/scala/com/nutomic/ensichat/aodvv2/MessageHeader.scala b/app/src/main/scala/com/nutomic/ensichat/aodvv2/MessageHeader.scala index a69e0a1..293b8d8 100644 --- a/app/src/main/scala/com/nutomic/ensichat/aodvv2/MessageHeader.scala +++ b/app/src/main/scala/com/nutomic/ensichat/aodvv2/MessageHeader.scala @@ -7,11 +7,11 @@ import com.nutomic.ensichat.util.BufferUtils object MessageHeader { - val Length = 16 + 2 * Address.Length + val Length = 20 + 2 * Address.Length val DefaultHopLimit = 20 - val Version = 3 + val Version = 0 class ParseMessageException(detailMessage: String) extends RuntimeException(detailMessage) { } @@ -38,7 +38,7 @@ object MessageHeader { val seqNum = BufferUtils.getUnsignedShort(b) val metric = BufferUtils.getUnsignedByte(b) - new MessageHeader(messageType, hopLimit, time, origin, target, seqNum, metric, length, hopCount) + new MessageHeader(messageType, hopLimit, origin, target, seqNum, metric, time, length, hopCount) } } @@ -48,27 +48,26 @@ object MessageHeader { */ class MessageHeader(val MessageType: Int, val HopLimit: Int, - val Time: Date, val Origin: Address, val Target: Address, val SequenceNumber: Int, val Metric: Int, + val Time: Date = new Date(), val Length: Long = -1, val HopCount: Int = 0) { /** * Writes the header to byte array. */ - def write(body: MessageBody): Array[Byte] = { + def write(contentLength: Int): Array[Byte] = { val b = ByteBuffer.allocate(MessageHeader.Length) - val bodyBytes = body.write val versionAndType = (MessageHeader.Version << 12) | MessageType BufferUtils.putUnsignedShort(b, versionAndType) BufferUtils.putUnsignedByte(b, HopLimit) BufferUtils.putUnsignedByte(b, HopCount) - BufferUtils.putUnsignedInt(b, MessageHeader.Length + bodyBytes.length) + BufferUtils.putUnsignedInt(b, MessageHeader.Length + contentLength) b.putInt((Time.getTime / 1000).toInt) b.put(Origin.Bytes) b.put(Target.Bytes) @@ -77,7 +76,7 @@ class MessageHeader(val MessageType: Int, BufferUtils.putUnsignedByte(b, Metric) BufferUtils.putUnsignedByte(b, 0) - b.array() ++ bodyBytes + b.array() } override def equals(a: Any): Boolean = a match { @@ -89,9 +88,8 @@ class MessageHeader(val MessageType: Int, Target == o.Target && SequenceNumber == o.SequenceNumber && Metric == o.Metric && - // Don't compare length as it may be unknown (when header was just created without a body). - //Length == o.Length && HopCount == o.HopCount + // Don't compare length as it may be unknown (when header was just created without a body). case _ => false } diff --git a/app/src/main/scala/com/nutomic/ensichat/aodvv2/RequestAddContact.scala b/app/src/main/scala/com/nutomic/ensichat/aodvv2/RequestAddContact.scala new file mode 100644 index 0000000..1434597 --- /dev/null +++ b/app/src/main/scala/com/nutomic/ensichat/aodvv2/RequestAddContact.scala @@ -0,0 +1,34 @@ +package com.nutomic.ensichat.aodvv2 + +import java.nio.ByteBuffer + +object RequestAddContact { + + val Type = 4 + + /** + * Constructs [[RequestAddContact]] instance from byte array. + */ + def read(array: Array[Byte]): RequestAddContact = { + new RequestAddContact() + } + +} + +/** + * Sent when the user initiates adding another device as a contact. + */ +class RequestAddContact extends MessageBody { + + override def Type = RequestAddContact.Type + + override def write: Array[Byte] = { + val b = ByteBuffer.allocate(length) + b.array() + } + + override def toString = "RequestAddContact()" + + override def length = 4 + +} diff --git a/app/src/main/scala/com/nutomic/ensichat/aodvv2/ResultAddContact.scala b/app/src/main/scala/com/nutomic/ensichat/aodvv2/ResultAddContact.scala new file mode 100644 index 0000000..191f868 --- /dev/null +++ b/app/src/main/scala/com/nutomic/ensichat/aodvv2/ResultAddContact.scala @@ -0,0 +1,41 @@ +package com.nutomic.ensichat.aodvv2 + +import java.nio.ByteBuffer + +import com.nutomic.ensichat.util.BufferUtils + +object ResultAddContact { + + val Type = 5 + + /** + * Constructs [[ResultAddContact]] instance from byte array. + */ + def read(array: Array[Byte]): ResultAddContact = { + val b = ByteBuffer.wrap(array) + val first = BufferUtils.getUnsignedByte(b) + val accepted = (first & 0x80) != 0 + new ResultAddContact(accepted) + } + +} + +/** + * Contains the result of a [[RequestAddContact]] message. + */ +class ResultAddContact(val Accepted: Boolean) extends MessageBody { + + override def Type = ResultAddContact.Type + + override def write: Array[Byte] = { + val b = ByteBuffer.allocate(length) + BufferUtils.putUnsignedByte(b, if (Accepted) 0x80 else 0) + (0 to 1).foreach(_ => BufferUtils.putUnsignedByte(b, 0)) + b.array() + } + + override def toString = "ResultAddContact(Accepted=" + Accepted + ")" + + override def length = 4 + +} diff --git a/app/src/main/scala/com/nutomic/ensichat/aodvv2/Text.scala b/app/src/main/scala/com/nutomic/ensichat/aodvv2/Text.scala new file mode 100644 index 0000000..0b7e04d --- /dev/null +++ b/app/src/main/scala/com/nutomic/ensichat/aodvv2/Text.scala @@ -0,0 +1,50 @@ +package com.nutomic.ensichat.aodvv2 + +import java.nio.ByteBuffer + +import com.nutomic.ensichat.util.BufferUtils + +object Text { + + val Type = 6 + + val Charset = "UTF-8" + + /** + * Constructs [[Text]] instance from byte array. + */ + def read(array: Array[Byte]): Text = { + val b = ByteBuffer.wrap(array) + val length = BufferUtils.getUnsignedInt(b).toInt + val bytes = new Array[Byte](length) + b.get(bytes, 0, length) + new Text(new String(bytes, Text.Charset)) + } + +} + +/** + * Holds a plain text message. + */ +class Text(val text: String) extends MessageBody { + + override def Type = Text.Type + + override def write: Array[Byte] = { + val bytes = text.getBytes(Text.Charset) + val b = ByteBuffer.allocate(4 + bytes.length) + BufferUtils.putUnsignedInt(b, bytes.length) + b.put(bytes) + b.array() + } + + override def equals(a: Any): Boolean = a match { + case o: Text => text == o.text + case _ => false + } + + override def toString = "Text(" + text + ")" + + override def length = write.length + +} diff --git a/app/src/main/scala/com/nutomic/ensichat/bluetooth/ChatService.scala b/app/src/main/scala/com/nutomic/ensichat/bluetooth/ChatService.scala index 5352412..daee8b7 100644 --- a/app/src/main/scala/com/nutomic/ensichat/bluetooth/ChatService.scala +++ b/app/src/main/scala/com/nutomic/ensichat/bluetooth/ChatService.scala @@ -1,6 +1,6 @@ package com.nutomic.ensichat.bluetooth -import java.util.{Date, UUID} +import java.util.UUID import android.app.Service import android.bluetooth.{BluetoothAdapter, BluetoothDevice, BluetoothSocket} @@ -14,7 +14,6 @@ import com.nutomic.ensichat.aodvv2._ import com.nutomic.ensichat.bluetooth.ChatService.{OnMessageReceivedListener, OnNearbyContactsChangedListener} import com.nutomic.ensichat.messages._ import com.nutomic.ensichat.util.Database -import org.msgpack.ScalaMessagePack import scala.collection.SortedSet import scala.collection.immutable.{HashMap, HashSet, TreeSet} @@ -77,6 +76,8 @@ class ChatService extends Service { private lazy val Crypto = new Crypto(this) + private var discovered = Set[Device]() + private val AddressDeviceMap = HashBiMap.create[Address, Device.ID]() /** @@ -89,6 +90,8 @@ class ChatService extends Service { registerReceiver(DeviceDiscoveredReceiver, new IntentFilter(BluetoothDevice.ACTION_FOUND)) registerReceiver(BluetoothStateReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)) + registerReceiver(DiscoveryFinishedReceiver, + new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) if (bluetoothAdapter.isEnabled) { startBluetoothConnections() } @@ -116,6 +119,7 @@ class ChatService extends Service { cancelDiscovery = true unregisterReceiver(DeviceDiscoveredReceiver) unregisterReceiver(BluetoothStateReceiver) + unregisterReceiver(DiscoveryFinishedReceiver) } /** @@ -126,12 +130,12 @@ class ChatService extends Service { return if (!bluetoothAdapter.isDiscovering) { - Log.v(Tag, "Running discovery") + Log.v(Tag, "Starting discovery") bluetoothAdapter.startDiscovery() } val scanInterval = PreferenceManager.getDefaultSharedPreferences(this) - .getString("scan_interval_seconds", "5").toInt * 1000 + .getString("scan_interval_seconds", "15").toInt * 1000 MainHandler.postDelayed(new Runnable { override def run(): Unit = discover() }, scanInterval) @@ -142,10 +146,21 @@ class ChatService extends Service { */ private val DeviceDiscoveredReceiver = new BroadcastReceiver() { override def onReceive(context: Context, intent: Intent) { - val device: Device = - new Device(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE), false) - devices += (device.Id -> device) - new ConnectThread(device, onConnectionChanged).start() + discovered += new Device(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE), false) + } + } + + /** + * Iniates the actual connection to discovered devices. + */ + private val DiscoveryFinishedReceiver = new BroadcastReceiver() { + override def onReceive(context: Context, intent: Intent): Unit = { + discovered.filterNot(d => connections.keySet.contains(d.Id)) + .foreach { d => + new ConnectThread(d, onConnectionChanged).start() + devices += (d.Id -> d) + } + discovered = Set[Device]() } } @@ -200,7 +215,7 @@ class ChatService extends Service { def onConnectionChanged(device: Device, socket: BluetoothSocket): Unit = { devices += (device.Id -> device) - if (device.Connected) { + if (device.Connected && !connections.keySet.contains(device.Id)) { connections += (device.Id -> new TransferThread(device, socket, this, Crypto, onReceiveMessage)) connections(device.Id).start() @@ -219,79 +234,75 @@ class ChatService extends Service { } /** - * Sends message to the device specified as receiver, + * Sends a new message to the given target address. */ - def send(message: Message): Unit = { - assert(message.sender == Crypto.getLocalAddress, "Message must be sent from local device") - - if (!AddressDeviceMap.containsKey(message.receiver)) { - Log.w(Tag, "Receiver " + message.receiver + " is not connected, ignoring message") + def sendTo(target: Address, body: MessageBody): Unit = { + if (!AddressDeviceMap.containsKey(target)) { + Log.w(Tag, "Receiver " + target + " is not connected, ignoring message") return } - val header = new MessageHeader(Data.Type, MessageHeader.DefaultHopLimit, - new Date(), Crypto.getLocalAddress, message.receiver, 0, 0) + val header = new MessageHeader(body.Type, MessageHeader.DefaultHopLimit, + Crypto.getLocalAddress, target, 0, 0) - val plain = message.write(Crypto.calculateSignature(message)) - val (encrypted, key) = Crypto.encrypt(message.receiver, plain) - val packer = new ScalaMessagePack().createBufferPacker() - packer - .write(encrypted) - .write(key) - val body = new Data(packer.toByteArray) - - connections.apply(AddressDeviceMap.get(message.receiver)).send(header, body) - Database.addMessage(message) + val msg = new Message(header, body) + val encrypted = Crypto.encrypt(Crypto.sign(msg)) + connections.apply(AddressDeviceMap.get(target)).send(encrypted) + Database.addMessage(msg) + callMessageReceivedListeners(msg) } /** * Saves the message to database and sends it to registered listeners. * - * If you want to send a new message, use [[send]]. + * If you want to send a new message, use [[sendTo]]. * * Messages must always be sent between local device and a contact. * - * NOTE: Messages sent from the local node using [[send]] are also passed through this method. + * NOTE: Messages sent from the local node using [[sendTo]] are also passed through this method. */ - private def onReceiveMessage(header: MessageHeader, body: MessageBody, device: Device.ID): Unit = { - assert(header.Origin != Crypto.getLocalAddress) + private def onReceiveMessage(message: Message, device: Device.ID): Unit = { + assert(message.Header.Origin != Crypto.getLocalAddress) - body match { + message.Body match { case info: ConnectionInfo => - if (header.Origin == Crypto.getLocalAddress) + if (message.Header.Origin == Crypto.getLocalAddress) return onNeighborConnected(info, device) - case data: Data => - val up = new ScalaMessagePack().createBufferUnpacker(data.data) - val encrypted = up.readByteArray() - val key = up.readByteArray() - val (message, signature) = Message.read(Crypto.decrypt(encrypted, key)) - - if (!Crypto.isValidSignature(message, signature)) { - Log.i(Tag, "Dropping message with invalid signature from " + header.Origin) + case _ => + val decrypted = Crypto.decrypt(message) + if (!Crypto.verify(decrypted)) { + Log.i(Tag, "Dropping message with invalid signature from " + message.Header.Origin) return } - Database.addMessage(message) - MainHandler.post(new Runnable { - override def run(): Unit = { - messageListeners.foreach(l => - if (l.get != null) - l.apply().onMessageReceived(new TreeSet[Message]()(Message.Ordering) + message) - else - messageListeners -= l) - } - }) + callMessageReceivedListeners(decrypted) + Database.addMessage(decrypted) } } + /** + * Calls all [[OnMessageReceivedListener]]s with the new message. + */ + private def callMessageReceivedListeners(message: Message): Unit = { + MainHandler.post(new Runnable { + override def run(): Unit = { + messageListeners.foreach(l => + if (l.get != null) + l.apply().onMessageReceived(new TreeSet[Message]()(Message.Ordering) + message) + else + messageListeners -= l) + } + }) + } + /** * Called when a [[ConnectionInfo]] message from a new neighbor is received. */ private def onNeighborConnected(info: ConnectionInfo, device: Device.ID): Unit = { val sender = Crypto.calculateAddress(info.key) if (sender == Address.Broadcast || sender == Address.Null) { - connections(device).close() + Log.i(Tag, "Received ConnectionInfo message with invalid sender " + sender + ", ignoring") return } diff --git a/app/src/main/scala/com/nutomic/ensichat/bluetooth/ConnectThread.scala b/app/src/main/scala/com/nutomic/ensichat/bluetooth/ConnectThread.scala index f3e9d4b..967574d 100644 --- a/app/src/main/scala/com/nutomic/ensichat/bluetooth/ConnectThread.scala +++ b/app/src/main/scala/com/nutomic/ensichat/bluetooth/ConnectThread.scala @@ -22,7 +22,7 @@ class ConnectThread(device: Device, onConnected: (Device, BluetoothSocket) => Un Socket.connect() } catch { case e: IOException => - Log.w(Tag, "Failed to connect to " + device.toString, e) + Log.v(Tag, "Failed to connect to " + device.toString, e) try { Socket.close() } catch { diff --git a/app/src/main/scala/com/nutomic/ensichat/bluetooth/ListenThread.scala b/app/src/main/scala/com/nutomic/ensichat/bluetooth/ListenThread.scala index 0fd95bd..750a134 100644 --- a/app/src/main/scala/com/nutomic/ensichat/bluetooth/ListenThread.scala +++ b/app/src/main/scala/com/nutomic/ensichat/bluetooth/ListenThread.scala @@ -41,6 +41,7 @@ class ListenThread(name: String, adapter: BluetoothAdapter, } val device: Device = new Device(socket.getRemoteDevice, true) + Log.i(Tag, "Incoming connection from " + device.toString) onConnected(device, socket) } } diff --git a/app/src/main/scala/com/nutomic/ensichat/bluetooth/TransferThread.scala b/app/src/main/scala/com/nutomic/ensichat/bluetooth/TransferThread.scala index 4761de1..2126efe 100644 --- a/app/src/main/scala/com/nutomic/ensichat/bluetooth/TransferThread.scala +++ b/app/src/main/scala/com/nutomic/ensichat/bluetooth/TransferThread.scala @@ -1,7 +1,6 @@ package com.nutomic.ensichat.bluetooth import java.io._ -import java.util.Date import android.bluetooth.BluetoothSocket import android.util.Log @@ -18,7 +17,7 @@ import com.nutomic.ensichat.messages.Crypto * @param onReceive Called when a message was received from the other device. */ class TransferThread(device: Device, socket: BluetoothSocket, service: ChatService, - crypto: Crypto, onReceive: (MessageHeader, MessageBody, Device.ID) => Unit) + crypto: Crypto, onReceive: (Message, Device.ID) => Unit) extends Thread { private val Tag: String = "TransferThread" @@ -44,26 +43,16 @@ class TransferThread(device: Device, socket: BluetoothSocket, service: ChatServi override def run(): Unit = { Log.i(Tag, "Starting data transfer with " + device.toString) - send(new MessageHeader(ConnectionInfo.Type, ConnectionInfo.HopLimit, new Date(), Address.Null, - Address.Null, 0, 0), new ConnectionInfo(crypto.getLocalPublicKey)) + send(crypto.sign(new Message(new MessageHeader(ConnectionInfo.Type, ConnectionInfo.HopLimit, + Address.Null, Address.Null, 0, 0), new ConnectionInfo(crypto.getLocalPublicKey)))) while (socket.isConnected) { try { - val headerBytes = new Array[Byte](MessageHeader.Length) - InStream.read(headerBytes, 0, MessageHeader.Length) - val header = MessageHeader.read(headerBytes) - val bodyLength = (header.Length - MessageHeader.Length).toInt + if (InStream.available() > 0) { + val msg = Message.read(InStream) - val bodyBytes = new Array[Byte](bodyLength) - InStream.read(bodyBytes, 0, bodyLength) - - val body = - header.MessageType match { - case ConnectionInfo.Type => ConnectionInfo.read(bodyBytes) - case Data.Type => Data.read(bodyBytes) - } - - onReceive(header, body, device.Id) + onReceive(msg, device.Id) + } } catch { case e: RuntimeException => Log.i(Tag, "Received invalid message", e) @@ -73,11 +62,12 @@ class TransferThread(device: Device, socket: BluetoothSocket, service: ChatServi } } service.onConnectionChanged(new Device(device.bluetoothDevice, false), null) + Log.i(Tag, "Neighbor " + device + " has disconnected") } - def send(header: MessageHeader, body: MessageBody): Unit = { + def send(msg: Message): Unit = { try { - OutStream.write(header.write(body)) + OutStream.write(msg.write) } catch { case e: IOException => Log.e(Tag, "Failed to write message", e) } @@ -85,6 +75,7 @@ class TransferThread(device: Device, socket: BluetoothSocket, service: ChatServi def close(): Unit = { try { + Log.i(Tag, "Closing connection to " + device) socket.close() } catch { case e: IOException => Log.e(Tag, "Failed to close socket", e); diff --git a/app/src/main/scala/com/nutomic/ensichat/fragments/ChatFragment.scala b/app/src/main/scala/com/nutomic/ensichat/fragments/ChatFragment.scala index 49669cf..85423e0 100644 --- a/app/src/main/scala/com/nutomic/ensichat/fragments/ChatFragment.scala +++ b/app/src/main/scala/com/nutomic/ensichat/fragments/ChatFragment.scala @@ -1,7 +1,5 @@ package com.nutomic.ensichat.fragments -import java.util.Date - import android.app.ListFragment import android.os.Bundle import android.view.View.OnClickListener @@ -11,10 +9,9 @@ import android.widget.TextView.OnEditorActionListener import android.widget._ import com.nutomic.ensichat.R import com.nutomic.ensichat.activities.EnsiChatActivity -import com.nutomic.ensichat.aodvv2.Address +import com.nutomic.ensichat.aodvv2.{Address, Message, Text} import com.nutomic.ensichat.bluetooth.ChatService import com.nutomic.ensichat.bluetooth.ChatService.OnMessageReceivedListener -import com.nutomic.ensichat.messages.{Crypto, Message, TextMessage} import com.nutomic.ensichat.util.MessagesAdapter import scala.collection.SortedSet @@ -43,7 +40,7 @@ class ChatFragment extends ListFragment with OnClickListener private var listView: ListView = _ - private var adapter: ArrayAdapter[TextMessage] = _ + private var adapter: ArrayAdapter[Message] = _ override def onActivityCreated(savedInstanceState: Bundle): Unit = { super.onActivityCreated(savedInstanceState) @@ -103,10 +100,8 @@ class ChatFragment extends ListFragment with OnClickListener case R.id.send => val text = messageText.getText.toString.trim if (!text.isEmpty) { - val message = - new TextMessage(Crypto.getLocalAddress(getActivity), address, new Date(), text.toString) - chatService.send(message) - adapter.add(message) + val message = new Text(text.toString) + chatService.sendTo(address, message) messageText.getText.clear() } } @@ -115,9 +110,8 @@ class ChatFragment extends ListFragment with OnClickListener * Displays new messages in UI. */ override def onMessageReceived(messages: SortedSet[Message]): Unit = { - messages.filter(m => m.sender == address || m.receiver == address) - .filter(_.isInstanceOf[TextMessage]) - .foreach(m => adapter.add(m.asInstanceOf[TextMessage])) + messages.filter(m => Set(m.Header.Origin, m.Header.Target).contains(address)) + .foreach(adapter.add) } /** diff --git a/app/src/main/scala/com/nutomic/ensichat/messages/Crypto.scala b/app/src/main/scala/com/nutomic/ensichat/messages/Crypto.scala index 515d345..3706823 100644 --- a/app/src/main/scala/com/nutomic/ensichat/messages/Crypto.scala +++ b/app/src/main/scala/com/nutomic/ensichat/messages/Crypto.scala @@ -9,7 +9,7 @@ import javax.crypto.{Cipher, CipherOutputStream, KeyGenerator, SecretKey} import android.content.Context import android.preference.PreferenceManager import android.util.Log -import com.nutomic.ensichat.aodvv2.Address +import com.nutomic.ensichat.aodvv2._ import com.nutomic.ensichat.messages.Crypto._ import com.nutomic.ensichat.util.PRNGFixes @@ -107,6 +107,7 @@ class Crypto(Context: Context) { * * @throws RuntimeException If the key does not exist. */ + @throws[RuntimeException] def getPublicKey(address: Address): PublicKey = { loadKey(address.toString, classOf[PublicKey]) } @@ -114,49 +115,32 @@ class Crypto(Context: Context) { /** * Adds a new public key for a remote device. * - * If a key for the device already exists, nothing is done. - * - * @param address The device to which the key belongs. - * @param key The new key to add. + * @throws RuntimeException If a this key */ + @throws[RuntimeException] def addPublicKey(address: Address, key: PublicKey): Unit = { - if (!havePublicKey(address)) { - saveKey(address.toString, key) - } else { - Log.i(Tag, "Already have key for " + address.toString + ", not overwriting") - } + if (havePublicKey(address)) + throw new RuntimeException("Already have key for " + address + ", not overwriting") + + saveKey(address.toString, key) } - /** - * Checks if the message was properly signed. - * - * This is done by signing the output of [[Message.write()]] called with an empty signature. - * - * @param message The message to verify. - * @param signature The signature that was sent - * @return True if the signature is valid. - */ - def isValidSignature(message: Message, signature: Array[Byte], key: PublicKey = null): Boolean = { - val publicKey = - if (key != null) key - else loadKey(message.sender.toString, classOf[PublicKey]) - val sig = Signature.getInstance(SignAlgorithm) - sig.initVerify(publicKey) - sig.update(message.write(Array[Byte]())) - sig.verify(signature) - } - - /** - * Returns a cryptographic signature for the given message (using local private key). - * - * This is done by signing the output of [[Message.write()]] called with an empty signature. - */ - def calculateSignature(message: Message): Array[Byte] = { + def sign(msg: Message): Message = { val sig = Signature.getInstance(SignAlgorithm) val key = loadKey(PrivateKeyAlias, classOf[PrivateKey]) sig.initSign(key) - sig.update(message.write(Array[Byte]())) - sig.sign + sig.update(msg.Body.write) + new Message(msg.Header, new CryptoData(Option(sig.sign), None), msg.Body) + } + + def verify(msg: Message, key: PublicKey = null): Boolean = { + val publicKey = + if (key != null) key + else loadKey(msg.Header.Origin.toString, classOf[PublicKey]) + val sig = Signature.getInstance(SignAlgorithm) + sig.initVerify(publicKey) + sig.update(msg.Body.write) + sig.verify(msg.Crypto.Signature.get) } /** @@ -239,50 +223,42 @@ class Crypto(Context: Context) { */ private def keyFolder = new File(Context.getFilesDir, "keys") - /** - * Encrypts data for the given receiver. - * - * @param receiver The device that should be able to decrypt this message. - * @param data The message to encrypt. - * @param key Optional RSA public key to use for encryption. - * @return Pair of AES encrypted data and RSA encrypted AES key. - */ - def encrypt(receiver: Address, data: Array[Byte], key: PublicKey = null): - (Array[Byte], Array[Byte]) = { + def encrypt(msg: Message, key: PublicKey = null): Message = { + assert(msg.Crypto.Signature.isDefined, "Message must be signed before encryption") + // Symmetric encryption of data val secretKey = makeSecretKey() val symmetricCipher = Cipher.getInstance(SymmetricCipherAlgorithm) symmetricCipher.init(Cipher.ENCRYPT_MODE, secretKey) - val encryptedData = copyThroughCipher(symmetricCipher, data) + val encrypted = new EncryptedBody(copyThroughCipher(symmetricCipher, msg.Body.write)) // Asymmetric encryption of secret key val publicKey = if (key != null) key - else loadKey(receiver.toString, classOf[PublicKey]) + else loadKey(msg.Header.Target.toString, classOf[PublicKey]) val asymmetricCipher = Cipher.getInstance(KeyAlgorithm) asymmetricCipher.init(Cipher.WRAP_MODE, publicKey) - (encryptedData, asymmetricCipher.wrap(secretKey)) + new Message(msg.Header, + new CryptoData(msg.Crypto.Signature, Option(asymmetricCipher.wrap(secretKey))), encrypted) } - /** - * Decrypts the output of [[encrypt]]. - * - * @param data The AES encrypted data to decrypt. - * @param key The RSA encrypted AES key used to encrypt data. - * @return The plain text data. - */ - def decrypt(data: Array[Byte], key: Array[Byte]): Array[Byte] = { + def decrypt(msg: Message): Message = { // Asymmetric decryption of secret key val asymmetricCipher = Cipher.getInstance(KeyAlgorithm) asymmetricCipher.init(Cipher.UNWRAP_MODE, loadKey(PrivateKeyAlias, classOf[PrivateKey])) - val secretKey = asymmetricCipher.unwrap(key, SymmetricKeyAlgorithm, Cipher.SECRET_KEY) + val key = asymmetricCipher.unwrap(msg.Crypto.Key.get, SymmetricKeyAlgorithm, Cipher.SECRET_KEY) // Symmetric decryption of data val symmetricCipher = Cipher.getInstance(SymmetricCipherAlgorithm) - symmetricCipher.init(Cipher.DECRYPT_MODE, secretKey) - val dec = copyThroughCipher(symmetricCipher, data) - dec + symmetricCipher.init(Cipher.DECRYPT_MODE, key) + val decryped = copyThroughCipher(symmetricCipher, msg.Body.asInstanceOf[EncryptedBody].Data) + val body = msg.Header.MessageType match { + case RequestAddContact.Type => RequestAddContact.read(decryped) + case ResultAddContact.Type => ResultAddContact.read(decryped) + case Text.Type => Text.read(decryped) + } + new Message(msg.Header, msg.Crypto, body) } /** diff --git a/app/src/main/scala/com/nutomic/ensichat/messages/Message.scala b/app/src/main/scala/com/nutomic/ensichat/messages/Message.scala deleted file mode 100644 index 715c8ba..0000000 --- a/app/src/main/scala/com/nutomic/ensichat/messages/Message.scala +++ /dev/null @@ -1,122 +0,0 @@ -package com.nutomic.ensichat.messages - -import java.io.IOException -import java.util.{Date, Objects} - -import com.nutomic.ensichat.aodvv2.Address -import org.msgpack.ScalaMessagePack -import org.msgpack.packer.Packer - -object Message { - - /** - * Types of messages that can be transfered. - * - * There must be one type for each implementation and vice versa. - */ - object Type { - val Text = 1 - val RequestAddContact = 2 - val ResultAddContact = 3 - } - - /** - * Orders messages by date, oldest messages first. - */ - val Ordering = new Ordering[Message] { - override def compare(m1: Message, m2: Message) = m1.date.compareTo(m2.date) - } - - /** - * Reads a byte array that was written by [[Message.write]] into the correct - * message implementation.. - * - * @return Deserialized message and sits signature. - */ - def read(bytes: Array[Byte]): (Message, Array[Byte]) = { - val up = new ScalaMessagePack().createBufferUnpacker(bytes) - - @throws[IOException]("If the message can't be parsed") - @throws[RuntimeException]("If the message has an unknown type") - val messageType = up.readInt() - val sender = new Address(up.readByteArray()) - val receiver = new Address(up.readByteArray()) - val date = new Date(up.readLong()) - val sig = up.readByteArray() - (messageType match { - case Type.Text => TextMessage.read(sender, receiver, date, up) - case Type.RequestAddContact => RequestAddContactMessage.read(sender, receiver, date, up) - case Type.ResultAddContact => ResultAddContactMessage.read(sender, receiver, date, up) - case t => - throw new RuntimeException("Received message of unknown type " + t) - }, sig) - } - -} - -/** - * Message object that can be sent between remote devices. - * - * @param messageType One of [[Message.Type]]. - */ -@Deprecated -abstract class Message(val messageType: Int) { - - /** - * Device where the message was sent from. - */ - val sender: Address - - /** - * Device the message is addressed to. - */ - val receiver: Address - - /** - * Timestamp of message creation. - */ - val date: Date - - /** - * Writes this message and the given signature into byte array. - * - * Signature may not be null, but can be an empty array. - */ - def write(signature: Array[Byte]): Array[Byte] = { - val packer = new ScalaMessagePack().createBufferPacker() - packer.write(messageType) - .write(sender.Bytes) - .write(receiver.Bytes) - .write(date.getTime) - .write(signature) - doWrite(packer) - packer.toByteArray - } - - /** - * Serializes any extra data for implementing classes. - */ - protected def doWrite(packer: Packer): Unit - - /** - * Returns true if objects are equal. - * - * Implementations must provide their own implementation to check the result of this - * function and their own data. - */ - override def equals(a: Any): Boolean = a match { - case o: Message => sender == o.sender && receiver == o.receiver && date == o.date - case _ => false - } - - /** - * Returns a hash code for this object. - * - * Implementations must provide their own implementation to check the result of this - * function and their own data. - */ - override def hashCode: Int = Objects.hash(sender, receiver, date) - - override def toString: String - -} diff --git a/app/src/main/scala/com/nutomic/ensichat/messages/RequestAddContactMessage.scala b/app/src/main/scala/com/nutomic/ensichat/messages/RequestAddContactMessage.scala deleted file mode 100644 index 000a261..0000000 --- a/app/src/main/scala/com/nutomic/ensichat/messages/RequestAddContactMessage.scala +++ /dev/null @@ -1,30 +0,0 @@ -package com.nutomic.ensichat.messages - -import java.util.Date - -import com.nutomic.ensichat.activities.AddContactsActivity -import com.nutomic.ensichat.aodvv2.Address -import com.nutomic.ensichat.messages.Message._ -import org.msgpack.packer.Packer -import org.msgpack.unpacker.Unpacker - -object RequestAddContactMessage { - - def read(sender: Address, receiver: Address, date: Date, up: Unpacker) = - new RequestAddContactMessage(sender, receiver, date) - -} - -/** - * Message sent by [[AddContactsActivity]] to notify a device that it should be added as a contact. - */ -class RequestAddContactMessage(override val sender: Address, override val receiver: Address, - override val date: Date) extends Message(Type.RequestAddContact) { - - override def doWrite(packer: Packer) = { - } - - override def toString = "RequestAddContactMessage(" + sender.toString + ", " + receiver.toString + - ", " + date.toString + ")" - -} diff --git a/app/src/main/scala/com/nutomic/ensichat/messages/ResultAddContactMessage.scala b/app/src/main/scala/com/nutomic/ensichat/messages/ResultAddContactMessage.scala deleted file mode 100644 index cf8e04b..0000000 --- a/app/src/main/scala/com/nutomic/ensichat/messages/ResultAddContactMessage.scala +++ /dev/null @@ -1,37 +0,0 @@ -package com.nutomic.ensichat.messages - -import java.util.{Date, Objects} - -import com.nutomic.ensichat.activities.AddContactsActivity -import com.nutomic.ensichat.aodvv2.Address -import com.nutomic.ensichat.messages.Message._ -import org.msgpack.packer.Packer -import org.msgpack.unpacker.Unpacker - -object ResultAddContactMessage { - - def read(sender: Address, receiver: Address, date: Date, up: Unpacker) = - new ResultAddContactMessage(sender, receiver, date, up.readBoolean()) - -} - -/** - * Message sent by [[AddContactsActivity]] to tell a device whether the user confirmed adding it - * to contacts. - */ -class ResultAddContactMessage(override val sender: Address, override val receiver: Address, - override val date: Date, val Accepted: Boolean) - extends Message(Type.ResultAddContact) { - - override def doWrite(packer: Packer) = packer.write(Accepted) - - override def equals(a: Any) = - super.equals(a) && a.asInstanceOf[ResultAddContactMessage].Accepted == Accepted - - override def hashCode = - Objects.hash(super.hashCode: java.lang.Integer, Accepted: java.lang.Boolean) - - override def toString = "ResultAddContactMessage(" + sender.toString + ", " + receiver.toString + - ", " + date.toString + ", " + Accepted + ")" - -} diff --git a/app/src/main/scala/com/nutomic/ensichat/messages/TextMessage.scala b/app/src/main/scala/com/nutomic/ensichat/messages/TextMessage.scala deleted file mode 100644 index e9887c6..0000000 --- a/app/src/main/scala/com/nutomic/ensichat/messages/TextMessage.scala +++ /dev/null @@ -1,32 +0,0 @@ -package com.nutomic.ensichat.messages - -import java.util.{Date, Objects} - -import com.nutomic.ensichat.aodvv2.Address -import com.nutomic.ensichat.messages.Message._ -import org.msgpack.packer.Packer -import org.msgpack.unpacker.Unpacker - -object TextMessage { - - def read(sender: Address, receiver: Address, date: Date, up: Unpacker): TextMessage = - new TextMessage(sender, receiver, date, up.readString()) - -} - -/** - * Message that contains text. - */ -class TextMessage(override val sender: Address, override val receiver: Address, - override val date: Date, val text: String) extends Message(Type.Text) { - - override def doWrite(packer: Packer) = packer.write(text) - - override def equals(a: Any) = super.equals(a) && a.asInstanceOf[TextMessage].text == text - - override def hashCode = Objects.hash(super.hashCode: java.lang.Integer, text) - - override def toString = "TextMessage(" + sender.toString + ", " + receiver.toString + - ", " + date.toString + ", " + text + ")" - -} diff --git a/app/src/main/scala/com/nutomic/ensichat/util/BufferUtils.scala b/app/src/main/scala/com/nutomic/ensichat/util/BufferUtils.scala index 78ad102..df8cb51 100644 --- a/app/src/main/scala/com/nutomic/ensichat/util/BufferUtils.scala +++ b/app/src/main/scala/com/nutomic/ensichat/util/BufferUtils.scala @@ -2,6 +2,9 @@ package com.nutomic.ensichat.util import java.nio.ByteBuffer +/** + * Provides various helper methods for [[ByteBuffer]]. + */ object BufferUtils { def getUnsignedByte(bb: ByteBuffer): Short = (bb.get & 0xff).toShort @@ -25,6 +28,6 @@ object BufferUtils { b } - def toString(bb: ByteBuffer)= bb.array().slice(0, 4).map("%02X" format _).mkString + def toString(array: Array[Byte]) = array.map("%02X".format(_)).mkString } \ No newline at end of file diff --git a/app/src/main/scala/com/nutomic/ensichat/util/Database.scala b/app/src/main/scala/com/nutomic/ensichat/util/Database.scala index a464a3a..d7c005e 100644 --- a/app/src/main/scala/com/nutomic/ensichat/util/Database.scala +++ b/app/src/main/scala/com/nutomic/ensichat/util/Database.scala @@ -4,8 +4,7 @@ import java.util.Date import android.content.{ContentValues, Context} import android.database.sqlite.{SQLiteDatabase, SQLiteOpenHelper} -import com.nutomic.ensichat.aodvv2.Address -import com.nutomic.ensichat.messages._ +import com.nutomic.ensichat.aodvv2._ import scala.collection.SortedSet import scala.collection.immutable.TreeSet @@ -18,8 +17,8 @@ object Database { private val CreateMessagesTable = "CREATE TABLE messages(" + "_id integer primary key autoincrement," + - "sender text not null," + - "receiver text not null," + + "origin text not null," + + "target text not null," + "text text not null," + "date integer not null);" // Unix timestamp of message. @@ -35,8 +34,6 @@ object Database { class Database(context: Context) extends SQLiteOpenHelper(context, Database.DatabaseName, null, Database.DatabaseVersion) { - private val Tag = "MessageStore" - private var contactsUpdatedListeners = Set[() => Unit]() override def onCreate(db: SQLiteDatabase): Unit = { @@ -49,17 +46,21 @@ class Database(context: Context) extends SQLiteOpenHelper(context, Database.Data */ def getMessages(address: Address, count: Int): SortedSet[Message] = { val c = getReadableDatabase.query(true, - "messages", Array("sender", "receiver", "text", "date"), - "sender = ? OR receiver = ?", Array(address.toString, address.toString), + "messages", Array("origin", "target", "text", "date"), + "origin = ? OR target = ?", Array(address.toString, address.toString), null, null, "date DESC", count.toString) var messages = new TreeSet[Message]()(Message.Ordering) while (c.moveToNext()) { - val m = new TextMessage( - new Address(c.getString(c.getColumnIndex("sender"))), - new Address(c.getString(c.getColumnIndex("receiver"))), - new Date(c.getLong(c.getColumnIndex("date"))), - new String(c.getString(c.getColumnIndex ("text")))) - messages += m + val header = new MessageHeader( + Text.Type, + -1, + new Address(c.getString(c.getColumnIndex("origin"))), + new Address(c.getString(c.getColumnIndex("target"))), + -1, + -1, + new Date(c.getLong(c.getColumnIndex("date")))) + val body = new Text(new String(c.getString(c.getColumnIndex ("text")))) + messages += new Message(header, body) } c.close() messages @@ -68,16 +69,16 @@ class Database(context: Context) extends SQLiteOpenHelper(context, Database.Data /** * Inserts the given new message into the database. */ - def addMessage(message: Message): Unit = message match { - case msg: TextMessage => + def addMessage(message: Message): Unit = message.Body match { + case msg: Text => val cv = new ContentValues() - cv.put("sender", msg.sender.toString) - cv.put("receiver", msg.receiver.toString) + cv.put("origin", message.Header.Origin.toString) + cv.put("target", message.Header.Target.toString) // toString used as workaround for compile error with Long. - cv.put("date", msg.date.getTime.toString) + cv.put("date", message.Header.Time.getTime.toString) cv.put("text", msg.text) getWritableDatabase.insert("messages", null, cv) - case _: RequestAddContactMessage | _: ResultAddContactMessage => + case _: ConnectionInfo | _: RequestAddContact | _: ResultAddContact => // Never stored. } diff --git a/app/src/main/scala/com/nutomic/ensichat/util/MessagesAdapter.scala b/app/src/main/scala/com/nutomic/ensichat/util/MessagesAdapter.scala index 5ed39be..035dea2 100644 --- a/app/src/main/scala/com/nutomic/ensichat/util/MessagesAdapter.scala +++ b/app/src/main/scala/com/nutomic/ensichat/util/MessagesAdapter.scala @@ -4,14 +4,13 @@ import android.content.Context import android.view.{Gravity, View, ViewGroup} import android.widget.{ArrayAdapter, RelativeLayout, TextView} import com.nutomic.ensichat.R -import com.nutomic.ensichat.aodvv2.Address -import com.nutomic.ensichat.messages.TextMessage +import com.nutomic.ensichat.aodvv2.{Address, Message, Text} /** - * Displays [[TextMessage]]s in ListView. + * Displays [[Message]]s in ListView. */ class MessagesAdapter(context: Context, remoteAddress: Address) extends - ArrayAdapter[TextMessage](context, R.layout.item_message, android.R.id.text1) { + ArrayAdapter[Message](context, R.layout.item_message, android.R.id.text1) { /** * Free space to the right/left to a message depending on who sent it, in dip. @@ -22,11 +21,11 @@ class MessagesAdapter(context: Context, remoteAddress: Address) extends val view = super.getView(position, convertView, parent).asInstanceOf[RelativeLayout] val tv = view.findViewById(android.R.id.text1).asInstanceOf[TextView] - tv.setText(getItem(position).text) + tv.setText(getItem(position).Body.asInstanceOf[Text].text) val lp = new RelativeLayout.LayoutParams(tv.getLayoutParams) val margin = (MessageMargin * context.getResources.getDisplayMetrics.density).toInt - if (getItem(position).sender != remoteAddress) { + if (getItem(position).Header.Origin != remoteAddress) { view.setGravity(Gravity.RIGHT) lp.setMargins(margin, 0, 0, 0) } else { diff --git a/build.gradle b/build.gradle index 996243c..216ac9a 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:0.14.2' - classpath 'com.github.ben-manes:gradle-versions-plugin:0.5' + classpath 'com.github.ben-manes:gradle-versions-plugin:0.6' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/gradle/wrapper/gradle-wrapper.jar b/gradle/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..3d0dee6e8edfecc92e04653ec780de06f7b34f8b GIT binary patch literal 51017 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 H-~Rf4?cF@p literal 0 HcmV?d00001 diff --git a/gradle/gradle/wrapper/gradle-wrapper.properties b/gradle/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..67123dd --- /dev/null +++ b/gradle/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Dec 04 22:57:09 EET 2014 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-bin.zip diff --git a/gradle/gradlew b/gradle/gradlew new file mode 100755 index 0000000..91a7e26 --- /dev/null +++ b/gradle/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradle/gradlew.bat b/gradle/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/gradle/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/gradle/local.properties b/gradle/local.properties new file mode 100644 index 0000000..66280f3 --- /dev/null +++ b/gradle/local.properties @@ -0,0 +1,11 @@ +## This file is automatically generated by Android Studio. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Thu Dec 04 22:57:09 EET 2014 +sdk.dir=/home/felix/software/android-sdk diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8c0fb64a8698b08ecc4158d828ca593c4928e9dd..3d0dee6e8edfecc92e04653ec780de06f7b34f8b 100644 GIT binary patch delta 43591 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%0A$#o6WfPc2U@ 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
    G$9-3O@w*JGCZ~kHa^laDM&(RI3)d{c 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 delta 42496 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