diff options
author | Michael Brown <mcb30@ipxe.org> | 2015-04-13 12:26:05 +0100 |
---|---|---|
committer | Michael Brown <mcb30@ipxe.org> | 2015-04-13 12:26:05 +0100 |
commit | d9166bbcaec90f77abb0b0fc29ad1934a606f3e0 (patch) | |
tree | aa28fca696fa110aadc666ca790ac171cd3b69da | |
parent | 755d2b8f6be681a2e620534b237471b75f28ed8c (diff) | |
download | ipxe-d9166bbcaec90f77abb0b0fc29ad1934a606f3e0.tar.gz |
[peerdist] Add support for decoding PeerDist Content Information
Signed-off-by: Michael Brown <mcb30@ipxe.org>
-rw-r--r-- | src/include/ipxe/errfile.h | 1 | ||||
-rw-r--r-- | src/include/ipxe/pccrc.h | 445 | ||||
-rw-r--r-- | src/net/pccrc.c | 803 | ||||
-rw-r--r-- | src/tests/pccrc_test.c | 507 | ||||
-rw-r--r-- | src/tests/tests.c | 1 |
5 files changed, 1757 insertions, 0 deletions
diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index f0e5871b..e43eec26 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -237,6 +237,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_dhcpv6 ( ERRFILE_NET | 0x003b0000 ) #define ERRFILE_nfs_uri ( ERRFILE_NET | 0x003c0000 ) #define ERRFILE_rndis ( ERRFILE_NET | 0x003d0000 ) +#define ERRFILE_pccrc ( ERRFILE_NET | 0x003e0000 ) #define ERRFILE_image ( ERRFILE_IMAGE | 0x00000000 ) #define ERRFILE_elf ( ERRFILE_IMAGE | 0x00010000 ) diff --git a/src/include/ipxe/pccrc.h b/src/include/ipxe/pccrc.h new file mode 100644 index 00000000..5506becb --- /dev/null +++ b/src/include/ipxe/pccrc.h @@ -0,0 +1,445 @@ +#ifndef _IPXE_PCCRC_H +#define _IPXE_PCCRC_H + +/** @file + * + * Peer Content Caching and Retrieval: Content Identification [MS-PCCRC] + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdint.h> +#include <byteswap.h> +#include <ipxe/uaccess.h> +#include <ipxe/crypto.h> + +/****************************************************************************** + * + * Content Information versioning + * + ****************************************************************************** + * + * Note that version 1 data structures are little-endian, but version + * 2 data structures are big-endian. + */ + +/** Content Information version number */ +union peerdist_info_version { + /** Raw version number + * + * Always little-endian, regardless of whether the + * encompassing structure is version 1 (little-endian) or + * version 2 (big-endian). + */ + uint16_t raw; + /** Major:minor version number */ + struct { + /** Minor version number */ + uint8_t minor; + /** Major version number */ + uint8_t major; + } __attribute__ (( packed )); +} __attribute__ (( packed )); + +/** Content Information version 1 */ +#define PEERDIST_INFO_V1 0x0100 + +/** Content Information version 2 */ +#define PEERDIST_INFO_V2 0x0200 + +/****************************************************************************** + * + * Content Information version 1 + * + ****************************************************************************** + */ + +/** Content Information version 1 data structure header + * + * All fields are little-endian. + */ +struct peerdist_info_v1 { + /** Version number */ + union peerdist_info_version version; + /** Hash algorithm + * + * This is a @c PEERDIST_INFO_V1_HASH_XXX constant. + */ + uint32_t hash; + /** Length to skip in first segment + * + * Length at the start of the first segment which is not + * included within the content range. + */ + uint32_t first; + /** Length to read in last segment, or zero + * + * Length within the last segment which is included within the + * content range. A zero value indicates that the whole of + * the last segment is included within the content range. + */ + uint32_t last; + /** Number of segments within the content information */ + uint32_t segments; + /* Followed by a variable-length array of segment descriptions + * and a list of variable-length block descriptions: + * + * peerdist_info_v1_segment_t(digestsize) segment[segments]; + * peerdist_info_v1_block_t(digestsize, block0.blocks) block0; + * peerdist_info_v1_block_t(digestsize, block1.blocks) block1; + * ... + * peerdist_info_v1_block_t(digestsize, blockN.blocks) blockN; + */ +} __attribute__ (( packed )); + +/** SHA-256 hash algorithm */ +#define PEERDIST_INFO_V1_HASH_SHA256 0x0000800cUL + +/** SHA-384 hash algorithm */ +#define PEERDIST_INFO_V1_HASH_SHA384 0x0000800dUL + +/** SHA-512 hash algorithm */ +#define PEERDIST_INFO_V1_HASH_SHA512 0x0000800eUL + +/** Content Information version 1 segment description header + * + * All fields are little-endian. + */ +struct peerdist_info_v1_segment { + /** Offset of this segment within the content */ + uint64_t offset; + /** Length of this segment + * + * Should always be 32MB, except for the last segment within + * the content. + */ + uint32_t len; + /** Block size for this segment + * + * Should always be 64kB. Note that the last block within the + * last segment may actually be less than 64kB. + */ + uint32_t blksize; + /* Followed by two variable-length hashes: + * + * uint8_t hash[digestsize]; + * uint8_t secret[digestsize]; + * + * where digestsize is the digest size for the selected hash + * algorithm. + * + * Note that the hash is taken over (the hashes of all blocks + * within) the entire segment, even if the blocks do not + * intersect the content range (and so do not appear within + * the block list). It therefore functions only as a segment + * identifier; it cannot be used to verify the content of the + * segment (since we may not download all blocks within the + * segment). + */ +} __attribute__ (( packed )); + +/** Content Information version 1 segment description + * + * @v digestsize Digest size + */ +#define peerdist_info_v1_segment_t( digestsize ) \ + struct { \ + struct peerdist_info_v1_segment segment; \ + uint8_t hash[digestsize]; \ + uint8_t secret[digestsize]; \ + } __attribute__ (( packed )) + +/** Content Information version 1 block description header + * + * All fields are little-endian. + */ +struct peerdist_info_v1_block { + /** Number of blocks within the block description + * + * This is the number of blocks within the segment which + * overlap the content range. It may therefore be less than + * the number of blocks within the segment. + */ + uint32_t blocks; + /* Followed by an array of variable-length hashes: + * + * uint8_t hash[blocks][digestsize]; + * + * where digestsize is the digest size for the selected hash + * algorithm. + */ + } __attribute__ (( packed )); + +/** Content Information version 1 block description + * + * @v digestsize Digest size + * @v blocks Number of blocks + */ +#define peerdist_info_v1_block_t( digestsize, blocks ) \ + struct { \ + struct peerdist_info_v1_block block; \ + uint8_t hash[blocks][digestsize]; \ + } __attribute__ (( packed )) + +/****************************************************************************** + * + * Content Information version 2 + * + ****************************************************************************** + */ + +/** Content Information version 2 data structure header + * + * All fields are big-endian. + */ +struct peerdist_info_v2 { + /** Version number */ + union peerdist_info_version version; + /** Hash algorithm + * + * This is a @c PEERDIST_INFO_V2_HASH_XXX constant. + */ + uint8_t hash; + /** Offset of the first segment within the content */ + uint64_t offset; + /** Index of the first segment within the content */ + uint64_t index; + /** Length to skip in first segment + * + * Length at the start of the first segment which is not + * included within the content range. + */ + uint32_t first; + /** Length of content range, or zero + * + * Length of the content range. A zero indicates that + * everything up to the end of the last segment is included in + * the content range. + */ + uint64_t len; + /* Followed by a list of chunk descriptions */ +} __attribute__ (( packed )); + +/** SHA-512 hash algorithm with output truncated to first 256 bits */ +#define PEERDIST_INFO_V2_HASH_SHA512_TRUNC 0x04 + +/** Content Information version 2 chunk description header + * + * All fields are big-endian. + */ +struct peerdist_info_v2_chunk { + /** Chunk type */ + uint8_t type; + /** Chunk data length */ + uint32_t len; + /* Followed by an array of segment descriptions: + * + * peerdist_info_v2_segment_t(digestsize) segment[segments] + * + * where digestsize is the digest size for the selected hash + * algorithm, and segments is equal to @c len divided by the + * size of each segment array entry. + */ +} __attribute__ (( packed )); + +/** Content Information version 2 chunk description + * + * @v digestsize Digest size + */ +#define peerdist_info_v2_chunk_t( digestsize ) \ + struct { \ + struct peerdist_info_v2_chunk chunk; \ + peerdist_info_v2_segment_t ( digestsize ) segment[0]; \ + } __attribute__ (( packed )) + +/** Chunk type */ +#define PEERDIST_INFO_V2_CHUNK_TYPE 0x00 + +/** Content Information version 2 segment description header + * + * All fields are big-endian. + */ +struct peerdist_info_v2_segment { + /** Segment length */ + uint32_t len; + /* Followed by two variable-length hashes: + * + * uint8_t hash[digestsize]; + * uint8_t secret[digestsize]; + * + * where digestsize is the digest size for the selected hash + * algorithm. + */ +} __attribute__ (( packed )); + +/** Content Information version 2 segment description + * + * @v digestsize Digest size + */ +#define peerdist_info_v2_segment_t( digestsize ) \ + struct { \ + struct peerdist_info_v2_segment segment; \ + uint8_t hash[digestsize]; \ + uint8_t secret[digestsize]; \ + } __attribute__ (( packed )) + +/****************************************************************************** + * + * Content Information + * + ****************************************************************************** + */ + +/** Maximum digest size for any supported algorithm + * + * The largest digest size that we support is for SHA-512 at 64 bytes + */ +#define PEERDIST_DIGEST_MAX_SIZE 64 + +/** Raw content information */ +struct peerdist_raw { + /** Data buffer */ + userptr_t data; + /** Length of data buffer */ + size_t len; +}; + +/** A content range */ +struct peerdist_range { + /** Start offset */ + size_t start; + /** End offset */ + size_t end; +}; + +/** Content information */ +struct peerdist_info { + /** Raw content information */ + struct peerdist_raw raw; + + /** Content information operations */ + struct peerdist_info_operations *op; + /** Digest algorithm */ + struct digest_algorithm *digest; + /** Digest size + * + * Note that this may be shorter than the digest size of the + * digest algorithm. The truncation does not always take + * place as soon as a digest is calculated. For example, + * version 2 content information uses SHA-512 with a truncated + * digest size of 32 (256 bits), but the segment identifier + * ("HoHoDk") is calculated by using HMAC with the full + * SHA-512 digest and then truncating the HMAC output, rather + * than by simply using HMAC with the truncated SHA-512 + * digest. This is, of course, totally undocumented. + */ + size_t digestsize; + /** Content range */ + struct peerdist_range range; + /** Trimmed content range */ + struct peerdist_range trim; + /** Number of segments within the content information */ + unsigned int segments; +}; + +/** A content information segment */ +struct peerdist_info_segment { + /** Content information */ + const struct peerdist_info *info; + /** Segment index */ + unsigned int index; + + /** Content range + * + * Note that this range may exceed the overall content range. + */ + struct peerdist_range range; + /** Number of blocks within this segment */ + unsigned int blocks; + /** Block size */ + size_t blksize; + /** Segment hash of data + * + * This is MS-PCCRC's "HoD". + */ + uint8_t hash[PEERDIST_DIGEST_MAX_SIZE]; + /** Segment secret + * + * This is MS-PCCRC's "Ke = Kp". + */ + uint8_t secret[PEERDIST_DIGEST_MAX_SIZE]; + /** Segment identifier + * + * This is MS-PCCRC's "HoHoDk". + */ + uint8_t id[PEERDIST_DIGEST_MAX_SIZE]; +}; + +/** Magic string constant used to calculate segment identifier + * + * Note that the MS-PCCRC specification states that this constant is + * + * "the null-terminated ASCII string constant "MS_P2P_CACHING"; + * string literals are all ASCII strings with NULL terminators + * unless otherwise noted." + * + * The specification lies. This constant is a UTF-16LE string, not an + * ASCII string. The terminating wNUL *is* included within the + * constant. + */ +#define PEERDIST_SEGMENT_ID_MAGIC L"MS_P2P_CACHING" + +/** A content information block */ +struct peerdist_info_block { + /** Content information segment */ + const struct peerdist_info_segment *segment; + /** Block index */ + unsigned int index; + + /** Content range + * + * Note that this range may exceed the overall content range. + */ + struct peerdist_range range; + /** Block hash */ + uint8_t hash[PEERDIST_DIGEST_MAX_SIZE]; +}; + +/** Content information operations */ +struct peerdist_info_operations { + /** + * Populate content information + * + * @v info Content information to fill in + * @ret rc Return status code + */ + int ( * info ) ( struct peerdist_info *info ); + /** + * Populate content information segment + * + * @v segment Content information segment to fill in + * @ret rc Return status code + */ + int ( * segment ) ( struct peerdist_info_segment *segment ); + /** + * Populate content information block + * + * @v block Content information block to fill in + * @ret rc Return status code + */ + int ( * block ) ( struct peerdist_info_block *block ); +}; + +extern struct digest_algorithm sha512_trunc_algorithm; + +extern int peerdist_info ( userptr_t data, size_t len, + struct peerdist_info *info ); +extern int peerdist_info_segment ( const struct peerdist_info *info, + struct peerdist_info_segment *segment, + unsigned int index ); +extern int peerdist_info_block ( const struct peerdist_info_segment *segment, + struct peerdist_info_block *block, + unsigned int index ); + +#endif /* _IPXE_PCCRC_H */ diff --git a/src/net/pccrc.c b/src/net/pccrc.c new file mode 100644 index 00000000..10342207 --- /dev/null +++ b/src/net/pccrc.c @@ -0,0 +1,803 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <errno.h> +#include <assert.h> +#include <ipxe/uaccess.h> +#include <ipxe/sha256.h> +#include <ipxe/sha512.h> +#include <ipxe/hmac.h> +#include <ipxe/base16.h> +#include <ipxe/pccrc.h> + +/** @file + * + * Peer Content Caching and Retrieval: Content Identification [MS-PCCRC] + * + */ + +/****************************************************************************** + * + * Utility functions + * + ****************************************************************************** + */ + +/** + * Transcribe hash value (for debugging) + * + * @v info Content information + * @v hash Hash value + * @ret string Hash value string + */ +static inline const char * +peerdist_info_hash_ntoa ( const struct peerdist_info *info, const void *hash ) { + static char buf[ ( 2 * PEERDIST_DIGEST_MAX_SIZE ) + 1 /* NUL */ ]; + size_t digestsize = info->digestsize; + + /* Sanity check */ + assert ( info != NULL ); + assert ( digestsize != 0 ); + assert ( base16_encoded_len ( digestsize ) < sizeof ( buf ) ); + + /* Transcribe hash value */ + base16_encode ( hash, digestsize, buf ); + return buf; +} + +/** + * Get raw data + * + * @v info Content information + * @v data Data buffer + * @v offset Starting offset + * @v len Length + * @ret rc Return status code + */ +static int peerdist_info_get ( const struct peerdist_info *info, void *data, + size_t offset, size_t len ) { + + /* Sanity check */ + if ( ( offset > info->raw.len ) || + ( len > ( info->raw.len - offset ) ) ) { + DBGC ( info, "PCCRC %p data underrun at [%zx,%zx) of %zx\n", + info, offset, ( offset + len ), info->raw.len ); + return -ERANGE; + } + + /* Copy data */ + copy_from_user ( data, info->raw.data, offset, len ); + + return 0; +} + +/** + * Populate segment hashes + * + * @v segment Content information segment to fill in + * @v hash Segment hash of data + * @v secret Segment secret + */ +static void peerdist_info_segment_hash ( struct peerdist_info_segment *segment, + const void *hash, const void *secret ){ + const struct peerdist_info *info = segment->info; + struct digest_algorithm *digest = info->digest; + uint8_t ctx[digest->ctxsize]; + size_t digestsize = info->digestsize; + size_t secretsize = digestsize; + static const uint16_t magic[] = PEERDIST_SEGMENT_ID_MAGIC; + + /* Sanity check */ + assert ( digestsize <= sizeof ( segment->hash ) ); + assert ( digestsize <= sizeof ( segment->secret ) ); + assert ( digestsize <= sizeof ( segment->id ) ); + + /* Get segment hash of data */ + memcpy ( segment->hash, hash, digestsize ); + + /* Get segment secret */ + memcpy ( segment->secret, secret, digestsize ); + + /* Calculate segment identifier */ + hmac_init ( digest, ctx, segment->secret, &secretsize ); + assert ( secretsize == digestsize ); + hmac_update ( digest, ctx, segment->hash, digestsize ); + hmac_update ( digest, ctx, magic, sizeof ( magic ) ); + hmac_final ( digest, ctx, segment->secret, &secretsize, segment->id ); + assert ( secretsize == digestsize ); +} + +/****************************************************************************** + * + * Content Information version 1 + * + ****************************************************************************** + */ + +/** + * Get number of blocks within a block description + * + * @v info Content information + * @v offset Block description offset + * @ret blocks Number of blocks, or negative error + */ +static int peerdist_info_v1_blocks ( const struct peerdist_info *info, + size_t offset ) { + struct peerdist_info_v1_block raw; + unsigned int blocks; + int rc; + + /* Get block description header */ + if ( ( rc = peerdist_info_get ( info, &raw, offset, + sizeof ( raw ) ) ) != 0 ) + return rc; + + /* Calculate number of blocks */ + blocks = le32_to_cpu ( raw.blocks ); + + return blocks; +} + +/** + * Locate block description + * + * @v info Content information + * @v index Segment index + * @ret offset Block description offset, or negative error + */ +static ssize_t peerdist_info_v1_block_offset ( const struct peerdist_info *info, + unsigned int index ) { + size_t digestsize = info->digestsize; + unsigned int i; + size_t offset; + int blocks; + int rc; + + /* Sanity check */ + assert ( index < info->segments ); + + /* Calculate offset of first block description */ + offset = ( sizeof ( struct peerdist_info_v1 ) + + ( info->segments * + sizeof ( peerdist_info_v1_segment_t ( digestsize ) ) ) ); + + /* Iterate over block descriptions until we find this segment */ + for ( i = 0 ; i < index ; i++ ) { + + /* Get number of blocks */ + blocks = peerdist_info_v1_blocks ( info, offset ); + if ( blocks < 0 ) { + rc = blocks; + DBGC ( info, "PCCRC %p segment %d could not get number " + "of blocks: %s\n", info, i, strerror ( rc ) ); + return rc; + } + + /* Move to next block description */ + offset += sizeof ( peerdist_info_v1_block_t ( digestsize, + blocks ) ); + } + + return offset; +} + +/** + * Populate content information + * + * @v info Content information to fill in + * @ret rc Return status code + */ +static int peerdist_info_v1 ( struct peerdist_info *info ) { + struct peerdist_info_v1 raw; + struct peerdist_info_segment first; + struct peerdist_info_segment last; + size_t first_skip; + size_t last_skip; + size_t last_read; + int rc; + + /* Get raw header */ + if ( ( rc = peerdist_info_get ( info, &raw, 0, sizeof ( raw ) ) ) != 0){ + DBGC ( info, "PCCRC %p could not get V1 content information: " + "%s\n", info, strerror ( rc ) ); + return rc; + } + assert ( raw.version.raw == cpu_to_le16 ( PEERDIST_INFO_V1 ) ); + + /* Determine hash algorithm */ + switch ( raw.hash ) { + case cpu_to_le32 ( PEERDIST_INFO_V1_HASH_SHA256 ) : + info->digest = &sha256_algorithm; + break; + case cpu_to_le32 ( PEERDIST_INFO_V1_HASH_SHA384 ) : + info->digest = &sha384_algorithm; + break; + case cpu_to_le32 ( PEERDIST_INFO_V1_HASH_SHA512 ) : + info->digest = &sha512_algorithm; + break; + default: + DBGC ( info, "PCCRC %p unsupported hash algorithm %#08x\n", + info, le32_to_cpu ( raw.hash ) ); + return -ENOTSUP; + } + info->digestsize = info->digest->digestsize; + assert ( info->digest != NULL ); + DBGC2 ( info, "PCCRC %p using %s[%zd]\n", + info, info->digest->name, ( info->digestsize * 8 ) ); + + /* Calculate number of segments */ + info->segments = le32_to_cpu ( raw.segments ); + + /* Get first segment */ + if ( ( rc = peerdist_info_segment ( info, &first, 0 ) ) != 0 ) + return rc; + + /* Calculate range start offset */ + info->range.start = first.range.start; + + /* Calculate trimmed range start offset */ + first_skip = le32_to_cpu ( raw.first ); + info->trim.start = ( first.range.start + first_skip ); + + /* Get last segment */ + if ( ( rc = peerdist_info_segment ( info, &last, + ( info->segments - 1 ) ) ) != 0 ) + return rc; + + /* Calculate range end offset */ + info->range.end = last.range.end; + + /* Calculate trimmed range end offset */ + if ( raw.last ) { + /* Explicit length to include from last segment is given */ + last_read = le32_to_cpu ( raw.last ); + last_skip = ( last.index ? 0 : first_skip ); + info->trim.end = ( last.range.start + last_skip + last_read ); + } else { + /* No explicit length given: range extends to end of segment */ + info->trim.end = last.range.end; + } + + return 0; +} + +/** + * Populate content information segment + * + * @v segment Content information segment to fill in + * @ret rc Return status code + */ +static int peerdist_info_v1_segment ( struct peerdist_info_segment *segment ) { + const struct peerdist_info *info = segment->info; + size_t digestsize = info->digestsize; + peerdist_info_v1_segment_t ( digestsize ) raw; + ssize_t raw_offset; + int blocks; + int rc; + + /* Sanity checks */ + assert ( segment->index < info->segments ); + + /* Get raw description */ + raw_offset = ( sizeof ( struct peerdist_info_v1 ) + + ( segment->index * sizeof ( raw ) ) ); + if ( ( rc = peerdist_info_get ( info, &raw, raw_offset, + sizeof ( raw ) ) ) != 0 ) { + DBGC ( info, "PCCRC %p segment %d could not get segment " + "description: %s\n", info, segment->index, + strerror ( rc ) ); + return rc; + } + + /* Calculate start offset of this segment */ + segment->range.start = le64_to_cpu ( raw.segment.offset ); + + /* Calculate end offset of this segment */ + segment->range.end = ( segment->range.start + + le32_to_cpu ( raw.segment.len ) ); + + /* Calculate block size of this segment */ + segment->blksize = le32_to_cpu ( raw.segment.blksize ); + + /* Locate block description for this segment */ + raw_offset = peerdist_info_v1_block_offset ( info, segment->index ); + if ( raw_offset < 0 ) { + rc = raw_offset; + return rc; + } + + /* Get number of blocks */ + blocks = peerdist_info_v1_blocks ( info, raw_offset ); + if ( blocks < 0 ) { + rc = blocks; + DBGC ( info, "PCCRC %p segment %d could not get number of " + "blocks: %s\n", info, segment->index, strerror ( rc ) ); + return rc; + } + segment->blocks = blocks; + + /* Calculate segment hashes */ + peerdist_info_segment_hash ( segment, raw.hash, raw.secret ); + + return 0; +} + +/** + * Populate content information block + * + * @v block Content information block to fill in + * @ret rc Return status code + */ +static int peerdist_info_v1_block ( struct peerdist_info_block *block ) { + const struct peerdist_info_segment *segment = block->segment; + const struct peerdist_info *info = segment->info; + size_t digestsize = info->digestsize; + peerdist_info_v1_block_t ( digestsize, segment->blocks ) raw; + ssize_t raw_offset; + int rc; + + /* Sanity checks */ + assert ( block->index < segment->blocks ); + + /* Calculate start offset of this block */ + block->range.start = ( segment->range.start + + ( block->index * segment->blksize ) ); + + /* Calculate end offset of this block */ + block->range.end = ( block->range.start + segment->blksize ); + if ( block->range.end > segment->range.end ) + block->range.end = segment->range.end; + + /* Locate block description */ + raw_offset = peerdist_info_v1_block_offset ( info, segment->index ); + if ( raw_offset < 0 ) { + rc = raw_offset; + return rc; + } + + /* Get block hash */ + raw_offset += offsetof ( typeof ( raw ), hash[block->index] ); + if ( ( rc = peerdist_info_get ( info, block->hash, raw_offset, + digestsize ) ) != 0 ) { + DBGC ( info, "PCCRC %p segment %d block %d could not get " + "hash: %s\n", info, segment->index, block->index, + strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** Content information version 1 operations */ +static struct peerdist_info_operations peerdist_info_v1_operations = { + .info = peerdist_info_v1, + .segment = peerdist_info_v1_segment, + .block = peerdist_info_v1_block, +}; + +/****************************************************************************** + * + * Content Information version 2 + * + ****************************************************************************** + */ + +/** A segment cursor */ +struct peerdist_info_v2_cursor { + /** Raw data offset */ + size_t offset; + /** Number of segments remaining within this chunk */ + unsigned int remaining; + /** Accumulated segment length */ + size_t len; +}; + +/** + * Initialise segment cursor + * + * @v cursor Segment cursor + */ +static inline void +peerdist_info_v2_cursor_init ( struct peerdist_info_v2_cursor *cursor ) { + + /* Initialise cursor */ + cursor->offset = ( sizeof ( struct peerdist_info_v2 ) + + sizeof ( struct peerdist_info_v2_chunk ) ); + cursor->remaining = 0; + cursor->len = 0; +} + +/** + * Update segment cursor to next segment description + * + * @v info Content information + * @v offset Current offset + * @v remaining Number of segments remaining within this chunk + * @ret rc Return status code + */ +static int +peerdist_info_v2_cursor_next ( const struct peerdist_info *info, + struct peerdist_info_v2_cursor *cursor ) { + size_t digestsize = info->digestsize; + peerdist_info_v2_segment_t ( digestsize ) raw; + struct peerdist_info_v2_chunk chunk; + int rc; + + /* Get chunk description if applicable */ + if ( ! cursor->remaining ) { + + /* Get chunk description */ + if ( ( rc = peerdist_info_get ( info, &chunk, + ( cursor->offset - + sizeof ( chunk ) ), + sizeof ( chunk ) ) ) != 0 ) + return rc; + + /* Update number of segments remaining */ + cursor->remaining = ( be32_to_cpu ( chunk.len ) / + sizeof ( raw ) ); + } + + /* Get segment description header */ + if ( ( rc = peerdist_info_get ( info, &raw.segment, cursor->offset, + sizeof ( raw.segment ) ) ) != 0 ) + return rc; + + /* Update cursor */ + cursor->offset += sizeof ( raw ); + cursor->remaining--; + if ( ! cursor->remaining ) + cursor->offset += sizeof ( chunk ); + cursor->len += be32_to_cpu ( raw.segment.len ); + + return 0; +} + +/** + * Get number of segments and total length + * + * @v info Content information + * @v len Length to fill in + * @ret rc Number of segments, or negative error + */ +static int peerdist_info_v2_segments ( const struct peerdist_info *info, + size_t *len ) { + struct peerdist_info_v2_cursor cursor; + unsigned int segments; + int rc; + + /* Iterate over all segments */ + for ( peerdist_info_v2_cursor_init ( &cursor ), segments = 0 ; + cursor.offset < info->raw.len ; segments++ ) { + + /* Update segment cursor */ + if ( ( rc = peerdist_info_v2_cursor_next ( info, + &cursor ) ) != 0 ) { + DBGC ( info, "PCCRC %p segment %d could not update " + "segment cursor: %s\n", + info, segments, strerror ( rc ) ); + return rc; + } + } + + /* Record accumulated length */ + *len = cursor.len; + + return segments; +} + +/** + * Populate content information + * + * @v info Content information to fill in + * @ret rc Return status code + */ +static int peerdist_info_v2 ( struct peerdist_info *info ) { + struct peerdist_info_v2 raw; + size_t len = 0; + int segments; + int rc; + + /* Get raw header */ + if ( ( rc = peerdist_info_get ( info, &raw, 0, sizeof ( raw ) ) ) != 0){ + DBGC ( info, "PCCRC %p could not get V2 content information: " + "%s\n", info, strerror ( rc ) ); + return rc; + } + assert ( raw.version.raw == cpu_to_le16 ( PEERDIST_INFO_V2 ) ); + + /* Determine hash algorithm */ + switch ( raw.hash ) { + case PEERDIST_INFO_V2_HASH_SHA512_TRUNC : + info->digest = &sha512_algorithm; + info->digestsize = ( 256 / 8 ); + break; + default: + DBGC ( info, "PCCRC %p unsupported hash algorithm %#02x\n", + info, raw.hash ); + return -ENOTSUP; + } + assert ( info->digest != NULL ); + DBGC2 ( info, "PCCRC %p using %s[%zd]\n", + info, info->digest->name, ( info->digestsize * 8 ) ); + + /* Calculate number of segments and total length */ + segments = peerdist_info_v2_segments ( info, &len ); + if ( segments < 0 ) { + rc = segments; + DBGC ( info, "PCCRC %p could not get segment count and length: " + "%s\n", info, strerror ( rc ) ); + return rc; + } + info->segments = segments; + + /* Calculate range start offset */ + info->range.start = be64_to_cpu ( raw.offset ); + + /* Calculate trimmed range start offset */ + info->trim.start = ( info->range.start + be32_to_cpu ( raw.first ) ); + + /* Calculate range end offset */ + info->range.end = ( info->range.start + len ); + + /* Calculate trimmed range end offset */ + info->trim.end = ( raw.len ? be64_to_cpu ( raw.len ) : + info->range.end ); + + return 0; +} + +/** + * Populate content information segment + * + * @v segment Content information segment to fill in + * @ret rc Return status code + */ +static int peerdist_info_v2_segment ( struct peerdist_info_segment *segment ) { + const struct peerdist_info *info = segment->info; + size_t digestsize = info->digestsize; + peerdist_info_v2_segment_t ( digestsize ) raw; + struct peerdist_info_v2_cursor cursor; + unsigned int index; + size_t len; + int rc; + + /* Sanity checks */ + assert ( segment->index < info->segments ); + + /* Iterate over all segments before the target segment */ + for ( peerdist_info_v2_cursor_init ( &cursor ), index = 0 ; + index < segment->index ; index++ ) { + + /* Update segment cursor */ + if ( ( rc = peerdist_info_v2_cursor_next ( info, + &cursor ) ) != 0 ) { + DBGC ( info, "PCCRC %p segment %d could not update " + "segment cursor: %s\n", + info, index, strerror ( rc ) ); + return rc; + } + } + + /* Get raw description */ + if ( ( rc = peerdist_info_get ( info, &raw, cursor.offset, + sizeof ( raw ) ) ) != 0 ) { + DBGC ( info, "PCCRC %p segment %d could not get segment " + "description: %s\n", + info, segment->index, strerror ( rc ) ); + return rc; + } + + /* Calculate start offset of this segment */ + segment->range.start = ( info->range.start + cursor.len ); + + /* Calculate end offset of this segment */ + len = be32_to_cpu ( raw.segment.len ); + segment->range.end = ( segment->range.start + len ); + + /* Model as a segment containing a single block */ + segment->blocks = 1; + segment->blksize = len; + + /* Calculate segment hashes */ + peerdist_info_segment_hash ( segment, raw.hash, raw.secret ); + + return 0; +} + +/** + * Populate content information block + * + * @v block Content information block to fill in + * @ret rc Return status code + */ +static int peerdist_info_v2_block ( struct peerdist_info_block *block ) { + const struct peerdist_info_segment *segment = block->segment; + const struct peerdist_info *info = segment->info; + size_t digestsize = info->digestsize; + + /* Sanity checks */ + assert ( block->index < segment->blocks ); + + /* Model as a block covering the whole segment */ + memcpy ( &block->range, &segment->range, sizeof ( block->range ) ); + memcpy ( block->hash, segment->hash, digestsize ); + + return 0; +} + +/** Content information version 2 operations */ +static struct peerdist_info_operations peerdist_info_v2_operations = { + .block = peerdist_info_v2_block, + .segment = peerdist_info_v2_segment, + .info = peerdist_info_v2, +}; + +/****************************************************************************** + * + * Content Information + * + ****************************************************************************** + */ + +/** + * Populate content information + * + * @v data Raw data + * @v len Length of raw data + * @v info Content information to fill in + * @ret rc Return status code + */ +int peerdist_info ( userptr_t data, size_t len, struct peerdist_info *info ) { + union peerdist_info_version version; + int rc; + + /* Initialise structure */ + memset ( info, 0, sizeof ( *info ) ); + info->raw.data = data; + info->raw.len = len; + + /* Get version */ + if ( ( rc = peerdist_info_get ( info, &version, 0, + sizeof ( version ) ) ) != 0 ) { + DBGC ( info, "PCCRC %p could not get version: %s\n", + info, strerror ( rc ) ); + return rc; + } + DBGC2 ( info, "PCCRC %p version %d.%d\n", + info, version.major, version.minor ); + + /* Determine version */ + switch ( version.raw ) { + case cpu_to_le16 ( PEERDIST_INFO_V1 ) : + info->op = &peerdist_info_v1_operations; + break; + case cpu_to_le16 ( PEERDIST_INFO_V2 ) : + info->op = &peerdist_info_v2_operations; + break; + default: + DBGC ( info, "PCCRC %p unsupported version %d.%d\n", + info, version.major, version.minor ); + return -ENOTSUP; + } + assert ( info->op != NULL ); + assert ( info->op->info != NULL ); + + /* Populate content information */ + if ( ( rc = info->op->info ( info ) ) != 0 ) + return rc; + + DBGC2 ( info, "PCCRC %p range [%08zx,%08zx) covers [%08zx,%08zx) with " + "%d segments\n", info, info->range.start, info->range.end, + info->trim.start, info->trim.end, info->segments ); + return 0; +} + +/** + * Populate content information segment + * + * @v info Content information + * @v segment Content information segment to fill in + * @v index Segment index + * @ret rc Return status code + */ +int peerdist_info_segment ( const struct peerdist_info *info, + struct peerdist_info_segment *segment, + unsigned int index ) { + int rc; + + /* Sanity checks */ + assert ( info != NULL ); + assert ( info->op != NULL ); + assert ( info->op->segment != NULL ); + if ( index >= info->segments ) { + DBGC ( info, "PCCRC %p segment %d of [0,%d) out of range\n", + info, index, info->segments ); + return -ERANGE; + } + + /* Initialise structure */ + memset ( segment, 0, sizeof ( *segment ) ); + segment->info = info; + segment->index = index; + + /* Populate content information segment */ + if ( ( rc = info->op->segment ( segment ) ) != 0 ) + return rc; + + DBGC2 ( info, "PCCRC %p segment %d covers [%08zx,%08zx) with %d " + "blocks\n", info, segment->index, segment->range.start, + segment->range.end, segment->blocks ); + DBGC2 ( info, "PCCRC %p segment %d digest %s\n", info, segment->index, + peerdist_info_hash_ntoa ( info, segment->hash ) ); + DBGC2 ( info, "PCCRC %p segment %d secret %s\n", info, segment->index, + peerdist_info_hash_ntoa ( info, segment->secret ) ); + DBGC2 ( info, "PCCRC %p segment %d identf %s\n", info, segment->index, + peerdist_info_hash_ntoa ( info, segment->id ) ); + return 0; +} + +/** + * Populate content information block + * + * @v segment Content information segment + * @v block Content information block to fill in + * @v index Block index + * @ret rc Return status code + */ +int peerdist_info_block ( const struct peerdist_info_segment *segment, + struct peerdist_info_block *block, + unsigned int index ) { + const struct peerdist_info *info = segment->info; + int rc; + + /* Sanity checks */ + assert ( segment != NULL ); + assert ( info != NULL ); + assert ( info->op != NULL ); + assert ( info->op->block != NULL ); + if ( index >= segment->blocks ) { + DBGC ( info, "PCCRC %p segment %d block %d of [0,%d) out of " + "range\n", info, segment->index, index, segment->blocks); + return -ERANGE; + } + + /* Initialise structure */ + memset ( block, 0, sizeof ( *block ) ); + block->segment = segment; + block->index = index; + + /* Populate content information block */ + if ( ( rc = info->op->block ( block ) ) != 0 ) + return rc; + + DBGC2 ( info, "PCCRC %p segment %d block %d hash %s\n", + info, segment->index, block->index, + peerdist_info_hash_ntoa ( info, block->hash ) ); + DBGC2 ( info, "PCCRC %p segment %d block %d covers [%08zx,%08zx)\n", + info, segment->index, block->index, block->range.start, + block->range.end ); + return 0; +} diff --git a/src/tests/pccrc_test.c b/src/tests/pccrc_test.c new file mode 100644 index 00000000..53d569dc --- /dev/null +++ b/src/tests/pccrc_test.c @@ -0,0 +1,507 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * Peer Content Caching and Retrieval: Content Identification [MS-PCCRC] tests + * + */ + +/* Forcibly enable assertions */ +#undef NDEBUG + +#include <stdint.h> +#include <string.h> +#include <assert.h> +#include <ipxe/uaccess.h> +#include <ipxe/pccrc.h> +#include <ipxe/sha256.h> +#include <ipxe/sha512.h> +#include <ipxe/hmac.h> +#include <ipxe/test.h> + +/** Define inline raw data */ +#define DATA(...) { __VA_ARGS__ } + +/** A content information test */ +struct peerdist_info_test { + /** Raw content information */ + const void *data; + /** Length of raw content information */ + size_t len; + /** Expected digest algorithm */ + struct digest_algorithm *expected_digest; + /** Expected digest size */ + size_t expected_digestsize; + /** Expected trimmed content range */ + struct peerdist_range expected_trim; + /** Expected number of segments */ + unsigned int expected_segments; +}; + +/** + * Define a content information test + * + * @v name Test name + * @v DATA Raw content information + * @v DIGEST Expected digest algorithm + * @v DIGESTSIZE Expected digest size + * @v START Expected trimmed content range start offset + * @v END Expected trimmed content range end offset + * @v SEGMENTS Expected number of segments + * @ret test Content information test + * + * Raw content information can be obtained from PeerDist-capable web + * servers using wget's "--header" option to inject the relevant + * PeerDist headers. For example: + * + * wget --header "Accept-Encoding: peerdist" \ + * --header "X-P2P-PeerDist: Version=1.0" \ + * http://peerdist.server.address/test.url -O - | xxd -i -c 11 + * + * Version 1 content information can be retrieved using the headers: + * + * Accept-Encoding: peerdist + * X-P2P-PeerDist: Version=1.0 + * + * Version 2 content information can be retrieved (from compatible + * servers) using the headers: + * + * Accept-Encoding: peerdist + * X-P2P-PeerDist: Version=1.1 + * X-P2P-PeerDistEx: MinContentInformation=2.0, MaxContentInformation=2.0 + */ +#define PEERDIST_INFO_TEST( name, DATA, DIGEST, DIGESTSIZE, START, END, \ + SEGMENTS ) \ + static const uint8_t name ## _data[] = DATA; \ + static struct peerdist_info_test name = { \ + .data = name ## _data, \ + .len = sizeof ( name ## _data ), \ + .expected_digest = DIGEST, \ + .expected_digestsize = DIGESTSIZE, \ + .expected_trim = { \ + .start = START, \ + .end = END, \ + }, \ + .expected_segments = SEGMENTS, \ + } + +/** A content information segment test */ +struct peerdist_info_segment_test { + /** Segment index */ + unsigned int index; + /** Expected content range */ + struct peerdist_range expected_range; + /** Expected number of blocks */ + unsigned int expected_blocks; + /** Expected block size */ + size_t expected_blksize; + /** Expected segment hash of data */ + uint8_t expected_hash[PEERDIST_DIGEST_MAX_SIZE]; + /** Expected segment secret */ + uint8_t expected_secret[PEERDIST_DIGEST_MAX_SIZE]; + /** Expected segment identifier */ + uint8_t expected_id[PEERDIST_DIGEST_MAX_SIZE]; +}; + +/** + * Define a content information segment test + * + * @v name Test name + * @v INDEX Segment index + * @v START Expected content range start offset + * @v END Expected content range end offset + * @v BLOCKS Expected number of blocks + * @v BLKSIZE Expected block size + * @v HASH Expected segment hash of data + * @v SECRET Expected segment secret + * @v ID Expected segment identifier + * @ret test Content information segment test + */ +#define PEERDIST_INFO_SEGMENT_TEST( name, INDEX, START, END, BLOCKS, \ + BLKSIZE, HASH, SECRET, ID ) \ + static struct peerdist_info_segment_test name = { \ + .index = INDEX, \ + .expected_range = { \ + .start = START, \ + .end = END, \ + }, \ + .expected_blocks = BLOCKS, \ + .expected_blksize = BLKSIZE, \ + .expected_hash = HASH, \ + .expected_secret = SECRET, \ + .expected_id = ID, \ + } + +/** A content information block test */ +struct peerdist_info_block_test { + /** Block index */ + unsigned int index; + /** Expected content range */ + struct peerdist_range expected_range; + /** Expected hash of data */ + uint8_t expected_hash[PEERDIST_DIGEST_MAX_SIZE]; +}; + +/** + * Define a content information block test + * + * @v name Test name + * @v INDEX Block index + * @v START Expected content range start offset + * @v END Expected content range end offset + * @v HASH Expected hash of data + * @ret test Content information block test + */ +#define PEERDIST_INFO_BLOCK_TEST( name, INDEX, START, END, HASH ) \ + static struct peerdist_info_block_test name = { \ + .index = INDEX, \ + .expected_range = { \ + .start = START, \ + .end = END, \ + }, \ + .expected_hash = HASH, \ + } + +/** + * Define a server passphrase + * + * @v name Server passphrase name + * @v DATA Raw server passphrase + * + * The server passphrase can be exported from a Windows BranchCache + * server using the command: + * + * netsh branchcache exportkey exported.key somepassword + * + * and this encrypted exported key can be decrypted using the + * oSSL_key_dx or mcrypt_key_dx utilities found in the (prototype) + * Prequel project at https://fedorahosted.org/prequel/ : + * + * oSSL_key_dx exported.key somepassword + * or + * mcrypt_key_dx exported.key somepassword + * + * Either command will display both the server passphrase and the + * "Server Secret". Note that this latter is the version 1 server + * secret (i.e. the SHA-256 of the server passphrase); the + * corresponding version 2 server secret can be obtained by + * calculating the truncated SHA-512 of the server passphrase. + * + * We do not know the server passphrase during normal operation. We + * use it in the self-tests only to check for typos and other errors + * in the test vectors, by checking that the segment secret defined in + * a content information segment test is as expected. + */ +#define SERVER_PASSPHRASE( name, DATA ) \ + static uint8_t name[] = DATA + +/** Server passphrase used for these test vectors */ +SERVER_PASSPHRASE ( passphrase, + DATA ( 0x2a, 0x3d, 0x73, 0xeb, 0x43, 0x5e, 0x9f, 0x2b, 0x8a, 0x34, 0x42, + 0x67, 0xe7, 0x46, 0x7a, 0x3c, 0x73, 0x85, 0xc6, 0xe0, 0x55, 0xe2, + 0xb4, 0xd3, 0x0d, 0xfe, 0xc7, 0xc3, 0x8b, 0x0e, 0xd7, 0x2c ) ); + +/** IIS logo (iis-85.png) content information version 1 */ +PEERDIST_INFO_TEST ( iis_85_png_v1, + DATA ( 0x00, 0x01, 0x0c, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7e, 0x85, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0xd8, 0xd9, 0x76, 0x35, 0x4a, 0x48, 0x72, 0xe9, 0x25, 0x76, + 0x18, 0x03, 0xf4, 0x58, 0xd9, 0xda, 0xaa, 0x67, 0xf8, 0xe3, 0x1c, + 0x63, 0x0f, 0xb7, 0x4e, 0x6a, 0x31, 0x2e, 0xf8, 0xa2, 0x5a, 0xba, + 0x11, 0xaf, 0xc0, 0xd7, 0x94, 0x92, 0x43, 0xf9, 0x4f, 0x9c, 0x1f, + 0xab, 0x35, 0xd9, 0xfd, 0x1e, 0x33, 0x1f, 0xcf, 0x78, 0x11, 0xa2, + 0xe0, 0x1d, 0x35, 0x87, 0xb3, 0x8d, 0x77, 0x0a, 0x29, 0xe2, 0x02, + 0x00, 0x00, 0x00, 0x73, 0xc1, 0x8a, 0xb8, 0x54, 0x91, 0x10, 0xf8, + 0xe9, 0x0e, 0x71, 0xbb, 0xc3, 0xab, 0x2a, 0xa8, 0xc4, 0x4d, 0x13, + 0xf4, 0x92, 0x94, 0x99, 0x25, 0x5b, 0x66, 0x0f, 0x24, 0xec, 0x77, + 0x80, 0x0b, 0x97, 0x4b, 0xdd, 0x65, 0x56, 0x7f, 0xde, 0xec, 0xcd, + 0xaf, 0xe4, 0x57, 0xa9, 0x50, 0x3b, 0x45, 0x48, 0xf6, 0x6e, 0xd3, + 0xb1, 0x88, 0xdc, 0xfd, 0xa0, 0xac, 0x38, 0x2b, 0x09, 0x71, 0x1a, + 0xcc ), + &sha256_algorithm, 32, 0, 99710, 1 ); + +/** IIS logo (iis-85.png) content information version 1 segment 0 */ +PEERDIST_INFO_SEGMENT_TEST ( iis_85_png_v1_s0, 0, + 0, 99710, 2, 65536, + DATA ( 0xd8, 0xd9, 0x76, 0x35, 0x4a, 0x48, 0x72, 0xe9, 0x25, 0x76, 0x18, + 0x03, 0xf4, 0x58, 0xd9, 0xda, 0xaa, 0x67, 0xf8, 0xe3, 0x1c, 0x63, + 0x0f, 0xb7, 0x4e, 0x6a, 0x31, 0x2e, 0xf8, 0xa2, 0x5a, 0xba ), + DATA ( 0x11, 0xaf, 0xc0, 0xd7, 0x94, 0x92, 0x43, 0xf9, 0x4f, 0x9c, 0x1f, + 0xab, 0x35, 0xd9, 0xfd, 0x1e, 0x33, 0x1f, 0xcf, 0x78, 0x11, 0xa2, + 0xe0, 0x1d, 0x35, 0x87, 0xb3, 0x8d, 0x77, 0x0a, 0x29, 0xe2 ), + DATA ( 0x49, 0x1b, 0x21, 0x7d, 0xbe, 0xe2, 0xb5, 0xf1, 0x2c, 0xa7, 0x9b, + 0x01, 0x5e, 0x06, 0xf4, 0xbb, 0xe6, 0x4f, 0x97, 0x45, 0xba, 0xd7, + 0x86, 0x7a, 0xef, 0x17, 0xde, 0x59, 0x92, 0x7e, 0xdc, 0xe9 ) ); + +/** IIS logo (iis-85.png) content information version 1 segment 0 block 0 */ +PEERDIST_INFO_BLOCK_TEST ( iis_85_png_v1_s0_b0, 0, + 0, 65536, + DATA ( 0x73, 0xc1, 0x8a, 0xb8, 0x54, 0x91, 0x10, 0xf8, 0xe9, 0x0e, 0x71, + 0xbb, 0xc3, 0xab, 0x2a, 0xa8, 0xc4, 0x4d, 0x13, 0xf4, 0x92, 0x94, + 0x99, 0x25, 0x5b, 0x66, 0x0f, 0x24, 0xec, 0x77, 0x80, 0x0b ) ); + +/** IIS logo (iis-85.png) content information version 1 segment 0 block 1 */ +PEERDIST_INFO_BLOCK_TEST ( iis_85_png_v1_s0_b1, 1, + 65536, 99710, + DATA ( 0x97, 0x4b, 0xdd, 0x65, 0x56, 0x7f, 0xde, 0xec, 0xcd, 0xaf, 0xe4, + 0x57, 0xa9, 0x50, 0x3b, 0x45, 0x48, 0xf6, 0x6e, 0xd3, 0xb1, 0x88, + 0xdc, 0xfd, 0xa0, 0xac, 0x38, 0x2b, 0x09, 0x71, 0x1a, 0xcc ) ); + +/** IIS logo (iis-85.png) content information version 2 */ +PEERDIST_INFO_TEST ( iis_85_png_v2, + DATA ( 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x88, 0x00, 0x00, 0x99, 0xde, 0xe0, 0xd0, 0xc3, 0x58, + 0xe2, 0x68, 0x4b, 0x62, 0x33, 0x0d, 0x32, 0xb5, 0xf1, 0x97, 0x87, + 0x24, 0xa0, 0xd0, 0xa5, 0x2b, 0xdc, 0x5e, 0x78, 0x1f, 0xae, 0x71, + 0xff, 0x57, 0xa8, 0xbe, 0x3d, 0xd4, 0x58, 0x03, 0x7e, 0xd4, 0x04, + 0x11, 0x6b, 0xb6, 0x16, 0xd9, 0xb1, 0x41, 0x16, 0x08, 0x85, 0x20, + 0xc4, 0x7c, 0xdc, 0x50, 0xab, 0xce, 0xa3, 0xfa, 0xe1, 0x88, 0xa9, + 0x8e, 0xa2, 0x2d, 0xf3, 0xc0, 0x00, 0x00, 0xeb, 0xa0, 0x33, 0x81, + 0xd0, 0xd0, 0xcb, 0x74, 0xf4, 0xb6, 0x13, 0xd8, 0x21, 0x0f, 0x37, + 0xf0, 0x02, 0xa0, 0x6f, 0x39, 0x10, 0x58, 0x60, 0x96, 0xa1, 0x30, + 0xd3, 0x43, 0x98, 0xc0, 0x8e, 0x66, 0xd7, 0xbc, 0xb8, 0xb6, 0xeb, + 0x77, 0x83, 0xe4, 0xf8, 0x07, 0x64, 0x7b, 0x63, 0xf1, 0x46, 0xb5, + 0x2f, 0x4a, 0xc8, 0x9c, 0xcc, 0x7a, 0xbf, 0x5f, 0xa1, 0x1a, 0xca, + 0xfc, 0x2a, 0xcf, 0x50, 0x28, 0x58, 0x6c ), + &sha512_algorithm, 32, 0, 99710, 2 ); + +/** IIS logo (iis-85.png) content information version 2 segment 0 */ +PEERDIST_INFO_SEGMENT_TEST ( iis_85_png_v2_s0, 0, + 0, 39390, 1, 39390, + DATA ( 0xe0, 0xd0, 0xc3, 0x58, 0xe2, 0x68, 0x4b, 0x62, 0x33, 0x0d, 0x32, + 0xb5, 0xf1, 0x97, 0x87, 0x24, 0xa0, 0xd0, 0xa5, 0x2b, 0xdc, 0x5e, + 0x78, 0x1f, 0xae, 0x71, 0xff, 0x57, 0xa8, 0xbe, 0x3d, 0xd4 ), + DATA ( 0x58, 0x03, 0x7e, 0xd4, 0x04, 0x11, 0x6b, 0xb6, 0x16, 0xd9, 0xb1, + 0x41, 0x16, 0x08, 0x85, 0x20, 0xc4, 0x7c, 0xdc, 0x50, 0xab, 0xce, + 0xa3, 0xfa, 0xe1, 0x88, 0xa9, 0x8e, 0xa2, 0x2d, 0xf3, 0xc0 ), + DATA ( 0x33, 0x71, 0xbb, 0xea, 0xdd, 0xb6, 0x23, 0x53, 0xad, 0xce, 0xf9, + 0x70, 0xa0, 0x6f, 0xdf, 0x65, 0x00, 0x1e, 0x04, 0x21, 0xf4, 0xc7, + 0x10, 0x82, 0x76, 0xb0, 0xc3, 0x7a, 0x9f, 0x9e, 0xc1, 0x0f ) ); + +/** IIS logo (iis-85.png) content information version 2 segment 0 block 0 */ +PEERDIST_INFO_BLOCK_TEST ( iis_85_png_v2_s0_b0, 0, + 0, 39390, + DATA ( 0xe0, 0xd0, 0xc3, 0x58, 0xe2, 0x68, 0x4b, 0x62, 0x33, 0x0d, 0x32, + 0xb5, 0xf1, 0x97, 0x87, 0x24, 0xa0, 0xd0, 0xa5, 0x2b, 0xdc, 0x5e, + 0x78, 0x1f, 0xae, 0x71, 0xff, 0x57, 0xa8, 0xbe, 0x3d, 0xd4 ) ); + +/** IIS logo (iis-85.png) content information version 2 segment 1 */ +PEERDIST_INFO_SEGMENT_TEST ( iis_85_png_v2_s1, 1, + 39390, 99710, 1, 60320, + DATA ( 0x33, 0x81, 0xd0, 0xd0, 0xcb, 0x74, 0xf4, 0xb6, 0x13, 0xd8, 0x21, + 0x0f, 0x37, 0xf0, 0x02, 0xa0, 0x6f, 0x39, 0x10, 0x58, 0x60, 0x96, + 0xa1, 0x30, 0xd3, 0x43, 0x98, 0xc0, 0x8e, 0x66, 0xd7, 0xbc ), + DATA ( 0xb8, 0xb6, 0xeb, 0x77, 0x83, 0xe4, 0xf8, 0x07, 0x64, 0x7b, 0x63, + 0xf1, 0x46, 0xb5, 0x2f, 0x4a, 0xc8, 0x9c, 0xcc, 0x7a, 0xbf, 0x5f, + 0xa1, 0x1a, 0xca, 0xfc, 0x2a, 0xcf, 0x50, 0x28, 0x58, 0x6c ), + DATA ( 0xd7, 0xe9, 0x24, 0x42, 0x5e, 0x8f, 0x4f, 0x88, 0xf0, 0x1d, 0xc6, + 0xa9, 0xbb, 0x1b, 0xc3, 0x7b, 0xe1, 0x13, 0xec, 0x79, 0x17, 0xc7, + 0x45, 0xd4, 0x96, 0x5c, 0x2b, 0x55, 0xfa, 0x16, 0x3a, 0x6e ) ); + +/** IIS logo (iis-85.png) content information version 2 segment 1 block 0 */ +PEERDIST_INFO_BLOCK_TEST ( iis_85_png_v2_s1_b0, 0, + 39390, 99710, + DATA ( 0x33, 0x81, 0xd0, 0xd0, 0xcb, 0x74, 0xf4, 0xb6, 0x13, 0xd8, 0x21, + 0x0f, 0x37, 0xf0, 0x02, 0xa0, 0x6f, 0x39, 0x10, 0x58, 0x60, 0x96, + 0xa1, 0x30, 0xd3, 0x43, 0x98, 0xc0, 0x8e, 0x66, 0xd7, 0xbc ) ); + +/** + * Report content information test result + * + * @v test Content information test + * @v info Content information to fill in + * @v file Test code file + * @v line Test code line + */ +static void peerdist_info_okx ( struct peerdist_info_test *test, + struct peerdist_info *info, + const char *file, unsigned int line ) { + + /* Parse content information */ + okx ( peerdist_info ( virt_to_user ( test->data ), test->len, + info ) == 0, file, line ); + + /* Verify content information */ + okx ( info->raw.data == virt_to_user ( test->data ), file, line ); + okx ( info->raw.len == test->len, file, line ); + okx ( info->digest == test->expected_digest, file, line ); + okx ( info->digestsize == test->expected_digestsize, file, line ); + okx ( info->trim.start >= info->range.start, file, line ); + okx ( info->trim.start == test->expected_trim.start, file, line ); + okx ( info->trim.end <= info->range.end, file, line ); + okx ( info->trim.end == test->expected_trim.end, file, line ); + okx ( info->segments == test->expected_segments, file, line ); +} +#define peerdist_info_ok( test, info ) \ + peerdist_info_okx ( test, info, __FILE__, __LINE__ ) + +/** + * Report content information segment test result + * + * @v test Content information segment test + * @v info Content information + * @v segment Segment information to fill in + * @v file Test code file + * @v line Test code line + */ +static void peerdist_info_segment_okx ( struct peerdist_info_segment_test *test, + const struct peerdist_info *info, + struct peerdist_info_segment *segment, + const char *file, unsigned int line ) { + size_t digestsize = info->digestsize; + + /* Parse content information segment */ + okx ( peerdist_info_segment ( info, segment, test->index ) == 0, + file, line ); + + /* Verify content information segment */ + okx ( segment->info == info, file, line ); + okx ( segment->index == test->index, file, line ); + okx ( segment->range.start == test->expected_range.start, file, line ); + okx ( segment->range.end == test->expected_range.end, file, line ); + okx ( segment->blocks == test->expected_blocks, file, line ); + okx ( segment->blksize == test->expected_blksize, file, line ); + okx ( memcmp ( segment->hash, test->expected_hash, + digestsize ) == 0, file, line ); + okx ( memcmp ( segment->secret, test->expected_secret, + digestsize ) == 0, file, line ); + okx ( memcmp ( segment->id, test->expected_id, + digestsize ) == 0, file, line ); +} +#define peerdist_info_segment_ok( test, info, segment ) \ + peerdist_info_segment_okx ( test, info, segment, __FILE__, __LINE__ ) + +/** + * Report content information block test result + * + * @v test Content information block test + * @v segment Segment information + * @v block Block information to fill in + * @v file Test code file + * @v line Test code line + */ +static void +peerdist_info_block_okx ( struct peerdist_info_block_test *test, + const struct peerdist_info_segment *segment, + struct peerdist_info_block *block, + const char *file, unsigned int line ) { + const struct peerdist_info *info = segment->info; + size_t digestsize = info->digestsize; + + /* Parse content information block */ + okx ( peerdist_info_block ( segment, block, test->index ) == 0, + file, line ); + + /* Verify content information block */ + okx ( block->segment == segment, file, line ); + okx ( block->index == test->index, file, line ); + okx ( block->range.start == test->expected_range.start, file, line ); + okx ( block->range.end == test->expected_range.end, file, line ); + okx ( memcmp ( block->hash, test->expected_hash, + digestsize ) == 0, file, line ); +} +#define peerdist_info_block_ok( test, segment, block ) \ + peerdist_info_block_okx ( test, segment, block, __FILE__, __LINE__ ) + +/** + * Report server passphrase test result + * + * @v test Content information segment test + * @v info Content information + * @v pass Server passphrase + * @v pass_len Length of server passphrase + * @v file Test code file + * @v line Test code line + */ +static void +peerdist_info_passphrase_okx ( struct peerdist_info_segment_test *test, + const struct peerdist_info *info, + uint8_t *pass, size_t pass_len, + const char *file, unsigned int line ) { + struct digest_algorithm *digest = info->digest; + uint8_t ctx[digest->ctxsize]; + uint8_t secret[digest->digestsize]; + uint8_t expected[digest->digestsize]; + size_t digestsize = info->digestsize; + size_t secretsize = digestsize; + + /* Calculate server secret */ + digest_init ( digest, ctx ); + digest_update ( digest, ctx, pass, pass_len ); + digest_final ( digest, ctx, secret ); + + /* Calculate expected segment secret */ + hmac_init ( digest, ctx, secret, &secretsize ); + assert ( secretsize == digestsize ); + hmac_update ( digest, ctx, test->expected_hash, digestsize ); + hmac_final ( digest, ctx, secret, &secretsize, expected ); + assert ( secretsize == digestsize ); + + /* Verify segment secret */ + okx ( memcmp ( test->expected_secret, expected, digestsize ) == 0, + file, line ); +} +#define peerdist_info_passphrase_ok( test, info, pass, pass_len ) \ + peerdist_info_passphrase_okx ( test, info, pass, pass_len, \ + __FILE__, __LINE__ ) + +/** + * Perform content information self-tests + * + */ +static void peerdist_info_test_exec ( void ) { + struct peerdist_info info; + struct peerdist_info_segment segment; + struct peerdist_info_block block; + + /* IIS logo (iis-85.png) content information version 1 */ + peerdist_info_ok ( &iis_85_png_v1, &info ); + peerdist_info_passphrase_ok ( &iis_85_png_v1_s0, &info, + passphrase, sizeof ( passphrase ) ); + peerdist_info_segment_ok ( &iis_85_png_v1_s0, &info, &segment ); + peerdist_info_block_ok ( &iis_85_png_v1_s0_b0, &segment, &block ); + peerdist_info_block_ok ( &iis_85_png_v1_s0_b1, &segment, &block ); + + /* IIS logo (iis-85.png) content information version 2 */ + peerdist_info_ok ( &iis_85_png_v2, &info ); + peerdist_info_passphrase_ok ( &iis_85_png_v2_s0, &info, + passphrase, sizeof ( passphrase ) ); + peerdist_info_segment_ok ( &iis_85_png_v2_s0, &info, &segment ); + peerdist_info_block_ok ( &iis_85_png_v2_s0_b0, &segment, &block ); + peerdist_info_passphrase_ok ( &iis_85_png_v2_s1, &info, + passphrase, sizeof ( passphrase ) ); + peerdist_info_segment_ok ( &iis_85_png_v2_s1, &info, &segment ); + peerdist_info_block_ok ( &iis_85_png_v2_s1_b0, &segment, &block ); +} + +/** Content information self-test */ +struct self_test peerdist_info_test __self_test = { + .name = "pccrc", + .exec = peerdist_info_test_exec, +}; diff --git a/src/tests/tests.c b/src/tests/tests.c index 40c33796..97e510f6 100644 --- a/src/tests/tests.c +++ b/src/tests/tests.c @@ -65,3 +65,4 @@ REQUIRE_OBJECT ( dns_test ); REQUIRE_OBJECT ( uri_test ); REQUIRE_OBJECT ( profile_test ); REQUIRE_OBJECT ( setjmp_test ); +REQUIRE_OBJECT ( pccrc_test ); |