static uint8_t* der_encode_cloud_parameters(SecKeyRef publicKey, CFDataRef paramters, CFErrorRef* error,
                                            const uint8_t* der, uint8_t* der_end)
{
    uint8_t* original_der_end = der_end;
    
    return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, original_der_end, der,
                                       der_encode_public_bytes(publicKey, error, der,
                                                               der_encode_data_or_null(paramters, error, der, der_end)));
}
static uint8_t *der_encode_pbkdf2_params(size_t saltLen, const uint8_t *salt,
                                         unsigned long iterationCount,
                                         unsigned long keyLength,
                                         const uint8_t *der, uint8_t *der_end)
{
    uint8_t* original_der_end = der_end;

    return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, original_der_end, der,
           ccder_encode_raw_octet_string(saltLen, salt, der,
           ccder_encode_uint64(iterationCount, der,
           ccder_encode_uint64(keyLength, der,
           der_encode_SecAsn1Oid(&CSSMOID_PKCS5_HMAC_SHA1, der, der_end)))));
}
static uint8_t* SOSCoderEncodeToDER(SOSCoderRef coder, CFErrorRef* error, const uint8_t* der, uint8_t* der_end) {
    if(!der_end) return NULL;
    uint8_t* result = NULL;
    CFMutableDataRef otr_state = sessSerialized(coder, error);
    
    if(otr_state) {
        result = ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
                                             der_encode_data(otr_state, error, der,
                                             der_encode_bool(coder->waitingForDataPacket, der,
                                             der_encode_optional_data(coder->pendingResponse, error, der, der_end))));
        CFReleaseSafe(otr_state);
    }
    return result;
}
uint8_t* der_encode_RecoveryKeyBag(SOSRecoveryKeyBagRef RecoveryKeyBag, CFErrorRef *error,
                                   const uint8_t *der, uint8_t *der_end) {
    uint8_t *result = NULL;
    if (der_end == NULL) return der_end;
    if(RecoveryKeyBag && RecoveryKeyBag->recoveryKeyBag && RecoveryKeyBag->accountDSID && RecoveryKeyBag->generation) {
        der_end = ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
                    der_encode_string(RecoveryKeyBag->accountDSID, error, der,
                    SOSGenCountEncodeToDER(RecoveryKeyBag->generation, error, der,
                    ccder_encode_uint64(RecoveryKeyBag->rkbVersion, der,
                    der_encode_data(RecoveryKeyBag->recoveryKeyBag, error, der, der_end)))));
        
        require_quiet(der_end == der, errOut);
        result = der_end;
    } else {
        SOSCreateError(kSOSErrorEncodeFailure, CFSTR("Null RecoveryKeyBag"), NULL, error);
    }
    
errOut:
    return result;
}
uint8_t* der_encode_generalizedtime(CFAbsoluteTime at, CFErrorRef *error,
                                    const uint8_t *der, uint8_t *der_end)
{
    return ccder_encode_constructed_tl(CCDER_GENERALIZED_TIME, der_end, der,
           der_encode_generalizedtime_body(at, error, der, der_end));
}
size_t der_sizeof_dictionary(CFDictionaryRef dict, CFErrorRef *error)
{
    struct size_context context = { .success = true, .size = 0, .error = error };

    
    CFDictionaryApplyFunction(dict, add_key_value_size, &context);
    
    if (!context.success)
        return 0;

    return ccder_sizeof(CCDER_CONSTRUCTED_SET, context.size);
}

static uint8_t* der_encode_key_value(CFPropertyListRef key, CFPropertyListRef value, CFErrorRef *error,
                                     const uint8_t* der, uint8_t *der_end)
{
    return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
           der_encode_plist(key, error, der,
           der_encode_plist(value, error, der, der_end)));
}

struct encode_context {
    bool         success;
    CFErrorRef * error;
    CFMutableArrayRef list;
    CFAllocatorRef allocator;
};

static void add_sequence_to_array(const void *key_void, const void *value_void, void *context_void)
{
    struct encode_context *context = (struct encode_context *) context_void;
    if (context->success) {
        CFTypeRef key = (CFTypeRef) key_void;
        CFTypeRef value = (CFTypeRef) value_void;

        size_t der_size = der_sizeof_key_value(key, value, context->error);
        if (der_size == 0) {
            context-> success = false;
        } else {
            CFMutableDataRef encoded_kv = CFDataCreateMutable(context->allocator, der_size);
            CFDataSetLength(encoded_kv, der_size);

            uint8_t* const encode_begin = CFDataGetMutableBytePtr(encoded_kv);
            uint8_t* encode_end = encode_begin + der_size;

            encode_end = der_encode_key_value(key, value, context->error, encode_begin, encode_end);

            if (encode_end != NULL) {
                CFDataDeleteBytes(encoded_kv, CFRangeMake(0, (encode_end - encode_begin)));
                CFArrayAppendValue(context->list, encoded_kv);
            } else {
                context-> success = false;
            }

            CFReleaseNull(encoded_kv);
        }
    }
}

static CFComparisonResult cfdata_compare_contents(const void *val1, const void *val2, void *context __unused)
{
    return CFDataCompare((CFDataRef) val1, (CFDataRef) val2);
}


uint8_t* der_encode_dictionary(CFDictionaryRef dictionary, CFErrorRef *error,
                               const uint8_t *der, uint8_t *der_end)
{
    CFMutableArrayRef elements = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
    
    struct encode_context context = { .success = true, .error = error, .list = elements };
    CFDictionaryApplyFunction(dictionary, add_sequence_to_array, &context);
    
    if (!context.success) {
        CFReleaseNull(elements);
        return NULL;
    }
    
    CFRange allOfThem = CFRangeMake(0, CFArrayGetCount(elements));

    CFArraySortValues(elements, allOfThem, cfdata_compare_contents, NULL);

    uint8_t* original_der_end = der_end;

    for(CFIndex position = CFArrayGetCount(elements); position > 0;) {
        --position;
        CFDataRef data = CFArrayGetValueAtIndex(elements, position);
        der_end = ccder_encode_body(CFDataGetLength(data), CFDataGetBytePtr(data), der, der_end);
    }

    CFReleaseNull(elements);

    return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SET, original_der_end, der, der_end);

}