SOSTransport.c   [plain text]


/*
 * Created by Michael Brouwer on 2/14/12.
 * Copyright 2012 Apple Inc. All Rights Reserved.
 */

/*
 * SOSTransport.c -  Implementation of the secure object syncing transport
 */

#include <SecureObjectSync/SOSTransport.h>
#include <utilities/SecCFError.h>
#include <utilities/SecCFWrappers.h>
#include <dispatch/dispatch.h>
#include <stdlib.h>

static CFStringRef sErrorDomain = CFSTR("com.apple.security.sos.transport.error");

/* SOSDigestVector code. */

#define VECTOR_GROW(vector, count, capacity) \
    do { \
    if ((count) > capacity) { \
        capacity = ((capacity) + 16) * 3 / 2; \
        if (capacity < (count)) \
            capacity = (count); \
        vector = realloc((vector), sizeof(*(vector)) * capacity); \
    } \
} while (0)

static void SOSDigestVectorEnsureCapacity(struct SOSDigestVector *dv, size_t count) {
	VECTOR_GROW(dv->digest, count, dv->capacity);
}

void SOSDigestVectorReplaceAtIndex(struct SOSDigestVector *dv, size_t ix, const uint8_t *digest)
{
	SOSDigestVectorEnsureCapacity(dv, ix + 1);
	memcpy(dv->digest[ix], digest, SOSDigestSize);
	dv->is_sorted = false;
}

static void SOSDigestVectorAppendOrdered(struct SOSDigestVector *dv, const uint8_t *digest)
{
	SOSDigestVectorEnsureCapacity(dv, dv->count + 1);
	memcpy(dv->digest[dv->count++], digest, SOSDigestSize);
}

void SOSDigestVectorAppend(struct SOSDigestVector *dv, const uint8_t *digest)
{
    SOSDigestVectorAppendOrdered(dv, digest);
	dv->is_sorted = false;
}

static int SOSDigestCompare(const void *a, const void *b)
{
	return memcmp(a, b, SOSDigestSize);
}

void SOSDigestVectorSort(struct SOSDigestVector *dv)
{
	qsort(dv->digest, dv->count, sizeof(*dv->digest), SOSDigestCompare);
	dv->is_sorted = true;
}

bool SOSDigestVectorContains(struct SOSDigestVector *dv, const uint8_t *digest)
{
    return SOSDigestVectorIndexOf(dv, digest) != (size_t)-1;
}

size_t SOSDigestVectorIndexOf(struct SOSDigestVector *dv, const uint8_t *digest)
{
	if (!dv->is_sorted)
		SOSDigestVectorSort(dv);
    const void *pos = bsearch(digest, dv->digest, dv->count, sizeof(*dv->digest), SOSDigestCompare);
    return pos ? ((size_t)(pos - (void *)dv->digest)) / SOSDigestSize : ((size_t)-1);
}

void SOSDigestVectorFree(struct SOSDigestVector *dv)
{
	free(dv->digest);
	dv->digest = NULL;
	dv->count = 0;
	dv->capacity = 0;
	dv->is_sorted = false;
}

void SOSDigestVectorApply(struct SOSDigestVector *dv,
                          void *context, SOSDigestVectorApplyFunc func)
{
	if (!dv->is_sorted)
		SOSDigestVectorSort(dv);

	for (size_t ix = 0; ix < dv->count; ++ix) {
		func(context, dv->digest[ix]);
	}
}

static void SOSDigestVectorAppendMultiple(struct SOSDigestVector *dv,
                                          size_t count, const uint8_t *digests) {
    if (count) {
        SOSDigestVectorEnsureCapacity(dv, dv->count + count);
        memcpy(dv->digest[dv->count], digests, count * SOSDigestSize);
        dv->count += count;
    }
}

void SOSDigestVectorDiff(struct SOSDigestVector *dv1, struct SOSDigestVector *dv2,
                         struct SOSDigestVector *dv1_2, struct SOSDigestVector *dv2_1)
{
    /* dv1_2 and dv2_1 should be empty to start. */
    assert(dv1_2->count == 0);
    assert(dv2_1->count == 0);

	if (!dv1->is_sorted)
		SOSDigestVectorSort(dv1);
	if (!dv2->is_sorted)
		SOSDigestVectorSort(dv2);

