diff --git a/quic.c b/quic.c new file mode 100644 index 0000000..a2dc291 --- /dev/null +++ b/quic.c @@ -0,0 +1,137 @@ +#include "quic.h" + +static const uint32_t supported_versions[] = { + 1, // version 1, RFC 9000 + 0x6b3343cf, // version 2, RFC 9369 +}; + +/** + * Packet number. + */ +struct quic_pnumber { + uint8_t d1; + uint8_t d2; + uint8_t d3; + uint8_t d4; +}; + +uint64_t quic_parse_varlength(uint8_t *variable, uint64_t *mlen) { + if (mlen && *mlen == 0) return 0; + uint64_t vr = (*variable & 0x3F); + uint8_t len = 1 << (*variable >> 6); + + if (mlen) { + if (*mlen < len) return 0; + *mlen = len; + } + + ++variable; + for (uint8_t i = 1; i < len; i++) { + vr = (vr << 8) + *variable; + ++variable; + } + + return vr; +} + +int quic_parse_data(uint8_t *raw_payload, uint32_t raw_payload_len, + struct quic_lhdr **qch, uint32_t *qch_len, + struct quic_cids *qci, + uint8_t **payload, uint32_t *plen) { + if ( raw_payload == NULL || + raw_payload_len < sizeof(struct quic_lhdr)) + goto invalid_packet; + + struct quic_lhdr *nqch = (struct quic_lhdr *)raw_payload; + uint32_t left_len = raw_payload_len - sizeof(struct quic_lhdr); + uint8_t *cur_rawptr = raw_payload + sizeof(struct quic_lhdr); + if (!nqch->fixed) + return -EPROTO; + + uint8_t found = 0; + for (uint8_t i = 0; i < sizeof(supported_versions); i++) { + if (nqch->version == supported_versions[i]) { + found = 1; + } + } + + if (!found) + return -EPROTO; + + if (left_len < 2) goto invalid_packet; + struct quic_cids nqci = {0}; + + nqci.dst_len = *cur_rawptr++; + left_len--; + if (left_len < nqci.dst_len) goto invalid_packet; + nqci.dst_id = cur_rawptr; + cur_rawptr += nqci.dst_len; + left_len -= nqci.dst_len; + + nqci.src_len = *cur_rawptr++; + left_len--; + if (left_len < nqci.src_len) goto invalid_packet; + nqci.src_id = cur_rawptr; + cur_rawptr += nqci.src_len; + left_len -= nqci.src_len; + + if (qch) *qch = nqch; + if (qch_len) { + *qch_len = sizeof(struct quic_lhdr) + + nqci.src_len + nqci.dst_len; + } + if (qci) *qci = nqci; + if (payload) *payload = cur_rawptr; + if (plen) *plen = left_len; + + return 0; + +invalid_packet: + return -EINVAL; +} + +int quic_parse_initial_message(uint8_t *inpayload, uint32_t inplen, + const struct quic_lhdr *qch, + struct quici_hdr *qhdr, + uint8_t **payload, uint32_t *plen) { + if (inplen < 3) goto invalid_packet; + struct quici_hdr nqhdr; + + uint8_t *cur_ptr = inpayload; + uint32_t left_len = inplen; + uint64_t tlen = left_len; + + nqhdr.token_len = quic_parse_varlength(cur_ptr, &tlen); + nqhdr.token = cur_ptr + tlen; + + if (left_len < nqhdr.token_len + tlen) + goto invalid_packet; + cur_ptr += tlen + nqhdr.token_len; + left_len -= tlen + nqhdr.token_len; + + tlen = left_len; + nqhdr.length = quic_parse_varlength(cur_ptr, &tlen); + + if (left_len != nqhdr.length + tlen && + left_len <= qch->number_length + 1) + goto invalid_packet; + + uint32_t packet_number = 0; + + for (uint8_t i = 0; i <= qch->number_length; i++) { + packet_number = (packet_number << 8) + *cur_ptr++; + left_len--; + } + + nqhdr.packet_number = packet_number; + + if (qhdr) *qhdr = nqhdr; + if (payload) *payload = cur_ptr; + if (plen) *plen = left_len; + + return 0; + +invalid_packet: + lgerror("QUIC invalid Initial packet", -EINVAL); + return -EINVAL; +} diff --git a/quic.h b/quic.h new file mode 100644 index 0000000..cf46df6 --- /dev/null +++ b/quic.h @@ -0,0 +1,113 @@ +#ifndef QUIC_H +#define QUIC_H +#include "types.h" + +/** +* @macro +* +* :macro:`NGTCP2_INITIAL_SALT_V1` is a salt value which is used to +* derive initial secret. It is used for QUIC v1. +*/ +#define QUIC_INITIAL_SALT_V1 \ + "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17\x9a\xe6\xa4\xc8\x0c\xad" \ + "\xcc\xbb\x7f\x0a" + +/** +* @macro +* +* :macro:`NGTCP2_INITIAL_SALT_V2` is a salt value which is used to +* derive initial secret. It is used for QUIC v2. +*/ +#define QUIC_INITIAL_SALT_V2 \ + "\x0d\xed\xe3\xde\xf7\x00\xa6\xdb\x81\x93\x81\xbe\x6e\x26\x9d\xcb" \ + "\xf9\xbd\x2e\xd9" + +#define QUIC_INITIAL_TYPE_V1 0x00 +#define QUIC_0_RTT_TYPE_V1 0x01 +#define QUIC_HANDSHAKE_TYPE_V1 0x02 +#define QUIC_RETRY_TYPE_V1 0x03 + +#define QUIC_INITIAL_TYPE_V2 0b01 +#define QUIC_0_RTT_TYPE_V2 0b10 +#define QUIC_HANDSHAKE_TYPE_V2 0b11 +#define QUIC_RETRY_TYPE_V2 0b00 + +/** + * Quic Large Header + */ +struct quic_lhdr { +#if __BYTE_ORDER == __LITTLE_ENDIAN + uint8_t number_length: 2; + uint8_t reserved: 2; + uint8_t type: 2; + uint8_t fixed: 1; + uint8_t form: 1; +#elif __BYTE_ORDER == __BIG_ENDIAN + uint8_t form: 1; + uint8_t fixed: 1; + uint8_t type: 2; + uint8_t reserved: 2; + uint8_t number_length: 2; +#else +#error "Undefined endian" +#endif + + uint32_t version; +}; + +/** + * Quic Large Header Ids + * (separated from the original header because of varying dst + */ +struct quic_cids { + uint8_t dst_len; + uint8_t *dst_id; + uint8_t src_len; + uint8_t *src_id; +}; + +/** + * Parses QUIС raw data (UDP payload) to quic large header and + * quic payload. + * + * \qch_len is sizeof(qch) + qci->dst_len + qci->src_id + * \payload is Type-Specific payload (#17.2). + */ +int quic_parse_data(uint8_t *raw_payload, uint32_t raw_payload_len, + struct quic_lhdr **qch, uint32_t *qch_len, + struct quic_cids *qci, + uint8_t **payload, uint32_t *plen); + + +/** + * Parses QUIC variable-length integer. (#16) + * \variable is a pointer to the sequence to be parsed + * (varlen integer in big endian format) + * + * \mlen Used to signal about variable length and validate left length + * in the buffer. + */ +uint64_t quic_parse_varlength(uint8_t *variable, uint64_t *mlen); + +// quici stands for QUIC Initial + +/** + * This structure should be parsed + */ +struct quici_hdr { + uint64_t token_len; + uint8_t *token; + uint64_t length; + uint32_t packet_number; +}; + +/** + * Parses QUIC initial payload. + * \inpayload is a raw QUIC payload (payload after quic large header) + */ +int quic_parse_initial_message(uint8_t *inpayload, uint32_t inplen, + const struct quic_lhdr *qch, + struct quici_hdr *qhdr, + uint8_t **payload, uint32_t *plen); + +#endif /* QUIC_H */