digest-ed2k 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
@@ -0,0 +1,29 @@
1
+ require 'rake/testtask'
2
+ require 'rdoc/task'
3
+
4
+ spec = eval(File.read("digest-ed2k.gemspec"))
5
+ source = FileList["lib/**/*.rb"]
6
+
7
+ begin
8
+ # why is require documented to return true/false
9
+ # if it raises LoadError when it should return false
10
+ require 'yard'
11
+
12
+ YARD::Rake::YardocTask.new do |yd|
13
+ yd.files = source
14
+ end
15
+
16
+ task :doc => :yard
17
+ rescue LoadError
18
+ task :doc => :rdoc
19
+ end
20
+
21
+ RDoc::Task.new do |rd|
22
+ rd.rdoc_files = source
23
+ end
24
+
25
+ Rake::TestTask.new do |t|
26
+ t.libs << 't'
27
+ t.test_files = spec.test_files
28
+ t.verbose = false
29
+ end
@@ -0,0 +1,17 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'digest-ed2k'
3
+ s.version = '1.0.0'
4
+ s.date = '2012-04-11'
5
+ s.license = 'ISC'
6
+ s.summary = 'Digest module for calculating ED2K hashes.'
7
+ s.description = s.summary + ' Uses openssl for MD4 hashing.'
8
+ s.authors = ['Kovensky']
9
+ s.email = ['diogomfranco@gmail.com']
10
+ s.test_files = Dir["t/*.rb"]
11
+ s.files = Dir["lib/**/*.rb"] + s.test_files +
12
+ ["#{s.name}.gemspec","Rakefile", ".gemtest"]
13
+ s.require_path = 'lib'
14
+ s.homepage = 'https://github.com/Kovensky/digest-ed2k'
15
+
16
+ s.add_development_dependency 'yard'
17
+ end
@@ -0,0 +1,193 @@
1
+ # Implementation of the ed2k hash algorithm.
2
+
3
+ # @author Kovensky (mailto:diogomfranco@gmail.com)
4
+
5
+ require 'openssl'
6
+ require 'digest'
7
+
8
+ # {Digest::Class Digest} subclass that calculates an ed2k hash.
9
+ #
10
+ # The implemented algorithm is as described on http://wiki.anidb.net/w/Ed2k,
11
+ # using the "red code" method (appending null to hashlist).
12
+ #
13
+ # Uses {OpenSSL::Digest OpenSSL::Digest::MD4} internally for the MD4 hashing.
14
+ #
15
+ # @note Due to ed2k's use of 9500KB chunks, this object can be up to around
16
+ # 10MB large in memory, not counting OpenSSL::Digest::MD4's state.
17
+ class Digest::ED2k < Digest::Class
18
+ # The version
19
+ VERSION = '1.0.0'
20
+
21
+ # Chunk size of the ed2k hash, 9500KB.
22
+ CHUNK_SIZE = 9728000
23
+
24
+ # Creates a reset object.
25
+ # @param initial_chunk optionally appends data to the new object
26
+ def initialize (initial_chunk = nil)
27
+ @md4 = OpenSSL::Digest::MD4.new
28
+ self.reset
29
+ self << initial_chunk if initial_chunk
30
+ end
31
+
32
+ # Reads the contents of `io`, 9500KB at a time,
33
+ # until EOF, and add to the digest.
34
+ #
35
+ # @param [IO] io the {IO} to be read
36
+ # @return self for convenient chaining.
37
+ def io (io)
38
+ # why do I have to use a while instead of an each_chunk or sth
39
+ buf = ""
40
+ while io.read CHUNK_SIZE, buf
41
+ self << buf
42
+ end
43
+ return self
44
+ end
45
+
46
+ # Opens the file pointed by path and calls {#io} with it.
47
+ #
48
+ # @param [String] path path to a file that will be read
49
+ # @return self for convenient chaining.
50
+ def file (path)
51
+ File.open(path) do |f|
52
+ return self.io f
53
+ end
54
+ end
55
+
56
+ # Resets the state; effectively the same as constructing a new object.
57
+ # @return self for convenient chaining.
58
+ def reset
59
+ @md4.reset
60
+ @buf = ""
61
+ @rounds = 0
62
+ @finalized = false
63
+ return self
64
+ end
65
+
66
+ # Append `data` to the digest. Will raise an {ArgumentError}
67
+ # if the object has been {#finalize}d before.
68
+ #
69
+ # Every 9500KB of accumulated data will be hashed as per the ed2k algorithm.
70
+ #
71
+ # @param [String] data the string to be appended to the digest
72
+ # @return self for convenient chaining.
73
+ def update (data)
74
+ if @finalized
75
+ raise ArgumentError.new("Can't add to an ed2k hash after finalizing. Call reset if you want to calculate a new hash.")
76
+ end
77
+ @buf += data
78
+ _sync
79
+ return self
80
+ end
81
+ alias << update
82
+
83
+ # {#finalize}s the digest and returns it.
84
+ #
85
+ # @note Due to how ed2k hashes work, once the digest has been obtained,
86
+ # the object must be {#reset} before being reused. The {#digest!} family
87
+ # of methods automatically resets the object after being called.
88
+ #
89
+ # @param str if present and non-nil, will cause digest to reset self,
90
+ # update it with `str` and return the new digest.
91
+ # @return [String] the raw digest
92
+ def digest(str = nil)
93
+ unless str
94
+ self.finalize
95
+ @md4.digest
96
+ else
97
+ reset
98
+ self << str
99
+ digest
100
+ end
101
+ end
102
+
103
+ # (see #digest)
104
+ # @note calls {#reset} on self after obtaining digest
105
+ def digest!
106
+ ret = digest
107
+ reset
108
+ return ret
109
+ end
110
+
111
+ # {include:#digest}
112
+ # @note (see #digest)
113
+ # @param (see #digest)
114
+ # @return [String] the digest as a hexadecimal string
115
+ def hexdigest(str = nil)
116
+ unless str
117
+ self.finalize
118
+ @md4.hexdigest
119
+ else
120
+ reset
121
+ self << str
122
+ hexdigest
123
+ end
124
+ end
125
+
126
+ # {include:#hexdigest}
127
+ # @param (see #hexdigest)
128
+ # @return (see #hexdigest)
129
+ # @note (see #digest!)
130
+ def hexdigest!
131
+ ret = hexdigest
132
+ reset
133
+ return ret
134
+ end
135
+
136
+ # Finalizes the digest and prevents any new data from being added
137
+ # to it. This is automatically called by the {#digest} family of methods.
138
+ # The only way to unlock the object is to {#reset} it.
139
+ #
140
+ # @return self for convenient chaining.
141
+ def finalize
142
+ unless @finalized
143
+ if @rounds > 0
144
+ @md4 << OpenSSL::Digest::MD4.digest(@buf)
145
+ else
146
+ @md4.reset
147
+ @md4 << @buf
148
+ end
149
+ @buf = nil
150
+ @finalized = true
151
+ end
152
+ return self
153
+ end
154
+
155
+ # Override for {Object#inspect Object's inspect}
156
+ def inspect
157
+ return "#<ed2k unfinalized>" unless @finalized
158
+ return "#<ed2k hash=\"#{digest}\">"
159
+ end
160
+
161
+ private
162
+ # When the internal buffer exceeds {CHUNK_SIZE}, feed CHUNK_SIZE chunks
163
+ # to the MD4 hashing function until the buffer is below CHUNK_SIZE
164
+ # of length.
165
+ def _sync
166
+ while @buf.length >= CHUNK_SIZE
167
+ @md4 << OpenSSL::Digest::MD4.digest(@buf[0...CHUNK_SIZE])
168
+ @buf = @buf[CHUNK_SIZE...@buf.length] || ""
169
+ @rounds += 1
170
+ end
171
+ end
172
+
173
+ public
174
+ # Calculates and returns the {#digest} on data.
175
+ def self.digest (data)
176
+ return new(data).digest
177
+ end
178
+
179
+ # Calculates and returns the {#hexdigest} on data.
180
+ def self.hexdigest (data)
181
+ return new(data).hexdigest
182
+ end
183
+
184
+ # The same as calling {#io}(io) on a new object.
185
+ def self.io(io)
186
+ return new.io io
187
+ end
188
+
189
+ # The same as calling {#file}(path) on a new object.
190
+ def self.file(path)
191
+ return new.file path
192
+ end
193
+ end
@@ -0,0 +1,41 @@
1
+ #require 'minitest/autorun'
2
+ require 'test/unit'
3
+ require 'digest/ed2k'
4
+
5
+ class Zeroes < Test::Unit::TestCase
6
+ def test_zero_length_file
7
+ assert_equal '31d6cfe0d16ae931b73c59d7e0c089c0',
8
+ Digest::ED2k.hexdigest('')
9
+ end
10
+
11
+ def add_by_random_chunks (str, ed2k = Digest::ED2k.new)
12
+ loops = 2000 # don't let bad random numbers freeze the test
13
+ while str.length > 1 && loops > 0
14
+ r = rand(str.length)
15
+ ed2k << str[0...r]
16
+ str = str[r...str.length]
17
+ loops -= 1
18
+ end
19
+ ed2k << str
20
+ end
21
+
22
+ def test_single_chunk_zero_file
23
+ assert_equal 'fc21d9af828f92a8df64beac3357425d',
24
+ add_by_random_chunks("\0" * Digest::ED2k::CHUNK_SIZE).hexdigest
25
+ end
26
+
27
+ def test_two_chunk_zero_file
28
+ assert_equal '114b21c63a74b6ca922291a11177dd5c',
29
+ add_by_random_chunks("\0" * (2 * Digest::ED2k::CHUNK_SIZE)).hexdigest
30
+ end
31
+
32
+ def test_two_and_half_chunk_zero_file
33
+ assert_equal 'a4b0b40d5b13bb045b0ce2a8fad8b393',
34
+ add_by_random_chunks("\0" * (5 * Digest::ED2k::CHUNK_SIZE / 2)).hexdigest
35
+ end
36
+
37
+ def test_three_and_half_chunk_by_object_random
38
+ assert_equal '57d514e8e464257aca18e50e47194e42',
39
+ add_by_random_chunks("\0" * (7 * Digest::ED2k::CHUNK_SIZE / 2)).hexdigest
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: digest-ed2k
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kovensky
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: yard
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: Digest module for calculating ED2K hashes. Uses openssl for MD4 hashing.
31
+ email:
32
+ - diogomfranco@gmail.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - lib/digest/ed2k.rb
38
+ - t/zeroes.rb
39
+ - digest-ed2k.gemspec
40
+ - Rakefile
41
+ - .gemtest
42
+ homepage: https://github.com/Kovensky/digest-ed2k
43
+ licenses:
44
+ - ISC
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 1.8.21
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Digest module for calculating ED2K hashes.
67
+ test_files:
68
+ - t/zeroes.rb
69
+ has_rdoc: