Skip to content

Commit

Permalink
Added basic ivp4 option handling
Browse files Browse the repository at this point in the history
  • Loading branch information
compscidr committed Sep 13, 2024
1 parent a2083cb commit c1a89ed
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 17 deletions.
24 changes: 16 additions & 8 deletions knet/src/main/kotlin/com/jasonernst/knet/ip/options/Ipv4Option.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.jasonernst.knet.ip.options

import com.jasonernst.knet.PacketTooShortException
import org.slf4j.LoggerFactory
import java.nio.ByteBuffer
import java.nio.ByteOrder

Expand Down Expand Up @@ -41,16 +42,18 @@ abstract class Ipv4Option(
open val size: UByte,
) {

Check warning on line 43 in knet/src/main/kotlin/com/jasonernst/knet/ip/options/Ipv4Option.kt

View check run for this annotation

Codecov / codecov/patch

knet/src/main/kotlin/com/jasonernst/knet/ip/options/Ipv4Option.kt#L43

Added line #L43 was not covered by tests
companion object {
private val logger = LoggerFactory.getLogger(Ipv4Option::class.java)

fun parseOptions(
stream: ByteBuffer,
limit: Int,
limit: Int = stream.limit(),
): List<Ipv4Option> {
val options = ArrayList<Ipv4Option>()
while (stream.position() + 1 <= limit) {
val kindOctet = stream.get().toUByte()
// high bit is copied flag
val isCopied = kindOctet.toInt() and 0b10000000 == 0b10000000
val classByte = kindOctet.toInt() and 0b01100000 shr 5
val classByte = (kindOctet.toInt() and 0b01100000) shr 5
val optionClass = Ipv4OptionClassType.fromKind(classByte.toUByte())
val kind = (kindOctet.toInt() and 0b00011111).toUByte()
if (kind == Ipv4OptionType.EndOfOptionList.kind) {
Expand All @@ -62,12 +65,14 @@ abstract class Ipv4Option(
if (stream.remaining() < 1) {
throw PacketTooShortException("Can't determine length of ipv4 option because we have no bytes left")
}
// this length includes the previous two bytes
val length = (stream.get().toUByte() - 2u).toUByte()
if (stream.remaining() < length.toInt()) {
// this length includes the previous two bytes which is why we need adjustment
// we don't apply it directly to length because we want to construct the option
// with the correct length which includes the first two fields
val length = (stream.get().toUByte())
if (stream.remaining() < length.toInt() - 2) {
throw PacketTooShortException("Can't parse ipv4 option because we don't have enough bytes left for the data")
}
val data = ByteArray(length.toInt())
val data = ByteArray(length.toInt() - 2)
stream.get(data)

val type =
Expand All @@ -84,7 +89,10 @@ abstract class Ipv4Option(
}

open fun toByteArray(order: ByteOrder = ByteOrder.BIG_ENDIAN): ByteArray {
val typeInt = isCopied.toInt() shl 7 or (optionClass.kind.toInt() shl 5) or type.kind.toInt()
return byteArrayOf(typeInt.toByte())
val copiedInt = (isCopied.toInt() shl 7)
val classInt = optionClass.kind.toInt() shl 5
val typeInt = type.kind.toInt()
val typeByte = (copiedInt + classInt + typeInt).toByte()
return byteArrayOf(typeByte)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ enum class Ipv4OptionType(
TimeStamp(4u),

// fake type we defined for when we don't have the type in the enum
Unknown(99u),
Unknown(31u), // max value since this is 5 bits
;

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,45 @@ import java.nio.ByteBuffer
import java.nio.ByteOrder

data class Ipv4OptionUnknown(
override val isCopied: Boolean,
override val optionClass: Ipv4OptionClassType,
override val type: Ipv4OptionType,
override val size: UByte,
val data: ByteArray,
override val isCopied: Boolean = true,
override val optionClass: Ipv4OptionClassType = Ipv4OptionClassType.Control,
override val type: Ipv4OptionType = Ipv4OptionType.Unknown,
override val size: UByte = MINIMUM_SIZE,
val data: ByteArray = ByteArray(0),
) : Ipv4Option(isCopied, optionClass, type, size) {
companion object {
val MINIMUM_SIZE: UByte = 2u // 1 byte for type, 1 byte for size
}

override fun toByteArray(order: ByteOrder): ByteArray {
val buffer = ByteBuffer.allocate(2 + data.size)
val buffer = ByteBuffer.allocate(MINIMUM_SIZE.toInt() + data.size)
buffer.put(super.toByteArray(order)) // get the type byte sorted out
buffer.put(size.toByte())
buffer.put(data)
return buffer.array()
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as Ipv4OptionUnknown

if (isCopied != other.isCopied) return false
if (optionClass != other.optionClass) return false
if (type != other.type) return false
if (size != other.size) return false
if (!data.contentEquals(other.data)) return false

return true
}

override fun hashCode(): Int {
var result = isCopied.hashCode()
result = 31 * result + optionClass.hashCode()
result = 31 * result + type.hashCode()
result = 31 * result + size.hashCode()
result = 31 * result + data.contentHashCode()
return result
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.jasonernst.knet.ip.options

import com.jasonernst.knet.PacketTooShortException
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.slf4j.LoggerFactory
import java.nio.ByteBuffer

Expand All @@ -25,6 +28,18 @@ class Ipv4OptionTest {
assertEquals(options, parsedOptions)
}

@Test fun copiedClassTypeTest() {
val option =
Ipv4OptionUnknown(isCopied = true, optionClass = Ipv4OptionClassType.DebuggingAndMeasurement, type = Ipv4OptionType.StreamId)
val typeByte = option.toByteArray()[0]
val copied = typeByte.toInt() and 0b10000000 == 0b10000000
assertTrue(copied)
val classByte = (typeByte.toInt() and 0b01100000 shr 5).toUByte()
assertEquals(Ipv4OptionClassType.DebuggingAndMeasurement.kind, classByte)
val kind = (typeByte.toInt() and 0b00011111).toUByte()
assertEquals(Ipv4OptionType.StreamId.kind, kind)
}

@Test fun unknownOption() {
// unhandled option, but in list
val stream = ByteBuffer.wrap(byteArrayOf(0xFE.toByte(), 0x04, 0x00, 0x00))
Expand All @@ -35,9 +50,80 @@ class Ipv4OptionTest {
val stream2 = ByteBuffer.wrap(byteArrayOf(0x02.toByte(), 0x04, 0x00, 0x00))
val parsedOptions2 = Ipv4Option.parseOptions(stream2, 4)
assertTrue(parsedOptions2[0] is Ipv4OptionUnknown)

// unknown option good path (failing for some reason)
val unhandledOption =
Ipv4OptionUnknown(isCopied = true, optionClass = Ipv4OptionClassType.DebuggingAndMeasurement, type = Ipv4OptionType.Unknown)
val stream3 = ByteBuffer.wrap(unhandledOption.toByteArray())
val parsedOptions3 = Ipv4Option.parseOptions(stream3)
assertEquals(1, parsedOptions3.size)
assertEquals(unhandledOption, parsedOptions3[0])
}

@Test fun unknownOptionTooShortLength() {
val stream = ByteBuffer.wrap(byteArrayOf(0xFE.toByte()))
assertThrows<PacketTooShortException> {
Ipv4Option.parseOptions(stream)
}
}

@Test fun unknownOptionTooShortLengthOk() {
val stream = ByteBuffer.wrap(byteArrayOf(0xFE.toByte(), 0x04, 0x00))
assertThrows<PacketTooShortException> {
Ipv4Option.parseOptions(stream, 3)
}
}

@Test fun unknownOptionEquals() {
val option1 =
Ipv4OptionUnknown(isCopied = true, optionClass = Ipv4OptionClassType.DebuggingAndMeasurement, type = Ipv4OptionType.Unknown)
val option2 =
Ipv4OptionUnknown(isCopied = true, optionClass = Ipv4OptionClassType.DebuggingAndMeasurement, type = Ipv4OptionType.Unknown)
assertEquals(option1, option2)

val option3 = Ipv4OptionEndOfOptionList(isCopied = true, optionClass = Ipv4OptionClassType.DebuggingAndMeasurement)
assertNotEquals(option1, option3)

val option4 =
Ipv4OptionUnknown(isCopied = false, optionClass = Ipv4OptionClassType.DebuggingAndMeasurement, type = Ipv4OptionType.Unknown)
assertNotEquals(option1, option4)

val option5 = Ipv4OptionUnknown(isCopied = true, optionClass = Ipv4OptionClassType.Control, type = Ipv4OptionType.Unknown)
assertNotEquals(option1, option5)

val option6 =
Ipv4OptionUnknown(
isCopied = true,
optionClass = Ipv4OptionClassType.DebuggingAndMeasurement,
type = Ipv4OptionType.EndOfOptionList,
)
assertNotEquals(option1, option6)

val option7 =
Ipv4OptionUnknown(
isCopied = true,
optionClass = Ipv4OptionClassType.DebuggingAndMeasurement,
type = Ipv4OptionType.Unknown,
size = 10u,
)
assertNotEquals(option1, option7)

val option8 =
Ipv4OptionUnknown(
isCopied = true,
optionClass = Ipv4OptionClassType.DebuggingAndMeasurement,
type = Ipv4OptionType.Unknown,
size = 10u,
data = byteArrayOf(0x00, 0x01, 0x02),
)
assertNotEquals(option7, option8)
}

@Test fun unknownOptionTooShort() {
// wip
@Test fun unknownOptionHashCodeTest() {
val map: MutableMap<Ipv4OptionUnknown, String> = mutableMapOf()
val option1 =
Ipv4OptionUnknown(isCopied = true, optionClass = Ipv4OptionClassType.DebuggingAndMeasurement, type = Ipv4OptionType.Unknown)
map[option1] = "test"
assertTrue(map.containsKey(option1))
}
}

0 comments on commit c1a89ed

Please sign in to comment.