Cleaned up header, moved time to text.
This commit is contained in:
parent
5cbf918d86
commit
bd9ea26bd5
7 changed files with 43 additions and 53 deletions
23
PROTOCOL.md
23
PROTOCOL.md
|
@ -37,7 +37,7 @@ the AES key is wrapped with the recipient's public RSA key.
|
|||
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) \
|
||||
\ Header (76 bytes) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
|
@ -52,19 +52,17 @@ the AES key is wrapped with the recipient's public RSA key.
|
|||
|
||||
### Header
|
||||
|
||||
Every message starts with one 32 bit word indicating the message
|
||||
Every message starts with one 76 byte header indicating the message
|
||||
version, type and ID, followed by the length of the message. The
|
||||
header is in network byte order, i.e. big endian.
|
||||
|
||||
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
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Ver | Type | Hop Limit | Hop Count |
|
||||
| Version | Type | Hop Limit | Hop Count |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Length |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Time |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
| Origin Address |
|
||||
| |
|
||||
|
@ -73,15 +71,15 @@ header is in network byte order, i.e. big endian.
|
|||
| Target Address |
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Sequence Number | Metric | Reserved |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Body Length |
|
||||
| Reserved |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
Ver specifies the protocol version number. This is currently 0. A
|
||||
Version specifies the protocol version number. This is currently 0. A
|
||||
message with unknown version number MUST be ignored. The connection
|
||||
where such a packet came from MAY be closed.
|
||||
|
||||
Type is one of the message types specified below.
|
||||
|
||||
Hop Limit SHOULD be set to `MAX_HOP_COUNT` on message creation, and
|
||||
MUST NOT be changed by a forwarding node.
|
||||
|
||||
|
@ -102,9 +100,6 @@ message.
|
|||
Target Address is the address of the node that should receive the
|
||||
message.
|
||||
|
||||
Sequence number is the sequence number of either the source or target
|
||||
node for this message, depending on type.
|
||||
|
||||
|
||||
### Encryption Data
|
||||
|
||||
|
@ -203,7 +198,7 @@ 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 |
|
||||
| Time |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Text Length |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
@ -212,4 +207,6 @@ A simple chat message.
|
|||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
Time is the unix timestamp of message sending.
|
||||
|
||||
Text the string to be transferred, encoded as UTF-8.
|
||||
|
|
|
@ -10,19 +10,15 @@ import com.nutomic.ensichat.protocol.messages.MessageHeaderTest._
|
|||
object MessageHeaderTest {
|
||||
|
||||
val h1 = new MessageHeader(Text.Type, MessageHeader.DefaultHopLimit, AddressTest.a1,
|
||||
AddressTest.a2, 1234, 0, new GregorianCalendar(1970, 1, 1).getTime, 567, 8)
|
||||
AddressTest.a2, 1234, 0)
|
||||
|
||||
val h2 = new MessageHeader(Text.Type, 0, AddressTest.a1, AddressTest.a3, 8765, 234,
|
||||
new GregorianCalendar(2014, 6, 10).getTime, 0, 0xff)
|
||||
val h2 = new MessageHeader(Text.Type, 0, AddressTest.a1, AddressTest.a3, 8765, 234)
|
||||
|
||||
val h3 = new MessageHeader(Text.Type, 0xff, AddressTest.a4, AddressTest.a2, 0, 56,
|
||||
new GregorianCalendar(2020, 11, 11).getTime, 0xffff, 0)
|
||||
val h3 = new MessageHeader(Text.Type, 0xff, AddressTest.a4, AddressTest.a2, 0, 56)
|
||||
|
||||
val h4 = new MessageHeader(0xfff, 0, Address.Null, Address.Broadcast, 0, 0xff,
|
||||
new GregorianCalendar(1990, 1, 1).getTime, 0, 0xff)
|
||||
val h4 = new MessageHeader(0xfff, 0, Address.Null, Address.Broadcast, 0, 0xff)
|
||||
|
||||
val h5 = new MessageHeader(ConnectionInfo.Type, 0xff, Address.Broadcast, Address.Null, 0xffff, 0,
|
||||
new GregorianCalendar(2035, 12, 31).getTime, 0xffff, 0)
|
||||
val h5 = new MessageHeader(ConnectionInfo.Type, 0xff, Address.Broadcast, Address.Null, 0xffff, 0)
|
||||
|
||||
val headers = Set(h1, h2, h3, h4, h5)
|
||||
|
||||
|
|
|
@ -13,11 +13,11 @@ import scala.collection.immutable.TreeSet
|
|||
|
||||
object MessageTest {
|
||||
|
||||
val m1 = new Message(h1, new Text("first"))
|
||||
val m1 = new Message(h1, new Text("first", new GregorianCalendar(1970, 1, 1).getTime))
|
||||
|
||||
val m2 = new Message(h2, new Text("second"))
|
||||
val m2 = new Message(h2, new Text("second", new GregorianCalendar(2014, 6, 10).getTime))
|
||||
|
||||
val m3 = new Message(h3, new Text("third"))
|
||||
val m3 = new Message(h3, new Text("third", new GregorianCalendar(2020, 11, 11).getTime))
|
||||
|
||||
val messages = Set(m1, m2, m3)
|
||||
|
||||
|
@ -47,8 +47,7 @@ class MessageTest extends AndroidTestCase {
|
|||
}
|
||||
|
||||
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 header = new MessageHeader(ConnectionInfo.Type, 0xff, AddressTest.a4, AddressTest.a2, 0, 56)
|
||||
val m = new Message(header, ConnectionInfoTest.generateCi(getContext))
|
||||
|
||||
val signed = Crypto.sign(m)
|
||||
|
|
|
@ -8,7 +8,10 @@ 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)
|
||||
override def compare(m1: Message, m2: Message) = (m1.Body, m2.Body) match {
|
||||
case (t1: Text, t2: Text) => t1.time.compareTo(t2.time)
|
||||
case _ => 0
|
||||
}
|
||||
}
|
||||
|
||||
def read(stream: InputStream): Message = {
|
||||
|
|
|
@ -7,7 +7,7 @@ import com.nutomic.ensichat.protocol.{Address, BufferUtils}
|
|||
|
||||
object MessageHeader {
|
||||
|
||||
val Length = 20 + 2 * Address.Length
|
||||
val Length = 12 + 2 * Address.Length
|
||||
|
||||
val DefaultHopLimit = 20
|
||||
|
||||
|
@ -31,14 +31,10 @@ object MessageHeader {
|
|||
val hopCount = BufferUtils.getUnsignedByte(b)
|
||||
|
||||
val length = BufferUtils.getUnsignedInt(b)
|
||||
val time = new Date(b.getInt().toLong * 1000)
|
||||
val origin = new Address(BufferUtils.getByteArray(b, Address.Length))
|
||||
val target = new Address(BufferUtils.getByteArray(b, Address.Length))
|
||||
|
||||
val seqNum = BufferUtils.getUnsignedShort(b)
|
||||
val metric = BufferUtils.getUnsignedByte(b)
|
||||
|
||||
new MessageHeader(messageType, hopLimit, origin, target, seqNum, metric, time, length, hopCount)
|
||||
new MessageHeader(messageType, hopLimit, origin, target, length, hopCount)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -50,9 +46,6 @@ case class MessageHeader(MessageType: Int,
|
|||
HopLimit: Int,
|
||||
Origin: Address,
|
||||
Target: Address,
|
||||
SequenceNumber: Int,
|
||||
Metric: Int,
|
||||
Time: Date = new Date(),
|
||||
Length: Long = -1,
|
||||
HopCount: Int = 0) {
|
||||
|
||||
|
@ -68,13 +61,10 @@ case class MessageHeader(MessageType: Int,
|
|||
BufferUtils.putUnsignedByte(b, HopCount)
|
||||
|
||||
BufferUtils.putUnsignedInt(b, MessageHeader.Length + contentLength)
|
||||
b.putInt((Time.getTime / 1000).toInt)
|
||||
b.put(Origin.Bytes)
|
||||
b.put(Target.Bytes)
|
||||
|
||||
BufferUtils.putUnsignedShort(b, SequenceNumber)
|
||||
BufferUtils.putUnsignedByte(b, Metric)
|
||||
BufferUtils.putUnsignedByte(b, 0)
|
||||
BufferUtils.putUnsignedInt(b, 0)
|
||||
|
||||
b.array()
|
||||
}
|
||||
|
@ -83,11 +73,8 @@ case class MessageHeader(MessageType: Int,
|
|||
case o: MessageHeader =>
|
||||
MessageType == o.MessageType &&
|
||||
HopLimit == o.HopLimit &&
|
||||
Time.getTime / 1000 == o.Time.getTime / 1000 &&
|
||||
Origin == o.Origin &&
|
||||
Target == o.Target &&
|
||||
SequenceNumber == o.SequenceNumber &&
|
||||
Metric == o.Metric &&
|
||||
HopCount == o.HopCount
|
||||
// Don't compare length as it may be unknown (when header was just created without a body).
|
||||
case _ => false
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.nutomic.ensichat.protocol.messages
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.Date
|
||||
|
||||
import com.nutomic.ensichat.protocol.BufferUtils
|
||||
|
||||
|
@ -15,10 +16,11 @@ object Text {
|
|||
*/
|
||||
def read(array: Array[Byte]): Text = {
|
||||
val b = ByteBuffer.wrap(array)
|
||||
val time = new Date(BufferUtils.getUnsignedInt(b) * 1000)
|
||||
val length = BufferUtils.getUnsignedInt(b).toInt
|
||||
val bytes = new Array[Byte](length)
|
||||
b.get(bytes, 0, length)
|
||||
new Text(new String(bytes, Text.Charset))
|
||||
new Text(new String(bytes, Text.Charset), time)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,18 +28,24 @@ object Text {
|
|||
/**
|
||||
* Holds a plain text message.
|
||||
*/
|
||||
case class Text(text: String) extends MessageBody {
|
||||
case class Text(text: String, time: Date = new Date()) 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)
|
||||
val b = ByteBuffer.allocate(length)
|
||||
BufferUtils.putUnsignedInt(b, time.getTime / 1000)
|
||||
BufferUtils.putUnsignedInt(b, bytes.length)
|
||||
b.put(bytes)
|
||||
b.array()
|
||||
}
|
||||
|
||||
override def length = write.length
|
||||
override def length = 8 + text.getBytes(Text.Charset).length
|
||||
|
||||
override def equals(a: Any): Boolean = a match {
|
||||
case o: Text => text == text && time.getTime / 1000 == o.time.getTime / 1000
|
||||
case _ => false
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -58,9 +58,9 @@ class Database(context: Context) extends SQLiteOpenHelper(context, Database.Data
|
|||
new Address(c.getString(c.getColumnIndex("origin"))),
|
||||
new Address(c.getString(c.getColumnIndex("target"))),
|
||||
-1,
|
||||
-1,
|
||||
-1)
|
||||
val body = new Text(new String(c.getString(c.getColumnIndex ("text"))),
|
||||
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()
|
||||
|
@ -71,13 +71,13 @@ class Database(context: Context) extends SQLiteOpenHelper(context, Database.Data
|
|||
* Inserts the given new message into the database.
|
||||
*/
|
||||
def addMessage(message: Message): Unit = message.Body match {
|
||||
case msg: Text =>
|
||||
case text: Text =>
|
||||
val cv = new ContentValues()
|
||||
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", message.Header.Time.getTime.toString)
|
||||
cv.put("text", msg.text)
|
||||
cv.put("date", text.time.getTime.toString)
|
||||
cv.put("text", text.text)
|
||||
getWritableDatabase.insert("messages", null, cv)
|
||||
case _: ConnectionInfo | _: RequestAddContact | _: ResultAddContact =>
|
||||
// Never stored.
|
||||
|
|
Reference in a new issue