Skip to content

Commit

Permalink
Add an HTTP server example to showcase the new TCP server API.
Browse files Browse the repository at this point in the history
Signed-off-by: Hugo Lefeuvre <[email protected]>
  • Loading branch information
hlef committed Oct 9, 2024
1 parent 4c9d323 commit 1df9fa4
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 0 deletions.
6 changes: 6 additions & 0 deletions examples/05.HTTP_SERVER/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
HTTP Server Example
===================

This example shows simple use of the TCP socket server API.
It opens server port 80 and serves a static HTTP page there.
Note that this is *not* intended as an example of how to build an HTTP server.
209 changes: 209 additions & 0 deletions examples/05.HTTP_SERVER/http_server.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
#include "timeout.h"
#include <NetAPI.h>
#include <debug.hh>
#include <errno.h>
#include <fail-simulator-on-error.h>
#include <memory>
#include <string_view>
#include <thread.h>
#include <tick_macros.h>

using CHERI::Capability;

using Debug = ConditionalDebug<true, "HTTP server example test">;
constexpr bool UseIPv6 = CHERIOT_RTOS_OPTION_IPv6;

#define LISTEN_PORT 80
DECLARE_AND_DEFINE_BIND_CAPABILITY(HTTPPort, UseIPv6, LISTEN_PORT);

DECLARE_AND_DEFINE_ALLOCATOR_CAPABILITY(TestMalloc, 32 * 1024);
#define TEST_MALLOC STATIC_SEALED_VALUE(TestMalloc)

static char reply[] =
"HTTP/1.1 200 OK\r\n"
"Content-type: text/html\r\n"
"Connection: close\r\n"
"\r\n"
"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">"
"<html>"
"<head><title>Hello from CHERIoT!</title></head>"
"<body><h1>It works!</h1><p>Served from a CHERIoT device.</p></body>"
"</html>\n";

/**
* Maximum number of clients the server will server before shutting down. This
* is useful to check that the server can handle multiple clients before
* terminating.
*/
static const uint16_t MaxClients = 10;

/**
* This example server is written to showcase the network stack server API, but
* also the network stack restart. If one of the network stack APIs fails in a
* way likely caused by a crash, the server will wait this small delay before
* retrying to give time to the network stack to reset.
*/
static const uint16_t RestartDelay = 100; // in ticks

