| /* |
| * checksum.c: checksum routines |
| * |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| #define APR_WANT_BYTEFUNC |
| |
| #include <ctype.h> |
| |
| #include <apr_md5.h> |
| #include <apr_sha1.h> |
| |
| #include "svn_checksum.h" |
| #include "svn_error.h" |
| #include "svn_ctype.h" |
| #include "svn_sorts.h" |
| |
| #include "checksum.h" |
| #include "fnv1a.h" |
| |
| #include "private/svn_subr_private.h" |
| |
| #include "svn_private_config.h" |
| |
| |
| |
| /* The MD5 digest for the empty string. */ |
| static const unsigned char md5_empty_string_digest_array[] = { |
| 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, |
| 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e |
| }; |
| |
| /* The SHA1 digest for the empty string. */ |
| static const unsigned char sha1_empty_string_digest_array[] = { |
| 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, |
| 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09 |
| }; |
| |
| /* The FNV-1a digest for the empty string. */ |
| static const unsigned char fnv1a_32_empty_string_digest_array[] = { |
| 0x81, 0x1c, 0x9d, 0xc5 |
| }; |
| |
| /* The FNV-1a digest for the empty string. */ |
| static const unsigned char fnv1a_32x4_empty_string_digest_array[] = { |
| 0xcd, 0x6d, 0x9a, 0x85 |
| }; |
| |
| /* Digests for an empty string, indexed by checksum type */ |
| static const unsigned char * empty_string_digests[] = { |
| md5_empty_string_digest_array, |
| sha1_empty_string_digest_array, |
| fnv1a_32_empty_string_digest_array, |
| fnv1a_32x4_empty_string_digest_array |
| }; |
| |
| /* Digest sizes in bytes, indexed by checksum type */ |
| static const apr_size_t digest_sizes[] = { |
| APR_MD5_DIGESTSIZE, |
| APR_SHA1_DIGESTSIZE, |
| sizeof(apr_uint32_t), |
| sizeof(apr_uint32_t) |
| }; |
| |
| /* Checksum type prefixes used in serialized checksums. */ |
| static const char *ckind_str[] = { |
| "$md5 $", |
| "$sha1$", |
| "$fnv1$", |
| "$fnvm$", |
| /* ### svn_checksum_deserialize() assumes all these have the same strlen() */ |
| }; |
| |
| /* Returns the digest size of it's argument. */ |
| #define DIGESTSIZE(k) \ |
| (((k) < svn_checksum_md5 || (k) > svn_checksum_fnv1a_32x4) ? 0 : digest_sizes[k]) |
| |
| /* Largest supported digest size */ |
| #define MAX_DIGESTSIZE (MAX(APR_MD5_DIGESTSIZE,APR_SHA1_DIGESTSIZE)) |
| |
| const unsigned char * |
| svn__empty_string_digest(svn_checksum_kind_t kind) |
| { |
| return empty_string_digests[kind]; |
| } |
| |
| const char * |
| svn__digest_to_cstring_display(const unsigned char digest[], |
| apr_size_t digest_size, |
| apr_pool_t *pool) |
| { |
| static const char *hex = "0123456789abcdef"; |
| char *str = apr_palloc(pool, (digest_size * 2) + 1); |
| apr_size_t i; |
| |
| for (i = 0; i < digest_size; i++) |
| { |
| str[i*2] = hex[digest[i] >> 4]; |
| str[i*2+1] = hex[digest[i] & 0x0f]; |
| } |
| str[i*2] = '\0'; |
| |
| return str; |
| } |
| |
| |
| const char * |
| svn__digest_to_cstring(const unsigned char digest[], |
| apr_size_t digest_size, |
| apr_pool_t *pool) |
| { |
| static const unsigned char zeros_digest[MAX_DIGESTSIZE] = { 0 }; |
| |
| if (memcmp(digest, zeros_digest, digest_size) != 0) |
| return svn__digest_to_cstring_display(digest, digest_size, pool); |
| else |
| return NULL; |
| } |
| |
| |
| svn_boolean_t |
| svn__digests_match(const unsigned char d1[], |
| const unsigned char d2[], |
| apr_size_t digest_size) |
| { |
| static const unsigned char zeros[MAX_DIGESTSIZE] = { 0 }; |
| |
| return ((memcmp(d1, d2, digest_size) == 0) |
| || (memcmp(d2, zeros, digest_size) == 0) |
| || (memcmp(d1, zeros, digest_size) == 0)); |
| } |
| |
| /* Check to see if KIND is something we recognize. If not, return |
| * SVN_ERR_BAD_CHECKSUM_KIND */ |
| static svn_error_t * |
| validate_kind(svn_checksum_kind_t kind) |
| { |
| if (kind >= svn_checksum_md5 && kind <= svn_checksum_fnv1a_32x4) |
| return SVN_NO_ERROR; |
| else |
| return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL); |
| } |
| |
| /* Create a svn_checksum_t with everything but the contents of the |
| digest populated. */ |
| static svn_checksum_t * |
| checksum_create_without_digest(svn_checksum_kind_t kind, |
| apr_size_t digest_size, |
| apr_pool_t *pool) |
| { |
| /* Use apr_palloc() instead of apr_pcalloc() so that the digest |
| * contents are only set once by the caller. */ |
| svn_checksum_t *checksum = apr_palloc(pool, sizeof(*checksum) + digest_size); |
| checksum->digest = (unsigned char *)checksum + sizeof(*checksum); |
| checksum->kind = kind; |
| return checksum; |
| } |
| |
| /* Return a checksum object, allocated in POOL. The checksum will be of |
| * type KIND and contain the given DIGEST. |
| */ |
| static svn_checksum_t * |
| checksum_create(svn_checksum_kind_t kind, |
| const unsigned char *digest, |
| apr_pool_t *pool) |
| { |
| apr_size_t digest_size = DIGESTSIZE(kind); |
| svn_checksum_t *checksum = checksum_create_without_digest(kind, digest_size, |
| pool); |
| memcpy((unsigned char *)checksum->digest, digest, digest_size); |
| return checksum; |
| } |
| |
| svn_checksum_t * |
| svn_checksum_create(svn_checksum_kind_t kind, |
| apr_pool_t *pool) |
| { |
| svn_checksum_t *checksum; |
| apr_size_t digest_size; |
| |
| switch (kind) |
| { |
| case svn_checksum_md5: |
| case svn_checksum_sha1: |
| case svn_checksum_fnv1a_32: |
| case svn_checksum_fnv1a_32x4: |
| digest_size = digest_sizes[kind]; |
| break; |
| |
| default: |
| return NULL; |
| } |
| |
| checksum = checksum_create_without_digest(kind, digest_size, pool); |
| memset((unsigned char *) checksum->digest, 0, digest_size); |
| return checksum; |
| } |
| |
| svn_checksum_t * |
| svn_checksum__from_digest_md5(const unsigned char *digest, |
| apr_pool_t *result_pool) |
| { |
| return checksum_create(svn_checksum_md5, digest, result_pool); |
| } |
| |
| svn_checksum_t * |
| svn_checksum__from_digest_sha1(const unsigned char *digest, |
| apr_pool_t *result_pool) |
| { |
| return checksum_create(svn_checksum_sha1, digest, result_pool); |
| } |
| |
| svn_checksum_t * |
| svn_checksum__from_digest_fnv1a_32(const unsigned char *digest, |
| apr_pool_t *result_pool) |
| { |
| return checksum_create(svn_checksum_fnv1a_32, digest, result_pool); |
| } |
| |
| svn_checksum_t * |
| svn_checksum__from_digest_fnv1a_32x4(const unsigned char *digest, |
| apr_pool_t *result_pool) |
| { |
| return checksum_create(svn_checksum_fnv1a_32x4, digest, result_pool); |
| } |
| |
| svn_error_t * |
| svn_checksum_clear(svn_checksum_t *checksum) |
| { |
| SVN_ERR(validate_kind(checksum->kind)); |
| |
| memset((unsigned char *) checksum->digest, 0, DIGESTSIZE(checksum->kind)); |
| return SVN_NO_ERROR; |
| } |
| |
| svn_boolean_t |
| svn_checksum_match(const svn_checksum_t *checksum1, |
| const svn_checksum_t *checksum2) |
| { |
| if (checksum1 == NULL || checksum2 == NULL) |
| return TRUE; |
| |
| if (checksum1->kind != checksum2->kind) |
| return FALSE; |
| |
| switch (checksum1->kind) |
| { |
| case svn_checksum_md5: |
| case svn_checksum_sha1: |
| case svn_checksum_fnv1a_32: |
| case svn_checksum_fnv1a_32x4: |
| return svn__digests_match(checksum1->digest, |
| checksum2->digest, |
| digest_sizes[checksum1->kind]); |
| |
| default: |
| /* We really shouldn't get here, but if we do... */ |
| return FALSE; |
| } |
| } |
| |
| const char * |
| svn_checksum_to_cstring_display(const svn_checksum_t *checksum, |
| apr_pool_t *pool) |
| { |
| switch (checksum->kind) |
| { |
| case svn_checksum_md5: |
| case svn_checksum_sha1: |
| case svn_checksum_fnv1a_32: |
| case svn_checksum_fnv1a_32x4: |
| return svn__digest_to_cstring_display(checksum->digest, |
| digest_sizes[checksum->kind], |
| pool); |
| |
| default: |
| /* We really shouldn't get here, but if we do... */ |
| return NULL; |
| } |
| } |
| |
| const char * |
| svn_checksum_to_cstring(const svn_checksum_t *checksum, |
| apr_pool_t *pool) |
| { |
| if (checksum == NULL) |
| return NULL; |
| |
| switch (checksum->kind) |
| { |
| case svn_checksum_md5: |
| case svn_checksum_sha1: |
| case svn_checksum_fnv1a_32: |
| case svn_checksum_fnv1a_32x4: |
| return svn__digest_to_cstring(checksum->digest, |
| digest_sizes[checksum->kind], |
| pool); |
| |
| default: |
| /* We really shouldn't get here, but if we do... */ |
| return NULL; |
| } |
| } |
| |
| |
| const char * |
| svn_checksum_serialize(const svn_checksum_t *checksum, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT_NO_RETURN(checksum->kind >= svn_checksum_md5 |
| || checksum->kind <= svn_checksum_fnv1a_32x4); |
| return apr_pstrcat(result_pool, |
| ckind_str[checksum->kind], |
| svn_checksum_to_cstring(checksum, scratch_pool), |
| SVN_VA_NULL); |
| } |
| |
| |
| svn_error_t * |
| svn_checksum_deserialize(const svn_checksum_t **checksum, |
| const char *data, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_checksum_kind_t kind; |
| svn_checksum_t *parsed_checksum; |
| |
| /* All prefixes have the same length. */ |
| apr_size_t prefix_len = strlen(ckind_str[0]); |
| |
| /* "$md5 $...", "$sha1$..." or ... */ |
| if (strlen(data) <= prefix_len) |
| return svn_error_createf(SVN_ERR_BAD_CHECKSUM_PARSE, NULL, |
| _("Invalid prefix in checksum '%s'"), |
| data); |
| |
| for (kind = svn_checksum_md5; kind <= svn_checksum_fnv1a_32x4; ++kind) |
| if (strncmp(ckind_str[kind], data, prefix_len) == 0) |
| { |
| SVN_ERR(svn_checksum_parse_hex(&parsed_checksum, kind, |
| data + prefix_len, result_pool)); |
| *checksum = parsed_checksum; |
| return SVN_NO_ERROR; |
| } |
| |
| return svn_error_createf(SVN_ERR_BAD_CHECKSUM_KIND, NULL, |
| "Unknown checksum kind in '%s'", data); |
| } |
| |
| |
| svn_error_t * |
| svn_checksum_parse_hex(svn_checksum_t **checksum, |
| svn_checksum_kind_t kind, |
| const char *hex, |
| apr_pool_t *pool) |
| { |
| apr_size_t i, len; |
| unsigned char is_nonzero = 0; |
| unsigned char *digest; |
| static const unsigned char xdigitval[256] = |
| { |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, /* 0-7 */ |
| 0x08,0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, /* 8-9 */ |
| 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0xFF, /* A-F */ |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0xFF, /* a-f */ |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, |
| }; |
| |
| if (hex == NULL) |
| { |
| *checksum = NULL; |
| return SVN_NO_ERROR; |
| } |
| |
| SVN_ERR(validate_kind(kind)); |
| |
| *checksum = svn_checksum_create(kind, pool); |
| digest = (unsigned char *)(*checksum)->digest; |
| len = DIGESTSIZE(kind); |
| |
| for (i = 0; i < len; i++) |
| { |
| unsigned char x1 = xdigitval[(unsigned char)hex[i * 2]]; |
| unsigned char x2 = xdigitval[(unsigned char)hex[i * 2 + 1]]; |
| if (x1 == 0xFF || x2 == 0xFF) |
| return svn_error_create(SVN_ERR_BAD_CHECKSUM_PARSE, NULL, NULL); |
| |
| digest[i] = (x1 << 4) | x2; |
| is_nonzero |= digest[i]; |
| } |
| |
| if (!is_nonzero) |
| *checksum = NULL; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_checksum_t * |
| svn_checksum_dup(const svn_checksum_t *checksum, |
| apr_pool_t *pool) |
| { |
| /* The duplicate of a NULL checksum is a NULL... */ |
| if (checksum == NULL) |
| return NULL; |
| |
| /* Without this check on valid checksum kind a NULL svn_checksum_t |
| * pointer is returned which could cause a core dump at an |
| * indeterminate time in the future because callers are not |
| * expecting a NULL pointer. This commit forces an early abort() so |
| * it's easier to track down where the issue arose. */ |
| switch (checksum->kind) |
| { |
| case svn_checksum_md5: |
| case svn_checksum_sha1: |
| case svn_checksum_fnv1a_32: |
| case svn_checksum_fnv1a_32x4: |
| return checksum_create(checksum->kind, checksum->digest, pool); |
| |
| default: |
| SVN_ERR_MALFUNCTION_NO_RETURN(); |
| break; |
| } |
| } |
| |
| svn_error_t * |
| svn_checksum(svn_checksum_t **checksum, |
| svn_checksum_kind_t kind, |
| const void *data, |
| apr_size_t len, |
| apr_pool_t *pool) |
| { |
| apr_sha1_ctx_t sha1_ctx; |
| |
| SVN_ERR(validate_kind(kind)); |
| *checksum = svn_checksum_create(kind, pool); |
| |
| switch (kind) |
| { |
| case svn_checksum_md5: |
| apr_md5((unsigned char *)(*checksum)->digest, data, len); |
| break; |
| |
| case svn_checksum_sha1: |
| apr_sha1_init(&sha1_ctx); |
| apr_sha1_update(&sha1_ctx, data, (unsigned int)len); |
| apr_sha1_final((unsigned char *)(*checksum)->digest, &sha1_ctx); |
| break; |
| |
| case svn_checksum_fnv1a_32: |
| *(apr_uint32_t *)(*checksum)->digest |
| = htonl(svn__fnv1a_32(data, len)); |
| break; |
| |
| case svn_checksum_fnv1a_32x4: |
| *(apr_uint32_t *)(*checksum)->digest |
| = htonl(svn__fnv1a_32x4(data, len)); |
| break; |
| |
| default: |
| /* We really shouldn't get here, but if we do... */ |
| return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_checksum_t * |
| svn_checksum_empty_checksum(svn_checksum_kind_t kind, |
| apr_pool_t *pool) |
| { |
| switch (kind) |
| { |
| case svn_checksum_md5: |
| case svn_checksum_sha1: |
| case svn_checksum_fnv1a_32: |
| case svn_checksum_fnv1a_32x4: |
| return checksum_create(kind, empty_string_digests[kind], pool); |
| |
| default: |
| /* We really shouldn't get here, but if we do... */ |
| SVN_ERR_MALFUNCTION_NO_RETURN(); |
| } |
| } |
| |
| struct svn_checksum_ctx_t |
| { |
| void *apr_ctx; |
| svn_checksum_kind_t kind; |
| }; |
| |
| svn_checksum_ctx_t * |
| svn_checksum_ctx_create(svn_checksum_kind_t kind, |
| apr_pool_t *pool) |
| { |
| svn_checksum_ctx_t *ctx = apr_palloc(pool, sizeof(*ctx)); |
| |
| ctx->kind = kind; |
| switch (kind) |
| { |
| case svn_checksum_md5: |
| ctx->apr_ctx = apr_palloc(pool, sizeof(apr_md5_ctx_t)); |
| apr_md5_init(ctx->apr_ctx); |
| break; |
| |
| case svn_checksum_sha1: |
| ctx->apr_ctx = apr_palloc(pool, sizeof(apr_sha1_ctx_t)); |
| apr_sha1_init(ctx->apr_ctx); |
| break; |
| |
| case svn_checksum_fnv1a_32: |
| ctx->apr_ctx = svn_fnv1a_32__context_create(pool); |
| break; |
| |
| case svn_checksum_fnv1a_32x4: |
| ctx->apr_ctx = svn_fnv1a_32x4__context_create(pool); |
| break; |
| |
| default: |
| SVN_ERR_MALFUNCTION_NO_RETURN(); |
| } |
| |
| return ctx; |
| } |
| |
| svn_error_t * |
| svn_checksum_ctx_reset(svn_checksum_ctx_t *ctx) |
| { |
| switch (ctx->kind) |
| { |
| case svn_checksum_md5: |
| memset(ctx->apr_ctx, 0, sizeof(apr_md5_ctx_t)); |
| apr_md5_init(ctx->apr_ctx); |
| break; |
| |
| case svn_checksum_sha1: |
| memset(ctx->apr_ctx, 0, sizeof(apr_sha1_ctx_t)); |
| apr_sha1_init(ctx->apr_ctx); |
| break; |
| |
| case svn_checksum_fnv1a_32: |
| svn_fnv1a_32__context_reset(ctx->apr_ctx); |
| break; |
| |
| case svn_checksum_fnv1a_32x4: |
| svn_fnv1a_32x4__context_reset(ctx->apr_ctx); |
| break; |
| |
| default: |
| SVN_ERR_MALFUNCTION(); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_checksum_update(svn_checksum_ctx_t *ctx, |
| const void *data, |
| apr_size_t len) |
| { |
| switch (ctx->kind) |
| { |
| case svn_checksum_md5: |
| apr_md5_update(ctx->apr_ctx, data, len); |
| break; |
| |
| case svn_checksum_sha1: |
| apr_sha1_update(ctx->apr_ctx, data, (unsigned int)len); |
| break; |
| |
| case svn_checksum_fnv1a_32: |
| svn_fnv1a_32__update(ctx->apr_ctx, data, len); |
| break; |
| |
| case svn_checksum_fnv1a_32x4: |
| svn_fnv1a_32x4__update(ctx->apr_ctx, data, len); |
| break; |
| |
| default: |
| /* We really shouldn't get here, but if we do... */ |
| return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_checksum_final(svn_checksum_t **checksum, |
| const svn_checksum_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| *checksum = svn_checksum_create(ctx->kind, pool); |
| |
| switch (ctx->kind) |
| { |
| case svn_checksum_md5: |
| apr_md5_final((unsigned char *)(*checksum)->digest, ctx->apr_ctx); |
| break; |
| |
| case svn_checksum_sha1: |
| apr_sha1_final((unsigned char *)(*checksum)->digest, ctx->apr_ctx); |
| break; |
| |
| case svn_checksum_fnv1a_32: |
| *(apr_uint32_t *)(*checksum)->digest |
| = htonl(svn_fnv1a_32__finalize(ctx->apr_ctx)); |
| break; |
| |
| case svn_checksum_fnv1a_32x4: |
| *(apr_uint32_t *)(*checksum)->digest |
| = htonl(svn_fnv1a_32x4__finalize(ctx->apr_ctx)); |
| break; |
| |
| default: |
| /* We really shouldn't get here, but if we do... */ |
| return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| apr_size_t |
| svn_checksum_size(const svn_checksum_t *checksum) |
| { |
| return DIGESTSIZE(checksum->kind); |
| } |
| |
| svn_error_t * |
| svn_checksum_mismatch_err(const svn_checksum_t *expected, |
| const svn_checksum_t *actual, |
| apr_pool_t *scratch_pool, |
| const char *fmt, |
| ...) |
| { |
| va_list ap; |
| const char *desc; |
| |
| va_start(ap, fmt); |
| desc = apr_pvsprintf(scratch_pool, fmt, ap); |
| va_end(ap); |
| |
| return svn_error_createf(SVN_ERR_CHECKSUM_MISMATCH, NULL, |
| _("%s:\n" |
| " expected: %s\n" |
| " actual: %s\n"), |
| desc, |
| svn_checksum_to_cstring_display(expected, scratch_pool), |
| svn_checksum_to_cstring_display(actual, scratch_pool)); |
| } |
| |
| svn_boolean_t |
| svn_checksum_is_empty_checksum(svn_checksum_t *checksum) |
| { |
| /* By definition, the NULL checksum matches all others, including the |
| empty one. */ |
| if (!checksum) |
| return TRUE; |
| |
| switch (checksum->kind) |
| { |
| case svn_checksum_md5: |
| case svn_checksum_sha1: |
| case svn_checksum_fnv1a_32: |
| case svn_checksum_fnv1a_32x4: |
| return svn__digests_match(checksum->digest, |
| svn__empty_string_digest(checksum->kind), |
| digest_sizes[checksum->kind]); |
| |
| default: |
| /* We really shouldn't get here, but if we do... */ |
| SVN_ERR_MALFUNCTION_NO_RETURN(); |
| } |
| } |
| |
| /* Checksum calculating stream wrappers. |
| */ |
| |
| /* Baton used by write_handler and close_handler to calculate the checksum |
| * and return the result to the stream creator. It accommodates the data |
| * needed by svn_checksum__wrap_write_stream_fnv1a_32x4 as well as |
| * svn_checksum__wrap_write_stream. |
| */ |
| typedef struct stream_baton_t |
| { |
| /* Stream we are wrapping. Forward write() and close() operations to it. */ |
| svn_stream_t *inner_stream; |
| |
| /* Build the checksum data in here. */ |
| svn_checksum_ctx_t *context; |
| |
| /* Write the final checksum here. May be NULL. */ |
| svn_checksum_t **checksum; |
| |
| /* Copy the digest of the final checksum. May be NULL. */ |
| unsigned char *digest; |
| |
| /* Allocate the resulting checksum here. */ |
| apr_pool_t *pool; |
| } stream_baton_t; |
| |
| /* Implement svn_write_fn_t. |
| * Update checksum and pass data on to inner stream. |
| */ |
| static svn_error_t * |
| write_handler(void *baton, |
| const char *data, |
| apr_size_t *len) |
| { |
| stream_baton_t *b = baton; |
| |
| SVN_ERR(svn_checksum_update(b->context, data, *len)); |
| SVN_ERR(svn_stream_write(b->inner_stream, data, len)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Implement svn_close_fn_t. |
| * Finalize checksum calculation and write results. Close inner stream. |
| */ |
| static svn_error_t * |
| close_handler(void *baton) |
| { |
| stream_baton_t *b = baton; |
| svn_checksum_t *local_checksum; |
| |
| /* Ensure we can always write to *B->CHECKSUM. */ |
| if (!b->checksum) |
| b->checksum = &local_checksum; |
| |
| /* Get the final checksum. */ |
| SVN_ERR(svn_checksum_final(b->checksum, b->context, b->pool)); |
| |
| /* Extract digest, if wanted. */ |
| if (b->digest) |
| { |
| apr_size_t digest_size = DIGESTSIZE((*b->checksum)->kind); |
| memcpy(b->digest, (*b->checksum)->digest, digest_size); |
| } |
| |
| /* Done here. Now, close the underlying stream as well. */ |
| return svn_error_trace(svn_stream_close(b->inner_stream)); |
| } |
| |
| /* Common constructor function for svn_checksum__wrap_write_stream and |
| * svn_checksum__wrap_write_stream_fnv1a_32x4, taking the superset of their |
| * respecting parameters. |
| * |
| * In the current usage, either CHECKSUM or DIGEST will be NULL but this |
| * function does not enforce any such restriction. Also, the caller must |
| * make sure that DIGEST refers to a buffer of sufficient length. |
| */ |
| static svn_stream_t * |
| wrap_write_stream(svn_checksum_t **checksum, |
| unsigned char *digest, |
| svn_stream_t *inner_stream, |
| svn_checksum_kind_t kind, |
| apr_pool_t *pool) |
| { |
| svn_stream_t *outer_stream; |
| |
| stream_baton_t *baton = apr_pcalloc(pool, sizeof(*baton)); |
| baton->inner_stream = inner_stream; |
| baton->context = svn_checksum_ctx_create(kind, pool); |
| baton->checksum = checksum; |
| baton->digest = digest; |
| baton->pool = pool; |
| |
| outer_stream = svn_stream_create(baton, pool); |
| svn_stream_set_write(outer_stream, write_handler); |
| svn_stream_set_close(outer_stream, close_handler); |
| |
| return outer_stream; |
| } |
| |
| svn_stream_t * |
| svn_checksum__wrap_write_stream(svn_checksum_t **checksum, |
| svn_stream_t *inner_stream, |
| svn_checksum_kind_t kind, |
| apr_pool_t *pool) |
| { |
| return wrap_write_stream(checksum, NULL, inner_stream, kind, pool); |
| } |
| |
| /* Implement svn_close_fn_t. |
| * For FNV-1a-like checksums, we want the checksum as 32 bit integer instead |
| * of a big endian 4 byte sequence. This simply wraps close_handler adding |
| * the digest conversion. |
| */ |
| static svn_error_t * |
| close_handler_fnv1a_32x4(void *baton) |
| { |
| stream_baton_t *b = baton; |
| SVN_ERR(close_handler(baton)); |
| |
| *(apr_uint32_t *)b->digest = ntohl(*(apr_uint32_t *)b->digest); |
| return SVN_NO_ERROR; |
| } |
| |
| svn_stream_t * |
| svn_checksum__wrap_write_stream_fnv1a_32x4(apr_uint32_t *digest, |
| svn_stream_t *inner_stream, |
| apr_pool_t *pool) |
| { |
| svn_stream_t *result |
| = wrap_write_stream(NULL, (unsigned char *)digest, inner_stream, |
| svn_checksum_fnv1a_32x4, pool); |
| svn_stream_set_close(result, close_handler_fnv1a_32x4); |
| |
| return result; |
| } |