class Digest::ED2k

{Digest::Class Digest} subclass that calculates an ed2k hash.

The implemented algorithm is as described on wiki.anidb.net/w/Ed2k, using the “red code” method (appending null to hashlist).

Uses {OpenSSL::Digest OpenSSL::Digest::MD4} internally for the MD4 hashing.

@note Due to ed2k’s use of 9500KB chunks, this object can be up to around

10MB large in memory, not counting OpenSSL::Digest::MD4's state.

Constants

CHUNK_SIZE

Chunk size of the ed2k hash, 9500KB.

VERSION

The version

Public Class Methods

digest(data) click to toggle source

Calculates and returns the {#digest} on data.

# File lib/digest/ed2k.rb, line 175
def self.digest (data)
    return new(data).digest
end
file(path) click to toggle source

The same as calling {#file}(path) on a new object.

# File lib/digest/ed2k.rb, line 190
def self.file(path)
    return new.file path
end
hexdigest(data) click to toggle source

Calculates and returns the {#hexdigest} on data.

# File lib/digest/ed2k.rb, line 180
def self.hexdigest (data)
    return new(data).hexdigest
end
io(io) click to toggle source

The same as calling {#io}(io) on a new object.

# File lib/digest/ed2k.rb, line 185
def self.io(io)
    return new.io io
end
new(initial_chunk = nil) click to toggle source

Creates a reset object. @param initial_chunk optionally appends data to the new object

# File lib/digest/ed2k.rb, line 26
def initialize (initial_chunk = nil)
    @md4 = OpenSSL::Digest::MD4.new
    self.reset
    self << initial_chunk if initial_chunk
end

Public Instance Methods

<<(data)
Alias for: update
digest(str = nil) click to toggle source

{#finalize}s the digest and returns it.

@note Due to how ed2k hashes work, once the digest has been obtained,

the object must be {#reset} before being reused. The {#digest!} family
of methods automatically resets the object after being called.

@param str if present and non-nil, will cause digest to reset self,

update it with `str` and return the new digest.

@return [String] the raw digest

# File lib/digest/ed2k.rb, line 92
def digest(str = nil)
    unless str
        self.finalize
        @md4.digest
    else
        reset
        self << str
        digest
    end
end
digest!() click to toggle source

(see digest) @note calls {#reset} on self after obtaining digest

# File lib/digest/ed2k.rb, line 105
def digest!
    ret = digest
    reset
    return ret
end
file(path) click to toggle source

Opens the file pointed by path and calls {#io} with it.

@param [String] path path to a file that will be read @return self for convenient chaining.

# File lib/digest/ed2k.rb, line 50
def file (path)
    File.open(path) do |f|
        return self.io f
    end
end
finalize() click to toggle source

Finalizes the digest and prevents any new data from being added to it. This is automatically called by the {#digest} family of methods. The only way to unlock the object is to {#reset} it.

@return self for convenient chaining.

# File lib/digest/ed2k.rb, line 141
def finalize
    unless @finalized
        if @rounds > 0
            @md4 << OpenSSL::Digest::MD4.digest(@buf)
        else
            @md4.reset
            @md4 << @buf
        end
        @buf = nil
        @finalized = true
    end
    return self
end
hexdigest(str = nil) click to toggle source

{include:#digest} @note (see digest) @param (see digest) @return [String] the digest as a hexadecimal string

# File lib/digest/ed2k.rb, line 115
def hexdigest(str = nil)
    unless str
        self.finalize
        @md4.hexdigest
    else
        reset
        self << str
        hexdigest
    end
end
hexdigest!() click to toggle source

{include:#hexdigest} @param (see hexdigest) @return (see hexdigest) @note (see digest!)

# File lib/digest/ed2k.rb, line 130
def hexdigest!
    ret = hexdigest
    reset
    return ret
end
inspect() click to toggle source

Override for {Object#inspect Object’s inspect}

# File lib/digest/ed2k.rb, line 156
def inspect
    return "#<ed2k unfinalized>" unless @finalized
    return "#<ed2k hash=\"#{digest}\">"
end
io(io) click to toggle source

Reads the contents of ‘io`, 9500KB at a time, until EOF, and add to the digest.

@param [IO] io the {IO} to be read @return self for convenient chaining.

# File lib/digest/ed2k.rb, line 37
def io (io)
    # why do I have to use a while instead of an each_chunk or sth
    buf = ""
    while io.read CHUNK_SIZE, buf
        self << buf
    end
    return self
end
reset() click to toggle source

Resets the state; effectively the same as constructing a new object. @return self for convenient chaining.

# File lib/digest/ed2k.rb, line 58
def reset
    @md4.reset
    @buf = ""
    @rounds = 0
    @finalized = false
    return self
end
update(data) click to toggle source

Append ‘data` to the digest. Will raise an {ArgumentError} if the object has been {#finalize}d before.

Every 9500KB of accumulated data will be hashed as per the ed2k algorithm.

@param [String] data the string to be appended to the digest @return self for convenient chaining.

# File lib/digest/ed2k.rb, line 73
def update (data)
    if @finalized
        raise ArgumentError.new("Can't add to an ed2k hash after finalizing. Call reset if you want to calculate a new hash.")
    end
    @buf += data
    _sync
    return self
end
Also aliased as: <<

Private Instance Methods

_sync() click to toggle source

When the internal buffer exceeds {CHUNK_SIZE}, feed CHUNK_SIZE chunks to the MD4 hashing function until the buffer is below CHUNK_SIZE of length.

# File lib/digest/ed2k.rb, line 165
def _sync
    while @buf.length >= CHUNK_SIZE
        @md4 << OpenSSL::Digest::MD4.digest(@buf[0...CHUNK_SIZE])
        @buf = @buf[CHUNK_SIZE...@buf.length] || ""
        @rounds += 1
    end
end