Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions include/pouch/transport/serial/broker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* Copyright (c) 2026 Golioth, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#include <pouch/port.h>

/**
* @file broker.h
* @brief Pouch Serial transport (broker side)
*
* The broker is the master of the serial exchange. It initiates all transfers,
* drives the provisioning and data exchange sequence, and owns a
* @ref pouch_serial_broker instance allocated by the transport adapter.
*
* Typical usage:
*
* 1. Embed @ref pouch_serial_broker inside your adapter context struct.
* 2. Fill in a @ref pouch_serial_broker_adapter with your ready/end callbacks
* and the adapter's maximum frame size.
* 3. Call @ref pouch_serial_broker_init once at startup.
* 4. Call @ref pouch_serial_broker_start to begin an exchange with a device.
* 5. Feed received frames to @ref pouch_serial_broker_recv.
* 6. Call @ref pouch_serial_broker_notify when the device signals that it
* has data available (e.g. via an interrupt line).
*/

struct pouch_serial_broker;
struct pouch_gateway_node_info;

/**
* Transport adapter interface for the serial broker.
*
* The adapter is responsible for all physical bus concerns (framing, length
* delimitation, error detection). It presents complete frames to the broker
* protocol layer and retrieves complete frames from it via
* @ref pouch_serial_broker_frame_get.
*/
struct pouch_serial_broker_adapter
{
/**
* Signal to the adapter that the broker has a frame ready to send.
*
* Called whenever @ref pouch_serial_broker_frame_get would return a non-zero
* length. The adapter should call @ref pouch_serial_broker_frame_get at its
* next opportunity.
*
* @param broker Broker instance.
*/
void (*ready)(const struct pouch_serial_broker *broker);

/**
* Called when the exchange sequence completes or fails.
*
* @param broker Broker instance.
* @param success true if the full exchange completed successfully.
*/
void (*end)(const struct pouch_serial_broker *broker, bool success);
};

/**
* Create a broker instance.
* The broker must be started with @ref pouch_serial_broker_start before it can
* process frames.
*
* @param adapter Serial adapter to use for this broker.
* @return Pointer to the created broker instance, or NULL on failure.
*/
struct pouch_serial_broker *pouch_serial_broker_create(
const struct pouch_serial_broker_adapter *adapter);

/**
* Destroy a broker instance and free its resources.
*
* @param broker Broker instance to destroy.
*/
void pouch_serial_broker_destroy(struct pouch_serial_broker *broker);

/**
* Start the Pouch exchange sequence with a device.
*
* Drives the full sequence:
* Info -> Server Cert (if needed) -> Device Cert (if needed) ->
* Uplink drain -> Downlink delivery
*
* The adapter's @c end callback is invoked when the sequence completes or
* fails. Only one exchange may be in progress at a time.
*
* @param broker Broker instance.
* @param node Gateway node info carrying provisioning state and gateway
* contexts. Must remain valid until @c adapter->end is called.
* @return 0 on success, negative error code if the exchange cannot be started.
*/
int pouch_serial_broker_start(struct pouch_serial_broker *broker);

/**
* Deliver a received frame from the device to the broker transport layer.
*
* Called by the transport adapter with each complete frame received from the
* device. The first byte of @p frame is the encoded header; the remaining
* bytes are the payload.
*
* @param broker Broker instance.
* @param frame Frame bytes, starting with the 1-byte header.
* @param len Total frame length in bytes (header + payload).
* @return 0 on success, negative error code on failure.
*/
int pouch_serial_broker_recv(struct pouch_serial_broker *broker, const void *frame, size_t len);

/**
* Get a frame of data to send over the serial link.
*
* Called by the transport adapter when it needs to send data to the device.
* The broker fills @p buf with the next pending frame (header + payload).
*
* @param broker Broker instance.
* @param buf Buffer that should be filled with the frame to send.
* @param maxlen Maximum number of bytes to write to @p buf. The broker will
* not write more than this many bytes.
*
* @return Number of bytes written to @p buf, or 0 if there is no frame ready.
*/
size_t pouch_serial_broker_frame_get(struct pouch_serial_broker *broker,
uint8_t *buf,
size_t maxlen);

/**
* Signal to the broker that the device has data available to send.
*
* Called by the transport adapter when the device asserts an interrupt line or
* otherwise indicates it has uplink data ready. The broker will issue a prompt
* on the uplink channel at its next opportunity.
*
* @param broker Broker instance.
*/
void pouch_serial_broker_notify(struct pouch_serial_broker *broker);

