Browse Source
This seems to have added some random disconnects, but there doesn't seem to be a reason for this in the code.send-bitcoin
43 changed files with 983 additions and 584 deletions
@ -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) |
|||
} |
|||
} |
|||
|
|||
} |
@ -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)) |
|||
} |
|||
} |
|||
|
|||
} |
@ -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) |
|||
} |
|||
} |
|||
|
|||
} |
@ -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) |
|||
} |
|||
|
|||
} |
@ -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) + ")" |
|||
|
|||
} |
@ -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() |
|||
} |
|||
|
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
|||
|
|||
} |
@ -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 |
|||
|
|||
} |
|||
|
@ -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 |
|||
|
|||
} |
@ -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 |
|||
|
|||
} |
@ -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 |
|||
|
|||
} |