From cf3ca2efd6ac58577e1250db64745b77ca8a4d62 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Wed, 10 Apr 2024 13:13:33 +0100 Subject: [PATCH 1/8] Don't compile in IPv6 to the firewall when IPv6 is disabled. --- lib/firewall/firewall.cc | 17 +++++++++++++++-- lib/firewall/xmake.lua | 5 +++++ lib/netapi/NetAPI.cc | 22 +++++++++++++++++++--- lib/tcpip/network_wrapper.cc | 2 +- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/lib/firewall/firewall.cc b/lib/firewall/firewall.cc index 1c0965d..97aa105 100644 --- a/lib/firewall/firewall.cc +++ b/lib/firewall/firewall.cc @@ -48,8 +48,10 @@ namespace enum class EtherType : uint16_t { IPv4 = 0x0008, +#ifdef ENABLE_IPV6 IPv6 = 0xDD86, - ARP = 0x0608, +#endif + ARP = 0x0608, }; const char *ethertype_as_string(EtherType etherType) @@ -58,8 +60,10 @@ namespace { case EtherType::IPv4: return "IPv4"; +#ifdef ENABLE_IPV6 case EtherType::IPv6: return "IPv6"; +#endif case EtherType::ARP: return "ARP"; default: @@ -479,15 +483,18 @@ namespace } return ret; } +#ifdef ENABLE_IPV6 // For now, permit all outbound IPv6 packets. + // FIXME: Check the firewall for IPv6! case EtherType::IPv6: { Debug::log("Permitting outbound IPv6 packet"); return true; break; } +#endif } - return true; + return false; } bool packet_filter_ingress(const uint8_t *data, size_t length) @@ -503,9 +510,12 @@ namespace reinterpret_cast(const_cast(data)); switch (ethernetHeader->etherType) { +#ifdef ENABLE_IPV6 // For now, testing with v6 disabled. + // FIXME: Check the firewall for IPv6! case EtherType::IPv6: return true; +#endif case EtherType::ARP: Debug::log("Saw ARP frame"); return true; @@ -704,6 +714,7 @@ namespace } } // namespace +#ifdef ENABLE_IPV6 void firewall_add_tcpipv6_endpoint(uint8_t *remoteAddress, uint16_t localPort, uint16_t remotePort) @@ -748,3 +759,5 @@ void firewall_remove_udpipv6_remote_endpoint(uint8_t *remoteAddress, IPProtocolNumber::UDP, *copy, localPort, remotePort); } } + +#endif diff --git a/lib/firewall/xmake.lua b/lib/firewall/xmake.lua index e1af39d..19d36f6 100644 --- a/lib/firewall/xmake.lua +++ b/lib/firewall/xmake.lua @@ -1,6 +1,11 @@ compartment("Firewall") add_includedirs("../../include") + on_load(function(target) + target:add('options', "IPv6") + local IPv6 = get_config("IPv6") + target:add("defines", "CHERIOT_RTOS_OPTION_IPv6=" .. tostring(IPv6)) + end) --FIXME: The FreeRTOS compat headers need to work with this mode! --add_defines("CHERIOT_NO_AMBIENT_MALLOC", "CHERIOT_NO_NEW_DELETE") add_files("firewall.cc") diff --git a/lib/netapi/NetAPI.cc b/lib/netapi/NetAPI.cc index 3b2ad0a..4b6cbfe 100644 --- a/lib/netapi/NetAPI.cc +++ b/lib/netapi/NetAPI.cc @@ -63,7 +63,15 @@ SObj network_socket_connect_tcp(Timeout *timeout, Debug::log("Failed to resolve host"); return nullptr; } - bool isIPv6 = address.kind == NetworkAddress::AddressKindIPv6; + bool isIPv6 = address.kind == NetworkAddress::AddressKindIPv6; + if constexpr (!UseIPv6) + { + if (isIPv6) + { + Debug::log("IPv6 is not supported"); + return nullptr; + } + } auto sealedSocket = network_socket_create_and_bind( timeout, mallocCapability, isIPv6, ConnectionTypeTCP); auto kind = network_socket_kind(sealedSocket); @@ -148,8 +156,16 @@ NetworkAddress network_socket_udp_authorise_host(Timeout *timeout, } if (isIPv6) { - firewall_add_udpipv6_endpoint( - address.ipv6, kind.localPort, ntohs(host->port)); + if constexpr (!UseIPv6) + { + Debug::log("IPv6 is not supported"); + return {NetworkAddress::AddressKindInvalid}; + } + else + { + firewall_add_udpipv6_endpoint( + address.ipv6, kind.localPort, ntohs(host->port)); + } } else { diff --git a/lib/tcpip/network_wrapper.cc b/lib/tcpip/network_wrapper.cc index 93ad8e4..e2b6030 100644 --- a/lib/tcpip/network_wrapper.cc +++ b/lib/tcpip/network_wrapper.cc @@ -580,7 +580,7 @@ int network_socket_close(Timeout *t, SObj mallocCapability, SObj sealedSocket) // happen in practice and has no impact for us. FreeRTOS_shutdown(socket->socket, FREERTOS_SHUT_RDWR); auto localPort = ntohs(socket->socket->usLocalPort); - if (socket->socket->bits.bIsIPv6) + if (UseIPv6 && socket->socket->bits.bIsIPv6) { if (isTCP) { From 349c9f1eeca82db7edbf2b2cca9b21246c134de9 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Wed, 17 Apr 2024 15:47:37 +0100 Subject: [PATCH 2/8] Generate random MAC addresses for soft MACs --- lib/firewall/firewall.cc | 65 +++++++++++++++++++++++++++++++++++++++- lib/firewall/firewall.h | 7 +++++ lib/tcpip/startup.cc | 2 +- 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/lib/firewall/firewall.cc b/lib/firewall/firewall.cc index 97aa105..14cf650 100644 --- a/lib/firewall/firewall.cc +++ b/lib/firewall/firewall.cc @@ -7,6 +7,7 @@ #include //#include #include +#include #include #include #include @@ -86,6 +87,43 @@ namespace */ using MACAddress = std::array; + /** + * Returns the MAC address for the network interface. + */ + MACAddress &mac_address() + { + static MACAddress macAddress = []() { + auto ðernet = lazy_network_interface(); + if constexpr (EthernetDevice::has_unique_mac_address()) + { + return ethernet.mac_address_default(); + } + else + { + std::array macAddress; + EntropySource entropy; + for (auto &byte : macAddress) + { + byte = entropy(); + } + // Set the local bit (second bit transmitted from first byte) to + // 1 to indicate a locally administered MAC + macAddress[0] |= 0b01; + // Make sure that the broadcast bit is 0 + macAddress[0] &= ~0b1; + Debug::log("MAC address: {}:{}:{}:{}:{}:{}", + macAddress[0], + macAddress[1], + macAddress[2], + macAddress[3], + macAddress[4], + macAddress[5]); + return macAddress; + } + }(); + return macAddress; + } + /** * Ethernet header. */ @@ -499,6 +537,8 @@ namespace bool packet_filter_ingress(const uint8_t *data, size_t length) { + static constinit MACAddress broadcastMAC = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; // Not a valid Ethernet frame (64 bytes including four-byte FCS, which // is stripped by this point). if (length < 60) @@ -508,6 +548,19 @@ namespace } EthernetHeader *ethernetHeader = reinterpret_cast(const_cast(data)); + if ((ethernetHeader->destination != mac_address()) && + (ethernetHeader->destination != broadcastMAC)) + { + Debug::log( + "Dropping frame with destination MAC address {}:{}:{}:{}:{}:{}", + ethernetHeader->destination[0], + ethernetHeader->destination[1], + ethernetHeader->destination[2], + ethernetHeader->destination[3], + ethernetHeader->destination[4], + ethernetHeader->destination[5]); + return false; + } switch (ethernetHeader->etherType) { #ifdef ENABLE_IPV6 @@ -550,7 +603,9 @@ bool ethernet_driver_start() } Debug::log("Initialising network interface"); auto ðernet = lazy_network_interface(); - ethernet.mac_address_set(); + // If the device has a unique MAC address, use it. Otherwise, generate a + // random locally administered one. + ethernet.mac_address_set(mac_address()); // Poke the barrier and make the driver thread start. barrier = 2; barrier.notify_one(); @@ -761,3 +816,11 @@ void firewall_remove_udpipv6_remote_endpoint(uint8_t *remoteAddress, } #endif + +uint8_t *firewall_mac_address_get() +{ + CHERI::Capability ret{mac_address().data()}; + ret.permissions() &= {CHERI::Permission::Load, CHERI::Permission::Global}; + ret.bounds() = 6; + return ret; +} diff --git a/lib/firewall/firewall.h b/lib/firewall/firewall.h index 0f8e595..78fda69 100644 --- a/lib/firewall/firewall.h +++ b/lib/firewall/firewall.h @@ -151,3 +151,10 @@ void __cheri_compartment("Firewall") firewall_remove_udpipv6_remote_endpoint(uint8_t *remoteAddress, uint16_t localPort, uint16_t remotePort); + +/** + * Get the MAC address of the ethernet device. + * + * Returns a read-only capability to the MAC address. + */ +uint8_t *__cheri_compartment("Firewall") firewall_mac_address_get(); diff --git a/lib/tcpip/startup.cc b/lib/tcpip/startup.cc index 16a7285..af254ae 100644 --- a/lib/tcpip/startup.cc +++ b/lib/tcpip/startup.cc @@ -90,7 +90,7 @@ void __cheri_compartment("TCPIP") network_start() NetMask, GatewayAddress, DNSServerAddress, - KunyanEthernet::mac_address_default().data()); + firewall_mac_address_get()); // Enable DHCP endpointIPv4.bits.bWantDHCP = pdTRUE; From 56d4738913bb19b228daf2b27c2549f9d9c02d5c Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Wed, 10 Apr 2024 13:17:01 +0100 Subject: [PATCH 3/8] Make the mutex protecting the MQTT structure recursive. --- include/mqtt.h | 6 ++++++ lib/mqtt/mqtt.cc | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/include/mqtt.h b/include/mqtt.h index 93892d2..b60f885 100644 --- a/include/mqtt.h +++ b/include/mqtt.h @@ -257,6 +257,12 @@ int __cheri_compartment("MQTT") mqtt_unsubscribe(Timeout *t, * Fetch ACK and PUBLISH notifications on a given MQTT connection, and keep * the connection alive. * + * This function will invoke the callbacks passed to `mqtt_connect`. The + * connection object is protected by a recursive mutex, so these callbacks can + * call additional publish and subscribe functions. If doing so, care must be + * taken to ensure that the buffer is not exhausted. Calling `mqtt_run` from a + * callback is not supported. + * * The return value is zero if notifications were successfully fetched, or a * negative error code. * diff --git a/lib/mqtt/mqtt.cc b/lib/mqtt/mqtt.cc index df19d4f..23692a4 100644 --- a/lib/mqtt/mqtt.cc +++ b/lib/mqtt/mqtt.cc @@ -68,7 +68,7 @@ namespace NetworkContext_t networkContext; // Lock on which the whole public API synchronizes. - FlagLockPriorityInherited lock; + RecursiveMutex lock; /** * Constructor of the CHERIoT MQTT context object. We keep From 3663ac415cab143937b96d43dcb7818690b6acee Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Wed, 10 Apr 2024 13:17:40 +0100 Subject: [PATCH 4/8] Move the MQTT buffer out of the MQTT state structure. For larger buffers, this does not fit in the maximum size of sealed objects. --- lib/mqtt/mqtt.cc | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/lib/mqtt/mqtt.cc b/lib/mqtt/mqtt.cc index 23692a4..6831fad 100644 --- a/lib/mqtt/mqtt.cc +++ b/lib/mqtt/mqtt.cc @@ -80,15 +80,22 @@ namespace } /** - * Destructor of the CHERIoT MQTT context object. This takes - * care of closing the TLS link, and de-allocating all objects. + * Destroy the CHERIoT MQTT context object. This takes care of closing + * the TLS link, and de-allocating all objects. */ - ~CHERIoTMqttContext() + void destroy(SObj allocator) { Timeout t{UnlimitedTimeout}; tls_connection_close(&t, tlsHandle); + heap_free(allocator, networkBuffer.pBuffer); } + /** + * No destructor. Implicit deletion is not allowed, `destroy` must be + * called explicitly. + */ + ~CHERIoTMqttContext() = delete; + /** * Following this we allocate variable length data: * - incoming publishes (array of MQTTPubAckInfo_t) @@ -534,7 +541,7 @@ SObj mqtt_connect(Timeout *t, // coreMQTT), we can assume that the allocator zeroes out for us. size_t handleSize = sizeof(CHERIoTMqttContext) - - sizeof(CHERIoTMqttContext::variableLengthData) + networkBufferSize + + sizeof(CHERIoTMqttContext::variableLengthData) + sizeof(MQTTPubAckInfo_t) * (incomingPublishCount + outgoingPublishCount); // Create a sealed MQTT handle. @@ -575,8 +582,14 @@ SObj mqtt_connect(Timeout *t, reinterpret_cast(&context->variableLengthData); MQTTPubAckInfo_t *outgoingPublishes = incomingPublishes + incomingPublishCount; - uint8_t *networkBuffer = reinterpret_cast(outgoingPublishes) + - sizeof(MQTTPubAckInfo_t) * outgoingPublishCount; + uint8_t *networkBuffer = + static_cast(heap_allocate(t, allocator, networkBufferSize)); + + if (networkBuffer == nullptr) + { + token_obj_destroy(allocator, mqtt_key(), sealedMQTTHandle); + return nullptr; + } // Initialize context nested structures. context->networkContext.tlsHandle = tlsHandle; @@ -594,7 +607,7 @@ SObj mqtt_connect(Timeout *t, // `token_obj_destroy` will free the `CHERIoTMqttContext` // object through `heap_free`, but not call its destructor. We // must do that manually. - context->~CHERIoTMqttContext(); + context->destroy(allocator); token_obj_destroy(allocator, mqtt_key(), sealedMQTTHandle); }; std::unique_ptr sealedContext{ @@ -787,7 +800,7 @@ int mqtt_disconnect(Timeout *t, SObj allocator, SObj mqttHandle) t, mqttHandle, [&](CHERIoTMqttContext *connection) { - connection->~CHERIoTMqttContext(); + connection->destroy(allocator); token_obj_destroy(allocator, mqtt_key(), mqttHandle); return 0; }, From 28092288d4064080f0f7fdf4bdc0a5d49bdb77f6 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Tue, 9 Apr 2024 13:55:24 +0100 Subject: [PATCH 5/8] Initial version of the demo (WIP). JavaScript VM is linked but not yet used. 16 KiB free. --- demos/2024-04-23-cheritech/demo.cc | 366 +++++++++++++++++++++++++ demos/2024-04-23-cheritech/host.cert.h | 30 ++ demos/2024-04-23-cheritech/xmake.lua | 67 +++++ 3 files changed, 463 insertions(+) create mode 100644 demos/2024-04-23-cheritech/demo.cc create mode 100644 demos/2024-04-23-cheritech/host.cert.h create mode 100644 demos/2024-04-23-cheritech/xmake.lua diff --git a/demos/2024-04-23-cheritech/demo.cc b/demos/2024-04-23-cheritech/demo.cc new file mode 100644 index 0000000..8a62271 --- /dev/null +++ b/demos/2024-04-23-cheritech/demo.cc @@ -0,0 +1,366 @@ +// Copyright SCI Semiconductor and CHERIoT Contributors. +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "host.cert.h" +#include "thread.h" +#include "timeout.h" +//#include "mosquitto.org.h" + +using CHERI::Capability; + +using Debug = ConditionalDebug; +constexpr bool UseIPv6 = CHERIOT_RTOS_OPTION_IPv6; + +// MQTT network buffer sizes +constexpr const size_t networkBufferSize = 1024; +constexpr const size_t incomingPublishCount = 100; +constexpr const size_t outgoingPublishCount = 100; + +namespace +{ + + DECLARE_AND_DEFINE_CONNECTION_CAPABILITY(DemoHost, + "cheriot.demo", + 8883, + ConnectionTypeTCP); + + DECLARE_AND_DEFINE_ALLOCATOR_CAPABILITY(mqttTestMalloc, 32 * 1024); + + constexpr const char *ledTopic = "cheri-led"; + int32_t ledSubscribePacketId = -1; + bool ledAckReceived = false; + + constexpr const char *buttonTopic = "cheri-button"; + int buttonCounter = 0; + + /// Helpers + + /// Returns a weak pseudo-random number. + uint64_t rand() + { + EntropySource rng; + return rng(); + } + + /** + * Note from the MQTT 3.1.1 spec: + * The Server MUST allow ClientIds which are between 1 and 23 UTF-8 encoded + * bytes in length, and that contain only the characters + * "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + * + * Note from us: + * UTF-8 encoding of 0-9, a-z, A-Z, is 1 Byte per character, so we should be + * able to do up to a length of 22 characters + zero byte. + */ + constexpr const int clientIDlength = 23; + constexpr const int clientIDPrefixLength = 8; + char clientID[clientIDlength] = "cheriot-XXXXXXXXXXXXXX"; + + /** + * Turn an LED on. + */ + void gpios_on() + { + MMIO_CAPABILITY(GPIO, gpio_led0)->enable_all(); + } + + /** + * Turn an LED on. + */ + void led_on(int32_t index) + { + MMIO_CAPABILITY(GPIO, gpio_led0)->led_on(index); + } + + /** + * Turn an LED off. + */ + void led_off(int32_t index) + { + MMIO_CAPABILITY(GPIO, gpio_led0)->led_off(index); + } + + /** + * Read a single button. + */ + int32_t read_button(int32_t index) + { + return MMIO_CAPABILITY(GPIO, gpio_led0)->button(index); + } + + uint32_t read_switches() + { + return MMIO_CAPABILITY(GPIO, gpio_led0)->switches(); + } + + /// Callbacks + + void __cheri_callback ackCallback(uint16_t packetID, bool isReject) + { + if (packetID == ledSubscribePacketId) + { + ledAckReceived = true; + } + } + + void __cheri_callback publishCallback(const char *topicName, + size_t topicNameLength, + const void *payload, + size_t payloadLength) + { + // TODO check input pointers + + const char *payloadStr = static_cast(payload); + size_t length = std::min(strlen(ledTopic), topicNameLength); + if (strncmp(topicName, ledTopic, length) == 0) + { + if (payloadLength >= 1) + { + switch (static_cast(payload)[0]) + { + case '0': + led_off(0); + led_off(1); + return; + case '1': + led_on(0); + led_off(1); + return; + case '2': + led_off(0); + led_on(1); + return; + case '3': + led_on(0); + led_on(1); + return; + } + } + } + + Debug::log("Received PUBLISH notification with invalid topic ({}) or " + "payload ({}).", + std::string_view{topicName, topicNameLength}, + std::string_view{payloadStr, payloadLength}); + } + + uint32_t switches; + +} // namespace + +/// Main demo + +void __cheri_compartment("mqtt_demo") demo() +{ + int ret; + Timeout t{MS_TO_TICKS(5000)}; + + gpios_on(); + + Debug::log("Starting MQTT demo..."); + network_start(); + Debug::log("Network is ready..."); + + // systemd decides to restart the ntp server when it detects a new + // interface. If we try to get NTP time too quickly, the server isn't + // ready. Wait one second to give it time to stabilise. + { + Timeout oneSecond(MS_TO_TICKS(1000)); + thread_sleep(&oneSecond); + } + + // SNTP must be run for the TLS stack to be able to check certificate dates. + Debug::log("Fetching NTP time..."); + t = Timeout{MS_TO_TICKS(1000)}; + while (sntp_update(&t) != 0) + { + Debug::log("Failed to update NTP time"); + t = Timeout{MS_TO_TICKS(1000)}; + } + + { + timeval tv; + int ret = gettimeofday(&tv, nullptr); + if (ret != 0) + { + Debug::log("Failed to get time of day: {}", ret); + } + else + { + // Truncate the epoch time to 32 bits for printing. + Debug::log("Current UNIX epoch time: {}", (int32_t)tv.tv_sec); + } + } + + while (true) + { + Debug::log("Generating client ID..."); + mqtt_generate_client_id(clientID + clientIDPrefixLength, + clientIDlength - clientIDPrefixLength - 1); + + Debug::log("Connecting to MQTT broker..."); + Debug::log("Quota left: {}", heap_quota_remaining(MALLOC_CAPABILITY)); + t = UnlimitedTimeout; + SObj handle = mqtt_connect(&t, + STATIC_SEALED_VALUE(mqttTestMalloc), + STATIC_SEALED_VALUE(DemoHost), + publishCallback, + ackCallback, + TAs, + TAs_NUM, + networkBufferSize, + incomingPublishCount, + outgoingPublishCount, + clientID, + strlen(clientID)); + + if (!Capability{handle}.is_valid()) + { + Debug::log("Failed to connect, retrying..."); + Timeout pause{MS_TO_TICKS(1000)}; + thread_sleep(&pause); + continue; + } + + Debug::log("Connected to MQTT broker!"); + + Debug::log("Subscribing to LED topic '{}'.", ledTopic); + + ret = mqtt_subscribe(&t, + handle, + 1, // QoS 1 = delivered at least once + ledTopic, + strlen(ledTopic)); + + if (ret < 0) + { + Debug::log("Failed to subscribe, error {}.", ret); + mqtt_disconnect(&t, STATIC_SEALED_VALUE(mqttTestMalloc), handle); + continue; + } + + ledSubscribePacketId = ret; + + Debug::log("Now fetching the SUBACKs."); + + while (!ledAckReceived) + { + t = Timeout{MS_TO_TICKS(100)}; + ret = mqtt_run(&t, handle); + + if (ret < 0) + { + Debug::log("Failed to wait for the SUBACK, error {}.", ret); + mqtt_disconnect(&t, STATIC_SEALED_VALUE(mqttTestMalloc), handle); + continue; + } + } + + Timeout coolDown{0}; + Debug::log("Now entering the main loop."); + // Hugo: If I comment out the next line, it will try a reconnect + // immediately. For some reason that I haven't been able to track down + // yet, this fails 100% of the time. + while (true) + { + SystickReturn timestampBefore = thread_systemtick_get(); + + // Check for PUBLISHes + t = Timeout{MS_TO_TICKS(100)}; + ret = mqtt_run(&t, handle); + + if (ret < 0) + { + Debug::log("Failed to wait for PUBLISHes, error {}.", ret); + break; + } + + uint32_t newSwitches = read_switches(); + if (newSwitches != switches) + { + for (int i = 0; i < 8; i++) + { + bool newSwitch = (newSwitches & (1 << i)) != 0; + bool oldSwitch = (switches & (1 << i)) != 0; + if (newSwitch != oldSwitch) + { + Debug::log("Setting switch {} to {}.", i, newSwitch ? "ON" : "OFF"); + char topic[] = "cheri-switch-X"; + topic[sizeof(topic) - 2] = '0' + i; + t = Timeout{MS_TO_TICKS(5000)}; + ret = mqtt_publish(&t, + handle, + 0, // Don't want acks for this one + topic, + sizeof(topic) - 1, + newSwitch ? "ON" : "OFF", + newSwitch ? 2 : 3); + Debug::log("Free heap space: {}", heap_available()); + if (ret < 0) + { + Debug::log( + "Failed to publish button change, error {}.", + ret); + break; + } + } + } + switches = newSwitches; + } + + // Check the button + if (read_button(0) && !coolDown.remaining) + { + Debug::log("Publishing {} to button topic '{}'.", + buttonCounter, + buttonTopic); + + char num_char[20] = {0}; + snprintf(num_char, 20, "%lu", buttonCounter); + + t = Timeout{MS_TO_TICKS(5000)}; + ret = mqtt_publish(&t, + handle, + 0, // Don't want acks for this one + buttonTopic, + strlen(buttonTopic), + static_cast(num_char), + strlen(num_char)); + + if (ret < 0) + { + Debug::log("Failed to publish, error {}.", ret); + break; + } + + buttonCounter++; + + // Set cool down timer + coolDown = Timeout{MS_TO_TICKS(500)}; + continue; + } + + SystickReturn timestampAfter = thread_systemtick_get(); + // Timeouts should not overflow a 32 bit value + coolDown.elapse(timestampAfter.lo - timestampBefore.lo); + } + Debug::log("Exiting main loop, cleaning up."); + mqtt_disconnect(&t, STATIC_SEALED_VALUE(mqttTestMalloc), handle); + // Sleep for a second to allow the network stack to clean up any + // outstanding allocations + Timeout oneSecond{MS_TO_TICKS(1000)}; + thread_sleep(&oneSecond); + } +} diff --git a/demos/2024-04-23-cheritech/host.cert.h b/demos/2024-04-23-cheritech/host.cert.h new file mode 100644 index 0000000..21b1f14 --- /dev/null +++ b/demos/2024-04-23-cheritech/host.cert.h @@ -0,0 +1,30 @@ +static const unsigned char TA0_DN[] = { + 0x30, 0x17, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, + 0x0C, 0x63, 0x68, 0x65, 0x72, 0x69, 0x6F, 0x74, 0x2E, 0x64, 0x65, 0x6D, + 0x6F +}; + +static const unsigned char TA0_EC_Q[] = { + 0x04, 0xDB, 0x3A, 0xBE, 0xAF, 0x9F, 0x69, 0xF9, 0x02, 0xA4, 0xA0, 0xA7, + 0x41, 0x82, 0xE5, 0xF1, 0x06, 0x5E, 0x0A, 0xA1, 0x7E, 0x43, 0x61, 0xBE, + 0xA0, 0x1F, 0x22, 0x3D, 0x5A, 0xB9, 0xFB, 0xA3, 0x86, 0xF3, 0xF2, 0xB3, + 0x59, 0xE0, 0x93, 0xC6, 0xE0, 0x9F, 0xA6, 0x22, 0x51, 0x10, 0x2A, 0xFF, + 0x5E, 0x20, 0x96, 0x27, 0xD6, 0x8C, 0x08, 0x59, 0x0E, 0x58, 0x6C, 0xA6, + 0x87, 0xA5, 0x9A, 0x96, 0x02 +}; + +static const br_x509_trust_anchor TAs[1] = { + { + { (unsigned char *)TA0_DN, sizeof TA0_DN }, + BR_X509_TA_CA, + { + BR_KEYTYPE_EC, + { .ec = { + BR_EC_secp256r1, + (unsigned char *)TA0_EC_Q, sizeof TA0_EC_Q, + } } + } + } +}; + +#define TAs_NUM 1 diff --git a/demos/2024-04-23-cheritech/xmake.lua b/demos/2024-04-23-cheritech/xmake.lua new file mode 100644 index 0000000..212afe9 --- /dev/null +++ b/demos/2024-04-23-cheritech/xmake.lua @@ -0,0 +1,67 @@ +-- Copyright SCI Semiconductor and 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 MQTT Demo") + +includes(sdkdir) + +set_toolchains("cheriot-clang") + +includes(path.join(sdkdir, "lib")) +includes("../../lib") + +option("board") + set_default("ibex-arty-a7-100") + +compartment("mqtt_demo") + set_default(false) + add_includedirs("../../include") + add_deps("freestanding", "TCPIP", "NetAPI", "TLS", "Firewall", "SNTP", "MQTT", "time_helpers", "debug", "microvium") + -- stdio only needed for debug prints in MQTT, can be removed with --debug-mqtt=n + add_deps("stdio") + add_files("demo.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("cheritech-demo") + set_policy("build.warning", true) + add_deps("mqtt_demo") + add_options("tls-rsa") + on_load(function(target) + target:values_set("board", "$(board)") + target:values_set("threads", { + { + compartment = "mqtt_demo", + priority = 1, + entry_point = "demo", + -- TLS requires *huge* stacks! + stack_size = 8160, + trusted_stack_frames = 7 + }, + { + -- TCP/IP stack thread. + compartment = "TCPIP", + priority = 1, + entry_point = "ip_thread_entry", + stack_size = 0x1000, + trusted_stack_frames = 5 + }, + { + -- Firewall thread, handles incoming packets as they arrive. + 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) From a8eb3fd6f436bf516a5f79b478f83bb5f867b886 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Wed, 10 Apr 2024 14:50:58 +0100 Subject: [PATCH 6/8] Update the demo to fetch JavaScript from the server and run it. --- demos/2024-04-23-cheritech/cheri.js | 87 ++++++ demos/2024-04-23-cheritech/demo.cc | 313 +++++++++----------- demos/2024-04-23-cheritech/microvium-ffi.h | 317 +++++++++++++++++++++ 3 files changed, 542 insertions(+), 175 deletions(-) create mode 100644 demos/2024-04-23-cheritech/cheri.js create mode 100644 demos/2024-04-23-cheritech/microvium-ffi.h diff --git a/demos/2024-04-23-cheritech/cheri.js b/demos/2024-04-23-cheritech/cheri.js new file mode 100644 index 0000000..cb0ea1a --- /dev/null +++ b/demos/2024-04-23-cheritech/cheri.js @@ -0,0 +1,87 @@ +// FFI Imports +// Each function imported from the host environment needs to be assigned to a +// global like this and identified by a constant that the resolver in the C/C++ +// code will understand. +// These constants are defined in the `Exports` enumeration. + + +var FFINumber = 1; + +/** + * Log function, writes all arguments to the UART. + */ +export const print = vmImport(FFINumber++); + +/** + * led_on(index). + * + * Turns on the LED at the specified index. + */ +export const led_on = vmImport(FFINumber++); + +/** + * led_off(index). + * + * Turns off the LED at the specified index. + */ +export const led_off = vmImport(FFINumber++); + +/** + * buttons_read(). + * + * Reads the value of all of the buttons, returning a 4-bit value indicating + * the states of all of them. + */ +export const buttons_read = vmImport(FFINumber++); + +/** + * switches_read(). + * + * Reads the value of all of the switches, returning a 4-bit value indicating + * the states of all of them. + */ +export const switches_read = vmImport(FFINumber++); + + +export const mqtt_publish = vmImport(FFINumber++); +export const mqtt_subscribe = vmImport(FFINumber++); + +/** + * led_set(index, state). + * + * Turns the LED at the specified index on or off depending on whether state is + * true or false. + */ +export function led_set(index, state) +{ + if (state) + { + led_on(index); + } + else + { + led_off(index); + } +} + +/** + * button_read(index). + * + * Reads the value of the button at the specified index. + */ +export function button_read(index) +{ + return (buttons_read() & (1 << index)) !== 0; +} + + +/** + * switch_read(index). + * + * Reads the value of the switch at the specified index. + */ +export function switch_read(index) +{ + return (switches_read() & (1 << index)) !== 0; +} + diff --git a/demos/2024-04-23-cheritech/demo.cc b/demos/2024-04-23-cheritech/demo.cc index 8a62271..6f2c14d 100644 --- a/demos/2024-04-23-cheritech/demo.cc +++ b/demos/2024-04-23-cheritech/demo.cc @@ -11,12 +11,12 @@ #include #include #include +#include #include +#include #include "host.cert.h" -#include "thread.h" -#include "timeout.h" -//#include "mosquitto.org.h" +#include "microvium-ffi.h" using CHERI::Capability; @@ -24,7 +24,7 @@ using Debug = ConditionalDebug; constexpr bool UseIPv6 = CHERIOT_RTOS_OPTION_IPv6; // MQTT network buffer sizes -constexpr const size_t networkBufferSize = 1024; +constexpr const size_t networkBufferSize = 2048; constexpr const size_t incomingPublishCount = 100; constexpr const size_t outgoingPublishCount = 100; @@ -38,21 +38,15 @@ namespace DECLARE_AND_DEFINE_ALLOCATOR_CAPABILITY(mqttTestMalloc, 32 * 1024); - constexpr const char *ledTopic = "cheri-led"; - int32_t ledSubscribePacketId = -1; - bool ledAckReceived = false; + constexpr std::string_view CodeTopic{"cheri-code"}; + int32_t codeSubscribePacketId = -1; + bool codeAckReceived = false; constexpr const char *buttonTopic = "cheri-button"; int buttonCounter = 0; - /// Helpers - - /// Returns a weak pseudo-random number. - uint64_t rand() - { - EntropySource rng; - return rng(); - } + std::unique_ptr vm; + std::vector bytecode; /** * Note from the MQTT 3.1.1 spec: @@ -64,54 +58,17 @@ namespace * UTF-8 encoding of 0-9, a-z, A-Z, is 1 Byte per character, so we should be * able to do up to a length of 22 characters + zero byte. */ - constexpr const int clientIDlength = 23; - constexpr const int clientIDPrefixLength = 8; + constexpr const int clientIDlength = 23; + constexpr const int clientIDPrefixLength = 8; char clientID[clientIDlength] = "cheriot-XXXXXXXXXXXXXX"; - /** - * Turn an LED on. - */ - void gpios_on() - { - MMIO_CAPABILITY(GPIO, gpio_led0)->enable_all(); - } - - /** - * Turn an LED on. - */ - void led_on(int32_t index) - { - MMIO_CAPABILITY(GPIO, gpio_led0)->led_on(index); - } - - /** - * Turn an LED off. - */ - void led_off(int32_t index) - { - MMIO_CAPABILITY(GPIO, gpio_led0)->led_off(index); - } - - /** - * Read a single button. - */ - int32_t read_button(int32_t index) - { - return MMIO_CAPABILITY(GPIO, gpio_led0)->button(index); - } - - uint32_t read_switches() - { - return MMIO_CAPABILITY(GPIO, gpio_led0)->switches(); - } - /// Callbacks void __cheri_callback ackCallback(uint16_t packetID, bool isReject) { - if (packetID == ledSubscribePacketId) + if (packetID == codeSubscribePacketId) { - ledAckReceived = true; + codeAckReceived = true; } } @@ -120,43 +77,96 @@ namespace const void *payload, size_t payloadLength) { - // TODO check input pointers + std::string_view topic{topicName, topicNameLength}; + // FIXME: This is a work around for a compiler bug. __builtin_memcmp + // is being expanded to a call to memcmp with the wrong calling + // convention and so we get linker errors. + // if (topic == CodeTopic) + if ((CodeTopic.size() == topic.size()) && + (memcmp(topic.data(), CodeTopic.data(), CodeTopic.size()) == 0)) + { + Debug::log("Received new JavaScript code."); + vm.reset(); + bytecode.clear(); + bytecode.reserve(payloadLength); + bytecode.insert(bytecode.end(), + static_cast(payload), + static_cast(payload) + + payloadLength); + Debug::log("Copied JavaScript bytecode"); + mvm_VM *rawVm; + auto err = mvm_restore( + &rawVm, /* Out pointer to the VM */ + bytecode.data(), /* Bytecode data */ + bytecode.size(), /* Bytecode length */ + MALLOC_CAPABILITY, /* Capability used to allocate memory */ + ::resolve_import); /* Callback used to resolve FFI imports */ + if (err == MVM_E_SUCCESS) + { + Debug::log("Successfully loaded bytecode."); + vm.reset(rawVm); + } + else + { + // If this is not valid bytecode, give up. + Debug::log("Failed to parse bytecode: {}", err); + } + // Don't try to handle the new-code message in JavaScript. + return; + } - const char *payloadStr = static_cast(payload); - size_t length = std::min(strlen(ledTopic), topicNameLength); - if (strncmp(topicName, ledTopic, length) == 0) + if (!vm) { - if (payloadLength >= 1) + return; + } + + mvm_Value callback; + if (mvm_resolveExports(vm.get(), &ExportPublished, &callback, 1) == + MVM_E_SUCCESS) + { + mvm_Value args[2]; + args[0] = mvm_newString(vm.get(), topicName, topicNameLength); + args[1] = mvm_newString( + vm.get(), static_cast(payload), payloadLength); + // Set a limit of bytecodes to execute, to prevent infinite loops. + mvm_stopAfterNInstructions(vm.get(), 20000); + // Call the function: + int err = mvm_call(vm.get(), callback, nullptr, args, 2); + if (err != MVM_E_SUCCESS) { - switch (static_cast(payload)[0]) - { - case '0': - led_off(0); - led_off(1); - return; - case '1': - led_on(0); - led_off(1); - return; - case '2': - led_off(0); - led_on(1); - return; - case '3': - led_on(0); - led_on(1); - return; - } + Debug::log("Failed to call publish callback function: {}", err); } } + } + + /// Handle to the MQTT connection. + SObj handle; - Debug::log("Received PUBLISH notification with invalid topic ({}) or " - "payload ({}).", - std::string_view{topicName, topicNameLength}, - std::string_view{payloadStr, payloadLength}); + bool export_mqtt_publish(std::string_view topic, std::string_view message) + { + Timeout t{UnlimitedTimeout}; + Debug::log("Publishing message to topic '{}' ({}): '{}' ({})", + topic, + topic.size(), + message, + message.size()); + auto ret = mqtt_publish(&t, + handle, + 0, // Don't want acks for this one + topic.data(), + topic.size(), + message.data(), + message.size()); + Debug::log("Publish returned {}", ret); + return ret == 0; } - uint32_t switches; + bool export_mqtt_subscribe(std::string_view topic) + { + Timeout t{MS_TO_TICKS(100)}; + auto ret = mqtt_subscribe(&t, handle, 1, topic.data(), topic.size()); + return ret >= 0; + } } // namespace @@ -167,7 +177,7 @@ void __cheri_compartment("mqtt_demo") demo() int ret; Timeout t{MS_TO_TICKS(5000)}; - gpios_on(); + MMIO_CAPABILITY(GPIO, gpio_led0)->enable_all(); Debug::log("Starting MQTT demo..."); network_start(); @@ -206,25 +216,27 @@ void __cheri_compartment("mqtt_demo") demo() while (true) { + vm.reset(); + bytecode.clear(); Debug::log("Generating client ID..."); mqtt_generate_client_id(clientID + clientIDPrefixLength, clientIDlength - clientIDPrefixLength - 1); Debug::log("Connecting to MQTT broker..."); Debug::log("Quota left: {}", heap_quota_remaining(MALLOC_CAPABILITY)); - t = UnlimitedTimeout; - SObj handle = mqtt_connect(&t, - STATIC_SEALED_VALUE(mqttTestMalloc), - STATIC_SEALED_VALUE(DemoHost), - publishCallback, - ackCallback, - TAs, - TAs_NUM, - networkBufferSize, - incomingPublishCount, - outgoingPublishCount, - clientID, - strlen(clientID)); + t = UnlimitedTimeout; + handle = mqtt_connect(&t, + STATIC_SEALED_VALUE(mqttTestMalloc), + STATIC_SEALED_VALUE(DemoHost), + publishCallback, + ackCallback, + TAs, + TAs_NUM, + networkBufferSize, + incomingPublishCount, + outgoingPublishCount, + clientID, + strlen(clientID)); if (!Capability{handle}.is_valid()) { @@ -236,13 +248,13 @@ void __cheri_compartment("mqtt_demo") demo() Debug::log("Connected to MQTT broker!"); - Debug::log("Subscribing to LED topic '{}'.", ledTopic); + Debug::log("Subscribing to JavaScript code topic '{}'.", CodeTopic); ret = mqtt_subscribe(&t, handle, 1, // QoS 1 = delivered at least once - ledTopic, - strlen(ledTopic)); + CodeTopic.data(), + CodeTopic.size()); if (ret < 0) { @@ -251,110 +263,61 @@ void __cheri_compartment("mqtt_demo") demo() continue; } - ledSubscribePacketId = ret; + codeSubscribePacketId = ret; Debug::log("Now fetching the SUBACKs."); - while (!ledAckReceived) + while (!codeAckReceived) { - t = Timeout{MS_TO_TICKS(100)}; + t = Timeout{MS_TO_TICKS(1000)}; ret = mqtt_run(&t, handle); if (ret < 0) { - Debug::log("Failed to wait for the SUBACK, error {}.", ret); - mqtt_disconnect(&t, STATIC_SEALED_VALUE(mqttTestMalloc), handle); + Debug::log( + "Failed to wait for the SUBACK for the code node, error {}.", + ret); + mqtt_disconnect( + &t, STATIC_SEALED_VALUE(mqttTestMalloc), handle); continue; } } Timeout coolDown{0}; Debug::log("Now entering the main loop."); - // Hugo: If I comment out the next line, it will try a reconnect - // immediately. For some reason that I haven't been able to track down - // yet, this fails 100% of the time. while (true) { - SystickReturn timestampBefore = thread_systemtick_get(); - // Check for PUBLISHes - t = Timeout{MS_TO_TICKS(100)}; + t = Timeout{MS_TO_TICKS(100)}; + // Debug::log("{} bytes of heap free", heap_available()); ret = mqtt_run(&t, handle); - if (ret < 0) + if ((ret < 0) && (ret != -ETIMEDOUT)) { Debug::log("Failed to wait for PUBLISHes, error {}.", ret); break; } - uint32_t newSwitches = read_switches(); - if (newSwitches != switches) + if (!vm) { - for (int i = 0; i < 8; i++) - { - bool newSwitch = (newSwitches & (1 << i)) != 0; - bool oldSwitch = (switches & (1 << i)) != 0; - if (newSwitch != oldSwitch) - { - Debug::log("Setting switch {} to {}.", i, newSwitch ? "ON" : "OFF"); - char topic[] = "cheri-switch-X"; - topic[sizeof(topic) - 2] = '0' + i; - t = Timeout{MS_TO_TICKS(5000)}; - ret = mqtt_publish(&t, - handle, - 0, // Don't want acks for this one - topic, - sizeof(topic) - 1, - newSwitch ? "ON" : "OFF", - newSwitch ? 2 : 3); - Debug::log("Free heap space: {}", heap_available()); - if (ret < 0) - { - Debug::log( - "Failed to publish button change, error {}.", - ret); - break; - } - } - } - switches = newSwitches; + continue; } - // Check the button - if (read_button(0) && !coolDown.remaining) + mvm_Value callback; + if (mvm_resolveExports(vm.get(), &ExportTick, &callback, 1) == + MVM_E_SUCCESS) { - Debug::log("Publishing {} to button topic '{}'.", - buttonCounter, - buttonTopic); - - char num_char[20] = {0}; - snprintf(num_char, 20, "%lu", buttonCounter); - - t = Timeout{MS_TO_TICKS(5000)}; - ret = mqtt_publish(&t, - handle, - 0, // Don't want acks for this one - buttonTopic, - strlen(buttonTopic), - static_cast(num_char), - strlen(num_char)); - - if (ret < 0) + // Set a limit of bytecodes to execute, to prevent infinite + // loops. + mvm_stopAfterNInstructions(vm.get(), 20000); + // Call the function: + int err = mvm_call(vm.get(), callback, nullptr, nullptr, 0); + if (err != MVM_E_SUCCESS) { - Debug::log("Failed to publish, error {}.", ret); - break; + Debug::log("Failed to call tick callback function: {}", + err); } - - buttonCounter++; - - // Set cool down timer - coolDown = Timeout{MS_TO_TICKS(500)}; - continue; } - - SystickReturn timestampAfter = thread_systemtick_get(); - // Timeouts should not overflow a 32 bit value - coolDown.elapse(timestampAfter.lo - timestampBefore.lo); } Debug::log("Exiting main loop, cleaning up."); mqtt_disconnect(&t, STATIC_SEALED_VALUE(mqttTestMalloc), handle); diff --git a/demos/2024-04-23-cheritech/microvium-ffi.h b/demos/2024-04-23-cheritech/microvium-ffi.h new file mode 100644 index 0000000..7d81b41 --- /dev/null +++ b/demos/2024-04-23-cheritech/microvium-ffi.h @@ -0,0 +1,317 @@ +#pragma once + +#include "microvium/microvium.h" +#include +#include +#include +#include +#include +#include +#include + +/** + * Code related to the JavaScript interpreter. + */ +namespace +{ + using CHERI::Capability; + + /** + * Constants for functions exposed to JavaScript->C++ FFI + * + * The values here must match the ones used in cheri.js. + */ + enum Exports : mvm_HostFunctionID + { + Print = 1, + LEDOn, + LEDOff, + ReadButtons, + ReadSwitches, + MQTTPublish, + MQTTSubscribe, + }; + + /// Constant for the run function exposed to C++->JavaScript FFI + static constexpr mvm_VMExportID ExportTick = 1234; + static constexpr mvm_VMExportID ExportPublished = 1235; + + /** + * Template that returns JavaScript argument specified in `arg` as a C++ + * type T. + */ + template + T extract_argument(mvm_VM *vm, mvm_Value arg); + + /** + * Specialisation to return integers. + */ + template<> + __always_inline int32_t extract_argument(mvm_VM *vm, mvm_Value arg) + { + return mvm_toInt32(vm, arg); + } + + /** + * Specialisation to return booleans. + */ + template<> + __always_inline bool extract_argument(mvm_VM *vm, mvm_Value arg) + { + return mvm_toBool(vm, arg); + } + + /** + * Specialisation to return string views. + */ + template<> + __always_inline std::string_view + extract_argument(mvm_VM *vm, mvm_Value arg) + { + size_t length; + const char *buffer = mvm_toStringUtf8(vm, arg, &length); + return {buffer, length}; + } + + /** + * Populate a tuple with arguments from an array of JavaScript values. + * This uses `extract_argument` to coerce each JavaScript value to the + * expected type. + */ + template + __always_inline void + args_to_tuple(Tuple &tuple, mvm_VM *vm, mvm_Value *args) + { + if constexpr (Idx < std::tuple_size_v) + { + std::get(tuple) = extract_argument< + std::remove_reference_t(tuple))>>( + vm, args[Idx]); + args_to_tuple(tuple, vm, args); + } + } + + /** + * Helper template to extract the arguments from a function type. + */ + template + struct FunctionSignature; + + /** + * The concrete specialisation that decomposes the function type. + */ + template + struct FunctionSignature + { + /** + * A tuple type containing all of the argument types of the function + * whose type is being extracted. + */ + using ArgumentType = std::tuple; + }; + + /** + * The concrete specialisation that decomposes the function type. + */ + template + struct FunctionSignature + { + /** + * A tuple type containing all of the argument types of the function + * whose type is being extracted. + */ + using ArgumentType = std::tuple; + }; + + /** + * Call `Fn` with arguments from the Microvium arguments array. + * + * This is a wrapper that allows automatic forwarding from a function + * exported to JavaScript + */ + template + __always_inline mvm_TeError call_export(mvm_VM *vm, + mvm_Value *result, + mvm_Value *args, + uint8_t argsCount) + { + using TupleType = typename FunctionSignature< + std::remove_pointer_t>::ArgumentType; + // Return an error if we have the wrong number of arguments. + if (argsCount < std::tuple_size_v) + { + return MVM_E_UNEXPECTED; + } + // Get the arguments in a tuple. + TupleType arguments; + args_to_tuple(arguments, vm, args); + // If this returns void, we don't need to do anything with the return. + if constexpr (std::is_same_v) + { + std::apply(Fn, arguments); + } + else + { + // Coerce the return type to a JavaScript object of the correct + // type and return it. + auto primitiveResult = std::apply(Fn, arguments); + if constexpr (std::is_same_v) + { + *result = mvm_newBoolean(primitiveResult); + } + if constexpr (std::is_same_v) + { + *result = mvm_newInt32(vm, primitiveResult); + } + if constexpr (std::is_same_v) + { + *result = mvm_newString( + vm, primitiveResult.data(), primitiveResult.size()); + } + } + return MVM_E_SUCCESS; + } + + /** + * Helper that maps from Exports + */ + template + constexpr static std::nullptr_t ExportedFn = nullptr; + + /** + * Turn an LED on. + */ + void export_led_on(int32_t index) + { + MMIO_CAPABILITY(GPIO, gpio_led0)->led_on(index); + } + + template<> + constexpr static auto ExportedFn = export_led_on; + + /** + * Turn an LED off. + */ + void export_led_off(int32_t index) + { + MMIO_CAPABILITY(GPIO, gpio_led0)->led_off(index); + } + + template<> + constexpr static auto ExportedFn = export_led_off; + + /** + * Read all buttons. + */ + int32_t export_read_buttons() + { + return MMIO_CAPABILITY(GPIO, gpio_led0)->buttons(); + } + + template<> + constexpr static auto ExportedFn = export_read_buttons; + + /** + * Read all switches. + */ + int32_t export_read_switches() + { + return MMIO_CAPABILITY(GPIO, gpio_led0)->switches(); + } + + template<> + constexpr static auto ExportedFn = export_read_switches; + + /** + * Publish a message to an MQTT topic. + */ + bool export_mqtt_publish(std::string_view topic, std::string_view message); + + template<> + constexpr static auto ExportedFn = export_mqtt_publish; + + /** + * Subscribe to an MQTT topic. + */ + bool export_mqtt_subscribe(std::string_view topic); + + template<> + constexpr static auto ExportedFn = export_mqtt_subscribe; + + /** + * Base template for exported functions. Forwards to the function defined + * with `ExportedFn`. + */ + template + mvm_TeError exported_function(mvm_VM *vm, + mvm_HostFunctionID, + mvm_Value *result, + mvm_Value *args, + uint8_t argCount) + { + return call_export>(vm, result, args, argCount); + } + + /** + * Print a string passed from JavaScript. + */ + template<> + mvm_TeError exported_function(mvm_VM *vm, + mvm_HostFunctionID funcID, + mvm_Value *result, + mvm_Value *args, + uint8_t argCount) + { + // Helper to write a C string to the UART. + auto puts = [](const char *str) { + auto *uart = MMIO_CAPABILITY(Uart, uart); + while (char c = *(str++)) + { + uart->blocking_write(c); + } + }; + puts("\033[32;1m"); + // Iterate over the arguments. + for (unsigned i = 0; i < argCount; i++) + { + // Coerce the argument to a string and get it as a C string and + // write it to the UART. + puts(mvm_toStringUtf8(vm, args[i], nullptr)); + } + // Write a trailing newline + puts("\033[0m\n"); + // Unconditionally return success + return MVM_E_SUCCESS; + } + + /** + * Callback from microvium that resolves imports. + * + * This resolves each function to the template instantiation of + * `exported_function` with `funcID` as the template parameter. + */ + mvm_TeError + resolve_import(mvm_HostFunctionID funcID, void *, mvm_TfHostFunction *out) + { + return magic_enum::enum_switch( + [&](auto val) { + constexpr Exports Export = val; + *out = exported_function; + return MVM_E_SUCCESS; + }, + Exports(funcID), + MVM_E_UNRESOLVED_IMPORT); + } + + /** + * Helper that deletes a Microvium VM when used with a C++ unique pointer. + */ + struct MVMDeleter + { + void operator()(mvm_VM *mvm) const + { + mvm_free(mvm); + } + }; +} // namespace From 20c98ee57f3416ff0d28afc8ee29c2e3974d9e10 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Wed, 10 Apr 2024 15:34:41 +0100 Subject: [PATCH 7/8] Move the JavaScript into a separate compartment. --- demos/2024-04-23-cheritech/demo.cc | 130 ++------- demos/2024-04-23-cheritech/microvium-ffi.h | 317 --------------------- demos/2024-04-23-cheritech/xmake.lua | 8 +- 3 files changed, 32 insertions(+), 423 deletions(-) delete mode 100644 demos/2024-04-23-cheritech/microvium-ffi.h diff --git a/demos/2024-04-23-cheritech/demo.cc b/demos/2024-04-23-cheritech/demo.cc index 6f2c14d..f4d265a 100644 --- a/demos/2024-04-23-cheritech/demo.cc +++ b/demos/2024-04-23-cheritech/demo.cc @@ -16,7 +16,7 @@ #include #include "host.cert.h" -#include "microvium-ffi.h" +#include "javascript.hh" using CHERI::Capability; @@ -45,9 +45,6 @@ namespace constexpr const char *buttonTopic = "cheri-button"; int buttonCounter = 0; - std::unique_ptr vm; - std::vector bytecode; - /** * Note from the MQTT 3.1.1 spec: * The Server MUST allow ClientIds which are between 1 and 23 UTF-8 encoded @@ -74,7 +71,7 @@ namespace void __cheri_callback publishCallback(const char *topicName, size_t topicNameLength, - const void *payload, + const void *payloadData, size_t payloadLength) { std::string_view topic{topicName, topicNameLength}; @@ -85,90 +82,39 @@ namespace if ((CodeTopic.size() == topic.size()) && (memcmp(topic.data(), CodeTopic.data(), CodeTopic.size()) == 0)) { - Debug::log("Received new JavaScript code."); - vm.reset(); - bytecode.clear(); - bytecode.reserve(payloadLength); - bytecode.insert(bytecode.end(), - static_cast(payload), - static_cast(payload) + - payloadLength); - Debug::log("Copied JavaScript bytecode"); - mvm_VM *rawVm; - auto err = mvm_restore( - &rawVm, /* Out pointer to the VM */ - bytecode.data(), /* Bytecode data */ - bytecode.size(), /* Bytecode length */ - MALLOC_CAPABILITY, /* Capability used to allocate memory */ - ::resolve_import); /* Callback used to resolve FFI imports */ - if (err == MVM_E_SUCCESS) - { - Debug::log("Successfully loaded bytecode."); - vm.reset(rawVm); - } - else - { - // If this is not valid bytecode, give up. - Debug::log("Failed to parse bytecode: {}", err); - } - // Don't try to handle the new-code message in JavaScript. + load_javascript(payloadData, payloadLength); return; } - if (!vm) - { - return; - } - - mvm_Value callback; - if (mvm_resolveExports(vm.get(), &ExportPublished, &callback, 1) == - MVM_E_SUCCESS) - { - mvm_Value args[2]; - args[0] = mvm_newString(vm.get(), topicName, topicNameLength); - args[1] = mvm_newString( - vm.get(), static_cast(payload), payloadLength); - // Set a limit of bytecodes to execute, to prevent infinite loops. - mvm_stopAfterNInstructions(vm.get(), 20000); - // Call the function: - int err = mvm_call(vm.get(), callback, nullptr, args, 2); - if (err != MVM_E_SUCCESS) - { - Debug::log("Failed to call publish callback function: {}", err); - } - } + std::string_view payload{static_cast(payloadData), + payloadLength}; + publish(topic, payload); } /// Handle to the MQTT connection. SObj handle; - bool export_mqtt_publish(std::string_view topic, std::string_view message) - { - Timeout t{UnlimitedTimeout}; - Debug::log("Publishing message to topic '{}' ({}): '{}' ({})", - topic, - topic.size(), - message, - message.size()); - auto ret = mqtt_publish(&t, - handle, - 0, // Don't want acks for this one - topic.data(), - topic.size(), - message.data(), - message.size()); - Debug::log("Publish returned {}", ret); - return ret == 0; - } +} // namespace - bool export_mqtt_subscribe(std::string_view topic) - { - Timeout t{MS_TO_TICKS(100)}; - auto ret = mqtt_subscribe(&t, handle, 1, topic.data(), topic.size()); - return ret >= 0; - } +bool mqtt_publish(std::string_view topic, std::string_view message) +{ + Timeout t{UnlimitedTimeout}; + auto ret = mqtt_publish(&t, + handle, + 0, // Don't want acks for this one + topic.data(), + topic.size(), + message.data(), + message.size()); + return ret != 0; +} -} // namespace +bool mqtt_subscribe(std::string_view topic) +{ + Timeout t{MS_TO_TICKS(100)}; + auto ret = mqtt_subscribe(&t, handle, 1, topic.data(), topic.size()); + return ret >= 0; +} /// Main demo @@ -216,8 +162,7 @@ void __cheri_compartment("mqtt_demo") demo() while (true) { - vm.reset(); - bytecode.clear(); + load_javascript(nullptr, 0); Debug::log("Generating client ID..."); mqtt_generate_client_id(clientID + clientIDPrefixLength, clientIDlength - clientIDPrefixLength - 1); @@ -283,7 +228,6 @@ void __cheri_compartment("mqtt_demo") demo() } } - Timeout coolDown{0}; Debug::log("Now entering the main loop."); while (true) { @@ -297,27 +241,7 @@ void __cheri_compartment("mqtt_demo") demo() Debug::log("Failed to wait for PUBLISHes, error {}.", ret); break; } - - if (!vm) - { - continue; - } - - mvm_Value callback; - if (mvm_resolveExports(vm.get(), &ExportTick, &callback, 1) == - MVM_E_SUCCESS) - { - // Set a limit of bytecodes to execute, to prevent infinite - // loops. - mvm_stopAfterNInstructions(vm.get(), 20000); - // Call the function: - int err = mvm_call(vm.get(), callback, nullptr, nullptr, 0); - if (err != MVM_E_SUCCESS) - { - Debug::log("Failed to call tick callback function: {}", - err); - } - } + tick(); } Debug::log("Exiting main loop, cleaning up."); mqtt_disconnect(&t, STATIC_SEALED_VALUE(mqttTestMalloc), handle); diff --git a/demos/2024-04-23-cheritech/microvium-ffi.h b/demos/2024-04-23-cheritech/microvium-ffi.h deleted file mode 100644 index 7d81b41..0000000 --- a/demos/2024-04-23-cheritech/microvium-ffi.h +++ /dev/null @@ -1,317 +0,0 @@ -#pragma once - -#include "microvium/microvium.h" -#include -#include -#include -#include -#include -#include -#include - -/** - * Code related to the JavaScript interpreter. - */ -namespace -{ - using CHERI::Capability; - - /** - * Constants for functions exposed to JavaScript->C++ FFI - * - * The values here must match the ones used in cheri.js. - */ - enum Exports : mvm_HostFunctionID - { - Print = 1, - LEDOn, - LEDOff, - ReadButtons, - ReadSwitches, - MQTTPublish, - MQTTSubscribe, - }; - - /// Constant for the run function exposed to C++->JavaScript FFI - static constexpr mvm_VMExportID ExportTick = 1234; - static constexpr mvm_VMExportID ExportPublished = 1235; - - /** - * Template that returns JavaScript argument specified in `arg` as a C++ - * type T. - */ - template - T extract_argument(mvm_VM *vm, mvm_Value arg); - - /** - * Specialisation to return integers. - */ - template<> - __always_inline int32_t extract_argument(mvm_VM *vm, mvm_Value arg) - { - return mvm_toInt32(vm, arg); - } - - /** - * Specialisation to return booleans. - */ - template<> - __always_inline bool extract_argument(mvm_VM *vm, mvm_Value arg) - { - return mvm_toBool(vm, arg); - } - - /** - * Specialisation to return string views. - */ - template<> - __always_inline std::string_view - extract_argument(mvm_VM *vm, mvm_Value arg) - { - size_t length; - const char *buffer = mvm_toStringUtf8(vm, arg, &length); - return {buffer, length}; - } - - /** - * Populate a tuple with arguments from an array of JavaScript values. - * This uses `extract_argument` to coerce each JavaScript value to the - * expected type. - */ - template - __always_inline void - args_to_tuple(Tuple &tuple, mvm_VM *vm, mvm_Value *args) - { - if constexpr (Idx < std::tuple_size_v) - { - std::get(tuple) = extract_argument< - std::remove_reference_t(tuple))>>( - vm, args[Idx]); - args_to_tuple(tuple, vm, args); - } - } - - /** - * Helper template to extract the arguments from a function type. - */ - template - struct FunctionSignature; - - /** - * The concrete specialisation that decomposes the function type. - */ - template - struct FunctionSignature - { - /** - * A tuple type containing all of the argument types of the function - * whose type is being extracted. - */ - using ArgumentType = std::tuple; - }; - - /** - * The concrete specialisation that decomposes the function type. - */ - template - struct FunctionSignature - { - /** - * A tuple type containing all of the argument types of the function - * whose type is being extracted. - */ - using ArgumentType = std::tuple; - }; - - /** - * Call `Fn` with arguments from the Microvium arguments array. - * - * This is a wrapper that allows automatic forwarding from a function - * exported to JavaScript - */ - template - __always_inline mvm_TeError call_export(mvm_VM *vm, - mvm_Value *result, - mvm_Value *args, - uint8_t argsCount) - { - using TupleType = typename FunctionSignature< - std::remove_pointer_t>::ArgumentType; - // Return an error if we have the wrong number of arguments. - if (argsCount < std::tuple_size_v) - { - return MVM_E_UNEXPECTED; - } - // Get the arguments in a tuple. - TupleType arguments; - args_to_tuple(arguments, vm, args); - // If this returns void, we don't need to do anything with the return. - if constexpr (std::is_same_v) - { - std::apply(Fn, arguments); - } - else - { - // Coerce the return type to a JavaScript object of the correct - // type and return it. - auto primitiveResult = std::apply(Fn, arguments); - if constexpr (std::is_same_v) - { - *result = mvm_newBoolean(primitiveResult); - } - if constexpr (std::is_same_v) - { - *result = mvm_newInt32(vm, primitiveResult); - } - if constexpr (std::is_same_v) - { - *result = mvm_newString( - vm, primitiveResult.data(), primitiveResult.size()); - } - } - return MVM_E_SUCCESS; - } - - /** - * Helper that maps from Exports - */ - template - constexpr static std::nullptr_t ExportedFn = nullptr; - - /** - * Turn an LED on. - */ - void export_led_on(int32_t index) - { - MMIO_CAPABILITY(GPIO, gpio_led0)->led_on(index); - } - - template<> - constexpr static auto ExportedFn = export_led_on; - - /** - * Turn an LED off. - */ - void export_led_off(int32_t index) - { - MMIO_CAPABILITY(GPIO, gpio_led0)->led_off(index); - } - - template<> - constexpr static auto ExportedFn = export_led_off; - - /** - * Read all buttons. - */ - int32_t export_read_buttons() - { - return MMIO_CAPABILITY(GPIO, gpio_led0)->buttons(); - } - - template<> - constexpr static auto ExportedFn = export_read_buttons; - - /** - * Read all switches. - */ - int32_t export_read_switches() - { - return MMIO_CAPABILITY(GPIO, gpio_led0)->switches(); - } - - template<> - constexpr static auto ExportedFn = export_read_switches; - - /** - * Publish a message to an MQTT topic. - */ - bool export_mqtt_publish(std::string_view topic, std::string_view message); - - template<> - constexpr static auto ExportedFn = export_mqtt_publish; - - /** - * Subscribe to an MQTT topic. - */ - bool export_mqtt_subscribe(std::string_view topic); - - template<> - constexpr static auto ExportedFn = export_mqtt_subscribe; - - /** - * Base template for exported functions. Forwards to the function defined - * with `ExportedFn`. - */ - template - mvm_TeError exported_function(mvm_VM *vm, - mvm_HostFunctionID, - mvm_Value *result, - mvm_Value *args, - uint8_t argCount) - { - return call_export>(vm, result, args, argCount); - } - - /** - * Print a string passed from JavaScript. - */ - template<> - mvm_TeError exported_function(mvm_VM *vm, - mvm_HostFunctionID funcID, - mvm_Value *result, - mvm_Value *args, - uint8_t argCount) - { - // Helper to write a C string to the UART. - auto puts = [](const char *str) { - auto *uart = MMIO_CAPABILITY(Uart, uart); - while (char c = *(str++)) - { - uart->blocking_write(c); - } - }; - puts("\033[32;1m"); - // Iterate over the arguments. - for (unsigned i = 0; i < argCount; i++) - { - // Coerce the argument to a string and get it as a C string and - // write it to the UART. - puts(mvm_toStringUtf8(vm, args[i], nullptr)); - } - // Write a trailing newline - puts("\033[0m\n"); - // Unconditionally return success - return MVM_E_SUCCESS; - } - - /** - * Callback from microvium that resolves imports. - * - * This resolves each function to the template instantiation of - * `exported_function` with `funcID` as the template parameter. - */ - mvm_TeError - resolve_import(mvm_HostFunctionID funcID, void *, mvm_TfHostFunction *out) - { - return magic_enum::enum_switch( - [&](auto val) { - constexpr Exports Export = val; - *out = exported_function; - return MVM_E_SUCCESS; - }, - Exports(funcID), - MVM_E_UNRESOLVED_IMPORT); - } - - /** - * Helper that deletes a Microvium VM when used with a C++ unique pointer. - */ - struct MVMDeleter - { - void operator()(mvm_VM *mvm) const - { - mvm_free(mvm); - } - }; -} // namespace diff --git a/demos/2024-04-23-cheritech/xmake.lua b/demos/2024-04-23-cheritech/xmake.lua index 212afe9..29433b3 100644 --- a/demos/2024-04-23-cheritech/xmake.lua +++ b/demos/2024-04-23-cheritech/xmake.lua @@ -19,9 +19,7 @@ option("board") compartment("mqtt_demo") set_default(false) add_includedirs("../../include") - add_deps("freestanding", "TCPIP", "NetAPI", "TLS", "Firewall", "SNTP", "MQTT", "time_helpers", "debug", "microvium") - -- stdio only needed for debug prints in MQTT, can be removed with --debug-mqtt=n - add_deps("stdio") + add_deps("freestanding", "TCPIP", "NetAPI", "TLS", "Firewall", "SNTP", "MQTT", "time_helpers", "debug", "javascript") add_files("demo.cc") on_load(function(target) target:add('options', "IPv6") @@ -29,6 +27,10 @@ compartment("mqtt_demo") target:add("defines", "CHERIOT_RTOS_OPTION_IPv6=" .. tostring(IPv6)) end) +compartment("javascript") + add_deps("freestanding", "microvium", "debug") + add_files("javascript.cc") + firmware("cheritech-demo") set_policy("build.warning", true) add_deps("mqtt_demo") From e0adc3df651dca07ce8ce43602d87728e4d6eab0 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Thu, 18 Apr 2024 10:59:29 +0100 Subject: [PATCH 8/8] Add the things needed for the host to the demo. --- demos/2024-04-23-cheritech/morello/README.txt | 139 ++++++++++++++++++ .../morello/home/demo/.minirc.dfl | 9 ++ .../morello/home/demo/audit.sh | 21 +++ .../morello/home/demo/script/cheri.js | 87 +++++++++++ .../morello/home/demo/script/compile.sh | 5 + .../morello/home/demo/script/demo.js | 109 ++++++++++++++ .../morello/opt/etc/mosquitto/certs/cert.pem | 11 ++ .../morello/opt/etc/mosquitto/certs/key.pem | 5 + .../morello/opt/etc/mosquitto/mosquitto.conf | 8 + .../morello/usr/local/etc/ntpd.conf | 13 ++ .../morello/usr/local64/etc/dhcpd.conf | 22 +++ .../usr/local64/etc/namedb/db.cheriot.demo | 10 ++ .../usr/local64/etc/namedb/db.pool.ntp.org | 11 ++ 13 files changed, 450 insertions(+) create mode 100644 demos/2024-04-23-cheritech/morello/README.txt create mode 100644 demos/2024-04-23-cheritech/morello/home/demo/.minirc.dfl create mode 100755 demos/2024-04-23-cheritech/morello/home/demo/audit.sh create mode 100644 demos/2024-04-23-cheritech/morello/home/demo/script/cheri.js create mode 100755 demos/2024-04-23-cheritech/morello/home/demo/script/compile.sh create mode 100644 demos/2024-04-23-cheritech/morello/home/demo/script/demo.js create mode 100644 demos/2024-04-23-cheritech/morello/opt/etc/mosquitto/certs/cert.pem create mode 100644 demos/2024-04-23-cheritech/morello/opt/etc/mosquitto/certs/key.pem create mode 100644 demos/2024-04-23-cheritech/morello/opt/etc/mosquitto/mosquitto.conf create mode 100644 demos/2024-04-23-cheritech/morello/usr/local/etc/ntpd.conf create mode 100644 demos/2024-04-23-cheritech/morello/usr/local64/etc/dhcpd.conf create mode 100644 demos/2024-04-23-cheritech/morello/usr/local64/etc/namedb/db.cheriot.demo create mode 100644 demos/2024-04-23-cheritech/morello/usr/local64/etc/namedb/db.pool.ntp.org diff --git a/demos/2024-04-23-cheritech/morello/README.txt b/demos/2024-04-23-cheritech/morello/README.txt new file mode 100644 index 0000000..d1693d9 --- /dev/null +++ b/demos/2024-04-23-cheritech/morello/README.txt @@ -0,0 +1,139 @@ +Morello machine setup +===================== + +This directory contains the files that are necessary to set up the Morello machine to act as the server in this demo. + +Note: This contains the *private* key used on the server for the demo. +This would allow anyone to impersonate the server. +This does not matter because it is used *only* for the demo, never use this key for anything important! +Including the key here remove the need to generate a new header file for the client portion of the demo. + +Pure-capability packages: + +minicom + +Hybrid packages: + +bind918 +isc-dhcp44-server +jq +npm +wireshark + +Built from source: + +cheriot-audit (no port yet) +mosquitto (xsltproc is broken and the port's no-docs mode doesn't work). + +Make sure to build Release builds (-O0 is *really* slow on Morello, with -O0 Mosquitto can't keep up with two clients on FPGA!). +Install in /opt. + +The following lines need to be added to /etc/rc.conf: + +``` +# Network interface for the demo +ifconfig_ue0="inet 10.0.0.10 netmask 255.0.0.0" + +# DHCP server +dhcpd_enable="YES" # dhcpd enabled? +dhcpd_ifaces="ue0" # ethernet interface(s) +dhcpd_withumask="022" # file creation mask + +# bind +named_enable="YES" + +# NTP +ntpd_enable="YES" + +# Mosquitto +mosquitto_enable="YES" + +devfs_enable="YES" +``` + +Setting up DHCP +--------------- + +The first thing that the demo will do is try to get a DHCP lease. +This requires dhcpd to listen in the demo ethernet adaptor (configured in `rc.conf`) and to provide the host IP (10.0.0.10) as the DNS server. +The `usr/local64/etc/dhcpd.conf` file contains the configuration for the DHCP server and should be copied into `/usr/local64/etc/dhcpd.conf`. + +Setting up DNS +-------------- + +After acquiring a DHCP lease, the demo will try to look up host names via DNS. +For disconnected operation, we will fake the two DNS names (pool.ntp.org and cheriot.demo) by configuring the DNS server to be authoritative for these zones. +Add the following lines to the end of `/usr/local64/etc/namedb/named.conf`: + +``` +zone "cheriot.demo" { + type master; + file "/usr/local64/etc/namedb/db.cheriot.demo"; +}; + +zone "pool.ntp.org" { + type master; + file "/usr/local64/etc/namedb/db.pool.ntp.org"; +}; +``` + +Then copy the `db.cheriot.demo` and `db.pool.ntp.org` files from `usr/local64/etc/namedb` to `/usr/local64/etc/namedb/`. + +Setting up NTP +-------------- + +For disconnected operation, the NTP server needs to be configured to lie and pretend that it is an authoritative server when it can't sync with a real NTP server. +The following lines in /etc/ntp.conf will do this: + +``` +server 127.127.1.0 prefer +fudge 127.127.1.0 #stratum 10 +``` + +Note: It would be better to use `tos orphan 4`, but this defaults to a 10-minute timeout before deciding to become authoritative and this needs to be dropped to a few seconds. + +Setting up Mosquitto +-------------------- + +The Mosquitto MQTT server configuration is in `opt/etc/mosquitto/`. +Copy these files into `/opt/etc/mosquitto/`. +You can also copy the [rc script](https://github.com/freebsd/freebsd-ports/blob/main/net/mosquitto/files/mosquitto.in) from the port into `/usr/local/etc/rc.d/mosquitto` (replace `%%PREFIX%%` with `/opt`). +Alternatively, you can just start mosquitto manually and run it in the foreground. + +Wireshark +--------- + +To inspect the packets, use Wireshark. +This requires that the demo user has access to the `bpf` device. +The easiest way of doing this is to add the user to a group called `bpf` and add the following to `/etc/devfs.conf`: + +``` +own bpf root:bpf +perm bpf 660 +``` + +Console UART +------------ + +The `home/demo/.minirc.dfl` file contains the configuration for minicom to connect to the FPGA. +Run minicom as `minicom -c on -D /dev/ttyU1` or `minicom -c on -D /dev/ttyU3` to connect to the FPGA. +The demo user will need to have access to the USB TTY devices. +The easiest way to do this is to add the user to the `dialer` group and add the following to `/etc/devfs.conf`: + +``` +own ttyU* root:dialer +perm ttyU* 660 +``` + +Note that each FPGA has two FDTI devices, you need to use the *odd* numbered ones. + +Driving the demo +---------------- + +The auditing portions of the demo are driven by the `audit.sh` script in `home/demo`. +Drop this in a directory along with the board description JSON and the firmware JSON from the final build. + +The script to push new JavaScript, and an example JavaScript file, for the demo are in: `home/demo/script` +The `cheri.js` file here is the host interfaces, people may wish to modify `demo.js` to show dynamic code updates. +Note: MQTT does not do caching, so you must push out the JavaScript each time a new client connects. + diff --git a/demos/2024-04-23-cheritech/morello/home/demo/.minirc.dfl b/demos/2024-04-23-cheritech/morello/home/demo/.minirc.dfl new file mode 100644 index 0000000..b309104 --- /dev/null +++ b/demos/2024-04-23-cheritech/morello/home/demo/.minirc.dfl @@ -0,0 +1,9 @@ +# Machine-generated file - use setup menu in minicom to change parameters. +pu baudrate 115200 +pu bits 8 +pu parity N +pu stopbits 1 +pu rtscts No +pu addlinefeed No +pu linewrap Yes +pu addcarreturn Yes diff --git a/demos/2024-04-23-cheritech/morello/home/demo/audit.sh b/demos/2024-04-23-cheritech/morello/home/demo/audit.sh new file mode 100755 index 0000000..5898a0a --- /dev/null +++ b/demos/2024-04-23-cheritech/morello/home/demo/audit.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +if [ $# -eq 0 ] ; then + echo Query required. Try one of the following: + echo Print all connection capabilities: + echo -e \\tdata.network_stack.all_connection_capabilities + echo Is the network stack configuration valid? + echo -e "\\t'data.network_stack.valid(kunyan_ethernet)'" + echo Print all allocator capabilities and their owners: + echo -e "\\t'[ { \"owner\": owner, \"capability\": data.rtos.decode_allocator_capability(c) } | c = input.compartments[owner].imports[_] ; data.rtos.is_allocator_capability(c) ]'" + echo Print all compartments that invoke functions in the JavaScript compartment: + echo -e "\\t'data.compartment.compartments_calling(\"javascript\")'" + echo Print all compartments that invoke functions in the allocator: + echo -e "\\t'data.compartment.compartments_calling(\"allocator\")'" + echo Print all compartments that have direct access to the LEDs / switches: + echo -e "\\t'data.compartment.compartments_with_mmio_import(data.board.devices.gpio_led0)'" +else + echo "cheriot-audit --board ibex-arty-a7-100.json --firmware-report cheritech-demo.json --module network_stack.rego --query \"$1\"" + cheriot-audit --board ibex-arty-a7-100.json --firmware-report cheritech-demo.json --module network_stack.rego --query "$1" | jq +fi + diff --git a/demos/2024-04-23-cheritech/morello/home/demo/script/cheri.js b/demos/2024-04-23-cheritech/morello/home/demo/script/cheri.js new file mode 100644 index 0000000..cb0ea1a --- /dev/null +++ b/demos/2024-04-23-cheritech/morello/home/demo/script/cheri.js @@ -0,0 +1,87 @@ +// FFI Imports +// Each function imported from the host environment needs to be assigned to a +// global like this and identified by a constant that the resolver in the C/C++ +// code will understand. +// These constants are defined in the `Exports` enumeration. + + +var FFINumber = 1; + +/** + * Log function, writes all arguments to the UART. + */ +export const print = vmImport(FFINumber++); + +/** + * led_on(index). + * + * Turns on the LED at the specified index. + */ +export const led_on = vmImport(FFINumber++); + +/** + * led_off(index). + * + * Turns off the LED at the specified index. + */ +export const led_off = vmImport(FFINumber++); + +/** + * buttons_read(). + * + * Reads the value of all of the buttons, returning a 4-bit value indicating + * the states of all of them. + */ +export const buttons_read = vmImport(FFINumber++); + +/** + * switches_read(). + * + * Reads the value of all of the switches, returning a 4-bit value indicating + * the states of all of them. + */ +export const switches_read = vmImport(FFINumber++); + + +export const mqtt_publish = vmImport(FFINumber++); +export const mqtt_subscribe = vmImport(FFINumber++); + +/** + * led_set(index, state). + * + * Turns the LED at the specified index on or off depending on whether state is + * true or false. + */ +export function led_set(index, state) +{ + if (state) + { + led_on(index); + } + else + { + led_off(index); + } +} + +/** + * button_read(index). + * + * Reads the value of the button at the specified index. + */ +export function button_read(index) +{ + return (buttons_read() & (1 << index)) !== 0; +} + + +/** + * switch_read(index). + * + * Reads the value of the switch at the specified index. + */ +export function switch_read(index) +{ + return (switches_read() & (1 << index)) !== 0; +} + diff --git a/demos/2024-04-23-cheritech/morello/home/demo/script/compile.sh b/demos/2024-04-23-cheritech/morello/home/demo/script/compile.sh new file mode 100755 index 0000000..7a8718f --- /dev/null +++ b/demos/2024-04-23-cheritech/morello/home/demo/script/compile.sh @@ -0,0 +1,5 @@ +#!/bin/sh +set -e +microvium demo.js +echo Publishing code to MQTT broker +mosquitto_pub -h cheriot.demo -p 8883 --cafile /opt/etc/mosquitto/certs/cert.pem -t cheri-code -f demo.mvm-bc diff --git a/demos/2024-04-23-cheritech/morello/home/demo/script/demo.js b/demos/2024-04-23-cheritech/morello/home/demo/script/demo.js new file mode 100644 index 0000000..57adddd --- /dev/null +++ b/demos/2024-04-23-cheritech/morello/home/demo/script/demo.js @@ -0,0 +1,109 @@ +import * as host from "./cheri.js" + +var ticks = 0 +var switches = 0 + +/** + * Subscribe to a topic, print to the UART whether the subscription was + * successful. + */ +function subscribe(topic) +{ + var ret = host.mqtt_subscribe(topic) + host.print("Subscribe ", topic, " returned: ", ret) + if (ret) + { + host.print("Subscribed to", topic) + } + else + { + host.print("Failed to subscribe to ", topic) + } +} + +/** + * On first run, subscribe to the switch topics. + */ +function first_run() +{ + subscribe("cheri-switch-0") + subscribe("cheri-switch-1") +} + +/** + * Tick function, called every 100ms (roughly). + */ +function tick() +{ + if (ticks === 0) + { + first_run(); + } + ticks++ + // If we're not a lightswitch, don't do anything else. + if (host.switch_read(3)) + { + return; + } + // If we're not a lightbulb, make sure the lights are out + host.led_off(0) + host.led_off(1) + // Uncomment the next block to validate that the tick callback is being called. + /* + if (ticks % 5 === 0) + { + host.print("tick: ", ticks) + } + */ + var new_switches = host.switches_read() + if (new_switches !== switches) + { + for (var i = 0 ; i < 2 ; i++) + { + if ((new_switches & (1 << i)) !== (switches & (1 << i))) + { + host.print("Switch ", i, " changed to ", (new_switches & (1 << i)) ? "on" : "off") + host.mqtt_publish("cheri-switch-" + i, (new_switches & (1 << i)) ? "on" : "off") + } + } + switches = new_switches + } +} + +/** + * Publish notification callback, called whenever a new publish message is + * received from the MQTT broker. + */ +function message(topic, message) +{ + host.print("Received message on topic: ", topic, " message: ", message) + var switchNumber = -1 + // If we're not a lightbulb, don't do anything else. + if (!host.switch_read(3)) + { + return; + } + if (topic === "cheri-switch-0") + { + switchNumber = 0 + } + else if (topic === "cheri-switch-1") + { + switchNumber = 1 + } + else + { + return + } + if (message === "on") + { + host.led_on(switchNumber) + } + else + { + host.led_off(switchNumber) + } +} + +vmExport(1234, tick); +vmExport(1235, message); diff --git a/demos/2024-04-23-cheritech/morello/opt/etc/mosquitto/certs/cert.pem b/demos/2024-04-23-cheritech/morello/opt/etc/mosquitto/certs/cert.pem new file mode 100644 index 0000000..1ddab5d --- /dev/null +++ b/demos/2024-04-23-cheritech/morello/opt/etc/mosquitto/certs/cert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBgzCCASmgAwIBAgIUeyRaxt/cqeeZ1JByg4V4shx4lhowCgYIKoZIzj0EAwIw +FzEVMBMGA1UEAwwMY2hlcmlvdC5kZW1vMB4XDTI0MDQwODE0NTcwMVoXDTI1MDQw +ODE0NTcwMVowFzEVMBMGA1UEAwwMY2hlcmlvdC5kZW1vMFkwEwYHKoZIzj0CAQYI +KoZIzj0DAQcDQgAE2zq+r59p+QKkoKdBguXxBl4KoX5DYb6gHyI9Wrn7o4bz8rNZ +4JPG4J+mIlEQKv9eIJYn1owIWQ5YbKaHpZqWAqNTMFEwHQYDVR0OBBYEFBdDvYEz +T9pLdHbNwBVFT9wwQGVdMB8GA1UdIwQYMBaAFBdDvYEzT9pLdHbNwBVFT9wwQGVd +MA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIgb2epifZyBtLofZsk +gs5HqfpKuiMijfe3Q+H7ETP3aIwCIQDYBIR7uQ4s24mK3dcj+u5Qc6gSr/WuBZGO +xzxrtzDGTw== +-----END CERTIFICATE----- diff --git a/demos/2024-04-23-cheritech/morello/opt/etc/mosquitto/certs/key.pem b/demos/2024-04-23-cheritech/morello/opt/etc/mosquitto/certs/key.pem new file mode 100644 index 0000000..e912dcc --- /dev/null +++ b/demos/2024-04-23-cheritech/morello/opt/etc/mosquitto/certs/key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgFF2t/aeGHzLHsP4k +63Q9yIFLeU8+mtOylWjhfwwQbNihRANCAATbOr6vn2n5AqSgp0GC5fEGXgqhfkNh +vqAfIj1aufujhvPys1ngk8bgn6YiURAq/14glifWjAhZDlhspoelmpYC +-----END PRIVATE KEY----- diff --git a/demos/2024-04-23-cheritech/morello/opt/etc/mosquitto/mosquitto.conf b/demos/2024-04-23-cheritech/morello/opt/etc/mosquitto/mosquitto.conf new file mode 100644 index 0000000..2d9f63b --- /dev/null +++ b/demos/2024-04-23-cheritech/morello/opt/etc/mosquitto/mosquitto.conf @@ -0,0 +1,8 @@ +listener 8883 10.0.0.10 +tls_keyform pem +keyfile /opt/etc/mosquitto/certs/key.pem +certfile /opt/etc/mosquitto/certs/cert.pem +log_type all +allow_anonymous true +connection_messages true + diff --git a/demos/2024-04-23-cheritech/morello/usr/local/etc/ntpd.conf b/demos/2024-04-23-cheritech/morello/usr/local/etc/ntpd.conf new file mode 100644 index 0000000..84b4177 --- /dev/null +++ b/demos/2024-04-23-cheritech/morello/usr/local/etc/ntpd.conf @@ -0,0 +1,13 @@ +# $OpenBSD: ntpd.conf,v 1.16 2019/11/06 19:04:12 deraadt Exp $ +# +# See ntpd.conf(5) and /etc/examples/ntpd.conf + +servers pool.ntp.org +server time.cloudflare.com +sensor * + +listen on 10.0.0.10 + +constraint from "9.9.9.9" # quad9 v4 without DNS +constraint from "2620:fe::fe" # quad9 v6 without DNS +constraints from "www.google.com" # intentionally not 8.8.8.8 diff --git a/demos/2024-04-23-cheritech/morello/usr/local64/etc/dhcpd.conf b/demos/2024-04-23-cheritech/morello/usr/local64/etc/dhcpd.conf new file mode 100644 index 0000000..39cffce --- /dev/null +++ b/demos/2024-04-23-cheritech/morello/usr/local64/etc/dhcpd.conf @@ -0,0 +1,22 @@ +# dhcpd.conf +# +# Sample configuration file for ISC dhcpd +# + +default-lease-time 600; +max-lease-time 6000; + +# If this DHCP server is the official DHCP server for the local +# network, the authoritative directive should be uncommented. +authoritative; + +# Use this to send dhcp log messages to a different log file (you also +# have to hack syslog.conf to complete the redirection). +log-facility local7; + +subnet 10.0.0.0 netmask 255.0.0.0 { + range 10.0.0.1 10.0.0.8; + option domain-name-servers 10.0.0.10; + allow-unknown-clients; +} + diff --git a/demos/2024-04-23-cheritech/morello/usr/local64/etc/namedb/db.cheriot.demo b/demos/2024-04-23-cheritech/morello/usr/local64/etc/namedb/db.cheriot.demo new file mode 100644 index 0000000..3298085 --- /dev/null +++ b/demos/2024-04-23-cheritech/morello/usr/local64/etc/namedb/db.cheriot.demo @@ -0,0 +1,10 @@ +@ IN SOA cheriot.demo. root.cheriot.demo. ( + 2 ; Serial + 604800 ; Refresh + 86400 ; Retry + 2419200 ; Expire + 604800 ) ; Negative Cache TTL +; +@ IN NS ns.cheriot.demo. +@ IN A 10.0.0.10 +ns IN A 10.0.0.10 diff --git a/demos/2024-04-23-cheritech/morello/usr/local64/etc/namedb/db.pool.ntp.org b/demos/2024-04-23-cheritech/morello/usr/local64/etc/namedb/db.pool.ntp.org new file mode 100644 index 0000000..d197174 --- /dev/null +++ b/demos/2024-04-23-cheritech/morello/usr/local64/etc/namedb/db.pool.ntp.org @@ -0,0 +1,11 @@ +@ IN SOA pool.ntp.org. root.pool.ntp.org. ( + 2 ; Serial + 604800 ; Refresh + 86400 ; Retry + 2419200 ; Expire + 604800 ) ; Negative Cache TTL +; +@ IN NS ns.pool.ntp.org. +@ IN A 10.0.0.10 +ns IN A 10.0.0.10 +