/**
* Get the adapter associated with a broker instance.
*
* @param broker Broker instance.
* @return Pointer to the adapter passed to @ref pouch_serial_broker_create.
*/
const struct pouch_serial_broker_adapter *pouch_serial_broker_adapter_get(
const struct pouch_serial_broker *broker);
20 changes: 20 additions & 0 deletions include/pouch/transport/serial/common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2026 Golioth, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once

#define POUCH_SERIAL_HEADER_LEN 1
#define POUCH_SERIAL_CH_BROKER_TO_DEVICE(ch) ((ch) & 1)

enum pouch_serial_channel_id
{
POUCH_SERIAL_CH_INFO, /**< Device -> Broker: device metadata and flags */
POUCH_SERIAL_CH_SERVER_CERT, /**< Broker -> Device: Golioth server certificate chain */
POUCH_SERIAL_CH_DEVICE_CERT, /**< Device -> Broker: device leaf certificate */
POUCH_SERIAL_CH_DOWNLINK, /**< Broker -> Device: inbound pouches */
POUCH_SERIAL_CH_UPLINK, /**< Device -> Broker: outbound pouches */

POUCH_SERIAL_CHANNEL_COUNT,
};
59 changes: 59 additions & 0 deletions include/pouch/transport/serial/device.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (c) 2026 Golioth, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once

#include <stddef.h>
#include <stdint.h>

/**
* @file device.h
* @brief Pouch Serial transport - device side.
*
* The device side is a singleton module: there is no per-instance struct
* exposed to the transport adapter. The adapter calls the module-level
* @ref pouch_serial_device_recv function to deliver received frames and
* implements the @ref pouch_serial_device_adapter vtable to transmit frames
* and optionally signal the broker when data is available.
*/

typedef void (*pouch_serial_device_ready_cb_t)(void);

/**
* Initialize the serial device transport.
*
* Must be called once before any frames are delivered via @ref pouch_serial_device_recv.
*
* @param ready_cb Optional callback to signal when the device has data available to send.
*
* @return 0 on success, negative error code on failure.
*/
void pouch_serial_device_init(pouch_serial_device_ready_cb_t ready_cb);

/**
* Deliver a received frame from the broker to the device transport layer.
*
* Called by the transport adapter with each complete frame received from the
* broker. The first byte of @p frame is the encoded header; the remaining
* bytes are the payload.
*
* @param frame Frame bytes, starting with the 1-byte header.
* @param len Total frame length in bytes (header + payload).
* @return 0 on success, negative error code on failure.
*/
int pouch_serial_device_recv(const void *frame, size_t len);

/**
* Get a frame of data to send over the serial link.
*
* Called by the transport adapter when it needs to send data.
*
* @param buf Buffer that should be filled with data to send.
* @param maxlen Maximum number of bytes to send in the buffer. The device transport
* layer will not write more than this many bytes to the buffer.
*
* @return Number of bytes written to the buffer.
*/
size_t pouch_serial_device_frame_get(uint8_t *buf, size_t maxlen);
6 changes: 3 additions & 3 deletions port/zephyr/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ menuconfig POUCH
Pouch is a protocol for communicating with Golioth from devices not
directly connected to the internet.

# Port-specific log-level constants
rsource "Kconfig.port_log_levels"

if POUCH

# Pouch common
Expand Down Expand Up @@ -128,6 +125,9 @@ endmenu
# Transport choices
rsource "transport/Kconfig"

# Port-specific log-level constants
rsource "Kconfig.port_log_levels"

endif

# Gateway
Expand Down
29 changes: 17 additions & 12 deletions port/zephyr/transport/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,22 @@
choice
prompt "Pouch transport"
optional
default POUCH_TRANSPORT_SERIAL if POUCH_TRANSPORT_SERIAL_BROKER
default POUCH_TRANSPORT_BLE_GATT if BT
help
Select the transport protocol that will carry Pouch

config POUCH_TRANSPORT_NONE
bool "NONE"
help
No transport is selected. Useful for unit testing.

Comment on lines -11 to -15

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need this for building unit tests without the dependencies of a given transport.

config POUCH_TRANSPORT_BLE_GATT
bool "BLE GATT"
depends on BT
select BT_GATT_DYNAMIC_DB
select POUCH_TRANSPORT_SAR
help
BLE GATT transport for Pouch