    size_t i1 = 0, i2 = 0;
    while (i1 < dv1->count && i2 < dv2->count) {
        int delta = SOSDigestCompare(dv1->digest[i1], dv2->digest[i2]);
        if (delta == 0) {
            ++i1, ++i2;
        } else if (delta < 0) {
            SOSDigestVectorAppendOrdered(dv1_2, dv1->digest[i1++]);
        } else {
            SOSDigestVectorAppendOrdered(dv2_1, dv2->digest[i2++]);
        }
    }
    SOSDigestVectorAppendMultiple(dv1_2, dv1->count - i1, dv1->digest[i1]);
    SOSDigestVectorAppendMultiple(dv2_1, dv2->count - i2, dv2->digest[i2]);

    dv1_2->is_sorted = true;
    dv2_1->is_sorted = true;
}

bool SOSDigestVectorPatch(struct SOSDigestVector *base, struct SOSDigestVector *removals,
                          struct SOSDigestVector *additions, struct SOSDigestVector *dv,
                          CFErrorRef *error)
{
    size_t base_ix = 0, removals_ix = 0, additions_ix = 0;
	if (!base->is_sorted)
		SOSDigestVectorSort(base);
	if (!removals->is_sorted)
		SOSDigestVectorSort(removals);
	if (!additions->is_sorted)
		SOSDigestVectorSort(additions);

    assert(dv->count == 0);
    SOSDigestVectorEnsureCapacity(dv, base->count - removals->count + additions->count);
    dv->is_sorted = true;

    while (base_ix < base->count) {
        const uint8_t *d = base->digest[base_ix];
        if (additions_ix < additions->count && SOSDigestCompare(d, additions->digest[additions_ix]) > 0) {
            SOSDigestVectorAppendOrdered(dv, additions->digest[additions_ix++]);
        } else if (removals_ix < removals->count && SOSDigestCompare(d, removals->digest[removals_ix]) == 0) {
            base_ix++;
            removals_ix++;
        } else {
            SOSDigestVectorAppendOrdered(dv, base->digest[base_ix++]);
        }
    }

    if (removals_ix != removals->count) {
        SecCFCreateErrorWithFormat(1, sErrorDomain, NULL, error, NULL, CFSTR("%lu extra removals left"), removals->count - removals_ix);
        goto errOut;
    }

    while (additions_ix < additions->count) {
        if (dv->count > 0 && SOSDigestCompare(dv->digest[dv->count - 1], additions->digest[additions_ix]) >= 0) {
            SecCFCreateErrorWithFormat(1, sErrorDomain, NULL, error, NULL, CFSTR("unordered addition (%lu left)"), additions->count - additions_ix);
            goto errOut;
        }
        SOSDigestVectorAppendOrdered(dv, additions->digest[additions_ix++]);
    }

    return true;
errOut:
    return false;
}



/* SOSManifest implementation. */
struct __OpaqueSOSManifest {
};

SOSManifestRef SOSManifestCreateWithBytes(const uint8_t *bytes, size_t len,
                                          CFErrorRef *error) {
    SOSManifestRef manifest = (SOSManifestRef)CFDataCreate(NULL, bytes, (CFIndex)len);
    if (!manifest)
        SecCFCreateErrorWithFormat(kSOSManifestCreateError, sErrorDomain, NULL, error, NULL, CFSTR("Failed to create manifest"));

    return manifest;
}

SOSManifestRef SOSManifestCreateWithData(CFDataRef data, CFErrorRef *error)
{
    SOSManifestRef manifest = NULL;

    if (data)
        manifest = (SOSManifestRef)CFDataCreateCopy(kCFAllocatorDefault, data);
    else
        manifest = (SOSManifestRef)CFDataCreate(kCFAllocatorDefault, NULL, 0);

    if (!manifest)
        SecCFCreateErrorWithFormat(kSOSManifestCreateError, sErrorDomain, NULL, error, NULL, CFSTR("Failed to create manifest"));

    return manifest;
}

void SOSManifestDispose(SOSManifestRef m) {
    CFRelease(m);
}

size_t SOSManifestGetSize(SOSManifestRef m) {
    return (size_t)CFDataGetLength((CFDataRef)m);
}

size_t SOSManifestGetCount(SOSManifestRef m) {
    return SOSManifestGetSize(m) / SOSDigestSize;
}

const uint8_t *SOSManifestGetBytePtr(SOSManifestRef m) {
    return CFDataGetBytePtr((CFDataRef)m);
}

CFDataRef SOSManifestGetData(SOSManifestRef m) {
    return (CFDataRef)m;
}


