/***
  This file is part of systemd.

  Copyright (C) 2014 Tom Gundersen
  Copyright (C) 2014 Intel Corporation. All rights reserved.

  systemd is free software; you can redistribute it and/or modify it
  under the terms of the GNU Lesser General Public License as published by
  the Free Software Foundation; either version 2.1 of the License, or
  (at your option) any later version.

  systemd is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/

#include "config.h"

#include "nm-sd-adapt.h"

#include <errno.h>

#include "util.h"

#include "dhcp6-lease-internal.h"

int dhcp6_lease_clear_timers(DHCP6IA *ia) {
        assert_return(ia, -EINVAL);

        ia->timeout_t1 = sd_event_source_unref(ia->timeout_t1);
        ia->timeout_t2 = sd_event_source_unref(ia->timeout_t2);

        return 0;
}

int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire) {
        DHCP6Address *addr;
        uint32_t valid = 0, t;

        assert_return(ia, -EINVAL);
        assert_return(expire, -EINVAL);

        LIST_FOREACH(addresses, addr, ia->addresses) {
                t = be32toh(addr->iaaddr.lifetime_valid);
                if (valid < t)
                        valid = t;
        }

        t = be32toh(ia->lifetime_t2);
        if (t > valid)
                return -EINVAL;

        *expire = valid - t;

        return 0;
}

DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia) {
        DHCP6Address *address;

        if (!ia)
                return NULL;

        dhcp6_lease_clear_timers(ia);

        while (ia->addresses) {
                address = ia->addresses;

                LIST_REMOVE(addresses, ia->addresses, address);

                free(address);
        }

        return NULL;
}

int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id,
                             size_t len) {
        assert_return(lease, -EINVAL);
        assert_return(id, -EINVAL);

        free(lease->serverid);

        lease->serverid = memdup(id, len);
        if (!lease->serverid)
                return -EINVAL;

        lease->serverid_len = len;

        return 0;
}

int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **id, size_t *len) {
        assert_return(lease, -EINVAL);
        assert_return(id, -EINVAL);
        assert_return(len, -EINVAL);

        *id = lease->serverid;
        *len = lease->serverid_len;

        return 0;
}

int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference) {
        assert_return(lease, -EINVAL);

        lease->preference = preference;

        return 0;
}

int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference) {
        assert_return(lease, -EINVAL);
        assert_return(preference, -EINVAL);

        *preference = lease->preference;

        return 0;
}

int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease) {
        assert_return(lease, -EINVAL);

        lease->rapid_commit = true;

        return 0;
}

int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit) {
        assert_return(lease, -EINVAL);
        assert_return(rapid_commit, -EINVAL);

        *rapid_commit = lease->rapid_commit;

        return 0;
}

int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid) {
        assert_return(lease, -EINVAL);
        assert_return(iaid, -EINVAL);

        *iaid = lease->ia.id;

        return 0;
}

int sd_dhcp6_lease_get_next_address(sd_dhcp6_lease *lease,
                                    struct in6_addr *addr,
                                    uint32_t *lifetime_preferred,
                                    uint32_t *lifetime_valid) {
        assert_return(lease, -EINVAL);
        assert_return(addr, -EINVAL);
        assert_return(lifetime_preferred, -EINVAL);
        assert_return(lifetime_valid, -EINVAL);

        if (!lease->addr_iter)
                return -ENOMSG;

        memcpy(addr, &lease->addr_iter->iaaddr.address,
                sizeof(struct in6_addr));
        *lifetime_preferred =
                be32toh(lease->addr_iter->iaaddr.lifetime_preferred);
        *lifetime_valid = be32toh(lease->addr_iter->iaaddr.lifetime_valid);

        lease->addr_iter = lease->addr_iter->addresses_next;

        return 0;
}

int sd_dhcp6_lease_get_first_address(sd_dhcp6_lease *lease,
                                     struct in6_addr *addr,
                                     uint32_t *lifetime_preferred,
                                     uint32_t *lifetime_valid) {
        assert_return(lease, -EINVAL);
        assert_return(addr, -EINVAL);
        assert_return(lifetime_preferred, -EINVAL);
        assert_return(lifetime_valid, -EINVAL);

        if (!lease->ia.addresses)
                return -ENOMSG;

        lease->addr_iter = lease->ia.addresses;

        return sd_dhcp6_lease_get_next_address(lease, addr, lifetime_preferred,
                                               lifetime_valid);
}

sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease) {
        if (lease)
                assert_se(REFCNT_INC(lease->n_ref) >= 2);

        return lease;
}

sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease) {
        if (lease && REFCNT_DEC(lease->n_ref) <= 0) {
                free(lease->serverid);
                dhcp6_lease_free_ia(&lease->ia);

                free(lease);
        }

        return NULL;
}

int dhcp6_lease_new(sd_dhcp6_lease **ret) {
        sd_dhcp6_lease *lease;

        lease = new0(sd_dhcp6_lease, 1);
        if (!lease)
                return -ENOMEM;

        lease->n_ref = REFCNT_INIT;

        LIST_HEAD_INIT(lease->ia.addresses);

        *ret = lease;
        return 0;
}