void __cheri_compartment("http_server_example") example()
{
network_start();

auto heapAtStart = heap_quota_remaining(TEST_MALLOC);

uint16_t clientsCounter = 0;

Debug::log("Starting the server.");

while (clientsCounter < MaxClients)
{
Debug::log("Creating a listening socket.");
Timeout unlimited{UnlimitedTimeout};
auto socket = network_socket_listen_tcp(
&unlimited, TEST_MALLOC, STATIC_SEALED_VALUE(HTTPPort));

if (!Capability{socket}.is_valid())
{
Debug::log("Failed to create a listening socket.");
// This may have failed because of a network stack
// crash. Sleep a little bit to enable a reset.
Timeout sleep{RestartDelay};
thread_sleep(&sleep);
continue;
}

Debug::log("Listening on port {}...", LISTEN_PORT);
while (clientsCounter < MaxClients)
{
NetworkAddress clientAddress = {0};
uint16_t clientPort = 0;

auto clientSocket = network_socket_accept_tcp(
&unlimited, TEST_MALLOC, socket, &clientAddress, &clientPort);

if (!Capability{clientSocket}.is_valid())
{
Debug::log("Failed to establish a connection.");
Timeout sleep{RestartDelay};
thread_sleep(&sleep);
break;
}

if (!UseIPv6)
{
Debug::log("Established a connection with {}.{}.{}.{}:{}",
static_cast<int>(clientAddress.ipv4) & 0xff,
static_cast<int>(clientAddress.ipv4 >> 8) & 0xff,
static_cast<int>(clientAddress.ipv4 >> 16) & 0xff,
static_cast<int>(clientAddress.ipv4 >> 24) & 0xff,
clientPort);
}
else
{
Debug::log("Established a connection.");
}

clientsCounter++;

auto [received, buffer] =
network_socket_receive(&unlimited, TEST_MALLOC, clientSocket);

// For this simple server, we do not care about what the client
// sent (we will always serve the same content).
int ret = heap_free(TEST_MALLOC, buffer);
if (ret != 0)
{
// This may happen if the network stack crashed.
Debug::log("Failed to free receive buffer: {}", ret);
// Do not break here - we still want to free the socket.
}

if (received > 0)
{
Debug::log(
"Received {} bytes from the client, serving static content.",
received);
constexpr size_t toSend = sizeof(reply) - 1;
size_t sent = 0;
while (sent < toSend)
{
size_t remaining = toSend - sent;

size_t sentThisCall = network_socket_send(
&unlimited, clientSocket, &(reply[sent]), remaining);
Debug::log("Sent {} bytes", sentThisCall);

if (sentThisCall >= 0)
{
sent += sentThisCall;
}
else
{
Debug::log("Send failed: {}", sentThisCall);
break;
}
}
}
else
{
Debug::log(
"Failed to receive request from the client, error {}.",
received);
}

Debug::log("Terminating the connection with the client.");
int retries = 10;
// In a retry loop to be more rebust to network stack crashes.
for (; retries > 0; retries--)
{
if (network_socket_close(
&unlimited, TEST_MALLOC, clientSocket) == 0)
{
break;
}
Timeout sleep{RestartDelay};
thread_sleep(&sleep);
}

if (retries == 0)
{
Debug::log("Failed to close the client socket.");
}
}

Debug::log("Closing the listening socket.");
int retries = 10;
// In a retry loop to be more rebust to network stack crashes.
for (; retries > 0; retries--)
{
if (network_socket_close(&unlimited, TEST_MALLOC, socket) == 0)
{
break;
}
Timeout sleep{RestartDelay};
thread_sleep(&sleep);
}

if (retries == 0)
{
Debug::log("Failed to close the listening socket.");
}
}

Debug::log("Now checking for leaks.");
auto heapAtEnd = heap_quota_remaining(TEST_MALLOC);
if (heapAtEnd < heapAtStart)
{
Debug::log("Warning: The implementation leaked {} bytes (start: {} vs. "
"end: {}).",
heapAtStart - heapAtEnd,
heapAtEnd,
heapAtStart);
}
else
{
Debug::log("No leaks detected.");
}

Debug::log("Terminating the server.");
}
62 changes: 62 additions & 0 deletions examples/05.HTTP_SERVER/xmake.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
-- Copyright CHERIoT Contributors.
-- SPDX-License-Identifier: MIT

-- Update this to point to the location of the CHERIoT SDK
sdkdir = path.absolute("../../../cheriot-rtos/sdk")

set_project("CHERIoT HTTP Server Example")

includes(sdkdir)

set_toolchains("cheriot-clang")

includes(path.join(sdkdir, "lib"))
includes("../../lib")

option("board")
set_default("ibex-arty-a7-100")

compartment("http_server_example")
set_default(false)
add_includedirs("../../include")
add_deps("freestanding", "TCPIP", "NetAPI")
add_files("http_server.cc")
on_load(function(target)
target:add('options', "IPv6")
local IPv6 = get_config("IPv6")
target:add("defines", "CHERIOT_RTOS_OPTION_IPv6=" .. tostring(IPv6))
end)

firmware("05.http_server_example")
set_policy("build.warning", true)
add_deps("TCPIP", "Firewall", "NetAPI", "http_server_example", "atomic8", "debug")
on_load(function(target)
target:values_set("board", "$(board)")
target:values_set("threads", {
{
compartment = "http_server_example",
priority = 1,
entry_point = "example",
stack_size = 0xe00,
trusted_stack_frames = 6
},
{
compartment = "TCPIP",
priority = 1,
entry_point = "ip_thread_entry",
stack_size = 0xe00,
trusted_stack_frames = 5
},
{
compartment = "Firewall",
-- Higher priority, this will be back-pressured by the message
-- queue if the network stack can't keep up, but we want
-- packets to arrive immediately.
priority = 2,
entry_point = "ethernet_run_driver",
stack_size = 0x1000,
trusted_stack_frames = 5
}
}, {expand = false})
end)

0 comments on commit 1df9fa4

Please sign in to comment.