Skip to content

Commit

Permalink
fix: Implement high-level classes to manipulate handshake messages (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
gnarea authored Jul 9, 2020
1 parent aa1d64f commit 75a6b64
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 56 deletions.
56 changes: 2 additions & 54 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ plugins {
id('idea')
}

apply from: 'jacoco.gradle'

group = "tech.relaycorp"

repositories {
Expand Down Expand Up @@ -55,60 +57,6 @@ tasks.withType(KotlinCompile).configureEach {
protobuf {
protoc { artifact = "com.google.protobuf:protoc:$protobufVersion" }
}
sourceSets {
main {
kotlin {
setBuildDir("build/generated/source/proto/main/java")
}
}
}

jacoco {
toolVersion = "0.8.5"
}

jacocoTestReport {
reports {
xml.enabled = true
html.enabled = true
html.destination = file("$buildDir/reports/coverage")
}
}

jacocoTestCoverageVerification {
violationRules {
rule {
limit {
counter = "CLASS"
value = "MISSEDCOUNT"
maximum = "0".toBigDecimal()
}
limit {
counter = "METHOD"
value = "MISSEDCOUNT"
maximum = "0".toBigDecimal()
}

limit {
counter = "BRANCH"
value = "MISSEDCOUNT"
maximum = "0".toBigDecimal()
}
}
}
}

tasks.test {
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed")
}
finalizedBy("jacocoTestReport")
doLast {
println("View code coverage at:")
println("file://$buildDir/reports/coverage/index.html")
}
}

tasks.withType(KotlinCompile).configureEach {
kotlinOptions.jvmTarget = "1.8"
Expand Down
60 changes: 60 additions & 0 deletions jacoco.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
jacoco {
toolVersion = "0.8.5"
}

def jacocoFiles = files([
fileTree(
dir: project.buildDir,
includes: ["classes/kotlin/main/"],
)
])

jacocoTestReport {
classDirectories.from = jacocoFiles

reports {
xml.enabled = true
html.enabled = true
html.destination = file("$buildDir/reports/coverage")
}
}

jacocoTestCoverageVerification {
afterEvaluate {
classDirectories.from = jacocoFiles
}

violationRules {
rule {
limit {
counter = "CLASS"
value = "MISSEDCOUNT"
maximum = "0".toBigDecimal()
}

limit {
counter = "METHOD"
value = "MISSEDCOUNT"
maximum = "0".toBigDecimal()
}

limit {
counter = "BRANCH"
value = "MISSEDCOUNT"
maximum = "0".toBigDecimal()
}
}
}
}

test {
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed")
}
finalizedBy("jacocoTestReport")
doLast {
println("View code coverage at:")
println("file://$buildDir/reports/coverage/index.html")
}
}
24 changes: 24 additions & 0 deletions src/main/kotlin/tech/relaycorp/poweb/handshake/Challenge.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package tech.relaycorp.poweb.handshake

import com.google.protobuf.ByteString
import com.google.protobuf.InvalidProtocolBufferException
import tech.relaycorp.poweb.internal.protobuf_messages.handshake.Challenge as PBChallenge

class Challenge(val nonce: ByteArray) {
fun serialize(): ByteArray {
val pbChallenge = PBChallenge.newBuilder()
.setGatewayNonce(ByteString.copyFrom(nonce)).build()
return pbChallenge.toByteArray()
}

companion object {
fun deserialize(serialization: ByteArray): Challenge {
val pbChallenge = try {
PBChallenge.parseFrom(serialization)
} catch (_: InvalidProtocolBufferException) {
throw InvalidMessageException("Message is not a valid challenge")
}
return Challenge(pbChallenge.gatewayNonce.toByteArray())
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package tech.relaycorp.poweb.handshake

class InvalidMessageException(message: String) : Exception(message)
26 changes: 26 additions & 0 deletions src/main/kotlin/tech/relaycorp/poweb/handshake/Response.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package tech.relaycorp.poweb.handshake

import com.google.protobuf.ByteString
import com.google.protobuf.InvalidProtocolBufferException
import tech.relaycorp.poweb.internal.protobuf_messages.handshake.Response as PBResponse

class Response(val nonceSignatures: Array<ByteArray>) {
fun serialize(): ByteArray {
val pbResponse = PBResponse.newBuilder()
.addAllGatewayNonceSignatures(nonceSignatures.map { ByteString.copyFrom(it) })
.build()
return pbResponse.toByteArray()
}

companion object {
fun deserialize(serialization: ByteArray): Response {
val pbResponse = try {
PBResponse.parseFrom(serialization)
} catch (_: InvalidProtocolBufferException) {
throw InvalidMessageException("Message is not a valid response")
}
val nonceSignatures = pbResponse.gatewayNonceSignaturesList.map { it.toByteArray() }
return Response(nonceSignatures.toTypedArray())
}
}
}
4 changes: 2 additions & 2 deletions src/main/proto/poweb-handshake.proto
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
syntax = "proto3";

option java_multiple_files = true;
option java_package = "tech.relaycorp.poweb.messages.handshake";
option java_package = "tech.relaycorp.poweb.internal.protobuf_messages.handshake";

package relaynet.poweb.handshake;

message Challenge {
// Sent by the gateway at the start of the connection

string gateway_nonce = 1;
bytes gateway_nonce = 1;
}

message Response {
Expand Down
49 changes: 49 additions & 0 deletions src/test/kotlin/tech/relaycorp/poweb/handshake/ChallengeTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package tech.relaycorp.poweb.handshake

import com.google.protobuf.ByteString
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import kotlin.test.assertEquals
import tech.relaycorp.poweb.internal.protobuf_messages.handshake.Challenge as PBChallenge

class ChallengeTest {
val nonce = "the-nonce".toByteArray()

@Nested
inner class Serialize {
@Test
fun `Message should be serialized`() {
val challenge = Challenge(nonce)

val serialization = challenge.serialize()

val pbChallenge = PBChallenge.parseFrom(serialization)
assertEquals(nonce.asList(), pbChallenge.gatewayNonce.toByteArray().asList())
}
}

@Nested
inner class Deserialize {
@Test
fun `Invalid serialization should be refused`() {
val serialization = "This is invalid".toByteArray()

val exception = assertThrows<InvalidMessageException> {
Challenge.deserialize(serialization)
}

assertEquals("Message is not a valid challenge", exception.message)
}

@Test
fun `Valid serialization should be accepted`() {
val serialization = PBChallenge.newBuilder()
.setGatewayNonce(ByteString.copyFrom(nonce)).build().toByteArray()

val challenge = Challenge.deserialize(serialization)

assertEquals(nonce.asList(), challenge.nonce.asList())
}
}
}
103 changes: 103 additions & 0 deletions src/test/kotlin/tech/relaycorp/poweb/handshake/ResponseTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package tech.relaycorp.poweb.handshake

import com.google.protobuf.ByteString
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import kotlin.test.assertEquals
import tech.relaycorp.poweb.internal.protobuf_messages.handshake.Response as PBResponse

class ResponseTest {
val signature1 = "signature1".toByteArray()
val signature2 = "signature2".toByteArray()

@Nested
inner class Serialize {
@Test
fun `Response with no signatures`() {
val response = Response(arrayOf())

val serialization = response.serialize()

val pbResponse = PBResponse.parseFrom(serialization)
assertEquals(0, pbResponse.gatewayNonceSignaturesCount)
}

@Test
fun `Response with one signature`() {
val response = Response(arrayOf(signature1))

val serialization = response.serialize()

val pbResponse = PBResponse.parseFrom(serialization)
assertEquals(1, pbResponse.gatewayNonceSignaturesCount)
assertEquals(
listOf(signature1.asList()),
pbResponse.gatewayNonceSignaturesList.map { it.toByteArray().asList() }
)
}

@Test
fun `Response with two signatures`() {
val response = Response(arrayOf(signature1, signature2))

val serialization = response.serialize()

val pbResponse = PBResponse.parseFrom(serialization)
assertEquals(2, pbResponse.gatewayNonceSignaturesCount)
assertEquals(
listOf(signature1.asList(), signature2.asList()),
pbResponse.gatewayNonceSignaturesList.map { it.toByteArray().asList() }
)
}
}

@Nested
inner class Deserialize {
@Test
fun `Invalid serialization should be refused`() {
val serialization = "This is invalid".toByteArray()

val exception = assertThrows<InvalidMessageException> {
Response.deserialize(serialization)
}

assertEquals("Message is not a valid response", exception.message)
}

@Test
fun `Valid serialization with no signatures should be accepted`() {
val serialization = PBResponse.newBuilder().build().toByteArray()

val response = Response.deserialize(serialization)

assertEquals(0, response.nonceSignatures.size)
}

@Test
fun `Valid serialization with one signature should be accepted`() {
val serialization = PBResponse.newBuilder()
.addGatewayNonceSignatures(ByteString.copyFrom(signature1))
.build()
.toByteArray()

val response = Response.deserialize(serialization)

assertEquals(signature1.asList(), response.nonceSignatures.first().asList())
}

@Test
fun `Valid serialization with multiple signatures should be accepted`() {
val serialization = PBResponse.newBuilder()
.addAllGatewayNonceSignatures(mutableListOf(
ByteString.copyFrom(signature1),
ByteString.copyFrom(signature2))
)
.build().toByteArray()

val response = Response.deserialize(serialization)

assertEquals(signature1.asList(), response.nonceSignatures.first().asList())
}
}
}

0 comments on commit 75a6b64

Please sign in to comment.