/*!The Treasure Box Library
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Copyright (C) 2009-present, TBOOX Open Source Group.
 *
 * @author      ruki
 * @file        ipv6.c
 * @ingroup     network
 *
 */

/* //////////////////////////////////////////////////////////////////////////////////////
 * trace
 */
#define TB_TRACE_MODULE_NAME            "ipv6"
#define TB_TRACE_MODULE_DEBUG           (0)

/* //////////////////////////////////////////////////////////////////////////////////////
 * includes
 */
#include "ipv6.h"
#include "ipv4.h"
#include "ipaddr.h"
#include "../libc/libc.h"
#include "../math/math.h"
#include "../utils/utils.h"
#include "../string/string.h"
#include "../platform/platform.h"

/* //////////////////////////////////////////////////////////////////////////////////////
 * interfaces
 */
tb_void_t tb_ipv6_clear(tb_ipv6_ref_t ipv6)
{
    // check
    tb_assert_and_check_return(ipv6);

    // clear it
    tb_memset(ipv6, 0, sizeof(tb_ipv6_t));
}
tb_bool_t tb_ipv6_is_any(tb_ipv6_ref_t ipv6)
{
    // check
    tb_assert_and_check_return_val(ipv6, tb_true);

    // is any?
    return !(ipv6->addr.u32[0] || ipv6->addr.u32[1] || ipv6->addr.u32[2] || ipv6->addr.u32[3]);
}
tb_bool_t tb_ipv6_is_loopback(tb_ipv6_ref_t ipv6)
{
    // check
    tb_assert_and_check_return_val(ipv6, tb_true);

    // is loopback?
    return !(ipv6->addr.u32[0] || ipv6->addr.u32[1] || ipv6->addr.u32[2]) && (ipv6->addr.u32[3] == 0x01000000);
}
tb_bool_t tb_ipv6_is_linklocal(tb_ipv6_ref_t ipv6)
{
    // check
    tb_assert_and_check_return_val(ipv6, tb_true);

    // is linklocal?
    return (ipv6->addr.u8[0] == 0xfe) && ((ipv6->addr.u8[1] & 0xc0) == 0x80);
}
tb_bool_t tb_ipv6_is_mc_linklocal(tb_ipv6_ref_t ipv6)
{
    // check
    tb_assert_and_check_return_val(ipv6, tb_true);

    // is mc linklocal?
    return tb_ipv6_is_multicast(ipv6) && ((ipv6->addr.u8[1] & 0x0f) == 0x02);
}
tb_bool_t tb_ipv6_is_sitelocal(tb_ipv6_ref_t ipv6)
{
    // check
    tb_assert_and_check_return_val(ipv6, tb_true);

    // is sitelocal?
    return (ipv6->addr.u8[0] == 0xfe) && ((ipv6->addr.u8[1] & 0xc0) == 0xc0);
}
tb_bool_t tb_ipv6_is_multicast(tb_ipv6_ref_t ipv6)
{
    // check
    tb_assert_and_check_return_val(ipv6, tb_true);

    // is multicast?
    return (ipv6->addr.u8[0] == 0xff);
}
tb_bool_t tb_ipv6_is_equal(tb_ipv6_ref_t ipv6, tb_ipv6_ref_t other)
{
    // check
    tb_assert_and_check_return_val(ipv6 && other, tb_false);

    // have scope id?
    tb_bool_t have_scope_id = tb_ipv6_is_linklocal(ipv6) || tb_ipv6_is_mc_linklocal(ipv6);

    // is equal?
    return (!have_scope_id || (ipv6->scope_id == other->scope_id))
        && (ipv6->addr.u32[0] == other->addr.u32[0])
        && (ipv6->addr.u32[1] == other->addr.u32[1])
        && (ipv6->addr.u32[2] == other->addr.u32[2])
        && (ipv6->addr.u32[3] == other->addr.u32[3]);
}
tb_char_t const* tb_ipv6_cstr(tb_ipv6_ref_t ipv6, tb_char_t* data, tb_size_t maxn)
{
    // check
    tb_assert_and_check_return_val(ipv6 && data && maxn >= TB_IPV6_CSTR_MAXN, tb_null);

    // make scope_id
    tb_char_t scope_id[20] = {0};
    tb_bool_t have_scope_id = tb_ipv6_is_linklocal(ipv6) || tb_ipv6_is_mc_linklocal(ipv6);
    if (have_scope_id) tb_snprintf(scope_id, sizeof(scope_id) - 1, "%%%u", ipv6->scope_id);

    // make ipv6
    tb_long_t size = tb_snprintf(   data
                                ,   maxn - 1
                                ,   "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x%s"
                                ,   tb_bits_swap_u16(ipv6->addr.u16[0])
                                ,   tb_bits_swap_u16(ipv6->addr.u16[1])
                                ,   tb_bits_swap_u16(ipv6->addr.u16[2])
                                ,   tb_bits_swap_u16(ipv6->addr.u16[3])
                                ,   tb_bits_swap_u16(ipv6->addr.u16[4])
                                ,   tb_bits_swap_u16(ipv6->addr.u16[5])
                                ,   tb_bits_swap_u16(ipv6->addr.u16[6])
                                ,   tb_bits_swap_u16(ipv6->addr.u16[7])
                                ,   have_scope_id? scope_id : "");
    if (size >= 0) data[size] = '\0';

    // ok
    return data;
}
tb_bool_t tb_ipv6_cstr_set(tb_ipv6_ref_t ipv6, tb_char_t const* cstr)
{
    // check
    tb_assert_and_check_return_val(cstr, tb_false);

    // ipv4: ::ffff:xxx.xxx.xxx.xxx?
    if (!tb_strnicmp(cstr, "::ffff:", 7))
    {
        // attempt to make ipv6 from ipv4
        tb_ipv4_t ipv4;
        if (tb_ipv4_cstr_set(&ipv4, cstr + 7))
        {
            // make ipv6
            ipv6->addr.u32[0] = 0;
            ipv6->addr.u32[1] = 0;
            ipv6->addr.u32[2] = 0xffff0000;
            ipv6->addr.u32[3] = ipv4.u32;

            // ok
            return tb_true;
        }
    }
    // ipv4: ::xxx.xxx.xxx.xxx?
    else if (!tb_strnicmp(cstr, "::", 2))
    {
        // attempt to make ipv6 from ipv4
        tb_ipv4_t ipv4;
        if (tb_ipv4_cstr_set(&ipv4, cstr + 2))
        {
            // make ipv6
            ipv6->addr.u32[0] = 0;
            ipv6->addr.u32[1] = 0;
            ipv6->addr.u32[2] = 0;
            ipv6->addr.u32[3] = ipv4.u32;

            // ok
            return tb_true;
        }
    }

    // done
    tb_uint32_t         v = 0;
    tb_size_t           i = 0;
    tb_char_t           c = '\0';
    tb_char_t const*    p = cstr;
    tb_bool_t           ok = tb_true;
    tb_bool_t           stub = tb_false;
    tb_char_t           prev = '\0';
    tb_ipv6_t           temp = {0};
    do
    {
        // save previous character
        prev = c;

        // the character
        c = *p++;

        // digit?
        if (tb_isdigit16(c) && v <= 0xffff)
        {
            // update value
            if (tb_isdigit10(c))
                v = (v << 4) + (c - '0');
            else if (c > ('a' - 1) && c < ('f' + 1))
                v = (v << 4) + (c - 'a') + 10;
            else if (c > ('A' - 1) && c < ('F' + 1))
                v = (v << 4) + (c - 'A') + 10;
            else
            {
                // abort
                tb_assert(0);

                // failed
                ok = tb_false;
                break;
            }
        }
        // "::"?
        else if (c == ':' && *p == ':' && p[1] != ':' && !stub)
        {
            // save value
            temp.addr.u16[i++] = tb_bits_swap_u16(v);

            // clear value
            v = 0;

            // clear previous value
            prev = '\0';

            // find the left count of ':'
            tb_long_t           n = 0;
            tb_char_t const*    q = p;
            while (*q)
            {
                if (*q == ':') n++;
                q++;
            }
            tb_check_break_state(n <= 6, ok, tb_false);

            // compute the stub count
            n = 8 - n - i;
            tb_check_break_state(n > 0, ok, tb_false);

            // save the stub value
            while (n-- > 0) temp.addr.u16[i++] = 0;

            // only one "::"
            stub = tb_true;

            // skip ':'
            p++;
        }
        // ':' or '\0' or '%'?
        else if (i < 8 && ((c == ':' && *p != ':') || !c) && v <= 0xffff && prev)
        {
            // save value
            temp.addr.u16[i++] = tb_bits_swap_u16(v);

            // clear value
            v = 0;

            // clear previous value
            prev = '\0';
        }
        // "%xxx"?
        else if (i == 7 && c == '%' && *p)
        {
            // save value
            temp.addr.u16[i++] = tb_bits_swap_u16(v);

            // is scope id?
            if (tb_isdigit(*p))
            {
                // save the scope id
                temp.scope_id = tb_atoi(p);

                // trace
                tb_trace_d("scope_id: %u", temp.scope_id);
            }
#ifndef TB_CONFIG_MICRO_ENABLE
            // is interface name?
            else
            {
                // trace
                tb_trace_d("name: %s", p);

                // get the scope id from the interface name
                tb_ipaddr_t ipaddr;
                if (tb_ifaddrs_ipaddr(tb_ifaddrs(), p, tb_false, TB_IPADDR_FAMILY_IPV6, &ipaddr))
                {
                    // trace
                    tb_trace_d("scope_id: %u", ipaddr.u.ipv6.scope_id);

                    // save the scope id
                    temp.scope_id = ipaddr.u.ipv6.scope_id;
                }
                // clear the scope id
                else temp.scope_id = 0;
            }
#else
            // clear the scope id
            else temp.scope_id = 0;
#endif

            // end
            break;
        }
        // failed?
        else
        {
            ok = tb_false;
            break;
        }

    } while (c);

    // failed
    if (i != 8) ok = tb_false;

    // save it if ok
    if (ok && ipv6) *ipv6 = temp;

    // trace
//    tb_assertf(ok, "invalid addr: %s", cstr);

    // ok?
    return ok;
}

