Skip to content

Commit e08c598

Browse files
committed
Add TCP options
1 parent e8b247a commit e08c598

21 files changed

Lines changed: 575 additions & 18 deletions

src/main/java/com/github/hirsivaja/ip/tcp/TcpHeader.java

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.github.hirsivaja.ip.tcp;
22

3-
import com.github.hirsivaja.ip.ByteArray;
3+
import com.github.hirsivaja.ip.tcp.option.TcpOption;
44
import java.nio.ByteBuffer;
5+
import java.util.ArrayList;
6+
import java.util.List;
57

68
public record TcpHeader(
79
short srcPort,
@@ -12,37 +14,40 @@ public record TcpHeader(
1214
short windowSize,
1315
short checksum,
1416
short urgentPointer,
15-
ByteArray options) {
17+
List<TcpOption> options) {
1618
public static final int TCP_HEADER_LEN = 20;
1719
private static final int DATA_OFFSET_SHIFT = 4;
1820

19-
@SuppressWarnings("squid:S00107")
2021
public TcpHeader(short srcPort, short dstPort, int sequenceNumber, int ackNumber, TcpFlags flags, short windowSize,
21-
short urgentPointer, byte[] options) {
22-
this(srcPort, dstPort, sequenceNumber, ackNumber, flags, windowSize, (short) 0, urgentPointer, options);
22+
short urgentPointer) {
23+
this(srcPort, dstPort, sequenceNumber, ackNumber, flags, windowSize, (short) 0, urgentPointer, List.of());
2324
}
2425

2526
@SuppressWarnings("squid:S00107")
2627
public TcpHeader(short srcPort, short dstPort, int sequenceNumber, int ackNumber, TcpFlags flags, short windowSize,
27-
short checksum, short urgentPointer, byte[] options) {
28-
this(srcPort, dstPort, sequenceNumber, ackNumber, flags, windowSize, checksum, urgentPointer, new ByteArray(options));
28+
short urgentPointer, List<TcpOption> options) {
29+
this(srcPort, dstPort, sequenceNumber, ackNumber, flags, windowSize, (short) 0, urgentPointer, options);
2930
}
3031

3132
public void encode(ByteBuffer out) {
3233
out.putShort(srcPort);
3334
out.putShort(dstPort);
3435
out.putInt(sequenceNumber);
3536
out.putInt(ackNumber);
36-
out.put((byte) (((options.array().length / 4) + 5) << DATA_OFFSET_SHIFT));
37+
out.put((byte) (((optionsLength() / 4) + 5) << DATA_OFFSET_SHIFT));
3738
out.put(flags.toByte());
3839
out.putShort(windowSize);
3940
out.putShort(checksum);
4041
out.putShort(urgentPointer);
41-
out.put(options.array());
42+
options.forEach(option -> option.encode(out));
4243
}
4344

4445
public int length() {
45-
return TCP_HEADER_LEN + options.array().length;
46+
return TCP_HEADER_LEN + optionsLength();
47+
}
48+
49+
public int optionsLength() {
50+
return options.stream().mapToInt(TcpOption::length).sum();
4651
}
4752

4853
public static TcpHeader decode(ByteBuffer in) {
@@ -55,15 +60,19 @@ public static TcpHeader decode(ByteBuffer in) {
5560
short windowSize = in.getShort();
5661
short checksum = in.getShort();
5762
short urgentPointer = in.getShort();
58-
byte[] options = new byte[(dataOffset - 5) * 4];
59-
in.get(options);
63+
List<TcpOption> options = new ArrayList<>();
64+
int optionsLength = (dataOffset - 5) * 4;
65+
while(optionsLength > 0) {
66+
TcpOption option = TcpOption.decode(in);
67+
options.add(option);
68+
optionsLength -= option.length();
69+
}
70+
if(optionsLength != 0) {
71+
throw new IllegalArgumentException("Could not decode options for the TCP header.");
72+
}
6073
return new TcpHeader(srcPort, dstPort, sequenceNumber, ackNumber, flags, windowSize, checksum, urgentPointer, options);
6174
}
6275

63-
public byte[] rawOptions() {
64-
return options.array();
65-
}
66-
6776
public int uSrcPort() {
6877
return Short.toUnsignedInt(srcPort);
6978
}

src/main/java/com/github/hirsivaja/ip/tcp/TcpMessagePayload.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public TcpMessagePayload(IpHeader header, TcpHeader tcpHeader, ByteArray payload
2525
tcpHeader.checksum();
2626
this.tcpHeader = new TcpHeader(tcpHeader.srcPort(), tcpHeader.dstPort(), tcpHeader.sequenceNumber(),
2727
tcpHeader.ackNumber(), tcpHeader.flags(), tcpHeader.windowSize(), checksum,
28-
tcpHeader.urgentPointer(), tcpHeader.rawOptions());
28+
tcpHeader.urgentPointer(), tcpHeader.options());
2929
this.payload = payload;
3030
}
3131

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.github.hirsivaja.ip.tcp.option;
2+
3+
import com.github.hirsivaja.ip.ByteArray;
4+
import java.nio.ByteBuffer;
5+
6+
public record EncryptionNegotiation(ByteArray data) implements TcpOption {
7+
8+
public EncryptionNegotiation(byte[] data) {
9+
this(new ByteArray(data));
10+
}
11+
12+
@Override
13+
public void encode(ByteBuffer out) {
14+
out.put(optionType().type());
15+
out.put((byte) (length()));
16+
out.put(data.array());
17+
}
18+
19+
@Override
20+
public int length() {
21+
return 2 + data.length();
22+
}
23+
24+
@Override
25+
public TcpOptionType optionType() {
26+
return TcpOptionType.ENCRYPTION_NEGOTIATION;
27+
}
28+
29+
public static EncryptionNegotiation decode(ByteBuffer in){
30+
byte[] data = new byte[in.remaining()];
31+
in.get(data);
32+
return new EncryptionNegotiation(data);
33+
}
34+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.github.hirsivaja.ip.tcp.option;
2+
3+
import java.nio.ByteBuffer;
4+
5+
public record Eool() implements TcpOption {
6+
7+
@Override
8+
public void encode(ByteBuffer out) {
9+
out.put(optionType().type());
10+
}
11+
12+
@Override
13+
public int length() {
14+
return 1;
15+
}
16+
17+
@Override
18+
public TcpOptionType optionType() {
19+
return TcpOptionType.END_OF_OPTIONS_LIST;
20+
}
21+
22+
public static TcpOption decode() {
23+
return new Eool();
24+
}
25+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.github.hirsivaja.ip.tcp.option;
2+
3+
import com.github.hirsivaja.ip.ByteArray;
4+
import java.nio.ByteBuffer;
5+
6+
public record GenericTcpOption(TcpOptionType optionType, ByteArray data) implements TcpOption {
7+
8+
public GenericTcpOption(TcpOptionType optionType, byte[] data) {
9+
this(optionType, new ByteArray(data));
10+
}
11+
12+
@Override
13+
public void encode(ByteBuffer out) {
14+
out.put(optionType().type());
15+
out.put((byte) (length()));
16+
out.put(data.array());
17+
}
18+
19+
@Override
20+
public int length() {
21+
return data.length() + 2;
22+
}
23+
24+
public static GenericTcpOption decode(ByteBuffer in, TcpOptionType optionType){
25+
byte[] data = new byte[in.remaining()];
26+
in.get(data);
27+
return new GenericTcpOption(optionType, data);
28+
}
29+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.github.hirsivaja.ip.tcp.option;
2+
3+
import java.nio.ByteBuffer;
4+
5+
public record MaximumSegmentSize(short maximumSegmentSize) implements TcpOption {
6+
7+
@Override
8+
public void encode(ByteBuffer out) {
9+
out.put(optionType().type());
10+
out.put((byte) (length()));
11+
out.putShort(maximumSegmentSize);
12+
}
13+
14+
@Override
15+
public int length() {
16+
return 4;
17+
}
18+
19+
@Override
20+
public TcpOptionType optionType() {
21+
return TcpOptionType.MAXIMUM_SEGMENT_SIZE;
22+
}
23+
24+
public static MaximumSegmentSize decode(ByteBuffer in){
25+
short maximumSegmentSize = in.getShort();
26+
return new MaximumSegmentSize(maximumSegmentSize);
27+
}
28+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.github.hirsivaja.ip.tcp.option;
2+
3+
import com.github.hirsivaja.ip.ByteArray;
4+
import java.nio.ByteBuffer;
5+
6+
public record MultipathTcp(ByteArray subtypeData) implements TcpOption {
7+
8+
public MultipathTcp(byte[] subtypeData) {
9+
this(new ByteArray(subtypeData));
10+
}
11+
12+
@Override
13+
public void encode(ByteBuffer out) {
14+
out.put(optionType().type());
15+
out.put((byte) (length()));
16+
out.put(subtypeData.array());
17+
}
18+
19+
@Override
20+
public int length() {
21+
return 2 + subtypeData.length();
22+
}
23+
24+
@Override
25+
public TcpOptionType optionType() {
26+
return TcpOptionType.MULTIPATH_TCP;
27+
}
28+
29+
public static MultipathTcp decode(ByteBuffer in){
30+
byte[] subtypeData = new byte[in.remaining()];
31+
in.get(subtypeData);
32+
return new MultipathTcp(subtypeData);
33+
}
34+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.github.hirsivaja.ip.tcp.option;
2+
3+
import java.nio.ByteBuffer;
4+
5+
public record Nop() implements TcpOption {
6+
7+
@Override
8+
public void encode(ByteBuffer out) {
9+
out.put(optionType().type());
10+
}
11+
12+
@Override
13+
public int length() {
14+
return 1;
15+
}
16+
17+
@Override
18+
public TcpOptionType optionType() {
19+
return TcpOptionType.NO_OPERATION;
20+
}
21+
22+
public static TcpOption decode() {
23+
return new Nop();
24+
}
25+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.github.hirsivaja.ip.tcp.option;
2+
3+
import java.nio.ByteBuffer;
4+
5+
public record QuickStart(byte functionAndRateRequest, byte qsTtl, int qsNonce) implements TcpOption {
6+
7+
@Override
8+
public void encode(ByteBuffer out) {
9+
out.put(optionType().type());
10+
out.put((byte) (length() - 2));
11+
out.put(functionAndRateRequest);
12+
out.put(qsTtl);
13+
out.putInt(qsNonce);
14+
}
15+
16+
@Override
17+
public int length() {
18+
return 8;
19+
}
20+
21+
@Override
22+
public TcpOptionType optionType() {
23+
return TcpOptionType.QUICK_START_RESPONSE;
24+
}
25+
26+
public static TcpOption decode(ByteBuffer in) {
27+
byte functionAndRateRequest = in.get();
28+
byte qsTtl = in.get();
29+
int qsNonce = in.getInt();
30+
return new QuickStart(functionAndRateRequest, qsTtl, qsNonce);
31+
}
32+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.github.hirsivaja.ip.tcp.option;
2+
3+
import java.nio.ByteBuffer;
4+
import java.util.ArrayList;
5+
import java.util.List;
6+
7+
public record Sack(List<Integer> blocks) implements TcpOption {
8+
9+
@Override
10+
public void encode(ByteBuffer out) {
11+
out.put(optionType().type());
12+
out.put((byte) (length()));
13+
blocks.forEach(out::putInt);
14+
}
15+
16+
@Override
17+
public int length() {
18+
return 2 + 4 * blocks.size();
19+
}
20+
21+
@Override
22+
public TcpOptionType optionType() {
23+
return TcpOptionType.SACK;
24+
}
25+
26+
public static Sack decode(ByteBuffer in){
27+
List<Integer> blocks = new ArrayList<>();
28+
for(int i = 0; i < in.remaining() / 4; i++) {
29+
blocks.add(in.getInt());
30+
}
31+
return new Sack(blocks);
32+
}
33+
}

0 commit comments

Comments
 (0)