#ifndef _IPV6_UTILS_H
#define _IPV6_UTILS_H

#ifdef __cplusplus
extern "C"
{
#endif

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/ip6.h>

/*
 * IPv6 Header Format
 *
 *   0                   1                   2                   3
 *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |Version| Traffic Class |           Flow Label                  |
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |         Payload Length        |  Next Header  |   Hop Limit   |
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |                                                               |
 *  +                                                               +
 *  |                                                               |
 *  +                         Source Address                        +
 *  |                                                               |
 *  +                                                               +
 *  |                                                               |
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |                                                               |
 *  +                                                               +
 *  |                                                               |
 *  +                      Destination Address                      +
 *  |                                                               |
 *  +                                                               +
 *  |                                                               |
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *
 * Fragment Header
 *
 *  0                   1                   2                   3
 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |  Next Header  |   Reserved    |      Fragment Offset    |Res|M|
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                         Identification                        |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 */

/******************************************************************************
 * get
 ******************************************************************************/

static inline uint8_t ipv6_hdr_get_version(const struct ip6_hdr *hdr)
{
    return (ntohl(hdr->ip6_flow) & 0xf0000000) >> 28;
}

static inline uint8_t ipv6_hdr_get_traffic_class(const struct ip6_hdr *hdr)
{
    return (ntohl(hdr->ip6_flow) & 0x0ff00000) >> 20;
}

static inline uint32_t ipv6_hdr_get_flow_label(const struct ip6_hdr *hdr)
{
    return ntohl(hdr->ip6_flow) & 0x000fffff;
}

static inline uint16_t ipv6_hdr_get_payload_len(const struct ip6_hdr *hdr)
{
    return ntohs(hdr->ip6_plen);
}

static inline uint8_t ipv6_hdr_get_next_header(const struct ip6_hdr *hdr)
{
    return hdr->ip6_nxt;
}

static inline uint8_t ipv6_hdr_get_hop_limit(const struct ip6_hdr *hdr)
{
    return hdr->ip6_hlim;
}

static inline struct in6_addr ipv6_hdr_get_src_in6_addr(const struct ip6_hdr *hdr)
{
    return hdr->ip6_src;
}

static inline struct in6_addr ipv6_hdr_get_dst_in6_addr(const struct ip6_hdr *hdr)
{
    return hdr->ip6_dst;
}

static inline struct ip6_frag *ipv6_hdr_get_frag_ext(const struct ip6_hdr *hdr)
{
    if (hdr->ip6_nxt != IPPROTO_FRAGMENT)
    {
        return NULL;
    }
    return (struct ip6_frag *)((char *)hdr + sizeof(struct ip6_hdr));
}

/******************************************************************************
 * set
 ******************************************************************************/

static inline void ipv6_hdr_set_version(struct ip6_hdr *hdr, uint8_t version)
{
    hdr->ip6_flow = htonl((ntohl(hdr->ip6_flow) & 0x0fffffff) | (version << 28));
}

static inline void ipv6_hdr_set_traffic_class(struct ip6_hdr *hdr, uint8_t traffic_class)
{
    hdr->ip6_flow = htonl((ntohl(hdr->ip6_flow) & 0xf00fffff) | (traffic_class << 20));
}

static inline void ipv6_hdr_set_flow_label(struct ip6_hdr *hdr, uint32_t flow_label)
{
    hdr->ip6_flow = htonl((ntohl(hdr->ip6_flow) & 0xfff00000) | flow_label);
}

static inline void ipv6_hdr_set_payload_len(struct ip6_hdr *hdr, uint16_t payload_len)
{
    hdr->ip6_plen = htons(payload_len);
}

static inline void ipv6_hdr_set_next_header(struct ip6_hdr *hdr, uint8_t next_header)
{
    hdr->ip6_nxt = next_header;
}

static inline void ipv6_hdr_set_hop_limit(struct ip6_hdr *hdr, uint8_t hop_limit)
{
    hdr->ip6_hlim = hop_limit;
}

static inline void ipv6_hdr_set_src_in6_addr(struct ip6_hdr *hdr, struct in6_addr src_addr)
{
    hdr->ip6_src = src_addr;
}

static inline void ipv6_hdr_set_dst_in6_addr(struct ip6_hdr *hdr, struct in6_addr dst_addr)
{
    hdr->ip6_dst = dst_addr;
}

/******************************************************************************
 * IPv6 frag extension headers
 ******************************************************************************/

static inline uint8_t ipv6_frag_get_next_header(const struct ip6_frag *frag)
{
    return frag->ip6f_nxt;
}

static inline uint16_t ipv6_frag_get_offset(const struct ip6_frag *frag)
{
    return ntohs(frag->ip6f_offlg & IP6F_OFF_MASK);
}

static inline uint32_t ipv6_frag_get_ident(const struct ip6_frag *frag)
{
    return ntohl(frag->ip6f_ident);
}

static inline bool ipv6_frag_get_more(const struct ip6_frag *frag)
{
    return (frag->ip6f_offlg & IP6F_MORE_FRAG);
}

static inline void ipv6_frag_set_next_header(struct ip6_frag *frag, uint8_t next_header)
{
    frag->ip6f_nxt = next_header;
}

static inline void ipv6_frag_set_offset(struct ip6_frag *frag, uint16_t offset)
{
    frag->ip6f_offlg = (frag->ip6f_offlg & ~IP6F_OFF_MASK) | htons(offset);
}

static inline void ipv6_frag_set_ident(struct ip6_frag *frag, uint32_t ident)
{
    frag->ip6f_ident = htonl(ident);
}

static inline void ipv6_frag_set_more(struct ip6_frag *frag, bool more)
{
    if (more)
    {
        frag->ip6f_offlg |= IP6F_MORE_FRAG;
    }
    else
    {
        frag->ip6f_offlg &= ~IP6F_MORE_FRAG;
    }
}

/******************************************************************************
 * print
 ******************************************************************************/

static inline int ipv6_hdr_to_str(const struct ip6_hdr *hdr, char *buf, size_t size)
{
    memset(buf, 0, size);
    char src_addr_str[INET6_ADDRSTRLEN] = {0};
    char dst_addr_str[INET6_ADDRSTRLEN] = {0};

    struct in6_addr src_addr = ipv6_hdr_get_src_in6_addr(hdr);
    struct in6_addr dst_addr = ipv6_hdr_get_dst_in6_addr(hdr);
    inet_ntop(AF_INET6, &src_addr, src_addr_str, INET6_ADDRSTRLEN);
    inet_ntop(AF_INET6, &dst_addr, dst_addr_str, INET6_ADDRSTRLEN);

    return snprintf(buf, size, "IPv6: version=%u traffic_class=%u flow_label=%u payload_len=%u next_header=%u hop_limit=%u src_addr=%s dst_addr=%s",
                    ipv6_hdr_get_version(hdr), ipv6_hdr_get_traffic_class(hdr), ipv6_hdr_get_flow_label(hdr), ipv6_hdr_get_payload_len(hdr),
                    ipv6_hdr_get_next_header(hdr), ipv6_hdr_get_hop_limit(hdr), src_addr_str, dst_addr_str);
}

#ifdef __cplusplus
}
#endif

#endif
