nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
Loading...
Searching...
No Matches
service.hpp
Go to the documentation of this file.
1// nros-cpp: Service server class
2// Freestanding C++ — no exceptions, no STL required
3
10#ifndef NROS_CPP_SERVICE_HPP
11#define NROS_CPP_SERVICE_HPP
12
13#include <cstdint>
14#include <cstddef>
15
16#include "nros/config.hpp"
17#include "nros/result.hpp"
18
19#include "nros_cpp_ffi.h"
20
21// Phase 189.M3.3.e — `nros_cpp_service_server_register` is excluded from
22// cbindgen (its Rust signature uses `RawServiceCallback`, an external-crate
23// type alias cbindgen names without defining). Declare it locally with a plain
24// function-pointer typedef matching the ABI (`bool(req, req_len, resp,
25// resp_cap, resp_len, ctx)`).
26extern "C" {
27typedef bool (*nros_cpp_service_request_callback_t)(const uint8_t* req, size_t req_len,
28 uint8_t* resp, size_t resp_cap,
29 size_t* resp_len, void* ctx);
30
32 const char* service_name, const char* type_name,
33 const char* type_hash, nros_cpp_qos_t qos,
35 void* context, uint8_t sched_context,
36 size_t* out_handle_id);
37} // extern "C"
38
39namespace nros {
40
59template <typename S> class Service {
60 public:
61 using RequestType = typename S::Request;
62 using ResponseType = typename S::Response;
63
69 void* ctx);
70
80 if (!initialized_) return Result(ErrorCode::NotInitialized);
81 uint8_t buf[RequestType::SERIALIZED_SIZE_MAX];
82 size_t len = 0;
83 int64_t seq = 0;
85 nros_cpp_service_server_try_recv_raw(storage_, buf, sizeof(buf), &len, &seq);
86 if (ret != 0) return Result(ret);
87 if (len == 0) return Result(ErrorCode::TryAgain);
88 if (RequestType::ffi_deserialize(buf, len, &req) != 0) return Result(ErrorCode::Error);
89 seq_id = seq;
90 return Result::success();
91 }
92
99 if (!initialized_) return Result(ErrorCode::NotInitialized);
100 uint8_t buf[ResponseType::SERIALIZED_SIZE_MAX];
101 size_t len = 0;
102 if (ResponseType::ffi_serialize(&resp, buf, sizeof(buf), &len) != 0) {
103 return Result(ErrorCode::Error);
104 }
106 }
107
109 bool is_valid() const { return initialized_; }
110
118 if (initialized_ && !callback_mode_) {
120 }
121 initialized_ = false;
122 }
123
124 // Move semantics (non-copyable). Poll-style relocation goes through the
125 // `nros_cpp_service_server_relocate` runtime call (Phase 84.C1). A
126 // callback-style service must NOT be moved after register — the arena holds
127 // `this` as the trampoline context (Phase 189.M3.3.e); the move only
128 // transfers bookkeeping and leaves that pointer stale, so don't.
130 : initialized_(other.initialized_), user_fn_(other.user_fn_),
131 user_fn_ctx_(other.user_fn_ctx_), user_ctx_(other.user_ctx_),
132 handle_id_(other.handle_id_), callback_mode_(other.callback_mode_) {
133 if (other.initialized_ && !other.callback_mode_) {
134 nros_cpp_service_server_relocate(other.storage_, storage_);
135 }
136 other.initialized_ = false;
137 }
138
140 if (this != &other) {
141 if (initialized_ && !callback_mode_) {
143 }
144 initialized_ = other.initialized_;
145 user_fn_ = other.user_fn_;
146 user_fn_ctx_ = other.user_fn_ctx_;
147 user_ctx_ = other.user_ctx_;
148 handle_id_ = other.handle_id_;
149 callback_mode_ = other.callback_mode_;
150 if (other.initialized_ && !other.callback_mode_) {
151 nros_cpp_service_server_relocate(other.storage_, storage_);
152 }
153 other.initialized_ = false;
154 }
155 return *this;
156 }
157
160 Service() : storage_(), initialized_(false) {}
161
164 size_t handle_id() const { return handle_id_; }
165
166 private:
167 Service(const Service&) = delete;
168 Service& operator=(const Service&) = delete;
169
170 friend class Node;
171
176 static bool request_trampoline(const uint8_t* req, size_t req_len, uint8_t* resp,
177 size_t resp_cap, size_t* resp_len, void* ctx) {
178 auto* self = static_cast<Service*>(ctx);
179 if (self == nullptr) return false;
181 if (RequestType::ffi_deserialize(req, req_len, &request) != 0) return false;
183 if (self->user_fn_ != nullptr) {
184 self->user_fn_(request, response);
185 } else if (self->user_fn_ctx_ != nullptr) {
186 self->user_fn_ctx_(request, response, self->user_ctx_);
187 } else {
188 return false;
189 }
190 size_t len = 0;
191 if (ResponseType::ffi_serialize(&response, resp, resp_cap, &len) != 0) return false;
192 *resp_len = len;
193 return true;
194 }
195
196 alignas(8) uint8_t storage_[NROS_SERVICE_SERVER_SIZE];
197 bool initialized_;
198 // Callback-style state (Phase 189.M3.3.e); unused in poll mode.
199 TypedServiceFn user_fn_ = nullptr;
200 TypedServiceFnWithCtx user_fn_ctx_ = nullptr;
201 void* user_ctx_ = nullptr;
202 size_t handle_id_ = static_cast<size_t>(-1);
203 bool callback_mode_ = false;
204};
205
206} // namespace nros
207
208// Phase 84.G8: out-of-line definition of Node::create_service<S>().
209#include "nros/node.hpp"
210
211namespace nros {
212
213template <typename S>
215 if (!initialized_) return Result(ErrorCode::NotInitialized);
217 ffi_qos.reliability = static_cast<nros_cpp_qos_reliability_t>(qos.reliability_raw());
218 ffi_qos.durability = static_cast<nros_cpp_qos_durability_t>(qos.durability_raw());
219 ffi_qos.history = static_cast<nros_cpp_qos_history_t>(qos.history_raw());
220 ffi_qos.liveliness_kind = static_cast<nros_cpp_qos_liveliness_t>(qos.liveliness_raw());
221 ffi_qos.depth = qos.depth();
222 ffi_qos.deadline_ms = qos.deadline_ms();
223 ffi_qos.lifespan_ms = qos.lifespan_ms();
224 ffi_qos.liveliness_lease_ms = qos.liveliness_lease_ms();
225 ffi_qos.avoid_ros_namespace_conventions = qos.avoid_ros_namespace_conventions() ? 1 : 0;
227 &handle_, service_name, S::TYPE_NAME, S::Request::TYPE_HASH, ffi_qos, out.storage_);
228 if (ret == 0) {
229 out.initialized_ = true;
230 }
231 return Result(ret);
232}
233
234// Phase 189.M3.3.e — callback-style (arena-registered) service. The arena owns
235// the server + dispatches `out`'s request handler during spin_once, so the
236// handle is real and `options.sched_context` is functional.
237template <typename S, typename F, typename>
239 const ServiceOptions& options) {
240 if (!initialized_) return Result(ErrorCode::NotInitialized);
242 ffi_qos.reliability = static_cast<nros_cpp_qos_reliability_t>(qos.reliability_raw());
243 ffi_qos.durability = static_cast<nros_cpp_qos_durability_t>(qos.durability_raw());
244 ffi_qos.history = static_cast<nros_cpp_qos_history_t>(qos.history_raw());
245 ffi_qos.liveliness_kind = static_cast<nros_cpp_qos_liveliness_t>(qos.liveliness_raw());
246 ffi_qos.depth = qos.depth();
247 ffi_qos.deadline_ms = qos.deadline_ms();
248 ffi_qos.lifespan_ms = qos.lifespan_ms();
249 ffi_qos.liveliness_lease_ms = qos.liveliness_lease_ms();
250 ffi_qos.avoid_ros_namespace_conventions = qos.avoid_ros_namespace_conventions() ? 1 : 0;
251
252 // Store the user handler (compile error if F isn't convertible).
253 out.user_fn_ = typename Service<S>::TypedServiceFn(callback);
254 out.user_fn_ctx_ = nullptr;
255 out.user_ctx_ = nullptr;
256
257 uint8_t sched = (options.sched_context == SCHED_CONTEXT_UNSET)
258 ? 0u
259 : static_cast<uint8_t>(options.sched_context);
260 size_t handle = static_cast<size_t>(-1);
262 &handle_, service_name, S::TYPE_NAME, S::Request::TYPE_HASH, ffi_qos,
264 &out, sched, &handle);
265 if (ret == 0) {
266 out.handle_id_ = handle;
267 out.callback_mode_ = true;
268 out.initialized_ = true;
269 }
270 return Result(ret);
271}
272
273} // namespace nros
274
275#endif // NROS_CPP_SERVICE_HPP
Definition future.hpp:40
Definition node.hpp:158
Result create_service(Service< S > &out, const char *service_name, const QoS &qos=QoS::services())
Definition service.hpp:214
Definition qos.hpp:67
Definition result.hpp:52
static constexpr Result success()
Named constructors.
Definition result.hpp:74
Definition service.hpp:59
void(*)(const RequestType &request, ResponseType &response, void *ctx) TypedServiceFnWithCtx
Definition service.hpp:69
~Service()
Definition service.hpp:117
Result try_recv_request(RequestType &req, int64_t &seq_id)
Definition service.hpp:79
typename S::Request RequestType
Definition service.hpp:61
Service(Service &&other)
Definition service.hpp:129
Service & operator=(Service &&other)
Definition service.hpp:139
Result send_reply(int64_t seq_id, const ResponseType &resp)
Definition service.hpp:98
typename S::Response ResponseType
Definition service.hpp:62
void(*)(const RequestType &request, ResponseType &response) TypedServiceFn
Definition service.hpp:67
size_t handle_id() const
Definition service.hpp:164
Service()
Definition service.hpp:160
bool is_valid() const
Check if the service is initialized and valid.
Definition service.hpp:109
Inline storage-size macros for opaque entity buffers.
int nros_cpp_ret_t
Definition future.hpp:20
Definition nros.hpp:42
@ Error
Generic failure not covered by a more specific code.
@ TryAgain
Transient — no data ready yet (non-blocking take). Retry later.
nros::Node and global session helpers.
nros_cpp_qos_history_t
Definition qos.hpp:31
nros_cpp_qos_liveliness_t
Definition qos.hpp:35
nros_cpp_qos_durability_t
Definition qos.hpp:27
nros_cpp_qos_reliability_t
Definition qos.hpp:23
nros::Result, nros::ErrorCode, and the NROS_TRY macro.
bool(* nros_cpp_service_request_callback_t)(const uint8_t *req, size_t req_len, uint8_t *resp, size_t resp_cap, size_t *resp_len, void *ctx)
Definition service.hpp:27
nros_cpp_ret_t nros_cpp_service_server_register(const nros_cpp_node_t *node, const char *service_name, const char *type_name, const char *type_hash, nros_cpp_qos_t qos, nros_cpp_service_request_callback_t callback, void *context, uint8_t sched_context, size_t *out_handle_id)
Definition qos.hpp:41