//
//  SOSPeerInfoV2.c
//  sec
//
//  Created by Richard Murphy on 1/26/15.
//
//

#include <AssertMacros.h>
#include "SOSPeerInfoV2.h"
#include "keychain/SecureObjectSync/SOSInternal.h"
#include "keychain/SecureObjectSync/SOSPeerInfoV2.h"
#include "keychain/SecureObjectSync/SOSPeerInfoPriv.h"
#include "keychain/SecureObjectSync/SOSPeerInfoInternal.h"
#include <utilities/der_plist.h>
#include <utilities/der_plist_internal.h>
#include <corecrypto/ccder.h>
#include <utilities/der_date.h>


// Description Dictionary Entries Added for V2
CFStringRef sV2DictionaryKey            = CFSTR("V2DictionaryData");        // CFData wrapper for V2 extensions
CFStringRef sViewsKey                   = CFSTR("Views");                   // Array of View labels
CFStringRef sSerialNumberKey            = CFSTR("SerialNumber");
CFStringRef sMachineIDKey               = CFSTR("MachineIDKey");
CFStringRef sViewsPending               = CFSTR("ViewsPending");            // Array of View labels (pending)

CFStringRef kSOSHsaCrKeyDictionary      = CFSTR("HSADictionary");
CFStringRef sRingState                  = CFSTR("RingState");
CFStringRef sBackupKeyKey               = CFSTR("BackupKey");
CFStringRef sEscrowRecord               = CFSTR("EscrowRecord");

CFStringRef SOSGestaltSerial = NULL;

#if TARGET_OS_IPHONE

// need entitlement for this:

#include <MobileGestalt.h>

static CFStringRef SOSCopySerialNumberAsString(CFErrorRef *error) {
    if (SOSGestaltSerial)
        return CFRetain(SOSGestaltSerial);

    CFTypeRef iosAnswer = (CFStringRef) MGCopyAnswer(kMGQSerialNumber, NULL);
    if(!iosAnswer) {
        SOSCreateError(kSOSErrorAllocationFailure,  CFSTR("No Memory"), NULL, error);
    }
    return (CFStringRef) iosAnswer;
}

#else

#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>

static CFStringRef SOSCopySerialNumberAsString(CFErrorRef *error) {
    if (SOSGestaltSerial)
        return CFRetain(SOSGestaltSerial);
    CFStringRef serialNumber = NULL;
    CFStringRef retval = NULL;

    io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
    require_action_quiet(platformExpert, errOut, SOSCreateError(kSOSErrorAllocationFailure,  CFSTR("No Memory"), NULL, error));
    serialNumber = IORegistryEntryCreateCFProperty(platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0);
    if(serialNumber) retval = CFStringCreateCopy(kCFAllocatorDefault, serialNumber);
    IOObjectRelease(platformExpert);
    CFReleaseNull(serialNumber);
errOut:
    return retval;
}

#endif

bool SOSPeerInfoSerialNumberIsSet(SOSPeerInfoRef pi) {
    CFStringRef serial = SOSPeerInfoV2DictionaryCopyString(pi, sSerialNumberKey);
    bool retval = (serial != NULL);
    CFReleaseNull(serial);
    return retval;
}

void SOSPeerInfoSetSerialNumber(SOSPeerInfoRef pi) {
    CFStringRef serialNumber = SOSCopySerialNumberAsString(NULL);
    if(serialNumber) SOSPeerInfoV2DictionarySetValue(pi, sSerialNumberKey, serialNumber);
    CFReleaseNull(serialNumber);
}

const CFStringRef SOSSerialUnknown = CFSTR("Unknown");

void SOSPeerInfoSetTestSerialNumber(SOSPeerInfoRef pi, CFStringRef serialNumber) {
    if(serialNumber) SOSPeerInfoV2DictionarySetValue(pi, sSerialNumberKey, serialNumber);
}

CFStringRef SOSPeerInfoCopySerialNumber(SOSPeerInfoRef pi) {
    CFStringRef retval = SOSPeerInfoV2DictionaryCopyString(pi, sSerialNumberKey);
    return (retval ? retval : CFRetain(SOSSerialUnknown));
}

static bool SOSPeerInfoV2SanityCheck(SOSPeerInfoRef pi) {
    if(!pi) {
        return false;
    }
    if(!SOSPeerInfoVersionHasV2Data(pi)) {
        return false;
    }
    return true;
}

