Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a80c3bd
Showing
14 changed files
with
237 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.bin binary |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
*.egg-info | ||
*.pyc | ||
.coverage | ||
.eggs | ||
.vscode | ||
/build | ||
/dist | ||
/docs/_build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
dist: xenial | ||
install: pip3 install coverage flake8 | ||
language: python | ||
python: "3.7" | ||
script: .travis/script | ||
sudo: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
#!/bin/sh | ||
|
||
set -e | ||
|
||
if [ "$BUILD" = "sdist" ]; then | ||
python3 setup.py sdist bdist_wheel | ||
if [ -n "$TRAVIS_TAG" ]; then | ||
pip3 install pyopenssl twine | ||
python3 -m twine upload --skip-existing dist/* | ||
fi | ||
else | ||
flake8 aioquic tests | ||
coverage run setup.py test | ||
curl -s https://codecov.io/bash | bash | ||
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
aioquic | ||
======= | ||
|
||
|travis| |codecov| | ||
|
||
.. |travis| image:: https://img.shields.io/travis/com/aiortc/aioquic.svg | ||
:target: https://travis-ci.com/aiortc/aioquic | ||
|
||
.. |codecov| image:: https://img.shields.io/codecov/c/github/aiortc/aioquic.svg | ||
:target: https://codecov.io/gh/aiortc/aioquic | ||
|
||
What is ``aioquic``? | ||
-------------------- | ||
|
||
``aioquic`` is a library for Quick UDP Internet Connections (QUIC) in Python. | ||
It is built on top of ``asyncio``, Python's standard asynchronous I/O | ||
framework. |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
from dataclasses import dataclass | ||
from struct import unpack_from | ||
|
||
PACKET_LONG_HEADER = 0x80 | ||
PACKET_FIXED_BIT = 0x40 | ||
|
||
PACKET_TYPE_INITIAL = PACKET_LONG_HEADER | PACKET_FIXED_BIT | 0x00 | ||
PACKET_TYPE_0RTT = PACKET_LONG_HEADER | PACKET_FIXED_BIT | 0x10 | ||
PACKET_TYPE_HANDSHAKE = PACKET_LONG_HEADER | PACKET_FIXED_BIT | 0x20 | ||
PACKET_TYPE_RETRY = PACKET_LONG_HEADER | PACKET_FIXED_BIT | 0x30 | ||
PACKET_TYPE_MASK = 0xf0 | ||
|
||
PROTOCOL_VERSION = 0xFF000011 # draft 17 | ||
|
||
VARIABLE_LENGTH_FORMATS = [ | ||
(1, '!B', 0x3f), | ||
(2, '!H', 0x3fff), | ||
(4, '!L', 0x3fffffff), | ||
(8, '!Q', 0x3fffffffffffffff), | ||
] | ||
|
||
|
||
def decode_cid_length(length): | ||
return length + 3 if length else 0 | ||
|
||
|
||
def unpack_variable_length(data, pos=0): | ||
kind = data[pos] // 64 | ||
length, fmt, mask = VARIABLE_LENGTH_FORMATS[kind] | ||
return unpack_from(fmt, data, pos)[0] & mask, pos + length | ||
|
||
|
||
@dataclass | ||
class QuicHeader: | ||
version: int | ||
destination_cid: bytes | ||
source_cid: bytes | ||
token: bytes = b'' | ||
|
||
@classmethod | ||
def parse(cls, data): | ||
datagram_length = len(data) | ||
if datagram_length < 2: | ||
raise ValueError('Packet is too short (%d bytes)' % datagram_length) | ||
|
||
first_byte = data[0] | ||
if first_byte & PACKET_LONG_HEADER: | ||
if datagram_length < 6: | ||
raise ValueError('Long header is too short (%d bytes)' % datagram_length) | ||
|
||
version, cid_lengths = unpack_from('!LB', data, 1) | ||
pos = 6 | ||
|
||
destination_cid_length = decode_cid_length(cid_lengths // 16) | ||
destination_cid = data[pos:pos + destination_cid_length] | ||
pos += destination_cid_length | ||
|
||
source_cid_length = decode_cid_length(cid_lengths % 16) | ||
source_cid = data[pos:pos + source_cid_length] | ||
pos += source_cid_length | ||
|
||
packet_type = first_byte & PACKET_TYPE_MASK | ||
if packet_type == PACKET_TYPE_INITIAL: | ||
token_length, pos = unpack_variable_length(data, pos) | ||
token = data[pos:pos + token_length] | ||
pos += token_length | ||
|
||
length, pos = unpack_variable_length(data, pos) | ||
else: | ||
raise ValueError('Long header packet type 0x%x is not supported' % packet_type) | ||
|
||
return QuicHeader( | ||
version=version, | ||
destination_cid=destination_cid, | ||
source_cid=source_cid, | ||
token=token) | ||
else: | ||
# short header packet | ||
raise ValueError('Short header is not supported yet') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
[coverage:run] | ||
source = aioquic | ||
|
||
[flake8] | ||
max-line-length=100 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import os.path | ||
|
||
import setuptools | ||
|
||
root_dir = os.path.abspath(os.path.dirname(__file__)) | ||
readme_file = os.path.join(root_dir, 'README.rst') | ||
with open(readme_file, encoding='utf-8') as f: | ||
long_description = f.read() | ||
|
||
setuptools.setup( | ||
name='aioquic', | ||
version='0.0.1', | ||
description='An implementation of QUIC', | ||
long_description=long_description, | ||
url='https://github.com/aiortc/aioquic', | ||
author='Jeremy Lainé', | ||
author_email='jeremy.laine@m4x.org', | ||
license='BSD', | ||
classifiers=[ | ||
'Development Status :: 1 - Planning', | ||
'Environment :: Web Environment', | ||
'Intended Audience :: Developers', | ||
'License :: OSI Approved :: BSD License', | ||
'Operating System :: OS Independent', | ||
'Programming Language :: Python', | ||
'Programming Language :: Python :: 3', | ||
'Programming Language :: Python :: 3.5', | ||
'Programming Language :: Python :: 3.6', | ||
'Programming Language :: Python :: 3.7', | ||
], | ||
packages=['aioquic'], | ||
) |
Empty file.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import binascii | ||
from unittest import TestCase | ||
|
||
from aioquic.packet import QuicHeader, unpack_variable_length | ||
|
||
from .utils import load | ||
|
||
|
||
class UtilTest(TestCase): | ||
def test_unpack_variable_length(self): | ||
# 1 byte | ||
self.assertEqual(unpack_variable_length(b'\x00'), (0, 1)) | ||
self.assertEqual(unpack_variable_length(b'\x01'), (1, 1)) | ||
self.assertEqual(unpack_variable_length(b'\x25'), (37, 1)) | ||
self.assertEqual(unpack_variable_length(b'\x3f'), (63, 1)) | ||
|
||
# 2 bytes | ||
self.assertEqual(unpack_variable_length(b'\x7b\xbd'), (15293, 2)) | ||
self.assertEqual(unpack_variable_length(b'\x7f\xff'), (16383, 2)) | ||
|
||
# 4 bytes | ||
self.assertEqual(unpack_variable_length(b'\x9d\x7f\x3e\x7d'), (494878333, 4)) | ||
self.assertEqual(unpack_variable_length(b'\xbf\xff\xff\xff'), (1073741823, 4)) | ||
|
||
# 8 bytes | ||
self.assertEqual(unpack_variable_length(b'\xc2\x19\x7c\x5e\xff\x14\xe8\x8c'), | ||
(151288809941952652, 8)) | ||
self.assertEqual(unpack_variable_length(b'\xff\xff\xff\xff\xff\xff\xff\xff'), | ||
(4611686018427387903, 8)) | ||
|
||
|
||
class PacketTest(TestCase): | ||
def test_parse_initial_client(self): | ||
data = load('initial_client.bin') | ||
header = QuicHeader.parse(data) | ||
self.assertEqual(header.version, 0xff000011) | ||
self.assertEqual(header.destination_cid, binascii.unhexlify('90ed1e1c7b04b5d3')) | ||
self.assertEqual(header.source_cid, b'') | ||
self.assertEqual(header.token, b'') | ||
|
||
def test_parse_initial_server(self): | ||
data = load('initial_server.bin') | ||
header = QuicHeader.parse(data) | ||
self.assertEqual(header.version, 0xff000011) | ||
self.assertEqual(header.destination_cid, b'') | ||
self.assertEqual(header.source_cid, binascii.unhexlify('0fcee9852fde8780')) | ||
self.assertEqual(header.token, b'') | ||
|
||
def test_parse_long_header_bad_packet_type(self): | ||
with self.assertRaises(ValueError) as cm: | ||
QuicHeader.parse(b'\x80\x00\x00\x00\x00\x00') | ||
self.assertEqual(str(cm.exception), 'Long header packet type 0x80 is not supported') | ||
|
||
def test_parse_long_header_too_short(self): | ||
with self.assertRaises(ValueError) as cm: | ||
QuicHeader.parse(b'\x80\x00') | ||
self.assertEqual(str(cm.exception), 'Long header is too short (2 bytes)') | ||
|
||
def test_parse_short_header(self): | ||
with self.assertRaises(ValueError) as cm: | ||
QuicHeader.parse(b'\x00\x00') | ||
self.assertEqual(str(cm.exception), 'Short header is not supported yet') | ||
|
||
def test_parse_too_short_header(self): | ||
with self.assertRaises(ValueError) as cm: | ||
QuicHeader.parse(b'\x00') | ||
self.assertEqual(str(cm.exception), 'Packet is too short (1 bytes)') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import os | ||
|
||
|
||
def load(name): | ||
path = os.path.join(os.path.dirname(__file__), name) | ||
with open(path, 'rb') as fp: | ||
return fp.read() |