]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[peerdist] Add support for decoding PeerDist Content Information
authorMichael Brown <mcb30@ipxe.org>
Mon, 13 Apr 2015 11:26:05 +0000 (12:26 +0100)
committerMichael Brown <mcb30@ipxe.org>
Mon, 13 Apr 2015 11:26:05 +0000 (12:26 +0100)
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/errfile.h
src/include/ipxe/pccrc.h [new file with mode: 0644]
src/net/pccrc.c [new file with mode: 0644]
src/tests/pccrc_test.c [new file with mode: 0644]
src/tests/tests.c

index f0e5871bd3440a860bc83bf64716296e9c905f08..e43eec26994dc8c78badee97235b5f77904c276b 100644 (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 )
diff --git a/src/include/ipxe/pccrc.h b/src/include/ipxe/pccrc.h
new file mode 100644 (file)
index 0000000..5506bec
--- /dev/null
@@ -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 (file)
index 0000000..1034220
--- /dev/null
@@ -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 (file)
index 0000000..53d569d
--- /dev/null
@@ -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,
+};
index 40c3379612f6e1a075afe15a24bcb6fdf92f58c9..97e510f6e314150bb7ba197e68828481cfdb226d 100644 (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 );