pespin has submitted this change. ( https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/43649?usp=email )
Change subject: library/HTTP2_*: Add initial HTTP2 client/server support ......................................................................
library/HTTP2_*: Add initial HTTP2 client/server support
An HTTP2 client is implemented through the HTTP2_Adapter module, while an HTTP2 server is implemented throught the HTTP2_Server_Emulation module.
This is just an initial working solution (already being used clientand server side upcoming SMF_Tests 5G SBI patches), the interface may be changes and extended in the future (eg. we may want to add some intermediate layer using HTTP2_Message in the HTTP2_Adapter instead of writing raw HTTP2_Send/HTTP2_Recv records.)
Change-Id: I3ab440517e925a7c402dd4055c223ef568405adc --- M deps/Makefile A library/HTTP2_Adapter.ttcn A library/HTTP2_CodecPort.ttcn A library/HTTP2_CodecPort_CtrlFunct.ttcn A library/HTTP2_CodecPort_CtrlFunctDef.cc A library/HTTP2_Functions.ttcn A library/HTTP2_Server_Emulation.ttcn A library/HTTP2_Templates.ttcn 8 files changed, 1,122 insertions(+), 0 deletions(-)
Approvals: pespin: Looks good to me, approved Jenkins Builder: Verified osmith: Looks good to me, but someone else must approve laforge: Looks good to me, but someone else must approve
diff --git a/deps/Makefile b/deps/Makefile index f230577..10c343c 100644 --- a/deps/Makefile +++ b/deps/Makefile @@ -16,6 +16,7 @@ titan.ProtocolModules.GRE \ titan.ProtocolModules.GTP_v13.5.0 \ titan.ProtocolModules.GTPv2_v13.7.0 \ + titan.ProtocolModules.HTTP2 \ titan.ProtocolModules.ICMP \ titan.ProtocolModules.ICMPv6 \ titan.ProtocolModules.IP \ @@ -82,6 +83,7 @@ titan.ProtocolModules.GRE_commit= R.2.A-5-g654c446 titan.ProtocolModules.GTP_v13.5.0_commit= 6b769f985eb91bf5a4332f29faa4a043b23ce62e titan.ProtocolModules.GTPv2_v13.7.0_commit= R.2.A-5-g85cb124 +titan.ProtocolModules.HTTP2_commit= 297ac60099b567c8819ad349e22f185ec89a9c58 titan.ProtocolModules.ICMP_commit= e49d9fb9f7de637b4bf4803dc6b6e911a8661640 titan.ProtocolModules.ICMPv6_commit= 46f4d9b6e1e3c794294a92588401a81e4881dd27 titan.ProtocolModules.IP_commit= 1be86705f39ae38f3c04b2109806ee20d25e91d0 diff --git a/library/HTTP2_Adapter.ttcn b/library/HTTP2_Adapter.ttcn new file mode 100644 index 0000000..7892618 --- /dev/null +++ b/library/HTTP2_Adapter.ttcn @@ -0,0 +1,101 @@ +module HTTP2_Adapter { + +/* HTTP2 Adapter component + * (C) 2019 by Harald Welte laforge@gnumonks.org + * (C) 2026 by sysmocom s.f.m.c. GmbH info@sysmocom.de + * All rights reserved. + * + * Released under the terms of GNU General Public License, Version 2 or + * (at your option) any later version. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + */ + +import from HTTP2_Types all; +import from HTTP2_CodecPort all; +import from HTTP2_CodecPort_CtrlFunct all; +import from IPL4asp_Types all; +import from IPL4asp_PortType all; +import from Socket_API_Definitions all; + +import from HTTP2_Templates all; + +import from Native_Functions all; +import from Misc_Helpers all; + +type component HTTP2_CT { + port HTTP2_CODEC_PT HTTP2; + /* double underscore to have "g_pars" available on components extending this one: */ + var template (omit) HTTP2_Adapter_Params g_http2_pars; + + /* Connection identifier of the client / server itself */ + var IPL4asp_Types.ConnectionId g_self_conn_id := -1; +}; + +type record HTTP2_Adapter_Params { + charstring local_host, + integer local_port, + charstring remote_host, + integer remote_port, + boolean use_ssl +}; + +template (value) HTTP2_Adapter_Params +ts_HTTP2_Adapter_Params(template (value) charstring local_host, + template (value) integer local_port, + template (value) charstring remote_host, + template (value) integer remote_port, + template (value) boolean use_ssl) := { + local_host := local_host, + local_port := local_port, + remote_host := remote_host, + remote_port := remote_port, + use_ssl := use_ssl +} + +function f_http2_adapter_init() runs on HTTP2_CT { + map(self:HTTP2, system:HTTP2_CODEC_PT); +} + +private function f_http2_adapter_getMsgLen(in octetstring pl_stream, + inout Socket_API_Definitions.ro_integer args) +return integer { + var HTTP2_ro_integer args_unused := {}; + return f_HTTP2_msglen(pl_stream, args_unused); +} + + +private function f_set_tcp_segmentation() +runs on HTTP2_CT { + /* Set function for dissecting the binary stream into packets */ + var f_IPL4_getMsgLen vl_f := refers(f_http2_adapter_getMsgLen); + HTTP2_CodecPort_CtrlFunct.f_IPL4_setGetMsgLen(HTTP2, g_self_conn_id, vl_f, {}); +} + +/* Function to use to connect as client to a remote IPA Server */ +function f_http2_adapter_connect(charstring remote_host, IPL4asp_Types.PortNumber remote_port, + charstring local_host, IPL4asp_Types.PortNumber local_port, boolean use_ssl := false) +runs on HTTP2_CT { + var IPL4asp_Types.Result res; + res := HTTP2_CodecPort_CtrlFunct.f_IPL4_connect(HTTP2, remote_host, remote_port, + local_host, local_port, 0, { tcp:={} }); + if (not ispresent(res.connId)) { + setverdict(fail, "Could not connect HTTP2 socket from ", local_host, " port ", local_port, + " to ", remote_host, " port ", remote_port, "; check your configuration"); + mtc.stop; + } + g_self_conn_id := res.connId; + g_http2_pars := ts_HTTP2_Adapter_Params(remote_host, remote_port, local_host, local_port, use_ssl); + + f_set_tcp_segmentation(); + HTTP2.send(ts_HTTP2_Send_magic(g_self_conn_id)); +} + +function f_http2_adapter_send(template (value) HTTP2_Frame http2_frame) +runs on HTTP2_CT +{ + HTTP2.send(ts_HTTP2_Send(g_self_conn_id, http2_frame)); +} + +} diff --git a/library/HTTP2_CodecPort.ttcn b/library/HTTP2_CodecPort.ttcn new file mode 100644 index 0000000..d36ff96 --- /dev/null +++ b/library/HTTP2_CodecPort.ttcn @@ -0,0 +1,108 @@ +module HTTP2_CodecPort { + +/* Simple HTTP2 Codec Port, translating between raw TCP octetstring payload + * towards the IPL4asp port provider, and HTTP2 primitives + * which carry the decoded HTTP2 data types as payload. + * + * (C) 2017-2018 by Harald Welte laforge@gnumonks.org + * All rights reserved. + * + * Released under the terms of GNU General Public License, Version 2 or + * (at your option) any later version. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + + import from IPL4asp_PortType all; + import from IPL4asp_Types all; + import from HTTP2_Types all; + + type record HTTP2_Recv { + ConnectionId connId, + HTTP2_Frame frame optional, + HTTP2_decoder_error_descr pl_error_descr optional + } + + template (present) HTTP2_Recv tr_HTTP2_Recv(template (present) ConnectionId connId := ?, + template HTTP2_Frame frame := *, + template HTTP2_decoder_error_descr pl_error_descr := *) := { + connId := connId, + frame := frame, + pl_error_descr := pl_error_descr + } + + type record HTTP2_Send { + ConnectionId connId, + HTTP2_Frame frame + }; + + template (value) HTTP2_Send + ts_HTTP2_Send(template (value) ConnectionId connId, + template (value) HTTP2_Frame frame) := { + connId := connId, + frame := frame + } + + + type record HTTP2_Send_charstring { + ConnectionId connId, + charstring data + }; + + template (value) HTTP2_Send_charstring + ts_HTTP2_Send_charstring(template (value) ConnectionId connId, + template (value) charstring data) := { + connId := connId, + data := data + } + + const charstring c_HTTP2_magic := "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; + + template (value) HTTP2_Send_charstring + ts_HTTP2_Send_magic(template (value) ConnectionId connId) := + ts_HTTP2_Send_charstring(connId, c_HTTP2_magic); + + private function IPL4_to_HTTP2_Recv(in ASP_RecvFrom pin, out HTTP2_Recv pout) { + var integer rc; + pout.connId := pin.connId; + if (lengthof(pin.msg) == lengthof(HTTP2_connection_preface) and + pin.msg == HTTP2_connection_preface) { + log(pin.connId, ": Rx HTTP2 preface"); + pout.pl_error_descr := omit; + pout.frame := omit; + } else { + rc := f_HTTP2_decode_frame(pin.msg, pout.frame, pout.pl_error_descr); + if (rc == 0) { + pout.pl_error_descr := omit; + } else { + pout.frame := omit; + } + log(pin.connId, ": f_HTTP2_decode_frame(", pin.msg, ") returned ", rc); + } + } with { extension "prototype(fast)" } + + private function HTTP2_to_IPL4_Send(in HTTP2_Send pin, out ASP_Send pout) { + pout.connId := pin.connId; + pout.proto := { tcp := {} }; + pout.msg := f_HTTP2_encode_frame(pin.frame); + } with { extension "prototype(fast)" } + + private function HTTP2_Send_charstring_to_IPL4_Send(in HTTP2_Send_charstring pin, out ASP_Send pout) { + pout.connId := pin.connId; + pout.proto := { tcp := {} }; + pout.msg := char2oct(pin.data); + } with { extension "prototype(fast)" } + + type port HTTP2_CODEC_PT message { + out HTTP2_Send, HTTP2_Send_charstring; + in HTTP2_Recv, + ASP_ConnId_ReadyToRelease, + ASP_Event; + } with { extension "user IPL4asp_PT + out(HTTP2_Send -> ASP_Send: function(HTTP2_to_IPL4_Send); + HTTP2_Send_charstring -> ASP_Send: function(HTTP2_Send_charstring_to_IPL4_Send)) + in(ASP_RecvFrom -> HTTP2_Recv: function(IPL4_to_HTTP2_Recv); + ASP_ConnId_ReadyToRelease -> ASP_ConnId_ReadyToRelease: simple; + ASP_Event -> ASP_Event: simple)" + } +} diff --git a/library/HTTP2_CodecPort_CtrlFunct.ttcn b/library/HTTP2_CodecPort_CtrlFunct.ttcn new file mode 100644 index 0000000..1a48043 --- /dev/null +++ b/library/HTTP2_CodecPort_CtrlFunct.ttcn @@ -0,0 +1,51 @@ +module HTTP2_CodecPort_CtrlFunct { + +import from HTTP2_CodecPort all; +import from IPL4asp_Types all; + +external function f_IPL4_listen( + inout HTTP2_CODEC_PT portRef, + in HostName locName, + in PortNumber locPort, + in ProtoTuple proto, + in OptionList options := {} +) return Result; + +external function f_IPL4_connect( + inout HTTP2_CODEC_PT portRef, + in HostName remName, + in PortNumber remPort, + in HostName locName, + in PortNumber locPort, + in ConnectionId connId, + in ProtoTuple proto, + in OptionList options := {} +) return Result; + +external function f_IPL4_close( + inout HTTP2_CODEC_PT portRef, + in ConnectionId id, + in ProtoTuple proto := { unspecified := {} } +) return Result; + +external function f_IPL4_setUserData( + inout HTTP2_CODEC_PT portRef, + in ConnectionId id, + in UserData userData +) return Result; + +external function f_IPL4_getUserData( + inout HTTP2_CODEC_PT portRef, + in ConnectionId id, + out UserData userData +) return Result; + +external function f_IPL4_setGetMsgLen( + inout HTTP2_CODEC_PT portRef, + in ConnectionId id, + inout f_IPL4_getMsgLen f, + in ro_integer msgLenArgs +); + +} + diff --git a/library/HTTP2_CodecPort_CtrlFunctDef.cc b/library/HTTP2_CodecPort_CtrlFunctDef.cc new file mode 100644 index 0000000..7059d28 --- /dev/null +++ b/library/HTTP2_CodecPort_CtrlFunctDef.cc @@ -0,0 +1,66 @@ +#include "IPL4asp_PortType.hh" +#include "HTTP2_CodecPort.hh" +#include "IPL4asp_PT.hh" + +namespace HTTP2__CodecPort__CtrlFunct { + +IPL4asp__Types::Result f__IPL4__listen( + HTTP2__CodecPort::HTTP2__CODEC__PT& portRef, + const IPL4asp__Types::HostName& locName, + const IPL4asp__Types::PortNumber& locPort, + const IPL4asp__Types::ProtoTuple& proto, + const IPL4asp__Types::OptionList& options) +{ + return f__IPL4__PROVIDER__listen(portRef, locName, locPort, proto, options); +} + +IPL4asp__Types::Result f__IPL4__connect( + HTTP2__CodecPort::HTTP2__CODEC__PT& portRef, + const IPL4asp__Types::HostName& remName, + const IPL4asp__Types::PortNumber& remPort, + const IPL4asp__Types::HostName& locName, + const IPL4asp__Types::PortNumber& locPort, + const IPL4asp__Types::ConnectionId& connId, + const IPL4asp__Types::ProtoTuple& proto, + const IPL4asp__Types::OptionList& options) +{ + return f__IPL4__PROVIDER__connect(portRef, remName, remPort, + locName, locPort, connId, proto, options); +} + +IPL4asp__Types::Result f__IPL4__close( + HTTP2__CodecPort::HTTP2__CODEC__PT& portRef, + const IPL4asp__Types::ConnectionId& connId, + const IPL4asp__Types::ProtoTuple& proto) +{ + return f__IPL4__PROVIDER__close(portRef, connId, proto); +} + +IPL4asp__Types::Result f__IPL4__setUserData( + HTTP2__CodecPort::HTTP2__CODEC__PT& portRef, + const IPL4asp__Types::ConnectionId& connId, + const IPL4asp__Types::UserData& userData) +{ + return f__IPL4__PROVIDER__setUserData(portRef, connId, userData); +} + +IPL4asp__Types::Result f__IPL4__getUserData( + HTTP2__CodecPort::HTTP2__CODEC__PT& portRef, + const IPL4asp__Types::ConnectionId& connId, + IPL4asp__Types::UserData& userData) +{ + return f__IPL4__PROVIDER__getUserData(portRef, connId, userData); +} + +void f__IPL4__setGetMsgLen( + HTTP2__CodecPort::HTTP2__CODEC__PT& portRef, + const IPL4asp__Types::ConnectionId& connId, + Socket__API__Definitions::f__getMsgLen& f, + const Socket__API__Definitions::ro__integer& msgLenArgs) +{ + return f__IPL4__PROVIDER__setGetMsgLen(portRef, connId, f, msgLenArgs); +} + + +} + diff --git a/library/HTTP2_Functions.ttcn b/library/HTTP2_Functions.ttcn new file mode 100644 index 0000000..95e5ea1 --- /dev/null +++ b/library/HTTP2_Functions.ttcn @@ -0,0 +1,32 @@ +module HTTP2_Functions { + +import from HTTP2_Types all; +import from HTTP2_Templates all; + +import from TCCConversion_Functions all; //f_strstr() + +function f_HTTP2_header_list_find_header(HTTP2_header_list headers, charstring header_name) +return template (omit) charstring { + for (var integer i := 0; i < lengthof(headers); i := i + 1) { + if (headers[i].header_name == header_name) { + return headers[i].header_value; + } + } + return omit; +} + +function f_HTTP2_header_content_type_multipart_val_get_boundary(charstring content_type_val) +return charstring { + var charstring boundary; + var integer pos := f_strstr(content_type_val, "boundary="", 0); + if (pos < 0) { + return ""; + } + pos := pos + lengthof("boundary=""); + // -1: Remove trailing """ + boundary := substr(content_type_val, pos, lengthof(content_type_val) - pos - 1); + return boundary; +} + + +} diff --git a/library/HTTP2_Server_Emulation.ttcn b/library/HTTP2_Server_Emulation.ttcn new file mode 100644 index 0000000..779d7f3 --- /dev/null +++ b/library/HTTP2_Server_Emulation.ttcn @@ -0,0 +1,526 @@ +/* HTTP2 Emulation in TTCN-3 + * + * (C) 2026 by sysmocom - s.f.m.c. GmbH info@sysmocom.de + * + * Released under the terms of GNU General Public License, Version 2 or + * (at your option) any later version. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This module implements a HTTP/HTTPs 2.0 server, + */ + +module HTTP2_Server_Emulation { + +import from HTTP2_Types all; +import from HTTP2_CodecPort all; +import from HTTP2_CodecPort_CtrlFunct all; +import from IPL4asp_Types all; +import from IPL4asp_PortType all; +import from Socket_API_Definitions all; + +import from HTTP2_Templates all; + +import from Native_Functions all; +import from Misc_Helpers all; + + +/*************** + * HTTP2_Msg + **************/ + +type record HTTP2_Msg { + IPL4asp_Types.ConnectionId conn_id, + integer stream_id, + HTTP2_header_block hb, + HTTP2_Data_frame body optional +} + +template (present) HTTP2_Msg +tr_HTTP2_Msg(template (present) IPL4asp_Types.ConnectionId conn_id := ?, + template (present) integer stream_id := ?, + template (present) HTTP2_header_block hb := ?, + template HTTP2_Data_frame body := *) := { + conn_id := conn_id, + stream_id := stream_id, + hb := hb, + body := body +} + +template (value) HTTP2_Msg +ts_HTTP2_Msg(template (value) IPL4asp_Types.ConnectionId conn_id, + template (value) integer stream_id, + template (value) HTTP2_header_block hb, + template (omit) HTTP2_Data_frame body := omit) := { + conn_id := conn_id, + stream_id := stream_id, + hb := hb, + body := body +} + +template (value) HTTP2_Msg +ts_HTTP2_Msg_Status(template (value) IPL4asp_Types.ConnectionId conn_id, + template (value) integer stream_id, + template (value) integer status, + template (omit) HTTP2_header_list headers := omit, + template (omit) HTTP2_Data_frame body := omit) := +ts_HTTP2_Msg(conn_id, + stream_id, + hb := ts_HTTP2_header_block(pseudo_headers := ts_HTTP2_pseudo_headers(status := status), + headers := headers), + body := body + ); + +template (value) HTTP2_Msg +ts_HTTP2_Msg_200_OK(template (value) IPL4asp_Types.ConnectionId conn_id, + template (value) integer stream_id, + template (omit) HTTP2_header_list headers := omit, + template (omit) HTTP2_Data_frame body := omit) := +ts_HTTP2_Msg_Status(conn_id, stream_id, 200, headers, body); + +template (value) HTTP2_Msg +ts_HTTP2_Msg_201_Created(template (value) IPL4asp_Types.ConnectionId conn_id, + template (value) integer stream_id, + template (omit) HTTP2_header_list headers := omit, + template (omit) HTTP2_Data_frame body := omit) := +ts_HTTP2_Msg_Status(conn_id, stream_id, 201, headers, body); + + +function f_HTTP2_Msg_append_hdr_content_length(inout HTTP2_Msg http2_msg) +{ + if (not ispresent(http2_msg.body)) { + return; + } + var integer content_length := lengthof(http2_msg.body.data); /* TODO: http2_msg.body.padding ? */ + http2_msg.hb.headers := http2_msg.hb.headers & + { valueof(ts_HTTP2_Hdr_content_length(int2str(content_length))) }; +} + +/********************* + * HTTP2_Server_Conn + ********************/ +type enumerated HTTP2_Server_Conn_State { + HTTP2_SERVER_CONN_ST_UNUSED, + HTTP2_SERVER_CONN_ST_WAIT_PREFACE, + HTTP2_SERVER_CONN_ST_WAIT_SETTINGS, + HTTP2_SERVER_CONN_ST_WAIT_HEADER, + HTTP2_SERVER_CONN_ST_WAIT_BODY +} + +/* An HTTP2 connection established against the HTTP2 Server */ +type record HTTP2_Server_Conn { + IPL4asp_Types.ConnectionId conn_id, + HTTP2_Server_Conn_State state, + HTTP2_header_block rx_hb optional /* Contains received header while in HTTP2_SERVER_CONN_ST_WAIT_BODY */ +} + +private function f_HTTP2_Server_Conn_init(inout HTTP2_Server_Conn conn) +{ + conn := { + conn_id := -1, + state := HTTP2_SERVER_CONN_ST_UNUSED, + rx_hb := omit + }; +} + +private function f_HTTP2_Server_Conn_str(in HTTP2_Server_Conn conn) return charstring { + return "HTTP2_Server_Conn(" & int2str(conn.conn_id) & "," & log2str(conn.state) & ")"; +} + +private function f_HTTP2_Server_Conn_st_change(inout HTTP2_Server_Conn conn, HTTP2_Server_Conn_State state) +{ + log(f_HTTP2_Server_Conn_str(conn), ": state change to ", state); + conn.state := state +} + +private function f_HTTP2_Server_Conn_comp_decode(inout HTTP2_Server_Conn conn, octetstring header_block_fragment) +return HTTP2_header_block +{ + var HTTP2_header_block pl_hblock; + var HTTP2_comp_context ctx; + var integer rc; + + ctx := HTTP2_comp_context_init(); + rc := HTTP2_comp_context_decode(ctx, pl_hblock, header_block_fragment); + HTTP2_comp_context_free(ctx); + if (rc != 0) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str(f_HTTP2_Server_Conn_str(conn), + ": HTTP2_comp_context_decode returned ", rc)); + } + + return pl_hblock; +} + +private function f_HTTP2_Server_Conn_comp_encode(inout HTTP2_Server_Conn conn, HTTP2_header_block hb) +return octetstring +{ + var octetstring pl_frame_data; + var HTTP2_comp_context ctx; + var integer rc; + + ctx := HTTP2_comp_context_init(); + rc := HTTP2_comp_context_encode(ctx, hb, pl_frame_data); + HTTP2_comp_context_free(ctx); + if (rc != 0) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str(f_HTTP2_Server_Conn_str(conn), + ": HTTP2_comp_context_encode returned ", rc)); + } + + return pl_frame_data; +} + +/*********************************************************************** + * Main Emulation Component (HTTP2 Server) + ***********************************************************************/ + +type record Http2ServerEmulationCfg { + charstring http2_bind_ip, + integer http2_bind_port +}; + +type component HTTP2_Server_Emulation_CT { + /* Communication with underlying HTTP2 CodecPort */ + port HTTP2_CODEC_PT HTTP2; + + /* Communication with Clients */ + port HTTP2_CONN_PT CONN; + port HTTP2_CONN_PROC_PT CONN_PROC; + + /* Configuration by the user */ + var Http2ServerEmulationCfg g_http2_cfg; + + /* Connection identifier of the server itself */ + var IPL4asp_Types.ConnectionId g_self_conn_id := -1; + var HTTP2_Server_Conn conn_table[16]; + var HTTP2_ConnHdlr vc_ConnHdlr_table[16]; +}; + +private function f_conn_table_init() +runs on HTTP2_Server_Emulation_CT { + var integer i; + for (i := 0; i < sizeof(conn_table); i := i + 1) { + f_HTTP2_Server_Conn_init(conn_table[i]); + } +} + +private function f_conn_table_find_by_conn_id(IPL4asp_Types.ConnectionId conn_id) +runs on HTTP2_Server_Emulation_CT return integer { + var integer i; + for (i := 0; i < sizeof(conn_table); i := i + 1) { + if (conn_table[i].conn_id == conn_id) { + return i; + } + } + return -1; +} + +private function f_conn_table_find_free() +runs on HTTP2_Server_Emulation_CT return integer { + return f_conn_table_find_by_conn_id(-1); +} + +private function f_vc_ConnHdlr_table_add(HTTP2_ConnHdlr vc_conn) +runs on HTTP2_Server_Emulation_CT { + var integer i; + for (i := 0; i < sizeof(vc_ConnHdlr_table); i := i + 1) { + if (not isbound(vc_ConnHdlr_table[i])) { + vc_ConnHdlr_table[i] := vc_conn; + return; + } + } + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("No Space in vc_ConnHdlr_table[i] for ", vc_conn)); +} + +private function f_http2_adapter_getMsgLen(in octetstring pl_stream, + inout Socket_API_Definitions.ro_integer args) +return integer { + var HTTP2_ro_integer args_unused := {}; + return f_HTTP2_msglen(pl_stream, args_unused); +} + + +private function f_set_tcp_segmentation() +runs on HTTP2_Server_Emulation_CT { + /* Set function for dissecting the binary stream into packets */ + var f_IPL4_getMsgLen vl_f := refers(f_http2_adapter_getMsgLen); + HTTP2_CodecPort_CtrlFunct.f_IPL4_setGetMsgLen(HTTP2, g_self_conn_id, vl_f, {}); +} + +/* Function to use to connect as client to a remote HTTP2 Server */ +function f_bind(charstring local_host, IPL4asp_Types.PortNumber local_port) +runs on HTTP2_Server_Emulation_CT { + var IPL4asp_Types.Result res; + res := HTTP2_CodecPort_CtrlFunct.f_IPL4_listen(HTTP2, local_host, local_port, { tcp:={} }); + if (not ispresent(res.connId)) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Could not listen HTTP2 socket ", + local_host, ":", local_port, + ", check your configuration")); + } + g_self_conn_id := res.connId; +} + +private function f_init(Http2ServerEmulationCfg cfg) +runs on HTTP2_Server_Emulation_CT { + + g_http2_cfg := cfg; + + f_conn_table_init(); + + /* Start HTTP2 server */ + map(self:HTTP2, system:HTTP2_CODEC_PT); + f_bind(g_http2_cfg.http2_bind_ip, g_http2_cfg.http2_bind_port); + + f_set_tcp_segmentation(); +} + +/* Forward request to all registered clients */ +private function forward_http2_msg(HTTP2_Msg http2_msg) +runs on HTTP2_Server_Emulation_CT { + + for (var integer i := 0; i < sizeof(vc_ConnHdlr_table); i := i + 1) { + if (isbound(vc_ConnHdlr_table[i])) { + CONN.send(http2_msg) to vc_ConnHdlr_table[i]; + } + } +} + +private function f_HTTP2_Server_Conn_tx(inout HTTP2_Server_Conn conn, template (value) HTTP2_Frame http2_frame) +runs on HTTP2_Server_Emulation_CT { + var HTTP2_Send http2_tx; + http2_tx := valueof(ts_HTTP2_Send(conn.conn_id, valueof(http2_frame))); + HTTP2.send(http2_tx); +} + +private function f_HTTP2_Server_Conn_rx_http2(inout HTTP2_Server_Conn conn, HTTP2_Recv http2_rx) +runs on HTTP2_Server_Emulation_CT { + var template (value) HTTP2_Msg http2_msg; + var template (value) HTTP2_Frame http2_frame; + + if (ispresent(http2_rx.pl_error_descr)) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str(f_HTTP2_Server_Conn_str(conn), ": Rx HTTP2 decode error: ", http2_rx)); + } + + select (conn.state) { + case (HTTP2_SERVER_CONN_ST_UNUSED) { // created + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str(f_HTTP2_Server_Conn_str(conn), ": Rx in unexpected state: ", http2_rx)); + } + case (HTTP2_SERVER_CONN_ST_WAIT_PREFACE) { + /* We expect to receive an empty frame, signalled by HTTP2_Codec_port meaning the magic/preface was received. */ + if (ispresent(http2_rx.frame) or ispresent(http2_rx.pl_error_descr)) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str(conn, ": Rx in unexpected state: ", http2_rx)); + } + var template (value) HTTP2_Setting_list settings := { + ts_HTTP2_Setting_MAX_FRAME_SIZE(1048576), + ts_HTTP2_Setting_MAX_CONCURRENT_STREAMS(250), + ts_HTTP2_Setting_MAX_HEADER_LIST_SIZE(10485760), + ts_HTTP2_Setting_INITIAL_WINDOW_SIZE(1048576) + }; + http2_frame := ts_HTTP2_Frame_Settings(settings := settings); + f_HTTP2_Server_Conn_tx(conn, http2_frame); + f_HTTP2_Server_Conn_st_change(conn, HTTP2_SERVER_CONN_ST_WAIT_SETTINGS); + return; + } + case (HTTP2_SERVER_CONN_ST_WAIT_SETTINGS) { + if (not ispresent(http2_rx.frame)) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str(f_HTTP2_Server_Conn_str(conn), ": Rx unexpected: ", http2_rx)); + } + if (not match(http2_rx.frame, tr_HTTP2_Frame_Settings())) { + return; /* TODO: we should only allow WINDOW_UPDATE here? */ + } + /* Tx Settings ACK and Window Update: */ + f_HTTP2_Server_Conn_tx(conn, ts_HTTP2_Frame_Settings(ack_flag := true)); + f_HTTP2_Server_Conn_tx(conn, ts_HTTP2_Frame_Window_Update(0, 1073741824)); + f_HTTP2_Server_Conn_st_change(conn, HTTP2_SERVER_CONN_ST_WAIT_HEADER); + return; + } + case (HTTP2_SERVER_CONN_ST_WAIT_HEADER) { + if (not ispresent(http2_rx.frame)) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str(f_HTTP2_Server_Conn_str(conn), ": Rx unexpected: ", http2_rx)); + } + if (not match(http2_rx.frame, tr_HTTP2_Frame_Header())) { + + return; /* TODO: we should only allow WINDOW_UPDATE and SETTINGS here... */ + } + + var HTTP2_header_block hb; + hb := f_HTTP2_Server_Conn_comp_decode(conn, http2_rx.frame.header_frame.header_block_fragment); + /* TODO: store header in Conn or send to ConNHdlr? */ + log(f_HTTP2_Server_Conn_str(conn), ": Decompressed header: ", hb); + if (not http2_rx.frame.header_frame.end_stream_flag) { + conn.rx_hb := hb; + f_HTTP2_Server_Conn_st_change(conn, HTTP2_SERVER_CONN_ST_WAIT_BODY); + return; + } + + http2_msg := ts_HTTP2_Msg(conn.conn_id, http2_rx.frame.header_frame.stream_id, hb, omit); + forward_http2_msg(valueof(http2_msg)); + return; + } + case (HTTP2_SERVER_CONN_ST_WAIT_BODY) { + if (not ispresent(http2_rx.frame)) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str(f_HTTP2_Server_Conn_str(conn), ": Rx unexpected: ", http2_rx)); + } + if (not match(http2_rx.frame, tr_HTTP2_Frame_Data())) { + return; /* TODO: we should only allow WINDOW_UPDATE and SETTINGS here... */ + } + if (not http2_rx.frame.data_frame.end_stream_flag) { + log("FIXME: Multiple DATA blocks not supported! requires updating HTTP2_Msg.body to be a list!"); + setverdict(fail); + } + http2_msg := ts_HTTP2_Msg(conn.conn_id, + http2_rx.frame.data_frame.stream_id, + conn.rx_hb, + http2_rx.frame.data_frame); + forward_http2_msg(valueof(http2_msg)); + return; + } + case else { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str(f_HTTP2_Server_Conn_str(conn), ": Rx in unexpected state: ", http2_rx)); + } + } +} + +function main(Http2ServerEmulationCfg cfg) +runs on HTTP2_Server_Emulation_CT { + + var HTTP2_ConnHdlr vc_conn; + var ASP_Event asp_evt; + var HTTP2_Recv http2_rx; + var HTTP2_Send http2_tx; + var HTTP2_Msg http2_msg; + var template (value) HTTP2_Frame http2_frame; + var integer idx; + + f_init(cfg); + + while(true) { + alt { + + /* server only */ + [] HTTP2.receive(ASP_Event:{connOpened:=?}) -> value asp_evt { + //log("HTTP2_Server: Rx ", asp_evt); + log("HTTP2_Server: Established a new connection (conn_id=", asp_evt.connOpened.connId, ")"); + idx := f_conn_table_find_by_conn_id(asp_evt.connOpened.connId); + if (idx >= 0) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Rx connOpened for already existing conn_id ", + asp_evt.connOpened.connId)); + } + idx := f_conn_table_find_free(); + if (idx < 0) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "No more space in conn_table!"); + } + conn_table[idx].conn_id := asp_evt.connOpened.connId; + f_HTTP2_Server_Conn_st_change(conn_table[idx], HTTP2_SERVER_CONN_ST_WAIT_PREFACE); + } + + [] HTTP2.receive(ASP_Event:{connClosed:=?}) -> value asp_evt { + //log("HTTP2_Server: Rx ", asp_evt); + idx := f_conn_table_find_by_conn_id(asp_evt.connClosed.connId); + if (idx < 0) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Rx connClosed for unknown conn_id ", + asp_evt.connClosed.connId)); + } + // TODO: Notify/Remove ConnHdlrs? + f_HTTP2_Server_Conn_init(conn_table[idx]); + } + + [] HTTP2.receive(HTTP2_Recv:?) -> value http2_rx { + //log("HTTP2_Server: Rx ", http2_rx); + idx := f_conn_table_find_by_conn_id(http2_rx.connId); + if (idx < 0) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Rx HTTP2_Recv ", http2_rx, " for unknown conn_id ", + asp_evt.connClosed.connId)); + } + f_HTTP2_Server_Conn_rx_http2(conn_table[idx], http2_rx); + } + + [] CONN.receive(HTTP2_Msg:?) -> value http2_msg sender vc_conn { + //log("HTTP2_Server: Tx ", http2_msg); + var integer last_stream_id := http2_msg.stream_id; + + idx := f_conn_table_find_by_conn_id(http2_msg.conn_id); + if (idx < 0) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Rx HTTP2_Msg ", http2_rx, " for unknown conn_id ", + http2_msg.conn_id)); + } + + /* Send Header: */ + f_HTTP2_Msg_append_hdr_content_length(http2_msg); + var octetstring hdr_enc := f_HTTP2_Server_Conn_comp_encode(conn_table[idx], http2_msg.hb); + http2_frame := ts_HTTP2_Frame_Header(http2_msg.stream_id, + end_stream_flag := not ispresent(http2_msg.body), + end_header_flag := true, + header_block_fragment := hdr_enc); + f_HTTP2_Server_Conn_tx(conn_table[idx], http2_frame); + + /* Send body: */ + if (ispresent(http2_msg.body)) { + http2_frame := ts_HTTP2_Frame_Data(http2_msg.body.stream_id, + http2_msg.body.end_stream_flag, + http2_msg.body.data, + http2_msg.body.padding); + f_HTTP2_Server_Conn_tx(conn_table[idx], http2_frame); + } + /* Send GOAWAY since we are finished: */ + f_HTTP2_Server_Conn_tx(conn_table[idx], ts_HTTP2_Frame_Goaway(last_stream_id, 0 /* NO_ERROR */)); + } + [] CONN_PROC.getcall(HTTP2EM_register:{}) -> sender vc_conn { + //log("HTTP2_Server: Register ", vc_conn); + f_vc_ConnHdlr_table_add(vc_conn); + CONN_PROC.reply(HTTP2EM_register:{}) to vc_conn; + } + } + } + +} + + +/*********************************************************************** + * Interaction between Main and Client Components + ***********************************************************************/ + +type port HTTP2_CONN_PT message { + inout HTTP2_Msg; +} with { extension "internal" }; + +signature HTTP2EM_register(); + +type port HTTP2_CONN_PROC_PT procedure { + inout HTTP2EM_register; +} with { extension "internal" }; + + +/*********************************************************************** + * Client Component + ***********************************************************************/ + +type component HTTP2_ConnHdlr { + /* These ports are not connected to anything in this component, they + * serve as connection points for the component that extends this + * coponent. */ + port HTTP2_CONN_PT HTTP2_CONN[16]; + port HTTP2_CONN_PROC_PT HTTP2_CONN_PROC[16]; +}; + +function f_http2_server_emu_register(integer id := 0) runs on HTTP2_ConnHdlr { + HTTP2_CONN_PROC[id].call(HTTP2EM_register:{}) { + [] HTTP2_CONN_PROC[id].getreply(HTTP2EM_register:{}); + } +} + +} diff --git a/library/HTTP2_Templates.ttcn b/library/HTTP2_Templates.ttcn new file mode 100644 index 0000000..d6d8296 --- /dev/null +++ b/library/HTTP2_Templates.ttcn @@ -0,0 +1,236 @@ +module HTTP2_Templates { + +import from HTTP2_Types all; + +template (value) HTTP2_Setting_data +ts_HTTP2_Setting_data(template (value) integer setting_id, + template (value) integer setting_value) := { + setting_id := setting_id, + setting_value := setting_value +} + +template (value) HTTP2_Setting_data +ts_HTTP2_Setting_HEADER_TABLE_SIZE(template (value) integer setting_value) := +ts_HTTP2_Setting_data(c_SETTINGS_HEADER_TABLE_SIZE, setting_value); + +template (value) HTTP2_Setting_data +ts_HTTP2_Setting_ENABLE_PUSH(template (value) integer setting_value) := +ts_HTTP2_Setting_data(c_SETTINGS_ENABLE_PUSH, setting_value); + +template (value) HTTP2_Setting_data +ts_HTTP2_Setting_MAX_CONCURRENT_STREAMS(template (value) integer setting_value) := +ts_HTTP2_Setting_data(c_SETTINGS_MAX_CONCURRENT_STREAMS, setting_value); + +template (value) HTTP2_Setting_data +ts_HTTP2_Setting_INITIAL_WINDOW_SIZE(template (value) integer setting_value) := +ts_HTTP2_Setting_data(c_SETTINGS_INITIAL_WINDOW_SIZE, setting_value); + +template (value) HTTP2_Setting_data +ts_HTTP2_Setting_MAX_FRAME_SIZE(template (value) integer setting_value) := +ts_HTTP2_Setting_data(c_SETTINGS_MAX_FRAME_SIZE, setting_value); + +template (value) HTTP2_Setting_data +ts_HTTP2_Setting_MAX_HEADER_LIST_SIZE(template (value) integer setting_value) := +ts_HTTP2_Setting_data(c_SETTINGS_MAX_HEADER_LIST_SIZE, setting_value); + +template (present) HTTP2_Data_frame +tr_HTTP2_Data_frame(template (present) integer stream_id := ?, + template (present) boolean end_stream_flag := ?, + template (present) octetstring data := ?, + template octetstring padding := *) := { + stream_id := stream_id, + end_stream_flag := end_stream_flag, + data := data, + padding := padding +} + +template (value) HTTP2_Data_frame +ts_HTTP2_Data_frame(template (value) integer stream_id := 0, + template (value) boolean end_stream_flag := false, + template (value) octetstring data, + template (omit) octetstring padding := omit) := { + stream_id := stream_id, + end_stream_flag := end_stream_flag, + data := data, + padding := padding +} + + +template (present) HTTP2_Frame +tr_HTTP2_Frame_Settings(template (present) boolean ack_flag := ?, + template HTTP2_Setting_list settings := *) := { + settings_frame := { + ack_flag := ack_flag, + settings := settings + } +} + +template (value) HTTP2_Frame +ts_HTTP2_Frame_Settings(template (value) boolean ack_flag := false, + template (omit) HTTP2_Setting_list settings := omit) := { + settings_frame := { + ack_flag := ack_flag, + settings := settings + } +} + +template (value) HTTP2_Frame +ts_HTTP2_Frame_Goaway(template (value) integer last_stream_id, + template (value) integer error_code, + template (omit) octetstring debug_data := omit) := { + goaway_frame := { + last_stream_id := last_stream_id, + error_code := error_code, + debug_data := debug_data + } +} + +template (value) HTTP2_Frame +ts_HTTP2_Frame_Window_Update(template (value) integer stream_id := 0, + template (value) integer window_size_increment) := { + window_update_frame := { + stream_id := stream_id, + window_size_increment := window_size_increment + } +} + +template (present) HTTP2_Frame +tr_HTTP2_Frame_Data(template (present) integer stream_id := ?, + template (present) boolean end_stream_flag := ?, + template (present) octetstring data := ?, + template octetstring padding := *) := { + data_frame := tr_HTTP2_Data_frame(stream_id, + end_stream_flag, + data, + padding) +} + +template (value) HTTP2_Frame +ts_HTTP2_Frame_Data(template (value) integer stream_id := 0, + template (value) boolean end_stream_flag := false, + template (value) octetstring data, + template (omit) octetstring padding := omit) := { + data_frame := ts_HTTP2_Data_frame(stream_id, + end_stream_flag, + data, + padding) +} + +template (present) HTTP2_Frame +tr_HTTP2_Frame_Header(template (present) integer stream_id := ?, + template (present) boolean end_stream_flag := ?, + template (present) boolean end_header_flag := ?, + template HTTP2_Priority_data priority_data := *, + template (present) octetstring header_block_fragment := ?, + template octetstring padding := *) := { + header_frame := { + stream_id := stream_id, + end_stream_flag := end_stream_flag, + end_header_flag := end_header_flag, + priority_data := priority_data, + header_block_fragment := header_block_fragment, + padding := padding + } +} + +template (value) HTTP2_Frame +ts_HTTP2_Frame_Header(template (value) integer stream_id := 0, + template (value) boolean end_stream_flag := false, + template (value) boolean end_header_flag := false, + template (omit) HTTP2_Priority_data priority_data := omit, + template (value) octetstring header_block_fragment := ''O, + template (omit) octetstring padding := omit) := { + header_frame := { + stream_id := stream_id, + end_stream_flag := end_stream_flag, + end_header_flag := end_header_flag, + priority_data := priority_data, + header_block_fragment := header_block_fragment, + padding := padding + } +} + +template (present) HTTP2_pseudo_headers +tr_HTTP2_pseudo_headers(template charstring method := *, + template charstring scheme := *, + template charstring authority := *, + template charstring path := *, + template integer status := *) := { + method := method, + scheme := scheme, + authority := authority, + path := path, + status := status +} + +template (value) HTTP2_pseudo_headers +ts_HTTP2_pseudo_headers(template (omit) charstring method := omit, + template (omit) charstring scheme := omit, + template (omit) charstring authority := omit, + template (omit) charstring path := omit, + template (omit) integer status := omit) := { + method := method, + scheme := scheme, + authority := authority, + path := path, + status := status +} + +template (value) HTTP2_header_field +ts_HTTP2_header_field(template (value) charstring header_name, + template (omit) charstring header_value := omit) := { + header_name := header_name, + header_value := header_value +} + +const charstring c_HTTP2_HDR_content_type := "content-type"; +const charstring c_HTTP2_HDR_accept := "accept"; +const charstring c_HTTP2_HDR_user_agent := "user-agent"; +const charstring c_HTTP2_HDR_content_length := "content-length"; +const charstring c_HTTP2_HDR_accept_encoding := "accept-encoding"; +const charstring c_HTTP2_HDR_status := "status"; +const charstring c_HTTP2_HDR_location := "location"; + +template (value) HTTP2_header_field +ts_HTTP2_Hdr_content_type(template (omit) charstring header_value := omit) +:= ts_HTTP2_header_field(c_HTTP2_HDR_content_type, header_value); + +template (value) HTTP2_header_field +ts_HTTP2_Hdr_accept(template (omit) charstring header_value := omit) +:= ts_HTTP2_header_field(c_HTTP2_HDR_accept, header_value); + +template (value) HTTP2_header_field +ts_HTTP2_Hdr_user_agent(template (omit) charstring header_value := omit) +:= ts_HTTP2_header_field(c_HTTP2_HDR_user_agent, header_value); + +template (value) HTTP2_header_field +ts_HTTP2_Hdr_content_length(template (omit) charstring header_value := omit) +:= ts_HTTP2_header_field(c_HTTP2_HDR_content_length, header_value); + +template (value) HTTP2_header_field +ts_HTTP2_Hdr_accept_encoding(template (omit) charstring header_value := omit) +:= ts_HTTP2_header_field(c_HTTP2_HDR_accept_encoding, header_value); + +template (value) HTTP2_header_field +ts_HTTP2_Hdr_status(template (omit) charstring header_value := omit) +:= ts_HTTP2_header_field(c_HTTP2_HDR_status, header_value); + +template (value) HTTP2_header_field +ts_HTTP2_Hdr_location(template (omit) charstring header_value := omit) +:= ts_HTTP2_header_field(c_HTTP2_HDR_location, header_value); + + +template (present) HTTP2_header_block +tr_HTTP2_header_block(template HTTP2_pseudo_headers pseudo_headers := *, + template HTTP2_header_list headers := *) := { + pseudo_headers := pseudo_headers, + headers := headers +} +template (value) HTTP2_header_block +ts_HTTP2_header_block(template (omit) HTTP2_pseudo_headers pseudo_headers := omit, + template (omit) HTTP2_header_list headers := omit) := { + pseudo_headers := pseudo_headers, + headers := headers +} + +}