Moved time field from Text into ContentHeader.
This commit is contained in:
parent
85a8b71fe9
commit
80aa1b56ae
11 changed files with 64 additions and 47 deletions
12
PROTOCOL.md
12
PROTOCOL.md
|
@ -37,7 +37,7 @@ 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 (74 or 80 bytes) \
|
||||
\ Header (74 or 84 bytes) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
|
@ -76,6 +76,8 @@ header is in network byte order, i.e. big endian. The header may have
|
|||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Message ID |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Time |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
Version specifies the protocol version number. This is currently 0. A
|
||||
message with unknown version number MUST be ignored. The connection
|
||||
|
@ -114,7 +116,9 @@ Content-Type is one of those in section Content-Messages.
|
|||
Message ID is unique for each message by the same sender. A device MUST NOT
|
||||
ever send two messages with the same Message ID.
|
||||
|
||||
Only Content Messages have the Content-Type and Message ID
|
||||
Time is the unix timestamp of message sending.
|
||||
|
||||
Only Content Messages have the Content-Type Message ID and Time
|
||||
fields.
|
||||
|
||||
### Encryption Data
|
||||
|
@ -229,8 +233,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
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Time |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Text Length |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
|
@ -238,8 +240,6 @@ A simple chat message.
|
|||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
Time is the unix timestamp of message sending.
|
||||
|
||||
Text the string to be transferred, encoded as UTF-8.
|
||||
|
||||
### UserName (Content-Type = 4)
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
package com.nutomic.ensichat.protocol
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.util.GregorianCalendar
|
||||
|
||||
import android.test.AndroidTestCase
|
||||
import com.nutomic.ensichat.protocol.MessageTest._
|
||||
import com.nutomic.ensichat.protocol.body.{ConnectionInfo, ConnectionInfoTest, Text}
|
||||
import com.nutomic.ensichat.protocol.header.ContentHeaderTest._
|
||||
import com.nutomic.ensichat.protocol.MessageTest._
|
||||
import com.nutomic.ensichat.protocol.header.MessageHeader
|
||||
import junit.framework.Assert._
|
||||
|
||||
|
@ -14,11 +13,11 @@ import scala.collection.immutable.TreeSet
|
|||
|
||||
object MessageTest {
|
||||
|
||||
val m1 = new Message(h1, new Text("first", new GregorianCalendar(1970, 1, 1).getTime))
|
||||
val m1 = new Message(h1, new Text("first"))
|
||||
|
||||
val m2 = new Message(h2, new Text("second", new GregorianCalendar(2014, 6, 10).getTime))
|
||||
val m2 = new Message(h2, new Text("second"))
|
||||
|
||||
val m3 = new Message(h3, new Text("third", new GregorianCalendar(2020, 11, 11).getTime))
|
||||
val m3 = new Message(h3, new Text("third"))
|
||||
|
||||
val messages = Set(m1, m2, m3)
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package com.nutomic.ensichat.protocol
|
||||
|
||||
import java.util.GregorianCalendar
|
||||
|
||||
import android.test.AndroidTestCase
|
||||
import com.nutomic.ensichat.protocol.body.UserName
|
||||
import com.nutomic.ensichat.protocol.header.ContentHeader
|
||||
|
@ -74,7 +76,8 @@ class RouterTest extends AndroidTestCase {
|
|||
}
|
||||
|
||||
private def generateMessage(sender: Address, receiver: Address, seqNum: Int): Message = {
|
||||
val header = new ContentHeader(sender, receiver, seqNum, UserName.Type, 5)
|
||||
val header = new ContentHeader(sender, receiver, seqNum, UserName.Type, 5,
|
||||
new GregorianCalendar(2014, 6, 10).getTime)
|
||||
new Message(header, new UserName(""))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package com.nutomic.ensichat.protocol.header
|
||||
|
||||
import java.util.{GregorianCalendar, Date}
|
||||
|
||||
import android.test.AndroidTestCase
|
||||
import com.nutomic.ensichat.protocol.body.Text
|
||||
import com.nutomic.ensichat.protocol.{Address, AddressTest}
|
||||
|
@ -8,19 +10,19 @@ import junit.framework.Assert._
|
|||
object ContentHeaderTest {
|
||||
|
||||
val h1 = new ContentHeader(AddressTest.a1, AddressTest.a2, 1234,
|
||||
Text.Type, 123, 5)
|
||||
Text.Type, 123, new GregorianCalendar(1970, 1, 1).getTime, 5)
|
||||
|
||||
val h2 = new ContentHeader(AddressTest.a1, AddressTest.a3,
|
||||
30000, Text.Type, 8765, 20)
|
||||
30000, Text.Type, 8765, new GregorianCalendar(2014, 6, 10).getTime, 20)
|
||||
|
||||
val h3 = new ContentHeader(AddressTest.a4, AddressTest.a2,
|
||||
250, Text.Type, 77, 123)
|
||||
250, Text.Type, 77, new GregorianCalendar(2020, 11, 11).getTime, 123)
|
||||
|
||||
val h4 = new ContentHeader(Address.Null, Address.Broadcast,
|
||||
ContentHeader.SeqNumRange.last, 0, 0xffff, 0)
|
||||
ContentHeader.SeqNumRange.last, 0, 0xffff, new Date(0L), 0xff)
|
||||
|
||||
val h5 = new ContentHeader(Address.Broadcast, Address.Null,
|
||||
0, 0xff, 0, 0xff)
|
||||
0, 0xff, 0, new Date(0xffffffffL), 0)
|
||||
|
||||
val headers = Set(h1, h2, h3, h4, h5)
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package com.nutomic.ensichat.util
|
||||
|
||||
import java.util.GregorianCalendar
|
||||
|
||||
import android.content.Context
|
||||
import android.test.AndroidTestCase
|
||||
import com.nutomic.ensichat.protocol.body.{RequestAddContact, ResultAddContact}
|
||||
|
@ -24,12 +26,12 @@ class AddContactsHandlerTest extends AndroidTestCase {
|
|||
|
||||
private lazy val crypto = new Crypto(getContext)
|
||||
|
||||
private lazy val header1 =
|
||||
new ContentHeader(UserTest.u1.address, crypto.localAddress, 0, RequestAddContact.Type, 0)
|
||||
private lazy val header2 =
|
||||
new ContentHeader(UserTest.u1.address, crypto.localAddress, 0, ResultAddContact.Type, 0)
|
||||
private lazy val header3 =
|
||||
new ContentHeader(crypto.localAddress, UserTest.u1.address, 0, ResultAddContact.Type, 0)
|
||||
private lazy val header1 = new ContentHeader(UserTest.u1.address, crypto.localAddress, 0,
|
||||
RequestAddContact.Type, 0, new GregorianCalendar(1970, 1, 1).getTime)
|
||||
private lazy val header2 = new ContentHeader(UserTest.u1.address, crypto.localAddress, 0,
|
||||
ResultAddContact.Type, 0, new GregorianCalendar(2014, 6, 10).getTime)
|
||||
private lazy val header3 = new ContentHeader(crypto.localAddress, UserTest.u1.address, 0,
|
||||
ResultAddContact.Type, 0, new GregorianCalendar(2020, 11, 11).getTime)
|
||||
|
||||
override def tearDown(): Unit = {
|
||||
super.tearDown()
|
||||
|
|
|
@ -80,6 +80,7 @@ class DatabaseTest extends AndroidTestCase {
|
|||
assertEquals(-1, msg.header.seqNum)
|
||||
assertEquals(h3.contentType, header.contentType)
|
||||
assertEquals(h3.messageId, header.messageId)
|
||||
assertEquals(h3.time, header.time)
|
||||
assertEquals(new CryptoData(None, None), msg.crypto)
|
||||
assertEquals(m3.body, msg.body)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package com.nutomic.ensichat.protocol
|
||||
|
||||
import java.util.Date
|
||||
|
||||
import android.app.Service
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.content.Intent
|
||||
|
@ -141,7 +143,7 @@ class ChatService extends Service {
|
|||
val sp = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val messageId = sp.getLong("message_id", 0)
|
||||
val header = new ContentHeader(crypto.localAddress, target, seqNumGenerator.next(),
|
||||
body.contentType, messageId)
|
||||
body.contentType, messageId, new Date())
|
||||
sp.edit().putLong("message_id", messageId + 1)
|
||||
|
||||
val msg = new Message(header, body)
|
||||
|
|
|
@ -12,8 +12,9 @@ object Message {
|
|||
* Orders messages by date, oldest messages first.
|
||||
*/
|
||||
val Ordering = new Ordering[Message] {
|
||||
override def compare(m1: Message, m2: Message) = (m1.body, m2.body) match {
|
||||
case (t1: Text, t2: Text) => t1.time.compareTo(t2.time)
|
||||
override def compare(m1: Message, m2: Message) = (m1.header, m2.header) match {
|
||||
case (h1: ContentHeader, h2: ContentHeader) =>
|
||||
h1.time.compareTo(h2.time)
|
||||
case _ => 0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.nutomic.ensichat.protocol.body
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.Date
|
||||
|
||||
import com.nutomic.ensichat.protocol.Message
|
||||
import com.nutomic.ensichat.util.BufferUtils
|
||||
|
||||
|
@ -14,11 +14,10 @@ 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, Message.Charset), time)
|
||||
new Text(new String(bytes, Message.Charset))
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,7 +25,7 @@ object Text {
|
|||
/**
|
||||
* Holds a plain text message.
|
||||
*/
|
||||
case class Text(text: String, time: Date = new Date()) extends MessageBody {
|
||||
case class Text(text: String) extends MessageBody {
|
||||
|
||||
override def protocolType = -1
|
||||
|
||||
|
@ -34,17 +33,16 @@ case class Text(text: String, time: Date = new Date()) extends MessageBody {
|
|||
|
||||
override def write: Array[Byte] = {
|
||||
val b = ByteBuffer.allocate(length)
|
||||
BufferUtils.putUnsignedInt(b, time.getTime / 1000)
|
||||
val bytes = text.getBytes(Message.Charset)
|
||||
BufferUtils.putUnsignedInt(b, bytes.length)
|
||||
b.put(bytes)
|
||||
b.array()
|
||||
}
|
||||
|
||||
override def length = 8 + text.getBytes(Message.Charset).length
|
||||
override def length = 4 + text.getBytes(Message.Charset).length
|
||||
|
||||
override def equals(a: Any): Boolean = a match {
|
||||
case o: Text => text == text && time.getTime / 1000 == o.time.getTime / 1000
|
||||
case o: Text => text == text
|
||||
case _ => false
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
package com.nutomic.ensichat.protocol.header
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.Date
|
||||
|
||||
import com.nutomic.ensichat.protocol.Address
|
||||
import com.nutomic.ensichat.util.BufferUtils
|
||||
|
||||
object ContentHeader {
|
||||
|
||||
val Length = 6
|
||||
val Length = 10
|
||||
|
||||
val ContentMessageType = 255
|
||||
|
||||
|
@ -20,10 +21,11 @@ object ContentHeader {
|
|||
val b = ByteBuffer.wrap(bytes)
|
||||
|
||||
val contentType = BufferUtils.getUnsignedShort(b)
|
||||
val messageId = BufferUtils.getUnsignedInt(b)
|
||||
val messageId = BufferUtils.getUnsignedInt(b)
|
||||
val time = BufferUtils.getUnsignedInt(b)
|
||||
|
||||
val ch = new ContentHeader(mh.origin, mh.target,
|
||||
mh.seqNum, contentType, messageId, mh.hopCount)
|
||||
val ch = new ContentHeader(mh.origin, mh.target, mh.seqNum, contentType, messageId,
|
||||
new Date(time * 1000), mh.hopCount)
|
||||
|
||||
val remaining = new Array[Byte](b.remaining())
|
||||
b.get(remaining, 0, b.remaining())
|
||||
|
@ -42,6 +44,7 @@ case class ContentHeader(override val origin: Address,
|
|||
override val seqNum: Int,
|
||||
contentType: Int,
|
||||
messageId: Long,
|
||||
time: Date,
|
||||
override val hopCount: Int = 0)
|
||||
extends AbstractHeader {
|
||||
|
||||
|
@ -59,6 +62,7 @@ case class ContentHeader(override val origin: Address,
|
|||
|
||||
BufferUtils.putUnsignedShort(b, contentType)
|
||||
BufferUtils.putUnsignedInt(b, messageId)
|
||||
BufferUtils.putUnsignedInt(b, time.getTime / 1000)
|
||||
|
||||
b.array()
|
||||
}
|
||||
|
@ -68,8 +72,9 @@ case class ContentHeader(override val origin: Address,
|
|||
override def equals(a: Any): Boolean = a match {
|
||||
case o: ContentHeader =>
|
||||
super.equals(a) &&
|
||||
contentType == o.contentType &&
|
||||
messageId == o.messageId
|
||||
contentType == o.contentType &&
|
||||
messageId == o.messageId &&
|
||||
time.getTime / 1000 == o.time.getTime / 1000
|
||||
case _ => false
|
||||
}
|
||||
|
||||
|
|
|
@ -63,11 +63,14 @@ class Database(context: Context)
|
|||
null, null, "date DESC", count.toString)
|
||||
var messages = new TreeSet[Message]()(Message.Ordering)
|
||||
while (c.moveToNext()) {
|
||||
val header = new ContentHeader(new Address(c.getString(c.getColumnIndex("origin"))),
|
||||
new Address(c.getString(c.getColumnIndex("target"))), -1, Text.Type,
|
||||
c.getLong(c.getColumnIndex("message_id")))
|
||||
val body = new Text(new String(c.getString(c.getColumnIndex ("text"))),
|
||||
val header = new ContentHeader(new Address(
|
||||
c.getString(c.getColumnIndex("origin"))),
|
||||
new Address(c.getString(c.getColumnIndex("target"))),
|
||||
-1,
|
||||
Text.Type,
|
||||
c.getLong(c.getColumnIndex("message_id")),
|
||||
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()
|
||||
|
@ -80,11 +83,12 @@ class Database(context: Context)
|
|||
override def onMessageReceived(msg: Message): Unit = msg.body match {
|
||||
case text: Text =>
|
||||
val cv = new ContentValues()
|
||||
cv.put("origin", msg.header.origin.toString)
|
||||
cv.put("target", msg.header.target.toString)
|
||||
val ch = msg.header.asInstanceOf[ContentHeader]
|
||||
cv.put("origin", ch.origin.toString)
|
||||
cv.put("target", ch.target.toString)
|
||||
// Need to use [[Long#toString]] because of https://issues.scala-lang.org/browse/SI-2991
|
||||
cv.put("message_id", msg.header.asInstanceOf[ContentHeader].messageId.toString)
|
||||
cv.put("date", text.time.getTime.toString)
|
||||
cv.put("message_id", ch.messageId.toString)
|
||||
cv.put("date", ch.time.getTime.toString)
|
||||
cv.put("text", text.text)
|
||||
getWritableDatabase.insert("messages", null, cv)
|
||||
case _: RequestAddContact | _: ResultAddContact =>
|
||||
|
|
Reference in a new issue