├── .github └── workflows │ └── maven.yml ├── .gitignore ├── README.md ├── checkstyle.xml ├── common ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── protocol7 │ │ └── quincy │ │ ├── Varint.java │ │ ├── Writeable.java │ │ └── utils │ │ ├── Bits.java │ │ ├── Bytes.java │ │ ├── Debug.java │ │ ├── Hex.java │ │ ├── Pair.java │ │ ├── Rnd.java │ │ └── Ticker.java │ └── test │ └── java │ └── com │ └── protocol7 │ └── quincy │ ├── TestUtil.java │ ├── VarintTest.java │ └── utils │ └── BitsTest.java ├── integration-tests ├── pom.xml └── src │ └── test │ ├── java │ └── com │ │ └── protocol7 │ │ └── quincy │ │ └── it │ │ ├── KeyUtil.java │ │ ├── QuicGoTest.java │ │ ├── QuicheClientTest.java │ │ ├── QuicheContainer.java │ │ └── QuicheTest.java │ └── resources │ ├── QuicheDockerfile │ ├── logback-test.xml │ ├── server.crt │ ├── server.der │ └── server.pem ├── pom.xml ├── quic-go-testcontainer ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── protocol7 │ │ └── testcontainers │ │ └── quicgo │ │ └── QuicGoContainer.java │ └── resources │ ├── QuicGoDockerfile │ └── main.go ├── quic ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── protocol7 │ │ └── quincy │ │ ├── Configuration.java │ │ ├── FrameSender.java │ │ ├── Futures.java │ │ ├── InboundHandler.java │ │ ├── OutboundHandler.java │ │ ├── Pipeline.java │ │ ├── PipelineContext.java │ │ ├── addressvalidation │ │ ├── InsecureQuicTokenHandler.java │ │ ├── QuicTokenHandler.java │ │ └── SecureQuicTokenHandler.java │ │ ├── connection │ │ ├── ClientStateMachine.java │ │ ├── Connection.java │ │ ├── ConnectionBootstrap.java │ │ ├── Connections.java │ │ ├── DefaultConnection.java │ │ ├── NettyPacketSender.java │ │ ├── PacketRouter.java │ │ ├── PacketSender.java │ │ ├── ServerStateMachine.java │ │ ├── State.java │ │ └── StateMachine.java │ │ ├── flowcontrol │ │ ├── DefaultFlowControlHandler.java │ │ ├── FlowControlCounter.java │ │ ├── FlowControlHandler.java │ │ └── TryConsumeResult.java │ │ ├── logging │ │ └── LoggingHandler.java │ │ ├── netty │ │ ├── QuicBuilder.java │ │ ├── QuicHandler.java │ │ └── QuicInitializer.java │ │ ├── protocol │ │ ├── ConnectionId.java │ │ ├── PacketNumber.java │ │ ├── Payload.java │ │ ├── StreamId.java │ │ ├── TransportError.java │ │ ├── Version.java │ │ ├── frames │ │ │ ├── AckFrame.java │ │ │ ├── AckRange.java │ │ │ ├── ApplicationCloseFrame.java │ │ │ ├── ConnectionCloseFrame.java │ │ │ ├── CryptoFrame.java │ │ │ ├── DataBlockedFrame.java │ │ │ ├── Frame.java │ │ │ ├── FrameType.java │ │ │ ├── HandshakeDoneFrame.java │ │ │ ├── MaxDataFrame.java │ │ │ ├── MaxStreamDataFrame.java │ │ │ ├── MaxStreamsFrame.java │ │ │ ├── NewToken.java │ │ │ ├── PaddingFrame.java │ │ │ ├── PingFrame.java │ │ │ ├── ResetStreamFrame.java │ │ │ ├── RetireConnectionIdFrame.java │ │ │ ├── StreamDataBlockedFrame.java │ │ │ ├── StreamFrame.java │ │ │ └── StreamsBlockedFrame.java │ │ └── packets │ │ │ ├── FullPacket.java │ │ │ ├── HalfParsedPacket.java │ │ │ ├── HandshakePacket.java │ │ │ ├── InitialPacket.java │ │ │ ├── LongHeaderPacket.java │ │ │ ├── Packet.java │ │ │ ├── PacketType.java │ │ │ ├── RetryPacket.java │ │ │ ├── ShortPacket.java │ │ │ └── VersionNegotiationPacket.java │ │ ├── reliability │ │ ├── AckDelay.java │ │ ├── AckQueue.java │ │ ├── AckUtil.java │ │ ├── PacketBuffer.java │ │ └── PacketBufferManager.java │ │ ├── streams │ │ ├── DefaultStream.java │ │ ├── DefaultStreamManager.java │ │ ├── ReceiveStateMachine.java │ │ ├── ReceivedDataBuffer.java │ │ ├── SendStateMachine.java │ │ ├── Stream.java │ │ ├── StreamHandler.java │ │ ├── StreamManager.java │ │ ├── StreamType.java │ │ └── Streams.java │ │ ├── termination │ │ └── TerminationManager.java │ │ └── tls │ │ ├── ClientTlsManager.java │ │ ├── ServerTlsManager.java │ │ └── TlsManager.java │ └── test │ ├── java │ └── com │ │ └── protocol7 │ │ └── quincy │ │ ├── ClientRunner.java │ │ ├── ClientServerConnectionTest.java │ │ ├── MockTimer.java │ │ ├── PipelineTest.java │ │ ├── ServerRunner.java │ │ ├── TestUtil.java │ │ ├── addressvalidation │ │ ├── InsecureQuicTokenHandlerTest.java │ │ └── SecureQuicTokenHandlerTest.java │ │ ├── client │ │ └── ClientTest.java │ │ ├── connection │ │ └── PacketRouterTest.java │ │ ├── flowcontrol │ │ ├── DefaultFlowControlHandlerTest.java │ │ ├── FlowControlCounterTest.java │ │ └── MockFlowControlHandler.java │ │ ├── logging │ │ └── LoggingHandlerTest.java │ │ ├── protocol │ │ ├── ConnectionIdTest.java │ │ ├── PacketNumberTest.java │ │ ├── PayloadTest.java │ │ ├── StreamIdTest.java │ │ ├── VersionTest.java │ │ ├── frames │ │ │ ├── AckFrameTest.java │ │ │ ├── AckRangeTest.java │ │ │ ├── ApplicationCloseFrameTest.java │ │ │ ├── ConnectionCloseFrameTest.java │ │ │ ├── CryptoFrameTest.java │ │ │ ├── DataBlockedFrameTest.java │ │ │ ├── FrameTest.java │ │ │ ├── MaxDataFrameTest.java │ │ │ ├── MaxStreamDataFrameTest.java │ │ │ ├── MaxStreamsFrameTest.java │ │ │ ├── NewTokenTest.java │ │ │ ├── PaddingFrameTest.java │ │ │ ├── PingFrameTest.java │ │ │ ├── ResetStreamFrameTest.java │ │ │ ├── RetireConnectionIdFrameTest.java │ │ │ ├── StreamDataBlockedFrameTest.java │ │ │ ├── StreamFrameTest.java │ │ │ └── StreamsBlockedFrameTest.java │ │ └── packets │ │ │ ├── HandshakePacketTest.java │ │ │ ├── InitialPacketTest.java │ │ │ ├── PacketTest.java │ │ │ ├── RetryPacketTest.java │ │ │ ├── ShortPacketTest.java │ │ │ └── VersionNegotiationPacketTest.java │ │ ├── reliability │ │ ├── AckDelayTest.java │ │ ├── AckQueueTest.java │ │ ├── PacketBufferManagerTest.java │ │ └── PacketBufferTest.java │ │ ├── server │ │ └── ServerTest.java │ │ ├── streams │ │ ├── DefaultStreamManagerTest.java │ │ ├── ReceivedDataBufferTest.java │ │ ├── StreamTest.java │ │ └── StreamsTest.java │ │ ├── termination │ │ └── TerminationManagerTest.java │ │ ├── tls │ │ ├── ClientTlsManagerTest.java │ │ └── ServerTlsManagerTest.java │ │ └── utils │ │ └── BitsTest.java │ └── resources │ ├── logback-test.xml │ ├── quic-go.der │ ├── server.crt │ └── server.der ├── quicly-testcontainer ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── protocol7 │ │ │ └── testcontainers │ │ │ └── quicly │ │ │ ├── PacketParser.java │ │ │ ├── QuiclyClientContainer.java │ │ │ ├── QuiclyContainer.java │ │ │ ├── QuiclyPacket.java │ │ │ └── QuiclyServerContainer.java │ └── resources │ │ ├── QuiclyClientDockerfile │ │ └── QuiclyDockerfile │ └── test │ └── java │ └── com │ └── protocol7 │ └── testcontainers │ └── quicly │ └── PacketParserTest.java └── tls ├── pom.xml └── src ├── main └── java │ └── com │ └── protocol7 │ └── quincy │ └── tls │ ├── CertificateValidator.java │ ├── CertificateVerify.java │ ├── CipherSuite.java │ ├── ClientTlsSession.java │ ├── ConstantTimeEquals.java │ ├── DefaultCertificateValidator.java │ ├── EncryptionLevel.java │ ├── Group.java │ ├── HKDF.java │ ├── Hash.java │ ├── KeyExchange.java │ ├── NoopCertificateValidator.java │ ├── ReceivedDataBuffer.java │ ├── ServerTlsSession.java │ ├── VerifyData.java │ ├── aead │ ├── AEAD.java │ ├── AEADProvider.java │ ├── AEADs.java │ ├── HandshakeAEAD.java │ ├── InitialAEAD.java │ ├── Labels.java │ ├── OneRttAEAD.java │ └── RetryTokenIntegrityTagAEAD.java │ ├── extensions │ ├── ALPN.java │ ├── Extension.java │ ├── ExtensionType.java │ ├── KeyShare.java │ ├── PskKeyExchangeModes.java │ ├── RawExtension.java │ ├── ServerName.java │ ├── SignatureAlgorithms.java │ ├── SupportedGroups.java │ ├── SupportedVersion.java │ ├── SupportedVersions.java │ ├── TransportParameterType.java │ └── TransportParameters.java │ └── messages │ ├── ClientHello.java │ ├── EncryptedExtensions.java │ ├── Finished.java │ ├── Message.java │ ├── MessageType.java │ ├── ServerCertificate.java │ ├── ServerCertificateVerify.java │ └── ServerHello.java └── test ├── java └── com │ └── protocol7 │ └── quincy │ └── tls │ ├── CertificateVerifyTest.java │ ├── CipherSuiteTest.java │ ├── ClientTlsSessionTest.java │ ├── DefaultCertificateValidatorTest.java │ ├── GroupTest.java │ ├── HashTest.java │ ├── KeyExchangeTest.java │ ├── KeyUtil.java │ ├── TestUtil.java │ ├── TlsSessionTest.java │ ├── VerifyDataTest.java │ ├── aead │ ├── AEADTest.java │ ├── AEADsTest.java │ ├── HandshakeAEADTest.java │ ├── InitialAEADTest.java │ ├── OneRttAEADTest.java │ ├── RetryTokenIntegrityTagAEADTest.java │ └── TestAEAD.java │ ├── extensions │ ├── ALPNTest.java │ ├── ExtensionTest.java │ ├── KeyShareTest.java │ ├── PskKeyExchangeModesTest.java │ ├── ServerNameTest.java │ ├── SignatureAlgorithmsTest.java │ ├── SupportedGroupsTest.java │ ├── SupportedVersionsTest.java │ └── TransportParametersTest.java │ └── messages │ ├── ClientHelloTest.java │ ├── EncryptedExtensionsTest.java │ ├── FinishedTest.java │ ├── ServerCertificateTest.java │ ├── ServerCertificateVerifyTest.java │ └── ServerHelloTest.java └── resources ├── gen_cert.sh ├── google1.crt ├── google2.crt ├── logback-test.xml ├── quic-go.der ├── server.crt ├── server.der └── server.pem /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: openjdk11 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 11 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 11 23 | - name: Build with Maven 24 | run: mvn -B package --file pom.xml 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea/ 3 | target/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quincy 2 | 3 | ![openjdk11](https://github.com/protocol7/quincy/workflows/openjdk11/badge.svg) 4 | 5 | Quincy is an implementation of [QUIC](https://quicwg.org/) in Java, based 6 | on the [Netty framework](https://netty.io/). Focusing on having fun and 7 | learning. Development is still very much in an early stage, exploring what 8 | an implementation could look like. That is, for now, priorities are 9 | Complete > Correct > Performant. 10 | 11 | We're more than happy to accept contributions, feel free to take a stab at 12 | anything missing, incomplete or wrong. 13 | 14 | - [ ] TLS (custom implementation) 15 | - [X] Messages/extensions 16 | - [X] Handshake 17 | - [ ] Certification validation 18 | - [ ] Key phase 19 | - [X] ALPN 20 | - [X] Protocol/packets/frames 21 | - [X] Connections 22 | - [ ] Packet coalescing 23 | - [ ] PMTU 24 | - [X] Version negotiation 25 | - [X] Streams 26 | - [ ] Reliability 27 | - [X] Acking 28 | - [X] Resends 29 | - [ ] Flow control 30 | - [X] Max data 31 | - [X] Max streams 32 | - [ ] Congestion control 33 | - [ ] Address validation 34 | - [X] Retry 35 | - [ ] Path validation 36 | - [ ] Connection migration 37 | - [ ] Connection termination 38 | - [X] Idle timeout 39 | - [X] Immediate close 40 | - [ ] Stateless reset 41 | - [X] Integration tests (quiche) 42 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | quincy-common 6 | jar 7 | 8 | 9 | com.protocol7 10 | quincy-parent 11 | 0.0.1-SNAPSHOT 12 | 13 | 14 | 15 | 16 | io.netty 17 | netty-buffer 18 | 19 | 20 | com.google.guava 21 | guava 22 | 23 | 24 | org.slf4j 25 | slf4j-api 26 | 27 | 28 | 29 | 30 | junit 31 | junit 32 | test 33 | 34 | 35 | mockito-core 36 | org.mockito 37 | test 38 | 39 | 40 | ch.qos.logback 41 | logback-classic 42 | test 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /common/src/main/java/com/protocol7/quincy/Writeable.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.quincy; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | public interface Writeable { 6 | 7 | void write(ByteBuf bb); 8 | } 9 | -------------------------------------------------------------------------------- /common/src/main/java/com/protocol7/quincy/utils/Bits.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.quincy.utils; 2 | 3 | public class Bits { 4 | 5 | public static long set(final long v, final int bit) { 6 | return v | 1 << bit; 7 | } 8 | 9 | public static int set(final int v, final int bit) { 10 | return v | 1 << bit; 11 | } 12 | 13 | public static long unset(final long v, final int bit) { 14 | return v & ~(1 << bit); 15 | } 16 | 17 | public static int unset(final int v, final int bit) { 18 | return v & ~(1 << bit); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /common/src/main/java/com/protocol7/quincy/utils/Bytes.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.quincy.utils; 2 | 3 | import com.protocol7.quincy.Writeable; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.Unpooled; 6 | import java.util.Arrays; 7 | import java.util.Collection; 8 | 9 | public class Bytes { 10 | 11 | public static byte[] drainToArray(final ByteBuf bb) { 12 | try { 13 | final byte[] b = new byte[bb.readableBytes()]; 14 | bb.readBytes(b); 15 | return b; 16 | } finally { 17 | bb.release(); 18 | } 19 | } 20 | 21 | public static byte[] peekToArray(final ByteBuf bb) { 22 | final byte[] b = new byte[bb.readableBytes()]; 23 | bb.markReaderIndex(); 24 | bb.readBytes(b); 25 | bb.resetReaderIndex(); 26 | return b; 27 | } 28 | 29 | public static byte[] concat(final byte[]... bs) { 30 | if (bs.length == 0) { 31 | return new byte[0]; 32 | } else if (bs.length == 1) { 33 | return bs[0]; 34 | } 35 | return com.google.common.primitives.Bytes.concat(bs); 36 | } 37 | 38 | public static int read24(final ByteBuf bb) { 39 | final byte[] b = new byte[3]; 40 | bb.readBytes(b); 41 | return (b[0] & 0xFF) << 16 | (b[1] & 0xFF) << 8 | (b[2] & 0xFF); 42 | } 43 | 44 | public static void write24(final ByteBuf bb, final int value) { 45 | bb.writeByte((value >> 16) & 0xFF); 46 | bb.writeByte((value >> 8) & 0xFF); 47 | bb.writeByte(value & 0xFF); 48 | } 49 | 50 | public static void set24(final ByteBuf bb, final int position, final int value) { 51 | bb.setByte(position, (value >> 16) & 0xFF); 52 | bb.setByte(position + 1, (value >> 8) & 0xFF); 53 | bb.setByte(position + 2, value & 0xFF); 54 | } 55 | 56 | public static byte[] write(final Writeable... writeables) { 57 | return write(Arrays.asList(writeables)); 58 | } 59 | 60 | public static byte[] write(final Collection writeables) { 61 | final ByteBuf bb = Unpooled.buffer(); 62 | for (final Writeable writeable : writeables) { 63 | writeable.write(bb); 64 | } 65 | return drainToArray(bb); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /common/src/main/java/com/protocol7/quincy/utils/Debug.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.quincy.utils; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | public class Debug { 6 | 7 | public static void buffer(final ByteBuf bb) { 8 | buffer("", bb); 9 | } 10 | 11 | public static void buffer(final String msg, final ByteBuf bb) { 12 | final int index = bb.readerIndex(); 13 | final byte[] b = new byte[bb.readableBytes()]; 14 | bb.readBytes(b); 15 | bb.readerIndex(index); 16 | 17 | System.out.println(msg + Hex.hex(b)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /common/src/main/java/com/protocol7/quincy/utils/Hex.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.quincy.utils; 2 | 3 | import com.google.common.io.BaseEncoding; 4 | 5 | public class Hex { 6 | 7 | private static final BaseEncoding HEX = BaseEncoding.base16().lowerCase(); 8 | 9 | public static String hex(final byte[] b) { 10 | return HEX.encode(b); 11 | } 12 | 13 | public static String hex(final byte b) { 14 | return HEX.encode(new byte[] {b}); 15 | } 16 | 17 | public static byte[] dehex(final String s) { 18 | return HEX.decode(s.replace(" ", "").toLowerCase()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /common/src/main/java/com/protocol7/quincy/utils/Pair.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.quincy.utils; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import java.util.Objects; 6 | 7 | public class Pair { 8 | 9 | public static Pair of(final F f, final S s) { 10 | return new Pair<>(f, s); 11 | } 12 | 13 | private final F first; 14 | private final S second; 15 | 16 | private Pair(final F first, final S second) { 17 | this.first = requireNonNull(first); 18 | this.second = requireNonNull(second); 19 | } 20 | 21 | public F getFirst() { 22 | return first; 23 | } 24 | 25 | public S getSecond() { 26 | return second; 27 | } 28 | 29 | @Override 30 | public boolean equals(final Object o) { 31 | if (this == o) return true; 32 | if (o == null || getClass() != o.getClass()) return false; 33 | final Pair pair = (Pair) o; 34 | return Objects.equals(first, pair.first) && Objects.equals(second, pair.second); 35 | } 36 | 37 | @Override 38 | public int hashCode() { 39 | return Objects.hash(first, second); 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "Pair{" + first + ", " + second + '}'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /common/src/main/java/com/protocol7/quincy/utils/Rnd.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.quincy.utils; 2 | 3 | import java.security.SecureRandom; 4 | 5 | public class Rnd { 6 | 7 | private static final SecureRandom rnd = new SecureRandom(); 8 | 9 | public static void rndBytes(final byte[] b) { 10 | rnd.nextBytes(b); 11 | } 12 | 13 | public static byte[] rndBytes(final int length) { 14 | final byte[] b = new byte[length]; 15 | rndBytes(b); 16 | return b; 17 | } 18 | 19 | public static int rndInt(final int min, final int max) { 20 | return rnd.nextInt(max - min) + min; 21 | } 22 | 23 | public static int rndInt() { 24 | return rnd.nextInt(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /common/src/main/java/com/protocol7/quincy/utils/Ticker.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.quincy.utils; 2 | 3 | public interface Ticker { 4 | 5 | static Ticker systemTicker() { 6 | return new Ticker() { 7 | 8 | @Override 9 | public long milliTime() { 10 | return System.currentTimeMillis(); 11 | } 12 | 13 | @Override 14 | public long nanoTime() { 15 | return System.nanoTime(); 16 | } 17 | }; 18 | } 19 | 20 | long milliTime(); 21 | 22 | long nanoTime(); 23 | } 24 | -------------------------------------------------------------------------------- /common/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 com.protocol7.quincy.utils.Bytes; 8 | import io.netty.buffer.ByteBuf; 9 | 10 | public class TestUtil { 11 | 12 | public static void assertBuffer(final byte[] expected, final ByteBuf actual) { 13 | assertBuffer(hex(expected), actual); 14 | } 15 | 16 | public static void assertBuffer(final String expected, final ByteBuf actual) { 17 | final byte[] actualBytes = new byte[actual.readableBytes()]; 18 | actual.readBytes(actualBytes); 19 | assertEquals(expected, hex(actualBytes)); 20 | assertBufferExhusted(actual); 21 | } 22 | 23 | public static void assertBufferExhusted(final ByteBuf bb) { 24 | assertFalse(bb.isReadable()); 25 | } 26 | 27 | public static void assertHex(final String expectedHex, final byte[] actual) { 28 | assertEquals(expectedHex, hex(actual)); 29 | } 30 | 31 | public static void assertHex(final String expectedHex, final ByteBuf actual) { 32 | final byte[] actualBytes = Bytes.peekToArray(actual); 33 | assertHex(expectedHex, actualBytes); 34 | } 35 | 36 | public static void assertHex(final byte[] expected, final byte[] actual) { 37 | assertEquals(hex(expected), hex(actual)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /common/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 | -------------------------------------------------------------------------------- /integration-tests/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | quincy-integration-tests 6 | jar 7 | 8 | 9 | com.protocol7 10 | quincy-parent 11 | 0.0.1-SNAPSHOT 12 | 13 | 14 | 15 | 16 | ${project.groupId} 17 | quincy-quic 18 | 19 | 20 | ${project.groupId} 21 | quic-go-testcontainer 22 | 23 | 24 | ${project.groupId} 25 | quicly-testcontainer 26 | 27 | 28 | 29 | 30 | junit 31 | junit 32 | test 33 | 34 | 35 | mockito-core 36 | org.mockito 37 | test 38 | 39 | 40 | ch.qos.logback 41 | logback-classic 42 | test 43 | 44 | 45 | org.testcontainers 46 | testcontainers 47 | test 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /integration-tests/src/test/java/com/protocol7/quincy/it/KeyUtil.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.quincy.it; 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.cert.Certificate; 11 | import java.security.cert.CertificateFactory; 12 | import java.security.spec.PKCS8EncodedKeySpec; 13 | import java.util.List; 14 | 15 | public class KeyUtil { 16 | 17 | public static List getCertsFromCrt(final String path) { 18 | try { 19 | final FileInputStream fin = new FileInputStream(path); 20 | final CertificateFactory f = CertificateFactory.getInstance("X.509"); 21 | final Certificate cert = f.generateCertificate(fin); 22 | return List.of(cert.getEncoded()); 23 | } catch (final GeneralSecurityException | IOException e) { 24 | throw new RuntimeException(e); 25 | } 26 | } 27 | 28 | public static PrivateKey getPrivateKey(final String path) { 29 | try { 30 | final byte[] b = Files.readAllBytes(Path.of(path)); 31 | final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(b); 32 | 33 | final KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 34 | 35 | return keyFactory.generatePrivate(keySpec); 36 | } catch (final GeneralSecurityException | IOException e) { 37 | throw new RuntimeException(e); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /integration-tests/src/test/java/com/protocol7/quincy/it/QuicheContainer.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.quincy.it; 2 | 3 | import com.github.dockerjava.api.model.ExposedPort; 4 | import com.github.dockerjava.api.model.InternetProtocol; 5 | import java.io.File; 6 | import java.net.InetSocketAddress; 7 | import org.testcontainers.containers.BindMode; 8 | import org.testcontainers.containers.GenericContainer; 9 | import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy; 10 | import org.testcontainers.containers.wait.strategy.Wait; 11 | import org.testcontainers.images.builder.ImageFromDockerfile; 12 | 13 | public class QuicheContainer extends GenericContainer { 14 | 15 | public QuicheContainer(final boolean wait) { 16 | super( 17 | new ImageFromDockerfile("quiche", false) 18 | .withFileFromClasspath("Dockerfile", "QuicheDockerfile")); 19 | 20 | if (wait) { 21 | waitingFor(Wait.forLogMessage(".*listening on.*", 1)); 22 | } else { 23 | withStartupCheckStrategy(new OneShotStartupCheckStrategy()); 24 | } 25 | withFileSystemBind( 26 | new File("src/test/resources").getAbsolutePath(), "/keys", BindMode.READ_ONLY); 27 | } 28 | 29 | private int getUdpPort() { 30 | return Integer.parseInt( 31 | getContainerInfo() 32 | .getNetworkSettings() 33 | .getPorts() 34 | .getBindings() 35 | .get(new ExposedPort(4433, InternetProtocol.UDP))[0] 36 | .getHostPortSpec()); 37 | } 38 | 39 | public InetSocketAddress getAddress() { 40 | return new InetSocketAddress(getContainerIpAddress(), getUdpPort()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /integration-tests/src/test/resources/QuicheDockerfile: -------------------------------------------------------------------------------- 1 | FROM cloudflare/quiche 2 | 3 | EXPOSE 4433/udp 4 | -------------------------------------------------------------------------------- /integration-tests/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 | -------------------------------------------------------------------------------- /integration-tests/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 | -------------------------------------------------------------------------------- /integration-tests/src/test/resources/server.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/protocol7/quincy/74e49d8a84a77eada23283ed823b8bc7e7ce321e/integration-tests/src/test/resources/server.der -------------------------------------------------------------------------------- /integration-tests/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 | -------------------------------------------------------------------------------- /quic-go-testcontainer/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | quic-go-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 | QuicGoDockerfile 38 | main.go 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /quic-go-testcontainer/src/main/java/com/protocol7/testcontainers/quicgo/QuicGoContainer.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.testcontainers.quicgo; 2 | 3 | import com.github.dockerjava.api.model.ExposedPort; 4 | import com.github.dockerjava.api.model.InternetProtocol; 5 | import java.net.InetSocketAddress; 6 | import org.testcontainers.containers.GenericContainer; 7 | import org.testcontainers.containers.wait.strategy.Wait; 8 | import org.testcontainers.images.builder.ImageFromDockerfile; 9 | 10 | public class QuicGoContainer extends GenericContainer { 11 | 12 | private static ImageFromDockerfile image() { 13 | return new ImageFromDockerfile("quic-go", false) 14 | .withFileFromClasspath("Dockerfile", "QuicGoDockerfile") 15 | .withFileFromClasspath("main.go", "main.go"); 16 | } 17 | 18 | public QuicGoContainer() { 19 | super(image()); 20 | 21 | withExposedPorts(6121); 22 | waitingFor(Wait.forLogMessage(".*server Listening for udp connections on.*\\n", 1)); 23 | } 24 | 25 | private int getUdpPort() { 26 | return Integer.parseInt( 27 | getContainerInfo() 28 | .getNetworkSettings() 29 | .getPorts() 30 | .getBindings() 31 | .get(new ExposedPort(6121, InternetProtocol.UDP))[0] 32 | .getHostPortSpec()); 33 | } 34 | 35 | public InetSocketAddress getAddress() { 36 | return new InetSocketAddress(getContainerIpAddress(), getUdpPort()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /quic-go-testcontainer/src/main/resources/QuicGoDockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.14.5 2 | 3 | ARG hash=d1c5297c0bd9176e5f76e4245a1bcb2fe51bed2a 4 | 5 | WORKDIR /root/src/github.com/lucas-clemente/quic-go 6 | 7 | RUN git clone https://github.com/lucas-clemente/quic-go.git . && git checkout $hash 8 | 9 | RUN go get -d -v ./... 10 | 11 | COPY main.go interop/main.go 12 | 13 | EXPOSE 6121/udp 14 | 15 | ENTRYPOINT ["go", "run", "interop/main.go"] 16 | -------------------------------------------------------------------------------- /quic/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | quincy-quic 6 | jar 7 | 8 | 9 | com.protocol7 10 | quincy-parent 11 | 0.0.1-SNAPSHOT 12 | 13 | 14 | 15 | 16 | ${project.groupId} 17 | quincy-tls 18 | 19 | 20 | io.netty 21 | netty-buffer 22 | 23 | 24 | io.netty 25 | netty-transport 26 | 27 | 28 | io.netty 29 | netty-handler 30 | 31 | 32 | com.google.guava 33 | guava 34 | 35 | 36 | org.slf4j 37 | slf4j-api 38 | 39 | 40 | 41 | 42 | ${project.groupId} 43 | quincy-tls 44 | tests 45 | test-jar 46 | test 47 | 48 | 49 | junit 50 | junit 51 | test 52 | 53 | 54 | org.hamcrest 55 | hamcrest-library 56 | test 57 | 58 | 59 | mockito-core 60 | org.mockito 61 | test 62 | 63 | 64 | ch.qos.logback 65 | logback-classic 66 | test 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /quic/src/main/java/com/protocol7/quincy/FrameSender.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.quincy; 2 | 3 | import com.protocol7.quincy.protocol.frames.Frame; 4 | import com.protocol7.quincy.protocol.packets.FullPacket; 5 | import com.protocol7.quincy.protocol.packets.Packet; 6 | import com.protocol7.quincy.tls.EncryptionLevel; 7 | 8 | // TODO consolidate with Sender 9 | public interface FrameSender { 10 | 11 | boolean isOpen(); 12 | 13 | Packet sendPacket(Packet p); 14 | 15 | FullPacket send(EncryptionLevel level, Frame... frames); 16 | } 17 | -------------------------------------------------------------------------------- /quic/src/main/java/com/protocol7/quincy/Futures.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.quincy; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.util.concurrent.DefaultPromise; 6 | import io.netty.util.concurrent.Future; 7 | import io.netty.util.concurrent.GenericFutureListener; 8 | import io.netty.util.concurrent.GlobalEventExecutor; 9 | import java.util.function.Function; 10 | 11 | public class Futures { 12 | 13 | public static Future thenSync(final Future future, final Function f) { 14 | final DefaultPromise result = new DefaultPromise<>(GlobalEventExecutor.INSTANCE); 15 | 16 | future.addListener( 17 | new GenericFutureListener>() { 18 | @Override 19 | public void operationComplete(final Future future) { 20 | result.setSuccess(f.apply(future.getNow())); // TODO handle exception 21 | } 22 | }); 23 | 24 | return result; 25 | } 26 | 27 | public static Future thenAsync(final Future future, final Function> f) { 28 | final DefaultPromise result = new DefaultPromise<>(GlobalEventExecutor.INSTANCE); 29 | 30 | future.addListener( 31 | new GenericFutureListener>() { 32 | @Override 33 | public void operationComplete(final Future future) throws Exception { 34 | Futures.thenSync( 35 | f.apply(future.get()), 36 | (Function) 37 | t -> { 38 | result.setSuccess(t); // TODO handle exception 39 | return null; 40 | }); 41 | } 42 | }); 43 | 44 | return result; 45 | } 46 | 47 | public static Future thenChannel(final ChannelFuture future) { 48 | return thenSync(future, aVoid -> future.channel()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /quic/src/main/java/com/protocol7/quincy/InboundHandler.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.quincy; 2 | 3 | import com.protocol7.quincy.protocol.packets.Packet; 4 | 5 | public interface InboundHandler { 6 | 7 | void onReceivePacket(final Packet packet, final PipelineContext ctx); 8 | } 9 | -------------------------------------------------------------------------------- /quic/src/main/java/com/protocol7/quincy/OutboundHandler.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.quincy; 2 | 3 | import com.protocol7.quincy.protocol.packets.Packet; 4 | 5 | public interface OutboundHandler { 6 | 7 | void beforeSendPacket(final Packet packet, final PipelineContext ctx); 8 | } 9 | -------------------------------------------------------------------------------- /quic/src/main/java/com/protocol7/quincy/PipelineContext.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.quincy; 2 | 3 | import com.protocol7.quincy.connection.State; 4 | import com.protocol7.quincy.protocol.TransportError; 5 | import com.protocol7.quincy.protocol.Version; 6 | import com.protocol7.quincy.protocol.frames.FrameType; 7 | import com.protocol7.quincy.protocol.packets.Packet; 8 | import java.net.InetSocketAddress; 9 | 10 | public interface PipelineContext extends FrameSender { 11 | 12 | void next(Packet packet); 13 | 14 | Version getVersion(); 15 | 16 | void closeConnection(TransportError error, final FrameType frameType, final String msg); 17 | 18 | InetSocketAddress getPeerAddress(); 19 | 20 | State getState(); 21 | 22 | void setState(final State state); 23 | } 24 | -------------------------------------------------------------------------------- /quic/src/main/java/com/protocol7/quincy/addressvalidation/QuicTokenHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package com.protocol7.quincy.addressvalidation; 17 | 18 | import com.protocol7.quincy.protocol.ConnectionId; 19 | import io.netty.buffer.ByteBuf; 20 | import java.net.InetSocketAddress; 21 | import java.util.Optional; 22 | 23 | /** Handle token related operations. */ 24 | public interface QuicTokenHandler { 25 | 26 | /** 27 | * Generate a new token for the given destination connection id and address. This token is written 28 | * to {@code out}. If no token should be generated and so no token validation should take place at 29 | * all this method should return {@code false}. 30 | * 31 | * @param dcid the destination connection id. 32 | * @param address the {@link InetSocketAddress} of the sender. 33 | * @return {@code true} if a token was written and so validation should happen, {@code false} 34 | * otherwise. 35 | */ 36 | byte[] writeToken(ConnectionId dcid, InetSocketAddress address); 37 | 38 | /** 39 | * Validate the token and return the offset, {@code -1} is returned if the token is not valid. 40 | * 41 | * @param token the {@link ByteBuf} that contains the token. The ownership is not transferred. 42 | * @param address the {@link InetSocketAddress} of the sender. 43 | * @return the start index after the token or {@code -1} if the token was not valid. 44 | */ 45 | Optional validateToken(byte[] token, InetSocketAddress address); 46 | 47 | /** 48 | * Return the maximal token length. 49 | * 50 | * @return the maximal supported token length. 51 | */ 52 | int maxTokenLength(); 53 | } 54 | -------------------------------------------------------------------------------- /quic/src/main/java/com/protocol7/quincy/connection/NettyPacketSender.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.quincy.connection; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import com.protocol7.quincy.protocol.packets.Packet; 6 | import com.protocol7.quincy.tls.aead.AEAD; 7 | import io.netty.buffer.ByteBuf; 8 | import io.netty.buffer.Unpooled; 9 | import io.netty.channel.Channel; 10 | import io.netty.channel.socket.DatagramPacket; 11 | import io.netty.util.concurrent.Future; 12 | import java.net.InetSocketAddress; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | public class NettyPacketSender implements PacketSender { 17 | 18 | private final Logger log = LoggerFactory.getLogger(NettyPacketSender.class); 19 | 20 | private final Channel channel; 21 | private final InetSocketAddress peerAddress; 22 | 23 | public NettyPacketSender(final Channel channel, final InetSocketAddress peerAddress) { 24 | this.channel = requireNonNull(channel); 25 | this.peerAddress = requireNonNull(peerAddress); 26 | } 27 | 28 | @Override 29 | public Future send(final Packet packet, final AEAD aead) { 30 | requireNonNull(packet); 31 | 32 | final ByteBuf bb = Unpooled.buffer(); 33 | packet.write(bb, aead); 34 | 35 | log.debug("Sending datagram for packet {}", packet); 36 | return channel.writeAndFlush(new DatagramPacket(bb, peerAddress)); 37 | } 38 | 39 | @Override 40 | public Future destroy() { 41 | return channel.close(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /quic/src/main/java/com/protocol7/quincy/connection/PacketSender.java: -------------------------------------------------------------------------------- 1 | package com.protocol7.quincy.connection; 2 | 3 | import com.protocol7.quincy.protocol.packets.Packet; 4 | import com.protocol7.quincy.tls.aead.AEAD; 5 | import io.netty.util.concurrent.Future; 6 | 7 | public interface PacketSender { 8 | Future send(Packet packet, AEAD aead); 9 | 10 | Future 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 | --------------------------------------------------------------------------------