static CFDataRef SOSPeerInfoGetV2Data(SOSPeerInfoRef pi) {
    if(SOSPeerInfoV2SanityCheck(pi) == false) return NULL;
    return asData(CFDictionaryGetValue(pi->description, sV2DictionaryKey), NULL);
}

static CFMutableDictionaryRef SOSCreateDictionaryFromDER(CFDataRef v2Data, CFErrorRef *error) {
    CFPropertyListRef pl = NULL;
    
    if(!v2Data) {
        secerror("Creating raw dictionary instead of creating from DER");
        return CFDictionaryCreateMutable(NULL, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    }

    if(CFGetTypeID(v2Data) != CFDataGetTypeID()) {
        SOSCreateError(kSOSErrorBadFormat, CFSTR("Corrupted v2Data Item"), NULL, error);
        goto fail;
    }

    const uint8_t *der_p = CFDataGetBytePtr(v2Data);
    const uint8_t *der_end = CFDataGetLength(v2Data) + der_p;
    
    der_p = der_decode_plist(NULL, &pl, error, der_p, der_end);

    if (der_p == NULL || der_p != der_end) {
        SOSCreateError(kSOSErrorBadFormat, CFSTR("Bad Format of Dictionary DER"), NULL, error);
        goto fail;
    }

    if (CFGetTypeID(pl) != CFDictionaryGetTypeID()) {
        CFStringRef description = CFCopyTypeIDDescription(CFGetTypeID(pl));
        SOSCreateErrorWithFormat(kSOSErrorUnexpectedType, NULL, error, NULL,
                                 CFSTR("Expected dictionary got %@"), description);
        CFReleaseNull(description);
        CFReleaseNull(pl);
        goto fail;
    }

    return (CFMutableDictionaryRef) pl;

fail:
    CFReleaseNull(pl);
    return NULL;
}


static CFDataRef SOSCreateDERFromDictionary(CFDictionaryRef di, CFErrorRef *error) {
    size_t size = der_sizeof_plist(di, error);
    if (size == 0) {
        return NULL;
    }
    uint8_t *der = malloc(size);
    if (der == NULL) {
        return NULL;
    }
    if (der_encode_plist(di, error, der, der+size) == NULL) {
        free(der);
        return NULL;
    }
    return CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, der, size, kCFAllocatorMalloc);
}


