diff --git a/CMakeLists.txt b/CMakeLists.txt
index ab624a519..0e016ce40 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -195,6 +195,7 @@ set(PICOHTTP_LIBRARY_FILES
picohttp/h3zero_common.c
picohttp/h3zero_server.c
picohttp/h3zero_uri.c
+ picohttp/picomask.c
picohttp/quicperf.c
picohttp/siduck.c
picohttp/webtransport.c
@@ -207,13 +208,16 @@ set(PICOHTTP_HEADERS
picohttp/democlient.h
picohttp/demoserver.h
picohttp/pico_webtransport.h
+ picohttp/picomask.h
+ picohttp/quicperf.c
picohttp/wt_baton.h)
set(PICOHTTP_TEST_LIBRARY_FILES
picoquictest/h3zerotest.c
picoquictest/h3zero_stream_test.c
picoquictest/h3zero_uri_test.c
- picoquictest/quicperf_test.c
+ picoquictest/picomask_test.c
+ picoquictest/picomask_test.c
picoquictest/webtransport_test.c)
OPTION(PICOQUIC_FETCH_PTLS "Fetch PicoTLS during configuration" OFF)
diff --git a/picohttp/picohttp.vcxproj b/picohttp/picohttp.vcxproj
index ea8873783..f97011586 100644
--- a/picohttp/picohttp.vcxproj
+++ b/picohttp/picohttp.vcxproj
@@ -144,6 +144,7 @@
+
@@ -155,6 +156,7 @@
+
diff --git a/picohttp/picohttp.vcxproj.filters b/picohttp/picohttp.vcxproj.filters
index 661a2f33b..69a8f2a6c 100644
--- a/picohttp/picohttp.vcxproj.filters
+++ b/picohttp/picohttp.vcxproj.filters
@@ -34,6 +34,9 @@
Source Files
+
+ Source Files
+
@@ -66,6 +69,9 @@
Source Files
+
+ Source Files
+
diff --git a/picohttp/picomask.c b/picohttp/picomask.c
new file mode 100644
index 000000000..ac94b44a0
--- /dev/null
+++ b/picohttp/picomask.c
@@ -0,0 +1,400 @@
+/*
+* Author: Christian Huitema
+* Copyright (c) 2024, Private Octopus, Inc.
+* All rights reserved.
+*
+* Permission to use, copy, modify, and distribute this software for any
+* purpose with or without fee is hereby granted, provided that the above
+* copyright notice and this permission notice appear in all copies.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. BE LIABLE FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/* Implementation of the masque service over picoquic and h3zero.
+*
+* The masque proxying is implemented using h3zero, and hooking
+* into several APIs.
+*
+* The management of Connect UDP uses the "extended
+* connect" API, just like web transport. We have two consider
+* multiple connection levels:
+*
+* - there is a single Masque context managing all common data,
+* including lists of other contexts and lists of connection
+* identifiers. This is tied to the QUIC context in which
+* QUIC and H3 connections as defined.
+* - there is one masque context per H3 connection. On the
+* server, this may mean one context per connection from masque
+* clients. On the client, there may be multiple contexts if the
+* client connects to multiple Masque proxies. This context
+* is tied to the H3 context of the connection.
+* - there is one UDP_CONNECT context per tuple,
+* where target is identified by IP address and port number.
+*
+* On the client, the proper matching between connections, paths and UDP
+* CONNECT contexts is debatable. We don't want something too
+* intrusive. One solution would be to use a specific interface
+* ID, if we can accept the risk of collision in the interface
+* ID space. The "local" address will have that interface ID
+* and the IP Address and port of the proxy. The "remote" address
+* will have the that interface ID
+* and the IP Address and port of the target.
+*
+* The flow will be:
+* - create or a connection to a target address, using the normal
+* API, but setting the "interface index" to the reserved
+* value "picomask interface ID".
+* - intercept packets sent to the "picomask interface", and
+* submit them to the picomask outgoing API.
+*
+* - if there is no connection yet to the selected proxy,
+* set one up.
+* - if the is no Connect UDP context for the proxy and the
+* target, set one up.
+* - if the context is created, queue the packets in the
+* connect UDP context, and wake up that context.
+*
+* In the reverse path, packets will be incoming from the targets,
+* and have to be associated with the selected context.
+* We need a procedure to intercept incoming packets, by examining
+* the connection ID and/or the IP addresses.
+*
+* We assume that the size of queues will be managed by congestion control.
+* If excessive, set the ECN marks or drop.
+*
+* On the server, data from target is incoming over the UDP socket. If it
+* does not match a local CID, it will be added to the queue
+* of the UDP CONNECT context that matches the target address
+* (remote address). This ends up with the same "send path"
+* as for the cleint.
+*
+* On the server, data from the client arrives in datagrams. It is queued
+* in front of the local socket, maybe witten diractly to the UDP
+* socket. Maybe integrate with the "prepare" API to catch these
+* packets, so we have only on connection loop.
+*
+* We need to define an H3Zero callback, using the same API as for
+* "post" or "web transport", to handle the "connect UDP" protocol.
+* The connection control messages will be handled through that
+* callback, as well as the tunneled datagrams.
+*
+* The forwarding path should be able to perform transforms,
+* for incoming as well as outgoing packets.
+*
+*/
+
+#include
+#include "picomask.h"
+#include "h3zero.h"
+#include "h3zero_common.h"
+#include "string.h"
+
+int picomask_callback(picoquic_cnx_t* cnx,
+ uint8_t* bytes, size_t length,
+ picohttp_call_back_event_t wt_event,
+ struct st_h3zero_stream_ctx_t* stream_ctx,
+ void* path_app_ctx);
+
+/* Context retrieval functions */
+static uint64_t table_udp_hash(const void * key)
+{
+ /* the key is a unique 64 bit number, so we keep this simple. */
+ return *((uint64_t*)key);
+}
+
+static int table_udp_compare(const void* key1, const void* key2)
+{
+ return (*((uint64_t*)key1) == *((uint64_t*)key2)) ? 0 : -1;
+}
+
+
+static picohash_item * table_udp_to_item(const void* key)
+{
+ picomask_cnx_ctx_t* cnx_ctx = (picomask_cnx_ctx_t*)key;
+ return &cnx_ctx->hash_item;
+}
+
+picomask_cnx_ctx_t* picomask_cnx_ctx_by_number(picomask_ctx_t* ctx, uint64_t picomask_number)
+{
+ picomask_cnx_ctx_t* cnx_ctx = NULL;
+ picohash_item* item;
+ picomask_cnx_ctx_t key = { 0 };
+ key.picomask_number = picomask_number;
+ key.hash_item.key = (void*)&key.picomask_number;
+ item = picohash_retrieve(ctx->table_udp_ctx, &key);
+
+ if (item != NULL) {
+ cnx_ctx = (picomask_cnx_ctx_t*)(((uint8_t*)(item)-
+ offsetof(struct st_picomask_cnx_ctx_t, hash_item)));
+ }
+ return cnx_ctx;
+}
+
+/* Init the global context */
+
+int picomask_ctx_init(picomask_ctx_t* ctx, size_t max_nb_connections)
+{
+ int ret = 0;
+
+ if ((ctx->table_udp_ctx = picohash_create_ex(max_nb_connections,
+ table_udp_hash, table_udp_compare, table_udp_to_item)) == NULL) {
+ ret = -1;
+ }
+
+ return ret;
+}
+
+void picomask_ctx_release(picomask_ctx_t* ctx)
+{
+ /* Delete all the existing contexts, by walking through
+ * the table */
+ /* then delete the table itself. */
+ picohash_delete(ctx->table_udp_ctx, 0);
+}
+
+
+/* Create the picomask context per udp connect */
+picomask_cnx_ctx_t* picomask_cnx_ctx_create(picomask_ctx_t* picomask_ctx)
+{
+ picomask_cnx_ctx_t* cnx_ctx = (picomask_cnx_ctx_t*)malloc(sizeof(picomask_cnx_ctx_t));
+ if (cnx_ctx != NULL) {
+ memset(cnx_ctx, 0, sizeof(picomask_cnx_ctx_t));
+ cnx_ctx->picomask_number = picomask_ctx->picomask_number_next++;
+ /* register in table of contexts */
+ picohash_insert(picomask_ctx->table_udp_ctx, cnx_ctx);
+ }
+ return cnx_ctx;
+}
+#if 0
+/* Update context when sending a connect request */
+int picomask_connecting(picoquic_cnx_t* cnx,
+ h3zero_stream_ctx_t* stream_ctx, void * v_masque_ctx)
+{
+ picomask_ctx_t* picomask_ctx = (picomask_ctx_t*)v_masque_ctx;
+
+ picoquic_log_app_message(cnx, "Outgoing connect udp on stream: %"PRIu64, stream_ctx->stream_id);
+
+ return 0;
+}
+
+/* Accept an incoming connection */
+int picomask_accept(picoquic_cnx_t* cnx,
+ uint8_t* path, size_t path_length,
+ struct st_h3zero_stream_ctx_t* stream_ctx,
+ void* path_app_ctx)
+{
+ int ret = 0;
+
+ /* Find the target's IP address and port number from the path */
+
+ /* Verify that the path is OK: the UDP proxy disallows UDP proxying requests
+ * to vulnerable targets, such as the UDP proxy's own addresses and localhost,
+ * link-local, multicast, and broadcast addresses */
+
+ /* If doing just connect UDP, by opposition to QUIC proxy, accept
+ * only one connection for a given IP+port. If doing QUIC proxy,
+ * manage the registered CID for the long header packets.
+ */
+
+ /* create a per connection context, indexed by stream ID and
+ * some unique identifier of the connection.
+ */
+
+ /* if all is well, */
+ return ret;
+}
+#endif
+
+/* Prepare datagram. This is the call from the inner connection,
+* stating that a datagram can now be sent. The masque context
+* pick the UDP connect context with the smallest wakeup time,
+ */
+
+/* picomask callback. This will be called from the web server
+* when the path points to a picomask callback.
+*/
+
+int picomask_callback(picoquic_cnx_t* cnx,
+ uint8_t* bytes, size_t length,
+ picohttp_call_back_event_t wt_event,
+ struct st_h3zero_stream_ctx_t* stream_ctx,
+ void* path_app_ctx)
+{
+ int ret = 0;
+ DBG_PRINTF("picomask_callback: %d, %" PRIi64 "\n", (int)wt_event, (stream_ctx == NULL)?(int64_t)-1:(int64_t)stream_ctx->stream_id);
+ switch (wt_event) {
+ case picohttp_callback_connect:
+ /* A connect has been received on this stream, and could be accepted.
+ * The path app context should point to the global masque context.
+ */
+ /* ret = picomask_accept(cnx, bytes, length, stream_ctx, path_app_ctx); */
+ break;
+ case picohttp_callback_connect_refused:
+ /* The response from the server has arrived and it is negative. The
+ * application needs to close that stream.
+ */
+ break;
+ case picohttp_callback_connect_accepted:
+ if (stream_ctx != NULL) {
+ /* Stream will now carry "capsules" */
+ stream_ctx->is_upgraded = 1;
+ }
+ break;
+ case picohttp_callback_post_fin:
+ case picohttp_callback_post_data:
+ /* Receiving capsule data on the control stream.
+ * if the FIN bit is set, the connection will be closed.
+ */
+ /* ret = picomask_stream_data(cnx, bytes, length, (wt_event == picohttp_callback_post_fin), stream_ctx, path_app_ctx); */
+ break;
+ case picohttp_callback_provide_data:
+ /* callback to provide data. Provide the next capsule. */
+ /* ret = picomask_provide_data(cnx, bytes, length, stream_ctx, path_app_ctx); */
+ break;
+ case picohttp_callback_post_datagram:
+ /* Stack received a datagram. Submit as "incoming" packet on the
+ * select connection and path */
+ /* ret = picomask_receive_datagram(cnx, bytes, length, stream_ctx, path_app_ctx); */
+ break;
+ case picohttp_callback_provide_datagram:
+ /* callback to provide data. This will translate to a "prepare data" call
+ * on the next available connection context and path context */
+ /* ret = picomask_provide_datagram(cnx, bytes, length, stream_ctx, path_app_ctx); */
+ break;
+ case picohttp_callback_reset:
+ /* Control stream has been abandoned. Abandon the whole connection. */
+ /* ret = picomask_stream_reset(cnx, stream_ctx, path_app_ctx); */
+ break;
+ case picohttp_callback_free: /* Used during clean up the stream. Only cause the freeing of memory. */
+ /* Free the memory attached to the stream */
+ break;
+ case picohttp_callback_deregister:
+ /* The app context has been removed from the registry.
+ * Its references should be removed from streams belonging to this session.
+ * On the client, the memory should be freed.
+ */
+ /* picomask_unlink_context(cnx, stream_ctx, path_app_ctx); */
+ break;
+ default:
+ /* protocol error */
+ ret = -1;
+ break;
+ }
+ return ret;
+}
+
+/* Connect is called when the path registers to use the tunnel service.
+* The call should document the masque context of the service, and
+* also the inner connection and inner path id.
+*/
+int picomask_connect(picoquic_cnx_t* cnx, picomask_ctx_t* picomask_ctx,
+ char const * server_path,
+ h3zero_callback_ctx_t* h3_ctx)
+{
+ int ret = 0;
+ uint64_t stream_id = picoquic_get_next_local_stream_id(cnx, 0);
+ h3zero_stream_ctx_t* stream_ctx = NULL;
+ picomask_cnx_ctx_t* cnx_ctx = NULL;
+#ifdef _WINDOWS
+ UNREFERENCED_PARAMETER(server_path);
+#endif
+ /* Create an H3 stream context */
+ if ((stream_ctx = h3zero_find_or_create_stream(
+ cnx, stream_id, h3_ctx, 1, 1)) == NULL) {
+ ret = -1;
+ }
+ /* Associate the stream with a per_stream context */
+ else if ((ret = picoquic_set_app_stream_ctx(cnx, stream_id, stream_ctx)) != 0) {
+ DBG_PRINTF("Could not set context for stream %"PRIu64, stream_id);
+ stream_ctx = NULL;
+ }
+ /* Create the connection context for the UDP connect */
+ else if ((cnx_ctx = picomask_cnx_ctx_create(picomask_ctx)) == NULL) {
+ DBG_PRINTF("Could not create UDP Connect context for stream %"PRIu64, stream_id);
+ ret = -1;
+ }
+ /* Register for the prefix in the H3 context. */
+ else if ((ret = h3zero_declare_stream_prefix(h3_ctx, stream_ctx->stream_id,
+ picomask_callback, cnx_ctx))!=0){
+ DBG_PRINTF("Could not declare prefix stream %"PRIu64, stream_id);
+ /* clean up? */
+ }
+ /* Finalize the context */
+ else {
+ /* WT_CONNECT finalizes the context with a call to the connecting event.
+ * But we do not have any sub protocol, so we can do that here.
+ */
+ cnx_ctx->cnx = cnx;
+ cnx_ctx->stream_id = stream_id;
+ /* to do: set target_addr from path */
+ /* Then, queue the UDP_CONNECT frame. */
+ }
+
+ if (ret != 0) {
+ /* clean up the partially created contexts */
+ if (cnx_ctx != NULL) {
+ /* delete that */
+ }
+ if (stream_ctx != NULL) {
+ h3zero_delete_stream(cnx, h3_ctx, stream_ctx);
+ }
+ }
+ return ret;
+}
+
+/* Calls from the inner connection:
+* state when a path is ready to send data. This is a combination of
+* having data to send, and not being limited by CC and pacing.
+*
+* Should document the "next time" for the path. Then, the
+* masque context will translate that into a "ready to send"
+* signal for the inner connection.
+ */
+
+/* Mapping a path to a proxy, on the client?
+*
+*/
+
+
+/* Creation of an outer connection.
+ * On client, this is done explicitly: create a connection to
+ * a proxy, get a unique UDP connection ID. This requires
+ * first creating an H3 connection (may already exist), and
+ * then issuing the UDP connect.
+ * On server, this is done upon successful connection from
+ * a client.
+ */
+
+
+
+/* management of outgoing packets at the client */
+int picomask_outgoing()
+{
+ /* Is there an established context for these addresses?
+ * if yes, queue it there.
+ */
+
+ /* If not, is there yet an established H3 connection for
+ * the source address?
+ */
+
+ /* If not, establish the h3 connection.
+ * TODO: provide credentials.
+ */
+
+ /* is there now an established H3 connection for
+ * the source address?*/
+
+ /* if not, return an error */
+ /* if yes, create a Connect UDP context, queue the packet to it */
+ return -1;
+}
\ No newline at end of file
diff --git a/picohttp/picomask.h b/picohttp/picomask.h
new file mode 100644
index 000000000..397799cf2
--- /dev/null
+++ b/picohttp/picomask.h
@@ -0,0 +1,76 @@
+/*
+* Author: Christian Huitema
+* Copyright (c) 2024, Private Octopus, Inc.
+* All rights reserved.
+*
+* Permission to use, copy, modify, and distribute this software for any
+* purpose with or without fee is hereby granted, provided that the above
+* copyright notice and this permission notice appear in all copies.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. BE LIABLE FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef PICOMASK_H
+#define PICOMASK_H
+
+#include "picohash.h"
+#include "picoquic.h"
+#include "picoquic_utils.h"
+#include "picoquic_internal.h"
+
+/*
+* Context is split between two levels:
+* - The global context of the picomask service, which
+* holds for example the list of all connection contexts,
+* the global list of incoming VCID, etc.
+* - The per connection entry, which holds the state
+* and the queue of packets for a given connect-udp
+* connection.
+* The global context is initialized when starting the
+* service.
+*/
+
+/* We need to reserve an interface ID that does not collude with
+* values likely used by the operating system. This excludes
+* small numbers, and special numbers like 0 or -1. We pick
+* a random 31 bit numbers, derived from the SHA1 hash of
+* "Picomask UDP interface":
+* 2798c62715dd8ce6e2c6dd92a37a8276f16c029e
+*/
+#define picomask_interface_id 0x2798c627
+
+typedef struct st_picomask_ctx_t {
+ picohash_table* table_udp_ctx;
+ uint64_t picomask_number_next;
+} picomask_ctx_t;
+
+typedef struct st_picomask_h3_ctx_t {
+ struct sockaddr_storage target_addr;
+ picoquic_cnx_t* cnx;
+} picomask_h3_ctx_t;
+
+typedef struct st_picomask_cnx_ctx_t {
+ picohash_item hash_item;
+ uint64_t picomask_number;
+ picoquic_cnx_t* cnx;
+ uint64_t stream_id;
+ struct sockaddr_storage target_addr;
+ /* Management of capsule protocol on control stream */
+ /* Management of datagram queue -- incoming packets
+ * that have to be processed locally */
+ picoquic_packet_t* outgoing_first;
+ picoquic_packet_t* outgoing_last;
+} picomask_cnx_ctx_t;
+
+
+
+#endif /* PICOMASK_H */
diff --git a/picoquictest/picomask_test.c b/picoquictest/picomask_test.c
new file mode 100644
index 000000000..26e8c70fa
--- /dev/null
+++ b/picoquictest/picomask_test.c
@@ -0,0 +1,354 @@
+/*
+* Author: Christian Huitema
+* Copyright (c) 2024, Private Octopus, Inc.
+* All rights reserved.
+*
+* Permission to use, copy, modify, and distribute this software for any
+* purpose with or without fee is hereby granted, provided that the above
+* copyright notice and this permission notice appear in all copies.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. BE LIABLE FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include
+#include
+#include "picomask.h"
+#include "h3zero.h"
+#include "h3zero_common.h"
+#include "picoquic.h"
+#include "picoquic_utils.h"
+
+typedef struct st_picomask_test_ctx_t {
+ uint64_t simulated_time;
+ /* Three quic nodes: client(0), proxy(1), target(2) */
+ picoquic_quic_t* quic[3];
+ struct sockaddr_storage addr[3];
+ picohttp_server_parameters_t server_context;
+ picohttp_server_parameters_t target_context;
+ /* Four links: server->client[0], client->server[1], server->target[2], target->server[3],
+ */
+ picoquictest_sim_link_t* link[4];
+ /* all nodes run H3, client as client, proxy and target as servers */
+ /* Transfer test will be by getting a test file from target to client */
+ /* ECN simulation */
+ uint8_t packet_ecn_default;
+ uint8_t recv_ecn_client;
+ uint8_t recv_ecn_server;
+} picoquic_picomask_test_ctx_t;
+
+/*
+* Delete the configuration
+*/
+void picomask_test_delete(picoquic_picomask_test_ctx_t* pt_ctx)
+{
+ for (int i = 0; i < 3; i++) {
+ if (pt_ctx->quic[i] != NULL) {
+ picoquic_free(pt_ctx->quic[i]);
+ pt_ctx->quic[i] = NULL;
+ }
+ }
+
+ for (int i = 0; i < 4; i++) {
+ if (pt_ctx->link[i] != NULL) {
+ picoquictest_sim_link_delete(pt_ctx->link[i]);
+ pt_ctx->link[i] = NULL;
+ }
+ }
+}
+/*
+* test configuration.
+*
+* Build a test network with three nodes: client, proxy, target.
+*/
+picoquic_picomask_test_ctx_t * picomask_test_config()
+{
+ int ret = 0;
+ char test_server_cert_file[512];
+ char test_server_key_file[512];
+ char test_server_cert_store_file[512];
+ picoquic_picomask_test_ctx_t* pt_ctx = NULL;
+
+ ret = picoquic_get_input_path(test_server_cert_file, sizeof(test_server_cert_file), picoquic_solution_dir,
+ PICOQUIC_TEST_FILE_SERVER_CERT);
+
+ if (ret == 0) {
+ ret = picoquic_get_input_path(test_server_key_file, sizeof(test_server_key_file), picoquic_solution_dir,
+ PICOQUIC_TEST_FILE_SERVER_KEY);
+ }
+
+ if (ret == 0) {
+ ret = picoquic_get_input_path(test_server_cert_store_file, sizeof(test_server_cert_store_file), picoquic_solution_dir,
+ PICOQUIC_TEST_FILE_CERT_STORE);
+ }
+
+ if (ret == 0) {
+ pt_ctx = (picoquic_picomask_test_ctx_t*)malloc(sizeof(picoquic_picomask_test_ctx_t));
+ if (pt_ctx == NULL) {
+ ret = -1;
+ }
+ else {
+ memset(pt_ctx, 0, sizeof(picoquic_picomask_test_ctx_t));
+ }
+ }
+
+ if (ret == 0) {
+ /* Set addresses */
+ for (int i = 0; i < 3; i++) {
+ unsigned long h = 0xa0000001;
+ struct sockaddr_in* a = (struct sockaddr_in*)&pt_ctx->addr[i];
+ a->sin_family = AF_INET;
+#ifdef _WINDOWS
+ a->sin_addr.S_un.S_addr = htonl(h + i);
+#else
+
+ a->sin_addr.s_addr = htonl(h + i);
+#endif
+ a->sin_port = htons(1234);
+ }
+ /* Create client context */
+ pt_ctx->quic[0] = picoquic_create(8, NULL, NULL, test_server_cert_store_file, NULL,
+ h3zero_callback,
+ /* TODO: default client callback context */ NULL,
+ NULL, NULL, NULL, pt_ctx->simulated_time,
+ &pt_ctx->simulated_time,
+ /* TODO -- should we store tickets? */NULL,
+ NULL, 0);
+ /* Create server context */
+ pt_ctx->quic[1] = picoquic_create(8, test_server_key_file, test_server_cert_file, NULL, "h3",
+ h3zero_callback,
+ &pt_ctx->server_context,
+ NULL, NULL, NULL, pt_ctx->simulated_time,
+ &pt_ctx->simulated_time,
+ /* TODO -- should we store tickets? */NULL,
+ NULL, 0);
+ /* Create target context */
+ pt_ctx->quic[2] = picoquic_create(8, test_server_key_file, test_server_cert_file, NULL, "h3",
+ h3zero_callback,
+ &pt_ctx->target_context,
+ NULL, NULL, NULL, pt_ctx->simulated_time,
+ &pt_ctx->simulated_time,
+ /* TODO -- should we store tickets? */NULL,
+ NULL, 0);
+ if (pt_ctx->quic[0] == NULL || pt_ctx->quic[1] == NULL || pt_ctx->quic[1] == NULL) {
+ ret = -1;
+ }
+ /* Create links */
+ for (int i = 0; ret == 9 && i < 4; i++) {
+ if ((pt_ctx->link[i] = picoquictest_sim_link_create(0.01, 10000, NULL, 0, pt_ctx->simulated_time)) == NULL) {
+ ret = -1;
+ }
+ }
+ }
+
+ if (ret < 0 && pt_ctx != NULL) {
+
+ }
+
+ return pt_ctx;
+}
+
+/* Process arrival of a packet from a link */
+int picomask_test_packet_arrival(picoquic_picomask_test_ctx_t* pt_ctx, int link_id, int * is_active)
+{
+ int ret = 0;
+ picoquictest_sim_packet_t* packet = picoquictest_sim_link_dequeue(pt_ctx->link[link_id], pt_ctx->simulated_time);
+
+ if (packet == NULL) {
+ /* unexpected, probably bug in test program */
+ ret = -1;
+ }
+ else {
+ int node_id = -1; /* by default, go to proxy */
+
+ switch (link_id) {
+ case 0:
+ node_id = 1;
+ break;
+ case 1:
+ node_id = 0;
+ break;
+ case 2:
+ node_id = 1;
+ break;
+ case 3:
+ node_id = 2;
+ break;
+ default:
+ ret = -1;
+ }
+
+ if (ret == 0) {
+ *is_active = 1;
+
+ ret = picoquic_incoming_packet(pt_ctx->quic[node_id],
+ packet->bytes, (uint32_t)packet->length,
+ (struct sockaddr*)&packet->addr_from,
+ (struct sockaddr*)&packet->addr_to, 0, 0,
+ pt_ctx->simulated_time);
+ }
+
+ free(packet);
+ }
+
+ return ret;
+}
+
+/* Packet departure from selected node */
+int picomask_test_packet_departure(picoquic_picomask_test_ctx_t* pt_ctx, int node_id,
+ int* is_active)
+{
+ int ret = 0;
+ picoquictest_sim_packet_t* packet = picoquictest_sim_link_create_packet();
+
+ if (packet == NULL) {
+ /* memory error during test. Something is really wrong. */
+ ret = -1;
+ }
+ else {
+ /* check whether there is something to send */
+ int if_index = 0;
+
+ ret = picoquic_prepare_next_packet(pt_ctx->quic[node_id], pt_ctx->simulated_time,
+ packet->bytes, PICOQUIC_MAX_PACKET_SIZE, &packet->length,
+ &packet->addr_to, &packet->addr_from, &if_index, NULL, NULL);
+
+ if (ret != 0)
+ {
+ /* useless test, but makes it easier to add a breakpoint under debugger */
+ free(packet);
+ ret = -1;
+ }
+ else if (packet->length > 0) {
+ /* Find link ID from node ID and destination IP */
+ int link_id = -1;
+
+ switch (node_id) {
+ case 0:
+ link_id = 0;
+ break;
+ case 1:
+ if (picoquic_compare_addr((struct sockaddr*)&packet->addr_to,
+ (struct sockaddr*)&pt_ctx->addr[0]) == 0) {
+ link_id = 1;
+ }
+ else if (picoquic_compare_addr((struct sockaddr*)&packet->addr_to,
+ (struct sockaddr*)&pt_ctx->addr[2]) == 0) {
+ link_id = 3;
+ }
+ else {
+ free(packet);
+ ret = -1;
+ }
+ break;
+ case 2:
+ link_id = 2;
+ break;
+ }
+ if (ret == 0) {
+ /* If the source address is not set, set it */
+ if (packet->addr_from.ss_family == 0) {
+ picoquic_store_addr(&packet->addr_from, (struct sockaddr*)&pt_ctx->addr[node_id]);
+ }
+ /* send now. */
+ *is_active = 1;
+ picoquictest_sim_link_submit(pt_ctx->link[link_id], packet, pt_ctx->simulated_time);
+ }
+ }
+ else {
+ free(packet);
+ }
+ }
+
+ return ret;
+}
+
+/* step by step simulation
+ */
+int picomask_test_step(picoquic_picomask_test_ctx_t* pt_ctx, int* is_active)
+{
+ int ret = 0;
+ uint64_t next_arrival_time = UINT64_MAX;
+ int arrival_index = -1;
+ uint64_t next_departure_time = UINT64_MAX;
+ int departure_index = -1;
+ int need_frame_departure = 0;
+ uint64_t next_frame_time = UINT64_MAX;
+ uint64_t next_time = UINT64_MAX;
+
+ /* Check earliest packet arrival */
+ for (int i = 0; i < 4; i++) {
+ uint64_t arrival = picoquictest_sim_link_next_arrival(pt_ctx->link[i], next_arrival_time);
+ if (arrival < next_arrival_time) {
+ next_arrival_time = arrival;
+ arrival_index = i;
+ }
+ }
+ if (next_arrival_time < next_time) {
+ next_time = next_arrival_time;
+ }
+
+ /* Check earliest packet departure */
+ for (int i = 0; i < 3; i++) {
+ uint64_t departure = picoquic_get_next_wake_time(pt_ctx->quic[i], pt_ctx->simulated_time);
+ if (departure < next_departure_time) {
+ next_departure_time = departure;
+ departure_index = i;
+ }
+ }
+ if (next_time > next_departure_time) {
+ next_time = next_departure_time;
+ }
+
+ /* Update the time now */
+ if (next_time > pt_ctx->simulated_time) {
+ pt_ctx->simulated_time = next_time;
+ }
+ else {
+ next_time = pt_ctx->simulated_time;
+ }
+
+ if (ret == 0) {
+ /* Perform earliest action */
+ if (next_arrival_time <= next_time) {
+ /* Process next packet from simulated link */
+ ret = picomask_test_packet_arrival(pt_ctx, arrival_index, is_active);
+ }
+ else {
+ /* Prepare next packet from selected connection */
+ ret = picomask_test_packet_departure(pt_ctx, departure_index, is_active);
+ }
+ }
+ if (ret < 0) {
+ DBG_PRINTF("Simulation fails at T=%" PRIu64, pt_ctx->simulated_time);
+ }
+
+ return ret;
+}
+
+/*
+* First test: verify that the UDP Connect context can be established.
+*/
+int picomask_udp_test()
+{
+ int ret = 0;
+ picoquic_picomask_test_ctx_t* pt_ctx = picomask_test_config();
+
+ if (pt_ctx == NULL) {
+ ret = -1;
+ }
+ else {
+ /* Create a client connection to the server */
+ /* On that connection, create a UDP connect context */
+ picomask_test_delete(pt_ctx);
+ }
+ return ret;
+}
\ No newline at end of file
diff --git a/picoquictest/picoquictest.vcxproj b/picoquictest/picoquictest.vcxproj
index a67e32b65..cd1c82b7d 100644
--- a/picoquictest/picoquictest.vcxproj
+++ b/picoquictest/picoquictest.vcxproj
@@ -177,6 +177,7 @@
+
diff --git a/picoquictest/picoquictest.vcxproj.filters b/picoquictest/picoquictest.vcxproj.filters
index 029e3320e..a1a792095 100644
--- a/picoquictest/picoquictest.vcxproj.filters
+++ b/picoquictest/picoquictest.vcxproj.filters
@@ -168,6 +168,9 @@
Source Files
+
+ Source Files
+
Source Files