-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
251 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
90 changes: 90 additions & 0 deletions
90
knet/src/main/kotlin/com/jasonernst/knet/ip/options/Ipv4Option.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package com.jasonernst.knet.ip.options | ||
|
||
import com.jasonernst.knet.PacketTooShortException | ||
import java.nio.ByteBuffer | ||
import java.nio.ByteOrder | ||
|
||
// because kotlin doesn't have a direct conversion function apparently... | ||
// https://stackoverflow.com/questions/46401879/boolean-int-conversion-in-kotlin | ||
fun Boolean.toInt() = if (this) 1 else 0 | ||
|
||
/** | ||
* From RFC791, page 15: | ||
* | ||
* The option field is variable in length. There may be zero or more | ||
* options. There are two cases for the format of an option: | ||
* | ||
* Case 1: A single octet of option-type. | ||
* | ||
* Case 2: An option-type octet, an option-length octet, and the | ||
* actual option-data octets. | ||
* | ||
* The option-length octet counts the option-type octet and the | ||
* option-length octet as well as the option-data octets. | ||
* | ||
* The option-type octet is viewed as having 3 fields: | ||
* | ||
* 1 bit copied flag, | ||
* 2 bits option class, | ||
* 5 bits option number. | ||
* | ||
* The copied flag indicates that this option is copied into all | ||
* fragments on fragmentation. | ||
* | ||
* 0 = not copied | ||
* 1 = copied | ||
*/ | ||
abstract class Ipv4Option( | ||
open val isCopied: Boolean = true, | ||
open val optionClass: Ipv4OptionClassType, | ||
open val type: Ipv4OptionType, | ||
open val size: UByte, | ||
) { | ||
companion object { | ||
fun parseOptions( | ||
stream: ByteBuffer, | ||
limit: Int, | ||
): 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 optionClass = Ipv4OptionClassType.fromKind(classByte.toUByte()) | ||
val kind = (kindOctet.toInt() and 0b00011111).toUByte() | ||
if (kind == Ipv4OptionType.EndOfOptionList.kind) { | ||
options.add(Ipv4OptionEndOfOptionList(isCopied, optionClass)) | ||
break | ||
} else if (kind == Ipv4OptionType.NoOperation.kind) { | ||
options.add(Ipv4OptionNoOperation(isCopied, optionClass)) | ||
} else { | ||
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()) { | ||
throw PacketTooShortException("Can't parse ipv4 option because we don't have enough bytes left for the data") | ||
} | ||
val data = ByteArray(length.toInt()) | ||
stream.get(data) | ||
|
||
val type = | ||
try { | ||
Ipv4OptionType.fromKind(kind) | ||
} catch (e: NoSuchElementException) { | ||
Ipv4OptionType.Unknown | ||
} | ||
options.add(Ipv4OptionUnknown(isCopied, optionClass, type, length, data)) | ||
} | ||
} | ||
return options | ||
} | ||
} | ||
|
||
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()) | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
knet/src/main/kotlin/com/jasonernst/knet/ip/options/Ipv4OptionClassType.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package com.jasonernst.knet.ip.options | ||
|
||
/** | ||
* The option classes are: | ||
* | ||
* 0 = control | ||
* 1 = reserved for future use | ||
* 2 = debugging and measurement | ||
* 3 = reserved for future use | ||
* | ||
* Since we only have 2 bits, any other value makes no sense and should | ||
* rightfully throw an exception when trying to parse it. | ||
*/ | ||
enum class Ipv4OptionClassType( | ||
val kind: UByte, | ||
) { | ||
Control(0u), | ||
Reserved1(1u), | ||
DebuggingAndMeasurement(2u), | ||
Reserved2(3u), | ||
; | ||
|
||
companion object { | ||
fun fromKind(kind: UByte) = entries.first { it.kind == kind } | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
knet/src/main/kotlin/com/jasonernst/knet/ip/options/Ipv4OptionEndOfOptionList.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.jasonernst.knet.ip.options | ||
|
||
data class Ipv4OptionEndOfOptionList( | ||
override val isCopied: Boolean, | ||
override val optionClass: Ipv4OptionClassType, | ||
override val type: Ipv4OptionType = Ipv4OptionType.EndOfOptionList, | ||
override val size: UByte = 1u, | ||
) : Ipv4Option(isCopied, optionClass, type, size) |
8 changes: 8 additions & 0 deletions
8
knet/src/main/kotlin/com/jasonernst/knet/ip/options/Ipv4OptionNoOperation.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.jasonernst.knet.ip.options | ||
|
||
data class Ipv4OptionNoOperation( | ||
override val isCopied: Boolean, | ||
override val optionClass: Ipv4OptionClassType, | ||
override val type: Ipv4OptionType = Ipv4OptionType.NoOperation, | ||
override val size: UByte = 1u, | ||
) : Ipv4Option(isCopied, optionClass, type, size) |
25 changes: 25 additions & 0 deletions
25
knet/src/main/kotlin/com/jasonernst/knet/ip/options/Ipv4OptionType.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.jasonernst.knet.ip.options | ||
|
||
/** | ||
* https://datatracker.ietf.org/doc/html/rfc791 page 15 | ||
*/ | ||
enum class Ipv4OptionType( | ||
val kind: UByte, | ||
) { | ||
EndOfOptionList(0u), | ||
NoOperation(1u), | ||
Security(2u), | ||
LooseSourceRouting(3u), | ||
StrictSourceRouting(9u), | ||
RecordRoute(7u), | ||
StreamId(8u), | ||
TimeStamp(4u), | ||
|
||
// fake type we defined for when we don't have the type in the enum | ||
Unknown(99u), | ||
; | ||
|
||
companion object { | ||
fun fromKind(kind: UByte) = entries.first { it.kind == kind } | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
knet/src/main/kotlin/com/jasonernst/knet/ip/options/Ipv4OptionUnknown.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.jasonernst.knet.ip.options | ||
|
||
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, | ||
) : Ipv4Option(isCopied, optionClass, type, size) { | ||
override fun toByteArray(order: ByteOrder): ByteArray { | ||
val buffer = ByteBuffer.allocate(2 + data.size) | ||
buffer.put(super.toByteArray(order)) // get the type byte sorted out | ||
buffer.put(size.toByte()) | ||
buffer.put(data) | ||
return buffer.array() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
knet/src/test/kotlin/com/jasonernst/knet/ip/options/Ipv4OptionTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package com.jasonernst.knet.ip.options | ||
|
||
import org.junit.jupiter.api.Assertions.assertEquals | ||
import org.junit.jupiter.api.Assertions.assertTrue | ||
import org.junit.jupiter.api.Test | ||
import org.slf4j.LoggerFactory | ||
import java.nio.ByteBuffer | ||
|
||
class Ipv4OptionTest { | ||
private val logger = LoggerFactory.getLogger(javaClass) | ||
|
||
@Test fun parseOptions() { | ||
val options = | ||
arrayListOf( | ||
Ipv4OptionNoOperation(true, Ipv4OptionClassType.Control), | ||
Ipv4OptionEndOfOptionList(true, Ipv4OptionClassType.Control), | ||
) | ||
val optionSize = options.sumOf { it.size.toInt() } | ||
val stream = ByteBuffer.allocate(optionSize) | ||
for (option in options) { | ||
stream.put(option.toByteArray()) | ||
} | ||
stream.rewind() | ||
val parsedOptions = Ipv4Option.parseOptions(stream, optionSize) | ||
assertEquals(options, parsedOptions) | ||
} | ||
|
||
@Test fun unknownOption() { | ||
// unhandled option, but in list | ||
val stream = ByteBuffer.wrap(byteArrayOf(0xFE.toByte(), 0x04, 0x00, 0x00)) | ||
val parsedOptions = Ipv4Option.parseOptions(stream, 4) | ||
assertTrue(parsedOptions[0] is Ipv4OptionUnknown) | ||
|
||
// unhandled option, not in list | ||
val stream2 = ByteBuffer.wrap(byteArrayOf(0x02.toByte(), 0x04, 0x00, 0x00)) | ||
val parsedOptions2 = Ipv4Option.parseOptions(stream2, 4) | ||
assertTrue(parsedOptions2[0] is Ipv4OptionUnknown) | ||
} | ||
|
||
@Test fun unknownOptionTooShort() { | ||
// wip | ||
} | ||
} |