Skip to content
Draft
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
20 changes: 20 additions & 0 deletions lib/api/wpc.h
Original file line number Diff line number Diff line change
Expand Up @@ -952,9 +952,29 @@ app_res_e WPC_send_data(const uint8_t * bytes,
* \param message_p
* The message to send
* \return Return code of the operation
* \note If the node role is sink and a valid SSR first-hop is available
* for the destination, SSR routing is used automatically.
*/
app_res_e WPC_send_data_with_options(const app_message_t * message_p);

/**
* \brief Enable or disable Selective Source Routing (SSR)
* \param enable
* True to enable SSR, false otherwise
* \return Return code of the operation
* \note Disabling SSR clears the learned SSR routes and prevents new
* routes from being learned until SSR is enabled again.
*/
app_res_e WPC_set_ssr_enabled(uint8_t enable);

/**
* \brief Flush all learned Selective Source Routing (SSR) routes
* \return Return code of the operation
* \note SSR remains enabled after this call and new registrations can
* repopulate the route table.
*/
app_res_e WPC_flush_ssr_routes(void);

/**
* \brief Set config data item
* \param endpoint
Expand Down
42 changes: 39 additions & 3 deletions lib/platform/linux/platform.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "logger.h"
#include "platform.h"
#include "reassembly.h"
#include "ssr.h"
#include "wpc_proto.h"

// Maximum number of indication to be retrieved from a single poll
Expand All @@ -29,6 +30,8 @@

// Mutex for sending, ie serial access
static pthread_mutex_t sending_mutex;
// Mutex for the in-process SSR routing state.
static pthread_mutex_t ssr_mutex;

// This thread is used to poll for indication
static pthread_t thread_polling;
Expand Down Expand Up @@ -309,6 +312,30 @@ void Platform_unlock_request()
pthread_mutex_unlock(&sending_mutex);
}

bool Platform_lock_ssr()
{
int res = pthread_mutex_lock(&ssr_mutex);
if (res != 0)
{
if (res == EINVAL)
{
LOGW("SSR mutex no longer exists (destroyed)\n");
}
else
{
LOGE("SSR mutex lock failed %d\n", res);
}
return false;
}

return true;
}

void Platform_unlock_ssr()
{
pthread_mutex_unlock(&ssr_mutex);
}

unsigned long long Platform_get_timestamp_ms_epoch()
{
struct timespec spec;
Expand Down Expand Up @@ -377,25 +404,33 @@ bool Platform_init(Platform_get_indication_f get_indication_f,
goto error2;
}

if (pthread_mutex_init(&ssr_mutex, &attr) != 0)
{
LOGE("SSR Mutex init failed\n");
goto error3;
}

// Start a thread to poll for indication
if (pthread_create(&thread_polling, NULL, poll_for_indication, NULL) != 0)
{
LOGE("Cannot create polling thread\n");
goto error3;
goto error4;
}

m_dispatch_thread_running = true;
// Start a thread to dispatch indication
if (pthread_create(&thread_dispatch, NULL, dispatch_indication, NULL) != 0)
{
LOGE("Cannot create dispatch thread\n");
goto error4;
goto error5;
}

return true;

error4:
error5:
pthread_kill(thread_polling, SIGKILL);
error4:
pthread_mutex_destroy(&ssr_mutex);
error3:
pthread_mutex_destroy(&m_queue_mutex);
error2:
Expand Down Expand Up @@ -431,6 +466,7 @@ void Platform_close()
}

// Destroy our mutexes
pthread_mutex_destroy(&ssr_mutex);
pthread_mutex_destroy(&m_queue_mutex);
pthread_mutex_destroy(&sending_mutex);
}
12 changes: 12 additions & 0 deletions lib/platform/platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ bool Platform_lock_request();
*/
void Platform_unlock_request();

/**
* \brief Call at the beginning of an SSR critical section.
* \note This lock protects the in-process SSR routing state, which is
* accessed both from the dispatch thread and from caller threads.
*/
bool Platform_lock_ssr();

/**
* \brief Called at the end of an SSR critical section.
*/
void Platform_unlock_ssr();

/**
* \brief Dynamic memory allocation
* \param size
Expand Down
4 changes: 3 additions & 1 deletion lib/wpc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ add_library(wpc STATIC
${CMAKE_CURRENT_LIST_DIR}/msap.c
${CMAKE_CURRENT_LIST_DIR}/slip.c
${CMAKE_CURRENT_LIST_DIR}/wpc.c
${CMAKE_CURRENT_LIST_DIR}/wpc_ssr.c
${CMAKE_CURRENT_LIST_DIR}/wpc_internal.c
${CMAKE_CURRENT_LIST_DIR}/reassembly/reassembly.c
${CMAKE_CURRENT_LIST_DIR}/ssr/fht.c
${CMAKE_CURRENT_LIST_DIR}/ssr/ssr.c
)

target_include_directories(wpc PUBLIC
Expand All @@ -17,4 +20,3 @@ target_include_directories(wpc PRIVATE
${PROJECT_SOURCE_DIR}/platform
${CMAKE_CURRENT_LIST_DIR}/include
)

72 changes: 72 additions & 0 deletions lib/wpc/include/fht.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* Wirepas Oy licensed under Apache License, Version 2.0
*
* See file LICENSE for full license details.
*
* First-Hop Table (FHT) for Selective Source Routing bookkeeping.
*
* The table stores the first hop learned for each registered node and is used
* to build SSR downlink transmissions.
*/
#ifndef FHT_H__
#define FHT_H__

#include <stdint.h>

/** Opaque first-hop table handle. */
typedef struct fht fht_t;

/** Create a new first-hop table. Returns NULL on allocation failure. */
fht_t *fht_create(void);

/** Destroy the table and free all memory. Safe to call with NULL. */
void fht_destroy(fht_t *table);

/** Remove all entries from the table while keeping its allocation. */
void fht_clear(fht_t *table);

/** Set the validity timer applied to all entries (seconds). */
void fht_set_validity(fht_t *table, uint32_t seconds);

/** Get the current validity timer (seconds). */
uint32_t fht_get_validity(const fht_t *table);

/**
* \brief Insert or update a route entry.
*
* \param table First-hop table.
* \param node_id Target node.
* \param first_hop_id Next hop toward that node (source routing anchor).
* \param timestamp Caller-provided time of this update (seconds).
* \return 0 on success, -1 on allocation failure.
*
* \note If an entry for node_id already exists and timestamp is not newer
* than the stored last_refresh, the update is silently ignored.
*/
int fht_update_route(fht_t *table,
uint32_t node_id,
uint32_t first_hop_id,
uint32_t timestamp);

/**
* \brief Look up the first hop for a node.
*
* If the entry is missing or expired, the output parameter is set to 0.
*
* \param table First-hop table.
* \param node_id Target node to look up.
* \param now Caller-provided current time (seconds).
* \param first_hop_id [out] First hop address, or 0 if not found/expired.
*/
void fht_get_first_hop(const fht_t *table,
uint32_t node_id,
uint32_t now,
uint32_t *first_hop_id);

/**
* \brief Remove all expired entries and rehash the table.
*
* \param now Caller-provided current time (seconds).
*/
void fht_purge_expired(fht_t *table, uint32_t now);

#endif /* FHT_H__ */
24 changes: 24 additions & 0 deletions lib/wpc/include/msap.h
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,22 @@ typedef struct __attribute__((__packed__))
uint8_t payload[MAXIMUM_CDC_ITEM_PAYLOAD_SIZE];
} msap_config_data_item_rx_ind_pl_t;

