Skip to content

Commit

Permalink
feat(CargoMessageSet): Implement method to iterate over messages (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
gnarea authored Jul 20, 2020
1 parent 22f85cb commit f246d1e
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 28 deletions.
3 changes: 0 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,7 @@ dependencies {
implementation("com.beanit:jasn1:1.11.2")
implementation("org.bouncycastle:bcprov-jdk15on:1.66")

// Use the Kotlin test library.
testImplementation("org.jetbrains.kotlin:kotlin-test")

// Use the Kotlin JUnit5 integration.
testImplementation("org.junit.jupiter:junit-jupiter:5.6.0")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.6.0")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
Expand Down
8 changes: 4 additions & 4 deletions src/main/kotlin/tech/relaycorp/relaynet/messages/Parcel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import tech.relaycorp.relaynet.wrappers.x509.Certificate
import java.io.InputStream
import java.time.ZonedDateTime

private val SERIALIZER = RAMFSerializer(0x50, 0x00)
internal val PARCEL_SERIALIZER = RAMFSerializer(0x50, 0x00)

/**
* Parcel
Expand All @@ -23,7 +23,7 @@ class Parcel(
ttl: Int? = null,
senderCertificateChain: Set<Certificate>? = null
) : EncryptedRAMFMessage<ServiceMessage>(
SERIALIZER,
PARCEL_SERIALIZER,
recipientAddress,
payload,
senderCertificate,
Expand All @@ -43,14 +43,14 @@ class Parcel(
@JvmStatic
@Throws(RAMFException::class)
override fun deserialize(serialization: ByteArray) =
SERIALIZER.deserialize(serialization, ::Parcel)
PARCEL_SERIALIZER.deserialize(serialization, ::Parcel)

/**
* Deserialize parcel
*/
@JvmStatic
@Throws(RAMFException::class)
override fun deserialize(serialization: InputStream) =
SERIALIZER.deserialize(serialization, ::Parcel)
PARCEL_SERIALIZER.deserialize(serialization, ::Parcel)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class ParcelCollectionAck(
companion object {
private const val concreteMessageType: Byte = 0x51
private const val concreteMessageVersion: Byte = 0
private val FORMAT_SIGNATURE = byteArrayOf(
internal val FORMAT_SIGNATURE = byteArrayOf(
*"Relaynet".toByteArray(),
concreteMessageType,
concreteMessageVersion
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package tech.relaycorp.relaynet.messages.payloads

import tech.relaycorp.relaynet.messages.PARCEL_SERIALIZER
import tech.relaycorp.relaynet.messages.ParcelCollectionAck

/**
* Message encapsulated in a cargo message set, classified with its type.
*/
class CargoMessage(val message: ByteArray) {
var type: Type? = null
private set

init {
if (10 <= message.size) {
val formatSignature = message.slice(0..9)
for (typeEnum in Type.values()) {
if (typeEnum.formatSignature == formatSignature) {
type = typeEnum
break
}
}
}
}

companion object {
enum class Type(internal val formatSignature: List<Byte>) {
PARCEL(PARCEL_SERIALIZER.formatSignature.asList()),
PCA(ParcelCollectionAck.FORMAT_SIGNATURE.asList())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ class CargoMessageSet(val messages: Array<ByteArray>) : EncryptedPayload() {
return ASN1Utils.serializeSequence(items)
}

/**
* Return the encapsulated messages, classified by type.
*/
fun classifyMessages(): Sequence<CargoMessage> = messages.asSequence().map { CargoMessage(it) }

companion object {
/**
* Deserialize a cargo message set.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,17 @@ private data class FieldSet(
)

internal class RAMFSerializer(val concreteMessageType: Byte, val concreteMessageVersion: Byte) {
val formatSignature =
byteArrayOf(*"Relaynet".toByteArray(), concreteMessageType, concreteMessageVersion)

fun serialize(
message: RAMFMessage<*>,
signerPrivateKey: PrivateKey,
hashingAlgorithm: HashingAlgorithm? = null
): ByteArray {
val output = ByteArrayOutputStream()

output.write("Relaynet".toByteArray())
output.write(concreteMessageType.toInt())
output.write(concreteMessageVersion.toInt())
output.write(formatSignature)

val fieldSetSerialized = serializeMessage(message)
val signedData = sign(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import org.bouncycastle.asn1.DERVisibleString
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import tech.relaycorp.relaynet.CERTIFICATE
import tech.relaycorp.relaynet.KEY_PAIR
import tech.relaycorp.relaynet.messages.Parcel
import tech.relaycorp.relaynet.messages.ParcelCollectionAck
import tech.relaycorp.relaynet.ramf.RAMFException
import tech.relaycorp.relaynet.wrappers.asn1.ASN1Utils
import kotlin.test.assertEquals
Expand All @@ -27,7 +31,7 @@ internal class CargoMessageSetTest {
}

@Nested
inner class Serialize {
inner class SerializePlaintext {
@Test
fun `An empty array should be serialized as such`() {
val cargoMessageSet = CargoMessageSet(emptyArray())
Expand Down Expand Up @@ -147,4 +151,34 @@ internal class CargoMessageSetTest {
assertEquals(message2.asList(), cargoMessageSet.messages[1].asList())
}
}

@Nested
inner class ClassifyMessages {
@Test
fun `Sequence should be empty if there are no messages`() {
val cargoMessageSet = CargoMessageSet(emptyArray())

assertEquals(0, cargoMessageSet.classifyMessages().count())
}

@Test
fun `Encapsulated messages should be wrapped in CargoMessage instances`() {
val recipientEndpointAddress = "https://foo.relaycorp.tech"
val parcelSerialized =
Parcel(recipientEndpointAddress, "".toByteArray(), CERTIFICATE)
.serialize(KEY_PAIR.private)
val pcaSerialized =
ParcelCollectionAck("0deadbeef", recipientEndpointAddress, "parcel-id")
.serialize()
val cargoMessageSet = CargoMessageSet(arrayOf(parcelSerialized, pcaSerialized))

val cargoMessages = cargoMessageSet.classifyMessages().asSequence().toList()

assertEquals(2, cargoMessages.size)
assertEquals(CargoMessage.Companion.Type.PARCEL, cargoMessages[0].type)
assertEquals(parcelSerialized.asList(), cargoMessages[0].message.asList())
assertEquals(CargoMessage.Companion.Type.PCA, cargoMessages[1].type)
assertEquals(pcaSerialized.asList(), cargoMessages[1].message.asList())
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package tech.relaycorp.relaynet.messages.payloads

import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import tech.relaycorp.relaynet.CERTIFICATE
import tech.relaycorp.relaynet.KEY_PAIR
import tech.relaycorp.relaynet.messages.Parcel
import tech.relaycorp.relaynet.messages.ParcelCollectionAck
import kotlin.test.assertEquals
import kotlin.test.assertNull

class CargoMessageTest {
@Nested
inner class Constructor {
private val recipientEndpointAddress = "https://foo.relaycorp.tech"

@Test
fun `Parcels should be correctly classified as such`() {
val parcel = Parcel(recipientEndpointAddress, "".toByteArray(), CERTIFICATE)
val parcelSerialized = parcel.serialize(KEY_PAIR.private)

val cargoMessage = CargoMessage(parcelSerialized)

assertEquals(CargoMessage.Companion.Type.PARCEL, cargoMessage.type)
}

@Test
fun `PCAs should be correctly classified as such`() {
val pca = ParcelCollectionAck("0deadbeef", recipientEndpointAddress, "parcel-id")
val pcaSerialized = pca.serialize()

val cargoMessage = CargoMessage(pcaSerialized)

assertEquals(CargoMessage.Companion.Type.PCA, cargoMessage.type)
}

@Test
fun `Messages too short to contain format signature should not be assigned a type`() {
val cargoMessage = CargoMessage("RelaynetP".toByteArray())

assertNull(cargoMessage.type)
}

@Test
fun `Invalid messages should not be assigned a type`() {
val cargoMessage = CargoMessage("RelaynetyP0".toByteArray())

assertNull(cargoMessage.type)
}
}
}
44 changes: 28 additions & 16 deletions src/test/kotlin/tech/relaycorp/relaynet/ramf/RAMFSerializerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,26 @@ class RAMFSerializerTest {
assertEquals("Message should not be larger than 9 MiB", exception.message)
}

@Test
fun `Input can be a ByteArray`() {
@Suppress("USELESS_IS_CHECK")
assertTrue(stubSerialization is ByteArray)

val message = STUB_SERIALIZER.deserialize(stubSerialization, ::StubEncryptedRAMFMessage)

assertEquals(stubMessage.recipientAddress, message.recipientAddress)
}

@Test
fun `Input can be an InputStream`() {
val message = STUB_SERIALIZER.deserialize(
stubSerialization.inputStream(),
::StubEncryptedRAMFMessage
)

assertEquals(stubMessage.recipientAddress, message.recipientAddress)
}

@Nested
inner class FormatSignature {
@Test
Expand Down Expand Up @@ -617,22 +637,14 @@ class RAMFSerializerTest {
}

@Test
fun `Input can be a ByteArray`() {
@Suppress("USELESS_IS_CHECK")
assertTrue(stubSerialization is ByteArray)

val message = STUB_SERIALIZER.deserialize(stubSerialization, ::StubEncryptedRAMFMessage)

assertEquals(stubMessage.recipientAddress, message.recipientAddress)
}

@Test
fun `Input can be an InputStream`() {
val message = STUB_SERIALIZER.deserialize(
stubSerialization.inputStream(),
::StubEncryptedRAMFMessage
fun `formatSignature should contain the type and version`() {
assertEquals(
byteArrayOf(
*"Relaynet".toByteArray(),
STUB_SERIALIZER.concreteMessageType,
STUB_SERIALIZER.concreteMessageVersion
).asList(),
STUB_SERIALIZER.formatSignature.asList()
)

assertEquals(stubMessage.recipientAddress, message.recipientAddress)
}
}

0 comments on commit f246d1e

Please sign in to comment.