Fix up Rubinius specific library specs.
[rbx.git] / lib / digest.rb
blob310030756a1b504a64d6a4dc103549a6229bab49
1 module Digest
3   # Creates a new Digest Class of +name+ using the C functions
4   # +init_function+, +update_function+ and +finish_function+.  The C
5   # algorithm's state is allocated from a C struct of size +struct_size+.
6   # The algorithm's block length is set to +block_length+ and digest output
7   # length to +digest_length+.
8   #
9   # Before calling, the C implementation of the algorithm needs to be loaded.
10   #
11   # The C functions are attached using the following signatures:
12   # [+init_function+] [:pointer], :void
13   # [+update_function+] [:pointer, :string, :int], :void
14   # [+finish_function+] [:pointer, :string], :void
15   #
16   # See digest/md5.rb for an example of usage.
17   def self.create(name, init_function, update_function, finish_function,
18                   struct_size, block_length, digest_length)
19     klass = ::Class.new Digest::Instance
20     Digest.const_set name, klass
22     context = ::Class.new FFI::Struct
23     # HACK FFI doesn't understand C arrays
24     context.instance_variable_set :@size, struct_size
25     klass.const_set :Context, context
27     klass.attach_function init_function, :digest_init, [:pointer], :void
28     klass.attach_function update_function, :digest_update,
29                           [:pointer, :string, :int], :void
30     klass.attach_function finish_function, :digest_finish,
31                           [:pointer, :string], :void
33     klass.const_set :BLOCK_LENGTH, block_length
34     klass.const_set :DIGEST_LENGTH, digest_length
36     klass.extend Digest::Class
38     klass
39   end
41   # Generates a hex-encoded version of a given +string+
42   def self.hexencode(string)
43     hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
45     result = ' '*(string.length * 2)
47     string.split('').to_a.each_with_index do |byte,i|
48       byte = byte[0]
49       result[i + i]     = hex[byte >> 4];
50       result[i + i + 1] = hex[byte & 0x0f];
51     end
53     result
54   end
56   # This module provides instance methods for a digest implementation
57   # object to calculate message digest values.
58   class Instance
60     def initialize
61       @context = nil
62       reset
63     end
65     def initialize_copy(other)
66       @context = @context.dup
67     end
69     # call-seq:
70     #     digest_obj.update(string) -> digest_obj
71     #     digest_obj << string -> digest_obj
72     #
73     # Updates the digest using a given +string+ and returns self.
74     #
75     # The update method and the left-shift operator are overridden by
76     # each implementation subclass. (One should be an alias for the
77     # other)
78     def update(string)
79       self.class.digest_update @context.pointer, string, string.length
80       self
81     end
82     alias :<< :update
84     # call-seq:
85     #     digest_obj.instance_eval { finish } -> digest_obj
86     #
87     # Finishes the digest and returns the resulting hash value.
88     #
89     # This method is overridden by each implementation subclass and often
90     # made private, because some of those subclasses may leave internal
91     # data uninitialized.  Do not call this method from outside.  Use
92     # #digest! instead, which ensures that internal data be reset for
93     # security reasons.
94     def finish
95       value = ' ' * digest_length
96       self.class.digest_finish @context.pointer, value
97       @context.free
98       @context = nil
99       value
100     end
102     # call-seq:
103     #     digest_obj.reset -> digest_obj
104     #
105     # Resets the digest to the initial state and returns self.
106     #
107     # This method is overridden by each implementation subclass.
108     def reset
109       @context.free if @context
110       @context = self.class::Context.new
111       self.class.digest_init @context.pointer
112     end
114     # call-seq:
115     #     digest_obj.new -> another_digest_obj
116     #
117     # Returns a new, initialized copy of the digest object.  Equivalent
118     # to digest_obj.clone.reset.
119     def new
120       self.clone.reset
121     end
123     # call-seq:
124     #     digest_obj.digest -> string
125     #     digest_obj.digest(string) -> string
126     #
127     # If none is given, returns the resulting hash value of the digest,
128     # keeping the digest's state.
129     #
130     # If a +string+ is given, returns the hash value for the given
131     # +string+, resetting the digest to the initial state before and
132     # after the process.
133     def digest(string=nil)
134       if (string)
135         reset
136         update(string)
137         value = finish
138         reset
139       else
140         clone = self.clone
141         value = clone.finish
142         clone.reset
143       end
145       value
146     end
148     # call-seq:
149     #     digest_obj.digest! -> string
150     #
151     # Returns the resulting hash value and resets the digest to the
152     # initial state.
153     def digest!
154       value = finish
155       reset
156       value
157     end
159     # call-seq:
160     #     digest_obj.hexdigest -> string
161     #     digest_obj.hexdigest(string) -> string
162     #
163     # If none is given, returns the resulting hash value of the digest in
164     # a hex-encoded form, keeping the digest's state.
165     #
166     # If a +string+ is given, returns the hash value for the given
167     # +string+ in a hex-encoded form, resetting the digest to the initial
168     # state before and after the process.
169     def hexdigest(data=nil)
170       Digest.hexencode(digest(data))
171     end
173     # call-seq:
174     #     digest_obj.hexdigest! -> string
175     #
176     # Returns the resulting hash value and resets the digest to the
177     # initial state.
178     def hexdigest!
179       result = hexdigest
180       reset
181       result
182     end
184     # call-seq:
185     #     digest_obj.to_s -> string
186     #
187     # Returns digest_obj.hexdigest.
188     def to_s
189       hexdigest
190     end
192     # call-seq:
193     #     digest_obj.inspect -> string
194     #
195     # Creates a printable version of the digest object.
196     def inspect
197       "#<#{self.class}: #{self.hexdigest}>"
198     end
200     # call-seq:
201     #     digest_obj == another_digest_obj -> boolean
202     #     digest_obj == string -> boolean
203     #
204     # If a string is given, checks whether it is equal to the hex-encoded
205     # digest value of the digest object.  If another digest instance is
206     # given, checks whether they have the same digest value.  Otherwise
207     # returns false.
208     def ==(other)
209       return hexdigest == other.hexdigest if other.is_a? Digest::Instance
210       to_s == other.to_str
211     end
213     # call-seq:
214     #     digest_obj.digest_length -> integer
215     #
216     # Returns the length of the hash value of the digest.
217     #
218     # This method should be overridden by each implementation subclass.
219     # If not, digest_obj.digest.length is returned.
220     def digest_length
221       self.class::DIGEST_LENGTH
222     end
224     # call-seq:
225     #     digest_obj.length -> integer
226     #     digest_obj.size -> integer
227     #
228     # Returns digest_obj.digest_length.
229     alias :length :digest_length
230     alias :size   :digest_length
232     # call-seq:
233     #     digest_obj.block_length -> integer
234     #
235     # Returns the block length of the digest.
236     #
237     # This method is overridden by each implementation subclass.
238     def block_length
239       self.class::BLOCK_LENGTH
240     end
242   end
244   module Class
245     # call-seq:
246     #     Digest::Class.digest(string, #parameters) -> hash_string
247     #
248     # Returns the hash value of a given +string+.  This is equivalent to
249     # Digest::Class.new(#parameters).digest(string), where extra
250     # +parameters+, if any, are passed through to the constructor and the
251     # +string+ is passed to #digest.
252     def digest(data)
253       raise ArgumentError, "no data given" unless data
254       self.new.digest(data)
255     end
257     # Returns the hex-encoded digest value of the given +data+.
258     def hexdigest(data = nil)
259       raise ArgumentError, 'no data given' if data.nil?
260       new.hexdigest data
261     end
263   end