Program Listing for File deserialization.cc#

↰ Return to documentation for file (src/deserialization.cc)

#include "nodes-graph/deserialization.h"


namespace ng::deserialization {

namespace {

    // Verifies that a length-prefixed element consumed exactly the number of bytes announced.
    void check_consumed(const ReadHead& head, std::size_t start, sr::len_t len, const char* what) {
        if (head.pos - start != len)
            throw std::runtime_error(std::string(what) + " length does not match the expected size");
    }

} // namespace


// --- Base types

sr::len_t deserialize_len(ReadHead& head) {
    sr::len_t len;
    head.read(&len, sizeof(len));
    return len;
}


sr::bound_t deserialize_bound(ReadHead& head) {
    sr::bound_t bound;
    head.read(&bound, sizeof(bound));
    return bound;
}


bool deserialize_bool(ReadHead& head) {
    uint8_t raw;
    head.read(&raw, sizeof(raw));
    if (raw > 1)
        throw std::runtime_error("Invalid boolean value");
    return raw == 1;
}


socket_t deserialize_socket_type(ReadHead& head) {
    socket_t socket_type;
    head.read(&socket_type, sizeof(socket_type));
    return socket_type;
}


// --- Strings and identifiers

std::string deserialize_string(ReadHead& head) {
    sr::len_t len = deserialize_len(head);
    if (len == 0)
        throw std::runtime_error("String length must include the null terminator");

    head.require(len);
    if (head.c_str()[len - 1] != '\0')
        throw std::runtime_error("String not null-terminated");

    std::string result(head.c_str(), len - 1);
    head.pos += len;
    return result;
}


qualifier_t deserialize_qualifier(ReadHead& head) {
    std::size_t start = head.pos;
    sr::len_t len = deserialize_len(head);

    qualifier_t qualifier;
    for (std::size_t i = 0 ; i < qualifier_t::N_CHUNKS ; i++) {
        qualifier.chunks[i] = deserialize_array<QUALIFIER_CHUNK_SIZE>(head);
    }

    check_consumed(head, start, len, "Qualifier");
    return qualifier;
}


socket_uuid_t deserialize_socket_uuid(ReadHead& head) {
    std::size_t start = head.pos;
    sr::len_t len = deserialize_len(head);

    node_id_t node_id = deserialize_array<NODE_ID_SIZE>(head);
    socket_id_t socket_id = deserialize_array<SOCKET_ID_SIZE>(head);

    check_consumed(head, start, len, "Socket UUID");
    return as_socket_uuid(node_id, socket_id);
}


link_t deserialize_link(ReadHead& head) {
    std::size_t start = head.pos;
    sr::len_t len = deserialize_len(head);

    socket_uuid_t to = deserialize_socket_uuid(head);
    socket_uuid_t from = deserialize_socket_uuid(head);

    check_consumed(head, start, len, "Link");
    return link_t{to, from};
}


// --- Graph components

SocketAttributes deserialize_socket_base(ReadHead& head) {
    SocketAttributes attrs;
    attrs.start = head.pos;
    attrs.len = deserialize_len(head);
    attrs.name = deserialize_string(head);
    attrs.id = deserialize_array<SOCKET_ID_SIZE>(head);
    attrs.type = deserialize_socket_type(head);
    attrs.active = deserialize_bool(head);
    attrs.on_added_link_callbacks = deserialize_set(head, deserialize_qualifier, sr::Bounds::callbacks);
    attrs.on_broken_link_callbacks = deserialize_set(head, deserialize_qualifier, sr::Bounds::callbacks);
    return attrs;
}


InputSocket deserialize_input_socket(ReadHead& head) {
    SocketAttributes attrs = deserialize_socket_base(head);

    std::string value = deserialize_string(head);
    std::set<std::string> allowed_values = deserialize_set(head, deserialize_string, sr::Bounds::whitelist);
    bool linkable = deserialize_bool(head);
    std::set<qualifier_t> on_value_changed_callbacks = deserialize_set(head, deserialize_qualifier, sr::Bounds::callbacks);

    check_consumed(head, attrs.start, attrs.len, "InputSocket");

    return InputSocket(
        attrs.name,
        attrs.id,
        attrs.type,
        attrs.active,
        value,
        linkable,
        allowed_values,
        attrs.on_added_link_callbacks,
        attrs.on_broken_link_callbacks,
        on_value_changed_callbacks
    );
}


OutputSocket deserialize_output_socket(ReadHead& head) {
    SocketAttributes attrs = deserialize_socket_base(head);

    check_consumed(head, attrs.start, attrs.len, "OutputSocket");

    return OutputSocket(
        attrs.name,
        attrs.id,
        attrs.type,
        attrs.active,
        attrs.on_added_link_callbacks,
        attrs.on_broken_link_callbacks
    );
}


Node deserialize_node(ReadHead& head) {
    std::size_t start = head.pos;
    sr::len_t len = deserialize_len(head);

    node_id_t id = deserialize_array<NODE_ID_SIZE>(head);
    qualifier_t qualifier = deserialize_qualifier(head);
    std::string name = deserialize_string(head);
    std::set<socket_id_t> input_sockets = deserialize_set(head, deserialize_array<SOCKET_ID_SIZE>, sr::Bounds::sockets);
    std::set<socket_id_t> output_sockets = deserialize_set(head, deserialize_array<SOCKET_ID_SIZE>, sr::Bounds::sockets);
    bool proxy = deserialize_bool(head);

    check_consumed(head, start, len, "Node");
    return Node(name, id, qualifier, input_sockets, output_sockets, proxy);
}


Graph::InterfaceProperties deserialize_interface_properties(ReadHead& head) {
    std::size_t start = head.pos;
    sr::len_t len = deserialize_len(head);

    socket_id_t socket_id = deserialize_array<SOCKET_ID_SIZE>(head);
    std::string show_as = deserialize_string(head);

    check_consumed(head, start, len, "InterfaceProperties");
    return Graph::InterfaceProperties{show_as, socket_id};
}


// ---


void Deserializer::deserialize_graph() {
    ReadHead head(buffer_);

    sr::len_t len = deserialize_len(head);
    if (len != buffer_.size())
        throw std::runtime_error("Graph length does not match the size of the buffer");

    if (deserialize_bound(head) != sr::Bounds::graph)
        throw std::runtime_error("Expected graph bound at the beginning of the graph deserialization");

    qualifier_t graph_qualifier = deserialize_qualifier(head);

    // Files written by a more recent version of the parser can't be read.
    sr::version_t version = deserialize_array<3>(head);
    if (!sr::is_sup_or_equal(sr::parser_version(), version))
        throw std::runtime_error("Parser version is not supported");

    if (deserialize_string(head) != sr::app_name())
        throw std::runtime_error("App name does not match the expected value");

    std::string graph_name = deserialize_string(head);
    Graph::links_t links = deserialize_set(head, deserialize_link, sr::Bounds::links);
    Graph::nodes_t nodes = deserialize_map(head, deserialize_array<NODE_ID_SIZE>, deserialize_node, sr::Bounds::nodes);
    Graph::input_sockets_t input_sockets = deserialize_map(head, deserialize_socket_uuid, deserialize_input_socket, sr::Bounds::sockets);
    Graph::output_sockets_t output_sockets = deserialize_map(head, deserialize_socket_uuid, deserialize_output_socket, sr::Bounds::sockets);
    Graph::interface_t parameters = deserialize_map(head, deserialize_socket_uuid, deserialize_interface_properties, sr::Bounds::interface);
    Graph::interface_t results = deserialize_map(head, deserialize_socket_uuid, deserialize_interface_properties, sr::Bounds::interface);

    if (deserialize_bound(head) != sr::Bounds::graph)
        throw std::runtime_error("Expected graph bound at the end of the graph deserialization");

    if (head.pos != len)
        throw std::runtime_error("Graph deserialization did not consume the expected number of bytes");

    graph_ = Graph(
        std::move(graph_name),
        std::move(nodes),
        std::move(links),
        std::move(input_sockets),
        std::move(output_sockets),
        std::move(parameters),
        std::move(results)
    );
    graph_.set_qualifier(graph_qualifier);
}

} // namespace ng::deserialization