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.
- data/.gemtest +0 -0
- data/Rakefile +29 -0
- data/digest-ed2k.gemspec +17 -0
- data/lib/digest/ed2k.rb +193 -0
- data/t/zeroes.rb +41 -0
- metadata +69 -0
data/.gemtest
ADDED
|
File without changes
|
data/Rakefile
ADDED
|
@@ -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
|
data/digest-ed2k.gemspec
ADDED
|
@@ -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
|
data/lib/digest/ed2k.rb
ADDED
|
@@ -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
|
data/t/zeroes.rb
ADDED
|
@@ -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:
|