bool SOSPeerInfoUpdateToV2(SOSPeerInfoRef pi, CFErrorRef *error) {
    bool retval = false;
    CFDataRef v2data = NULL;
    if(!pi) return false;
    
    SOSPeerInfoSetVersionNumber(pi, PEERINFO_CURRENT_VERSION);
    CFMutableDictionaryRef v2Dictionary = CFDictionaryCreateMutable(NULL, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFMutableSetRef views = SOSViewCopyViewSet(kViewSetDefault);
    CFMutableSetRef secproperties = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
    CFDictionaryAddValue(v2Dictionary, sViewsKey, views);
    
    require_action_quiet((v2data = SOSCreateDERFromDictionary(v2Dictionary, error)), out, SOSCreateError(kSOSErrorAllocationFailure, CFSTR("No Memory"), NULL, error));
    CFDictionaryAddValue(pi->description, sV2DictionaryKey, v2data);
    // To use the central serial number setting routine this must be done after the V2 dictionary is installed.
    SOSPeerInfoSetSerialNumber(pi);
    SOSPeerInfoExpandV2Data(pi, error);
    retval = true;
out:
    CFReleaseNull(views);
    CFReleaseNull(v2data);
    CFReleaseNull(v2Dictionary);
    CFReleaseNull(secproperties);
    return retval;
}

void SOSPeerInfoPackV2Data(SOSPeerInfoRef pi) {
    require(SOSPeerInfoV2SanityCheck(pi), errOut);
    require_quiet(pi->v2Dictionary, errOut);
    CFDataRef v2der = SOSCreateDERFromDictionary(pi->v2Dictionary, NULL);
    CFDictionarySetValue(pi->description, sV2DictionaryKey, v2der);
    CFReleaseNull(v2der);
errOut:
    return;
}

bool SOSPeerInfoExpandV2Data(SOSPeerInfoRef pi, CFErrorRef *error) {
    CFDataRef v2data = NULL;
    CFMutableDictionaryRef v2Dictionary = NULL;

    require_action_quiet((v2data = SOSPeerInfoGetV2Data(pi)), out, SOSCreateError(kSOSErrorDecodeFailure, CFSTR("No V2 Data in description"), NULL, error));
    require_action_quiet((v2Dictionary = SOSCreateDictionaryFromDER(v2data, error)), out, SOSCreateError(kSOSErrorDecodeFailure, CFSTR("Can't expand V2 Dictionary"), NULL, error));
    CFReleaseNull(pi->v2Dictionary);
    pi->v2Dictionary = v2Dictionary;
    return true;

out:
    CFReleaseNull(v2Dictionary);
    return false;

}

void SOSPeerInfoV2DictionarySetValue(SOSPeerInfoRef pi, const void *key, const void *value) {
    require_quiet(SOSPeerInfoExpandV2Data(pi, NULL), errOut);
    if (value == NULL)
        CFDictionaryRemoveValue(pi->v2Dictionary, key);
    else
        CFDictionarySetValue(pi->v2Dictionary, key, value);
    SOSPeerInfoPackV2Data(pi);
errOut:
    return;
}

void SOSPeerInfoV2DictionaryRemoveValue(SOSPeerInfoRef pi, const void *key) {
    require_quiet(SOSPeerInfoExpandV2Data(pi, NULL), errOut);
    CFDictionaryRemoveValue(pi->v2Dictionary, key);
    SOSPeerInfoPackV2Data(pi);
errOut:
    return;
}

bool SOSPeerInfoV2DictionaryHasBoolean(SOSPeerInfoRef pi, const void *key) {
    require_quiet(SOSPeerInfoExpandV2Data(pi, NULL), errOut);
    CFTypeRef value = CFDictionaryGetValue(pi->v2Dictionary, key);
    if(asBoolean(value,NULL) != NULL)
        return true;
errOut:
    return false;
}

// Not API to prevent owenership confusion
static CFStringRef SOSPeerInfoV2DictionaryGetAsString(SOSPeerInfoRef pi, const void *key) {
    require_quiet(SOSPeerInfoExpandV2Data(pi, NULL), errOut);
    return asString(CFDictionaryGetValue(pi->v2Dictionary, key), NULL);
errOut:
    return NULL;

}

bool SOSPeerInfoV2DictionaryHasStringValue(SOSPeerInfoRef pi, const void *key, CFStringRef value) {
    return CFEqualSafe(SOSPeerInfoV2DictionaryGetAsString(pi, key), value);
}

bool SOSPeerInfoV2DictionaryHasString(SOSPeerInfoRef pi, const void *key) {
    CFStringRef possibleID = SOSPeerInfoV2DictionaryGetAsString(pi, key);
    return possibleID != NULL && CFStringGetLength(possibleID) > 0;
}

bool SOSPeerInfoV2DictionaryHasSet(SOSPeerInfoRef pi, const void *key) {
    require_quiet(SOSPeerInfoExpandV2Data(pi, NULL), errOut);
    CFTypeRef value = CFDictionaryGetValue(pi->v2Dictionary, key);
    if(asSet(value,NULL) != NULL)
        return true;
errOut:
    return false;
}

bool SOSPeerInfoV2DictionaryHasData(SOSPeerInfoRef pi, const void *key) {
    require_quiet(SOSPeerInfoExpandV2Data(pi, NULL), errOut);
    CFTypeRef value = CFDictionaryGetValue(pi->v2Dictionary, key);
    if(asData(value,NULL) != NULL)
        return true;
errOut:
    return false;
}

CFMutableStringRef SOSPeerInfoV2DictionaryCopyString(SOSPeerInfoRef pi, const void *key) {
    require_quiet(SOSPeerInfoExpandV2Data(pi, NULL), errOut);
    CFStringRef value = asString(CFDictionaryGetValue(pi->v2Dictionary, key), NULL);
    if(value != NULL)
        return CFStringCreateMutableCopy(kCFAllocatorDefault, CFStringGetLength(value), value);
errOut:
    return NULL;
}

static void SOSPeerInfoV2DictionaryWithValue(SOSPeerInfoRef pi, const void *key, void(^operation)(CFTypeRef value)) {
    require_quiet(SOSPeerInfoExpandV2Data(pi, NULL), errOut);
    CFTypeRef value = CFRetainSafe(CFDictionaryGetValue(pi->v2Dictionary, key));
    operation(value);
    CFReleaseNull(value);
errOut:
    return;
}

void SOSPeerInfoV2DictionaryWithSet(SOSPeerInfoRef pi, const void *key, void(^operation)(CFSetRef set)) {
    SOSPeerInfoV2DictionaryWithValue(pi, key, ^(CFTypeRef value) {
        CFSetRef set = asSet(value, NULL);
        if (set) {
            operation(set);
        }
    });
}

CFMutableSetRef SOSPeerInfoV2DictionaryCopySet(SOSPeerInfoRef pi, const void *key) {
    require_quiet(SOSPeerInfoExpandV2Data(pi, NULL), errOut);
    CFSetRef value = asSet(CFDictionaryGetValue(pi->v2Dictionary, key), NULL);
    if(value != NULL)
        return CFSetCreateMutableCopy(kCFAllocatorDefault, CFSetGetCount(value), value);
errOut:
    return NULL;
}

void SOSPeerInfoV2DictionaryForEachSetValue(SOSPeerInfoRef pi, const void *key, void (^action)(const void* value)) {
    CFSetRef value = NULL;
    require_quiet(SOSPeerInfoExpandV2Data(pi, NULL), errOut);
    value = asSet(CFDictionaryGetValue(pi->v2Dictionary, key), NULL);

errOut:
    if (value) {
        CFSetForEach(value, action);
    }
}

bool SOSPeerInfoV2DictionaryHasSetContaining(SOSPeerInfoRef pi, const void *key, const void* member) {
    CFSetRef value = NULL;
    require_quiet(SOSPeerInfoExpandV2Data(pi, NULL), errOut);
    value = asSet(CFDictionaryGetValue(pi->v2Dictionary, key), NULL);
errOut:
    return value && CFSetContainsValue(value, member);
}

CFMutableDataRef SOSPeerInfoV2DictionaryCopyData(SOSPeerInfoRef pi, const void *key) {
    require_quiet(SOSPeerInfoExpandV2Data(pi, NULL), errOut);
    CFDataRef value = asData(CFDictionaryGetValue(pi->v2Dictionary, key), NULL);
    if(value != NULL)
        return CFDataCreateMutableCopy(kCFAllocatorDefault, CFDataGetLength(value), value);
errOut:
    return NULL;
}

CFBooleanRef SOSPeerInfoV2DictionaryCopyBoolean(SOSPeerInfoRef pi, const void *key) {
    require_quiet(SOSPeerInfoExpandV2Data(pi, NULL), errOut);
    CFBooleanRef value = asBoolean(CFDictionaryGetValue(pi->v2Dictionary, key), NULL);
    if(value != NULL)
        return CFRetainSafe(value);
errOut:
    return NULL;
}

CFMutableDictionaryRef SOSPeerInfoV2DictionaryCopyDictionary(SOSPeerInfoRef pi, const void *key) {
    require_quiet(SOSPeerInfoExpandV2Data(pi, NULL), errOut);
    CFDictionaryRef value = asDictionary(CFDictionaryGetValue(pi->v2Dictionary, key), NULL);
    if(value != NULL)
        return CFDictionaryCreateMutableCopy(kCFAllocatorDefault, CFDictionaryGetCount(value), value);
errOut:
    return NULL;
}


SOSPeerInfoRef SOSPeerInfoCopyWithV2DictionaryUpdate(CFAllocatorRef allocator, SOSPeerInfoRef toCopy, CFDictionaryRef newv2dict, SecKeyRef signingKey, CFErrorRef* error) {
    SOSPeerInfoRef retval = SOSPeerInfoCreateCopy(kCFAllocatorDefault, toCopy, error);
    require_quiet(retval, errOut);
    require_action_quiet(SOSPeerInfoExpandV2Data(retval, error), errOut, CFReleaseNull(retval));
    CFDictionaryForEach(newv2dict, ^(const void *key, const void *value) {
        CFDictionarySetValue(retval->v2Dictionary, key, value);
    });
    SOSPeerInfoPackV2Data(retval);
    require_action_quiet(SOSPeerInfoSign(signingKey, retval, error), errOut, CFReleaseNull(retval));
errOut:
    return retval;
}
