david/ipxe
david
/
ipxe
Archived
1
0
Fork 0

[peerdist] Add support for decoding PeerDist Content Information

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown 2015-04-13 12:26:05 +01:00
parent 755d2b8f6b
commit d9166bbcae
5 changed files with 1757 additions and 0 deletions

View File

@ -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 )

445
src/include/ipxe/pccrc.h Normal file
View File

@ -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 */

803
src/net/pccrc.c Normal file
View File

@ -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;
}

507
src/tests/pccrc_test.c Normal file
View File

@ -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,
};

View File

@ -65,3 +65,4 @@ REQUIRE_OBJECT ( dns_test );
REQUIRE_OBJECT ( uri_test );
REQUIRE_OBJECT ( profile_test );
REQUIRE_OBJECT ( setjmp_test );
REQUIRE_OBJECT ( pccrc_test );