(主に)ゆぞさん

 OpenSSL::Digest::* を Digest::Base のサブクラスにするべく修正
してみたので、レビューをお願いします。なお openssl/digest.rb の
処理は ossl_digest.c に取り込みました。

 メリットとしては、苦もなくAPIが統一される、 #file() などの追加
メソッドが使える、 bubblebabble や hmac (OpenSSL にもあるが)等の
アドインの恩恵を受けられる、などです。


 ところで、作業の中で

	OpenSSL::Digest::Digest::new(name)

なるインターフェースを発見しました。Digest のだぶりが見苦しい
感じがするので、代わりに OpenSSL::Digest(name) を定義して

	OpenSSL::Digest("MD5").new

と書けるようにしてみましたが、どうでしょうか。


 あとは、 size() と name() はインスタンスメソッドとして存在すべき
なのかよくわかりませんでしたが、互換性を考慮して残してあります。


 以上、よろしくお願いします。

-- 
                     /
                    /__  __            Akinori.org / MUSHA.org
                   / )  )  ) )  /     FreeBSD.org / Ruby-lang.org
Akinori MUSHA aka / (_ /  ( (__(  @ iDaemons.org / and.or.jp

"Different eyes see different things,
    Different hearts beat on different strings --
       But there are times for you and me when all such things agree"

M ext/openssl/ossl_digest.c
M ext/openssl/lib/openssl.rb
R ext/openssl/lib/openssl/digest.rb
M test/openssl/test_digest.rb
Index: ext/openssl/ossl_digest.c
===================================================================
RCS file: /src/ruby/ext/openssl/ossl_digest.c,v
retrieving revision 1.7
diff -u -r1.7 ossl_digest.c
--- ext/openssl/ossl_digest.c	31 Aug 2006 10:27:46 -0000	1.7
+++ ext/openssl/ossl_digest.c	13 Oct 2006 12:03:37 -0000
@@ -2,6 +2,7 @@
  * $Id: ossl_digest.c,v 1.7 2006/08/31 10:27:46 matz Exp $
  * 'OpenSSL for Ruby' project
  * Copyright (C) 2001-2002  Michal Rokos <m.rokos / sh.cvut.cz>
+ * Copyright (C) 2006 Akinori MUSHA <knu / iDaemons.org>
  * All rights reserved.
  */
 /*
@@ -10,17 +11,6 @@
  */
 #include "ossl.h"
 
-#define GetDigest(obj, ctx) do { \
-    Data_Get_Struct(obj, EVP_MD_CTX, ctx); \
-    if (!ctx) { \
-	ossl_raise(rb_eRuntimeError, "Digest CTX wasn't initialized!"); \
-    } \
-} while (0)
-#define SafeGetDigest(obj, ctx) do { \
-    OSSL_Check_Kind(obj, cDigest); \
-    GetDigest(obj, ctx); \
-} while (0)
-
 /*
  * Classes
  */
@@ -28,267 +18,272 @@
 VALUE cDigest;
 VALUE eDigestError;
 
-static VALUE ossl_digest_alloc(VALUE klass);
+static ID id_evp_md, id_new;
+
+static const char *digest_names[] = {
+    "DSS",
+    "DSS1",
+    "MD2",
+    "MD4",
+    "MD5",
+    "MDC2",
+    "RIPEMD160",
+    "SHA",
+    "SHA1",
+#if OPENSSL_VERSION_NUMBER > 0x00908000
+    "SHA224",
+    "SHA256",
+    "SHA384",
+    "SHA512",
+#endif
+};
 
 /*
- * Public
+ * Digest::Base
  */
-const EVP_MD *
-GetDigestPtr(VALUE obj)
+
+static EVP_MD *
+get_digest_base_metadata(VALUE klass)
 {
-    EVP_MD_CTX *ctx;
+    VALUE obj;
+    EVP_MD *md;
 
-    SafeGetDigest(obj, ctx);
+    if (rb_ivar_defined(klass, id_evp_md) == Qfalse) {
+        return NULL;
+    }
 
-    return EVP_MD_CTX_md(ctx); /*== ctx->digest*/
-}
+    obj = rb_ivar_get(klass, id_evp_md);
 
-VALUE
-ossl_digest_new(const EVP_MD *md)
-{  
-    VALUE ret;
-    EVP_MD_CTX *ctx;
+    Data_Get_Struct(obj, EVP_MD, md);
 
-    ret = ossl_digest_alloc(cDigest);
-    GetDigest(ret, ctx);
-    EVP_MD_CTX_init(ctx);
-    EVP_DigestInit_ex(ctx, md, NULL);
-   
-    return ret;
+    return md;
 }
 
-/*
- * Private
- */
 static VALUE
-ossl_digest_alloc(VALUE klass)
+rb_ossl_digest_base_alloc(VALUE klass)
 {
+    EVP_MD *md;
     EVP_MD_CTX *ctx;
     VALUE obj;
 
+    md = get_digest_base_metadata(klass);
+
+    if (md == NULL) {
+        return Data_Wrap_Struct(klass, 0, free, 0);
+    }
+
     ctx = EVP_MD_CTX_create();
-    if (ctx == NULL)
-	ossl_raise(rb_eRuntimeError, "EVP_MD_CTX_create() failed");
-    EVP_MD_CTX_init(ctx);
+
     obj = Data_Wrap_Struct(klass, 0, EVP_MD_CTX_destroy, ctx);
-	
+
     return obj;
 }
 
-VALUE ossl_digest_update(VALUE, VALUE);
-
 static VALUE
-ossl_digest_initialize(int argc, VALUE *argv, VALUE self)
+rb_ossl_digest_base_copy(VALUE copy, VALUE obj)
 {
-    EVP_MD_CTX *ctx;
-    const EVP_MD *md;
-    char *name;
-    VALUE type, data;
-
-    rb_scan_args(argc, argv, "11", &type, &data);
-    StringValue(type);
-    if (!NIL_P(data)) StringValue(data);
-    name = StringValuePtr(type);
-    
-    md = EVP_get_digestbyname(name);
-    if (!md) {
-	ossl_raise(rb_eRuntimeError, "Unsupported digest algorithm (%s).", name);
+    EVP_MD *md;
+    EVP_MD_CTX *ctx1, *ctx2;
+
+    if (copy == obj) return copy;
+    rb_check_frozen(copy);
+    md = get_digest_base_metadata(rb_obj_class(copy));
+
+    if (md == NULL) {
+        /* initialize_copy() is undefined or something */
+        rb_notimplement();
     }
-    GetDigest(self, ctx);
-    EVP_DigestInit_ex(ctx, md, NULL);
-    
-    if (!NIL_P(data)) return ossl_digest_update(self, data);
-    return self;
+
+    /* get_digest_base_metadata() may return a NULL */
+    if (md != get_digest_base_metadata(rb_obj_class(obj))) {
+	ossl_raise(rb_eTypeError, "wrong argument class");
+    }
+    Data_Get_Struct(obj, EVP_MD_CTX, ctx1);
+    Data_Get_Struct(copy, EVP_MD_CTX, ctx2);
+    EVP_MD_CTX_copy(ctx2, ctx1);
+
+    return copy;
 }
 
 static VALUE
-ossl_digest_copy(VALUE self, VALUE other)
+rb_ossl_digest_base_update(VALUE self, VALUE str)
 {
-    EVP_MD_CTX *ctx1, *ctx2;
-    
-    rb_check_frozen(self);
-    if (self == other) return self;
+    EVP_MD_CTX *ctx;
 
-    GetDigest(self, ctx1);
-    SafeGetDigest(other, ctx2);
+    Data_Get_Struct(self, EVP_MD_CTX, ctx);
 
-    if (!EVP_MD_CTX_copy(ctx1, ctx2)) {
-	ossl_raise(eDigestError, NULL);
+    if (ctx == NULL) {
+	ossl_raise(rb_eRuntimeError, "Unsupported digest algorithm");
     }
+
+    StringValue(str);
+    EVP_DigestUpdate(ctx, RSTRING_PTR(str), RSTRING_LEN(str));
+
     return self;
 }
 
 static VALUE
-ossl_digest_reset(VALUE self)
+rb_ossl_digest_base_init(int argc, VALUE *argv, VALUE self)
 {
+    EVP_MD *md;
     EVP_MD_CTX *ctx;
+    VALUE arg;
 
-    GetDigest(self, ctx);
-    EVP_DigestInit_ex(ctx, EVP_MD_CTX_md(ctx), NULL);
+    md = get_digest_base_metadata(rb_obj_class(self));
 
-    return self;
-}
+    if (md == NULL) {
+	ossl_raise(rb_eRuntimeError, "Unsupported digest algorithm");
+    }
 
-VALUE
-ossl_digest_update(VALUE self, VALUE data)
-{
-    EVP_MD_CTX *ctx;
+    Data_Get_Struct(self, EVP_MD_CTX, ctx);
 
-    StringValue(data);
-    GetDigest(self, ctx);
-    EVP_DigestUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data));
+    EVP_DigestInit_ex(ctx, md, NULL);
+
+    rb_scan_args(argc, argv, "01", &arg);
+
+    if (!NIL_P(arg)) rb_ossl_digest_base_update(self, arg);
 
     return self;
 }
 
-static void
-digest_final(EVP_MD_CTX *ctx, char **buf, int *buf_len)
+static VALUE
+rb_ossl_digest_base_digest(VALUE self)
 {
-    EVP_MD_CTX final;
+    EVP_MD *md;
+    EVP_MD_CTX *ctx1, *ctx2;
+    size_t ctx_size;
+    VALUE str;
 
-    if (!EVP_MD_CTX_copy(&final, ctx)) {
-	ossl_raise(eDigestError, NULL);
-    }
-    if (!(*buf = OPENSSL_malloc(EVP_MD_CTX_size(&final)))) {
-	EVP_MD_CTX_cleanup(&final);
-	ossl_raise(eDigestError, "Cannot allocate mem for digest");
+    md = get_digest_base_metadata(rb_obj_class(self));
+
+    if (md == NULL) {
+        /* subclasses must define update() */
+        rb_notimplement();
     }
-    EVP_DigestFinal_ex(&final, *buf, buf_len);
-    EVP_MD_CTX_cleanup(&final);
-}
 
-static VALUE
-ossl_digest_digest(VALUE self)
-{
-    EVP_MD_CTX *ctx;
-    char *buf;
-    int buf_len;
-    VALUE digest;
-	
-    GetDigest(self, ctx);
-    digest_final(ctx, &buf, &buf_len);
-    digest = ossl_buf2str(buf, buf_len);
-	
-    return digest;
+    Data_Get_Struct(self, EVP_MD_CTX, ctx1);
+
+    ctx2 = EVP_MD_CTX_create();
+    EVP_MD_CTX_copy(ctx2, ctx1);
+
+    str = rb_str_new(0, EVP_MD_CTX_size(ctx2));
+    EVP_DigestFinal_ex(ctx2, RSTRING_PTR(str), NULL);
+    EVP_MD_CTX_destroy(ctx2);
+
+    return str;
 }
 
 static VALUE
-ossl_digest_hexdigest(VALUE self)
+rb_ossl_digest_base_name(VALUE self)
 {
-    EVP_MD_CTX *ctx;
-    char *buf, *hexbuf;
-    int buf_len;
-    VALUE hexdigest;
-
-    GetDigest(self, ctx);
-    digest_final(ctx, &buf, &buf_len);
-    if (string2hex(buf, buf_len, &hexbuf, NULL) != 2 * buf_len) {
-	OPENSSL_free(buf);
-	ossl_raise(eDigestError, "Memory alloc error");
+    EVP_MD *md;
+
+    md = get_digest_base_metadata(rb_obj_class(self));
+
+    if (md == NULL) {
+        /* subclasses must define update() */
+        rb_notimplement();
     }
-    OPENSSL_free(buf);
-    hexdigest = ossl_buf2str(hexbuf, 2 * buf_len);
 
-    return hexdigest;
+    return rb_str_new2(EVP_MD_name(md));
 }
 
 static VALUE
-ossl_digest_s_digest(VALUE klass, VALUE str, VALUE data)
+rb_ossl_digest_base_size(VALUE self)
 {
-    VALUE obj = rb_class_new_instance(1, &str, klass);
+    EVP_MD *md;
+
+    md = get_digest_base_metadata(rb_obj_class(self));
 
-    ossl_digest_update(obj, data);
+    if (md == NULL) {
+        /* subclasses must define update() */
+        rb_notimplement();
+    }
 
-    return ossl_digest_digest(obj);
+    return INT2NUM(EVP_MD_size(md));
 }
 
 static VALUE
-ossl_digest_s_hexdigest(VALUE klass, VALUE str, VALUE data)
+ossl_digest_class_by_name(const char *name)
 {
-    VALUE obj = rb_class_new_instance(1, &str, klass);
+    VALUE klass = rb_const_get(mDigest, rb_intern(name));
 
-    ossl_digest_update(obj, data);
+    if (klass == Qnil) {
+	ossl_raise(rb_eRuntimeError, "Unsupported digest algorithm");
+    }
 
-    return ossl_digest_hexdigest(obj);
+    return klass;
 }
 
 static VALUE
-ossl_digest_equal(VALUE self, VALUE other)
+rb_ossl_s_Digest(VALUE klass, VALUE name)
 {
-    EVP_MD_CTX *ctx;
-    VALUE str1, str2;
+    StringValue(name);
 
-    if (rb_obj_is_kind_of(other, cDigest) == Qtrue) {
-	str2 = ossl_digest_digest(other);
-    } else {
-	StringValue(other);
-	str2 = other;
-    }
-    GetDigest(self, ctx);
-    if (RSTRING_LEN(str2) == EVP_MD_CTX_size(ctx)) {
-	str1 = ossl_digest_digest(self);
-    } else {
-	str1 = ossl_digest_hexdigest(self);
-    }
-    if (RSTRING_LEN(str1) == RSTRING_LEN(str2)
-	&& rb_str_cmp(str1, str2) == 0) {
-	return Qtrue;
-    }
-
-    return Qfalse;
+    return ossl_digest_class_by_name(RSTRING_PTR(name));
 }
 
-static VALUE
-ossl_digest_name(VALUE self)
+const EVP_MD *
+GetDigestPtr(VALUE obj)
 {
-    EVP_MD_CTX *ctx;
+    EVP_MD *md = get_digest_base_metadata(rb_obj_class(obj));
 
-    GetDigest(self, ctx);
+    if (md == NULL) {
+        ossl_raise(rb_eRuntimeError, "Unsupported digest algorithm");
+    }
 
-    return rb_str_new2(EVP_MD_name(EVP_MD_CTX_md(ctx)));
+    return md;
 }
 
-static VALUE
-ossl_digest_size(VALUE self)
+VALUE
+ossl_digest_new(const EVP_MD *md)
 {
-    EVP_MD_CTX *ctx;
-
-    GetDigest(self, ctx);
+    VALUE klass = ossl_digest_class_by_name(EVP_MD_name(md));
 
-    return INT2NUM(EVP_MD_CTX_size(ctx));
+    return rb_funcall(klass, id_new, 0);
 }
 
-/*
- * INIT
- */
 void
-Init_ossl_digest()
+Init_ossl_digest(void)
 {
+    int i;
+    VALUE klass;
+    VALUE rb_cDigest_Base;
+
+    id_evp_md = rb_intern("evp_md");
+    id_new = rb_intern("new");
+
+    rb_define_module_function(mOSSL, "Digest", rb_ossl_s_Digest, 1);
+
     mDigest = rb_define_module_under(mOSSL, "Digest");
-	
     eDigestError = rb_define_class_under(mDigest, "DigestError", eOSSLError);
-	
-    cDigest = rb_define_class_under(mDigest, "Digest", rb_cObject);
-	
-    rb_define_alloc_func(cDigest, ossl_digest_alloc);
-    rb_define_singleton_method(cDigest, "digest", ossl_digest_s_digest, 2);
-    rb_define_singleton_method(cDigest, "hexdigest", ossl_digest_s_hexdigest, 2);
-	
-    rb_define_method(cDigest, "initialize", ossl_digest_initialize, -1);
-    rb_define_method(cDigest, "reset", ossl_digest_reset, 0);
-    
-    rb_define_copy_func(cDigest, ossl_digest_copy);
-    
-    rb_define_method(cDigest, "digest", ossl_digest_digest, 0);
-    rb_define_method(cDigest, "hexdigest", ossl_digest_hexdigest, 0);
-    rb_define_alias(cDigest, "inspect", "hexdigest");
-    rb_define_alias(cDigest, "to_s", "hexdigest");
-    
-    rb_define_method(cDigest, "update", ossl_digest_update, 1);
-    rb_define_alias(cDigest, "<<", "update");
-    
-    rb_define_method(cDigest, "==", ossl_digest_equal, 1);
-    
-    rb_define_method(cDigest, "name", ossl_digest_name, 0);
-    rb_define_method(cDigest, "size", ossl_digest_size, 0);
+
+    rb_require("digest");
+    rb_cDigest_Base = rb_path2class("Digest::Base");
+
+    for (i = 0; i < sizeof(digest_names); i++) {
+        const char *name = digest_names[i];
+        const EVP_MD *md = EVP_get_digestbyname(name);
+
+        if (md == NULL)
+            continue;
+
+        klass = rb_define_class_under(mDigest, name, rb_cDigest_Base);
+
+        rb_ivar_set(klass, id_evp_md, Data_Wrap_Struct(rb_cObject, 0, 0, (void *)md));
+
+        rb_define_const(klass, "DIGEST_LENGTH", INT2NUM(EVP_MD_size(md)));
+        rb_define_const(klass, "BLOCK_LENGTH",  INT2NUM(EVP_MD_block_size(md)));
+
+        rb_define_alloc_func(klass, rb_ossl_digest_base_alloc);
+
+        rb_define_method(klass, "initialize", rb_ossl_digest_base_init, -1);
+        rb_define_method(klass, "initialize_copy",  rb_ossl_digest_base_copy, 1);
+        rb_define_method(klass, "update", rb_ossl_digest_base_update, 1);
+        rb_define_method(klass, "digest", rb_ossl_digest_base_digest, 0);
+
+        rb_define_method(klass, "name", rb_ossl_digest_base_name, 0);
+        rb_define_method(klass, "size", rb_ossl_digest_base_size, 0);
+        rb_define_method(klass, "length", rb_ossl_digest_base_size, 0);
+    }
 }
Index: ext/openssl/lib/openssl.rb
===================================================================
RCS file: /src/ruby/ext/openssl/lib/openssl.rb,v
retrieving revision 1.1
diff -u -r1.1 openssl.rb
--- ext/openssl/lib/openssl.rb	23 Jul 2003 16:11:29 -0000	1.1
+++ ext/openssl/lib/openssl.rb	13 Oct 2006 12:03:37 -0000
@@ -18,7 +18,6 @@
 
 require 'openssl/bn'
 require 'openssl/cipher'
-require 'openssl/digest'
 require 'openssl/ssl'
 require 'openssl/x509'
 
Index: ext/openssl/lib/openssl/digest.rb
===================================================================
RCS file: ext/openssl/lib/openssl/digest.rb
diff -N ext/openssl/lib/openssl/digest.rb
--- ext/openssl/lib/openssl/digest.rb	8 May 2006 00:11:59 -0000	1.3
+++ /dev/null	1 Jan 1970 00:00:00 -0000
@@ -1,49 +0,0 @@
-=begin
-= $RCSfile: digest.rb,v $ -- Ruby-space predefined Digest subclasses
-
-= Info
-  'OpenSSL for Ruby 2' project
-  Copyright (C) 2002  Michal Rokos <m.rokos / sh.cvut.cz>
-  All rights reserved.
-
-= Licence
-  This program is licenced under the same licence as Ruby.
-  (See the file 'LICENCE'.)
-
-= Version
-  $Id: digest.rb,v 1.3 2006/05/08 00:11:59 gotoyuzo Exp $
-=end
-
-##
-# Should we care what if somebody require this file directly?
-#require 'openssl'
-
-module OpenSSL
-  module Digest
-
-    alg = %w(DSS DSS1 MD2 MD4 MD5 MDC2 RIPEMD160 SHA SHA1)
-    if OPENSSL_VERSION_NUMBER > 0x00908000
-      alg += %w(SHA224 SHA256 SHA384 SHA512)
-    end
-
-    alg.each{|name|
-      klass = Class.new(Digest){
-        define_method(:initialize){|*data|
-          if data.length > 1
-            raise ArgumentError,
-              "wrong number of arguments (#{data.length} for 1)"
-          end
-          super(name, data.first)
-        }
-      }
-      singleton = (class <<klass; self; end)
-      singleton.class_eval{
-        define_method(:digest){|data| Digest.digest(name, data) }
-        define_method(:hexdigest){|data| Digest.hexdigest(name, data) }
-      }
-      const_set(name, klass)
-    }
-
-  end # Digest
-end # OpenSSL
-
Index: test/openssl/test_digest.rb
===================================================================
RCS file: /src/ruby/test/openssl/test_digest.rb,v
retrieving revision 1.3
diff -u -r1.3 test_digest.rb
--- test/openssl/test_digest.rb	8 May 2006 00:12:00 -0000	1.3
+++ test/openssl/test_digest.rb	13 Oct 2006 12:03:38 -0000
@@ -9,7 +9,7 @@
 
 class OpenSSL::TestDigest < Test::Unit::TestCase
   def setup
-    @d1 = OpenSSL::Digest::Digest::new("MD5")
+    @d1 = OpenSSL::Digest("MD5").new
     @d2 = OpenSSL::Digest::MD5.new
     @md = Digest::MD5.new
     @data = "DATA"