/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include "sd-bus.h"

#include "bus-map-properties.h"
#include "bus-message-util.h"
#include "bus-util.h"
#include "string-util.h"
#include "strv.h"

int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *reterr_error, void *userdata) {
        sd_id128_t *p = userdata;
        int r;

        assert(m);

        r = bus_message_read_id128(m, p);
        if (r < 0)
                return bus_log_parse_error_debug(r);

        return 0;
}

int bus_map_strv_sort(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *reterr_error, void *userdata) {
        char ***p = ASSERT_PTR(userdata);
        int r;

        assert(m);

        r = sd_bus_message_read_strv_extend(m, p);
        if (r < 0)
                return bus_log_parse_error_debug(r);

        strv_sort(*p);
        return 0;
}

int bus_map_job_id(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *reterr_error, void *userdata) {
        uint32_t *p = ASSERT_PTR(userdata);

        assert(m);

        return sd_bus_message_read(m, "(uo)", p, /* path= */ NULL);
}

static int map_basic(sd_bus_message *m, unsigned flags, void *userdata) {
        char type;
        int r;

        assert(m);
        assert(userdata);

        r = sd_bus_message_peek_type(m, &type, NULL);
        if (r < 0)
                return bus_log_parse_error_debug(r);

        switch (type) {

        case SD_BUS_TYPE_STRING:
        case SD_BUS_TYPE_OBJECT_PATH: {
                const char **p = userdata, *s;

                r = sd_bus_message_read_basic(m, type, &s);
                if (r < 0)
                        return bus_log_parse_error_debug(r);

                s = empty_to_null(s);

                if (FLAGS_SET(flags, BUS_MAP_STRDUP))
                        return free_and_strdup((char**) p, s);

                *p = s;
                return 0;
        }

        case SD_BUS_TYPE_ARRAY: {
                char ***p = userdata;

                r = sd_bus_message_read_strv_extend(m, p);
                if (r < 0)
                        return bus_log_parse_error_debug(r);

                return 0;
        }

        case SD_BUS_TYPE_BOOLEAN: {
                int b;

                r = sd_bus_message_read_basic(m, type, &b);
                if (r < 0)
                        return bus_log_parse_error_debug(r);

                if (flags & BUS_MAP_BOOLEAN_AS_BOOL)
                        *(bool*) userdata = b;
                else
                        *(int*) userdata = b;

                return 0;
        }

        case SD_BUS_TYPE_INT32:
        case SD_BUS_TYPE_UINT32: {
                uint32_t *p = userdata;

                r = sd_bus_message_read_basic(m, type, p);
                if (r < 0)
                        return bus_log_parse_error_debug(r);

                return 0;
        }

        case SD_BUS_TYPE_INT64:
        case SD_BUS_TYPE_UINT64: {
                uint64_t *p = userdata;

                r = sd_bus_message_read_basic(m, type, p);
                if (r < 0)
                        return bus_log_parse_error_debug(r);

                return 0;
        }

        case SD_BUS_TYPE_DOUBLE: {
                double *p = userdata;

                r = sd_bus_message_read_basic(m, type, p);
                if (r < 0)
                        return bus_log_parse_error_debug(r);

                return 0;
        }

        default:
                return -EOPNOTSUPP;
        }
}

int bus_message_map_all_properties(
                sd_bus_message *m,
                const struct bus_properties_map *map,
                unsigned flags,
                sd_bus_error *reterr_error,
                void *userdata) {

        int r;

        assert(m);
        assert(map);

        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}");
        if (r < 0)
                return bus_log_parse_error_debug(r);

        while ((r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) {
                const struct bus_properties_map *prop;
                const char *member;
                const char *contents;
                void *v;
                unsigned i;

                r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &member);
                if (r < 0)
                        return bus_log_parse_error_debug(r);

                for (i = 0, prop = NULL; map[i].member; i++)
                        if (streq(map[i].member, member)) {
                                prop = &map[i];
                                break;
                        }

                if (prop) {
                        r = sd_bus_message_peek_type(m, NULL, &contents);
                        if (r < 0)
                                return bus_log_parse_error_debug(r);

                        if (prop->signature && !streq(contents, prop->signature)) {
                                log_debug("Unexpected type for property '%s': expected '%s', got '%s', ignoring.",
                                          member, prop->signature, contents);
                                r = sd_bus_message_skip(m, "v");
                                if (r < 0)
                                        return bus_log_parse_error_debug(r);
                        } else {
                                r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents);
                                if (r < 0)
                                        return bus_log_parse_error_debug(r);

                                v = (uint8_t*) userdata + prop->offset;
                                if (prop->set)
                                        r = prop->set(sd_bus_message_get_bus(m), member, m, reterr_error, v);
                                else
                                        r = map_basic(m, flags, v);
                                if (r < 0)
                                        return bus_log_parse_error_debug(r);

                                r = sd_bus_message_exit_container(m);
                                if (r < 0)
                                        return bus_log_parse_error_debug(r);
                        }
                } else {
                        r = sd_bus_message_skip(m, "v");
                        if (r < 0)
                                return bus_log_parse_error_debug(r);
                }

                r = sd_bus_message_exit_container(m);
                if (r < 0)
                        return bus_log_parse_error_debug(r);
        }
        if (r < 0)
                return bus_log_parse_error_debug(r);

        r = sd_bus_message_exit_container(m);
        if (r < 0)
                return bus_log_parse_error_debug(r);

        return r;
}

int bus_map_all_properties(
                sd_bus *bus,
                const char *destination,
                const char *path,
                const struct bus_properties_map *map,
                unsigned flags,
                sd_bus_error *reterr_error,
                sd_bus_message **ret_reply,
                void *userdata) {

        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
        int r;

        assert(bus);
        assert(destination);
        assert(path);
        assert(map);
        assert(ret_reply || (flags & BUS_MAP_STRDUP));

        r = sd_bus_call_method(
                        bus,
                        destination,
                        path,
                        "org.freedesktop.DBus.Properties",
                        "GetAll",
                        reterr_error,
                        &reply,
                        "s", "");
        if (r < 0)
                return r;

        r = bus_message_map_all_properties(reply, map, flags, reterr_error, userdata);
        if (r < 0)
                return r;

        if (ret_reply)
                *ret_reply = TAKE_PTR(reply);

        return r;
}
