Skip to content

Instantly share code, notes, and snippets.

@landonf
Last active August 29, 2015 14:16
Show Gist options
  • Save landonf/bd09de4f4322bb7b699b to your computer and use it in GitHub Desktop.
Save landonf/bd09de4f4322bb7b699b to your computer and use it in GitHub Desktop.
declarative vs imperative codecs -- usage example of our C++ re-implementation of the Scala scodec library.
// Decoding and encoding of the entire file format
/**
* @internal
*
* Create a Codec instance for VDEFileItem structures.
*/
static Codec<VDEFileItem> fileItemCodec() {
using namespace codecs;
const auto fileMagic = ByteVector::Bytes("VPVDE", 5, false);
const auto varLengthBytes = variableSizeBytes(uint32L, identityBytes);
const auto vdeRecordVersion = (
("compat_version" | codecs::uint8 ) &
("feature_version" | codecs::uint8 )
).as<VDERecordVersion>();
const auto vdeSection = (
("offset" | uint64L ) &
("length" | uint64L )
).as<VDESectionRecord>();
const auto vdeHeader = (
("magic" | constant(fileMagic) ) >>
("file_version" | vdeRecordVersion ) &
("vde_encrypted_sect" | vdeSection ) &
("vde_session_sect" | vdeSection )
).as<VDEFileHeader>();
const auto pbkdf2Params = (
("iters" | uint32L ) &
("salt" | varLengthBytes )
).as<VDESessionMasterKeyParams>();
// TODO: Until we generalize Codec::as(), we have to lift the single Codec to a TupledCodec here
const auto hkdfParams = TupledCodec<ByteVector>(
("salt" | varLengthBytes )
).as<VDESessionSubkeyParams>();
const auto vdeSession = (
("version" | vdeRecordVersion ) &
("pbkdf2_params" | pbkdf2Params ) &
("hkdf_params" | hkdfParams ) &
("dpk" | varLengthBytes )
).as<VDESession>();
return (
("vde_header" | vdeHeader ) >>= [vdeSession](const VDEFileHeader &hdr) {
// TODO - offset combinators
auto padding1Length = hdr.encryptedSection().offset() - VDE_FILE_HEADER_SIZE;
auto encryptedLength = hdr.encryptedSection().length();
auto padding2Length = hdr.sessionSection().offset() - (hdr.encryptedSection().offset() + encryptedLength);
auto sessionLength = hdr.sessionSection().length();
return (
("padding_1" | ignore(padding1Length) ) >>
("encrypted_data" | bytes(encryptedLength) ) &
("padding_2" | ignore(padding2Length) ) >>
("vde_session" | fixedSizeBytes(sessionLength, vdeSession) )
);
}
).as<VDEFileItem>();
}
// Decoding (but not encoding) of only the file header
/**
* Read a VDE header at @a position, returning the number of bytes read.
*
* @param position The offset within @a data from which the array will be read.
* @param bytesRead If non-NULL, will be set to the total number of bytes consumed at @a position, including the size of any
* header data.
* @param outError If non-NULL and an error occurs, @a outError will be populated with an NSError instance in the PLCryptoErrorDomain.
*
* @return The VDE header, or nil if if an error occurs.
*
* @par Error Codes
* This method may fail in the following cases:
*
* - If less than the required number of bytes are available, PLCryptoInsufficientData will be returned.
* - If the decoded header's magic value does not match the expected VDE header magic, PLCryptoInvalidData will be returned.
*/
- (VDEHeader *) decodeHeaderAt: (size_t) position bytesRead: (size_t *) bytesRead error: (NSError **) outError {
/* Fetch the header bytes */
vde_header_t header;
if (![self getBytes: &header position: position length: sizeof(header) error: outError])
return nil;
if (bytesRead != NULL)
*bytesRead = sizeof(header);
/* Verify the magic value */
if (memcmp(header.magic, vde_magic, sizeof(vde_magic)) != 0) {
vde_populate_error(outError, VDEInvalidData, NSLocalizedString(@"The provided data does not appear to be a valid VDE item header", nil), nil, nil);
return nil;
}
/* Assemble the result */
VDERecordVersion *version = [[[VDERecordVersion alloc] initWithCompatibilityVersion: header.version.compat
featureVersion: header.version.feature] autorelease];
VDESectionRecord *encryptedSection = [[[VDESectionRecord alloc] initWithOffset: OSSwapLittleToHostInt64(header.encrypted_section.offset)
length: OSSwapLittleToHostInt64(header.encrypted_section.length)] autorelease];
VDESectionRecord *session = [[[VDESectionRecord alloc] initWithOffset: OSSwapLittleToHostInt64(header.session_section.offset)
length: OSSwapLittleToHostInt64(header.session_section.length)] autorelease];
return [[[VDEHeader alloc] initWithVersion: version
encryptedSection: encryptedSection
sessionSection: session] autorelease];;
}
/**
* Read a VDE length-prefixed byte array at @a position, returning the number of bytes read.
*
* @param position The offset within @a data from which the array will be read.
* @param bytesRead If non-NULL, will be set to the total number of bytes consumed at @a position, including the size of any
* header data.
* @param outError If non-NULL and an error occurs, @a outError will be populated with an NSError instance in the PLCryptoErrorDomain.
*
* @return The VDE byte array's data, or nil if if an error occurs.
*
* @par Error Codes
* This method may fail with a PLCryptoInsufficientData error.
*/
- (NSData *) decodeByteArrayAt: (size_t) position bytesRead: (size_t *) bytesRead error: (NSError **) outError {
size_t initialPosition = position;
/* Fetch the byte array */
vde_bytearray_t barray;
if (![self getBytes: &barray position: position length: sizeof(barray) error: outError])
return nil;
barray.length = OSSwapLittleToHostInt32(barray.length);
position += sizeof(vde_bytearray_t);
/* Fetch the actual bytes */
NSMutableData *result = [NSMutableData dataWithLength: barray.length];
if (![self getBytes: result.mutableBytes position: position length: barray.length error: outError])
return nil;
position += barray.length;
/* Provide the total number of bytes read */
if (bytesRead != NULL)
*bytesRead = position - initialPosition;
return result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment