libe3 0.2.5
C++ library for E3AP RAN vendor integration
Loading...
Searching...
No Matches
json_encoder.cpp
Go to the documentation of this file.
1/**
2 * @file json_encoder.cpp
3 * @brief JSON Encoder implementation
4 *
5 * SPDX-FileCopyrightText: Copyright (c) 2026 Northeastern University
6 * SPDX-License-Identifier: Apache-2.0
7 */
8
9#include "json_encoder.hpp"
10#include "libe3/logger.hpp"
11#include <iomanip>
12#include <optional>
13#include <sstream>
14#include <stdexcept>
15#include <utility>
16
17namespace libe3 {
18
19namespace {
20
21constexpr const char* LOG_TAG = "JsonEnc";
22
23std::string to_camel_case(const char* pascal) {
24 std::string s(pascal);
25 if (!s.empty()) s[0] = static_cast<char>(std::tolower(static_cast<unsigned char>(s[0])));
26 return s;
27}
28
29bool is_json_ws(char c) {
30 return c == ' ' || c == '\t' || c == '\n' || c == '\r';
31}
32
33size_t skip_ws(const std::string& text, size_t pos) {
34 while (pos < text.size() && is_json_ws(text[pos])) {
35 ++pos;
36 }
37 return pos;
38}
39
40// Skips a JSON string starting at text[pos] == '"'. Returns the index just
41// past the closing quote, or nullopt on truncation/malformed escapes.
42std::optional<size_t> skip_json_string(const std::string& text, size_t pos) {
43 const size_t n = text.size();
44 if (pos >= n || text[pos] != '"') return std::nullopt;
45 ++pos;
46 while (pos < n) {
47 char c = text[pos];
48 if (c == '\\') {
49 if (pos + 1 >= n) return std::nullopt;
50 if (text[pos + 1] == 'u') {
51 if (pos + 6 > n) return std::nullopt; // \ u X X X X
52 pos += 6;
53 continue;
54 }
55 pos += 2;
56 continue;
57 }
58 if (c == '"') {
59 return pos + 1;
60 }
61 ++pos;
62 }
63 return std::nullopt;
64}
65
66// Skips one JSON value starting at text[pos] (already at its first byte).
67// Returns the index just past the value, or nullopt on anything unexpected
68// or truncated. Objects/arrays are skipped by depth-counting braces/brackets
69// (with nested strings skipped via skip_json_string so a brace/bracket
70// character inside a string is never mistaken for structure); scalars (
71// numbers, true/false/null) are skipped up to the next structural delimiter
72// or whitespace.
73std::optional<size_t> skip_json_value(const std::string& text, size_t pos) {
74 const size_t n = text.size();
75 if (pos >= n) return std::nullopt;
76 char c = text[pos];
77 if (c == '"') {
78 return skip_json_string(text, pos);
79 }
80 if (c == '{' || c == '[') {
81 char open = c;
82 char close = (open == '{') ? '}' : ']';
83 int depth = 1;
84 ++pos;
85 while (pos < n && depth > 0) {
86 char cc = text[pos];
87 if (cc == '"') {
88 auto after = skip_json_string(text, pos);
89 if (!after) return std::nullopt;
90 pos = *after;
91 continue;
92 }
93 if (cc == open) {
94 ++depth;
95 } else if (cc == close) {
96 --depth;
97 }
98 ++pos;
99 }
100 if (depth != 0) return std::nullopt;
101 return pos;
102 }
103 // Bare token: number, true, false, or null.
104 while (pos < n) {
105 char cc = text[pos];
106 if (cc == ',' || cc == '}' || cc == ']' || is_json_ws(cc)) {
107 break;
108 }
109 ++pos;
110 }
111 return pos;
112}
113
114// Locates the byte span [start, end) of the value bound to `key` as a
115// top-level member of the JSON object beginning at text[obj_start]. `text`
116// is assumed to have already been proven well-formed JSON by the caller
117// (decode()'s prior nlohmann::json::parse succeeded), so this only has to
118// locate an already-guaranteed-to-exist member, not defend against
119// arbitrary malformed input; every access is still bounds-checked and any
120// unexpected byte pattern bails out to nullopt (never guesses), so a bug
121// here can only cost a caller its fast path, never correctness -- callers
122// are expected to fall back to re-serializing the parsed subtree on a miss.
123// Does not unescape keys: a key that itself contains an escape sequence
124// simply won't match and triggers the fallback (no encoder in this file
125// produces escaped key names).
126std::optional<std::pair<size_t, size_t>> find_top_level_member_span(
127 const std::string& text, size_t obj_start, const std::string& key) {
128 const size_t n = text.size();
129 if (obj_start >= n || text[obj_start] != '{') {
130 return std::nullopt;
131 }
132
133 size_t pos = obj_start + 1;
134 while (true) {
135 pos = skip_ws(text, pos);
136 if (pos >= n) return std::nullopt;
137 if (text[pos] == '}') {
138 return std::nullopt; // key not found
139 }
140 if (text[pos] != '"') return std::nullopt;
141
142 size_t key_start = pos + 1;
143 auto after_key = skip_json_string(text, pos);
144 if (!after_key) return std::nullopt;
145 size_t key_end = *after_key - 1; // index of the closing quote
146
147 pos = skip_ws(text, *after_key);
148 if (pos >= n || text[pos] != ':') return std::nullopt;
149 pos = skip_ws(text, pos + 1);
150
151 size_t value_start = pos;
152 auto after_value = skip_json_value(text, pos);
153 if (!after_value) return std::nullopt;
154
155 bool key_matches = (key_end - key_start == key.size()) &&
156 text.compare(key_start, key.size(), key) == 0;
157 if (key_matches) {
158 return std::make_pair(value_start, *after_value);
159 }
160
161 pos = skip_ws(text, *after_value);
162 if (pos < n && text[pos] == ',') {
163 ++pos;
164 continue;
165 }
166 return std::nullopt; // '}' (key not found) or anything unexpected
167 }
168}
169
170} // anonymous namespace
171
172
173// ============================================================================
174// Helper methods for type conversions
175// ============================================================================
176
177std::optional<PduType> JsonE3Encoder::string_to_pdu_type(const std::string& s) const {
178 if (s == "setupRequest") return PduType::SETUP_REQUEST;
179 if (s == "setupResponse") return PduType::SETUP_RESPONSE;
180 if (s == "subscriptionRequest") return PduType::SUBSCRIPTION_REQUEST;
181 if (s == "subscriptionDelete") return PduType::SUBSCRIPTION_DELETE;
182 if (s == "subscriptionResponse") return PduType::SUBSCRIPTION_RESPONSE;
183 if (s == "indicationMessage") return PduType::INDICATION_MESSAGE;
184 if (s == "dAppControlAction") return PduType::DAPP_CONTROL_ACTION;
185 if (s == "dAppReport") return PduType::DAPP_REPORT;
186 if (s == "xAppControlAction") return PduType::XAPP_CONTROL_ACTION;
187 if (s == "releaseMessage") return PduType::RELEASE_MESSAGE;
188 if (s == "messageAck") return PduType::MESSAGE_ACK;
189 return std::nullopt;
190}
191
192ErrorCode JsonE3Encoder::string_to_error_code(const std::string& s) const {
193 if (s == "SUCCESS") return ErrorCode::SUCCESS;
194 if (s == "INVALID_PARAM") return ErrorCode::INVALID_PARAM;
195 if (s == "TIMEOUT") return ErrorCode::TIMEOUT;
196 if (s == "NOT_FOUND") return ErrorCode::NOT_FOUND;
197 return ErrorCode::GENERIC_ERROR;
198}
199
200// ============================================================================
201// Binary encoding helpers
202// ============================================================================
203
204std::string JsonE3Encoder::binary_to_hex(const std::vector<uint8_t>& data) {
205 std::ostringstream ss;
206 for (uint8_t b : data) {
207 ss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(b);
208 }
209 return ss.str();
210}
211
212std::vector<uint8_t> JsonE3Encoder::hex_to_binary(const std::string& hex) {
213 std::vector<uint8_t> result;
214 result.reserve(hex.size() / 2);
215 for (size_t i = 0; i + 1 < hex.size(); i += 2) {
216 result.push_back(static_cast<uint8_t>(
217 std::stoi(hex.substr(i, 2), nullptr, 16)));
218 }
219 return result;
220}
221
222// ============================================================================
223// Encoding helpers for each PDU type
224// ============================================================================
225
226nlohmann::json JsonE3Encoder::encode_setup_request(const SetupRequest& req) const {
227 nlohmann::json j;
228 j["e3apProtocolVersion"] = req.e3ap_protocol_version;
229 j["dAppName"] = req.dapp_name;
230 j["dAppVersion"] = req.dapp_version;
231 j["vendor"] = req.vendor;
232
233 return j;
234}
235
236nlohmann::json JsonE3Encoder::encode_setup_response(const SetupResponse& resp) const {
237 nlohmann::json j;
238 j["requestId"] = resp.request_id;
239 j["responseCode"] = (resp.response_code == ResponseCode::POSITIVE) ? "positive" : "negative";
240 if (resp.e3ap_protocol_version.has_value()) {
241 j["e3apProtocolVersion"] = resp.e3ap_protocol_version.value();
242 }
243 if (resp.dapp_identifier.has_value()) {
244 j["dAppIdentifier"] = resp.dapp_identifier.value();
245 }
246 j["ranIdentifier"] = resp.ran_identifier;
247 if (!resp.ran_function_list.empty()) {
248 nlohmann::json ran_funcs = nlohmann::json::array();
249 for (const auto& func : resp.ran_function_list) {
250 nlohmann::json func_obj;
251 func_obj["ranFunctionIdentifier"] = func.ran_function_identifier;
252 func_obj["telemetryIdentifierList"] = func.telemetry_identifier_list;
253 func_obj["controlIdentifierList"] = func.control_identifier_list;
254 func_obj["ranFunctionData"] = binary_to_hex(func.ran_function_data);
255 ran_funcs.push_back(func_obj);
256 }
257 j["ranFunctionList"] = ran_funcs;
258 }
259 return j;
260}
261
262nlohmann::json JsonE3Encoder::encode_subscription_request(const SubscriptionRequest& req) const {
263 nlohmann::json j;
264 j["dAppIdentifier"] = req.dapp_identifier;
265 j["ranFunctionIdentifier"] = req.ran_function_identifier;
266 j["telemetryIdentifierList"] = req.telemetry_identifier_list;
267 j["controlIdentifierList"] = req.control_identifier_list;
268 if (req.subscription_time.has_value()) {
269 j["subscriptionTime"] = req.subscription_time.value();
270 }
271 if (req.periodicity.has_value()) {
272 j["periodicity"] = req.periodicity.value();
273 }
274 return j;
275}
276
277nlohmann::json JsonE3Encoder::encode_subscription_delete(const SubscriptionDelete& del) const {
278 nlohmann::json j;
279 j["dAppIdentifier"] = del.dapp_identifier;
280 j["subscriptionId"] = del.subscription_id;
281 return j;
282}
283
284nlohmann::json JsonE3Encoder::encode_subscription_response(const SubscriptionResponse& resp) const {
285 nlohmann::json j;
286 j["requestId"] = resp.request_id;
287 j["dAppIdentifier"] = resp.dapp_identifier;
288 j["responseCode"] = (resp.response_code == ResponseCode::POSITIVE) ? "positive" : "negative";
289 if (resp.subscription_id.has_value()) {
290 j["subscriptionId"] = resp.subscription_id.value();
291 }
292 return j;
293}
294
295std::optional<nlohmann::json> JsonE3Encoder::encode_indication_message(const IndicationMessage& msg) const {
296 if (!nlohmann::json::accept(msg.protocol_data)) {
297 E3_LOG_ERROR(LOG_TAG) << "Malformed protocolData for indication message "
298 << "(ranFunctionIdentifier=" << msg.ran_function_identifier
299 << ", payload size=" << msg.protocol_data.size() << " bytes)";
300 return std::nullopt; // caller reports ErrorCode::ENCODE_FAILED
301 }
302 nlohmann::json j;
303 j["dAppIdentifier"] = msg.dapp_identifier;
304 j["ranFunctionIdentifier"] = msg.ran_function_identifier;
305 // protocolData intentionally omitted: encode() splices the validated raw
306 // bytes verbatim into the dumped envelope instead of parsing them into a
307 // DOM here only to have root.dump() re-serialize them a moment later.
308 return j;
309}
310
311nlohmann::json JsonE3Encoder::encode_dapp_control_action(const DAppControlAction& action) const {
312 nlohmann::json j;
313 j["dAppIdentifier"] = action.dapp_identifier;
314 j["ranFunctionIdentifier"] = action.ran_function_identifier;
315 j["controlIdentifier"] = action.control_identifier;
316 // OPTIONAL: omitted entirely when unset, mirroring the ASN.1 absent field.
317 if (action.sequence_id != 0) j["sequenceId"] = action.sequence_id;
318 j["actionData"] = binary_to_hex(action.action_data);
319 return j;
320}
321
322nlohmann::json JsonE3Encoder::encode_dapp_report(const DAppReport& report) const {
323 nlohmann::json j;
324 j["dAppIdentifier"] = report.dapp_identifier;
325 j["ranFunctionIdentifier"] = report.ran_function_identifier;
326 j["sequenceId"] = report.sequence_id;
327 j["reportData"] = binary_to_hex(report.report_data);
328 return j;
329}
330
331nlohmann::json JsonE3Encoder::encode_xapp_control_action(const XAppControlAction& action) const {
332 nlohmann::json j;
333 j["dAppIdentifier"] = action.dapp_identifier;
334 j["ranFunctionIdentifier"] = action.ran_function_identifier;
335 j["sequenceId"] = action.sequence_id;
336 j["xAppControlData"] = binary_to_hex(action.xapp_control_data);
337 return j;
338}
339
340nlohmann::json JsonE3Encoder::encode_message_ack(const MessageAck& ack) const {
341 nlohmann::json j;
342 j["requestId"] = ack.request_id;
343 j["responseCode"] = (ack.response_code == ResponseCode::POSITIVE) ? "positive" : "negative";
344 return j;
345}
346
347// ============================================================================
348// Decoding helpers for each PDU type
349// ============================================================================
350
351SetupRequest JsonE3Encoder::decode_setup_request(const nlohmann::json& j) const {
352 SetupRequest req;
353 req.e3ap_protocol_version = j.value("e3apProtocolVersion", "");
354 req.dapp_name = j.value("dAppName", "");
355 req.dapp_version = j.value("dAppVersion", "");
356 req.vendor = j.value("vendor", "");
357
358 return req;
359}
360
361SetupResponse JsonE3Encoder::decode_setup_response(const nlohmann::json& j) const {
362 SetupResponse resp;
363 resp.request_id = j.value("requestId", 0u);
364
365 std::string response_code_str = j.value("responseCode", "negative");
366 resp.response_code = (response_code_str == "positive") ? ResponseCode::POSITIVE : ResponseCode::NEGATIVE;
367
368 if (j.contains("e3apProtocolVersion")) {
369 resp.e3ap_protocol_version = j["e3apProtocolVersion"].get<std::string>();
370 }
371 if (j.contains("dAppIdentifier")) {
372 resp.dapp_identifier = j["dAppIdentifier"].get<uint32_t>();
373 }
374 resp.ran_identifier = j.value("ranIdentifier", "");
375 if (j.contains("ranFunctionList")) {
376 for (const auto& func_obj : j["ranFunctionList"]) {
377 RanFunctionDef func;
378 func.ran_function_identifier = func_obj.value("ranFunctionIdentifier", 0u);
379 func.telemetry_identifier_list = func_obj.value("telemetryIdentifierList", std::vector<uint32_t>{});
380 func.control_identifier_list = func_obj.value("controlIdentifierList", std::vector<uint32_t>{});
381 func.ran_function_data = hex_to_binary(func_obj.value("ranFunctionData", ""));
382 resp.ran_function_list.push_back(func);
383 }
384 }
385 return resp;
386}
387
388SubscriptionRequest JsonE3Encoder::decode_subscription_request(const nlohmann::json& j) const {
389 SubscriptionRequest req;
390 req.dapp_identifier = j.value("dAppIdentifier", 0u);
391 req.ran_function_identifier = j.value("ranFunctionIdentifier", 0u);
392 req.telemetry_identifier_list = j.value("telemetryIdentifierList", std::vector<uint32_t>{});
393 req.control_identifier_list = j.value("controlIdentifierList", std::vector<uint32_t>{});
394 if (j.contains("subscriptionTime")) {
395 req.subscription_time = j["subscriptionTime"].get<uint32_t>();
396 }
397 if (j.contains("periodicity")) {
398 req.periodicity = j["periodicity"].get<uint32_t>();
399 }
400 return req;
401}
402
403SubscriptionDelete JsonE3Encoder::decode_subscription_delete(const nlohmann::json& j) const {
404 SubscriptionDelete del;
405 del.dapp_identifier = j.value("dAppIdentifier", 0u);
406 del.subscription_id = j.value("subscriptionId", 0u);
407 return del;
408}
409
410SubscriptionResponse JsonE3Encoder::decode_subscription_response(const nlohmann::json& j) const {
411 SubscriptionResponse resp;
412 resp.request_id = j.value("requestId", 0u);
413 resp.dapp_identifier = j.value("dAppIdentifier", 0u);
414 std::string response_code_str = j.value("responseCode", "negative");
415 resp.response_code = (response_code_str == "positive") ? ResponseCode::POSITIVE : ResponseCode::NEGATIVE;
416 if (j.contains("subscriptionId")) {
417 resp.subscription_id = j["subscriptionId"].get<uint32_t>();
418 }
419 return resp;
420}
421
422IndicationMessage JsonE3Encoder::decode_indication_message(const nlohmann::json& j,
423 const std::string& raw_json) const {
424 IndicationMessage msg;
425 msg.dapp_identifier = j.value("dAppIdentifier", 0u);
426 msg.ran_function_identifier = j.value("ranFunctionIdentifier", 0u);
427
428 size_t obj_start = raw_json.find_first_not_of(" \t\r\n");
429 auto span = (obj_start == std::string::npos)
430 ? std::nullopt
431 : find_top_level_member_span(raw_json, obj_start, "protocolData");
432
433 if (span) {
434 // Verbatim splice: byte-identical to what the SM originally emitted,
435 // instead of re-dumping the parsed subtree (which would silently
436 // reorder keys / drop whitespace for non-canonical input).
437 msg.protocol_data.assign(raw_json.begin() + static_cast<long>(span->first),
438 raw_json.begin() + static_cast<long>(span->second));
439 } else {
440 E3_LOG_DEBUG(LOG_TAG) << "protocolData byte-range scan missed; falling back to DOM dump "
441 "(ranFunctionIdentifier=" << msg.ran_function_identifier << ")";
442 std::string dumped = j["protocolData"].dump();
443 msg.protocol_data.assign(dumped.begin(), dumped.end());
444 }
445 return msg;
446}
447
448DAppControlAction JsonE3Encoder::decode_dapp_control_action(const nlohmann::json& j) const {
449 DAppControlAction action;
450 action.dapp_identifier = j.value("dAppIdentifier", 0u);
451 action.ran_function_identifier = j.value("ranFunctionIdentifier", 0u);
452 action.control_identifier = j.value("controlIdentifier", 0u);
453 action.sequence_id = j.value("sequenceId", 0u);
454 action.action_data = hex_to_binary(j.value("actionData", ""));
455 return action;
456}
457
458DAppReport JsonE3Encoder::decode_dapp_report(const nlohmann::json& j) const {
459 DAppReport report;
460 report.dapp_identifier = j.value("dAppIdentifier", 0u);
461 report.ran_function_identifier = j.value("ranFunctionIdentifier", 0u);
462 report.sequence_id = j.value("sequenceId", 0u);
463 report.report_data = hex_to_binary(j.value("reportData", ""));
464 return report;
465}
466
467XAppControlAction JsonE3Encoder::decode_xapp_control_action(const nlohmann::json& j) const {
468 XAppControlAction action;
469 action.dapp_identifier = j.value("dAppIdentifier", 0u);
470 action.ran_function_identifier = j.value("ranFunctionIdentifier", 0u);
471 action.sequence_id = j.value("sequenceId", 0u);
472 action.xapp_control_data = hex_to_binary(j.value("xAppControlData", ""));
473 return action;
474}
475
476MessageAck JsonE3Encoder::decode_message_ack(const nlohmann::json& j) const {
477 MessageAck ack;
478 ack.request_id = j.value("requestId", 0u);
479 std::string response_code_str = j.value("responseCode", "negative");
480 ack.response_code = (response_code_str == "positive") ? ResponseCode::POSITIVE : ResponseCode::NEGATIVE;
481 return ack;
482}
483
484// ReleaseMessage encode/decode
485nlohmann::json JsonE3Encoder::encode_release_message(const ReleaseMessage& msg) const {
486 nlohmann::json j;
487 j["dAppIdentifier"] = msg.dapp_identifier;
488 return j;
489}
490
491ReleaseMessage JsonE3Encoder::decode_release_message(const nlohmann::json& j) const {
492 ReleaseMessage msg;
493 msg.dapp_identifier = j.value("dAppIdentifier", 0u);
494 return msg;
495}
496
497// ============================================================================
498// Main encode/decode methods
499// ============================================================================
500
502 try {
503 nlohmann::json root;
504 root["type"] = to_camel_case(pdu_type_to_string(pdu.type));
505 root["id"] = pdu.message_id;
506 root["timestamp"] = pdu.timestamp;
507
508 // Set only for IndicationMessage: the SM's already-serialized payload,
509 // spliced verbatim into the dumped envelope below instead of being
510 // carried through the nlohmann tree (see encode_indication_message()).
511 std::optional<std::string> raw_protocol_data;
512 bool indication_payload_invalid = false;
513
514 // Encode payload fields directly into root (flat format)
515 std::visit([this, &root, &raw_protocol_data, &indication_payload_invalid](auto&& arg) {
516 using T = std::decay_t<decltype(arg)>;
517
518 nlohmann::json fields;
519 if constexpr (std::is_same_v<T, SetupRequest>) {
520 fields = encode_setup_request(arg);
521 }
522 else if constexpr (std::is_same_v<T, SetupResponse>) {
523 fields = encode_setup_response(arg);
524 }
525 else if constexpr (std::is_same_v<T, SubscriptionRequest>) {
526 fields = encode_subscription_request(arg);
527 }
528 else if constexpr (std::is_same_v<T, SubscriptionDelete>) {
529 fields = encode_subscription_delete(arg);
530 }
531 else if constexpr (std::is_same_v<T, SubscriptionResponse>) {
532 fields = encode_subscription_response(arg);
533 }
534 else if constexpr (std::is_same_v<T, IndicationMessage>) {
535 auto encoded_fields = encode_indication_message(arg);
536 if (!encoded_fields) {
537 indication_payload_invalid = true; // already logged with attribution
538 return;
539 }
540 fields = *encoded_fields;
541 raw_protocol_data.emplace(arg.protocol_data.begin(), arg.protocol_data.end());
542 }
543 else if constexpr (std::is_same_v<T, DAppControlAction>) {
544 fields = encode_dapp_control_action(arg);
545 }
546 else if constexpr (std::is_same_v<T, DAppReport>) {
547 fields = encode_dapp_report(arg);
548 }
549 else if constexpr (std::is_same_v<T, XAppControlAction>) {
550 fields = encode_xapp_control_action(arg);
551 }
552 else if constexpr (std::is_same_v<T, ReleaseMessage>) {
553 fields = encode_release_message(arg);
554 }
555 else if constexpr (std::is_same_v<T, MessageAck>) {
556 fields = encode_message_ack(arg);
557 }
558 root.update(fields);
559 }, pdu.choice);
560
561 if (indication_payload_invalid) {
562 return tl::unexpected(ErrorCode::ENCODE_FAILED);
563 }
564
565 std::string json_str = root.dump();
566
567 if (raw_protocol_data) {
568 // root.dump() sorts object keys lexicographically (documented
569 // nlohmann behavior); with protocolData excluded from the tree,
570 // "ranFunctionIdentifier" is exactly where protocolData would
571 // have sorted to ('d' < 'p' < 'r'), so this reproduces the same
572 // key position (and, for well-formed payloads, the same bytes)
573 // as before.
574 size_t pos = json_str.find("\"ranFunctionIdentifier\"");
575 if (pos == std::string::npos) {
576 E3_LOG_ERROR(LOG_TAG) << "Internal error: indication envelope missing "
577 "ranFunctionIdentifier; cannot splice protocolData";
578 return tl::unexpected(ErrorCode::ENCODE_FAILED);
579 }
580 json_str.insert(pos, "\"protocolData\":" + *raw_protocol_data + ",");
581 }
582
583 EncodedMessage msg;
584 msg.buffer.assign(json_str.begin(), json_str.end());
586
587 E3_LOG_TRACE(LOG_TAG) << "Encoded " << pdu_type_to_string(pdu.type)
588 << " (" << msg.size() << " bytes)";
589
590 return msg;
591 }
592 catch (const std::exception& e) {
593 E3_LOG_ERROR(LOG_TAG) << "JSON encode error: " << e.what();
594 return tl::unexpected(ErrorCode::ENCODE_FAILED);
595 }
596}
597
599 return decode(encoded.data(), encoded.size());
600}
601
602EncodeResult<Pdu> JsonE3Encoder::decode(const uint8_t* data, size_t size) {
603 if (data == nullptr || size == 0) {
604 E3_LOG_ERROR(LOG_TAG) << "Cannot decode empty data";
605 return tl::unexpected(ErrorCode::DECODE_FAILED);
606 }
607
608 try {
609 std::string json_str(reinterpret_cast<const char*>(data), size);
610
611 E3_LOG_TRACE(LOG_TAG) << "Decoding JSON (" << size << " bytes)";
612
613 nlohmann::json root = nlohmann::json::parse(json_str);
614
615 Pdu pdu;
616
617 // Get PDU type
618 std::string pdu_type_str = root.value("type", "");
619 auto pdu_type = string_to_pdu_type(pdu_type_str);
620 if (!pdu_type) {
621 E3_LOG_ERROR(LOG_TAG) << "Unrecognized PDU type: " << pdu_type_str;
622 return tl::unexpected(ErrorCode::DECODE_FAILED);
623 }
624 pdu.type = *pdu_type;
625 pdu.message_id = root.value("id", 0u);
626 pdu.timestamp = root.value("timestamp", 0ull);
627
628 if (root.contains("data")) {
629 E3_LOG_ERROR(LOG_TAG) << "Nested \"data\" wrapper is not supported; use flat format";
630 return tl::unexpected(ErrorCode::DECODE_FAILED);
631 }
632
633 // Decode based on PDU type
634 switch (pdu.type) {
636 pdu.choice = decode_setup_request(root);
637 break;
639 pdu.choice = decode_setup_response(root);
640 break;
642 pdu.choice = decode_subscription_request(root);
643 break;
645 pdu.choice = decode_subscription_delete(root);
646 break;
648 pdu.choice = decode_subscription_response(root);
649 break;
651 pdu.choice = decode_indication_message(root, json_str);
652 break;
654 pdu.choice = decode_dapp_control_action(root);
655 break;
657 pdu.choice = decode_dapp_report(root);
658 break;
660 pdu.choice = decode_xapp_control_action(root);
661 break;
663 pdu.choice = decode_release_message(root);
664 break;
666 pdu.choice = decode_message_ack(root);
667 break;
668 default:
669 E3_LOG_ERROR(LOG_TAG) << "Unknown PDU type: " << pdu_type_str;
670 return tl::unexpected(ErrorCode::DECODE_FAILED);
671 }
672
673 E3_LOG_TRACE(LOG_TAG) << "Decoded " << pdu_type_to_string(pdu.type);
674 return pdu;
675 }
676 catch (const nlohmann::json::parse_error& e) {
677 E3_LOG_ERROR(LOG_TAG) << "JSON parse error: " << e.what();
678 return tl::unexpected(ErrorCode::DECODE_FAILED);
679 }
680 catch (const std::exception& e) {
681 E3_LOG_ERROR(LOG_TAG) << "JSON decode error: " << e.what();
682 return tl::unexpected(ErrorCode::DECODE_FAILED);
683 }
684}
685
686} // namespace libe3
EncodeResult< Pdu > decode(const EncodedMessage &encoded) override
Decode bytes to a PDU.
EncodeResult< EncodedMessage > encode(const Pdu &pdu) override
Encode a PDU to bytes.
JSON Encoder for E3AP PDUs.
Logging facility for libe3.
#define E3_LOG_TRACE(component)
Definition logger.hpp:229
#define E3_LOG_DEBUG(component)
Definition logger.hpp:228
#define E3_LOG_ERROR(component)
Definition logger.hpp:225
ErrorCode
Error codes returned by libe3 operations Use shared error-code list (see libe3/error_codes....
Definition types.hpp:141
ResponseCode
E3AP response codes.
Definition types.hpp:101
@ NEGATIVE
Request rejected.
Definition types.hpp:103
@ POSITIVE
Request accepted.
Definition types.hpp:102
const char * pdu_type_to_string(PduType type) noexcept
Convert PduType to string representation.
Definition types.hpp:515
@ SUBSCRIPTION_DELETE
Definition types.hpp:113
@ SUBSCRIPTION_RESPONSE
Definition types.hpp:114
@ SUBSCRIPTION_REQUEST
Definition types.hpp:112
@ XAPP_CONTROL_ACTION
Definition types.hpp:118
@ DAPP_CONTROL_ACTION
Definition types.hpp:116
@ JSON
JSON encoding (for development/debugging).
Definition types.hpp:53
tl::expected< T, ErrorCode > EncodeResult
Result type for encoding/decoding operations.
E3AP dApp Control Action structure.
Definition types.hpp:272
E3AP dApp Report structure.
Definition types.hpp:293
uint32_t dapp_identifier
Definition types.hpp:294
Encoded message wrapper.
Definition types.hpp:175
std::vector< uint8_t > buffer
Encoded data buffer.
Definition types.hpp:176
const uint8_t * data() const noexcept
Definition types.hpp:185
EncodingFormat format
Format used for encoding.
Definition types.hpp:177
size_t size() const noexcept
Definition types.hpp:183
E3AP Indication Message structure.
Definition types.hpp:263
E3AP Message Acknowledgment structure.
Definition types.hpp:285
uint32_t request_id
ID of the request being acknowledged.
Definition types.hpp:286
Generic E3AP PDU structure.
Definition types.hpp:345
PduType type
Definition types.hpp:346
PduChoice choice
Definition types.hpp:347
uint64_t timestamp
Producer timestamp, in nanoseconds since the Unix epoch.
Definition types.hpp:363
uint32_t message_id
Unique message identifier.
Definition types.hpp:348
E3AP Release Message structure.
Definition types.hpp:321
uint32_t dapp_identifier
dApp identifier
Definition types.hpp:322
E3AP Setup Request structure.
Definition types.hpp:201
std::string e3ap_protocol_version
E3AP protocol version (e.g., "0.0.0").
Definition types.hpp:202
E3AP Setup Response structure.
Definition types.hpp:221
uint32_t request_id
ID of the corresponding SetupRequest.
Definition types.hpp:222
E3AP Subscription Delete structure.
Definition types.hpp:245
uint32_t dapp_identifier
dApp identifier
Definition types.hpp:246
E3AP Subscription Request structure.
Definition types.hpp:233
uint32_t dapp_identifier
dApp identifier
Definition types.hpp:234
E3AP Subscription Response structure.
Definition types.hpp:253
uint32_t request_id
ID of the corresponding SubscriptionRequest.
Definition types.hpp:254
E3AP xApp Control Action structure.
Definition types.hpp:306