21constexpr const char* LOG_TAG =
"JsonEnc";
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])));
29bool is_json_ws(
char c) {
30 return c ==
' ' || c ==
'\t' || c ==
'\n' || c ==
'\r';
33size_t skip_ws(
const std::string& text,
size_t pos) {
34 while (pos < text.size() && is_json_ws(text[pos])) {
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;
49 if (pos + 1 >= n)
return std::nullopt;
50 if (text[pos + 1] ==
'u') {
51 if (pos + 6 > n)
return std::nullopt;
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;
78 return skip_json_string(text, pos);
80 if (c ==
'{' || c ==
'[') {
82 char close = (open ==
'{') ?
'}' :
']';
85 while (pos < n && depth > 0) {
88 auto after = skip_json_string(text, pos);
89 if (!after)
return std::nullopt;
95 }
else if (cc == close) {
100 if (depth != 0)
return std::nullopt;
106 if (cc ==
',' || cc ==
'}' || cc ==
']' || is_json_ws(cc)) {
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] !=
'{') {
133 size_t pos = obj_start + 1;
135 pos = skip_ws(text, pos);
136 if (pos >= n)
return std::nullopt;
137 if (text[pos] ==
'}') {
140 if (text[pos] !=
'"')
return std::nullopt;
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;
147 pos = skip_ws(text, *after_key);
148 if (pos >= n || text[pos] !=
':')
return std::nullopt;
149 pos = skip_ws(text, pos + 1);
151 size_t value_start = pos;
152 auto after_value = skip_json_value(text, pos);
153 if (!after_value)
return std::nullopt;
155 bool key_matches = (key_end - key_start == key.size()) &&
156 text.compare(key_start, key.size(), key) == 0;
158 return std::make_pair(value_start, *after_value);
161 pos = skip_ws(text, *after_value);
162 if (pos < n && text[pos] ==
',') {
177std::optional<PduType> JsonE3Encoder::string_to_pdu_type(
const std::string& s)
const {
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;
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);
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)));
226nlohmann::json JsonE3Encoder::encode_setup_request(
const SetupRequest& req)
const {
228 j[
"e3apProtocolVersion"] = req.e3ap_protocol_version;
229 j[
"dAppName"] = req.dapp_name;
230 j[
"dAppVersion"] = req.dapp_version;
231 j[
"vendor"] = req.vendor;
236nlohmann::json JsonE3Encoder::encode_setup_response(
const SetupResponse& resp)
const {
238 j[
"requestId"] = resp.request_id;
240 if (resp.e3ap_protocol_version.has_value()) {
241 j[
"e3apProtocolVersion"] = resp.e3ap_protocol_version.value();
243 if (resp.dapp_identifier.has_value()) {
244 j[
"dAppIdentifier"] = resp.dapp_identifier.value();
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);
257 j[
"ranFunctionList"] = ran_funcs;
262nlohmann::json JsonE3Encoder::encode_subscription_request(
const SubscriptionRequest& req)
const {
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();
271 if (req.periodicity.has_value()) {
272 j[
"periodicity"] = req.periodicity.value();
277nlohmann::json JsonE3Encoder::encode_subscription_delete(
const SubscriptionDelete& del)
const {
279 j[
"dAppIdentifier"] = del.dapp_identifier;
280 j[
"subscriptionId"] = del.subscription_id;
284nlohmann::json JsonE3Encoder::encode_subscription_response(
const SubscriptionResponse& resp)
const {
286 j[
"requestId"] = resp.request_id;
287 j[
"dAppIdentifier"] = resp.dapp_identifier;
289 if (resp.subscription_id.has_value()) {
290 j[
"subscriptionId"] = resp.subscription_id.value();
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)";
303 j[
"dAppIdentifier"] = msg.dapp_identifier;
304 j[
"ranFunctionIdentifier"] = msg.ran_function_identifier;
311nlohmann::json JsonE3Encoder::encode_dapp_control_action(
const DAppControlAction& action)
const {
313 j[
"dAppIdentifier"] = action.dapp_identifier;
314 j[
"ranFunctionIdentifier"] = action.ran_function_identifier;
315 j[
"controlIdentifier"] = action.control_identifier;
317 if (action.sequence_id != 0) j[
"sequenceId"] = action.sequence_id;
318 j[
"actionData"] = binary_to_hex(action.action_data);
322nlohmann::json JsonE3Encoder::encode_dapp_report(
const DAppReport& report)
const {
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);
331nlohmann::json JsonE3Encoder::encode_xapp_control_action(
const XAppControlAction& action)
const {
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);
340nlohmann::json JsonE3Encoder::encode_message_ack(
const MessageAck& ack)
const {
342 j[
"requestId"] = ack.request_id;
351SetupRequest JsonE3Encoder::decode_setup_request(
const nlohmann::json& j)
const {
354 req.dapp_name = j.value(
"dAppName",
"");
355 req.dapp_version = j.value(
"dAppVersion",
"");
356 req.vendor = j.value(
"vendor",
"");
361SetupResponse JsonE3Encoder::decode_setup_response(
const nlohmann::json& j)
const {
365 std::string response_code_str = j.value(
"responseCode",
"negative");
368 if (j.contains(
"e3apProtocolVersion")) {
369 resp.e3ap_protocol_version = j[
"e3apProtocolVersion"].get<std::string>();
371 if (j.contains(
"dAppIdentifier")) {
372 resp.dapp_identifier = j[
"dAppIdentifier"].get<uint32_t>();
374 resp.ran_identifier = j.value(
"ranIdentifier",
"");
375 if (j.contains(
"ranFunctionList")) {
376 for (
const auto& func_obj : j[
"ranFunctionList"]) {
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);
388SubscriptionRequest JsonE3Encoder::decode_subscription_request(
const nlohmann::json& j)
const {
389 SubscriptionRequest req;
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>();
397 if (j.contains(
"periodicity")) {
398 req.periodicity = j[
"periodicity"].get<uint32_t>();
403SubscriptionDelete JsonE3Encoder::decode_subscription_delete(
const nlohmann::json& j)
const {
404 SubscriptionDelete del;
406 del.subscription_id = j.value(
"subscriptionId", 0u);
410SubscriptionResponse JsonE3Encoder::decode_subscription_response(
const nlohmann::json& j)
const {
411 SubscriptionResponse resp;
413 resp.dapp_identifier = j.value(
"dAppIdentifier", 0u);
414 std::string response_code_str = j.value(
"responseCode",
"negative");
416 if (j.contains(
"subscriptionId")) {
417 resp.subscription_id = j[
"subscriptionId"].get<uint32_t>();
422IndicationMessage JsonE3Encoder::decode_indication_message(
const nlohmann::json& j,
423 const std::string& raw_json)
const {
424 IndicationMessage msg;
426 msg.ran_function_identifier = j.value(
"ranFunctionIdentifier", 0u);
428 size_t obj_start = raw_json.find_first_not_of(
" \t\r\n");
429 auto span = (obj_start == std::string::npos)
431 : find_top_level_member_span(raw_json, obj_start,
"protocolData");
437 msg.protocol_data.assign(raw_json.begin() +
static_cast<long>(span->first),
438 raw_json.begin() +
static_cast<long>(span->second));
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());
448DAppControlAction JsonE3Encoder::decode_dapp_control_action(
const nlohmann::json& j)
const {
449 DAppControlAction action;
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",
""));
458DAppReport JsonE3Encoder::decode_dapp_report(
const nlohmann::json& j)
const {
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",
""));
467XAppControlAction JsonE3Encoder::decode_xapp_control_action(
const nlohmann::json& j)
const {
468 XAppControlAction action;
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",
""));
476MessageAck JsonE3Encoder::decode_message_ack(
const nlohmann::json& j)
const {
479 std::string response_code_str = j.value(
"responseCode",
"negative");
485nlohmann::json JsonE3Encoder::encode_release_message(
const ReleaseMessage& msg)
const {
487 j[
"dAppIdentifier"] = msg.dapp_identifier;
491ReleaseMessage JsonE3Encoder::decode_release_message(
const nlohmann::json& j)
const {
511 std::optional<std::string> raw_protocol_data;
512 bool indication_payload_invalid =
false;
515 std::visit([
this, &root, &raw_protocol_data, &indication_payload_invalid](
auto&& arg) {
516 using T = std::decay_t<
decltype(arg)>;
518 nlohmann::json fields;
519 if constexpr (std::is_same_v<T, SetupRequest>) {
520 fields = encode_setup_request(arg);
522 else if constexpr (std::is_same_v<T, SetupResponse>) {
523 fields = encode_setup_response(arg);
525 else if constexpr (std::is_same_v<T, SubscriptionRequest>) {
526 fields = encode_subscription_request(arg);
528 else if constexpr (std::is_same_v<T, SubscriptionDelete>) {
529 fields = encode_subscription_delete(arg);
531 else if constexpr (std::is_same_v<T, SubscriptionResponse>) {
532 fields = encode_subscription_response(arg);
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;
540 fields = *encoded_fields;
541 raw_protocol_data.emplace(arg.protocol_data.begin(), arg.protocol_data.end());
543 else if constexpr (std::is_same_v<T, DAppControlAction>) {
544 fields = encode_dapp_control_action(arg);
546 else if constexpr (std::is_same_v<T, DAppReport>) {
547 fields = encode_dapp_report(arg);
549 else if constexpr (std::is_same_v<T, XAppControlAction>) {
550 fields = encode_xapp_control_action(arg);
552 else if constexpr (std::is_same_v<T, ReleaseMessage>) {
553 fields = encode_release_message(arg);
555 else if constexpr (std::is_same_v<T, MessageAck>) {
556 fields = encode_message_ack(arg);
561 if (indication_payload_invalid) {
562 return tl::unexpected(ErrorCode::ENCODE_FAILED);
565 std::string json_str = root.dump();
567 if (raw_protocol_data) {
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);
580 json_str.insert(pos,
"\"protocolData\":" + *raw_protocol_data +
",");
584 msg.
buffer.assign(json_str.begin(), json_str.end());
588 <<
" (" << msg.
size() <<
" bytes)";
592 catch (
const std::exception& e) {
593 E3_LOG_ERROR(LOG_TAG) <<
"JSON encode error: " << e.what();
594 return tl::unexpected(ErrorCode::ENCODE_FAILED);
603 if (data ==
nullptr || size == 0) {
605 return tl::unexpected(ErrorCode::DECODE_FAILED);
609 std::string json_str(
reinterpret_cast<const char*
>(data), size);
611 E3_LOG_TRACE(LOG_TAG) <<
"Decoding JSON (" << size <<
" bytes)";
613 nlohmann::json root = nlohmann::json::parse(json_str);
618 std::string pdu_type_str = root.value(
"type",
"");
619 auto pdu_type = string_to_pdu_type(pdu_type_str);
621 E3_LOG_ERROR(LOG_TAG) <<
"Unrecognized PDU type: " << pdu_type_str;
622 return tl::unexpected(ErrorCode::DECODE_FAILED);
624 pdu.
type = *pdu_type;
626 pdu.
timestamp = root.value(
"timestamp", 0ull);
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);
636 pdu.
choice = decode_setup_request(root);
639 pdu.
choice = decode_setup_response(root);
642 pdu.
choice = decode_subscription_request(root);
645 pdu.
choice = decode_subscription_delete(root);
648 pdu.
choice = decode_subscription_response(root);
651 pdu.
choice = decode_indication_message(root, json_str);
654 pdu.
choice = decode_dapp_control_action(root);
657 pdu.
choice = decode_dapp_report(root);
660 pdu.
choice = decode_xapp_control_action(root);
663 pdu.
choice = decode_release_message(root);
666 pdu.
choice = decode_message_ack(root);
669 E3_LOG_ERROR(LOG_TAG) <<
"Unknown PDU type: " << pdu_type_str;
670 return tl::unexpected(ErrorCode::DECODE_FAILED);
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);
680 catch (
const std::exception& e) {
681 E3_LOG_ERROR(LOG_TAG) <<
"JSON decode error: " << e.what();
682 return tl::unexpected(ErrorCode::DECODE_FAILED);
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)
#define E3_LOG_DEBUG(component)
#define E3_LOG_ERROR(component)
ErrorCode
Error codes returned by libe3 operations Use shared error-code list (see libe3/error_codes....
ResponseCode
E3AP response codes.
@ NEGATIVE
Request rejected.
@ POSITIVE
Request accepted.
const char * pdu_type_to_string(PduType type) noexcept
Convert PduType to string representation.
@ JSON
JSON encoding (for development/debugging).
tl::expected< T, ErrorCode > EncodeResult
Result type for encoding/decoding operations.
E3AP dApp Control Action structure.
E3AP dApp Report structure.
std::vector< uint8_t > buffer
Encoded data buffer.
const uint8_t * data() const noexcept
EncodingFormat format
Format used for encoding.
size_t size() const noexcept
E3AP Indication Message structure.
E3AP Message Acknowledgment structure.
uint32_t request_id
ID of the request being acknowledged.
Generic E3AP PDU structure.
uint64_t timestamp
Producer timestamp, in nanoseconds since the Unix epoch.
uint32_t message_id
Unique message identifier.
E3AP Release Message structure.
uint32_t dapp_identifier
dApp identifier
E3AP Setup Request structure.
std::string e3ap_protocol_version
E3AP protocol version (e.g., "0.0.0").
E3AP Setup Response structure.
uint32_t request_id
ID of the corresponding SetupRequest.
E3AP Subscription Delete structure.
uint32_t dapp_identifier
dApp identifier
E3AP Subscription Request structure.
uint32_t dapp_identifier
dApp identifier
E3AP Subscription Response structure.
uint32_t request_id
ID of the corresponding SubscriptionRequest.
E3AP xApp Control Action structure.