//
//  KCDer.m
//  Security
//
//

#import <Foundation/Foundation.h>

#include <KeychainCircle/KCDer.h>
#import <KeychainCircle/KCError.h>
#import <os/overflow.h>

// These should probably be shared with security, but we don't export our der'izing functions yet.


static const uint8_t* kcder_decode_data_internal(NSData** data, bool copy,
                                          NSError**error,
                                          const uint8_t* der, const uint8_t *der_end)
{
    if (NULL == der)
        return NULL;

    size_t payload_size = 0;
    const uint8_t *payload = ccder_decode_tl(CCDER_OCTET_STRING, &payload_size, der, der_end);

    uintptr_t payload_end_computed = 0;
    if(os_add_overflow((uintptr_t)payload, payload_size, &payload_end_computed)) {
        KCJoiningErrorCreate(kDERUnknownEncoding, error, @"Bad payload size");
        return NULL;
    }
    if (NULL == payload || payload_end_computed > (uintptr_t) der_end) {
        KCJoiningErrorCreate(kDERUnknownEncoding, error, @"Unknown data encoding");
        return NULL;
    }

    *data = copy ? [NSData dataWithBytes: (void*)payload length: payload_size] :
                   [NSData dataWithBytesNoCopy: (void*)payload length:payload_size freeWhenDone:NO];

    if (NULL == *data) {
        KCJoiningErrorCreate(kAllocationFailure, error, @"Allocation failure!");
        return NULL;
    }

    return payload + payload_size;
}


const uint8_t* kcder_decode_data_nocopy(NSData** data,
                                        NSError**error,
                                        const uint8_t* der, const uint8_t *der_end)
{
    return kcder_decode_data_internal(data, NO, error, der, der_end);
}

const uint8_t* kcder_decode_data(NSData** data,
                                 NSError**error,
                                 const uint8_t* der, const uint8_t *der_end) {
    return kcder_decode_data_internal(data, YES, error, der, der_end);
}


size_t kcder_sizeof_data(NSData* data, NSError** error) {
    return ccder_sizeof_raw_octet_string(data.length);
}

uint8_t* kcder_encode_data_optional(NSData* _Nullable data, NSError**error,
                           const uint8_t *der, uint8_t *der_end)
{
    if (data == nil) return der_end;

    return kcder_encode_data(data, error, der, der_end);

}


uint8_t* kcder_encode_data(NSData* data, NSError**error,
                                  const uint8_t *der, uint8_t *der_end)
{
    return ccder_encode_tl(CCDER_OCTET_STRING, data.length, der,
                           ccder_encode_body(data.length, data.bytes, der, der_end));

}


const uint8_t* kcder_decode_string(NSString** string, NSError**error,
                                          const uint8_t* der, const uint8_t *der_end)
{
    if (NULL == der)
        return NULL;

    size_t payload_size = 0;
    const uint8_t *payload = ccder_decode_tl(CCDER_UTF8_STRING, &payload_size, der, der_end);

    uintptr_t payload_end_computed = 0;
    if(os_add_overflow((uintptr_t)payload, payload_size, &payload_end_computed)) {
        KCJoiningErrorCreate(kDERUnknownEncoding, error, @"Bad payload size");
        return NULL;
    }
    if (NULL == payload || payload_end_computed > (uintptr_t) der_end) {
        KCJoiningErrorCreate(kDERUnknownEncoding, error, @"Unknown string encoding");
        return NULL;
    }

    *string = [[NSString alloc] initWithBytes:payload length:payload_size encoding:NSUTF8StringEncoding];

    if (nil == *string) {
        KCJoiningErrorCreate(kAllocationFailure, error, @"Allocation failure!");
        return NULL;
    }

    return payload + payload_size;
}


size_t kcder_sizeof_string(NSString* string, NSError** error)
{
    return ccder_sizeof(CCDER_UTF8_STRING, [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
}


uint8_t* kcder_encode_string(NSString* string, NSError** error,
                                    const uint8_t *der, uint8_t *der_end)
{
    // Obey the NULL allowed rules.
    if (!der_end)
        return NULL;

    NSUInteger max = (der_end - der);
    void *buffer = der_end - max;
    NSUInteger used = 0;
    if (![string getBytes:buffer
                maxLength:max
               usedLength:&used
                 encoding:NSUTF8StringEncoding
                  options:0
                    range:NSMakeRange(0, string.length)
           remainingRange:nil]) {
        KCJoiningErrorCreate(kDERStringEncodingFailed, error, @"String encoding failed");
        return NULL;
    }

    return ccder_encode_tl(CCDER_UTF8_STRING, used, der,
                           ccder_encode_body(used, buffer, der, der_end));
    
}

uint8_t *kcder_encode_raw_octet_space(size_t s_size, uint8_t **location,
                                      const uint8_t *der, uint8_t *der_end) {
    der_end = ccder_encode_body_nocopy(s_size, der, der_end);
    if (der_end && location)
        *location = der_end;

    return ccder_encode_tl(CCDER_OCTET_STRING, s_size, der, der_end);
}

