digest-ed2k 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: