//
//  OSSIPv6PrefixResolver.m
//
//  Created by lingkun on 16/5/16.
//  Edit by zhouzhuo on 2016/5/22
//  Copyright © 2016 Ali. All rights reserved.

#import "OSSIPv6PrefixResolver.h"
#import "OSSLog.h"

#import <Foundation/Foundation.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>

static const __uint8_t NS_PREFIX_32[4] = {0x20, 0x01, 0x0d, 0xb8};
static const __uint8_t NS_PREFIX_40[5] = {0x20, 0x01, 0x0d, 0xb8, 0x01};
static const __uint8_t NS_PREFIX_48[6] = {0x20, 0x01, 0x0d, 0xb8, 0x01, 0x22};
static const __uint8_t NS_PREFIX_56[7] = {0x20, 0x01, 0x0d, 0xb8, 0x01, 0x22, 0x03};
static const __uint8_t NS_PREFIX_64[8] = {0x20, 0x01, 0x0d, 0xb8, 0x01, 0x22, 0x03, 0x44};
static const __uint8_t NS_PREFIX_96[12] = {0x20, 0x01, 0x0d, 0xb8, 0x01, 0x22, 0x03, 0x44, 0x00, 0x00, 0x00, 0x00};
static const __uint8_t WK_PREFIX_96[12] = {0x00, 0x64, 0xff, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

static const __uint8_t* V6_PREFIX_CONTENT_TABLE[7] = {
    WK_PREFIX_96,
    NS_PREFIX_32,
    NS_PREFIX_40,
    NS_PREFIX_48,
    NS_PREFIX_56,
    NS_PREFIX_64,
    NS_PREFIX_96};

static const __uint8_t V6_PREFIX_SIZE_TABLE[7] = {
    sizeof(WK_PREFIX_96)/sizeof(__uint8_t),
    sizeof(NS_PREFIX_32)/sizeof(__uint8_t),
    sizeof(NS_PREFIX_40)/sizeof(__uint8_t),
    sizeof(NS_PREFIX_48)/sizeof(__uint8_t),
    sizeof(NS_PREFIX_56)/sizeof(__uint8_t),
    sizeof(NS_PREFIX_64)/sizeof(__uint8_t),
    sizeof(NS_PREFIX_96)/sizeof(__uint8_t)};

static const __uint8_t V6_PREFIX_TABLE_SIZE = 7;

typedef enum {
    IPv6PrefixUnResolved = 0,
    IPv6PrefixResolving,
    IPv6PrefixResolved
} IPv6PrefixResolveStatus;

@implementation OSSIPv6PrefixResolver {
    IPv6PrefixResolveStatus ipv6PrefixResolveStatus;
    __uint8_t *ipv6Prefix;
    int prefixLen;
}

- (instancetype)init {
    if (self = [super init]) {
        ipv6PrefixResolveStatus = IPv6PrefixUnResolved;
        ipv6Prefix = (__uint8_t *)V6_PREFIX_CONTENT_TABLE[0];
        prefixLen = V6_PREFIX_SIZE_TABLE[0];
    }
    return self;
}

+ (instancetype)getInstance {
    static id singletonInstance = nil;
    static dispatch_once_t once_token;
    dispatch_once(&once_token, ^{
        if (!singletonInstance) {
            singletonInstance = [[super allocWithZone:NULL] init];
        }
    });
    return singletonInstance;
}

/**
 *	@brief	Updates IPv6 Prefix
 */
- (void)updateIPv6Prefix {
    @synchronized(self) {
        ipv6PrefixResolveStatus = IPv6PrefixUnResolved;
        [self resolveIPv6Prefix:ipv6Prefix];
    }
}

- (BOOL)isIPv6Prefix:(__uint8_t *)v6Prefix
       withPrefixLen:(int)pLen
              withIP:(__uint8_t *)ip
           withIPLen:(int)ipLen {
    for (int i = 0; i < pLen && i < ipLen; i++) {
        if (*(v6Prefix + i) != *(ip + i)) {
            return NO;
        }
    }
    return YES;
}

- (__uint8_t)resolveIPv6Prefix:(__uint8_t *)prefix {
    if ( !prefix ) {
        return 0;
    }
    __uint8_t len = prefixLen;
    memcpy(prefix, ipv6Prefix, prefixLen);
    @synchronized(self) {
        if (ipv6PrefixResolveStatus==IPv6PrefixUnResolved ) {
            ipv6PrefixResolveStatus = IPv6PrefixResolving;
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                struct addrinfo hints, *addr;
                memset(&hints, 0, sizeof(hints));
                hints.ai_family = PF_INET6;
                hints.ai_socktype = SOCK_STREAM;
                hints.ai_flags = AI_DEFAULT;
                
                if (0 != getaddrinfo("ipv4only.arpa", "http", &hints, &addr)) {
                    ipv6PrefixResolveStatus = IPv6PrefixUnResolved;
                    return;
                }
                
                if (addr && AF_INET6 == addr->ai_addr->sa_family) {
                    struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)(addr->ai_addr);
                    if ( !addr6 ) {
                        ipv6PrefixResolveStatus = IPv6PrefixUnResolved;
                        return;
                    }
                    
                    __uint8_t* u8 = addr6->sin6_addr.__u6_addr.__u6_addr8;
                    for (__uint8_t i=0; i < V6_PREFIX_TABLE_SIZE; i++) {
                        if ([self isIPv6Prefix:(__uint8_t *)V6_PREFIX_CONTENT_TABLE[i]
                                 withPrefixLen:V6_PREFIX_SIZE_TABLE[i]
                                        withIP:u8
                                     withIPLen:16]) {
                            
                            ipv6Prefix = (__uint8_t *)V6_PREFIX_CONTENT_TABLE[i];
                            prefixLen = V6_PREFIX_SIZE_TABLE[i];
                            ipv6PrefixResolveStatus = IPv6PrefixResolved;
                            break;
                        }
                    }
                    ipv6PrefixResolveStatus = IPv6PrefixUnResolved;
                }
                
            });
        }
    }
    
    return len;
}

