Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Implement high-level classes to manipulate handshake messages #15

Merged
merged 5 commits into from
Jul 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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())
}
}
}