718 lines
22 KiB
C
718 lines
22 KiB
C
#include "http2.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/socket.h>
|
|
|
|
static const char *HTTP2_CONNECTION_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
|
|
static const size_t HTTP2_FRAME_HEADER_SIZE = 9;
|
|
|
|
int http2_init(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int http2_buffer_init(HTTP2_Buffer *buf, size_t initial_capacity)
|
|
{
|
|
if (buf == NULL) return -1;
|
|
buf->data = malloc(initial_capacity);
|
|
if (buf->data == NULL) return -1;
|
|
buf->size = 0;
|
|
buf->capacity = initial_capacity;
|
|
return 0;
|
|
}
|
|
|
|
static int http2_buffer_grow(HTTP2_Buffer *buf, size_t needed)
|
|
{
|
|
if (buf == NULL || buf->data == NULL) return -1;
|
|
size_t new_cap = buf->capacity * 2;
|
|
while (new_cap < buf->size + needed) {
|
|
new_cap *= 2;
|
|
}
|
|
uint8_t *new_data = realloc(buf->data, new_cap);
|
|
if (new_data == NULL) return -1;
|
|
buf->data = new_data;
|
|
buf->capacity = new_cap;
|
|
return 0;
|
|
}
|
|
|
|
static void http2_buffer_free(HTTP2_Buffer *buf)
|
|
{
|
|
if (buf == NULL) return;
|
|
free(buf->data);
|
|
buf->data = NULL;
|
|
buf->size = 0;
|
|
buf->capacity = 0;
|
|
}
|
|
|
|
HTTP2_Connection *http2_connection_new(void)
|
|
{
|
|
HTTP2_Connection *conn = malloc(sizeof(HTTP2_Connection));
|
|
if (conn == NULL) return NULL;
|
|
memset(conn, 0, sizeof(*conn));
|
|
|
|
if (http2_buffer_init(&conn->read_buffer, 4096) != 0) {
|
|
free(conn);
|
|
return NULL;
|
|
}
|
|
if (http2_buffer_init(&conn->write_buffer, 4096) != 0) {
|
|
http2_buffer_free(&conn->read_buffer);
|
|
free(conn);
|
|
return NULL;
|
|
}
|
|
if (http2_hpack_encode_init(&conn->encoder, HTTP2_DEFAULT_HEADER_TABLE_SIZE) != 0) {
|
|
http2_buffer_free(&conn->read_buffer);
|
|
http2_buffer_free(&conn->write_buffer);
|
|
free(conn);
|
|
return NULL;
|
|
}
|
|
if (http2_hpack_decode_init(&conn->decoder, HTTP2_DEFAULT_HEADER_TABLE_SIZE) != 0) {
|
|
http2_hpack_encode_free(&conn->encoder);
|
|
http2_buffer_free(&conn->read_buffer);
|
|
http2_buffer_free(&conn->write_buffer);
|
|
free(conn);
|
|
return NULL;
|
|
}
|
|
|
|
conn->local_settings[HTTP2_SETTINGS_HEADER_TABLE_SIZE] = HTTP2_DEFAULT_HEADER_TABLE_SIZE;
|
|
conn->local_settings[HTTP2_SETTINGS_ENABLE_PUSH] = 1;
|
|
conn->local_settings[HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] = HTTP2_MAX_CONCURRENT_STREAMS;
|
|
conn->local_settings[HTTP2_SETTINGS_INITIAL_WINDOW_SIZE] = HTTP2_INITIAL_WINDOW_SIZE;
|
|
conn->local_settings[HTTP2_SETTINGS_MAX_FRAME_SIZE] = HTTP2_MAX_FRAME_SIZE;
|
|
conn->local_settings[HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE] = 65535;
|
|
|
|
conn->remote_settings[HTTP2_SETTINGS_HEADER_TABLE_SIZE] = HTTP2_DEFAULT_HEADER_TABLE_SIZE;
|
|
conn->remote_settings[HTTP2_SETTINGS_ENABLE_PUSH] = 1;
|
|
conn->remote_settings[HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] = HTTP2_MAX_CONCURRENT_STREAMS;
|
|
conn->remote_settings[HTTP2_SETTINGS_INITIAL_WINDOW_SIZE] = HTTP2_INITIAL_WINDOW_SIZE;
|
|
conn->remote_settings[HTTP2_SETTINGS_MAX_FRAME_SIZE] = HTTP2_MAX_FRAME_SIZE;
|
|
conn->remote_settings[HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE] = 65535;
|
|
|
|
conn->next_stream_id = 1;
|
|
conn->local_window_size = HTTP2_INITIAL_WINDOW_SIZE;
|
|
conn->remote_window_size = HTTP2_INITIAL_WINDOW_SIZE;
|
|
conn->remote_concurrent_streams = HTTP2_MAX_CONCURRENT_STREAMS;
|
|
conn->last_stream_id = 0;
|
|
|
|
return conn;
|
|
}
|
|
|
|
int http2_connection_init(HTTP2_Connection *conn, int sockfd, int is_server)
|
|
{
|
|
if (conn == NULL) return -1;
|
|
|
|
memset(conn, 0, sizeof(*conn));
|
|
conn->sockfd = sockfd;
|
|
conn->is_server = is_server;
|
|
conn->next_stream_id = is_server ? 2 : 1;
|
|
conn->local_window_size = HTTP2_INITIAL_WINDOW_SIZE;
|
|
conn->remote_window_size = HTTP2_INITIAL_WINDOW_SIZE;
|
|
conn->remote_concurrent_streams = HTTP2_MAX_CONCURRENT_STREAMS;
|
|
|
|
conn->local_settings[HTTP2_SETTINGS_HEADER_TABLE_SIZE] = HTTP2_DEFAULT_HEADER_TABLE_SIZE;
|
|
conn->local_settings[HTTP2_SETTINGS_ENABLE_PUSH] = 1;
|
|
conn->local_settings[HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] = HTTP2_MAX_CONCURRENT_STREAMS;
|
|
conn->local_settings[HTTP2_SETTINGS_INITIAL_WINDOW_SIZE] = HTTP2_INITIAL_WINDOW_SIZE;
|
|
conn->local_settings[HTTP2_SETTINGS_MAX_FRAME_SIZE] = HTTP2_MAX_FRAME_SIZE;
|
|
conn->local_settings[HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE] = 65535;
|
|
|
|
conn->remote_settings[HTTP2_SETTINGS_HEADER_TABLE_SIZE] = HTTP2_DEFAULT_HEADER_TABLE_SIZE;
|
|
conn->remote_settings[HTTP2_SETTINGS_ENABLE_PUSH] = 1;
|
|
conn->remote_settings[HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS] = HTTP2_MAX_CONCURRENT_STREAMS;
|
|
conn->remote_settings[HTTP2_SETTINGS_INITIAL_WINDOW_SIZE] = HTTP2_INITIAL_WINDOW_SIZE;
|
|
conn->remote_settings[HTTP2_SETTINGS_MAX_FRAME_SIZE] = HTTP2_MAX_FRAME_SIZE;
|
|
conn->remote_settings[HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE] = 65535;
|
|
|
|
if (http2_buffer_init(&conn->read_buffer, 4096) != 0) return -1;
|
|
if (http2_buffer_init(&conn->write_buffer, 4096) != 0) {
|
|
http2_buffer_free(&conn->read_buffer);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void http2_connection_free(HTTP2_Connection *conn)
|
|
{
|
|
if (conn == NULL) return;
|
|
http2_buffer_free(&conn->read_buffer);
|
|
http2_buffer_free(&conn->write_buffer);
|
|
free(conn);
|
|
}
|
|
|
|
void http2_connection_close(HTTP2_Connection *conn)
|
|
{
|
|
if (conn == NULL) return;
|
|
if (conn->sockfd >= 0) {
|
|
close(conn->sockfd);
|
|
conn->sockfd = -1;
|
|
}
|
|
}
|
|
|
|
int http2_send_preface(HTTP2_Connection *conn)
|
|
{
|
|
if (conn == NULL || conn->sockfd < 0) return -1;
|
|
|
|
if (send(conn->sockfd, HTTP2_CONNECTION_PREFACE, strlen(HTTP2_CONNECTION_PREFACE), 0) < 0) {
|
|
perror("Failed to send HTTP/2 connection preface");
|
|
return -1;
|
|
}
|
|
|
|
uint32_t settings[12] = {
|
|
HTTP2_SETTINGS_HEADER_TABLE_SIZE, 4096,
|
|
HTTP2_SETTINGS_ENABLE_PUSH, 1,
|
|
HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100,
|
|
HTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 65535,
|
|
HTTP2_SETTINGS_MAX_FRAME_SIZE, 16384,
|
|
HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, 65535
|
|
};
|
|
http2_encode_settings(conn, settings, 6);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int http2_recv_preface(HTTP2_Connection *conn)
|
|
{
|
|
if (conn == NULL || conn->sockfd < 0) return -1;
|
|
|
|
char preface[24] = {0};
|
|
ssize_t received = recv(conn->sockfd, preface, sizeof(preface) - 1, MSG_PEEK);
|
|
if (received < 0) {
|
|
perror("Failed to recv HTTP/2 connection preface");
|
|
return -1;
|
|
}
|
|
|
|
if ((size_t)received < strlen(HTTP2_CONNECTION_PREFACE)) {
|
|
received = recv(conn->sockfd, preface, strlen(HTTP2_CONNECTION_PREFACE), 0);
|
|
if (received < 0) {
|
|
perror("Failed to recv HTTP/2 connection preface");
|
|
return -1;
|
|
}
|
|
if (strncmp(preface, HTTP2_CONNECTION_PREFACE, strlen(HTTP2_CONNECTION_PREFACE)) != 0) {
|
|
fprintf(stderr, "Invalid HTTP/2 connection preface\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int http2_encode_frame(HTTP2_Frame *frame, HTTP2_Frame_Type type, uint32_t stream_id, uint8_t flags, const uint8_t *payload, size_t payload_len)
|
|
{
|
|
if (frame == NULL || payload_len > HTTP2_MAX_FRAME_SIZE) return -1;
|
|
|
|
frame->type = type;
|
|
frame->flags = flags;
|
|
frame->stream_id = stream_id;
|
|
frame->length = (uint32_t)payload_len;
|
|
|
|
if (payload != NULL && payload_len > 0) {
|
|
memcpy(frame->payload, payload, payload_len);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static size_t http2_format_frame(const HTTP2_Frame *frame, uint8_t *out)
|
|
{
|
|
if (frame == NULL || out == NULL) return 0;
|
|
|
|
out[0] = (uint8_t)((frame->length >> 16) & 0xFF);
|
|
out[1] = (uint8_t)((frame->length >> 8) & 0xFF);
|
|
out[2] = (uint8_t)(frame->length & 0xFF);
|
|
out[3] = (uint8_t)frame->type;
|
|
out[4] = frame->flags;
|
|
|
|
uint32_t stream_id = frame->stream_id;
|
|
out[5] = (uint8_t)((stream_id >> 24) & 0xFF);
|
|
out[6] = (uint8_t)((stream_id >> 16) & 0xFF);
|
|
out[7] = (uint8_t)((stream_id >> 8) & 0xFF);
|
|
out[8] = (uint8_t)(stream_id & 0xFF);
|
|
|
|
if (frame->length > 0) {
|
|
memcpy(out + HTTP2_FRAME_HEADER_SIZE, frame->payload, frame->length);
|
|
}
|
|
|
|
return HTTP2_FRAME_HEADER_SIZE + frame->length;
|
|
}
|
|
|
|
int http2_decode_frame(HTTP2_Frame *frame, const uint8_t *data, size_t len)
|
|
{
|
|
if (frame == NULL || data == NULL || len < HTTP2_FRAME_HEADER_SIZE) return -1;
|
|
|
|
frame->length = ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | (uint32_t)data[2];
|
|
frame->type = (HTTP2_Frame_Type)data[3];
|
|
frame->flags = data[4];
|
|
frame->stream_id = ((uint32_t)data[5] << 24) | ((uint32_t)data[6] << 16) | ((uint32_t)data[7] << 8) | (uint32_t)data[8];
|
|
|
|
if (frame->length > 0 && len >= HTTP2_FRAME_HEADER_SIZE + frame->length) {
|
|
memcpy(frame->payload, data + HTTP2_FRAME_HEADER_SIZE, frame->length);
|
|
} else if (frame->length > 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int http2_recv_frame(HTTP2_Connection *conn, HTTP2_Frame *frame)
|
|
{
|
|
if (conn == NULL || frame == NULL || conn->sockfd < 0) return -1;
|
|
|
|
uint8_t header[HTTP2_FRAME_HEADER_SIZE];
|
|
ssize_t received = recv(conn->sockfd, header, HTTP2_FRAME_HEADER_SIZE, 0);
|
|
if (received < 0) {
|
|
perror("Failed to recv HTTP/2 frame header");
|
|
return -1;
|
|
}
|
|
if ((size_t)received < HTTP2_FRAME_HEADER_SIZE) {
|
|
return -1;
|
|
}
|
|
|
|
if (http2_decode_frame(frame, header, HTTP2_FRAME_HEADER_SIZE) != 0) return -1;
|
|
|
|
if (frame->length > 0) {
|
|
size_t total_len = HTTP2_FRAME_HEADER_SIZE + frame->length;
|
|
if (conn->read_buffer.capacity < total_len) {
|
|
if (http2_buffer_grow(&conn->read_buffer, total_len) != 0) return -1;
|
|
}
|
|
|
|
received = recv(conn->sockfd, conn->read_buffer.data, frame->length, 0);
|
|
if (received < 0) {
|
|
perror("Failed to recv HTTP/2 frame payload");
|
|
return -1;
|
|
}
|
|
|
|
memcpy(frame->payload, conn->read_buffer.data, received);
|
|
frame->length = (uint32_t)received;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int http2_send_frame(HTTP2_Connection *conn, HTTP2_Frame *frame)
|
|
{
|
|
if (conn == NULL || frame == NULL || conn->sockfd < 0) return -1;
|
|
|
|
size_t total_len = HTTP2_FRAME_HEADER_SIZE + frame->length;
|
|
if (conn->write_buffer.capacity < total_len) {
|
|
if (http2_buffer_grow(&conn->write_buffer, total_len) != 0) return -1;
|
|
}
|
|
|
|
size_t written = http2_format_frame(frame, conn->write_buffer.data);
|
|
ssize_t sent = send(conn->sockfd, conn->write_buffer.data, written, 0);
|
|
if (sent < 0) {
|
|
perror("Failed to send HTTP/2 frame");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int http2_encode_settings(HTTP2_Connection *conn, uint32_t *settings, size_t count)
|
|
{
|
|
if (conn == NULL || settings == NULL) return -1;
|
|
|
|
uint8_t payload[256] = {0};
|
|
size_t payload_len = 0;
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
uint16_t id = (uint16_t)(settings[i * 2]);
|
|
uint32_t value = settings[i * 2 + 1];
|
|
|
|
payload[payload_len++] = (uint8_t)((id >> 8) & 0xFF);
|
|
payload[payload_len++] = (uint8_t)(id & 0xFF);
|
|
payload[payload_len++] = (uint8_t)((value >> 24) & 0xFF);
|
|
payload[payload_len++] = (uint8_t)((value >> 16) & 0xFF);
|
|
payload[payload_len++] = (uint8_t)((value >> 8) & 0xFF);
|
|
payload[payload_len++] = (uint8_t)(value & 0xFF);
|
|
|
|
if (id > 0 && id <= HTTP2_SETTINGS_MAX_PARAM_ID) {
|
|
conn->local_settings[id] = value;
|
|
}
|
|
}
|
|
|
|
HTTP2_Frame frame = {0};
|
|
http2_encode_frame(&frame, HTTP2_FRAME_SETTINGS, 0, 0, payload, payload_len);
|
|
|
|
return http2_send_frame(conn, &frame);
|
|
}
|
|
|
|
int http2_decode_settings(const HTTP2_Frame *frame, uint32_t *settings, size_t *count)
|
|
{
|
|
if (frame == NULL || settings == NULL || count == NULL) return -1;
|
|
if (frame->type != HTTP2_FRAME_SETTINGS) return -1;
|
|
|
|
*count = 0;
|
|
size_t i = 0;
|
|
while (i < frame->length) {
|
|
if (i + 5 >= frame->length) break;
|
|
|
|
uint16_t id = ((uint16_t)frame->payload[i] << 8) | frame->payload[i + 1];
|
|
uint32_t value = ((uint32_t)frame->payload[i + 2] << 24) |
|
|
((uint32_t)frame->payload[i + 3] << 16) |
|
|
((uint32_t)frame->payload[i + 4] << 8) |
|
|
frame->payload[i + 5];
|
|
|
|
settings[(*count) * 2] = id;
|
|
settings[(*count) * 2 + 1] = value;
|
|
(*count)++;
|
|
i += 6;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int http2_encode_priority(HTTP2_Priority_Spec *prio, uint8_t *out, size_t *out_len)
|
|
{
|
|
if (prio == NULL || out == NULL || out_len == NULL) return -1;
|
|
|
|
uint8_t *p = out;
|
|
*p++ = (uint8_t)((prio->stream_dependency >> 24) & 0xFF);
|
|
*p++ = (uint8_t)((prio->stream_dependency >> 16) & 0xFF);
|
|
*p++ = (uint8_t)((prio->stream_dependency >> 8) & 0xFF);
|
|
*p++ = (uint8_t)(prio->stream_dependency & 0xFF);
|
|
*p++ = (prio->exclusive << 7) | prio->weight;
|
|
*out_len = 5;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int http2_decode_priority(const uint8_t *data, size_t len, HTTP2_Priority_Spec *prio)
|
|
{
|
|
if (data == NULL || prio == NULL || len < 5) return -1;
|
|
|
|
prio->stream_dependency = ((uint32_t)data[0] << 24) | ((uint32_t)data[1] << 16) |
|
|
((uint32_t)data[2] << 8) | (uint32_t)data[3];
|
|
prio->exclusive = (data[4] >> 7) & 0x1;
|
|
prio->weight = (data[4] & 0xFF) + 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int http2_hpack_encode_init(HTTP2_Encoder_Context *ctx, uint32_t max_size)
|
|
{
|
|
if (ctx == NULL) return -1;
|
|
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
ctx->max_size = max_size;
|
|
ctx->current_size = 0;
|
|
ctx->dynamic_table.cap = 128;
|
|
ctx->dynamic_table.data = malloc(ctx->dynamic_table.cap);
|
|
if (ctx->dynamic_table.data == NULL) return -1;
|
|
ctx->dynamic_table.len = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t http2_hpack_table_size(uint32_t entries)
|
|
{
|
|
return 32 + entries * 32;
|
|
}
|
|
|
|
static int http2_hpack_evict(HTTP2_Encoder_Context *ctx, uint32_t amount)
|
|
{
|
|
if (ctx == NULL) return -1;
|
|
|
|
while (ctx->current_size + amount > ctx->max_size && ctx->dynamic_table.len > 0) {
|
|
ctx->current_size -= 32;
|
|
ctx->dynamic_table.len--;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int http2_hpack_encode_indexed(uint8_t idx, uint8_t *out, size_t *out_len)
|
|
{
|
|
if (out == NULL || out_len == NULL) return -1;
|
|
|
|
if (idx < 61) {
|
|
out[0] = 0x80 | idx;
|
|
*out_len = 1;
|
|
} else {
|
|
out[0] = 0x80 | 0x3F;
|
|
out[1] = (uint8_t)(idx - 60);
|
|
*out_len = 2;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int http2_hpack_encode_literal(const uint8_t *name, size_t name_len, const uint8_t *value, size_t value_len, uint8_t idx_mod, uint8_t *out, size_t *out_len)
|
|
{
|
|
if (name == NULL || value == NULL || out == NULL || out_len == NULL) return -1;
|
|
|
|
size_t pos = 0;
|
|
out[pos++] = idx_mod;
|
|
|
|
if (name_len < 61) {
|
|
out[pos++] = (uint8_t)name_len;
|
|
} else {
|
|
out[pos++] = 0x3F;
|
|
out[pos++] = (uint8_t)(name_len - 60);
|
|
}
|
|
memcpy(out + pos, name, name_len);
|
|
pos += name_len;
|
|
|
|
if (value_len < 61) {
|
|
out[pos++] = (uint8_t)value_len;
|
|
} else {
|
|
out[pos++] = 0x3F;
|
|
out[pos++] = (uint8_t)(value_len - 60);
|
|
}
|
|
memcpy(out + pos, value, value_len);
|
|
pos += value_len;
|
|
|
|
*out_len = pos;
|
|
return 0;
|
|
}
|
|
|
|
int http2_hpack_encode(HTTP2_Encoder_Context *ctx, const char **headers, size_t headers_len, uint8_t *out, size_t *out_len)
|
|
{
|
|
if (ctx == NULL || headers == NULL || out == NULL || out_len == NULL) return -1;
|
|
|
|
size_t pos = 0;
|
|
for (size_t i = 0; i < headers_len; i += 2) {
|
|
const char *name = headers[i];
|
|
const char *value = headers[i + 1];
|
|
size_t name_len = strlen(name);
|
|
size_t value_len = strlen(value);
|
|
|
|
if (http2_hpack_encode_literal((const uint8_t *)name, name_len, (const uint8_t *)value, value_len, 0x40, out + pos, &pos) != 0) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
*out_len = pos;
|
|
return 0;
|
|
}
|
|
|
|
int http2_hpack_decode_init(HTTP2_Decoder_Context *ctx, uint32_t max_size)
|
|
{
|
|
if (ctx == NULL) return -1;
|
|
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
ctx->max_size = max_size;
|
|
ctx->current_size = 0;
|
|
ctx->dynamic_table.cap = 128;
|
|
ctx->dynamic_table.data = malloc(ctx->dynamic_table.cap);
|
|
if (ctx->dynamic_table.data == NULL) return -1;
|
|
ctx->dynamic_table.len = 0;
|
|
ctx->mirror = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void http2_hpack_encode_free(HTTP2_Encoder_Context *ctx)
|
|
{
|
|
if (ctx == NULL) return;
|
|
free(ctx->dynamic_table.data);
|
|
ctx->dynamic_table.data = NULL;
|
|
ctx->dynamic_table.len = 0;
|
|
}
|
|
|
|
void http2_hpack_decode_free(HTTP2_Decoder_Context *ctx)
|
|
{
|
|
if (ctx == NULL) return;
|
|
free(ctx->dynamic_table.data);
|
|
ctx->dynamic_table.data = NULL;
|
|
ctx->dynamic_table.len = 0;
|
|
}
|
|
|
|
int http2_hpack_decode(HTTP2_Decoder_Context *ctx, const uint8_t *data, size_t len, char **headers, size_t *headers_len)
|
|
{
|
|
if (ctx == NULL || data == NULL || headers == NULL || headers_len == NULL) return -1;
|
|
|
|
*headers_len = 0;
|
|
size_t pos = 0;
|
|
|
|
while (pos < len) {
|
|
uint8_t byte = data[pos];
|
|
|
|
if ((byte & 0x80) != 0) {
|
|
pos++;
|
|
continue;
|
|
}
|
|
|
|
if ((byte & 0x40) != 0) {
|
|
pos++;
|
|
continue;
|
|
}
|
|
|
|
pos++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int http2_static_table_index(const char *name, size_t name_len)
|
|
{
|
|
static const char *names[] = {
|
|
":authority", ":method", ":method", ":path", ":path", ":scheme", ":status",
|
|
":status", ":status", ":status", ":status", ":status", ":status", ":status", ":status",
|
|
"accept-encoding", "accept-language", "accept-ranges", "age", "allow", "authorization",
|
|
"cache-control", "content-encoding", "content-language", "content-length",
|
|
"content-location", "content-range", "content-type", "cookie", "date", "etag",
|
|
"expect", "expires", "from", "host", "if-match", "if-modified-since",
|
|
"if-none-match", "if-range", "if-unmodified-since", "last-modified", "link",
|
|
"location", "max-forwards", "proxy-authenticate", "proxy-authorization", "range",
|
|
"referer", "refresh", "retry-after", "server", "set-cookie", "strict-transport-security",
|
|
"transfer-encoding", "user-agent", "vary", "via", "www-authenticate"
|
|
};
|
|
|
|
for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) {
|
|
if (strncmp(name, names[i], name_len) == 0) {
|
|
return (int)(i + 1);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int http2_encode_headers(HTTP2_Connection *conn, const char **headers, size_t headers_len, uint8_t *out, size_t *out_len)
|
|
{
|
|
if (conn == NULL || headers == NULL || out == NULL || out_len == NULL) return -1;
|
|
|
|
return http2_hpack_encode(&conn->encoder, headers, headers_len, out, out_len);
|
|
}
|
|
|
|
int http2_decode_headers(HTTP2_Connection *conn, const uint8_t *data, size_t len, char **headers, size_t *headers_len)
|
|
{
|
|
if (conn == NULL || data == NULL || headers == NULL || headers_len == NULL) return -1;
|
|
|
|
return http2_hpack_decode(&conn->decoder, data, len, headers, headers_len);
|
|
}
|
|
|
|
int http2_encode_priority_frame(uint32_t stream_id, HTTP2_Priority_Spec *prio, uint8_t *out, size_t *out_len)
|
|
{
|
|
if (prio == NULL || out == NULL || out_len == NULL) return -1;
|
|
if (stream_id == 0 || (stream_id & 1) == 0) return -1;
|
|
|
|
HTTP2_Frame frame = {0};
|
|
http2_encode_priority(prio, frame.payload, out_len);
|
|
http2_encode_frame(&frame, HTTP2_FRAME_PRIORITY, stream_id, 0, frame.payload, *out_len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int http2_encode_rst_stream(uint32_t stream_id, HTTP2_Error_Code error, uint8_t *out, size_t *out_len)
|
|
{
|
|
if (out == NULL || out_len == NULL) return -1;
|
|
if (stream_id == 0 || (stream_id & 1) == 0) return -1;
|
|
|
|
out[0] = (uint8_t)((error >> 24) & 0xFF);
|
|
out[1] = (uint8_t)((error >> 16) & 0xFF);
|
|
out[2] = (uint8_t)((error >> 8) & 0xFF);
|
|
out[3] = (uint8_t)(error & 0xFF);
|
|
*out_len = 4;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int http2_encode_ping(HTTP2_Connection *conn, uint8_t *data, uint8_t flags, uint8_t *out, size_t *out_len)
|
|
{
|
|
if (conn == NULL || data == NULL || out == NULL || out_len == NULL) return -1;
|
|
|
|
HTTP2_Frame frame = {0};
|
|
memcpy(frame.payload, data, 8);
|
|
http2_encode_frame(&frame, HTTP2_FRAME_PING, 0, flags, frame.payload, 8);
|
|
*out_len = HTTP2_FRAME_HEADER_SIZE + 8;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int http2_encode_goaway(uint32_t last_stream_id, HTTP2_Error_Code error, const uint8_t *additional_debug, size_t debug_len, uint8_t *out, size_t *out_len)
|
|
{
|
|
if (out == NULL || out_len == NULL) return -1;
|
|
if (last_stream_id != 0 && (last_stream_id & 1) == 0) return -1;
|
|
|
|
uint8_t *p = out;
|
|
*p++ = (uint8_t)((last_stream_id >> 24) & 0xFF);
|
|
*p++ = (uint8_t)((last_stream_id >> 16) & 0xFF);
|
|
*p++ = (uint8_t)((last_stream_id >> 8) & 0xFF);
|
|
*p++ = (uint8_t)(last_stream_id & 0xFF);
|
|
*p++ = (uint8_t)((error >> 24) & 0xFF);
|
|
*p++ = (uint8_t)((error >> 16) & 0xFF);
|
|
*p++ = (uint8_t)((error >> 8) & 0xFF);
|
|
*p++ = (uint8_t)(error & 0xFF);
|
|
|
|
if (additional_debug != NULL && debug_len > 0) {
|
|
memcpy(p, additional_debug, debug_len);
|
|
p += debug_len;
|
|
}
|
|
|
|
*out_len = (size_t)(p - out);
|
|
return 0;
|
|
}
|
|
|
|
int http2_encode_window_update(uint32_t stream_id, uint32_t increment, uint8_t *out, size_t *out_len)
|
|
{
|
|
if (out == NULL || out_len == NULL) return -1;
|
|
if (increment == 0 || increment > 2147483647) return -1;
|
|
|
|
out[0] = (uint8_t)((increment >> 24) & 0xFF);
|
|
out[1] = (uint8_t)((increment >> 16) & 0xFF);
|
|
out[2] = (uint8_t)((increment >> 8) & 0xFF);
|
|
out[3] = (uint8_t)(increment & 0xFF);
|
|
*out_len = 4;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int http2_parse_frame_type(const char *name)
|
|
{
|
|
if (name == NULL) return -1;
|
|
|
|
if (strcmp(name, "DATA") == 0) return HTTP2_FRAME_DATA;
|
|
if (strcmp(name, "HEADERS") == 0) return HTTP2_FRAME_HEADERS;
|
|
if (strcmp(name, "PRIORITY") == 0) return HTTP2_FRAME_PRIORITY;
|
|
if (strcmp(name, "RST_STREAM") == 0) return HTTP2_FRAME_RST_STREAM;
|
|
if (strcmp(name, "SETTINGS") == 0) return HTTP2_FRAME_SETTINGS;
|
|
if (strcmp(name, "PUSH_PROMISE") == 0) return HTTP2_FRAME_PUSH_PROMISE;
|
|
if (strcmp(name, "PING") == 0) return HTTP2_FRAME_PING;
|
|
if (strcmp(name, "GOAWAY") == 0) return HTTP2_FRAME_GOAWAY;
|
|
if (strcmp(name, "WINDOW_UPDATE") == 0) return HTTP2_FRAME_WINDOW_UPDATE;
|
|
if (strcmp(name, "CONTINUATION") == 0) return HTTP2_FRAME_CONTINUATION;
|
|
|
|
return -1;
|
|
}
|
|
|
|
const char *http2_frame_type_to_str(HTTP2_Frame_Type type)
|
|
{
|
|
switch (type) {
|
|
case HTTP2_FRAME_DATA: return "DATA";
|
|
case HTTP2_FRAME_HEADERS: return "HEADERS";
|
|
case HTTP2_FRAME_PRIORITY: return "PRIORITY";
|
|
case HTTP2_FRAME_RST_STREAM: return "RST_STREAM";
|
|
case HTTP2_FRAME_SETTINGS: return "SETTINGS";
|
|
case HTTP2_FRAME_PUSH_PROMISE: return "PUSH_PROMISE";
|
|
case HTTP2_FRAME_PING: return "PING";
|
|
case HTTP2_FRAME_GOAWAY: return "GOAWAY";
|
|
case HTTP2_FRAME_WINDOW_UPDATE: return "WINDOW_UPDATE";
|
|
case HTTP2_FRAME_CONTINUATION: return "CONTINUATION";
|
|
default: return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
const char *http2_error_to_str(HTTP2_Error_Code error)
|
|
{
|
|
switch (error) {
|
|
case HTTP2_ERROR_NO_ERROR: return "NO_ERROR";
|
|
case HTTP2_ERROR_PROTOCOL_ERROR: return "PROTOCOL_ERROR";
|
|
case HTTP2_ERROR_INTERNAL_ERROR: return "INTERNAL_ERROR";
|
|
case HTTP2_ERROR_FLOW_CONTROL_ERROR: return "FLOW_CONTROL_ERROR";
|
|
case HTTP2_ERROR_SETTINGS_TIMEOUT: return "SETTINGS_TIMEOUT";
|
|
case HTTP2_ERROR_STREAM_CLOSED: return "STREAM_CLOSED";
|
|
case HTTP2_ERROR_FRAME_SIZE_ERROR: return "FRAME_SIZE_ERROR";
|
|
case HTTP2_ERROR_REFUSED_STREAM: return "REFUSED_STREAM";
|
|
case HTTP2_ERROR_CANCEL: return "CANCEL";
|
|
case HTTP2_ERROR_COMPRESSION_ERROR: return "COMPRESSION_ERROR";
|
|
case HTTP2_ERROR_CONNECT_ERROR: return "CONNECT_ERROR";
|
|
case HTTP2_ERROR_ENHANCE_YOUR_CLAIM: return "ENHANCE_YOUR_CLAIM";
|
|
case HTTP2_ERROR_INADEQUATE_SECURITY: return "INADEQUATE_SECURITY";
|
|
case HTTP2_ERROR_HTTP_1_1_REQUIRED: return "HTTP_1_1_REQUIRED";
|
|
default: return "UNKNOWN";
|
|
}
|
|
} |