- (NSString *)convertIPv4toIPv6:(NSString *)ipv4 {
    if ([ipv4 length] <= 0) {
        return nil;
    }
    
    __uint8_t ipv6[16] = {0x00};
    __uint8_t length = [self resolveIPv6Prefix:ipv6];
    
    if (length <= 0) {
        return nil;
    }
    
    in_addr_t addr_v4 = inet_addr([ipv4 UTF8String]);
    
    // 按length的不同情况进行处理
    if (length==4 || length==12) { //32 bits or 96 bits
        ipv6[length+0] |= (__uint8_t)(addr_v4>>0 & 0xff);
        ipv6[length+1] |= (__uint8_t)(addr_v4>>8 & 0xff);
        ipv6[length+2] |= (__uint8_t)(addr_v4>>16 & 0xff);
        ipv6[length+3] |= (__uint8_t)(addr_v4>>24 & 0xff);
    }
    else if (length == 5) { //40 bits  :a.b.c.0.d
        ipv6[length+0] |= (__uint8_t)(addr_v4>>0 & 0xff);
        ipv6[length+1] |= (__uint8_t)(addr_v4>>8 & 0xff);
        ipv6[length+2] |= (__uint8_t)(addr_v4>>16 & 0xff);
        ipv6[length+4] |= (__uint8_t)(addr_v4>>24 & 0xff);
    }
    else if (length == 6) { //48 bits   :a.b.0.c.d
        ipv6[length+0] |= (__uint8_t)(addr_v4>>0 & 0xff);
        ipv6[length+1] |= (__uint8_t)(addr_v4>>8 & 0xff);
        ipv6[length+3] |= (__uint8_t)(addr_v4>>16 & 0xff);
        ipv6[length+4] |= (__uint8_t)(addr_v4>>24 & 0xff);
    }
    else if (length == 7) { //56 bits   :a.0.b.c.d
        ipv6[length+0] |= (__uint8_t)(addr_v4>>0 & 0xff);
        ipv6[length+2] |= (__uint8_t)(addr_v4>>8 & 0xff);
        ipv6[length+3] |= (__uint8_t)(addr_v4>>16 & 0xff);
        ipv6[length+4] |= (__uint8_t)(addr_v4>>24 & 0xff);
    }
    else if (length == 8) { //64 bits   :0.a.b.c.d
        ipv6[length+1] |= (__uint8_t)(addr_v4>>0 & 0xff);
        ipv6[length+2] |= (__uint8_t)(addr_v4>>8 & 0xff);
        ipv6[length+3] |= (__uint8_t)(addr_v4>>16 & 0xff);
        ipv6[length+4] |= (__uint8_t)(addr_v4>>24 & 0xff);
    }
    
    // constructs the IPv6 structure
    char addr_text[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
    if(inet_ntop(AF_INET6, ipv6, addr_text, INET6_ADDRSTRLEN)) {
        NSString *ret = [NSString stringWithUTF8String:addr_text];
        return ret;
    }
    return nil;
}

@end