|
| 1 | +#include "ruby.h" |
| 2 | +#include "rubyspec.h" |
| 3 | + |
| 4 | +#include "ruby/digest.h" |
| 5 | + |
| 6 | +#ifdef __cplusplus |
| 7 | +extern "C" { |
| 8 | +#endif |
| 9 | + |
| 10 | +#define DIGEST_LENGTH 20 |
| 11 | +#define BLOCK_LENGTH 40 |
| 12 | + |
| 13 | +const char *init_string = "Initialized\n"; |
| 14 | +const char *update_string = "Updated: "; |
| 15 | +const char *finish_string = "Finished\n"; |
| 16 | + |
| 17 | +#define PAYLOAD_SIZE 128 |
| 18 | + |
| 19 | +typedef struct CTX { |
| 20 | + uint8_t pos; |
| 21 | + char payload[PAYLOAD_SIZE]; |
| 22 | +} CTX; |
| 23 | + |
| 24 | +void* context = NULL; |
| 25 | + |
| 26 | +int digest_spec_plugin_init(void *raw_ctx) { |
| 27 | + // Make the context accessible to tests. This isn't safe, but there's no way to access the context otherwise. |
| 28 | + context = raw_ctx; |
| 29 | + |
| 30 | + struct CTX *ctx = (struct CTX *)raw_ctx; |
| 31 | + size_t len = strlen(init_string); |
| 32 | + |
| 33 | + // Clear the payload since this init function will be invoked as part of the `reset` operation. |
| 34 | + memset(ctx->payload, 0, PAYLOAD_SIZE); |
| 35 | + |
| 36 | + // Write a simple value we can verify in tests. |
| 37 | + // This is not what a real digest would do, but we're using a dummy digest plugin to test interactions. |
| 38 | + memcpy(ctx->payload, init_string, len); |
| 39 | + ctx->pos = (uint8_t) len; |
| 40 | + |
| 41 | + return 1; |
| 42 | +} |
| 43 | + |
| 44 | +void digest_spec_plugin_update(void *raw_ctx, unsigned char *ptr, size_t size) { |
| 45 | + struct CTX *ctx = (struct CTX *)raw_ctx; |
| 46 | + size_t update_str_len = strlen(update_string); |
| 47 | + |
| 48 | + if (ctx->pos + update_str_len + size >= PAYLOAD_SIZE) { |
| 49 | + rb_raise(rb_eRuntimeError, "update size too large; reset the digest and write fewer updates"); |
| 50 | + } |
| 51 | + |
| 52 | + // Write the supplied value to the payload so it can be easily verified in test. |
| 53 | + // This is not what a real digest would do, but we're using a dummy digest plugin to test interactions. |
| 54 | + memcpy(ctx->payload + ctx->pos, update_string, update_str_len); |
| 55 | + ctx->pos += update_str_len; |
| 56 | + |
| 57 | + memcpy(ctx->payload + ctx->pos, ptr, size); |
| 58 | + ctx->pos += size; |
| 59 | + |
| 60 | + return; |
| 61 | +} |
| 62 | + |
| 63 | +int digest_spec_plugin_finish(void *raw_ctx, unsigned char *ptr) { |
| 64 | + struct CTX *ctx = (struct CTX *)raw_ctx; |
| 65 | + size_t finish_string_len = strlen(finish_string); |
| 66 | + |
| 67 | + // We're always going to write DIGEST_LENGTH bytes. In a real plugin, this would be the digest value. Here we |
| 68 | + // write out a text string in order to make validation in tests easier. |
| 69 | + // |
| 70 | + // In order to delineate the output more clearly from an `Digest#update` call, we always write out the |
| 71 | + // `finish_string` message. That leaves `DIGEST_LENGTH - finish_string_len` bytes to read out of the context. |
| 72 | + size_t context_bytes = DIGEST_LENGTH - finish_string_len; |
| 73 | + |
| 74 | + memcpy(ptr, ctx->payload + (ctx->pos - context_bytes), context_bytes); |
| 75 | + memcpy(ptr + context_bytes, finish_string, finish_string_len); |
| 76 | + |
| 77 | + return 1; |
| 78 | +} |
| 79 | + |
| 80 | +static const rb_digest_metadata_t metadata = { |
| 81 | + // The RUBY_DIGEST_API_VERSION value comes from ruby/digest.h and may vary based on the Ruby being tested. Since |
| 82 | + // it isn't publicly exposed in the digest gem, we ignore for these tests. Either the test hard-codes an expected |
| 83 | + // value and is subject to breaking depending on the Ruby being run or we publicly expose `RUBY_DIGEST_API_VERSION`, |
| 84 | + // in which case the test would pass trivially. |
| 85 | + RUBY_DIGEST_API_VERSION, |
| 86 | + DIGEST_LENGTH, |
| 87 | + BLOCK_LENGTH, |
| 88 | + sizeof(CTX), |
| 89 | + (rb_digest_hash_init_func_t) digest_spec_plugin_init, |
| 90 | + (rb_digest_hash_update_func_t) digest_spec_plugin_update, |
| 91 | + (rb_digest_hash_finish_func_t) digest_spec_plugin_finish, |
| 92 | +}; |
| 93 | + |
| 94 | +// The `get_metadata_ptr` function is not publicly available in the digest gem. However, we need to use |
| 95 | +// to extract the `rb_digest_metadata_t*` value set up by the plugin so we reproduce and adjust the |
| 96 | +// definition here. |
| 97 | +// |
| 98 | +// Taken and adapted from https://github.com/ruby/digest/blob/v3.2.0/ext/digest/digest.c#L558-L568 |
| 99 | +static rb_digest_metadata_t * |
| 100 | +get_metadata_ptr(VALUE obj) |
| 101 | +{ |
| 102 | + rb_digest_metadata_t *algo; |
| 103 | + |
| 104 | +#ifdef DIGEST_USE_RB_EXT_RESOLVE_SYMBOL |
| 105 | + // In the digest gem there is an additional data type check performed before reading the value out. |
| 106 | + // Since the type definition isn't public, we can't use it as part of a type check here so we omit it. |
| 107 | + // This is safe to do because this code is intended to only load digest plugins written as part of this test suite. |
| 108 | + algo = RTYPEDDATA_DATA(obj); |
| 109 | +#else |
| 110 | +# undef RUBY_UNTYPED_DATA_WARNING |
| 111 | +# define RUBY_UNTYPED_DATA_WARNING 0 |
| 112 | + Data_Get_Struct(obj, rb_digest_metadata_t, algo); |
| 113 | +#endif |
| 114 | + |
| 115 | + return algo; |
| 116 | +} |
| 117 | + |
| 118 | +VALUE digest_spec_rb_digest_make_metadata(VALUE self) { |
| 119 | + return rb_digest_make_metadata(&metadata); |
| 120 | +} |
| 121 | + |
| 122 | +VALUE digest_spec_block_length(VALUE self, VALUE meta) { |
| 123 | + rb_digest_metadata_t* algo = get_metadata_ptr(meta); |
| 124 | + |
| 125 | + return SIZET2NUM(algo->block_len); |
| 126 | +} |
| 127 | + |
| 128 | +VALUE digest_spec_digest_length(VALUE self, VALUE meta) { |
| 129 | + rb_digest_metadata_t* algo = get_metadata_ptr(meta); |
| 130 | + |
| 131 | + return SIZET2NUM(algo->digest_len); |
| 132 | +} |
| 133 | + |
| 134 | +VALUE digest_spec_context_size(VALUE self, VALUE meta) { |
| 135 | + rb_digest_metadata_t* algo = get_metadata_ptr(meta); |
| 136 | + |
| 137 | + return SIZET2NUM(algo->ctx_size); |
| 138 | +} |
| 139 | + |
| 140 | +#define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x))) |
| 141 | + |
| 142 | +VALUE digest_spec_context(VALUE self, VALUE digest) { |
| 143 | + return PTR2NUM(context); |
| 144 | +} |
| 145 | + |
| 146 | +void Init_digest_spec(void) { |
| 147 | + VALUE cls; |
| 148 | + |
| 149 | + cls = rb_define_class("CApiDigestSpecs", rb_cObject); |
| 150 | + rb_define_method(cls, "rb_digest_make_metadata", digest_spec_rb_digest_make_metadata, 0); |
| 151 | + rb_define_method(cls, "block_length", digest_spec_block_length, 1); |
| 152 | + rb_define_method(cls, "digest_length", digest_spec_digest_length, 1); |
| 153 | + rb_define_method(cls, "context_size", digest_spec_context_size, 1); |
| 154 | + rb_define_method(cls, "context", digest_spec_context, 1); |
| 155 | + |
| 156 | + VALUE mDigest, cDigest_Base, cDigest; |
| 157 | + |
| 158 | + mDigest = rb_define_module("Digest"); |
| 159 | + mDigest = rb_digest_namespace(); |
| 160 | + cDigest_Base = rb_const_get(mDigest, rb_intern_const("Base")); |
| 161 | + |
| 162 | + cDigest = rb_define_class_under(mDigest, "TestDigest", cDigest_Base); |
| 163 | + rb_iv_set(cDigest, "metadata", rb_digest_make_metadata(&metadata)); |
| 164 | +} |
| 165 | + |
| 166 | +#ifdef __cplusplus |
| 167 | +} |
| 168 | +#endif |
0 commit comments