bool SOSManifestDiff(SOSManifestRef a, SOSManifestRef b,
                     SOSManifestRef *a_minus_b, SOSManifestRef *b_minus_a,
                     CFErrorRef *error) {
    bool result = true;
    struct SOSDigestVector dva = SOSDigestVectorInit,
                           dvb = SOSDigestVectorInit,
                           dvab = SOSDigestVectorInit,
                           dvba = SOSDigestVectorInit;
    SOSDigestVectorAppendMultiple(&dva, SOSManifestGetCount(a), SOSManifestGetBytePtr(a));
    SOSDigestVectorAppendMultiple(&dvb, SOSManifestGetCount(b), SOSManifestGetBytePtr(b));
    SOSDigestVectorDiff(&dva, &dvb, &dvab, &dvba);
    SOSDigestVectorFree(&dva);
    SOSDigestVectorFree(&dvb);

    if (a_minus_b) {
        *a_minus_b = SOSManifestCreateWithBytes((const uint8_t *)dvab.digest, dvab.count * SOSDigestSize, error);
        if (!*a_minus_b)
            result = false;
    }

    if (b_minus_a) {
        *b_minus_a = SOSManifestCreateWithBytes((const uint8_t *)dvba.digest, dvba.count * SOSDigestSize, error);
        if (!*b_minus_a)
            result = false;
    }

    SOSDigestVectorFree(&dvab);
    SOSDigestVectorFree(&dvba);

    return result;
}

SOSManifestRef SOSManifestCreateWithPatch(SOSManifestRef base,
                                          SOSManifestRef removals,
                                          SOSManifestRef additions,
                                          CFErrorRef *error) {
    struct SOSDigestVector dvbase = SOSDigestVectorInit,
                           dvresult = SOSDigestVectorInit,
                           dvremovals = SOSDigestVectorInit,
                           dvadditions = SOSDigestVectorInit;
    dvbase.is_sorted = dvresult.is_sorted = dvremovals.is_sorted = dvadditions.is_sorted = true;
    SOSDigestVectorAppendMultiple(&dvbase, SOSManifestGetCount(base), SOSManifestGetBytePtr(base));
    SOSDigestVectorAppendMultiple(&dvremovals, SOSManifestGetCount(removals), SOSManifestGetBytePtr(removals));
    SOSDigestVectorAppendMultiple(&dvadditions, SOSManifestGetCount(additions), SOSManifestGetBytePtr(additions));
    SOSManifestRef result;
    if (SOSDigestVectorPatch(&dvbase, &dvremovals, &dvadditions, &dvresult, error)) {
        result = SOSManifestCreateWithBytes((const uint8_t *)dvresult.digest, dvresult.count * SOSDigestSize, error);
    } else {
        result = NULL;
    }
    SOSDigestVectorFree(&dvbase);
    SOSDigestVectorFree(&dvresult);
    SOSDigestVectorFree(&dvremovals);
    SOSDigestVectorFree(&dvadditions);
    return result;
}

void SOSManifestForEach(SOSManifestRef m, void(^block)(CFDataRef e)) {
    CFDataRef e;
    const uint8_t *p, *q;
    for (p = SOSManifestGetBytePtr(m), q = p + SOSManifestGetSize(m);
         p + SOSDigestSize <= q; p += SOSDigestSize) {
        e = CFDataCreateWithBytesNoCopy(0, p, SOSDigestSize, kCFAllocatorNull);
        if (e) {
            block(e);
            CFRelease(e);
        }
    }
}

CFStringRef SOSManifestCopyDescription(SOSManifestRef m) {
    CFMutableStringRef desc = CFStringCreateMutable(0, 0);
    CFStringAppend(desc, CFSTR("<Manifest"));
    SOSManifestForEach(m, ^(CFDataRef e) {
        CFStringAppend(desc, CFSTR(" "));
        const uint8_t *d = CFDataGetBytePtr(e);
        CFStringAppendFormat(desc, 0, CFSTR("%02X%02X%02X%02X"), d[0], d[1], d[2], d[3]);
    });
    CFStringAppend(desc, CFSTR(">"));

    return desc;
}

#if 0
SOSObjectRef SOSManifestGetObject(SOSManifestRef m, SOSObjectID k) {
    return NULL;
}

void SOSManifestPutObject(SOSManifestRef m, SOSObjectID k, SOSObjectRef v) {

}


SOSManifestRef SOSManifestCreateSparse(void *get_ctx, SOSManifestGetF get_f) {
    return NULL;
}
#endif