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#include "nros/size_bound.hpp" // nros::rx_buffer_capacity<M> — the receive-buffer size
19
20#include "nros_cpp_ffi.h"
21
22// Phase 189.M3.3.e — `nros_cpp_service_server_register` is excluded from
23// cbindgen (its Rust signature uses `RawServiceCallback`, an external-crate
24// type alias cbindgen names without defining). Declare it locally with a plain
25// function-pointer typedef matching the ABI (`bool(req, req_len, resp,
26// resp_cap, resp_len, ctx)`).
27extern "C" {
28typedef bool (*nros_cpp_service_request_callback_t)(const uint8_t* req, size_t req_len,
29 uint8_t* resp, size_t resp_cap,
30 size_t* resp_len, void* ctx);
31
33 const char* service_name, const char* type_name,
34 const char* type_hash, nros_cpp_qos_t qos,
36 void* context, uint8_t sched_context,
37 size_t* out_handle_id);
38} // extern "C"
39
40namespace nros {
41
60template <typename S> class Service {
61 public:
62 using RequestType = typename S::Request;
63 using ResponseType = typename S::Response;
64
68 using TypedServiceFn = void (*)(const RequestType& request, ResponseType& response);
69 using TypedServiceFnWithCtx = void (*)(const RequestType& request, ResponseType& response,
70 void* ctx);
71
80 Result take_request(RequestType& req, int64_t& seq_id) {
81 return try_recv_request_sized<::nros::rx_buffer_capacity<RequestType>::value>(req, seq_id);
82 }
83
90 template <size_t Cap> Result try_recv_request_sized(RequestType& req, int64_t& seq_id) {
91 if (!initialized_) return Result(ErrorCode::NotInitialized);
92 uint8_t buf[Cap];
93 size_t len = 0;
94 int64_t seq = 0;
95 nros_cpp_ret_t ret =
96 nros_cpp_service_server_take_request_raw(storage_, buf, sizeof(buf), &len, &seq);
97 if (ret != 0) return Result(ret);
98 if (len == 0) return Result(ErrorCode::TryAgain);
99 if (RequestType::ffi_deserialize(buf, len, &req) != 0) return Result(ErrorCode::Error);
100 seq_id = seq;
101 return Result::success();
102 }
103
110 [[deprecated("Service::try_recv_request is deprecated; use Service::take_request")]] Result
111 try_recv_request(RequestType& req, int64_t& seq_id) {
112 return take_request(req, seq_id);
113 }
114
120 Result send_response(int64_t seq_id, const ResponseType& resp) {
121 if (!initialized_) return Result(ErrorCode::NotInitialized);
122 uint8_t buf[::nros::detail::buffer_bounds<ResponseType>::tx];
123 size_t len = 0;
124 if (ResponseType::ffi_serialize(&resp, buf, sizeof(buf), &len) != 0) {
125 return Result(ErrorCode::Error);
126 }
127 return Result(nros_cpp_service_server_send_response_raw(storage_, seq_id, buf, len));
128 }
129
135 [[deprecated("Service::send_reply() is deprecated; use Service::send_response()")]] Result
136 send_reply(int64_t seq_id, const ResponseType& resp) {
137 return send_response(seq_id, resp);
138 }
139
141 bool is_valid() const { return initialized_; }
142
150 if (initialized_ && !callback_mode_) {
151 nros_cpp_service_server_destroy(storage_);
152 }
153 initialized_ = false;
154 }
155
156 // Move semantics (non-copyable). Poll-style relocation goes through the
157 // `nros_cpp_service_server_relocate` runtime call (Phase 84.C1). A
158 // callback-style service must NOT be moved after register — the arena holds
159 // `this` as the trampoline context (Phase 189.M3.3.e); the move only
160 // transfers bookkeeping and leaves that pointer stale, so don't.
162 : initialized_(other.initialized_), user_fn_(other.user_fn_),
163 user_fn_ctx_(other.user_fn_ctx_), user_ctx_(other.user_ctx_),
164 handle_id_(other.handle_id_), callback_mode_(other.callback_mode_) {
165 if (other.initialized_ && !other.callback_mode_) {
166 nros_cpp_service_server_relocate(other.storage_, storage_);
167 }
168 other.initialized_ = false;
169 }
170
172 if (this != &other) {
173 if (initialized_ && !callback_mode_) {
174 nros_cpp_service_server_destroy(storage_);
175 }
176 initialized_ = other.initialized_;
177 user_fn_ = other.user_fn_;
178 user_fn_ctx_ = other.user_fn_ctx_;
179 user_ctx_ = other.user_ctx_;
180 handle_id_ = other.handle_id_;
181 callback_mode_ = other.callback_mode_;
182 if (other.initialized_ && !other.callback_mode_) {
183 nros_cpp_service_server_relocate(other.storage_, storage_);
184 }
185 other.initialized_ = false;
186 }
187 return *this;
188 }
189
192 Service() : storage_(), initialized_(false) {}
193
196 size_t handle_id() const { return handle_id_; }
197
198 private:
199 Service(const Service&) = delete;
200 Service& operator=(const Service&) = delete;
201
202 friend class Node;
203
208 static bool request_trampoline(const uint8_t* req, size_t req_len, uint8_t* resp,
209 size_t resp_cap, size_t* resp_len, void* ctx) {
210 auto* self = static_cast<Service*>(ctx);
211 if (self == nullptr) return false;
212 RequestType request;
213 if (RequestType::ffi_deserialize(req, req_len, &request) != 0) return false;
214 ResponseType response;
215 if (self->user_fn_ != nullptr) {
216 self->user_fn_(request, response);
217 } else if (self->user_fn_ctx_ != nullptr) {
218 self->user_fn_ctx_(request, response, self->user_ctx_);
219 } else {
220 return false;
221 }
222 size_t len = 0;
223 if (ResponseType::ffi_serialize(&response, resp, resp_cap, &len) != 0) return false;
224 *resp_len = len;
225 return true;
226 }
227
228 alignas(8) uint8_t storage_[NROS_SERVICE_SERVER_SIZE];
229 bool initialized_;
230 // Callback-style state (Phase 189.M3.3.e); unused in poll mode.
231 TypedServiceFn user_fn_ = nullptr;
232 TypedServiceFnWithCtx user_fn_ctx_ = nullptr;
233 void* user_ctx_ = nullptr;
234 size_t handle_id_ = static_cast<size_t>(-1);
235 bool callback_mode_ = false;
236};
237
238} // namespace nros
239
240// Phase 84.G8: out-of-line definition of Node::create_service<S>().
241#include "nros/node.hpp"
242
243namespace nros {
244
245template <typename S>
246Result Node::create_service(Service<S>& out, const char* service_name, const QoS& qos) {
247 if (!initialized_) return Result(ErrorCode::NotInitialized);
248 nros_cpp_qos_t ffi_qos = detail::qos_to_ffi(qos);
249 nros_cpp_ret_t ret = nros_cpp_service_server_create(
250 &handle_, service_name, S::TYPE_NAME, S::Request::TYPE_HASH, ffi_qos, out.storage_);
251 if (ret == 0) {
252 out.initialized_ = true;
253 }
254 return Result(ret);
255}
256
257// Phase 189.M3.3.e — callback-style (arena-registered) service. The arena owns
258// the server + dispatches `out`'s request handler during spin_once, so the
259// handle is real and `options.sched_context` is functional.
260template <typename S, typename F, typename>
261Result Node::create_service(Service<S>& out, const char* service_name, F callback, const QoS& qos,
262 const ServiceOptions& options) {
263 if (!initialized_) return Result(ErrorCode::NotInitialized);
264 nros_cpp_qos_t ffi_qos = detail::qos_to_ffi(qos);
265
266 // Store the user handler (compile error if F isn't convertible).
267 out.user_fn_ = typename Service<S>::TypedServiceFn(callback);
268 out.user_fn_ctx_ = nullptr;
269 out.user_ctx_ = nullptr;
270
271 uint8_t sched = (options.sched_context == SCHED_CONTEXT_UNSET)
272 ? 0u
273 : static_cast<uint8_t>(options.sched_context);
274 size_t handle = static_cast<size_t>(-1);
276 &handle_, service_name, S::TYPE_NAME, S::Request::TYPE_HASH, ffi_qos,
278 &out, sched, &handle);
279 if (ret == 0) {
280 out.handle_id_ = handle;
281 out.callback_mode_ = true;
282 out.initialized_ = true;
283 }
284 return Result(ret);
285}
286
287} // namespace nros
288
289#endif // NROS_CPP_SERVICE_HPP
Definition node.hpp:211
Result create_service(Service< S > &out, const char *service_name, const QoS &qos=QoS::services())
Definition service.hpp:246
Definition qos.hpp:173
Definition result.hpp:90
static constexpr Result success()
Named constructors.
Definition result.hpp:112
Definition service.hpp:60
void(*)(const RequestType &request, ResponseType &response, void *ctx) TypedServiceFnWithCtx
Definition service.hpp:70
Result try_recv_request_sized(RequestType &req, int64_t &seq_id)
Definition service.hpp:90
~Service()
Definition service.hpp:149
Result send_response(int64_t seq_id, const ResponseType &resp)
Definition service.hpp:120
Result try_recv_request(RequestType &req, int64_t &seq_id)
Definition service.hpp:111
typename S::Request RequestType
Definition service.hpp:62
Service(Service &&other)
Definition service.hpp:161
Service & operator=(Service &&other)
Definition service.hpp:171
Result send_reply(int64_t seq_id, const ResponseType &resp)
Definition service.hpp:136
typename S::Response ResponseType
Definition service.hpp:63
void(*)(const RequestType &request, ResponseType &response) TypedServiceFn
Definition service.hpp:68
size_t handle_id() const
Definition service.hpp:196
Result take_request(RequestType &req, int64_t &seq_id)
Definition service.hpp:80
Service()
Definition service.hpp:192
bool is_valid() const
Check if the service is initialized and valid.
Definition service.hpp:141
Inline storage-size macros for opaque entity buffers.
int nros_cpp_ret_t
Definition future.hpp:21
Definition nros.hpp:55
@ 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::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:28
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:54