destroy();
11 | }
12 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/connection/ServerStateMachine.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.connection;
2 |
3 | import com.protocol7.quincy.protocol.TransportError;
4 | import com.protocol7.quincy.protocol.frames.ConnectionCloseFrame;
5 | import com.protocol7.quincy.protocol.frames.FrameType;
6 | import com.protocol7.quincy.protocol.packets.InitialPacket;
7 | import com.protocol7.quincy.protocol.packets.Packet;
8 | import com.protocol7.quincy.tls.EncryptionLevel;
9 |
10 | public class ServerStateMachine extends StateMachine {
11 |
12 | public synchronized void handlePacket(final Connection connection, final Packet packet) {
13 | if (getState() == State.Started) {
14 | if (packet instanceof InitialPacket) {
15 | final InitialPacket initialPacket = (InitialPacket) packet;
16 |
17 | if (initialPacket.getToken().isPresent()) {
18 | // TODO remove?
19 | connection.setRemoteConnectionId(packet.getSourceConnectionId());
20 | }
21 | }
22 | }
23 | }
24 |
25 | public void closeImmediate(final Connection connection, final ConnectionCloseFrame ccf) {
26 | connection.send(EncryptionLevel.OneRtt, ccf);
27 |
28 | setState(State.Closing);
29 |
30 | setState(State.Closed);
31 | }
32 |
33 | public void closeImmediate(final Connection connection) {
34 | closeImmediate(
35 | connection,
36 | new ConnectionCloseFrame(
37 | TransportError.NO_ERROR.getValue(), FrameType.PADDING, "Closing connection"));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/connection/State.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.connection;
2 |
3 | public enum State {
4 | Started,
5 | BeforeHello,
6 | BeforeHandshake,
7 | BeforeDone,
8 | Done,
9 | Closing,
10 | Closed
11 | }
12 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/connection/StateMachine.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.connection;
2 |
3 | import com.protocol7.quincy.protocol.frames.ConnectionCloseFrame;
4 | import com.protocol7.quincy.protocol.packets.Packet;
5 |
6 | public abstract class StateMachine {
7 | private State state = State.Started;
8 |
9 | abstract void handlePacket(final Connection connection, final Packet packet);
10 |
11 | abstract void closeImmediate(final Connection connection, final ConnectionCloseFrame ccf);
12 |
13 | abstract void closeImmediate(final Connection connection);
14 |
15 | public State getState() {
16 | return state;
17 | }
18 |
19 | public void setState(final State state) {
20 | this.state = state;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/flowcontrol/FlowControlHandler.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.flowcontrol;
2 |
3 | import com.protocol7.quincy.InboundHandler;
4 | import com.protocol7.quincy.OutboundHandler;
5 |
6 | public interface FlowControlHandler extends InboundHandler, OutboundHandler {}
7 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/flowcontrol/TryConsumeResult.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.flowcontrol;
2 |
3 | public class TryConsumeResult {
4 | private final boolean success;
5 | private final long connectionOffset;
6 | private final long connectionMax;
7 | private final long streamOffset;
8 | private final long streamMax;
9 |
10 | public TryConsumeResult(
11 | final boolean success,
12 | final long connectionOffset,
13 | final long connectionMax,
14 | final long streamOffset,
15 | final long streamMax) {
16 | this.success = success;
17 | this.connectionOffset = connectionOffset;
18 | this.connectionMax = connectionMax;
19 | this.streamOffset = streamOffset;
20 | this.streamMax = streamMax;
21 | }
22 |
23 | public boolean isSuccess() {
24 | return success;
25 | }
26 |
27 | public long getConnectionOffset() {
28 | return connectionOffset;
29 | }
30 |
31 | public long getConnectionMax() {
32 | return connectionMax;
33 | }
34 |
35 | public long getStreamOffset() {
36 | return streamOffset;
37 | }
38 |
39 | public long getStreamMax() {
40 | return streamMax;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/logging/LoggingHandler.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.logging;
2 |
3 | import com.protocol7.quincy.InboundHandler;
4 | import com.protocol7.quincy.OutboundHandler;
5 | import com.protocol7.quincy.PipelineContext;
6 | import com.protocol7.quincy.protocol.packets.Packet;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 |
10 | public class LoggingHandler implements InboundHandler, OutboundHandler {
11 |
12 | private final Logger log = LoggerFactory.getLogger(LoggingHandler.class);
13 |
14 | private final boolean isClient;
15 |
16 | public LoggingHandler(final boolean isClient) {
17 | this.isClient = isClient;
18 | }
19 |
20 | @Override
21 | public void onReceivePacket(final Packet packet, final PipelineContext ctx) {
22 | log.info("<<< {} received state={} packet={}", actor(), ctx.getState(), packet);
23 |
24 | ctx.next(packet);
25 | }
26 |
27 | @Override
28 | public void beforeSendPacket(final Packet packet, final PipelineContext ctx) {
29 | log.info(">>> {} sent state={} packet={}", actor(), ctx.getState(), packet);
30 |
31 | ctx.next(packet);
32 | }
33 |
34 | private String actor() {
35 | if (isClient) {
36 | return "Client";
37 | } else {
38 | return "Server";
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/PacketNumber.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol;
2 |
3 | import com.google.common.base.Preconditions;
4 | import com.protocol7.quincy.Varint;
5 |
6 | public class PacketNumber {
7 |
8 | public static long parse(final byte[] b) {
9 | if (b.length < 1 || b.length > 4) {
10 | throw new IllegalArgumentException("Invalid packet buffer length");
11 | }
12 |
13 | long number = 0;
14 | for (int i = 0; i < b.length; i++) {
15 | number = number << 8 | (b[i] & 0xFF);
16 | }
17 | return number;
18 | }
19 |
20 | public static final long MIN = 0;
21 | public static final long MAX = 0xFFFFFFFFL;
22 |
23 | public static long validate(final long number) {
24 | Preconditions.checkArgument(number >= 0);
25 | Preconditions.checkArgument(number <= Varint.MAX);
26 | return number;
27 | }
28 |
29 | public static long next(final long number) {
30 | return number + 1;
31 | }
32 |
33 | private static int getLength(final long number) {
34 | if (number < MIN) {
35 | throw new IllegalArgumentException("number too small");
36 | } else if (number <= 0xFF) {
37 | return 1;
38 | } else if (number <= 0xFFFF) {
39 | return 2;
40 | } else if (number <= 0xFFFFFF) {
41 | return 3;
42 | } else if (number <= 0xFFFFFFFFL) {
43 | return 4;
44 | } else {
45 | throw new IllegalArgumentException("number too large");
46 | }
47 | }
48 |
49 | public static byte[] write(final long number) {
50 | if (number < MIN || number > MAX) {
51 | throw new IllegalArgumentException("Invalid number");
52 | }
53 |
54 | final int length = getLength(number);
55 | final byte[] b = new byte[length];
56 | for (int j = length; j > 0; j--) {
57 | b[length - j] = (byte) ((number >> (8 * (j - 1))) & 0xFF);
58 | }
59 | return b;
60 | }
61 |
62 | private PacketNumber() {}
63 | }
64 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/StreamId.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol;
2 |
3 | import static com.protocol7.quincy.utils.Bits.set;
4 | import static com.protocol7.quincy.utils.Bits.unset;
5 |
6 | import com.google.common.base.Preconditions;
7 | import com.protocol7.quincy.Varint;
8 | import io.netty.buffer.ByteBuf;
9 |
10 | public class StreamId {
11 |
12 | private static long encodeType(final boolean client, final boolean bidirectional, final long id) {
13 | long res = id;
14 | if (client) {
15 | res = unset(res, 0);
16 | } else {
17 | res = set(res, 0);
18 | }
19 | if (bidirectional) {
20 | res = unset(res, 1);
21 | } else {
22 | res = set(res, 1);
23 | }
24 | return res;
25 | }
26 |
27 | public static long next(final long prev, final boolean client, final boolean bidirectional) {
28 | long v = encodeType(client, bidirectional, prev);
29 |
30 | long tmp = v;
31 | while (v <= prev) {
32 | tmp++;
33 | v = encodeType(client, bidirectional, tmp);
34 | }
35 |
36 | return v;
37 | }
38 |
39 | public static long parse(final ByteBuf bb) {
40 | return Varint.readAsLong(bb);
41 | }
42 |
43 | public static long validate(final long id) {
44 | Preconditions.checkArgument(id >= 0);
45 | Preconditions.checkArgument(id <= Varint.MAX);
46 | return id;
47 | }
48 |
49 | public static boolean isClient(final long id) {
50 | return (id & 1) == 0;
51 | }
52 |
53 | public static boolean isBidirectional(final long id) {
54 | return (id & 0b10) == 0;
55 | }
56 |
57 | public static void write(final ByteBuf bb, final long id) {
58 | Varint.write(id, bb);
59 | }
60 |
61 | private StreamId() {}
62 | }
63 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/TransportError.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol;
2 |
3 | public enum TransportError {
4 | NO_ERROR(0x0),
5 | INTERNAL_ERROR(0x1),
6 | SERVER_BUSY(0x2),
7 | FLOW_CONTROL_ERROR(0x3),
8 | STREAM_ID_ERROR(0x4),
9 | STREAM_STATE_ERROR(0x5),
10 | FINAL_OFFSET_ERROR(0x6),
11 | FRAME_ENCODING_ERROR(0x7),
12 | TRANSPORT_PARAMETER_ERROR(0x8),
13 | VERSION_NEGOTIATION_ERROR(0x9),
14 | PROTOCOL_VIOLATION(0xA);
15 |
16 | private final int value;
17 |
18 | TransportError(final int value) {
19 | this.value = value;
20 | }
21 |
22 | public int getValue() {
23 | return value;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/Version.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol;
2 |
3 | import static com.protocol7.quincy.utils.Hex.dehex;
4 | import static com.protocol7.quincy.utils.Hex.hex;
5 |
6 | import io.netty.buffer.ByteBuf;
7 | import java.util.Arrays;
8 |
9 | public class Version {
10 | public static final Version VERSION_NEGOTIATION = new Version(dehex("00000000"));
11 | public static final Version FINAL = new Version(dehex("00000001"));
12 | public static final Version DRAFT_29 = new Version(dehex("ff00001d"));
13 |
14 | public static Version read(final ByteBuf bb) {
15 | final byte[] l = new byte[4];
16 | bb.readBytes(l);
17 |
18 | if (Arrays.equals(l, VERSION_NEGOTIATION.version)) {
19 | return VERSION_NEGOTIATION;
20 | } else if (Arrays.equals(l, FINAL.version)) {
21 | return FINAL;
22 | } else if (Arrays.equals(l, DRAFT_29.version)) {
23 | return DRAFT_29;
24 | } else {
25 | return new Version(l);
26 | }
27 | }
28 |
29 | private final byte[] version;
30 |
31 | public Version(final byte[] version) {
32 | this.version = version;
33 | }
34 |
35 | public void write(final ByteBuf bb) {
36 | bb.writeBytes(version);
37 | }
38 |
39 | public byte[] asBytes() {
40 | return version;
41 | }
42 |
43 | @Override
44 | public boolean equals(final Object o) {
45 | if (this == o) return true;
46 | if (o == null || getClass() != o.getClass()) return false;
47 | final Version version1 = (Version) o;
48 | return Arrays.equals(version, version1.version);
49 | }
50 |
51 | @Override
52 | public int hashCode() {
53 | return Arrays.hashCode(version);
54 | }
55 |
56 | @Override
57 | public String toString() {
58 | return "Version[" + hex(version) + ']';
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/frames/AckRange.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static com.google.common.base.Preconditions.checkArgument;
4 |
5 | import com.protocol7.quincy.protocol.PacketNumber;
6 | import java.util.Objects;
7 |
8 | public class AckRange {
9 |
10 | private final long smallest;
11 | private final long largest;
12 |
13 | public AckRange(final long smallest, final long largest) {
14 | checkArgument(largest >= smallest);
15 |
16 | this.smallest = PacketNumber.validate(smallest);
17 | this.largest = PacketNumber.validate(largest);
18 | }
19 |
20 | public long getSmallest() {
21 | return smallest;
22 | }
23 |
24 | public long getLargest() {
25 | return largest;
26 | }
27 |
28 | @Override
29 | public boolean equals(final Object o) {
30 | if (this == o) return true;
31 | if (o == null || getClass() != o.getClass()) return false;
32 | final AckRange ackRange = (AckRange) o;
33 | return smallest == ackRange.smallest && largest == ackRange.largest;
34 | }
35 |
36 | @Override
37 | public int hashCode() {
38 | return Objects.hash(smallest, largest);
39 | }
40 |
41 | @Override
42 | public String toString() {
43 | return "AckRange[" + smallest + ".." + largest + ']';
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/frames/ApplicationCloseFrame.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import com.protocol7.quincy.Varint;
4 | import io.netty.buffer.ByteBuf;
5 | import java.nio.charset.StandardCharsets;
6 |
7 | public class ApplicationCloseFrame extends Frame {
8 |
9 | public static ApplicationCloseFrame parse(final ByteBuf bb) {
10 | final byte type = bb.readByte();
11 | if (type != 0x1d) {
12 | throw new IllegalArgumentException("Illegal frame type");
13 | }
14 |
15 | final int errorCode = Varint.readAsInt(bb);
16 |
17 | final int reasonPhraseLength = Varint.readAsInt(bb);
18 |
19 | final byte[] reasonPhraseBytes = new byte[reasonPhraseLength];
20 | bb.readBytes(reasonPhraseBytes);
21 |
22 | return new ApplicationCloseFrame(
23 | errorCode, new String(reasonPhraseBytes, StandardCharsets.UTF_8));
24 | }
25 |
26 | private final int errorCode;
27 | private final String reasonPhrase;
28 |
29 | public ApplicationCloseFrame(final int errorCode, final String reasonPhrase) {
30 | super(FrameType.CONNECTION_CLOSE);
31 | this.errorCode = errorCode;
32 | this.reasonPhrase = reasonPhrase;
33 | }
34 |
35 | public int getErrorCode() {
36 | return errorCode;
37 | }
38 |
39 | public String getReasonPhrase() {
40 | return reasonPhrase;
41 | }
42 |
43 | @Override
44 | public void write(final ByteBuf bb) {
45 | bb.writeByte(0x1d);
46 |
47 | Varint.write(errorCode, bb);
48 |
49 | final byte[] reasonPhraseBytes = reasonPhrase.getBytes(StandardCharsets.UTF_8);
50 |
51 | Varint.write(reasonPhraseBytes.length, bb);
52 | bb.writeBytes(reasonPhraseBytes);
53 | }
54 |
55 | @Override
56 | public String toString() {
57 | return "ApplicationCloseFrame{"
58 | + ", errorCode="
59 | + errorCode
60 | + ", reasonPhrase='"
61 | + reasonPhrase
62 | + '\''
63 | + '}';
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/frames/ConnectionCloseFrame.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import com.protocol7.quincy.Varint;
4 | import io.netty.buffer.ByteBuf;
5 | import java.nio.charset.StandardCharsets;
6 |
7 | public class ConnectionCloseFrame extends Frame {
8 |
9 | public static ConnectionCloseFrame parse(final ByteBuf bb) {
10 | final byte type = bb.readByte();
11 | if (type != 0x1c) {
12 | throw new IllegalArgumentException("Illegal frame type");
13 | }
14 |
15 | final int errorCode = Varint.readAsInt(bb);
16 | final FrameType frameType = FrameType.fromByte(Varint.readAsByte(bb));
17 |
18 | final int reasonPhraseLength = Varint.readAsInt(bb);
19 |
20 | final byte[] reasonPhraseBytes = new byte[reasonPhraseLength];
21 | bb.readBytes(reasonPhraseBytes);
22 |
23 | return new ConnectionCloseFrame(
24 | errorCode, frameType, new String(reasonPhraseBytes, StandardCharsets.UTF_8));
25 | }
26 |
27 | private final int errorCode;
28 | private final FrameType frameType;
29 | private final String reasonPhrase;
30 |
31 | public ConnectionCloseFrame(
32 | final int errorCode, final FrameType frameType, final String reasonPhrase) {
33 | super(FrameType.CONNECTION_CLOSE);
34 | this.errorCode = errorCode;
35 | this.frameType = frameType;
36 | this.reasonPhrase = reasonPhrase;
37 | }
38 |
39 | public int getErrorCode() {
40 | return errorCode;
41 | }
42 |
43 | public FrameType getFrameType() {
44 | return frameType;
45 | }
46 |
47 | public String getReasonPhrase() {
48 | return reasonPhrase;
49 | }
50 |
51 | @Override
52 | public void write(final ByteBuf bb) {
53 | bb.writeByte(0x1c);
54 |
55 | Varint.write(errorCode, bb);
56 | Varint.write(frameType.getType(), bb);
57 |
58 | final byte[] reasonPhraseBytes = reasonPhrase.getBytes(StandardCharsets.UTF_8);
59 |
60 | Varint.write(reasonPhraseBytes.length, bb);
61 | bb.writeBytes(reasonPhraseBytes);
62 | }
63 |
64 | @Override
65 | public String toString() {
66 | return "ConnectionCloseFrame{"
67 | + ", errorCode="
68 | + errorCode
69 | + ", frameType="
70 | + frameType
71 | + ", reasonPhrase='"
72 | + reasonPhrase
73 | + '\''
74 | + '}';
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/frames/CryptoFrame.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import com.protocol7.quincy.Varint;
4 | import com.protocol7.quincy.utils.Hex;
5 | import io.netty.buffer.ByteBuf;
6 |
7 | public class CryptoFrame extends Frame {
8 |
9 | public static CryptoFrame parse(final ByteBuf bb) {
10 | final byte type = bb.readByte();
11 | if (type != FrameType.CRYPTO.getType()) {
12 | throw new IllegalArgumentException("Illegal frame type");
13 | }
14 |
15 | final long offset = Varint.readAsLong(bb);
16 | final int length = Varint.readAsInt(bb);
17 | final byte[] cryptoData = new byte[length];
18 | bb.readBytes(cryptoData);
19 | return new CryptoFrame(offset, cryptoData);
20 | }
21 |
22 | private final long offset;
23 | private final byte[] cryptoData;
24 |
25 | public CryptoFrame(final long offset, final byte[] cryptoData) {
26 | super(FrameType.CRYPTO);
27 | this.offset = offset;
28 | this.cryptoData = cryptoData;
29 | }
30 |
31 | public long getOffset() {
32 | return offset;
33 | }
34 |
35 | public byte[] getCryptoData() {
36 | return cryptoData;
37 | }
38 |
39 | @Override
40 | public void write(final ByteBuf bb) {
41 | bb.writeByte(getType().getType());
42 | Varint.write(offset, bb);
43 | Varint.write(cryptoData.length, bb);
44 | bb.writeBytes(cryptoData);
45 | }
46 |
47 | @Override
48 | public String toString() {
49 | return "CryptoFrame{" + "offset=" + offset + ", cryptoData=" + Hex.hex(cryptoData) + '}';
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/frames/DataBlockedFrame.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import com.protocol7.quincy.Varint;
4 | import io.netty.buffer.ByteBuf;
5 | import java.util.Objects;
6 |
7 | public class DataBlockedFrame extends Frame {
8 |
9 | public static DataBlockedFrame parse(final ByteBuf bb) {
10 | final byte type = bb.readByte();
11 | if (type != FrameType.DATA_BLOCKED.getType()) {
12 | throw new IllegalArgumentException("Illegal frame type");
13 | }
14 |
15 | final long maxData = Varint.readAsLong(bb);
16 |
17 | return new DataBlockedFrame(maxData);
18 | }
19 |
20 | private final long maxData;
21 |
22 | public DataBlockedFrame(final long maxData) {
23 | super(FrameType.DATA_BLOCKED);
24 |
25 | this.maxData = maxData;
26 | }
27 |
28 | public long getMaxData() {
29 | return maxData;
30 | }
31 |
32 | @Override
33 | public void write(final ByteBuf bb) {
34 | bb.writeByte(getType().getType());
35 |
36 | Varint.write(maxData, bb);
37 | }
38 |
39 | @Override
40 | public boolean equals(final Object o) {
41 | if (this == o) return true;
42 | if (o == null || getClass() != o.getClass()) return false;
43 | final DataBlockedFrame that = (DataBlockedFrame) o;
44 | return maxData == that.maxData;
45 | }
46 |
47 | @Override
48 | public int hashCode() {
49 | return Objects.hash(maxData);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/frames/HandshakeDoneFrame.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import io.netty.buffer.ByteBuf;
4 |
5 | public class HandshakeDoneFrame extends Frame {
6 |
7 | public static final HandshakeDoneFrame INSTANCE = new HandshakeDoneFrame();
8 |
9 | public static HandshakeDoneFrame parse(final ByteBuf bb) {
10 | final byte type = bb.readByte();
11 | if (type != FrameType.HANDSHAKE_DONE.getType()) {
12 | throw new IllegalArgumentException("Illegal frame type");
13 | }
14 |
15 | return INSTANCE;
16 | }
17 |
18 | private HandshakeDoneFrame() {
19 | super(FrameType.PING);
20 | }
21 |
22 | @Override
23 | public int calculateLength() {
24 | return 1;
25 | }
26 |
27 | @Override
28 | public void write(final ByteBuf bb) {
29 | bb.writeByte(getType().getType());
30 | }
31 |
32 | @Override
33 | public String toString() {
34 | return "HandshakeDoneFrame";
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/frames/MaxDataFrame.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import com.protocol7.quincy.Varint;
4 | import io.netty.buffer.ByteBuf;
5 | import java.util.Objects;
6 |
7 | public class MaxDataFrame extends Frame {
8 |
9 | public static MaxDataFrame parse(final ByteBuf bb) {
10 | final byte type = bb.readByte();
11 | if (type != FrameType.MAX_DATA.getType()) {
12 | throw new IllegalArgumentException("Illegal frame type");
13 | }
14 |
15 | final long maxData = Varint.readAsLong(bb);
16 |
17 | return new MaxDataFrame(maxData);
18 | }
19 |
20 | private final long maxData;
21 |
22 | public MaxDataFrame(final long maxData) {
23 | super(FrameType.MAX_DATA);
24 |
25 | this.maxData = maxData;
26 | }
27 |
28 | public long getMaxData() {
29 | return maxData;
30 | }
31 |
32 | @Override
33 | public void write(final ByteBuf bb) {
34 | bb.writeByte(getType().getType());
35 |
36 | Varint.write(maxData, bb);
37 | }
38 |
39 | @Override
40 | public boolean equals(final Object o) {
41 | if (this == o) return true;
42 | if (o == null || getClass() != o.getClass()) return false;
43 | final MaxDataFrame that = (MaxDataFrame) o;
44 | return maxData == that.maxData;
45 | }
46 |
47 | @Override
48 | public int hashCode() {
49 | return Objects.hash(maxData);
50 | }
51 |
52 | @Override
53 | public String toString() {
54 | return "MaxDataFrame{" + "maxData=" + maxData + '}';
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/frames/MaxStreamDataFrame.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static java.util.Objects.requireNonNull;
4 |
5 | import com.protocol7.quincy.Varint;
6 | import com.protocol7.quincy.protocol.StreamId;
7 | import io.netty.buffer.ByteBuf;
8 | import java.util.Objects;
9 |
10 | public class MaxStreamDataFrame extends Frame {
11 |
12 | public static MaxStreamDataFrame parse(final ByteBuf bb) {
13 | final byte type = bb.readByte();
14 | if (type != FrameType.MAX_STREAM_DATA.getType()) {
15 | throw new IllegalArgumentException("Illegal frame type");
16 | }
17 |
18 | final long streamId = StreamId.parse(bb);
19 | final long maxStreamData = Varint.readAsLong(bb);
20 |
21 | return new MaxStreamDataFrame(streamId, maxStreamData);
22 | }
23 |
24 | private final long streamId;
25 | private final long maxStreamData;
26 |
27 | public MaxStreamDataFrame(final long streamId, final long maxStreamData) {
28 | super(FrameType.MAX_STREAM_DATA);
29 |
30 | requireNonNull(streamId);
31 |
32 | this.streamId = StreamId.validate(streamId);
33 | this.maxStreamData = maxStreamData;
34 | }
35 |
36 | public long getStreamId() {
37 | return streamId;
38 | }
39 |
40 | public long getMaxStreamData() {
41 | return maxStreamData;
42 | }
43 |
44 | @Override
45 | public void write(final ByteBuf bb) {
46 | bb.writeByte(getType().getType());
47 |
48 | StreamId.write(bb, streamId);
49 | Varint.write(maxStreamData, bb);
50 | }
51 |
52 | @Override
53 | public boolean equals(final Object o) {
54 | if (this == o) return true;
55 | if (o == null || getClass() != o.getClass()) return false;
56 | final MaxStreamDataFrame that = (MaxStreamDataFrame) o;
57 | return maxStreamData == that.maxStreamData && Objects.equals(streamId, that.streamId);
58 | }
59 |
60 | @Override
61 | public int hashCode() {
62 | return Objects.hash(streamId, maxStreamData);
63 | }
64 |
65 | @Override
66 | public String toString() {
67 | return "MaxStreamDataFrame{"
68 | + "streamId="
69 | + streamId
70 | + ", maxStreamData="
71 | + maxStreamData
72 | + '}';
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/frames/MaxStreamsFrame.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import com.protocol7.quincy.Varint;
4 | import io.netty.buffer.ByteBuf;
5 |
6 | public class MaxStreamsFrame extends Frame {
7 |
8 | private static final byte BIDI_TYPE = 0x12;
9 | private static final byte UNI_TYPE = 0x13;
10 |
11 | public static MaxStreamsFrame parse(final ByteBuf bb) {
12 | final byte type = bb.readByte();
13 | if (type != BIDI_TYPE && type != UNI_TYPE) {
14 | throw new IllegalArgumentException("Illegal frame type");
15 | }
16 |
17 | final boolean bidi = type == BIDI_TYPE;
18 | final long maxStreams = Varint.readAsLong(bb);
19 | return new MaxStreamsFrame(maxStreams, bidi);
20 | }
21 |
22 | private final long maxStreams;
23 | private final boolean bidi;
24 |
25 | public MaxStreamsFrame(final long maxStreams, final boolean bidi) {
26 | super(FrameType.MAX_STREAMS);
27 |
28 | this.maxStreams = maxStreams;
29 | this.bidi = bidi;
30 | }
31 |
32 | public long getMaxStreams() {
33 | return maxStreams;
34 | }
35 |
36 | public boolean isBidi() {
37 | return bidi;
38 | }
39 |
40 | @Override
41 | public void write(final ByteBuf bb) {
42 | if (bidi) {
43 | bb.writeByte(BIDI_TYPE);
44 | } else {
45 | bb.writeByte(UNI_TYPE);
46 | }
47 |
48 | Varint.write(maxStreams, bb);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/frames/NewToken.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import com.protocol7.quincy.Varint;
4 | import com.protocol7.quincy.utils.Hex;
5 | import io.netty.buffer.ByteBuf;
6 |
7 | public class NewToken extends Frame {
8 |
9 | public static NewToken parse(final ByteBuf bb) {
10 | final byte type = bb.readByte();
11 | if (type != FrameType.NEW_TOKEN.getType()) {
12 | throw new IllegalArgumentException("Illegal frame type");
13 | }
14 |
15 | final int len = Varint.readAsInt(bb);
16 | final byte[] token = new byte[len];
17 | bb.readBytes(token);
18 |
19 | return new NewToken(token);
20 | }
21 |
22 | private final byte[] token;
23 |
24 | public NewToken(final byte[] token) {
25 | super(FrameType.NEW_TOKEN);
26 |
27 | this.token = token;
28 | }
29 |
30 | public byte[] getToken() {
31 | return token;
32 | }
33 |
34 | @Override
35 | public void write(final ByteBuf bb) {
36 | bb.writeByte(getType().getType());
37 |
38 | Varint.write(token.length, bb);
39 | bb.writeBytes(token);
40 | }
41 |
42 | @Override
43 | public String toString() {
44 | return "NewToken{" + Hex.hex(token) + '}';
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/frames/PaddingFrame.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static com.google.common.base.Preconditions.checkArgument;
4 |
5 | import io.netty.buffer.ByteBuf;
6 | import java.util.Objects;
7 |
8 | public class PaddingFrame extends Frame {
9 |
10 | public static PaddingFrame parse(final ByteBuf bb) {
11 | int length = 0;
12 | while (bb.isReadable()) {
13 | final int pos = bb.readerIndex();
14 | final byte b = bb.readByte();
15 | if (b == 0) {
16 | length += 1;
17 | } else {
18 | bb.readerIndex(pos);
19 | break;
20 | }
21 | }
22 |
23 | if (length == 0) {
24 | throw new IllegalArgumentException("Illegal frame type");
25 | }
26 |
27 | return new PaddingFrame(length);
28 | }
29 |
30 | private final int length;
31 |
32 | public PaddingFrame(final int length) {
33 | super(FrameType.PADDING);
34 |
35 | checkArgument(length > 0);
36 | this.length = length;
37 | }
38 |
39 | @Override
40 | public int calculateLength() {
41 | return length;
42 | }
43 |
44 | @Override
45 | public void write(final ByteBuf bb) {
46 | final byte[] b = new byte[length];
47 | bb.writeBytes(b);
48 | }
49 |
50 | @Override
51 | public boolean equals(final Object o) {
52 | if (this == o) return true;
53 | if (o == null || getClass() != o.getClass()) return false;
54 | final PaddingFrame that = (PaddingFrame) o;
55 | return length == that.length;
56 | }
57 |
58 | @Override
59 | public int hashCode() {
60 | return Objects.hash(length);
61 | }
62 |
63 | @Override
64 | public String toString() {
65 | return "PaddingFrame(" + length + ")";
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/frames/PingFrame.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import io.netty.buffer.ByteBuf;
4 |
5 | public class PingFrame extends Frame {
6 |
7 | public static final PingFrame INSTANCE = new PingFrame();
8 |
9 | public static PingFrame parse(final ByteBuf bb) {
10 | final byte type = bb.readByte();
11 | if (type != FrameType.PING.getType()) {
12 | throw new IllegalArgumentException("Illegal frame type");
13 | }
14 |
15 | return INSTANCE;
16 | }
17 |
18 | private PingFrame() {
19 | super(FrameType.PING);
20 | }
21 |
22 | @Override
23 | public int calculateLength() {
24 | return 1;
25 | }
26 |
27 | @Override
28 | public void write(final ByteBuf bb) {
29 | bb.writeByte(getType().getType());
30 | }
31 |
32 | @Override
33 | public String toString() {
34 | return "PingFrame";
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/frames/RetireConnectionIdFrame.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import com.protocol7.quincy.Varint;
4 | import io.netty.buffer.ByteBuf;
5 |
6 | public class RetireConnectionIdFrame extends Frame {
7 |
8 | private final long sequenceNumber;
9 |
10 | public static RetireConnectionIdFrame parse(final ByteBuf bb) {
11 | final byte type = bb.readByte();
12 | if (type != FrameType.RETIRE_CONNECTION_ID.getType()) {
13 | throw new IllegalArgumentException("Illegal frame type");
14 | }
15 |
16 | final long sequenceNumber = Varint.readAsLong(bb);
17 |
18 | return new RetireConnectionIdFrame(sequenceNumber);
19 | }
20 |
21 | public RetireConnectionIdFrame(final long sequenceNumber) {
22 | super(FrameType.RETIRE_CONNECTION_ID);
23 | this.sequenceNumber = sequenceNumber;
24 | }
25 |
26 | public long getSequenceNumber() {
27 | return sequenceNumber;
28 | }
29 |
30 | @Override
31 | public void write(final ByteBuf bb) {
32 | bb.writeByte(getType().getType());
33 |
34 | Varint.write(sequenceNumber, bb);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/frames/StreamDataBlockedFrame.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static java.util.Objects.requireNonNull;
4 |
5 | import com.protocol7.quincy.Varint;
6 | import com.protocol7.quincy.protocol.StreamId;
7 | import io.netty.buffer.ByteBuf;
8 | import java.util.Objects;
9 |
10 | public class StreamDataBlockedFrame extends Frame {
11 |
12 | public static StreamDataBlockedFrame parse(final ByteBuf bb) {
13 | final byte type = bb.readByte();
14 | if (type != FrameType.STREAM_DATA_BLOCKED.getType()) {
15 | throw new IllegalArgumentException("Illegal frame type");
16 | }
17 |
18 | final long streamId = StreamId.parse(bb);
19 | final long maxStreamData = Varint.readAsLong(bb);
20 |
21 | return new StreamDataBlockedFrame(streamId, maxStreamData);
22 | }
23 |
24 | private final long streamId;
25 | private final long maxStreamData;
26 |
27 | public StreamDataBlockedFrame(final long streamId, final long maxStreamData) {
28 | super(FrameType.STREAM_DATA_BLOCKED);
29 |
30 | requireNonNull(streamId);
31 |
32 | this.streamId = StreamId.validate(streamId);
33 | this.maxStreamData = maxStreamData;
34 | }
35 |
36 | public long getStreamId() {
37 | return streamId;
38 | }
39 |
40 | public long getMaxStreamData() {
41 | return maxStreamData;
42 | }
43 |
44 | @Override
45 | public void write(final ByteBuf bb) {
46 | bb.writeByte(getType().getType());
47 |
48 | StreamId.write(bb, streamId);
49 | Varint.write(maxStreamData, bb);
50 | }
51 |
52 | @Override
53 | public boolean equals(final Object o) {
54 | if (this == o) return true;
55 | if (o == null || getClass() != o.getClass()) return false;
56 | final StreamDataBlockedFrame that = (StreamDataBlockedFrame) o;
57 | return maxStreamData == that.maxStreamData && Objects.equals(streamId, that.streamId);
58 | }
59 |
60 | @Override
61 | public int hashCode() {
62 | return Objects.hash(streamId, maxStreamData);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/frames/StreamsBlockedFrame.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import com.protocol7.quincy.Varint;
4 | import io.netty.buffer.ByteBuf;
5 |
6 | public class StreamsBlockedFrame extends Frame {
7 |
8 | private static final byte BIDI_TYPE = 0x16;
9 | private static final byte UNI_TYPE = 0x17;
10 |
11 | public static StreamsBlockedFrame parse(final ByteBuf bb) {
12 | final byte type = bb.readByte();
13 | if (type != BIDI_TYPE && type != UNI_TYPE) {
14 | throw new IllegalArgumentException("Illegal frame type");
15 | }
16 |
17 | final boolean bidi = type == BIDI_TYPE;
18 | final long maxStreams = Varint.readAsLong(bb);
19 |
20 | return new StreamsBlockedFrame(maxStreams, bidi);
21 | }
22 |
23 | private final long maxStreams;
24 | private final boolean bidi;
25 |
26 | public StreamsBlockedFrame(final long maxStreams, final boolean bidi) {
27 | super(FrameType.STREAMS_BLOCKED);
28 |
29 | this.maxStreams = maxStreams;
30 | this.bidi = bidi;
31 | }
32 |
33 | public long getMaxStreams() {
34 | return maxStreams;
35 | }
36 |
37 | public boolean isBidi() {
38 | return bidi;
39 | }
40 |
41 | @Override
42 | public void write(final ByteBuf bb) {
43 | if (bidi) {
44 | bb.writeByte(BIDI_TYPE);
45 | } else {
46 | bb.writeByte(UNI_TYPE);
47 | }
48 |
49 | Varint.write(maxStreams, bb);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/packets/FullPacket.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.packets;
2 |
3 | import com.protocol7.quincy.protocol.Payload;
4 | import com.protocol7.quincy.protocol.frames.Frame;
5 |
6 | public interface FullPacket extends Packet {
7 |
8 | PacketType getType();
9 |
10 | FullPacket addFrame(Frame frame);
11 |
12 | long getPacketNumber();
13 |
14 | Payload getPayload();
15 | }
16 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/packets/HalfParsedPacket.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.packets;
2 |
3 | import com.protocol7.quincy.protocol.ConnectionId;
4 | import com.protocol7.quincy.protocol.Version;
5 | import com.protocol7.quincy.tls.aead.AEADProvider;
6 | import java.util.Optional;
7 |
8 | public interface HalfParsedPacket {
9 |
10 | PacketType getType();
11 |
12 | Optional getVersion();
13 |
14 | ConnectionId getDestinationConnectionId();
15 |
16 | Optional getSourceConnectionId();
17 |
18 | Optional getRetryToken();
19 |
20 | P complete(AEADProvider aeadProvider);
21 | }
22 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/protocol/packets/PacketType.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.packets;
2 |
3 | public enum PacketType {
4 | Initial((byte) 0x0),
5 | Zero_RTT_Protected((byte) 0x01),
6 | Handshake((byte) 0x02),
7 | Retry((byte) 0x03);
8 |
9 | public static PacketType fromByte(final byte b) {
10 |
11 | if (b == Initial.type) {
12 | return Initial;
13 | } else if (b == Retry.type) {
14 | return Retry;
15 | } else if (b == Handshake.type) {
16 | return Handshake;
17 | } else if (b == Zero_RTT_Protected.type) {
18 | return Zero_RTT_Protected;
19 | } else {
20 | throw new RuntimeException("Unknown long packet type");
21 | }
22 | }
23 |
24 | private final byte type;
25 |
26 | PacketType(final byte type) {
27 | this.type = type;
28 | }
29 |
30 | public byte getType() {
31 | return type;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/reliability/AckDelay.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.reliability;
2 |
3 | import static java.util.Objects.requireNonNull;
4 |
5 | import com.protocol7.quincy.utils.Ticker;
6 | import java.util.concurrent.TimeUnit;
7 |
8 | public class AckDelay {
9 |
10 | private final int ackDelayMultiplier;
11 | private final Ticker ticker;
12 |
13 | public AckDelay(final int ackDelayExponent, final Ticker ticker) {
14 | this.ackDelayMultiplier = (int) Math.pow(2, ackDelayExponent);
15 | this.ticker = requireNonNull(ticker);
16 | }
17 |
18 | public long calculate(final long delay, final TimeUnit unit) {
19 | return Math.max(unit.toMicros(delay) / ackDelayMultiplier, 0);
20 | }
21 |
22 | public long time() {
23 | return ticker.nanoTime();
24 | }
25 |
26 | public long delay(final long delay) {
27 | return Math.max(ticker.nanoTime() - delay, 0);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/reliability/AckUtil.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.reliability;
2 |
3 | import com.protocol7.quincy.protocol.frames.AckFrame;
4 | import com.protocol7.quincy.protocol.frames.AckRange;
5 |
6 | public class AckUtil {
7 |
8 | public static boolean contains(final AckFrame frame, final long packetNumber) {
9 | for (final AckRange range : frame.getRanges()) {
10 | if (packetNumber >= range.getSmallest() && packetNumber <= range.getLargest()) {
11 | return true;
12 | }
13 | }
14 | return false;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/reliability/PacketBuffer.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.reliability;
2 |
3 | import static com.protocol7.quincy.utils.Pair.of;
4 | import static java.util.Objects.requireNonNull;
5 |
6 | import com.protocol7.quincy.protocol.frames.Frame;
7 | import com.protocol7.quincy.protocol.packets.FullPacket;
8 | import com.protocol7.quincy.utils.Pair;
9 | import com.protocol7.quincy.utils.Ticker;
10 | import java.util.Collection;
11 | import java.util.List;
12 | import java.util.Map.Entry;
13 | import java.util.concurrent.ConcurrentHashMap;
14 | import java.util.concurrent.ConcurrentMap;
15 | import java.util.concurrent.TimeUnit;
16 | import java.util.stream.Collectors;
17 |
18 | public class PacketBuffer {
19 |
20 | private final ConcurrentMap, Long>> buffer = new ConcurrentHashMap<>();
21 | private final Ticker ticker;
22 |
23 | public PacketBuffer(final Ticker ticker) {
24 | this.ticker = requireNonNull(ticker);
25 | }
26 |
27 | public void put(final FullPacket packet) {
28 | requireNonNull(packet);
29 | buffer.put(packet.getPacketNumber(), of(packet.getPayload().getFrames(), ticker.nanoTime()));
30 | }
31 |
32 | public void clear() {
33 | buffer.clear();
34 | }
35 |
36 | public boolean remove(final long packetNumber) {
37 | return buffer.remove(packetNumber) != null;
38 | }
39 |
40 | public boolean contains(final long packetNumber) {
41 | return buffer.containsKey(packetNumber);
42 | }
43 |
44 | public boolean isEmpty() {
45 | return buffer.isEmpty();
46 | }
47 |
48 | public Collection drainSince(final long ttl, final TimeUnit unit) {
49 | final long since = ticker.nanoTime() - unit.toNanos(ttl);
50 |
51 | final List, Long>>> toDrain =
52 | buffer
53 | .entrySet()
54 | .stream()
55 | .filter(entry -> entry.getValue().getSecond() < since)
56 | .collect(Collectors.toUnmodifiableList());
57 |
58 | toDrain.forEach(entry -> remove(entry.getKey()));
59 |
60 | return toDrain
61 | .stream()
62 | .flatMap(entry -> entry.getValue().getFirst().stream())
63 | .collect(Collectors.toUnmodifiableList());
64 | }
65 |
66 | @Override
67 | public String toString() {
68 | return "PacketBuffer{" + buffer + '}';
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/streams/ReceiveStateMachine.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.streams;
2 |
3 | import static com.protocol7.quincy.streams.ReceiveStateMachine.ReceiveStreamState.*;
4 |
5 | public class ReceiveStateMachine {
6 |
7 | public enum ReceiveStreamState {
8 | Recv,
9 | SizeKnown,
10 | DataRecvd,
11 | ResetRecvd,
12 | DataRead,
13 | ResetRead
14 | }
15 |
16 | private ReceiveStreamState state = Recv;
17 |
18 | public void onStream(final boolean fin) {
19 | if (state == Recv || state == SizeKnown) {
20 | if (fin) {
21 | state = SizeKnown;
22 | }
23 | } else {
24 | throw new IllegalStateException();
25 | }
26 | }
27 |
28 | public void onReset() {
29 | if (state == Recv || state == SizeKnown || state == DataRecvd) {
30 | state = ResetRecvd;
31 | } else {
32 | throw new IllegalStateException();
33 | }
34 | }
35 |
36 | public void onAllData() {
37 | if (state == DataRecvd) {
38 | state = DataRead;
39 | } else {
40 | throw new IllegalStateException();
41 | }
42 | }
43 |
44 | public void onAppReadReset() {
45 | if (state == ResetRecvd) {
46 | state = ResetRead;
47 | } else {
48 | throw new IllegalStateException();
49 | }
50 | }
51 |
52 | public boolean canReceive() {
53 | return state == Recv;
54 | }
55 |
56 | public boolean canReset() {
57 | return state == Recv || state == SizeKnown || state == DataRecvd;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/streams/ReceivedDataBuffer.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.streams;
2 |
3 | import java.util.Optional;
4 | import java.util.TreeMap;
5 |
6 | // TODO optimize
7 | public class ReceivedDataBuffer {
8 |
9 | private final TreeMap buffer = new TreeMap<>();
10 | private long largestOffset = 0;
11 | private long readOffset = 0;
12 |
13 | public void onData(final byte[] data, final long offset, final boolean finish) {
14 | buffer.put(offset, data);
15 | if (finish) {
16 | this.largestOffset = offset;
17 | }
18 | }
19 |
20 | public boolean hasMore() {
21 | return buffer.get(readOffset) != null;
22 | }
23 |
24 | public Optional read() {
25 | final byte[] b = buffer.get(readOffset);
26 |
27 | if (b != null) {
28 | readOffset += b.length;
29 | return Optional.of(b);
30 | } else {
31 | return Optional.empty();
32 | }
33 | }
34 |
35 | public boolean isDone() {
36 | return readOffset > largestOffset;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/streams/SendStateMachine.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.streams;
2 |
3 | import static com.protocol7.quincy.streams.SendStateMachine.SendStreamState.*;
4 |
5 | import java.util.Collections;
6 | import java.util.Optional;
7 | import java.util.Set;
8 | import java.util.concurrent.ConcurrentHashMap;
9 |
10 | public class SendStateMachine {
11 |
12 | public SendStreamState getState() {
13 | return state;
14 | }
15 |
16 | public enum SendStreamState {
17 | Open,
18 | Send,
19 | DataSent,
20 | ResetSent,
21 | DataRecvd,
22 | ResetRecvd
23 | }
24 |
25 | private SendStreamState state = Open;
26 | private final Set outstandingStreamPackets =
27 | Collections.newSetFromMap(new ConcurrentHashMap<>());
28 | private Optional outstandingResetPacket = Optional.empty();
29 |
30 | public void onStream(final long pn, final boolean fin) {
31 | if (state == Open || state == Send) {
32 | if (fin) {
33 | state = DataSent;
34 | } else {
35 | state = Send;
36 | }
37 | } else {
38 | throw new IllegalStateException();
39 | }
40 | }
41 |
42 | public void onReset(final long pn) {
43 | if (state == Open || state == Send || state == DataSent) {
44 | state = ResetSent;
45 | outstandingResetPacket = Optional.of(pn);
46 | } else {
47 | throw new IllegalStateException();
48 | }
49 | }
50 |
51 | public void onAck(final long pn) {
52 | outstandingStreamPackets.remove(pn);
53 |
54 | if (state == DataSent && outstandingStreamPackets.isEmpty()) {
55 | state = DataRecvd;
56 | } else if (state == ResetSent) {
57 | if (outstandingResetPacket.isPresent() && outstandingResetPacket.get().equals(pn)) {
58 | state = ResetRecvd;
59 | } else {
60 | throw new IllegalStateException();
61 | }
62 | }
63 | }
64 |
65 | public boolean canSend() {
66 | return state == Open || state == Send;
67 | }
68 |
69 | public boolean canReset() {
70 | return state == Open || state == Send || state == DataSent;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/streams/Stream.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.streams;
2 |
3 | public interface Stream {
4 |
5 | long getId();
6 |
7 | StreamType getStreamType();
8 |
9 | void write(final byte[] b, boolean finish);
10 |
11 | void reset(int applicationErrorCode);
12 |
13 | boolean isFinished();
14 | }
15 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/streams/StreamHandler.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.streams;
2 |
3 | public interface StreamHandler {
4 | void onData(Stream stream, byte[] data, boolean finished);
5 | }
6 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/streams/StreamManager.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.streams;
2 |
3 | import com.protocol7.quincy.InboundHandler;
4 |
5 | public interface StreamManager extends InboundHandler {
6 |
7 | Stream openStream(boolean client, boolean bidirectional);
8 | }
9 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/streams/StreamType.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.streams;
2 |
3 | public enum StreamType {
4 | Receiving,
5 | Sending,
6 | Bidirectional;
7 |
8 | public boolean canSend() {
9 | return this == Sending || this == Bidirectional;
10 | }
11 |
12 | public boolean canReceive() {
13 | return this == Receiving || this == Bidirectional;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/streams/Streams.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.streams;
2 |
3 | import com.protocol7.quincy.FrameSender;
4 | import com.protocol7.quincy.protocol.StreamId;
5 | import java.util.Map;
6 | import java.util.concurrent.ConcurrentHashMap;
7 |
8 | public class Streams {
9 |
10 | private final FrameSender frameSender;
11 | private final Map streams = new ConcurrentHashMap<>();
12 | private long maxId = -1;
13 |
14 | public Streams(final FrameSender frameSender) {
15 | this.frameSender = frameSender;
16 | }
17 |
18 | public Stream openStream(
19 | final boolean client, final boolean bidirectional, final StreamHandler handler) {
20 | final StreamType type = bidirectional ? StreamType.Bidirectional : StreamType.Sending;
21 | final long streamId = StreamId.next(maxId, client, bidirectional);
22 | this.maxId = streamId;
23 | final DefaultStream stream = new DefaultStream(streamId, frameSender, handler, type);
24 | streams.put(streamId, stream);
25 | return stream;
26 | }
27 |
28 | public DefaultStream getOrCreate(final long streamId, final StreamHandler handler) {
29 | DefaultStream stream = streams.get(streamId);
30 | if (stream == null) {
31 | stream =
32 | new DefaultStream(
33 | streamId, frameSender, handler, StreamType.Bidirectional); // TODO support stream type
34 | final DefaultStream existingStream = streams.putIfAbsent(streamId, stream);
35 | if (existingStream != null) {
36 | stream = existingStream;
37 | }
38 | }
39 | return stream;
40 | }
41 |
42 | public void onAck(final long pn) {
43 | for (final DefaultStream stream : streams.values()) {
44 | stream.onAck(pn);
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/quic/src/main/java/com/protocol7/quincy/tls/TlsManager.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import com.protocol7.quincy.FrameSender;
4 | import com.protocol7.quincy.InboundHandler;
5 | import com.protocol7.quincy.connection.State;
6 | import com.protocol7.quincy.protocol.ConnectionId;
7 | import com.protocol7.quincy.tls.aead.AEAD;
8 | import io.netty.util.concurrent.Promise;
9 | import java.util.function.Consumer;
10 |
11 | public interface TlsManager extends InboundHandler {
12 |
13 | AEAD getAEAD(final EncryptionLevel level);
14 |
15 | void resetTlsSession(final ConnectionId connectionId);
16 |
17 | void handshake(
18 | final State state,
19 | final FrameSender sender,
20 | final Consumer stateSetter,
21 | final Promise promise);
22 | }
23 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/ClientRunner.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy;
2 |
3 | import com.protocol7.quincy.connection.Connection;
4 | import com.protocol7.quincy.netty.QuicBuilder;
5 | import com.protocol7.quincy.protocol.Version;
6 | import io.netty.bootstrap.Bootstrap;
7 | import io.netty.channel.Channel;
8 | import io.netty.channel.EventLoopGroup;
9 | import io.netty.channel.nio.NioEventLoopGroup;
10 | import io.netty.channel.socket.nio.NioDatagramChannel;
11 | import java.net.InetSocketAddress;
12 |
13 | public class ClientRunner {
14 |
15 | public static void main(final String[] args) throws InterruptedException {
16 | final InetSocketAddress peer = new InetSocketAddress("127.0.0.1", 4433);
17 |
18 | final EventLoopGroup workerGroup = new NioEventLoopGroup();
19 |
20 | try {
21 | final Bootstrap b = new Bootstrap();
22 | b.group(workerGroup);
23 | b.channel(NioDatagramChannel.class);
24 | b.remoteAddress(peer);
25 | b.handler(
26 | new QuicBuilder()
27 | .withApplicationProtocols("http/0.9")
28 | .withVersion(Version.DRAFT_29)
29 | .channelInitializer());
30 |
31 | final Channel channel = b.connect().sync().channel();
32 |
33 | final Connection connection =
34 | Connection.newBootstrap(channel)
35 | .withStreamHandler(
36 | (stream, data, finished) -> {
37 | System.out.println(new String(data));
38 | })
39 | .connect()
40 | .sync()
41 | .getNow();
42 |
43 | connection.openStream().write("GET /\r\n".getBytes(), true);
44 |
45 | System.out.println("Connected");
46 |
47 | Thread.sleep(1000);
48 |
49 | } finally {
50 | workerGroup.shutdownGracefully();
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/MockTimer.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy;
2 |
3 | import io.netty.util.Timeout;
4 | import io.netty.util.Timer;
5 | import io.netty.util.TimerTask;
6 | import java.util.HashSet;
7 | import java.util.Set;
8 | import java.util.concurrent.TimeUnit;
9 |
10 | public class MockTimer implements Timer {
11 |
12 | public Set timeouts = new HashSet<>();
13 |
14 | @Override
15 | public Timeout newTimeout(final TimerTask timerTask, final long l, final TimeUnit timeUnit) {
16 | final Timeout timeout =
17 | new Timeout() {
18 | @Override
19 | public Timer timer() {
20 | return MockTimer.this;
21 | }
22 |
23 | @Override
24 | public TimerTask task() {
25 | return timerTask;
26 | }
27 |
28 | @Override
29 | public boolean isExpired() {
30 | return false;
31 | }
32 |
33 | @Override
34 | public boolean isCancelled() {
35 | return false;
36 | }
37 |
38 | @Override
39 | public boolean cancel() {
40 | return false;
41 | }
42 | };
43 | timeouts.add(timeout);
44 | return timeout;
45 | }
46 |
47 | @Override
48 | public Set stop() {
49 | return timeouts;
50 | }
51 |
52 | public void trigger() throws Exception {
53 | for (final Timeout timeout : timeouts) {
54 | timeout.task().run(timeout);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/ServerRunner.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy;
2 |
3 | import com.protocol7.quincy.netty.QuicBuilder;
4 | import com.protocol7.quincy.streams.Stream;
5 | import com.protocol7.quincy.streams.StreamHandler;
6 | import com.protocol7.quincy.tls.KeyUtil;
7 | import io.netty.bootstrap.Bootstrap;
8 | import io.netty.channel.ChannelOption;
9 | import io.netty.channel.EventLoopGroup;
10 | import io.netty.channel.nio.NioEventLoopGroup;
11 | import io.netty.channel.socket.nio.NioDatagramChannel;
12 |
13 | public class ServerRunner {
14 |
15 | public static void main(final String[] args) throws InterruptedException {
16 | final EventLoopGroup workerGroup = new NioEventLoopGroup();
17 |
18 | try {
19 | final Bootstrap b = new Bootstrap();
20 | b.group(workerGroup);
21 | b.channel(NioDatagramChannel.class);
22 | b.option(ChannelOption.SO_BROADCAST, true);
23 | b.handler(
24 | new QuicBuilder()
25 | .withCertificates(KeyUtil.getCertsFromCrt("quic/src/test/resources/server.crt"))
26 | .withPrivateKey(KeyUtil.getPrivateKey("quic/src/test/resources/server.der"))
27 | .withStreamHandler(
28 | new StreamHandler() {
29 | @Override
30 | public void onData(
31 | final Stream stream, final byte[] data, final boolean finished) {
32 | System.out.println("server got message " + new String(data));
33 |
34 | stream.write("PONG".getBytes(), true);
35 | }
36 | })
37 | .channelInitializer());
38 |
39 | b.bind("0.0.0.0", 4444).awaitUninterruptibly();
40 | System.out.println("Bound");
41 |
42 | Thread.sleep(100000);
43 |
44 | } finally {
45 | workerGroup.shutdownGracefully();
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/TestUtil.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy;
2 |
3 | import static com.protocol7.quincy.utils.Hex.hex;
4 | import static org.junit.Assert.assertEquals;
5 | import static org.junit.Assert.assertFalse;
6 |
7 | import io.netty.buffer.ByteBuf;
8 | import java.net.InetSocketAddress;
9 |
10 | public class TestUtil {
11 |
12 | public static void assertBuffer(final String expected, final ByteBuf actual) {
13 | final byte[] actualBytes = new byte[actual.readableBytes()];
14 | actual.readBytes(actualBytes);
15 | assertEquals(expected, hex(actualBytes));
16 | assertBufferExhusted(actual);
17 | }
18 |
19 | public static void assertBufferExhusted(final ByteBuf bb) {
20 | assertFalse(bb.isReadable());
21 | }
22 |
23 | public static void assertHex(final String expectedHex, final byte[] actual) {
24 | assertEquals(expectedHex, hex(actual));
25 | }
26 |
27 | public static InetSocketAddress getTestAddress() {
28 | return new InetSocketAddress("127.0.0.1", 4444);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/flowcontrol/MockFlowControlHandler.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.flowcontrol;
2 |
3 | import com.protocol7.quincy.PipelineContext;
4 | import com.protocol7.quincy.protocol.packets.Packet;
5 |
6 | public class MockFlowControlHandler implements FlowControlHandler {
7 | @Override
8 | public void onReceivePacket(final Packet packet, final PipelineContext ctx) {
9 | ctx.next(packet);
10 | }
11 |
12 | @Override
13 | public void beforeSendPacket(final Packet packet, final PipelineContext ctx) {
14 | ctx.next(packet);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/logging/LoggingHandlerTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.logging;
2 |
3 | import static org.mockito.Mockito.verify;
4 |
5 | import com.protocol7.quincy.PipelineContext;
6 | import com.protocol7.quincy.protocol.ConnectionId;
7 | import com.protocol7.quincy.protocol.PacketNumber;
8 | import com.protocol7.quincy.protocol.frames.Frame;
9 | import com.protocol7.quincy.protocol.frames.PingFrame;
10 | import com.protocol7.quincy.protocol.packets.FullPacket;
11 | import com.protocol7.quincy.protocol.packets.Packet;
12 | import com.protocol7.quincy.protocol.packets.ShortPacket;
13 | import org.junit.Test;
14 | import org.junit.runner.RunWith;
15 | import org.mockito.Mock;
16 | import org.mockito.junit.MockitoJUnitRunner;
17 |
18 | @RunWith(MockitoJUnitRunner.class)
19 | public class LoggingHandlerTest {
20 |
21 | private final LoggingHandler handler = new LoggingHandler(true);
22 | @Mock private PipelineContext ctx;
23 |
24 | @Test
25 | public void receive() {
26 | final Packet packet = p(PingFrame.INSTANCE);
27 | handler.onReceivePacket(packet, ctx);
28 |
29 | verify(ctx).next(packet);
30 | }
31 |
32 | @Test
33 | public void send() {
34 | final Packet packet = p(PingFrame.INSTANCE);
35 | handler.beforeSendPacket(packet, ctx);
36 |
37 | verify(ctx).next(packet);
38 | }
39 |
40 | private FullPacket p(final Frame... frames) {
41 | return ShortPacket.create(
42 | false, ConnectionId.random(), ConnectionId.random(), PacketNumber.MIN, frames);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/PayloadTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import com.protocol7.quincy.TestUtil;
6 | import com.protocol7.quincy.protocol.frames.*;
7 | import com.protocol7.quincy.tls.aead.AEAD;
8 | import com.protocol7.quincy.tls.aead.TestAEAD;
9 | import com.protocol7.quincy.utils.Hex;
10 | import io.netty.buffer.ByteBuf;
11 | import io.netty.buffer.Unpooled;
12 | import org.junit.Test;
13 |
14 | public class PayloadTest {
15 |
16 | private final AEAD aead = TestAEAD.create();
17 | private final long pn = 1;
18 | private final byte[] aad = new byte[12];
19 |
20 | @Test
21 | public void roundtrip() {
22 | final Payload payload = new Payload(PingFrame.INSTANCE, new PaddingFrame(1));
23 |
24 | final ByteBuf bb = Unpooled.buffer();
25 | payload.write(bb, aead, pn, aad);
26 |
27 | final Payload parsed = Payload.parse(bb, payload.calculateLength(), aead, pn, aad);
28 |
29 | assertEquals(payload, parsed);
30 | }
31 |
32 | @Test
33 | public void write() {
34 | final Payload payload = new Payload(PingFrame.INSTANCE, new PaddingFrame(1));
35 |
36 | final ByteBuf bb = Unpooled.buffer();
37 | payload.write(bb, aead, pn, aad);
38 |
39 | TestUtil.assertBuffer("e1eca61dcd946af283d48c55a5d25967efd6", bb);
40 | }
41 |
42 | @Test
43 | public void parse() {
44 | final ByteBuf bb = Unpooled.copiedBuffer(Hex.dehex("e1eca61dcd946af283d48c55a5d25967efd6"));
45 | final Payload parsed = Payload.parse(bb, bb.writerIndex(), aead, pn, aad);
46 |
47 | final Payload expected = new Payload(PingFrame.INSTANCE, new PaddingFrame(1));
48 | assertEquals(expected, parsed);
49 | }
50 |
51 | @Test
52 | public void addFrame() {
53 | final Payload payload = new Payload(PingFrame.INSTANCE);
54 |
55 | final Payload withAdded = payload.addFrame(new PaddingFrame(1));
56 |
57 | final Payload expected = new Payload(PingFrame.INSTANCE, new PaddingFrame(1));
58 |
59 | assertEquals(expected, withAdded);
60 | assertEquals(new Payload(PingFrame.INSTANCE), payload); // must not been mutated
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/StreamIdTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import io.netty.buffer.ByteBuf;
6 | import io.netty.buffer.Unpooled;
7 | import org.junit.Test;
8 |
9 | public class StreamIdTest {
10 |
11 | @Test
12 | public void validateBounds() {
13 | StreamId.validate(0);
14 | StreamId.validate(123);
15 | StreamId.validate(4611686018427387903L);
16 | }
17 |
18 | @Test(expected = IllegalArgumentException.class)
19 | public void validateBoundsTooSmall() {
20 | StreamId.validate(-1);
21 | }
22 |
23 | @Test(expected = IllegalArgumentException.class)
24 | public void validateBoundsTooLarge() {
25 | StreamId.validate(4611686018427387904L);
26 | }
27 |
28 | @Test
29 | public void next() {
30 | assertEquals(0, StreamId.next(-1, true, true));
31 | assertEquals(1, StreamId.next(-1, false, true));
32 | assertEquals(2, StreamId.next(-1, true, false));
33 | assertEquals(3, StreamId.next(-1, false, false));
34 |
35 | assertEquals(4, StreamId.next(0, true, true));
36 | assertEquals(5, StreamId.next(1, false, true));
37 | assertEquals(6, StreamId.next(2, true, false));
38 | assertEquals(7, StreamId.next(3, false, false));
39 | }
40 |
41 | @Test
42 | public void roundtrip() {
43 | final long streamId = StreamId.next(0, true, true);
44 |
45 | final ByteBuf bb = Unpooled.buffer();
46 | StreamId.write(bb, streamId);
47 |
48 | final long parsed = StreamId.parse(bb);
49 |
50 | assertEquals(streamId, parsed);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/VersionTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import com.protocol7.quincy.TestUtil;
6 | import com.protocol7.quincy.utils.Hex;
7 | import io.netty.buffer.ByteBuf;
8 | import io.netty.buffer.Unpooled;
9 | import org.junit.Test;
10 |
11 | public class VersionTest {
12 |
13 | @Test
14 | public void write() {
15 | final ByteBuf bb = Unpooled.buffer();
16 | Version.DRAFT_29.write(bb);
17 |
18 | TestUtil.assertBuffer("ff00001d", bb);
19 | TestUtil.assertBufferExhusted(bb);
20 | }
21 |
22 | @Test
23 | public void read() {
24 | assertEquals(Version.DRAFT_29, Version.read(b("ff00001d")));
25 | assertEquals(Version.FINAL, Version.read(b("00000001")));
26 | assertEquals(Version.VERSION_NEGOTIATION, Version.read(b("00000000")));
27 | assertEquals(new Version(Hex.dehex("abcdabcd")), Version.read(b("abcdabcd")));
28 | }
29 |
30 | private ByteBuf b(final String d) {
31 | final ByteBuf bb = Unpooled.buffer();
32 | bb.writeBytes(Hex.dehex(d));
33 | return bb;
34 | }
35 |
36 | @Test
37 | public void roundtrip() {
38 | final ByteBuf bb = Unpooled.buffer();
39 | Version.DRAFT_29.write(bb);
40 |
41 | final Version parsed = Version.read(bb);
42 |
43 | assertEquals(Version.DRAFT_29, parsed);
44 | }
45 |
46 | @Test
47 | public void equals() {
48 | assertEquals(Version.DRAFT_29, Version.DRAFT_29);
49 | assertEquals(new Version(Version.DRAFT_29.asBytes()), Version.DRAFT_29);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/frames/AckFrameTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import com.protocol7.quincy.utils.Bytes;
6 | import com.protocol7.quincy.utils.Hex;
7 | import io.netty.buffer.ByteBuf;
8 | import io.netty.buffer.Unpooled;
9 | import java.util.List;
10 | import org.junit.Test;
11 |
12 | public class AckFrameTest {
13 |
14 | @Test
15 | public void roundtrip() {
16 | final List ranges =
17 | List.of(new AckRange(1, 5), new AckRange(7, 8), new AckRange(12, 100));
18 | final AckFrame frame = new AckFrame(1234, ranges);
19 |
20 | final ByteBuf bb = Unpooled.buffer();
21 | frame.write(bb);
22 |
23 | final AckFrame parsed = AckFrame.parse(bb);
24 |
25 | assertEquals(frame.getAckDelay(), parsed.getAckDelay());
26 | assertEquals(frame.getRanges(), parsed.getRanges());
27 | }
28 |
29 | @Test
30 | public void roundtripSinglePacket() {
31 | final List ranges = List.of(new AckRange(100, 100));
32 | final AckFrame frame = new AckFrame(1234, ranges);
33 |
34 | final ByteBuf bb = Unpooled.buffer();
35 | frame.write(bb);
36 |
37 | final AckFrame parsed = AckFrame.parse(bb);
38 |
39 | assertEquals(frame.getAckDelay(), parsed.getAckDelay());
40 | assertEquals(frame.getRanges(), parsed.getRanges());
41 | }
42 |
43 | @Test
44 | public void writeSinglePacket() {
45 | final List ranges = List.of(new AckRange(100, 100));
46 | final AckFrame frame = new AckFrame(1234, ranges);
47 |
48 | final ByteBuf bb = Unpooled.buffer();
49 | frame.write(bb);
50 |
51 | assertEquals("02406444d20000", Hex.hex(Bytes.drainToArray(bb)));
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/frames/AckRangeTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import org.junit.Test;
4 |
5 | public class AckRangeTest {
6 |
7 | @Test
8 | public void cstrValidation() {
9 | new AckRange(123, 124);
10 | new AckRange(123, 123);
11 | }
12 |
13 | @Test(expected = IllegalArgumentException.class)
14 | public void cstrValidationFail() {
15 | new AckRange(124, 123);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/frames/ApplicationCloseFrameTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import io.netty.buffer.ByteBuf;
6 | import io.netty.buffer.Unpooled;
7 | import org.junit.Test;
8 |
9 | public class ApplicationCloseFrameTest {
10 |
11 | @Test
12 | public void roundtripApplication() {
13 | final ApplicationCloseFrame acf = new ApplicationCloseFrame(12, "Hello world");
14 |
15 | final ByteBuf bb = Unpooled.buffer();
16 | acf.write(bb);
17 |
18 | final ApplicationCloseFrame parsed = ApplicationCloseFrame.parse(bb);
19 |
20 | assertEquals(acf.getErrorCode(), parsed.getErrorCode());
21 | assertEquals(acf.getReasonPhrase(), parsed.getReasonPhrase());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/frames/ConnectionCloseFrameTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import com.protocol7.quincy.utils.Hex;
6 | import io.netty.buffer.ByteBuf;
7 | import io.netty.buffer.Unpooled;
8 | import org.junit.Test;
9 |
10 | public class ConnectionCloseFrameTest {
11 |
12 | @Test
13 | public void parseKnown() {
14 | final byte[] b = Hex.dehex("1c19001b4e6f20726563656e74206e6574776f726b2061637469766974792e");
15 | final ByteBuf bb = Unpooled.wrappedBuffer(b);
16 |
17 | final ConnectionCloseFrame ccf = ConnectionCloseFrame.parse(bb);
18 |
19 | assertEquals(0x19, ccf.getErrorCode());
20 | assertEquals(FrameType.PADDING, ccf.getFrameType());
21 | assertEquals("No recent network activity.", ccf.getReasonPhrase());
22 | }
23 |
24 | @Test
25 | public void roundtripConnection() {
26 | final ConnectionCloseFrame ccf = new ConnectionCloseFrame(12, FrameType.STREAM, "Hello world");
27 |
28 | final ByteBuf bb = Unpooled.buffer();
29 | ccf.write(bb);
30 |
31 | final ConnectionCloseFrame parsed = ConnectionCloseFrame.parse(bb);
32 |
33 | assertEquals(ccf.getErrorCode(), parsed.getErrorCode());
34 | assertEquals(ccf.getFrameType(), parsed.getFrameType());
35 | assertEquals(ccf.getReasonPhrase(), parsed.getReasonPhrase());
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/frames/CryptoFrameTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static org.junit.Assert.assertArrayEquals;
4 | import static org.junit.Assert.assertEquals;
5 |
6 | import com.protocol7.quincy.utils.Hex;
7 | import io.netty.buffer.ByteBuf;
8 | import io.netty.buffer.Unpooled;
9 | import org.junit.Test;
10 |
11 | public class CryptoFrameTest {
12 |
13 | @Test
14 | public void roundtrip() {
15 | final CryptoFrame frame = new CryptoFrame(123, Hex.dehex("1234"));
16 |
17 | final ByteBuf bb = Unpooled.buffer();
18 | frame.write(bb);
19 |
20 | final CryptoFrame parsed = CryptoFrame.parse(bb);
21 |
22 | assertEquals(parsed.getOffset(), frame.getOffset());
23 | assertArrayEquals(parsed.getCryptoData(), frame.getCryptoData());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/frames/DataBlockedFrameTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import io.netty.buffer.ByteBuf;
6 | import io.netty.buffer.Unpooled;
7 | import org.junit.Test;
8 |
9 | public class DataBlockedFrameTest {
10 |
11 | @Test
12 | public void roundtrip() {
13 | final DataBlockedFrame frame = new DataBlockedFrame(456);
14 |
15 | final ByteBuf bb = Unpooled.buffer();
16 | frame.write(bb);
17 |
18 | final DataBlockedFrame parsed = DataBlockedFrame.parse(bb);
19 |
20 | assertEquals(frame.getMaxData(), parsed.getMaxData());
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/frames/MaxDataFrameTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import io.netty.buffer.ByteBuf;
6 | import io.netty.buffer.Unpooled;
7 | import org.junit.Test;
8 |
9 | public class MaxDataFrameTest {
10 |
11 | @Test
12 | public void roundtrip() {
13 | final MaxDataFrame frame = new MaxDataFrame(456);
14 |
15 | final ByteBuf bb = Unpooled.buffer();
16 | frame.write(bb);
17 |
18 | final MaxDataFrame parsed = MaxDataFrame.parse(bb);
19 |
20 | assertEquals(frame.getMaxData(), parsed.getMaxData());
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/frames/MaxStreamDataFrameTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import io.netty.buffer.ByteBuf;
6 | import io.netty.buffer.Unpooled;
7 | import org.junit.Test;
8 |
9 | public class MaxStreamDataFrameTest {
10 |
11 | @Test
12 | public void roundtrip() {
13 | final MaxStreamDataFrame frame = new MaxStreamDataFrame(123, 456);
14 |
15 | final ByteBuf bb = Unpooled.buffer();
16 | frame.write(bb);
17 |
18 | final MaxStreamDataFrame parsed = MaxStreamDataFrame.parse(bb);
19 |
20 | assertEquals(frame.getStreamId(), parsed.getStreamId());
21 | assertEquals(frame.getMaxStreamData(), parsed.getMaxStreamData());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/frames/MaxStreamsFrameTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import io.netty.buffer.ByteBuf;
6 | import io.netty.buffer.Unpooled;
7 | import org.junit.Test;
8 |
9 | public class MaxStreamsFrameTest {
10 |
11 | @Test
12 | public void roundtripBidi() {
13 | final MaxStreamsFrame frame = new MaxStreamsFrame(456, true);
14 |
15 | final ByteBuf bb = Unpooled.buffer();
16 | frame.write(bb);
17 |
18 | assertEquals(0x12, bb.getByte(0));
19 |
20 | final MaxStreamsFrame parsed = MaxStreamsFrame.parse(bb);
21 |
22 | assertEquals(frame.getMaxStreams(), parsed.getMaxStreams());
23 | }
24 |
25 | @Test
26 | public void roundtripUni() {
27 | final MaxStreamsFrame frame = new MaxStreamsFrame(456, false);
28 |
29 | final ByteBuf bb = Unpooled.buffer();
30 | frame.write(bb);
31 |
32 | assertEquals(0x13, bb.getByte(0));
33 |
34 | final MaxStreamsFrame parsed = MaxStreamsFrame.parse(bb);
35 |
36 | assertEquals(frame.getMaxStreams(), parsed.getMaxStreams());
37 | assertEquals(frame.isBidi(), parsed.isBidi());
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/frames/NewTokenTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import com.protocol7.quincy.utils.Rnd;
6 | import io.netty.buffer.ByteBuf;
7 | import io.netty.buffer.Unpooled;
8 | import org.junit.Test;
9 |
10 | public class NewTokenTest {
11 |
12 | @Test
13 | public void roundtrip() {
14 | final NewToken token = new NewToken(Rnd.rndBytes(20));
15 |
16 | final ByteBuf bb = Unpooled.buffer();
17 |
18 | token.write(bb);
19 |
20 | final NewToken parsed = NewToken.parse(bb);
21 |
22 | assertArrayEquals(token.getToken(), parsed.getToken());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/frames/PaddingFrameTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import com.protocol7.quincy.TestUtil;
6 | import com.protocol7.quincy.utils.Hex;
7 | import io.netty.buffer.ByteBuf;
8 | import io.netty.buffer.Unpooled;
9 | import org.junit.Test;
10 |
11 | public class PaddingFrameTest {
12 |
13 | @Test
14 | public void roundtrip() {
15 | final PaddingFrame frame = new PaddingFrame(10);
16 |
17 | final ByteBuf bb = Unpooled.buffer();
18 |
19 | frame.write(bb);
20 |
21 | final PaddingFrame parsed = PaddingFrame.parse(bb);
22 |
23 | assertEquals(frame, parsed);
24 | }
25 |
26 | @Test
27 | public void writeOne() {
28 | final PaddingFrame frame = new PaddingFrame(1);
29 | final ByteBuf bb = Unpooled.buffer();
30 | frame.write(bb);
31 |
32 | TestUtil.assertBuffer("00", bb);
33 | }
34 |
35 | @Test
36 | public void parseOne() {
37 | final PaddingFrame frame = PaddingFrame.parse(Unpooled.copiedBuffer(Hex.dehex("00")));
38 |
39 | assertEquals(new PaddingFrame(1), frame);
40 | }
41 |
42 | @Test
43 | public void parseMulti() {
44 | final PaddingFrame frame = PaddingFrame.parse(Unpooled.copiedBuffer(Hex.dehex("000000000000")));
45 |
46 | assertEquals(new PaddingFrame(6), frame);
47 | }
48 |
49 | @Test
50 | public void parseTrailing() {
51 | final ByteBuf bb = Unpooled.copiedBuffer(Hex.dehex("000000000000FF"));
52 | final PaddingFrame frame = PaddingFrame.parse(bb);
53 |
54 | assertEquals(new PaddingFrame(6), frame);
55 | assertEquals(1, bb.readableBytes());
56 | }
57 |
58 | @Test(expected = IllegalArgumentException.class)
59 | public void parseIllegal() {
60 | PaddingFrame.parse(Unpooled.copiedBuffer(Hex.dehex("FF")));
61 | }
62 |
63 | @Test(expected = IllegalArgumentException.class)
64 | public void cantBeEmpty() {
65 | new PaddingFrame(0);
66 | }
67 |
68 | @Test
69 | public void calculateLength() {
70 | assertEquals(10, new PaddingFrame(10).calculateLength());
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/frames/PingFrameTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import com.protocol7.quincy.TestUtil;
6 | import io.netty.buffer.ByteBuf;
7 | import io.netty.buffer.Unpooled;
8 | import org.junit.Test;
9 |
10 | public class PingFrameTest {
11 |
12 | public static final byte[] DATA = "hello".getBytes();
13 |
14 | @Test
15 | public void roundtrip() {
16 | final ByteBuf bb = Unpooled.buffer();
17 | final PingFrame frame = PingFrame.INSTANCE;
18 |
19 | frame.write(bb);
20 |
21 | final PingFrame parsed = PingFrame.parse(bb);
22 |
23 | assertEquals(frame, parsed);
24 | }
25 |
26 | @Test
27 | public void write() {
28 | final ByteBuf bb = Unpooled.buffer();
29 | final PingFrame frame = PingFrame.INSTANCE;
30 | frame.write(bb);
31 |
32 | TestUtil.assertBuffer("01", bb);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/frames/ResetStreamFrameTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import com.protocol7.quincy.protocol.StreamId;
6 | import io.netty.buffer.ByteBuf;
7 | import io.netty.buffer.Unpooled;
8 | import org.junit.Test;
9 |
10 | public class ResetStreamFrameTest {
11 |
12 | @Test(expected = IllegalArgumentException.class)
13 | public void invalidStreamId() {
14 | new ResetStreamFrame(-1, 123, 456);
15 | }
16 |
17 | @Test
18 | public void validAppErrorCode() {
19 | new ResetStreamFrame(StreamId.next(-1, true, true), 0, 456);
20 | new ResetStreamFrame(StreamId.next(-1, true, true), 123, 456);
21 | new ResetStreamFrame(StreamId.next(-1, true, true), 0xFFFF, 456);
22 | }
23 |
24 | @Test(expected = IllegalArgumentException.class)
25 | public void negativeAppErrorCode() {
26 | new ResetStreamFrame(StreamId.next(-1, true, true), -1, 456);
27 | }
28 |
29 | @Test(expected = IllegalArgumentException.class)
30 | public void tooLargeAppErrorCode() {
31 | new ResetStreamFrame(StreamId.next(-1, true, true), 0xFFFF + 1, 456);
32 | }
33 |
34 | @Test
35 | public void rountrip() {
36 | final long streamId = StreamId.next(-1, true, true);
37 | final ResetStreamFrame frame = new ResetStreamFrame(streamId, 123, 456);
38 |
39 | final ByteBuf bb = Unpooled.buffer();
40 | frame.write(bb);
41 |
42 | final ResetStreamFrame parsed = ResetStreamFrame.parse(bb);
43 |
44 | assertEquals(frame, parsed);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/frames/RetireConnectionIdFrameTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import io.netty.buffer.ByteBuf;
6 | import io.netty.buffer.Unpooled;
7 | import org.junit.Test;
8 |
9 | public class RetireConnectionIdFrameTest {
10 |
11 | @Test
12 | public void roundtrip() {
13 | final RetireConnectionIdFrame rcif = new RetireConnectionIdFrame(123);
14 |
15 | final ByteBuf bb = Unpooled.buffer();
16 | rcif.write(bb);
17 |
18 | final RetireConnectionIdFrame parsed = RetireConnectionIdFrame.parse(bb);
19 |
20 | assertEquals(rcif.getSequenceNumber(), parsed.getSequenceNumber());
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/frames/StreamDataBlockedFrameTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import io.netty.buffer.ByteBuf;
6 | import io.netty.buffer.Unpooled;
7 | import org.junit.Test;
8 |
9 | public class StreamDataBlockedFrameTest {
10 |
11 | @Test
12 | public void roundtrip() {
13 | final StreamDataBlockedFrame frame = new StreamDataBlockedFrame(123, 456);
14 |
15 | final ByteBuf bb = Unpooled.buffer();
16 | frame.write(bb);
17 |
18 | final StreamDataBlockedFrame parsed = StreamDataBlockedFrame.parse(bb);
19 |
20 | assertEquals(frame.getStreamId(), parsed.getStreamId());
21 | assertEquals(frame.getMaxStreamData(), parsed.getMaxStreamData());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/frames/StreamsBlockedFrameTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.frames;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import io.netty.buffer.ByteBuf;
6 | import io.netty.buffer.Unpooled;
7 | import org.junit.Test;
8 |
9 | public class StreamsBlockedFrameTest {
10 |
11 | @Test
12 | public void roundtripBidi() {
13 | final StreamsBlockedFrame frame = new StreamsBlockedFrame(456, true);
14 |
15 | final ByteBuf bb = Unpooled.buffer();
16 | frame.write(bb);
17 |
18 | assertEquals(0x16, bb.getByte(0));
19 |
20 | final StreamsBlockedFrame parsed = StreamsBlockedFrame.parse(bb);
21 |
22 | assertEquals(frame.getMaxStreams(), parsed.getMaxStreams());
23 | assertEquals(frame.isBidi(), parsed.isBidi());
24 | }
25 |
26 | @Test
27 | public void roundtripUni() {
28 | final StreamsBlockedFrame frame = new StreamsBlockedFrame(456, false);
29 |
30 | final ByteBuf bb = Unpooled.buffer();
31 | frame.write(bb);
32 |
33 | assertEquals(0x17, bb.getByte(0));
34 |
35 | final StreamsBlockedFrame parsed = StreamsBlockedFrame.parse(bb);
36 |
37 | assertEquals(frame.getMaxStreams(), parsed.getMaxStreams());
38 | assertEquals(frame.isBidi(), parsed.isBidi());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/protocol/packets/VersionNegotiationPacketTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.protocol.packets;
2 |
3 | import static org.junit.Assert.assertEquals;
4 | import static org.junit.Assert.assertTrue;
5 |
6 | import com.protocol7.quincy.protocol.ConnectionId;
7 | import com.protocol7.quincy.protocol.Version;
8 | import com.protocol7.quincy.tls.aead.AEAD;
9 | import com.protocol7.quincy.tls.aead.InitialAEAD;
10 | import io.netty.buffer.ByteBuf;
11 | import io.netty.buffer.Unpooled;
12 | import java.util.List;
13 | import org.junit.Test;
14 |
15 | public class VersionNegotiationPacketTest {
16 |
17 | private final ConnectionId dest = ConnectionId.random();
18 | private final ConnectionId src = ConnectionId.random();
19 | private final List supported = List.of(Version.DRAFT_29, Version.FINAL);
20 | private final VersionNegotiationPacket packet =
21 | new VersionNegotiationPacket(dest, src, supported);
22 |
23 | private final AEAD aead = InitialAEAD.create(ConnectionId.random().asBytes(), true);
24 |
25 | @Test
26 | public void roundtrip() {
27 | final ByteBuf bb = Unpooled.buffer();
28 | packet.write(bb, aead);
29 |
30 | final VersionNegotiationPacket parsed = VersionNegotiationPacket.parse(bb).complete(l -> aead);
31 |
32 | assertEquals(dest, parsed.getDestinationConnectionId());
33 | assertEquals(src, parsed.getSourceConnectionId());
34 | assertEquals(supported, parsed.getSupportedVersions());
35 | }
36 |
37 | @Test(expected = IllegalArgumentException.class)
38 | public void readInvalidMarker() {
39 | final ByteBuf bb = Unpooled.buffer();
40 | packet.write(bb, aead);
41 | bb.setByte(0, 0);
42 |
43 | VersionNegotiationPacket.parse(bb);
44 | }
45 |
46 | @Test
47 | public void randomMarker() {
48 | // marker must be random except for first bit
49 | final ByteBuf bb = Unpooled.buffer();
50 | packet.write(bb, aead);
51 | final byte marker1 = bb.readByte();
52 |
53 | final ByteBuf bb2 = Unpooled.buffer();
54 | packet.write(bb2, aead);
55 | final byte marker2 = bb2.readByte();
56 |
57 | assertTrue((0x80 & marker1) == 0x80);
58 | assertTrue((0x80 & marker2) == 0x80);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/reliability/AckDelayTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.reliability;
2 |
3 | import static org.junit.Assert.*;
4 | import static org.mockito.Mockito.when;
5 |
6 | import com.protocol7.quincy.utils.Ticker;
7 | import java.util.concurrent.TimeUnit;
8 | import org.junit.Before;
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 | import org.mockito.Mock;
12 | import org.mockito.junit.MockitoJUnitRunner;
13 |
14 | @RunWith(MockitoJUnitRunner.class)
15 | public class AckDelayTest {
16 |
17 | @Mock private Ticker ticker;
18 |
19 | private AckDelay ackDelay;
20 |
21 | @Before
22 | public void setUp() {
23 | when(ticker.nanoTime()).thenReturn(123L);
24 | ackDelay = new AckDelay(3, ticker);
25 | }
26 |
27 | @Test
28 | public void toAckDelay() {
29 | assertEquals(100, ackDelay.calculate(800 * 1000, TimeUnit.NANOSECONDS));
30 | }
31 |
32 | @Test
33 | public void toAckDelayNegative() {
34 | assertEquals(0, ackDelay.calculate(-800 * 1000, TimeUnit.NANOSECONDS));
35 | }
36 |
37 | @Test
38 | public void time() {
39 | assertEquals(123, ackDelay.time());
40 | }
41 |
42 | @Test
43 | public void delay() {
44 | assertEquals(23, ackDelay.delay(100));
45 | }
46 |
47 | @Test
48 | public void delayNegative() {
49 | assertEquals(0, ackDelay.delay(200));
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/reliability/AckQueueTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.reliability;
2 |
3 | import static com.protocol7.quincy.tls.EncryptionLevel.OneRtt;
4 | import static org.junit.Assert.*;
5 | import static org.mockito.Mockito.when;
6 |
7 | import com.protocol7.quincy.protocol.packets.ShortPacket;
8 | import com.protocol7.quincy.reliability.AckQueue.Entry;
9 | import java.util.List;
10 | import org.junit.Before;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 | import org.mockito.Mock;
14 | import org.mockito.junit.MockitoJUnitRunner;
15 |
16 | @RunWith(MockitoJUnitRunner.class)
17 | public class AckQueueTest {
18 |
19 | @Mock private ShortPacket packet1;
20 | @Mock private ShortPacket packet2;
21 | private final long pn1 = 1;
22 | private final long pn2 = 2;
23 |
24 | private final AckQueue queue = new AckQueue();
25 |
26 | @Before
27 | public void setUp() {
28 | when(packet1.getPacketNumber()).thenReturn(pn1);
29 | when(packet2.getPacketNumber()).thenReturn(pn2);
30 | }
31 |
32 | @Test
33 | public void test() {
34 | assertTrue(queue.drain(OneRtt).isEmpty());
35 |
36 | queue.add(packet1, 123);
37 | queue.add(packet2, 456);
38 |
39 | assertEquals(List.of(new Entry(1L, 123L), new Entry(2L, 456L)), queue.drain(OneRtt));
40 |
41 | assertTrue(queue.drain(OneRtt).isEmpty());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/streams/ReceivedDataBufferTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.streams;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import org.junit.Test;
6 |
7 | public class ReceivedDataBufferTest {
8 |
9 | public static final byte[] DATA1 = "hello".getBytes();
10 | public static final byte[] DATA2 = "world".getBytes();
11 |
12 | private final ReceivedDataBuffer buffer = new ReceivedDataBuffer();
13 |
14 | @Test
15 | public void inOrder() {
16 | buffer.onData(DATA1, 0, false);
17 | assertFalse(buffer.isDone());
18 | assertArrayEquals(DATA1, buffer.read().get());
19 |
20 | buffer.onData(DATA2, DATA1.length, true);
21 |
22 | assertArrayEquals(DATA2, buffer.read().get());
23 | assertTrue(buffer.isDone());
24 | }
25 |
26 | @Test
27 | public void outOfOrder() {
28 | buffer.onData(DATA2, DATA1.length, true);
29 |
30 | assertFalse(buffer.isDone());
31 | assertFalse(buffer.read().isPresent());
32 |
33 | buffer.onData(DATA1, 0, false);
34 |
35 | assertArrayEquals(DATA1, buffer.read().get());
36 | assertArrayEquals(DATA2, buffer.read().get());
37 | assertTrue(buffer.isDone());
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/streams/StreamsTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.streams;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import com.protocol7.quincy.PipelineContext;
6 | import com.protocol7.quincy.protocol.StreamId;
7 | import org.junit.Before;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.mockito.Mock;
11 | import org.mockito.junit.MockitoJUnitRunner;
12 |
13 | @RunWith(MockitoJUnitRunner.class)
14 | public class StreamsTest {
15 |
16 | @Mock PipelineContext ctx;
17 | @Mock StreamHandler listener;
18 |
19 | private Streams streams;
20 |
21 | @Before
22 | public void setUp() {
23 | this.streams = new Streams(ctx);
24 | }
25 |
26 | @Test
27 | public void openStream() {
28 | final Stream stream = streams.openStream(false, false, listener);
29 | assertEquals(3, stream.getId());
30 | }
31 |
32 | @Test
33 | public void createAndThenGet() {
34 | final long streamId = StreamId.next(-1, true, true);
35 | final DefaultStream stream1 = streams.getOrCreate(streamId, listener);
36 | final DefaultStream stream2 = streams.getOrCreate(streamId, listener);
37 |
38 | assertSame(stream1, stream2);
39 |
40 | // pick a different stream ID
41 | final DefaultStream stream3 =
42 | streams.getOrCreate(StreamId.next(streamId, true, true), listener);
43 | assertNotSame(stream1, stream3);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/quic/src/test/java/com/protocol7/quincy/utils/BitsTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.utils;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import org.junit.Test;
6 |
7 | public class BitsTest {
8 |
9 | @Test
10 | public void testSet() {
11 | assertEquals(3, Bits.set(2, 0));
12 | assertEquals(3, Bits.set(1, 1));
13 | assertEquals(1, Bits.set(1, 0));
14 | }
15 |
16 | @Test
17 | public void testUnset() {
18 | assertEquals(2, Bits.unset(3, 0));
19 | assertEquals(0, Bits.unset(1, 0));
20 | assertEquals(1, Bits.unset(1, 1));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/quic/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %d{HH:mm:ss.SSS} - %X{actor} %X{connectionid} %X{packetnumber} - %msg%n
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/quic/src/test/resources/quic-go.der:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/protocol7/quincy/74e49d8a84a77eada23283ed823b8bc7e7ce321e/quic/src/test/resources/quic-go.der
--------------------------------------------------------------------------------
/quic/src/test/resources/server.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICwDCCAagCCQDH1E/oPBWyETANBgkqhkiG9w0BAQsFADAiMSAwHgYDVQQDDBdu
3 | ZXR0eXF1aWMucHJvdG9jb2w3LmNvbTAeFw0xODExMTkwOTA1MzNaFw0xOTExMTkw
4 | OTA1MzNaMCIxIDAeBgNVBAMMF25ldHR5cXVpYy5wcm90b2NvbDcuY29tMIIBIjAN
5 | BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7eGSf1cmfBA6B4+esju7cIerk0/q
6 | o/TWAvybO6NBMUca2KL5tmdr+GBsB49Nn092dB6eXgoFm9tUKRZAPtdD8+Etd0G8
7 | eFbyntUTrvTvJuhLsLuK6jGQvmpoRIZ3Vt3Pj6gqTGtaxEbf/sScjPWK/WtLrTce
8 | CD3sZKxSl0orEI74OkqR1qgjESfRcHXodmlQPOoghNZWYJyOzttUx4iUKu4fGoom
9 | 0gO54g7m/QwCyRgP9U64thvjz2SmJnGewQZU59cWkl3fYmUpYBPvU4Sw3SgVc64W
10 | 8nfO8OJWoVqY95AN8A8NgtY8KtRJnHX60Uxp1w0vgTh3qhmFXvj0cCLESwIDAQAB
11 | MA0GCSqGSIb3DQEBCwUAA4IBAQCh6OSyT4h3pUsQVU4P7WfKwqrj2RF5PxKzo8vr
12 | jqI64JZXkm1cJN8oKcRwlxOCp58v3XoefBaMIiuL50cGBAjysqF3uP7Bb8J/1ACm
13 | kI9nR/ZkgI9RkmE9o1+vuwP8XRkiLC4UuNoasHbOKx7dO0ly/peo55qui/xbr08P
14 | WSRgXSsdc77pOObQc6qgH3wZY+SGGInmQu2XG9DpvgN0TqYHbh1j1tBnte+8ozrn
15 | /mduLSEqllQPralJri6NvFsPXi2cRUG9jcd8oEZUMJNpXgpT6rMUy6lIaDW5/ldp
16 | WiRULguzPqzV5X9k+mh7xvdNfiCocmC6f98N1jfqSeKyn417
17 | -----END CERTIFICATE-----
18 |
--------------------------------------------------------------------------------
/quic/src/test/resources/server.der:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/protocol7/quincy/74e49d8a84a77eada23283ed823b8bc7e7ce321e/quic/src/test/resources/server.der
--------------------------------------------------------------------------------
/quicly-testcontainer/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | quicly-testcontainer
6 |
7 |
8 | com.protocol7
9 | quincy-parent
10 | 0.0.1-SNAPSHOT
11 |
12 |
13 |
14 |
15 | org.testcontainers
16 | testcontainers
17 |
18 |
19 | ${project.groupId}
20 | quincy-common
21 |
22 |
23 | com.google.guava
24 | guava
25 |
26 |
27 | junit
28 | junit
29 |
30 |
31 |
32 |
33 |
34 |
35 | src/main/resources
36 |
37 | QuiclyDockerfile
38 | QuiclyClientDockerfile
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/quicly-testcontainer/src/main/java/com/protocol7/testcontainers/quicly/PacketParser.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.testcontainers.quicly;
2 |
3 | import com.protocol7.quincy.utils.Bytes;
4 | import com.protocol7.quincy.utils.Hex;
5 | import io.netty.buffer.ByteBuf;
6 | import io.netty.buffer.Unpooled;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | public class PacketParser {
11 |
12 | public static List parse(final List logs) {
13 | final List packets = new ArrayList<>();
14 |
15 | boolean inbound = false;
16 | final ByteBuf bb = Unpooled.buffer();
17 | for (final String log : List.copyOf(logs)) {
18 | if (log.startsWith("recvmsg") || log.startsWith("sendmsg")) {
19 | if (bb.writerIndex() > 0) {
20 | packets.add(new QuiclyPacket(inbound, Bytes.peekToArray(bb)));
21 | bb.clear();
22 | }
23 | inbound = log.startsWith("recvmsg");
24 | } else {
25 | bb.writeBytes(Hex.dehex(log.trim()));
26 | }
27 | }
28 | if (bb.writerIndex() > 0) {
29 | packets.add(new QuiclyPacket(inbound, Bytes.peekToArray(bb)));
30 | }
31 | return packets;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/quicly-testcontainer/src/main/java/com/protocol7/testcontainers/quicly/QuiclyClientContainer.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.testcontainers.quicly;
2 |
3 | import org.testcontainers.images.builder.ImageFromDockerfile;
4 |
5 | public class QuiclyClientContainer extends QuiclyContainer {
6 |
7 | private static ImageFromDockerfile image() {
8 | return new ImageFromDockerfile("quicly-client", false)
9 | .withFileFromClasspath("Dockerfile", "QuiclyClientDockerfile");
10 | }
11 |
12 | public QuiclyClientContainer(final String host, final int port) {
13 | super(image());
14 |
15 | // withCommand("./cli", "-vv", "-x", "X25519", host, Integer.toString(port));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/quicly-testcontainer/src/main/java/com/protocol7/testcontainers/quicly/QuiclyContainer.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.testcontainers.quicly;
2 |
3 | import com.github.dockerjava.api.command.InspectContainerResponse;
4 | import com.github.dockerjava.api.model.ExposedPort;
5 | import com.github.dockerjava.api.model.InternetProtocol;
6 | import java.net.InetSocketAddress;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 | import java.util.function.Consumer;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 | import org.testcontainers.containers.GenericContainer;
13 | import org.testcontainers.containers.output.OutputFrame;
14 | import org.testcontainers.containers.output.Slf4jLogConsumer;
15 | import org.testcontainers.images.builder.ImageFromDockerfile;
16 |
17 | public class QuiclyContainer extends GenericContainer {
18 |
19 | private final Logger log = LoggerFactory.getLogger("quicly");
20 |
21 | private final List logStatements = new ArrayList<>();
22 |
23 | public QuiclyContainer(final ImageFromDockerfile image) {
24 | super(image);
25 | }
26 |
27 | @Override
28 | protected void containerIsStarted(final InspectContainerResponse containerInfo) {
29 | final Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(log);
30 | followOutput(logConsumer);
31 | followOutput(
32 | new Consumer() {
33 | @Override
34 | public void accept(final OutputFrame outputFrame) {
35 | logStatements.add(outputFrame.getUtf8String());
36 | }
37 | });
38 | }
39 |
40 | private int getUdpPort() {
41 | return Integer.valueOf(
42 | getContainerInfo()
43 | .getNetworkSettings()
44 | .getPorts()
45 | .getBindings()
46 | .get(new ExposedPort(4433, InternetProtocol.UDP))[0]
47 | .getHostPortSpec());
48 | }
49 |
50 | public InetSocketAddress getAddress() {
51 | return new InetSocketAddress(getContainerIpAddress(), getUdpPort());
52 | }
53 |
54 | public List getPackets() {
55 | return PacketParser.parse(logStatements);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/quicly-testcontainer/src/main/java/com/protocol7/testcontainers/quicly/QuiclyPacket.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.testcontainers.quicly;
2 |
3 | import com.protocol7.quincy.utils.Hex;
4 |
5 | public class QuiclyPacket {
6 |
7 | private final boolean inbound;
8 | private final byte[] bytes;
9 |
10 | public QuiclyPacket(final boolean inbound, final byte[] bytes) {
11 | this.inbound = inbound;
12 | this.bytes = bytes;
13 | }
14 |
15 | public boolean isInbound() {
16 | return inbound;
17 | }
18 |
19 | public byte[] getBytes() {
20 | return bytes;
21 | }
22 |
23 | @Override
24 | public String toString() {
25 | return "QuiclyPacket{" + "inbound=" + inbound + ", bytes=" + Hex.hex(bytes) + '}';
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/quicly-testcontainer/src/main/java/com/protocol7/testcontainers/quicly/QuiclyServerContainer.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.testcontainers.quicly;
2 |
3 | import org.testcontainers.containers.wait.strategy.Wait;
4 | import org.testcontainers.images.builder.ImageFromDockerfile;
5 |
6 | public class QuiclyServerContainer extends QuiclyContainer {
7 |
8 | private static ImageFromDockerfile image() {
9 | return new ImageFromDockerfile("quicly", false)
10 | .withFileFromClasspath("Dockerfile", "QuiclyDockerfile");
11 | }
12 |
13 | public QuiclyServerContainer() {
14 | super(image());
15 |
16 | withExposedPorts(4433);
17 | waitingFor(Wait.forHealthcheck());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/quicly-testcontainer/src/main/resources/QuiclyClientDockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:18.04
2 |
3 | RUN apt-get update
4 | RUN apt-get --yes install git cmake gcc-6 g++-6 curl libssl-dev net-tools
5 |
6 | RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 60 --slave /usr/bin/g++ g++ /usr/bin/g++-6
7 |
8 | ARG hash=d4b83ba
9 |
10 | RUN git clone https://github.com/h2o/quicly.git
11 | RUN cd quicly && git checkout $hash && git submodule update --init --recursive && cmake -DOPENSSL_ROOT_DIR=$HOME/openssl-build . && make
12 |
13 | WORKDIR quicly
14 |
--------------------------------------------------------------------------------
/quicly-testcontainer/src/main/resources/QuiclyDockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:18.04
2 |
3 | RUN apt-get update
4 | RUN apt-get --yes install git cmake gcc-6 g++-6 curl libssl-dev net-tools
5 |
6 | RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 60 --slave /usr/bin/g++ g++ /usr/bin/g++-6
7 |
8 | ARG hash=bcd70c0147c52e62ad173e0e737e50f7d6819e0b
9 |
10 | RUN git clone https://github.com/h2o/quicly.git
11 | RUN cd quicly && git checkout $hash && git submodule update --init --recursive && cmake -DOPENSSL_ROOT_DIR=$HOME/openssl-build . && make
12 |
13 | WORKDIR quicly
14 |
15 | EXPOSE 4433/udp
16 |
17 | HEALTHCHECK --start-period=3s --interval=5s CMD netstat -an | grep 4433
18 |
19 | ENTRYPOINT ["./cli", "-c", "t/assets/server.crt", "-k", "t/assets/server.key", "-vv", "-x", "X25519", "-a", "http/1.1", "0.0.0.0", "4433"]
20 |
--------------------------------------------------------------------------------
/quicly-testcontainer/src/test/java/com/protocol7/testcontainers/quicly/PacketParserTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.testcontainers.quicly;
2 |
3 | import static org.junit.Assert.assertArrayEquals;
4 | import static org.junit.Assert.assertEquals;
5 |
6 | import com.protocol7.quincy.utils.Hex;
7 | import java.util.List;
8 | import org.junit.Test;
9 |
10 | public class PacketParserTest {
11 |
12 | @Test
13 | public void parse() {
14 | final List packets =
15 | PacketParser.parse(
16 | List.of(
17 | "recvmsg (1252 bytes):\n",
18 | " c7 ff 00 00 12 98 c1 e5 2e b8 22 96 d7 b4 d4 cc\n",
19 | " ec 03 b4 ef 52 48 3d ad 6d a0 63 15 bd\n",
20 | "sendmsg (1280 bytes):\n",
21 | " c0 ff 00 00 12 85 b4 ef 52 48 3d ad 6d a0 63 15\n",
22 | " bd ce 40 68 dd b5 75 4f b0 00 40 75 7b 69 05 4c\n"));
23 |
24 | assertEquals(2, packets.size());
25 |
26 | assertPacket(
27 | true,
28 | Hex.dehex(
29 | "c7 ff 00 00 12 98 c1 e5 2e b8 22 96 d7 b4 d4 cc ec 03 b4 ef 52 48 3d ad 6d a0 63 15 bd"),
30 | packets.get(0));
31 | assertPacket(
32 | false,
33 | Hex.dehex(
34 | "c0 ff 00 00 12 85 b4 ef 52 48 3d ad 6d a0 63 15 bd ce 40 68 dd b5 75 4f b0 00 40 75 7b 69 05 4c"),
35 | packets.get(1));
36 | }
37 |
38 | private void assertPacket(final boolean inbound, final byte[] bytes, final QuiclyPacket actual) {
39 | assertEquals(inbound, actual.isInbound());
40 | assertArrayEquals(bytes, actual.getBytes());
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tls/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | quincy-tls
6 | jar
7 |
8 |
9 | com.protocol7
10 | quincy-parent
11 | 0.0.1-SNAPSHOT
12 |
13 |
14 |
15 |
16 | ${project.groupId}
17 | quincy-common
18 |
19 |
20 | io.netty
21 | netty-buffer
22 |
23 |
24 | at.favre.lib
25 | hkdf
26 |
27 |
28 | com.google.guava
29 | guava
30 |
31 |
32 | org.slf4j
33 | slf4j-api
34 |
35 |
36 |
37 |
38 | junit
39 | junit
40 | test
41 |
42 |
43 | mockito-core
44 | org.mockito
45 | test
46 |
47 |
48 | ch.qos.logback
49 | logback-classic
50 | test
51 |
52 |
53 |
54 |
55 |
56 |
57 | org.apache.maven.plugins
58 | maven-jar-plugin
59 | 3.0.2
60 |
61 |
62 |
63 | test-jar
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/CertificateValidator.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.security.KeyStore;
6 | import java.security.KeyStoreException;
7 | import java.security.NoSuchAlgorithmException;
8 | import java.security.cert.CertificateException;
9 | import java.util.List;
10 |
11 | public interface CertificateValidator {
12 |
13 | static CertificateValidator defaults() {
14 | String trustStorePath = System.getProperty("javax.net.ssl.trustStore");
15 | String trustStorePwd = System.getProperty("javax.net.ssl.trustStorePassword");
16 | if (trustStorePath == null) {
17 | trustStorePath = System.getProperty("java.home") + "/lib/security/cacerts";
18 | trustStorePwd = "changeit";
19 | }
20 |
21 | final char[] pwd;
22 | if (trustStorePwd != null) {
23 | pwd = trustStorePwd.toCharArray();
24 | } else {
25 | pwd = null;
26 | }
27 |
28 | try {
29 | return new DefaultCertificateValidator(KeyStore.getInstance(new File(trustStorePath), pwd));
30 | } catch (final KeyStoreException
31 | | NoSuchAlgorithmException
32 | | CertificateException
33 | | IOException e) {
34 | throw new RuntimeException(e);
35 | }
36 | }
37 |
38 | boolean validate(List certificates);
39 | }
40 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/CipherSuite.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import java.util.ArrayList;
5 | import java.util.Collection;
6 | import java.util.EnumSet;
7 | import java.util.List;
8 | import java.util.Optional;
9 |
10 | public enum CipherSuite {
11 | TLS_AES_128_GCM_SHA256(0x1301),
12 | TLS_AES_256_GCM_SHA384(0x1302),
13 | TLS_CHACHA20_POLY1305_SHA256(0x1303);
14 |
15 | private static final EnumSet ALL = EnumSet.allOf(CipherSuite.class);
16 |
17 | public static final List SUPPORTED = List.of(TLS_AES_128_GCM_SHA256);
18 |
19 | public static List parseKnown(final ByteBuf bb) {
20 | final int len = bb.readShort() / 2;
21 |
22 | final List css = new ArrayList<>(len);
23 | for (int i = 0; i < len; i++) {
24 | fromValue(bb.readShort()).ifPresent(css::add);
25 | }
26 | return css;
27 | }
28 |
29 | public static Optional parseOne(final ByteBuf bb) {
30 | final int value = bb.readShort();
31 | return fromValue(value);
32 | }
33 |
34 | public static Optional fromValue(final int value) {
35 | for (final CipherSuite cs : ALL) {
36 | if (cs.value == value) {
37 | return Optional.ofNullable(cs);
38 | }
39 | }
40 | return Optional.empty();
41 | }
42 |
43 | public static void writeAll(final ByteBuf bb, final Collection css) {
44 | bb.writeShort(css.size() * 2);
45 |
46 | for (final CipherSuite cs : css) {
47 | bb.writeShort(cs.value);
48 | }
49 | }
50 |
51 | private final int value;
52 |
53 | CipherSuite(final int value) {
54 | this.value = value;
55 | }
56 |
57 | public int getValue() {
58 | return value;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/ConstantTimeEquals.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import java.security.MessageDigest;
4 |
5 | public class ConstantTimeEquals {
6 |
7 | public static boolean isEqual(final byte[] a, final byte[] b) {
8 | return MessageDigest.isEqual(a, b);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/DefaultCertificateValidator.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.security.GeneralSecurityException;
5 | import java.security.KeyStore;
6 | import java.security.cert.CertPath;
7 | import java.security.cert.CertPathValidator;
8 | import java.security.cert.CertPathValidatorException;
9 | import java.security.cert.CertificateException;
10 | import java.security.cert.CertificateFactory;
11 | import java.security.cert.PKIXParameters;
12 | import java.security.cert.X509Certificate;
13 | import java.util.List;
14 | import java.util.stream.Collectors;
15 |
16 | public class DefaultCertificateValidator implements CertificateValidator {
17 |
18 | private final KeyStore truststore;
19 |
20 | public DefaultCertificateValidator(final KeyStore truststore) {
21 | this.truststore = truststore;
22 | }
23 |
24 | @Override
25 | public boolean validate(final List certificates) {
26 | try {
27 | final CertificateFactory cf = CertificateFactory.getInstance("X.509");
28 |
29 | final List certlist =
30 | certificates
31 | .stream()
32 | .map(
33 | bytes -> {
34 | try {
35 | return (X509Certificate)
36 | cf.generateCertificate(new ByteArrayInputStream(bytes));
37 | } catch (CertificateException e) {
38 | throw new RuntimeException(e);
39 | }
40 | })
41 | .collect(Collectors.toUnmodifiableList());
42 |
43 | // Check the chain
44 | final CertPath cp = cf.generateCertPath(certlist);
45 |
46 | final PKIXParameters params = new PKIXParameters(truststore);
47 | params.setRevocationEnabled(false);
48 | final CertPathValidator cpv =
49 | CertPathValidator.getInstance(CertPathValidator.getDefaultType());
50 | cpv.validate(cp, params);
51 | return true;
52 | } catch (final CertPathValidatorException e) {
53 | e.printStackTrace();
54 | return false;
55 | } catch (final GeneralSecurityException e) {
56 | throw new RuntimeException(e);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/EncryptionLevel.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | public enum EncryptionLevel {
4 | Initial,
5 | Handshake,
6 | OneRtt;
7 | }
8 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/Group.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import java.util.EnumSet;
4 | import java.util.Optional;
5 |
6 | public enum Group {
7 | X25519(0x001d),
8 | X448(0x001e);
9 |
10 | public static Optional fromValue(final int value) {
11 | if (value == X25519.value) {
12 | return Optional.of(X25519);
13 | } else if (value == X448.value) {
14 | return Optional.of(X448);
15 | } else {
16 | return Optional.empty();
17 | }
18 | }
19 |
20 | public static final EnumSet ALL = EnumSet.allOf(Group.class);
21 |
22 | private final int value;
23 |
24 | Group(final int value) {
25 | this.value = value;
26 | }
27 |
28 | public int getValue() {
29 | return value;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/HKDF.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import com.protocol7.quincy.utils.Bytes;
4 | import io.netty.buffer.ByteBuf;
5 | import io.netty.buffer.Unpooled;
6 | import java.nio.charset.StandardCharsets;
7 |
8 | public class HKDF {
9 |
10 | private static final String LABEL_PREFIX = "tls13 ";
11 |
12 | public static final at.favre.lib.crypto.HKDF hkdf = at.favre.lib.crypto.HKDF.fromHmacSha256();
13 |
14 | // early_secret = hkdf-Extract(
15 | // salt=00,
16 | // key=00...)
17 | private static final byte[] EARLY_SECRET = hkdf.extract(new byte[1], new byte[32]);
18 | public static final byte[] EMPTY_HASH = Hash.sha256("".getBytes(StandardCharsets.US_ASCII));
19 |
20 | // derived_secret = hkdf-Expand-Label(
21 | // key = early_secret,
22 | // label = "derived",
23 | // context = empty_hash,
24 | // len = 32)
25 | private static final byte[] DERIVED_SECRET = expandLabel(EARLY_SECRET, "derived", EMPTY_HASH, 32);
26 |
27 | public static byte[] calculateHandshakeSecret(final byte[] sharedSecret) {
28 | // handshake_secret = hkdf-Extract(
29 | // salt = derived_secret,
30 | // key = shared_secret)
31 | return hkdf.extract(DERIVED_SECRET, sharedSecret);
32 | }
33 |
34 | public static byte[] extract(final byte[] salt, final byte[] inputKeyingMaterial) {
35 | return hkdf.extract(salt, inputKeyingMaterial);
36 | }
37 |
38 | public static byte[] expandLabel(
39 | final byte[] key, final String label, final byte[] context, final int length) {
40 | final byte[] expandedLabel = makeLabel(label, context, length);
41 | return hkdf.expand(key, expandedLabel, length);
42 | }
43 |
44 | private static byte[] makeLabel(final String label, final byte[] context, final int length) {
45 | final byte[] expandedLabel = (LABEL_PREFIX + label).getBytes(StandardCharsets.US_ASCII);
46 |
47 | final ByteBuf bb = Unpooled.buffer();
48 | bb.writeShort(length);
49 | bb.writeByte(expandedLabel.length);
50 |
51 | bb.writeBytes(expandedLabel);
52 |
53 | bb.writeByte(context.length);
54 | bb.writeBytes(context);
55 |
56 | return Bytes.drainToArray(bb);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/Hash.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import com.protocol7.quincy.utils.Bytes;
4 | import java.security.MessageDigest;
5 | import java.security.NoSuchAlgorithmException;
6 |
7 | public class Hash {
8 |
9 | private static final ThreadLocal digests =
10 | ThreadLocal.withInitial(
11 | () -> {
12 | try {
13 | return MessageDigest.getInstance("SHA-256");
14 | } catch (NoSuchAlgorithmException e) {
15 | throw new RuntimeException(e);
16 | }
17 | });
18 |
19 | public static byte[] sha256(final byte[]... data) {
20 | return digests.get().digest(Bytes.concat(data));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/NoopCertificateValidator.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import java.util.List;
4 |
5 | public class NoopCertificateValidator implements CertificateValidator {
6 |
7 | public static final CertificateValidator INSTANCE = new NoopCertificateValidator();
8 |
9 | private NoopCertificateValidator() {}
10 |
11 | @Override
12 | public boolean validate(final List certificates) {
13 | return true;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/ReceivedDataBuffer.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.buffer.Unpooled;
5 | import java.util.Optional;
6 | import java.util.TreeMap;
7 |
8 | // TODO optimize
9 | public class ReceivedDataBuffer {
10 |
11 | private final TreeMap buffer = new TreeMap<>();
12 |
13 | public void onData(final byte[] data, final long offset) {
14 | buffer.put(offset, data);
15 | }
16 |
17 | public Optional read() {
18 | long readOffset = 0;
19 | final ByteBuf bb = Unpooled.buffer();
20 |
21 | while (buffer.containsKey(readOffset)) {
22 | final byte[] b = buffer.get(readOffset);
23 | bb.writeBytes(b);
24 |
25 | readOffset += b.length;
26 | }
27 |
28 | if (bb.writerIndex() > 0) {
29 | return Optional.of(bb);
30 | } else {
31 | return Optional.empty();
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/VerifyData.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import static com.google.common.base.Preconditions.checkArgument;
4 | import static com.protocol7.quincy.tls.aead.Labels.FINISHED;
5 |
6 | import com.google.common.hash.Hashing;
7 |
8 | public class VerifyData {
9 |
10 | public static byte[] create(final byte[] handshakeTrafficSecret, final byte[] finishedHash) {
11 | checkArgument(handshakeTrafficSecret.length == 32);
12 | checkArgument(finishedHash.length == 32);
13 |
14 | // finished_key = HKDF-Expand-Label(
15 | // key = client_handshake_traffic_secret,
16 | // label = "finished",
17 | // context = "",
18 | // len = 32)
19 | final byte[] finishedKey = HKDF.expandLabel(handshakeTrafficSecret, FINISHED, new byte[0], 32);
20 |
21 | // verify_data = HMAC-SHA256(
22 | // key = finished_key,
23 | // msg = finished_hash)
24 | return Hashing.hmacSha256(finishedKey).hashBytes(finishedHash).asBytes();
25 | }
26 |
27 | public static boolean verify(
28 | final byte[] verifyData,
29 | final byte[] handshakeTrafficSecret,
30 | final byte[] finishedHash,
31 | final boolean quic) {
32 | checkArgument(verifyData.length > 0);
33 | checkArgument(handshakeTrafficSecret.length == 32);
34 | checkArgument(finishedHash.length == 32);
35 |
36 | final byte[] actual = create(handshakeTrafficSecret, finishedHash);
37 |
38 | return ConstantTimeEquals.isEqual(verifyData, actual);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/aead/AEADProvider.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.aead;
2 |
3 | import com.protocol7.quincy.tls.EncryptionLevel;
4 |
5 | public interface AEADProvider {
6 |
7 | AEAD get(EncryptionLevel level);
8 | }
9 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/aead/AEADs.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.aead;
2 |
3 | import static java.util.Objects.requireNonNull;
4 |
5 | import com.protocol7.quincy.tls.EncryptionLevel;
6 | import java.util.concurrent.atomic.AtomicReference;
7 |
8 | public class AEADs {
9 |
10 | private final AtomicReference initialAead;
11 | private final AtomicReference handshakeAead = new AtomicReference<>();
12 | private final AtomicReference oneRttAead = new AtomicReference<>();
13 |
14 | public AEADs(final AEAD initialAead) {
15 | this.initialAead = new AtomicReference<>(requireNonNull(initialAead));
16 | }
17 |
18 | public boolean available(final EncryptionLevel level) {
19 | if (level == EncryptionLevel.Initial) {
20 | return initialAead.get() != null;
21 | } else if (level == EncryptionLevel.Handshake) {
22 | return handshakeAead.get() != null;
23 | } else {
24 | return oneRttAead.get() != null;
25 | }
26 | }
27 |
28 | public AEAD get(final EncryptionLevel level) {
29 | requireNonNull(level);
30 |
31 | if (level == EncryptionLevel.Initial) {
32 | final AEAD aead = initialAead.get();
33 | if (aead == null) {
34 | throw new IllegalStateException("Initial AEAD not set");
35 | }
36 |
37 | return aead;
38 | } else if (level == EncryptionLevel.Handshake) {
39 | final AEAD aead = handshakeAead.get();
40 | if (aead == null) {
41 | throw new IllegalStateException("Handshake AEAD not set");
42 | }
43 |
44 | return aead;
45 | } else {
46 | final AEAD aead = oneRttAead.get();
47 | if (aead == null) {
48 | throw new IllegalStateException("1-RTT AEAD not set");
49 | }
50 |
51 | return aead;
52 | }
53 | }
54 |
55 | public void unsetInitialAead() {
56 | this.initialAead.set(null);
57 | }
58 |
59 | public void unsetHandshakeAead() {
60 | this.handshakeAead.set(null);
61 | }
62 |
63 | public void setHandshakeAead(final AEAD handshakeAead) {
64 | this.handshakeAead.set(requireNonNull(handshakeAead));
65 | }
66 |
67 | public void setOneRttAead(final AEAD oneRttAead) {
68 | this.oneRttAead.set(requireNonNull(oneRttAead));
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/aead/InitialAEAD.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.aead;
2 |
3 | import static com.protocol7.quincy.tls.aead.Labels.CLIENT_INITIAL;
4 | import static com.protocol7.quincy.tls.aead.Labels.HP_KEY;
5 | import static com.protocol7.quincy.tls.aead.Labels.IV;
6 | import static com.protocol7.quincy.tls.aead.Labels.KEY;
7 | import static com.protocol7.quincy.tls.aead.Labels.SERVER_INITIAL;
8 |
9 | import com.protocol7.quincy.tls.HKDF;
10 | import com.protocol7.quincy.utils.Hex;
11 |
12 | public class InitialAEAD {
13 |
14 | private static final byte[] QUIC_VERSION_1_SALT =
15 | Hex.dehex("afbfec289993d24c9e9786f19c6111e04390a899");
16 |
17 | public static AEAD create(final byte[] keyMaterial, final boolean isClient) {
18 | final byte[] initialSecret = HKDF.extract(QUIC_VERSION_1_SALT, keyMaterial);
19 |
20 | final int length = 32;
21 |
22 | final byte[] clientSecret = expand(initialSecret, CLIENT_INITIAL, length);
23 | final byte[] serverSecret = expand(initialSecret, SERVER_INITIAL, length);
24 |
25 | final byte[] mySecret;
26 | final byte[] otherSecret;
27 | if (isClient) {
28 | mySecret = clientSecret;
29 | otherSecret = serverSecret;
30 | } else {
31 | mySecret = serverSecret;
32 | otherSecret = clientSecret;
33 | }
34 |
35 | final byte[] myKey = expand(mySecret, KEY, 16);
36 | final byte[] myIV = expand(mySecret, IV, 12);
37 |
38 | final byte[] otherKey = expand(otherSecret, KEY, 16);
39 | final byte[] otherIV = expand(otherSecret, IV, 12);
40 |
41 | final byte[] myPnKey = expand(mySecret, HP_KEY, 16);
42 | final byte[] otherPnKey = expand(otherSecret, HP_KEY, 16);
43 |
44 | return new AEAD(myKey, otherKey, myIV, otherIV, myPnKey, otherPnKey);
45 | }
46 |
47 | private static byte[] expand(final byte[] secret, final String label, final int length) {
48 | return HKDF.expandLabel(secret, label, new byte[0], length);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/aead/Labels.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.aead;
2 |
3 | public class Labels {
4 |
5 | public static final String CLIENT_HANDSHAKE_TRAFFIC_SECRET = "c hs traffic";
6 | public static final String SERVER_HANDSHAKE_TRAFFIC_SECRET = "s hs traffic";
7 |
8 | public static final String CLIENT_APPLICATION_TRAFFIC_SECRET = "c ap traffic";
9 | public static final String SERVER_APPLICATION_TRAFFIC_SECRET = "s ap traffic";
10 |
11 | public static final String CLIENT_INITIAL = "client in";
12 | public static final String SERVER_INITIAL = "server in";
13 |
14 | public static final String DERIVED = "derived";
15 |
16 | public static final String KEY = "quic key";
17 | public static final String IV = "quic iv";
18 | public static final String HP_KEY = "quic hp";
19 |
20 | public static final String FINISHED = "finished";
21 |
22 | private Labels() {}
23 | }
24 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/aead/RetryTokenIntegrityTagAEAD.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.aead;
2 |
3 | import com.protocol7.quincy.tls.ConstantTimeEquals;
4 | import com.protocol7.quincy.utils.Hex;
5 | import java.security.GeneralSecurityException;
6 | import javax.crypto.Cipher;
7 | import javax.crypto.SecretKey;
8 | import javax.crypto.spec.GCMParameterSpec;
9 | import javax.crypto.spec.SecretKeySpec;
10 |
11 | public class RetryTokenIntegrityTagAEAD {
12 |
13 | private static final byte[] SECRET = Hex.dehex("ccce187ed09a09d05728155a6cb96be1");
14 | private static final byte[] NONCE = Hex.dehex("e54930f97f2136f0530a8c1c");
15 |
16 | public byte[] create(final byte[] retryPseudoPacket) throws GeneralSecurityException {
17 | final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "SunJCE");
18 | final SecretKey secretKey = new SecretKeySpec(SECRET, 0, SECRET.length, "AES");
19 | final GCMParameterSpec spec = new GCMParameterSpec(128, NONCE);
20 |
21 | cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec);
22 | cipher.updateAAD(retryPseudoPacket);
23 | return cipher.doFinal(new byte[0]);
24 | }
25 |
26 | public void verify(final byte[] retryPseudoPacket, final byte[] tag)
27 | throws GeneralSecurityException {
28 | final byte[] actualTag = create(retryPseudoPacket);
29 |
30 | if (!ConstantTimeEquals.isEqual(tag, actualTag)) {
31 | throw new RuntimeException("Invalid retry token integrity tag");
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/extensions/PskKeyExchangeModes.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.extensions;
2 |
3 | import static java.util.Arrays.asList;
4 |
5 | import io.netty.buffer.ByteBuf;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 | import java.util.Objects;
9 |
10 | public class PskKeyExchangeModes implements Extension {
11 |
12 | public static PskKeyExchangeModes defaults() {
13 | return new PskKeyExchangeModes(0x01); // PSK with (EC)DHE key establishment
14 | }
15 |
16 | public static PskKeyExchangeModes parse(final ByteBuf bb) {
17 | bb.readByte(); // length
18 |
19 | final List exchangeModes = new ArrayList<>();
20 | while (bb.isReadable()) {
21 | exchangeModes.add((int) bb.readByte());
22 | }
23 |
24 | return new PskKeyExchangeModes(exchangeModes);
25 | }
26 |
27 | private final List exchangeModes;
28 |
29 | public PskKeyExchangeModes(final List exchangeModes) {
30 | this.exchangeModes = exchangeModes;
31 | }
32 |
33 | public PskKeyExchangeModes(final Integer... exchangeModes) {
34 | this(asList(exchangeModes));
35 | }
36 |
37 | @Override
38 | public ExtensionType getType() {
39 | return ExtensionType.PSK_KEY_EXCHANGE_MODES;
40 | }
41 |
42 | public List getExchangeModes() {
43 | return exchangeModes;
44 | }
45 |
46 | @Override
47 | public void write(final ByteBuf bb, final boolean ignored) {
48 | bb.writeByte(exchangeModes.size());
49 |
50 | for (final int exchangeMode : exchangeModes) {
51 | bb.writeByte(exchangeMode);
52 | }
53 | }
54 |
55 | @Override
56 | public boolean equals(final Object o) {
57 | if (this == o) return true;
58 | if (o == null || getClass() != o.getClass()) return false;
59 | final PskKeyExchangeModes that = (PskKeyExchangeModes) o;
60 | return Objects.equals(exchangeModes, that.exchangeModes);
61 | }
62 |
63 | @Override
64 | public int hashCode() {
65 | return Objects.hash(exchangeModes);
66 | }
67 |
68 | @Override
69 | public String toString() {
70 | return "PskKeyExchangeModes{" + "exchangeModes=" + exchangeModes + '}';
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/extensions/RawExtension.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.extensions;
2 |
3 | import com.protocol7.quincy.utils.Bytes;
4 | import com.protocol7.quincy.utils.Hex;
5 | import io.netty.buffer.ByteBuf;
6 |
7 | public class RawExtension implements Extension {
8 |
9 | public static RawExtension parse(final ExtensionType type, final ByteBuf bb) {
10 | final byte[] b = Bytes.peekToArray(bb);
11 | return new RawExtension(type, b);
12 | }
13 |
14 | private final ExtensionType type;
15 | private final byte[] data;
16 |
17 | public RawExtension(final ExtensionType type, final byte[] data) {
18 | this.type = type;
19 | this.data = data;
20 | }
21 |
22 | @Override
23 | public ExtensionType getType() {
24 | return type;
25 | }
26 |
27 | public byte[] getData() {
28 | return data;
29 | }
30 |
31 | @Override
32 | public void write(final ByteBuf bb, final boolean isClient) {
33 | bb.writeBytes(data);
34 | }
35 |
36 | @Override
37 | public String toString() {
38 | return "RawExtension{" + "type=" + type + ", data=" + Hex.hex(data) + '}';
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/extensions/ServerName.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.extensions;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import java.nio.charset.StandardCharsets;
5 | import java.util.Objects;
6 |
7 | public class ServerName implements Extension {
8 |
9 | public static ServerName parse(final ByteBuf bb) {
10 | // TODO handle multiple names
11 | bb.readShort(); // first item length
12 | final byte nameType = bb.readByte();
13 |
14 | if (nameType != 0) {
15 | throw new IllegalArgumentException("Unknown name type " + nameType);
16 | }
17 |
18 | final int len = bb.readShort();
19 | final byte[] b = new byte[len];
20 | bb.readBytes(b);
21 |
22 | final String serverName = new String(b, StandardCharsets.US_ASCII);
23 |
24 | return new ServerName(serverName);
25 | }
26 |
27 | private final String serverName;
28 |
29 | public ServerName(final String serverName) {
30 | this.serverName = serverName;
31 | }
32 |
33 | @Override
34 | public ExtensionType getType() {
35 | return ExtensionType.SERVER_NAME;
36 | }
37 |
38 | @Override
39 | public void write(final ByteBuf bb, final boolean ignored) {
40 | final byte[] b = serverName.getBytes(StandardCharsets.US_ASCII);
41 |
42 | bb.writeShort(b.length + 3);
43 | bb.writeByte(0); // name type
44 | bb.writeShort(b.length);
45 | bb.writeBytes(b);
46 | }
47 |
48 | public String getServerName() {
49 | return serverName;
50 | }
51 |
52 | @Override
53 | public boolean equals(final Object o) {
54 | if (this == o) return true;
55 | if (o == null || getClass() != o.getClass()) return false;
56 | final ServerName that = (ServerName) o;
57 | return Objects.equals(serverName, that.serverName);
58 | }
59 |
60 | @Override
61 | public int hashCode() {
62 | return Objects.hash(serverName);
63 | }
64 |
65 | @Override
66 | public String toString() {
67 | return "ServerName{" + serverName + '}';
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/extensions/SignatureAlgorithms.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.extensions;
2 |
3 | import static java.util.Arrays.asList;
4 |
5 | import io.netty.buffer.ByteBuf;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 | import java.util.Objects;
9 |
10 | public class SignatureAlgorithms implements Extension {
11 |
12 | public static SignatureAlgorithms defaults() {
13 | return new SignatureAlgorithms(0x0804); // RSA-PSS-RSAE-SHA256
14 | }
15 |
16 | public static SignatureAlgorithms parse(final ByteBuf bb) {
17 | bb.readShort(); // length
18 |
19 | final List algorithms = new ArrayList<>();
20 | while (bb.isReadable()) {
21 | algorithms.add((int) bb.readShort());
22 | }
23 |
24 | return new SignatureAlgorithms(algorithms);
25 | }
26 |
27 | private final List algorithms;
28 |
29 | public SignatureAlgorithms(final List algorithms) {
30 | this.algorithms = algorithms;
31 | }
32 |
33 | public SignatureAlgorithms(final Integer... algorithms) {
34 | this(asList(algorithms));
35 | }
36 |
37 | @Override
38 | public ExtensionType getType() {
39 | return ExtensionType.SIGNATURE_ALGORITHMS;
40 | }
41 |
42 | public List getAlgorithms() {
43 | return algorithms;
44 | }
45 |
46 | @Override
47 | public void write(final ByteBuf bb, final boolean ignored) {
48 | bb.writeShort(algorithms.size() * 2);
49 |
50 | for (final int algorithm : algorithms) {
51 | bb.writeShort(algorithm);
52 | }
53 | }
54 |
55 | @Override
56 | public boolean equals(final Object o) {
57 | if (this == o) return true;
58 | if (o == null || getClass() != o.getClass()) return false;
59 | final SignatureAlgorithms that = (SignatureAlgorithms) o;
60 | return Objects.equals(algorithms, that.algorithms);
61 | }
62 |
63 | @Override
64 | public int hashCode() {
65 | return Objects.hash(algorithms);
66 | }
67 |
68 | @Override
69 | public String toString() {
70 | return "SignatureAlgorithms{" + algorithms + '}';
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/extensions/SupportedGroups.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.extensions;
2 |
3 | import com.google.common.collect.ImmutableList;
4 | import com.google.common.collect.ImmutableList.Builder;
5 | import com.protocol7.quincy.tls.Group;
6 | import io.netty.buffer.ByteBuf;
7 | import java.util.List;
8 | import java.util.Objects;
9 | import java.util.Optional;
10 |
11 | public class SupportedGroups implements Extension {
12 |
13 | public static SupportedGroups parse(final ByteBuf bb) {
14 | bb.readShort();
15 |
16 | final Builder groups = ImmutableList.builder();
17 |
18 | while (bb.isReadable()) {
19 | final Optional group = Group.fromValue(bb.readShort());
20 | group.ifPresent(groups::add);
21 | }
22 |
23 | return new SupportedGroups(groups.build());
24 | }
25 |
26 | private final List groups;
27 |
28 | public SupportedGroups(final List groups) {
29 | this.groups = groups;
30 | }
31 |
32 | public SupportedGroups(final Group... groups) {
33 | this.groups = ImmutableList.copyOf(groups);
34 | }
35 |
36 | public List getGroups() {
37 | return groups;
38 | }
39 |
40 | @Override
41 | public ExtensionType getType() {
42 | return ExtensionType.SUPPORTED_GROUPS;
43 | }
44 |
45 | @Override
46 | public void write(final ByteBuf bb, final boolean isClient) {
47 | bb.writeShort(groups.size() * 2);
48 |
49 | for (final Group group : groups) {
50 | bb.writeShort(group.getValue());
51 | }
52 | }
53 |
54 | @Override
55 | public boolean equals(final Object o) {
56 | if (this == o) return true;
57 | if (o == null || getClass() != o.getClass()) return false;
58 | final SupportedGroups that = (SupportedGroups) o;
59 | return Objects.equals(groups, that.groups);
60 | }
61 |
62 | @Override
63 | public int hashCode() {
64 | return Objects.hash(groups);
65 | }
66 |
67 | @Override
68 | public String toString() {
69 | return "SupportedGroups{" + groups + '}';
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/extensions/SupportedVersion.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.extensions;
2 |
3 | import java.util.Arrays;
4 |
5 | public enum SupportedVersion {
6 | TLS13(new byte[] {3, 4}),
7 | UNKNOWN(new byte[0]);
8 |
9 | public static SupportedVersion fromValue(final byte[] value) {
10 | if (value.length != 2) {
11 | throw new IllegalArgumentException("Invalid value length: " + value.length);
12 | }
13 |
14 | if (Arrays.equals(TLS13.value, value)) {
15 | return TLS13;
16 | } else {
17 | return UNKNOWN;
18 | }
19 | }
20 |
21 | private final byte[] value;
22 |
23 | SupportedVersion(final byte[] value) {
24 | this.value = value;
25 | }
26 |
27 | public byte[] getValue() {
28 | return value;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/extensions/TransportParameterType.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.extensions;
2 |
3 | import com.google.common.base.Preconditions;
4 | import java.util.EnumSet;
5 |
6 | public enum TransportParameterType {
7 | ORIGINAL_DESTINATION_CONNECTION_ID(0x0000),
8 | MAX_IDLE_TIMEOUT(0x0001),
9 | STATELESS_RESET_TOKEN(0x0002),
10 | MAX_UDP_PACKET_SIZE(0x0003),
11 | INITIAL_MAX_DATA(0x0004),
12 | INITIAL_MAX_STREAM_DATA_BIDI_LOCAL(0x0005),
13 | INITIAL_MAX_STREAM_DATA_BIDI_REMOTE(0x0006),
14 | INITIAL_MAX_STREAM_DATA_UNI(0x0007),
15 | INITIAL_MAX_STREAMS_BIDI(0x0008),
16 | INITIAL_MAX_STREAMS_UNI(0x0009),
17 | ACK_DELAY_EXPONENT(0x000a),
18 | MAX_ACK_DELAY(0x000b),
19 | DISABLE_ACTIVE_MIGRATION(0x000c),
20 | PREFERRED_ADDRESS(0x000d),
21 | ACTIVE_CONNECTION_ID_LIMIT(0x000e),
22 | INITIAL_SOURCE_CONNECTION_ID(0x000f),
23 | RETRY_SOURCE_CONNECTION_ID(0x0010),
24 | UNKNOWN(0xFFFF);
25 |
26 | public static TransportParameterType fromValue(final byte[] value) {
27 | Preconditions.checkArgument(value.length == 2);
28 |
29 | return fromValue(value[0] << 8 | value[1]);
30 | }
31 |
32 | public static TransportParameterType fromValue(final int value) {
33 | for (final TransportParameterType tp : EnumSet.allOf(TransportParameterType.class)) {
34 | if (tp.value == value) {
35 | return tp;
36 | }
37 | }
38 | return UNKNOWN;
39 | }
40 |
41 | private final short value;
42 |
43 | TransportParameterType(final int value) {
44 | this.value = (short) value;
45 | }
46 |
47 | public int getValue() {
48 | return value;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/messages/EncryptedExtensions.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.messages;
2 |
3 | import static com.protocol7.quincy.tls.messages.MessageType.ENCRYPTED_EXTENSIONS;
4 |
5 | import com.protocol7.quincy.Writeable;
6 | import com.protocol7.quincy.tls.extensions.Extension;
7 | import com.protocol7.quincy.utils.Bytes;
8 | import io.netty.buffer.ByteBuf;
9 | import java.util.Arrays;
10 | import java.util.List;
11 |
12 | public class EncryptedExtensions implements Message, Writeable {
13 |
14 | private static final MessageType TYPE = ENCRYPTED_EXTENSIONS;
15 |
16 | public static EncryptedExtensions parse(final ByteBuf bb, final boolean isClient) {
17 | // EE
18 | final int eeType = bb.readByte();
19 | if (eeType != TYPE.getType()) {
20 | throw new IllegalArgumentException("Invalid EE type: " + eeType);
21 | }
22 |
23 | final int eeMsgLen = Bytes.read24(bb);
24 | final int extLen = bb.readShort();
25 |
26 | final ByteBuf ext = bb.readBytes(extLen);
27 | try {
28 | final List extensions = Extension.parseAll(ext, isClient);
29 |
30 | return new EncryptedExtensions(extensions);
31 | } finally {
32 | ext.release();
33 | }
34 | }
35 |
36 | private final List extensions;
37 |
38 | public EncryptedExtensions(final List extensions) {
39 | this.extensions = extensions;
40 | }
41 |
42 | public EncryptedExtensions(final Extension... extensions) {
43 | this.extensions = Arrays.asList(extensions);
44 | }
45 |
46 | public List getExtensions() {
47 | return extensions;
48 | }
49 |
50 | public void write(final ByteBuf bb) {
51 | // EE
52 | bb.writeByte(TYPE.getType());
53 | final int eeMsgLenPos = bb.writerIndex();
54 | Bytes.write24(bb, 0);
55 |
56 | final int extLenPos = bb.writerIndex();
57 | bb.writeShort(0);
58 |
59 | Extension.writeAll(extensions, bb, false);
60 |
61 | Bytes.set24(bb, eeMsgLenPos, bb.writerIndex() - eeMsgLenPos - 3);
62 | bb.setShort(extLenPos, bb.writerIndex() - extLenPos - 2);
63 | }
64 |
65 | @Override
66 | public MessageType getType() {
67 | return TYPE;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/messages/Finished.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.messages;
2 |
3 | import static com.protocol7.quincy.tls.messages.MessageType.FINISHED;
4 |
5 | import com.protocol7.quincy.Writeable;
6 | import com.protocol7.quincy.tls.VerifyData;
7 | import com.protocol7.quincy.utils.Bytes;
8 | import io.netty.buffer.ByteBuf;
9 |
10 | public class Finished implements Message, Writeable {
11 |
12 | private static final MessageType TYPE = FINISHED;
13 |
14 | public static Finished createClientFinished(
15 | final byte[] clientHandshakeTrafficSecret, final byte[] finHash) {
16 | final byte[] verifyData = VerifyData.create(clientHandshakeTrafficSecret, finHash);
17 |
18 | return new Finished(verifyData);
19 | }
20 |
21 | public static Finished parse(final ByteBuf bb) {
22 | // server handshake finished
23 | final int finType = bb.readByte();
24 | if (finType != TYPE.getType()) {
25 | throw new IllegalArgumentException("Invalid fin type: " + finType);
26 | }
27 |
28 | final int finLen = Bytes.read24(bb);
29 |
30 | final byte[] verifyData = new byte[finLen];
31 | bb.readBytes(verifyData);
32 |
33 | return new Finished(verifyData);
34 | }
35 |
36 | private final byte[] verificationData;
37 |
38 | public Finished(final byte[] verificationData) {
39 | this.verificationData = verificationData;
40 | }
41 |
42 | public byte[] getVerificationData() {
43 | return verificationData;
44 | }
45 |
46 | public void write(final ByteBuf bb) {
47 | // server handshake finished
48 | bb.writeByte(TYPE.getType());
49 | Bytes.write24(bb, verificationData.length);
50 | bb.writeBytes(verificationData);
51 | }
52 |
53 | @Override
54 | public MessageType getType() {
55 | return TYPE;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/messages/Message.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.messages;
2 |
3 | import io.netty.buffer.ByteBuf;
4 |
5 | public interface Message {
6 |
7 | static Message parse(final ByteBuf bb, final boolean isClient) {
8 | final MessageType type = MessageType.from(bb.getByte(bb.readerIndex()));
9 |
10 | if (type == MessageType.CLIENT_HELLO) {
11 | return ClientHello.parse(bb, isClient);
12 | } else if (type == MessageType.SERVER_HELLO) {
13 | return ServerHello.parse(bb, isClient);
14 | } else if (type == MessageType.ENCRYPTED_EXTENSIONS) {
15 | return EncryptedExtensions.parse(bb, isClient);
16 | } else if (type == MessageType.SERVER_CERTIFICATE) {
17 | return ServerCertificate.parse(bb);
18 | } else if (type == MessageType.SERVER_CERTIFICATE_VERIFY) {
19 | return ServerCertificateVerify.parse(bb);
20 | } else if (type == MessageType.FINISHED) {
21 | return Finished.parse(bb);
22 | } else {
23 | throw new IllegalArgumentException("Unknown TLS message type");
24 | }
25 | }
26 |
27 | MessageType getType();
28 | }
29 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/messages/MessageType.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.messages;
2 |
3 | public enum MessageType {
4 | CLIENT_HELLO((byte) 0x01),
5 | SERVER_HELLO((byte) 0x02),
6 | ENCRYPTED_EXTENSIONS((byte) 0x08),
7 | SERVER_CERTIFICATE((byte) 0x0b),
8 | SERVER_CERTIFICATE_VERIFY((byte) 0x0f),
9 | FINISHED((byte) 0x14);
10 |
11 | public static MessageType from(final byte type) {
12 | if (type == CLIENT_HELLO.type) {
13 | return CLIENT_HELLO;
14 | } else if (type == SERVER_HELLO.type) {
15 | return SERVER_HELLO;
16 | } else if (type == ENCRYPTED_EXTENSIONS.type) {
17 | return ENCRYPTED_EXTENSIONS;
18 | } else if (type == SERVER_CERTIFICATE.type) {
19 | return SERVER_CERTIFICATE;
20 | } else if (type == SERVER_CERTIFICATE_VERIFY.type) {
21 | return SERVER_CERTIFICATE_VERIFY;
22 | } else if (type == FINISHED.type) {
23 | return FINISHED;
24 | } else {
25 | throw new IllegalArgumentException("Unknown TLS message type: " + type);
26 | }
27 | }
28 |
29 | private final byte type;
30 |
31 | MessageType(final byte type) {
32 | this.type = type;
33 | }
34 |
35 | public byte getType() {
36 | return type;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tls/src/main/java/com/protocol7/quincy/tls/messages/ServerCertificateVerify.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.messages;
2 |
3 | import static com.protocol7.quincy.tls.messages.MessageType.SERVER_CERTIFICATE_VERIFY;
4 |
5 | import com.protocol7.quincy.Writeable;
6 | import com.protocol7.quincy.utils.Bytes;
7 | import io.netty.buffer.ByteBuf;
8 |
9 | public class ServerCertificateVerify implements Message, Writeable {
10 |
11 | private static final MessageType TYPE = SERVER_CERTIFICATE_VERIFY;
12 |
13 | public static ServerCertificateVerify parse(final ByteBuf bb) {
14 | // server cert verify
15 | final int serverCertVerifyType = bb.readByte();
16 | if (serverCertVerifyType != TYPE.getType()) {
17 | throw new IllegalArgumentException(
18 | "Invalid server cert verify type: " + serverCertVerifyType);
19 | }
20 |
21 | final int scvMsgLen = Bytes.read24(bb);
22 |
23 | final int signType = bb.readShort();
24 | final int signLen = bb.readShort();
25 |
26 | final byte[] sign = new byte[signLen];
27 | bb.readBytes(sign);
28 |
29 | return new ServerCertificateVerify(signType, sign);
30 | }
31 |
32 | private final int type;
33 | private final byte[] signature;
34 |
35 | public ServerCertificateVerify(final int type, final byte[] signature) {
36 | this.type = type;
37 | this.signature = signature;
38 | }
39 |
40 | public int getVerifyType() {
41 | return type;
42 | }
43 |
44 | public byte[] getSignature() {
45 | return signature;
46 | }
47 |
48 | public void write(final ByteBuf bb) {
49 | // server cert verify
50 | bb.writeByte(TYPE.getType());
51 |
52 | final int scvMsgLenPos = bb.writerIndex();
53 | Bytes.write24(bb, 0);
54 |
55 | bb.writeShort(type);
56 | bb.writeShort(signature.length);
57 | bb.writeBytes(signature);
58 |
59 | Bytes.set24(bb, scvMsgLenPos, bb.writerIndex() - scvMsgLenPos - 3);
60 | }
61 |
62 | @Override
63 | public MessageType getType() {
64 | return TYPE;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/CertificateVerifyTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import com.protocol7.quincy.utils.Hex;
4 | import java.security.PrivateKey;
5 | import java.security.PublicKey;
6 | import org.junit.Assert;
7 | import org.junit.Test;
8 |
9 | public class CertificateVerifyTest {
10 |
11 | @Test
12 | public void roundtrip() {
13 | final byte[] hash =
14 | Hex.dehex("3e66361ada42c7cb97f9a62b00cae1d8b584174c745f9a338cf9f7cdd51d15f8");
15 |
16 | final PrivateKey privateKey = KeyUtil.getPrivateKey("src/test/resources/server.der");
17 | final byte[] actual = CertificateVerify.sign(hash, privateKey, false);
18 |
19 | final PublicKey publicKey =
20 | KeyUtil.getCertFromCrt("src/test/resources/server.crt").getPublicKey();
21 |
22 | Assert.assertTrue(CertificateVerify.verify(actual, hash, publicKey, false));
23 | }
24 |
25 | @Test
26 | public void verifyKnown() {
27 | // from quic-go, including a copy of the certificate for this test
28 | final byte[] sign =
29 | Hex.dehex(
30 | "868b5cae8578cbb3a5e4f2290e164623565bf8671eda69c2f225c138429f32b29e99dcb284bd0b1926579682a5130c79e5657d375e3f92bcf4ac7f8f47f999e1e5034c91f569a6fe908b1b9b8bc6064201359640f9e5dfd290dc8a3450f4dd474bac948c3bb09a016bbd01f25b19e8d305ccfb690e1030331d5769fcd89eba8522deda6f070ca476f416eecb44f3996c1a7be07243f8fbf04ea5baef55ebbf8fa2d037d1b032796d99e4dc44194a99e3c2e93d0dc6bb365c3685070ba4aea94693aa73811cfcb5d56ea3295779562d23bba5838eee1121b9851bb795862f1b060b7a6611577ada8f76da34ff11a65c1380985e47996363726a2d739ac84754a1");
31 | final byte[] hash =
32 | Hex.dehex("173322914473dfc8651a3f549eb7118a706be48f305aa738429d9af647f7722b");
33 |
34 | final PublicKey publicKey = KeyUtil.getPublicKey("src/test/resources/quic-go.der");
35 |
36 | Assert.assertTrue(CertificateVerify.verify(sign, hash, publicKey, false));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/CipherSuiteTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import static com.protocol7.quincy.tls.CipherSuite.TLS_AES_128_GCM_SHA256;
4 | import static com.protocol7.quincy.tls.CipherSuite.TLS_AES_256_GCM_SHA384;
5 | import static com.protocol7.quincy.tls.TestUtil.assertHex;
6 | import static org.junit.Assert.assertEquals;
7 | import static org.junit.Assert.assertFalse;
8 |
9 | import com.protocol7.quincy.utils.Hex;
10 | import io.netty.buffer.ByteBuf;
11 | import io.netty.buffer.Unpooled;
12 | import java.util.List;
13 | import org.junit.Test;
14 |
15 | public class CipherSuiteTest {
16 |
17 | @Test
18 | public void parseKnown() {
19 | final ByteBuf bb = Unpooled.wrappedBuffer(Hex.dehex("00 04 13 01 99 99"));
20 | assertEquals(List.of(TLS_AES_128_GCM_SHA256), CipherSuite.parseKnown(bb));
21 | }
22 |
23 | @Test
24 | public void parseOne() {
25 | final ByteBuf bb = Unpooled.wrappedBuffer(Hex.dehex("13 01"));
26 | assertEquals(TLS_AES_128_GCM_SHA256, CipherSuite.parseOne(bb).get());
27 | }
28 |
29 | @Test
30 | public void parseOneUnknown() {
31 | final ByteBuf bb = Unpooled.wrappedBuffer(Hex.dehex("99 99"));
32 | assertFalse(CipherSuite.parseOne(bb).isPresent());
33 | }
34 |
35 | @Test
36 | public void writeAll() {
37 | final List cipherSuites = List.of(TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384);
38 |
39 | final ByteBuf bb = Unpooled.buffer();
40 | CipherSuite.writeAll(bb, cipherSuites);
41 |
42 | assertHex("000413011302", bb);
43 | }
44 |
45 | @Test
46 | public void fromValue() {
47 | assertEquals(TLS_AES_128_GCM_SHA256, CipherSuite.fromValue(0x1301).get());
48 | }
49 |
50 | @Test
51 | public void fromValueUnknown() {
52 | assertFalse(CipherSuite.fromValue(0x9999).isPresent());
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/GroupTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import static com.protocol7.quincy.tls.Group.X25519;
4 | import static com.protocol7.quincy.tls.Group.X448;
5 | import static org.junit.Assert.assertEquals;
6 | import static org.junit.Assert.assertFalse;
7 |
8 | import org.junit.Test;
9 |
10 | public class GroupTest {
11 |
12 | @Test
13 | public void fromValue() {
14 | assertEquals(X25519, Group.fromValue(X25519.getValue()).get());
15 | assertEquals(X448, Group.fromValue(X448.getValue()).get());
16 | }
17 |
18 | @Test
19 | public void unknownFromValue() {
20 | assertFalse(Group.fromValue(123).isPresent());
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/HashTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import org.junit.Test;
4 |
5 | public class HashTest {
6 |
7 | @Test
8 | public void multiple256() {
9 | TestUtil.assertHex(
10 | "936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af",
11 | Hash.sha256("hello".getBytes(), "world".getBytes()));
12 | }
13 |
14 | @Test
15 | public void single256() {
16 | TestUtil.assertHex(
17 | "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
18 | Hash.sha256("hello".getBytes()));
19 | }
20 |
21 | @Test
22 | public void empty256() {
23 | TestUtil.assertHex(
24 | "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", Hash.sha256());
25 | }
26 |
27 | @Test(expected = NullPointerException.class)
28 | public void null256() {
29 | Hash.sha256(null);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/KeyExchangeTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import static org.junit.Assert.assertArrayEquals;
4 | import static org.junit.Assert.assertEquals;
5 |
6 | import org.junit.Test;
7 |
8 | public class KeyExchangeTest {
9 |
10 | @Test
11 | public void verifyKeys() {
12 | final KeyExchange keys = KeyExchange.generate(Group.X25519);
13 |
14 | assertEquals(32, keys.getPrivateKey().length);
15 | assertEquals(32, keys.getPublicKey().length);
16 | }
17 |
18 | @Test
19 | public void keyAgreementX25519() {
20 | final KeyExchange alice = KeyExchange.generate(Group.X25519);
21 | final KeyExchange bob = KeyExchange.generate(Group.X25519);
22 |
23 | final byte[] aliceShared = alice.generateSharedSecret(bob.getPublicKey());
24 | final byte[] bobShared = bob.generateSharedSecret(alice.getPublicKey());
25 |
26 | assertArrayEquals(aliceShared, bobShared);
27 | }
28 |
29 | @Test
30 | public void keyAgreementX448() {
31 | final KeyExchange alice = KeyExchange.generate(Group.X448);
32 | final KeyExchange bob = KeyExchange.generate(Group.X448);
33 |
34 | final byte[] aliceShared = alice.generateSharedSecret(bob.getPublicKey());
35 | final byte[] bobShared = bob.generateSharedSecret(alice.getPublicKey());
36 |
37 | assertArrayEquals(aliceShared, bobShared);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/KeyUtil.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import java.io.FileInputStream;
4 | import java.io.IOException;
5 | import java.nio.file.Files;
6 | import java.nio.file.Path;
7 | import java.security.GeneralSecurityException;
8 | import java.security.KeyFactory;
9 | import java.security.PrivateKey;
10 | import java.security.PublicKey;
11 | import java.security.cert.Certificate;
12 | import java.security.cert.CertificateFactory;
13 | import java.security.cert.X509Certificate;
14 | import java.security.spec.PKCS8EncodedKeySpec;
15 | import java.util.List;
16 |
17 | public class KeyUtil {
18 |
19 | public static PublicKey getPublicKey(final String path) {
20 | try {
21 | final CertificateFactory fact = CertificateFactory.getInstance("X.509");
22 |
23 | final Certificate cert = fact.generateCertificate(new FileInputStream(path));
24 | return cert.getPublicKey();
25 | } catch (final GeneralSecurityException | IOException e) {
26 | throw new RuntimeException(e);
27 | }
28 | }
29 |
30 | public static X509Certificate getCertFromCrt(final String path) {
31 | try {
32 | final FileInputStream fin = new FileInputStream(path);
33 | final CertificateFactory f = CertificateFactory.getInstance("X.509");
34 | return (X509Certificate) f.generateCertificate(fin);
35 | } catch (final GeneralSecurityException | IOException e) {
36 | throw new RuntimeException(e);
37 | }
38 | }
39 |
40 | public static List getCertsFromCrt(final String path) {
41 | try {
42 | final FileInputStream fin = new FileInputStream(path);
43 | final CertificateFactory f = CertificateFactory.getInstance("X.509");
44 | final Certificate cert = f.generateCertificate(fin);
45 | return List.of(cert.getEncoded());
46 | } catch (final GeneralSecurityException | IOException e) {
47 | throw new RuntimeException(e);
48 | }
49 | }
50 |
51 | public static PrivateKey getPrivateKey(final String path) {
52 | try {
53 | final byte[] b = Files.readAllBytes(Path.of(path));
54 | final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(b);
55 |
56 | final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
57 |
58 | return keyFactory.generatePrivate(keySpec);
59 | } catch (final GeneralSecurityException | IOException e) {
60 | throw new RuntimeException(e);
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/TestUtil.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import static com.protocol7.quincy.utils.Hex.hex;
4 | import static org.junit.Assert.assertEquals;
5 |
6 | import com.protocol7.quincy.tls.extensions.TransportParameters;
7 | import com.protocol7.quincy.utils.Bytes;
8 | import io.netty.buffer.ByteBuf;
9 |
10 | public class TestUtil {
11 |
12 | public static void assertHex(final String expectedHex, final byte[] actual) {
13 | assertEquals(expectedHex, hex(actual));
14 | }
15 |
16 | public static void assertHex(final String expectedHex, final ByteBuf actual) {
17 | final byte[] actualBytes = Bytes.peekToArray(actual);
18 | assertHex(expectedHex, actualBytes);
19 | }
20 |
21 | public static void assertHex(final byte[] expected, final byte[] actual) {
22 | assertEquals(hex(expected), hex(actual));
23 | }
24 |
25 | public static TransportParameters tps() {
26 | return TransportParameters.newBuilder()
27 | .withInitialMaxStreamDataBidiLocal(32768)
28 | .withInitialMaxData(49152)
29 | .withInitialMaxStreamsBidi(100)
30 | .withMaxIdleTimeout(30)
31 | .withMaxUDPPacketSize(1452)
32 | .withInitialMaxStreamsUni(100)
33 | .withDisableActiveMigration(true)
34 | .withInitialMaxStreamDataBidiRemote(32768)
35 | .withInitialMaxStreamDataUni(32768)
36 | .build();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/VerifyDataTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls;
2 |
3 | import static com.protocol7.quincy.tls.TestUtil.assertHex;
4 | import static com.protocol7.quincy.tls.VerifyData.create;
5 | import static org.junit.Assert.assertFalse;
6 | import static org.junit.Assert.assertTrue;
7 |
8 | import com.protocol7.quincy.utils.Hex;
9 | import com.protocol7.quincy.utils.Rnd;
10 | import org.junit.Test;
11 |
12 | public class VerifyDataTest {
13 |
14 | private final byte[] handshakeTrafficSecret =
15 | Hex.dehex("eb40b3b31cd6fc0ab7f2cdac01f4e91b9aebb729d76d63ee60f043a0daac12a6");
16 | private final byte[] finishedHash =
17 | Hex.dehex("7a795a4b9ee1753b2dcdbfe3d7718e5b7a6b85b8c5b17239543d9ea8828449c7");
18 |
19 | private final byte[] tlsVD =
20 | Hex.dehex("447cdde9e7321b90305f97755a1dcf630b9e0d03e3d5a0b9f99434eb677f319d");
21 |
22 | @Test
23 | public void testCreate() {
24 | assertHex(tlsVD, create(handshakeTrafficSecret, finishedHash));
25 | }
26 |
27 | @Test
28 | public void testVerify() {
29 | assertTrue(VerifyData.verify(tlsVD, handshakeTrafficSecret, finishedHash, false));
30 |
31 | assertFalse(VerifyData.verify(Rnd.rndBytes(32), handshakeTrafficSecret, finishedHash, false));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/aead/HandshakeAEADTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.aead;
2 |
3 | import static com.protocol7.quincy.utils.Hex.dehex;
4 | import static com.protocol7.quincy.utils.Hex.hex;
5 | import static org.junit.Assert.assertEquals;
6 |
7 | import com.protocol7.quincy.tls.HKDF;
8 | import org.junit.Test;
9 |
10 | public class HandshakeAEADTest {
11 |
12 | @Test
13 | public void testKnownTls() {
14 | final byte[] sharedSecret =
15 | dehex("df4a291baa1eb7cfa6934b29b474baad2697e29f1f920dcc77c8a0a088447624");
16 | final byte[] helloHash =
17 | dehex("da75ce1139ac80dae4044da932350cf65c97ccc9e33f1e6f7d2d4b18b736ffd5");
18 |
19 | final byte[] handshakeSecret = HKDF.calculateHandshakeSecret(sharedSecret);
20 |
21 | final AEAD aead = HandshakeAEAD.create(handshakeSecret, helloHash, true);
22 |
23 | assertEquals("25f3dd7e6173c9e01647cb9ef2f71c3d", hex(aead.getMyKey()));
24 | assertEquals("7832ada4194357104157f645758e34fb", hex(aead.getOtherKey()));
25 | assertEquals("034c31fe01a40f3734ae1420", hex(aead.getMyIV()));
26 | assertEquals("c6a739c3e2d30f92e89a9289", hex(aead.getOtherIV()));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/aead/InitialAEADTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.aead;
2 |
3 | import com.protocol7.quincy.utils.Hex;
4 | import org.junit.Assert;
5 | import org.junit.Test;
6 |
7 | public class InitialAEADTest {
8 |
9 | @Test
10 | public void testSpecExample() {
11 | final byte[] connId = Hex.dehex("8394c8f03e515708"); // from RFC
12 |
13 | final AEAD aead = InitialAEAD.create(connId, true);
14 |
15 | Assert.assertEquals("175257a31eb09dea9366d8bb79ad80ba", Hex.hex(aead.getMyKey()));
16 | Assert.assertEquals("149d0b1662ab871fbe63c49b5e655a5d", Hex.hex(aead.getOtherKey()));
17 | Assert.assertEquals("6b26114b9cba2b63a9e8dd4f", Hex.hex(aead.getMyIV()));
18 | Assert.assertEquals("bab2b12a4c76016ace47856d", Hex.hex(aead.getOtherIV()));
19 | Assert.assertEquals("9ddd12c994c0698b89374a9c077a3077", Hex.hex(aead.getMyPnKey()));
20 | Assert.assertEquals("c0c499a65a60024a18a250974ea01dfa", Hex.hex(aead.getOtherPnKey()));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/aead/OneRttAEADTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.aead;
2 |
3 | import static com.protocol7.quincy.utils.Hex.hex;
4 | import static org.junit.Assert.assertEquals;
5 |
6 | import com.protocol7.quincy.utils.Hex;
7 | import org.junit.Test;
8 |
9 | public class OneRttAEADTest {
10 |
11 | @Test
12 | public void knownTls() {
13 | final byte[] handshakeSecret =
14 | Hex.dehex("fb9fc80689b3a5d02c33243bf69a1b1b20705588a794304a6e7120155edf149a");
15 | final byte[] handshakeHash =
16 | Hex.dehex("22844b930e5e0a59a09d5ac35fc032fc91163b193874a265236e568077378d8b");
17 |
18 | final AEAD aead = OneRttAEAD.create(handshakeSecret, handshakeHash, true);
19 |
20 | assertEquals("3eb3fe82f5ac8e55458068f6f09a0e07", hex(aead.getMyKey()));
21 | assertEquals("e285f91ba60eae359d767af707710e45", hex(aead.getOtherKey()));
22 | assertEquals("fb51b454f6e2d176ae835d77", hex(aead.getMyIV()));
23 | assertEquals("1f3f0add9b67d2c388143e44", hex(aead.getOtherIV()));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/aead/RetryTokenIntegrityTagAEADTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.aead;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import com.protocol7.quincy.utils.Hex;
6 | import java.security.GeneralSecurityException;
7 | import org.junit.Test;
8 |
9 | public class RetryTokenIntegrityTagAEADTest {
10 |
11 | private final RetryTokenIntegrityTagAEAD aead = new RetryTokenIntegrityTagAEAD();
12 | private final byte[] retryPseudoPacket = new byte[18];
13 |
14 | @Test
15 | public void testCreate() throws GeneralSecurityException {
16 | final byte[] tag = aead.create(retryPseudoPacket);
17 |
18 | assertEquals("5535b8e1839966ae4c9c7cbd72cb668b", Hex.hex(tag));
19 | }
20 |
21 | @Test
22 | public void testVerify() throws GeneralSecurityException {
23 | aead.verify(retryPseudoPacket, Hex.dehex("5535b8e1839966ae4c9c7cbd72cb668b"));
24 | }
25 |
26 | @Test(expected = RuntimeException.class)
27 | public void testVerifyInvalid() throws GeneralSecurityException {
28 | aead.verify(retryPseudoPacket, Hex.dehex("1234"));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/aead/TestAEAD.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.aead;
2 |
3 | import com.protocol7.quincy.utils.Hex;
4 |
5 | public class TestAEAD {
6 |
7 | public static AEAD create() {
8 | final byte[] key = Hex.dehex("f2b91f8858998f9a4866f9738cfd2392");
9 | final byte[] iv = Hex.dehex("b060c60e9b71df30636211c7");
10 |
11 | return new AEAD(key, key, iv, iv, key, key);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/extensions/ALPNTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.extensions;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import io.netty.buffer.ByteBuf;
6 | import io.netty.buffer.Unpooled;
7 | import org.junit.Test;
8 |
9 | public class ALPNTest {
10 |
11 | @Test
12 | public void roundtrip() {
13 | final ALPN alpn = new ALPN("h3", "http/0.9");
14 |
15 | final ByteBuf bb = Unpooled.buffer();
16 |
17 | alpn.write(bb, false);
18 |
19 | final ALPN parsed = ALPN.parse(bb);
20 |
21 | assertEquals(alpn.getProtocols(), parsed.getProtocols());
22 | }
23 |
24 | @Test
25 | public void contains() {
26 | final ALPN alpn = new ALPN("h3", "http/0.9");
27 |
28 | assertTrue(alpn.contains("h3"));
29 | assertTrue(alpn.contains("http/0.9"));
30 | assertFalse(alpn.contains("http/1.1"));
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/extensions/ExtensionTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.extensions;
2 |
3 | import static com.protocol7.quincy.tls.Group.X25519;
4 | import static com.protocol7.quincy.tls.TestUtil.assertHex;
5 | import static org.junit.Assert.assertEquals;
6 |
7 | import com.protocol7.quincy.utils.Hex;
8 | import com.protocol7.quincy.utils.Rnd;
9 | import io.netty.buffer.ByteBuf;
10 | import io.netty.buffer.Unpooled;
11 | import java.util.Iterator;
12 | import java.util.List;
13 | import org.junit.Test;
14 |
15 | public class ExtensionTest {
16 |
17 | @Test
18 | public void roundtripClientToServer() {
19 | final List extensions =
20 | List.of(
21 | new ALPN("http/0.9"),
22 | TransportParameters.newBuilder().withMaxUDPPacketSize(123).build(),
23 | KeyShare.of(X25519, Rnd.rndBytes(16)),
24 | SupportedVersions.TLS13,
25 | new SupportedGroups(X25519),
26 | SignatureAlgorithms.defaults(),
27 | PskKeyExchangeModes.defaults(),
28 | new ServerName("foo"));
29 |
30 | final ByteBuf bb = Unpooled.buffer();
31 | Extension.writeAll(extensions, bb, true);
32 |
33 | final List parsed = Extension.parseAll(bb, false);
34 |
35 | assertEquals(extensions, parsed);
36 | }
37 |
38 | @Test
39 | public void parseKnown() {
40 | final byte[] b =
41 | Hex.dehex(
42 | "002b0002030400330024001d0020ae3492c510ba781d6e30ff69b66d47c710f7ef060f846e28bda2f995b4fb4645");
43 |
44 | final List ext = Extension.parseAll(Unpooled.wrappedBuffer(b), true);
45 |
46 | final Iterator iter = ext.iterator();
47 |
48 | final SupportedVersions supportedVersions = (SupportedVersions) iter.next();
49 | assertEquals(List.of(SupportedVersion.TLS13), supportedVersions.getVersions());
50 |
51 | final KeyShare keyShare = (KeyShare) iter.next();
52 | assertEquals(1, keyShare.getKeys().size());
53 | assertHex(
54 | "ae3492c510ba781d6e30ff69b66d47c710f7ef060f846e28bda2f995b4fb4645",
55 | keyShare.getKey(X25519).get());
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/extensions/KeyShareTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.extensions;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import com.protocol7.quincy.tls.Group;
6 | import com.protocol7.quincy.utils.Rnd;
7 | import io.netty.buffer.ByteBuf;
8 | import io.netty.buffer.Unpooled;
9 | import org.junit.Test;
10 |
11 | public class KeyShareTest {
12 |
13 | private final byte[] key = Rnd.rndBytes(32);
14 | private final byte[] key2 = Rnd.rndBytes(32);
15 | private final KeyShare ext = KeyShare.of(Group.X25519, key);
16 | private final KeyShare extMultiple = KeyShare.of(Group.X25519, key, Group.X448, key2);
17 |
18 | @Test
19 | public void getType() {
20 | assertEquals(ExtensionType.KEY_SHARE, ext.getType());
21 | }
22 |
23 | @Test
24 | public void roundtripSingleClientToServer() {
25 | assertRoundtripSingle(true);
26 | }
27 |
28 | @Test
29 | public void roundtripSingleServerToClient() {
30 | assertRoundtripSingle(false);
31 | }
32 |
33 | private void assertRoundtripSingle(final boolean clientToServer) {
34 | final ByteBuf bb = Unpooled.buffer();
35 |
36 | ext.write(bb, clientToServer);
37 |
38 | final KeyShare parsed = KeyShare.parse(bb, !clientToServer);
39 |
40 | assertEquals(ext, parsed);
41 | }
42 |
43 | @Test
44 | public void roundtripMultiple() {
45 | final ByteBuf bb = Unpooled.buffer();
46 |
47 | extMultiple.write(bb, true);
48 |
49 | final KeyShare parsed = KeyShare.parse(bb, false);
50 |
51 | assertEquals(extMultiple, parsed);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/extensions/PskKeyExchangeModesTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.extensions;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import io.netty.buffer.ByteBuf;
6 | import io.netty.buffer.Unpooled;
7 | import org.junit.Test;
8 |
9 | public class PskKeyExchangeModesTest {
10 |
11 | @Test
12 | public void roundtrip() {
13 | final PskKeyExchangeModes ext = new PskKeyExchangeModes(0x01);
14 |
15 | final ByteBuf bb = Unpooled.buffer();
16 | ext.write(bb, true);
17 |
18 | final PskKeyExchangeModes parsed = PskKeyExchangeModes.parse(bb);
19 |
20 | assertEquals(ext, parsed);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/extensions/ServerNameTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.extensions;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import io.netty.buffer.ByteBuf;
6 | import io.netty.buffer.Unpooled;
7 | import org.junit.Test;
8 |
9 | public class ServerNameTest {
10 |
11 | @Test
12 | public void roundtrip() {
13 | final ServerName serverName = new ServerName("hello.example.org");
14 |
15 | final ByteBuf bb = Unpooled.buffer();
16 |
17 | serverName.write(bb, false);
18 |
19 | final ServerName parsed = ServerName.parse(bb);
20 |
21 | assertEquals(serverName, parsed);
22 | assertEquals("hello.example.org", parsed.getServerName());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/extensions/SignatureAlgorithmsTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.extensions;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import io.netty.buffer.ByteBuf;
6 | import io.netty.buffer.Unpooled;
7 | import org.junit.Test;
8 |
9 | public class SignatureAlgorithmsTest {
10 |
11 | @Test
12 | public void roundtrip() {
13 | final SignatureAlgorithms ext = new SignatureAlgorithms(0x0804, 0x0403);
14 |
15 | final ByteBuf bb = Unpooled.buffer();
16 | ext.write(bb, true);
17 |
18 | final SignatureAlgorithms parsed = SignatureAlgorithms.parse(bb);
19 |
20 | assertEquals(ext, parsed);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/extensions/SupportedGroupsTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.extensions;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import com.protocol7.quincy.tls.Group;
6 | import io.netty.buffer.ByteBuf;
7 | import io.netty.buffer.Unpooled;
8 | import org.junit.Test;
9 |
10 | public class SupportedGroupsTest {
11 |
12 | @Test
13 | public void roundtrip() {
14 | final SupportedGroups supportedGroups = new SupportedGroups(Group.X25519, Group.X448);
15 |
16 | final ByteBuf bb = Unpooled.buffer();
17 |
18 | supportedGroups.write(bb, true);
19 |
20 | final SupportedGroups parsed = SupportedGroups.parse(bb);
21 |
22 | assertEquals(supportedGroups.getGroups(), parsed.getGroups());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/extensions/SupportedVersionsTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.extensions;
2 |
3 | import static com.protocol7.quincy.tls.extensions.SupportedVersions.TLS13;
4 | import static org.junit.Assert.assertEquals;
5 |
6 | import io.netty.buffer.ByteBuf;
7 | import io.netty.buffer.Unpooled;
8 | import java.util.List;
9 | import org.junit.Test;
10 |
11 | public class SupportedVersionsTest {
12 |
13 | @Test
14 | public void roundtripClientToServer() {
15 | assertRoundtrip(true);
16 | }
17 |
18 | @Test
19 | public void roundtripServerToClient() {
20 | assertRoundtrip(false);
21 | }
22 |
23 | private void assertRoundtrip(final boolean clientToServer) {
24 | final ByteBuf bb = Unpooled.buffer();
25 |
26 | TLS13.write(bb, clientToServer);
27 |
28 | final SupportedVersions parsed = SupportedVersions.parse(bb, !clientToServer);
29 | assertEquals(List.of(SupportedVersion.TLS13), parsed.getVersions());
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/messages/EncryptedExtensionsTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.messages;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import com.protocol7.quincy.tls.extensions.SupportedVersions;
6 | import io.netty.buffer.ByteBuf;
7 | import io.netty.buffer.Unpooled;
8 | import org.junit.Test;
9 |
10 | public class EncryptedExtensionsTest {
11 |
12 | @Test
13 | public void roundtrip() {
14 | final EncryptedExtensions ee = new EncryptedExtensions(SupportedVersions.TLS13);
15 |
16 | final ByteBuf bb = Unpooled.buffer();
17 | ee.write(bb);
18 |
19 | final EncryptedExtensions parsedEE = EncryptedExtensions.parse(bb, true);
20 |
21 | assertEquals(ee.getExtensions(), parsedEE.getExtensions());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/messages/FinishedTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.messages;
2 |
3 | import static com.protocol7.quincy.tls.TestUtil.assertHex;
4 | import static org.junit.Assert.assertEquals;
5 |
6 | import com.protocol7.quincy.utils.Hex;
7 | import io.netty.buffer.ByteBuf;
8 | import io.netty.buffer.Unpooled;
9 | import org.junit.Test;
10 |
11 | public class FinishedTest {
12 |
13 | @Test
14 | public void parseKnownServerHandshakeFinished() {
15 | final ByteBuf bb =
16 | Unpooled.wrappedBuffer(
17 | Hex.dehex("140000200c3dcdc53b56e8d4a127d104737ffc3093d005c7134837958cf32c33ee57ea96"));
18 |
19 | final Finished fin = Finished.parse(bb);
20 |
21 | assertHex(
22 | "0c3dcdc53b56e8d4a127d104737ffc3093d005c7134837958cf32c33ee57ea96",
23 | fin.getVerificationData());
24 | }
25 |
26 | @Test
27 | public void createClientFinished() {
28 | final Finished fin =
29 | Finished.createClientFinished(
30 | Hex.dehex("ff0e5b965291c608c1e8cd267eefc0afcc5e98a2786373f0db47b04786d72aea"),
31 | Hex.dehex("22844b930e5e0a59a09d5ac35fc032fc91163b193874a265236e568077378d8b"));
32 |
33 | assertEquals(
34 | "976017a77ae47f1658e28f7085fe37d149d1e9c91f56e1aebbe0c6bb054bd92b",
35 | Hex.hex(fin.getVerificationData()));
36 | }
37 |
38 | @Test
39 | public void roundtrip() {
40 | final byte[] certVerificationData =
41 | Hex.dehex("4c6e3380e3b4034484753f79b0946ffc8a201fb4d3c1e8031a815ede45d9dbed");
42 |
43 | final Finished fin = new Finished(certVerificationData);
44 |
45 | final ByteBuf bb = Unpooled.buffer();
46 | fin.write(bb);
47 |
48 | final Finished parsedSHE = Finished.parse(bb);
49 |
50 | assertHex(fin.getVerificationData(), parsedSHE.getVerificationData());
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tls/src/test/java/com/protocol7/quincy/tls/messages/ServerHelloTest.java:
--------------------------------------------------------------------------------
1 | package com.protocol7.quincy.tls.messages;
2 |
3 | import static com.protocol7.quincy.tls.TestUtil.assertHex;
4 | import static org.junit.Assert.assertEquals;
5 |
6 | import com.protocol7.quincy.tls.CipherSuite;
7 | import com.protocol7.quincy.tls.extensions.Extension;
8 | import com.protocol7.quincy.tls.extensions.SupportedVersions;
9 | import com.protocol7.quincy.utils.Hex;
10 | import com.protocol7.quincy.utils.Rnd;
11 | import io.netty.buffer.ByteBuf;
12 | import io.netty.buffer.Unpooled;
13 | import java.util.List;
14 | import org.junit.Test;
15 |
16 | public class ServerHelloTest {
17 |
18 | @Test
19 | public void parseKnown() {
20 | final byte[] sh =
21 | Hex.dehex(
22 | "0200005603037cdef17464db2589d38fd069fd8e593fd7deda108bb84e12720212c47b74f96100130100002e002b0002030400330024001d0020bc3dd7c4c45142be87d00e1b3dd1a02d43b0be4ab41b71e1e6dfbea39c385417");
23 | final ByteBuf bb = Unpooled.wrappedBuffer(sh);
24 |
25 | final ServerHello hello = ServerHello.parse(bb, true);
26 |
27 | assertHex(
28 | "7cdef17464db2589d38fd069fd8e593fd7deda108bb84e12720212c47b74f961",
29 | hello.getServerRandom());
30 | assertHex("", hello.getSessionId());
31 | assertEquals(CipherSuite.TLS_AES_128_GCM_SHA256, hello.getCipherSuites());
32 |
33 | assertEquals(2, hello.getExtensions().size());
34 | }
35 |
36 | @Test
37 | public void roundtrip() {
38 | final List ext = List.of(SupportedVersions.TLS13);
39 | final ServerHello sh =
40 | new ServerHello(Rnd.rndBytes(32), new byte[0], CipherSuite.TLS_AES_128_GCM_SHA256, ext);
41 |
42 | final ByteBuf bb = Unpooled.buffer();
43 |
44 | sh.write(bb);
45 |
46 | final ServerHello parsed = ServerHello.parse(bb, true);
47 |
48 | assertHex(sh.getServerRandom(), parsed.getServerRandom());
49 | assertHex(sh.getSessionId(), parsed.getSessionId());
50 | assertEquals(sh.getCipherSuites(), parsed.getCipherSuites());
51 |
52 | assertEquals(ext, parsed.getExtensions());
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/tls/src/test/resources/gen_cert.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | openssl genrsa -out server.pem 2048
4 | openssl pkcs8 -topk8 -inform PEM -outform DER -in server.pem -nocrypt > server.der
5 | openssl req -new -nodes -key server.pem -out csr.pem -subj /CN=quincy
6 | openssl req -x509 -nodes -sha256 -days 36500 -key server.pem -in csr.pem -out server.crt
7 |
8 | #rm server.pem
9 | rm csr.pem
10 |
--------------------------------------------------------------------------------
/tls/src/test/resources/google2.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIESjCCAzKgAwIBAgINAeO0mqGNiqmBJWlQuDANBgkqhkiG9w0BAQsFADBMMSAw
3 | HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFs
4 | U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNzA2MTUwMDAwNDJaFw0yMTEy
5 | MTUwMDAwNDJaMEIxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3Qg
6 | U2VydmljZXMxEzARBgNVBAMTCkdUUyBDQSAxTzEwggEiMA0GCSqGSIb3DQEBAQUA
7 | A4IBDwAwggEKAoIBAQDQGM9F1IvN05zkQO9+tN1pIRvJzzyOTHW5DzEZhD2ePCnv
8 | UA0Qk28FgICfKqC9EksC4T2fWBYk/jCfC3R3VZMdS/dN4ZKCEPZRrAzDsiKUDzRr
9 | mBBJ5wudgzndIMYcLe/RGGFl5yODIKgjEv/SJH/UL+dEaltN11BmsK+eQmMF++Ac
10 | xGNhr59qM/9il71I2dN8FGfcddwuaej4bXhp0LcQBbjxMcI7JP0aM3T4I+DsaxmK
11 | FsbjzaTNC9uzpFlgOIg7rR25xoynUxv8vNmkq7zdPGHXkxWY7oG9j+JkRyBABk7X
12 | rJfoucBZEqFJJSPk7XA0LKW0Y3z5oz2D0c1tJKwHAgMBAAGjggEzMIIBLzAOBgNV
13 | HQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1Ud
14 | EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFJjR+G4Q68+b7GCfGJAboOt9Cf0rMB8G
15 | A1UdIwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYuMDUGCCsGAQUFBwEBBCkwJzAl
16 | BggrBgEFBQcwAYYZaHR0cDovL29jc3AucGtpLmdvb2cvZ3NyMjAyBgNVHR8EKzAp
17 | MCegJaAjhiFodHRwOi8vY3JsLnBraS5nb29nL2dzcjIvZ3NyMi5jcmwwPwYDVR0g
18 | BDgwNjA0BgZngQwBAgIwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly9wa2kuZ29vZy9y
19 | ZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAGoA+Nnn78y6pRjd9XlQWNa7H
20 | TgiZ/r3RNGkmUmYHPQq6Scti9PEajvwRT2iWTHQr02fesqOqBY2ETUwgZQ+lltoN
21 | FvhsO9tvBCOIazpswWC9aJ9xju4tWDQH8NVU6YZZ/XteDSGU9YzJqPjY8q3MDxrz
22 | mqepBCf5o8mw/wJ4a2G6xzUr6Fb6T8McDO22PLRL6u3M4Tzs3A2M1j6bykJYi8wW
23 | IRdAvKLWZu/axBVbzYmqmwkm5zLSDW5nIAJbELCQCZwMH56t2Dvqofxs6BBcCFIZ
24 | USpxu6x6td0V7SvJCCosirSmIatj/9dSSVDQibet8q/7UK4v4ZUN80atnZz1yg==
25 | -----END CERTIFICATE-----
26 |
--------------------------------------------------------------------------------
/tls/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %d{HH:mm:ss.SSS} - %X{actor} %X{connectionid} %X{packetnumber} - %msg%n
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tls/src/test/resources/quic-go.der:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/protocol7/quincy/74e49d8a84a77eada23283ed823b8bc7e7ce321e/tls/src/test/resources/quic-go.der
--------------------------------------------------------------------------------
/tls/src/test/resources/server.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICoDCCAYgCCQDRPgfmXBqhmzANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZx
3 | dWluY3kwIBcNMjAxMjMwMTczNzQ2WhgPMjEyMDEyMDYxNzM3NDZaMBExDzANBgNV
4 | BAMMBnF1aW5jeTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOK3KAAP
5 | CCbzia01fwKmuQcU/GY0hTHs3Kz1muDNpv8ejjyH3ztqxXfoDTMAHSxqf2sn4ZPm
6 | j0TdOXGTq6ypMqNsjhl69cX7jkuTITLSueHiURG0xJP+YOzXVw3lYGbDixXwEPJv
7 | 4GrSntHdzURaGr7WRyAvlhW2/O1PCoVDC2d2vUzzOHdt2vBlxYsekVmJ6hVk/J2W
8 | 71vf6GovkTEkiNjsKmdU8Xre9nHDd5+qBkrswiD5vjdgp3yy9UgsYXaSI8RwoIFe
9 | jnjo+rXWz0a4oPTX1+pvlfBrtu5cUvvv3cOJ3gWHI4rdDUb1yR1AVZ1i+muxmeKW
10 | zhb3mZUPujNguqkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAVRCMmOkTq1Vg5DkV
11 | 3tjJ2RnCJcWyzPqW/+maL6HmXfGJzcy6F+Tg/szi3keH6fLb6y/z3DEbwJq+v/3x
12 | vu0V8J7F3A8bx3CKPuZ/cqR+rq2hxogon6Cc8+h+VXgkAKzbzQHz/JGcJEHt+ZoQ
13 | PnBLKQXWuiYbalfNJNO7+q+MGxuqwzsRghRDTNxuF2IUwb7hP3G41gclOn3QV/2Q
14 | F6IA9Vi4PsJ9FP/JrL/G1AUZCyYZgjJiVQ5KlKRbvRq8aFIe89iYwobqAKOPyGon
15 | RJwwNuPlP9xiQwUIfYANRWqtvSdxF0VjYjH6SbD5ZYno4jJwhOb0Nrm6UBXBS6fw
16 | b7Q5cA==
17 | -----END CERTIFICATE-----
18 |
--------------------------------------------------------------------------------
/tls/src/test/resources/server.der:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/protocol7/quincy/74e49d8a84a77eada23283ed823b8bc7e7ce321e/tls/src/test/resources/server.der
--------------------------------------------------------------------------------
/tls/src/test/resources/server.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEowIBAAKCAQEA4rcoAA8IJvOJrTV/Aqa5BxT8ZjSFMezcrPWa4M2m/x6OPIff
3 | O2rFd+gNMwAdLGp/ayfhk+aPRN05cZOrrKkyo2yOGXr1xfuOS5MhMtK54eJREbTE
4 | k/5g7NdXDeVgZsOLFfAQ8m/gatKe0d3NRFoavtZHIC+WFbb87U8KhUMLZ3a9TPM4
5 | d23a8GXFix6RWYnqFWT8nZbvW9/oai+RMSSI2OwqZ1Txet72ccN3n6oGSuzCIPm+
6 | N2CnfLL1SCxhdpIjxHCggV6OeOj6tdbPRrig9NfX6m+V8Gu27lxS++/dw4neBYcj
7 | it0NRvXJHUBVnWL6a7GZ4pbOFveZlQ+6M2C6qQIDAQABAoIBAHxPz4gQtfidqw0l
8 | eyoZ/vSKJkdoXuxcIzhXR4AiY4IZ4AYCvn2W8wXnYG1pj5WOI6W+7Wqqjj5FSz0i
9 | qox3DgQb/uKr0F2prIF2AEsczr2z2Z3qz6sSXVUgUmSVNEHE0NvLkY9NlvEb4efT
10 | Rb1H5shjOAbG8PWhK5h3sZ4WgAdPRRKctrNOCIbd1hFqQsGeaE+kWIUF2nljZ/Tf
11 | GmzWyN56CjluBmw77AG4OoEcob5nbhLvhMJmZ1LWzP9NCn8oNgXC4JDIN9jsRWNi
12 | EV1fBzPweqqkA24Et2CcEyvRoIOgNv1Gaccy/dCNiEI3KWU9i7YcTRJYmVuBxBzs
13 | cgYG8XECgYEA8sDLepEx2d6YVyT7B76ShfubRZEM96XEYsQwy5Sqix099oa0mEot
14 | 19tQzq/Q/prl3Ln+1ku5vQbT1VfZIu/0q4MM9C0BTmUQPXE0EZswO7Kn6f59eFMd
15 | TYkXzP8A/tXhDo3MAluJEvySupRlkiaFhZPPCb5i8PlAE6pltjKz5y0CgYEA7xZR
16 | sVF7B/sCojBAMF06lbgHMZtxdSYIeityu921ki4942FcpnvE27mA15bh80UhZqgz
17 | 9snwsZaIu8ZkqBvdTJ05p1S41OV2K3ioqafaoW9bAPTA1rHxgyOo5tioxL72tOnE
18 | yEB/mtUjWwT1UxEHPSaImaeJD7stXQxy+7p0Tu0CgYBkPynIW91yU3Ilyqe/8vsf
19 | SWA9wkDQpCwNfWeJKsOi31iPTeGWYku8MF2WfRSZj+4M0OJkLLFvVjp0h+qretxX
20 | V68pxswbS7EBLpaKDsREYurkvquh3PDk7BBgH46RrlFaaUQuVQ6uQI93bYDkcfQB
21 | zaBaLb0+NjA37s5CB34zoQKBgQDP6Bq2FWLld7O8kjTfWdL+Kv+mdcPd2Wr5whqN
22 | n6irK6cJubq1019GqzONRlnKEE2RVaeKbeTuqTbSAx24yjJQ01A1YIkyKS/vcYdJ
23 | sPt/8rOySyP+DtMz9KiFxdZM8Lrca4SBlwTgAYQzPEaRK3eeB4o2A+g+U8iI57B9
24 | kpBdqQKBgECqEXfKLgYgEI9qZZm6AfzFi/4+Xvi9L688OnhsPVAFnuFFlL4UYc62
25 | J6dU6GUjt/QRUk4uioIfCQSTQ5X6pdFK8wI5Q5eJgmQxcBIchA56EWO+fRHEzJDc
26 | uVZSrauEvVYi1KQMOsMmjpRa9i7RQvOKFsac7Pen9m30qWYY9wbM
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------