/**
* SSR registration indication (sink → gateway).
*
* Sent by the sink's Wirepas stack each time a mesh node registers its
* source-routing anchor. The gateway uses this information to update its
* First-Hop Table so it can build SSR-routed downlink transmissions.
*/
typedef struct __attribute__((__packed__))
{
uint8_t indication_status;
uint32_t source_address; /**< Node that sent the registration packet. */
uint32_t source_routing_id; /**< First-hop anchor (Long RD ID). */
uint32_t sink_address; /**< Sink that received the registration. */
uint32_t delay_hp; /**< End-to-end delay in 1/1024 second units. */
} msap_ssr_registration_ind_pl_t;

static inline void
convert_internal_to_app_scratchpad_status(app_scratchpad_status_t * status_p,
msap_scratchpad_status_conf_pl_t * internal_status_p)
Expand Down Expand Up @@ -561,6 +577,14 @@ void msap_scan_nbors_indication_handler(msap_scan_nbors_ind_pl_t * payload);
*/
void msap_config_data_item_rx_indication_handler(msap_config_data_item_rx_ind_pl_t * payload);

/**
* \brief Handler for SSR registration indication
* \param payload
* Pointer to the received SSR registration payload.
* Called by dispatch_indication() for MSAP_SSR_REGISTRATION_INDICATION.
*/
void msap_ssr_registration_indication_handler(msap_ssr_registration_ind_pl_t * payload);

