blob: 1c191651ce47a9b7acba4d664fa96497f7af06c1 [file] [edit]
#include "bej_encoder_json.hpp"
#include "bej_deferred_binding_format.hpp"
#include <algorithm>
#include <deque>
#include <string>
#include <string_view>
#include <vector>
namespace libbej
{
bool stackEmpty(void* dataPtr)
{
return (reinterpret_cast<std::vector<void*>*>(dataPtr))->empty();
}
void* stackPeek(void* dataPtr)
{
auto stack = reinterpret_cast<std::vector<void*>*>(dataPtr);
if (stack->empty())
{
return nullptr;
}
return stack->back();
}
void* stackPop(void* dataPtr)
{
auto stack = reinterpret_cast<std::vector<void*>*>(dataPtr);
if (stack->empty())
{
return nullptr;
}
void* value = stack->back();
stack->pop_back();
return value;
}
int stackPush(void* property, void* dataPtr)
{
auto stack = reinterpret_cast<std::vector<void*>*>(dataPtr);
stack->emplace_back(property);
return 0;
}
int getBejEncodedBuffer(const void* data, size_t dataSize, void* handlerContext)
{
auto stack = reinterpret_cast<std::vector<uint8_t>*>(handlerContext);
const uint8_t* dataBuf = reinterpret_cast<const uint8_t*>(data);
stack->insert(stack->end(), dataBuf, dataBuf + dataSize);
return 0;
}
std::vector<uint8_t> BejEncoderJson::getOutput()
{
std::vector<uint8_t> currentEncodedPayload = std::move(encodedPayload);
// Re-Initialize encodedPayload with empty vector to be used again for
// next encoding
encodedPayload = {};
return currentEncodedPayload;
}
// Records a leaf whose value was temporarily rewritten to a deferred binding
// placeholder, so encode() can restore the caller's tree after encoding.
struct DeferredBindingRestore
{
struct RedfishPropertyLeafString* leaf;
const char* originalValue;
bool originalDeferredBinding;
};
// One (value, placeholder) entry of the reverse lookup: the property value the
// caller wrote and the placeholder it is encoded as.
struct ReverseDeferredBinding
{
std::string value;
std::string placeholder;
};
// A flat table, not a hash map: the binding count is small (a handful per
// resource), so a linear scan is faster and lighter than hashing, and it is
// built once per encode.
using ReverseDeferredBindingTable = std::vector<ReverseDeferredBinding>;
static ReverseDeferredBindingTable buildReverseDeferredBindingTable(
const BejDeferredBindingMap& bindings)
{
ReverseDeferredBindingTable reverse;
reverse.reserve(bindings.size());
for (const auto& [token, value] : bindings)
{
// Empty values never produce a placeholder. When distinct placeholders
// share a value the first match found on lookup wins, so callers should
// keep values unique.
if (!value.empty())
{
reverse.push_back(
{value, std::format("{}{}", bejDeferredBindingMarker, token)});
}
}
return reverse;
}
static void updateTreeForDeferredBindings(
struct RedfishPropertyNode* node,
const ReverseDeferredBindingTable& reverse,
std::deque<std::string>& storage,
std::vector<DeferredBindingRestore>& restores)
{
if (!node || reverse.empty())
{
return;
}
if (bejTreeIsParentType(node))
{
struct RedfishPropertyParent* parent =
reinterpret_cast<struct RedfishPropertyParent*>(node);
struct RedfishPropertyNode* child =
reinterpret_cast<struct RedfishPropertyNode*>(parent->firstChild);
while (child != nullptr)
{
updateTreeForDeferredBindings(child, reverse, storage, restores);
child = reinterpret_cast<struct RedfishPropertyNode*>(
bejParentGoToNextChild(parent, child));
}
return;
}
// Only bejString leaves carry substitutable values on the encode side; the
// encoder core has no bejResourceLink leaf type to emit.
if (node->format.principalDataType != bejString)
{
return;
}
struct RedfishPropertyLeafString* leafStr =
reinterpret_cast<struct RedfishPropertyLeafString*>(node);
if (!leafStr->value)
{
return;
}
std::string_view value{leafStr->value};
auto match = std::find_if(reverse.begin(), reverse.end(),
[value](const ReverseDeferredBinding& entry) {
return std::string_view{entry.value} == value;
});
if (match == reverse.end())
{
return;
}
// Own the placeholder for the encode lifetime via `storage`; a deque keeps
// element addresses stable, so `value` may point at its c_str(). Record the
// original pointer and flag so encode() can restore the caller's tree.
restores.push_back(
{leafStr, leafStr->value, node->format.deferredBinding != 0});
storage.push_back(match->placeholder);
leafStr->value = storage.back().c_str();
bejTreeUpdateNodeFlags(node, /*deferredBinding=*/true,
node->format.readOnlyPropertyAndTopLevelAnnotation,
node->format.nullableProperty);
}
int BejEncoderJson::encode(const struct BejDictionaries* dictionaries,
enum BejSchemaClass schemaClass,
struct RedfishPropertyParent* root,
const BejDeferredBindingMap& bindings)
{
// Temporarily rewrite eligible string values to deferred binding
// placeholders. `storage` owns the placeholder strings for the encode
// duration; `restores` lets us put the caller's tree back afterwards so
// encode() leaves no side effect on the input.
ReverseDeferredBindingTable reverseBindings =
buildReverseDeferredBindingTable(bindings);
std::deque<std::string> deferredBindingStorage;
std::vector<DeferredBindingRestore> deferredBindingRestores;
updateTreeForDeferredBindings(
reinterpret_cast<struct RedfishPropertyNode*>(root), reverseBindings,
deferredBindingStorage, deferredBindingRestores);
struct BejEncoderOutputHandler output = {
.handlerContext = &encodedPayload,
.recvOutput = &getBejEncodedBuffer,
};
struct BejPointerStackCallback stackCallbacks = {
.stackContext = &stack,
.stackEmpty = stackEmpty,
.stackPeek = stackPeek,
.stackPop = stackPop,
.stackPush = stackPush,
.deleteStack = nullptr,
};
int rc = bejEncode(dictionaries, BEJ_DICTIONARY_START_AT_HEAD, schemaClass,
root, &output, &stackCallbacks);
// Restore the caller's tree (original value pointers and deferred binding
// flags); placeholders in `deferredBindingStorage` are unreferenced once
// this returns.
for (const DeferredBindingRestore& entry : deferredBindingRestores)
{
struct RedfishPropertyNode* node =
reinterpret_cast<struct RedfishPropertyNode*>(entry.leaf);
bejTreeUpdateNodeFlags(
node, entry.originalDeferredBinding,
node->format.readOnlyPropertyAndTopLevelAnnotation,
node->format.nullableProperty);
entry.leaf->value = entry.originalValue;
}
return rc;
}
} // namespace libbej