config POUCH_TRANSPORT_HTTP_CLIENT
bool "HTTP"
depends on POUCH
imply HTTP_CLIENT
select EVENTS
select NET_CONNECTION_MANAGER
Expand All @@ -30,24 +29,30 @@ config POUCH_TRANSPORT_HTTP_CLIENT

config POUCH_TRANSPORT_COAP_CLIENT
bool "CoAP"
depends on POUCH
select COAP
select NET_SOCKETS_SOCKOPT_TLS
select NET_SOCKETS_ENABLE_DTLS
help
CoAP/DTLS transport for Pouch

endchoice
config POUCH_TRANSPORT_SERIAL
bool "Serial"
help
Serial transport for Pouch. Implements the core serial transport,
running on top of a serial transport adapter.

config POUCH_TRANSPORT_ACK_TIMEOUT_MS
int "Pouch Transport ACK timeout (ms)"
default 1000
help
The timeout, in milliseconds, that a receiver will wait
for new packets before retransmitting an acknowledgement
Comment on lines -41 to -46

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think symbol is used by sar/receiver.c

endchoice

rsource "gatt/Kconfig"
rsource "http/Kconfig"
rsource "coap/Kconfig"
rsource "serial/Kconfig"

config POUCH_TRANSPORT_SAR
bool
help
Selected by transports that require Segmentation and Reassembly (SAR)
Comment on lines +52 to +55

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉


module = POUCH_TRANSPORT
module-str = Pouch Transport
Expand Down
6 changes: 6 additions & 0 deletions port/zephyr/transport/gatt/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ config POUCH_TRANSPORT_GATT_WINDOW_SIZE
the device. Larger numbers can increase throughput but will
use more RAM.

config POUCH_TRANSPORT_ACK_TIMEOUT_MS
int "Pouch Transport ACK timeout (ms)"
default 1000
help
The timeout, in milliseconds, that a receiver will wait
for new packets before retransmitting an acknowledgement

module = POUCH_GATT
module-str = Pouch GATT
Expand Down
28 changes: 28 additions & 0 deletions port/zephyr/transport/serial/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright (c) 2026 Golioth, Inc.
#
# SPDX-License-Identifier: Apache-2.0

menu "Pouch Serial Transport"
visible if POUCH_TRANSPORT_SERIAL

config POUCH_TRANSPORT_SERIAL_BROKER
bool
depends on POUCH_GATEWAY
default y
help
Enable the broker-based implementation of the Pouch Serial transport.

config POUCH_TRANSPORT_SERIAL_DEVICE
bool
depends on POUCH
default y
help
Enable the device implementation of the Pouch Serial transport, which
runs on the node device and communicates with a gateway running the
broker implementation.

module = POUCH_SERIAL
module-str = Pouch Serial Transport
source "subsys/logging/Kconfig.template.log_config"

endmenu
11 changes: 11 additions & 0 deletions src/gateway/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ zephyr_library_sources(
${ZEPHYR_POUCH_MODULE_DIR}/src/transport/endpoints/broker/downlink.c
${ZEPHYR_POUCH_MODULE_DIR}/src/transport/endpoints/broker/device_cert.c
${ZEPHYR_POUCH_MODULE_DIR}/src/transport/endpoints/broker/server_cert.c
)

zephyr_library_sources_ifdef(CONFIG_POUCH_GATEWAY_GATT
${ZEPHYR_POUCH_MODULE_DIR}/src/transport/sar/packet.c
${ZEPHYR_POUCH_MODULE_DIR}/src/transport/sar/receiver.c
${ZEPHYR_POUCH_MODULE_DIR}/src/transport/sar/sender.c
Expand All @@ -22,6 +25,14 @@ zephyr_library_sources(
${ZEPHYR_POUCH_MODULE_DIR}/port/zephyr/transport/gatt/broker/broker.c
${ZEPHYR_POUCH_MODULE_DIR}/port/zephyr/transport/gatt/broker/scan.c
)

zephyr_library_sources_ifdef(CONFIG_POUCH_TRANSPORT_SERIAL
${ZEPHYR_POUCH_MODULE_DIR}/src/transport/serial/channel.c
${ZEPHYR_POUCH_MODULE_DIR}/src/transport/serial/packet.c
${ZEPHYR_POUCH_MODULE_DIR}/src/transport/serial/serial.c
${ZEPHYR_POUCH_MODULE_DIR}/src/transport/serial/broker.c
)

zephyr_library_include_directories(${ZEPHYR_POUCH_MODULE_DIR}/src)

zephyr_library_link_libraries(mbedTLS)
Expand Down
Loading
Loading