/**
* \brief Register for app config data
* \param cb
Expand Down
125 changes: 125 additions & 0 deletions lib/wpc/include/ssr.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/* Wirepas Oy licensed under Apache License, Version 2.0
*
* See file LICENSE for full license details.
*
* SSR (Selective Source Routing) integration layer for c-mesh-api.
*
* This module maintains a First-Hop Table (FHT) fed by MSAP SSR registration
* indications received from the connected sink.
*
* Typical lifecycle:
* 1. ssr_init() - called once during WPC_initialize().
* 2. ssr_on_registration() - called by the MSAP handler each time a sink
* forwards an SSR registration packet from a mesh node.
* 3. ssr_get_first_hop() - queried by the send path or tests to retrieve
* the routing anchor for a node.
* 4. ssr_purge_expired() - called periodically from the platform dispatch
* thread (every DISPATCH_WAKEUP_TIMEOUT_S seconds).
* 5. ssr_deinit() - optional cleanup (primarily for tests).
*/
#ifndef SSR_H__
#define SSR_H__

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

/**
* \brief Callback invoked each time a new SSR registration is learned.
*
* \param source_address Node address that registered.
* \param first_hop_id First-hop anchor (source routing ID).
* \param delay_hp End-to-end delay in 1/1024 second units.
*/
typedef void (*onSsrRegistration_cb_f)(uint32_t source_address,
uint32_t first_hop_id,
uint32_t delay_hp);

/**
* \brief Initialise the SSR module and allocate the First-Hop Table.
*
* Safe to call multiple times; subsequent calls are no-ops if already
* initialised.
*/
void ssr_init(void);

/**
* \brief Release all SSR resources.
*
* After this call the module is in the same state as before ssr_init().
* Primarily intended for unit-test teardown.
*/
void ssr_deinit(void);

/**
* \brief Configure the sink address accepted by SSR registrations.
*
* Registrations learned from any other sink address are discarded.
*
* \param sink_address Node address of the connected sink.
*/
void ssr_set_sink_address(uint32_t sink_address);

/**
* \brief Disable SSR sink-address filtering until a sink address is set again.
*
* After this call all incoming registrations are discarded.
*/
void ssr_clear_sink_address(void);

/**
* \brief Clear all learned SSR routes while keeping the module initialised.
*/
void ssr_reset_routes(void);

/**
* \brief Process an incoming SSR registration from a sink.
*
* Called by the MSAP indication handler whenever
* MSAP_SSR_REGISTRATION_INDICATION is received.
*
* \param source_address Node that sent the registration packet.
* \param source_routing_id First-hop anchor chosen by the mesh for that node.
* \param sink_address Sink that received the registration.
* \param delay_hp End-to-end delay in 1/1024 second units; used to
* back-calculate the generation time of the packet.
*/
void ssr_on_registration(uint32_t source_address,
uint32_t source_routing_id,
uint32_t sink_address,
uint32_t delay_hp);

/**
* \brief Query the first hop to reach a destination node.
*
* \param dest Destination node address.
* \param first_hop_id [out] First-hop anchor address; 0 if no valid route.
* \return true if a valid, non-expired route was found.
*/
bool ssr_get_first_hop(uint32_t dest,
uint32_t *first_hop_id);

/**
* \brief Purge expired entries from the First-Hop Table.
*
* Should be called periodically (e.g. from the platform dispatch thread).
*/
void ssr_purge_expired(void);

/**
* \brief Register a callback to be notified on each new SSR registration.
*
* Only one callback can be registered at a time.
*
* \param cb Callback function.
* \return true on success, false if a callback is already registered.
*/
bool ssr_register_for_registration(onSsrRegistration_cb_f cb);

/**
* \brief Unregister the SSR registration callback.
*
* \return true on success, false if no callback was registered.
*/
bool ssr_unregister_from_registration(void);

#endif /* SSR_H__ */
Loading
Loading