# Copyright (C) 2005-2009, Parrot Foundation.
#
# Parrot MD5 library; Nick Glencross <nickg@glencros.demon.co.uk>
#                     Improvements from Leo and Jens Rieks
#
# Based on md5.c, from md5sum
#           written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995.

=head1 NAME

MD5.pir - calculates MD5 checksums

=head1 SYNOPSIS

  load_bytecode "Digest/MD5.pbc"
  $P0 = _md5sum("foo")
  _md5_print($P0)

or

  load_bytecode "Digest/MD5.pbc"
  $P0 = _md5sum("bar")
  $S0 = _md5_hex($P0)

or using the Object interface:

  load_bytecode "Digest/MD5.pbc"
  $P0 = new ["Digest";"MD5"]
  $P1 = $P0."md5sum"("baz")
  $P0."md5_print"()

=head1 DESCRIPTION

This is a pure Parrot MD5 hash routine. You should run it with the JIT
core if possible.

=head1 SUBROUTINES

=head2 _md5sum

Pass in a string, returns an Integer array with the result.

=head2 _md5_hex

Pass it the Integer array from _md5sum to get the checksum as string.

=head2 _md5_print

Pass it the Integer array to print the checksum.

=head1 METHODS

The MD5 class defines the following subroutines, which are very similar to the subroutine alternatives:

=cut

.HLL 'parrot'

.loadlib 'bit_ops'

###########################################################################
# Interface definition

# Export subroutines to globals
.sub onload :load
    .local pmc f
    f = get_hll_global ['Digest'], '_md5sum'
    set_global "_md5sum", f
    f = get_hll_global ['Digest'], '_md5_hex'
    set_global "_md5_hex", f
    f = get_hll_global ['Digest'], '_md5_print'
    set_global "_md5_print", f
.end

.namespace ['Digest';'MD5']

# Create Object Oriented interface
.sub '' :init :load :anon
    $P0 = newclass ['Digest';'MD5']
    $P0.'add_attribute'('context')
.end

=head2 C<md5sum( str )>

Pass in a string, returns an Integer array with the result, and stores the result in an attribute.

=cut

.sub 'md5sum' :method
    .param string str
    $P0 = _md5sum (str)
    setattribute self, 'context', $P0
    .return ($P0)
.end

=head2 C<md5_hex( )>

Uses the Integer array from _md5sum to return the checksum as string.

=cut

.sub 'md5_hex' :method
    $P0 = getattribute self, 'context'
    $S0 = _md5_hex($P0)
    .return ($S0)
.end

=head2 C<md5_print( )>

Uses the Integer array from _md5sum to print the checksum. Returns the checksum as a string.

=cut

.sub 'md5_print' :method
    $P0 = getattribute self, 'context'
    $S0 = _md5_hex($P0)
    say $S0
    .return ($S0)
.end

###########################################################################
# Main backend entry point

.namespace ["Digest"]

.sub _md5sum
    .param string str

    .local pmc context
    context = new 'FixedIntegerArray'
    context = 4

    .local pmc buffer
    buffer = _md5_create_buffer (str)

    _md5_init (context)
    _md5_process_buffer (context, buffer)

    .return (context)
.end


###########################################################################
# Low-level macros used in MD5

.macro FF (b,c,d)
    tmp  = .c ~ .d
    tmp &= .b
    tmp ~= .d
.endm

.macro FH (b,c,d)
    tmp  = .b ~ .c
    tmp ~= .d
.endm

.macro FI (b,c,d)
    tmp  = ~.d
    tmp |= .b
    tmp ~= .c
.endm

###########################################################################
# Higher level MD5 operations

.macro common (a, b, k, s, T)
    .a += tmp
    .a += .T
    tmp = .k + idx
    tmp = buffer[tmp]
    .a += tmp
    .a &= 0xffffffff
    tmp = rot .a, .s, 32
    .a  = .b + tmp
    .a &= 0xffffffff
.endm


.macro OP1 (aa,bb,cc,dd, kk, ss, TT)
    .FF     (.bb,.cc,.dd)
    .common (.aa, .bb, .kk, .ss, .TT)
.endm

.macro OP2 (aa,bb,cc,dd, kk, ss, TT)
    .FF     (.dd,.bb,.cc)
    .common (.aa, .bb, .kk, .ss, .TT)
.endm

.macro OP3 (aa,bb,cc,dd, kk, ss, TT)
    .FH     (.bb,.cc,.dd)
    .common (.aa, .bb, .kk, .ss, .TT)
.endm

.macro OP4 (aa,bb,cc,dd, kk, ss, TT)
    .FI     (.bb,.cc,.dd)
    .common (.aa, .bb, .kk, .ss, .TT)
.endm

###########################################################################

# Swap the bytes which make up a word

.macro swap (w)
     $I10 = .w & 0x000000ff
     $I11 = .w & 0x0000ff00
     $I12 = .w & 0x00ff0000
     $I13 = .w & 0xff000000

     $I10 = $I10 <<  24
     $I11 = $I11 <<  8
     $I12 = $I12 >>> 8
     $I13 = $I13 >>> 24

     $I10 |= $I11
     $I10 |= $I12
     $I10 |= $I13

     # For 64-bit architectures
     .w = $I10 & 0xffffffff
.endm

###########################################################################

# Set the initial MD5 constants

.sub _md5_init
    .param pmc context

    # Initial MD5 constants
    context[0] = 0x67452301
    context[1] = 0xefcdab89
    context[2] = 0x98badcfe
    context[3] = 0x10325476

.end

###########################################################################
# Create an internal scratchpad buffer

.sub _md5_create_buffer
    .param string str

    .local pmc buffer
    buffer = new 'FixedIntegerArray'

    .local int counter
    .local int subcounter
    .local int slow_counter

    .local int word, len

     len = length str

     $I1 = len - 1

     # Work out how many words to allocate
     .local int words
     words  = len + 8
     words |= 63
     inc words
     words /= 4

     buffer = words

     word         = 0
     counter      = 0
     subcounter   = 0
     slow_counter = 0

create_buffer_loop:

     $I5 = counter + subcounter

     if $I5 > len goto create_buffer_break

     # MD5 pad character, which goes last
     $I4 = 0x80

     if $I5 > $I1 goto string_char
     $I4 = ord str, $I5

string_char:

     word <<= 8
     word  |= $I4

     inc subcounter
     if subcounter != 4 goto create_buffer_loop

     .swap (word)

     buffer[slow_counter] = word

     word       = 0
     counter   += 4
     subcounter = 0
     inc slow_counter

     goto create_buffer_loop

create_buffer_break:

     # Check for a partial word

     if subcounter == 0 goto complete
     subcounter = 4 - subcounter
     $I0    = 8*subcounter
     word <<= $I0

     .swap (word)

     buffer[slow_counter] = word

complete:

     # The number of bits in the string go into the last two words

     $I0 = len >>> 29
     dec words
     buffer[words] = $I0

     $I0 = len << 3
     dec words
     buffer[words] = $I0

     .return (buffer)

.end

###########################################################################

.sub _md5_process_buffer
    .param pmc  context
    .param pmc  buffer

    .local int A, B, C, D
    .local int A_save, B_save, C_save, D_save

    .local int tmp, idx, len

    idx = 0
    len = elements buffer

    A = context[0]
    B = context[1]
    C = context[2]
    D = context[3]

loop:

    A_save = A
    B_save = B
    C_save = C
    D_save = D

    # Round 1.
    .OP1 (A, B, C, D, 0,  7, 0xd76aa478)
    .OP1 (D, A, B, C, 1, 12, 0xe8c7b756)
    .OP1 (C, D, A, B, 2, 17, 0x242070db)
    .OP1 (B, C, D, A, 3, 22, 0xc1bdceee)
    .OP1 (A, B, C, D, 4,  7, 0xf57c0faf)
    .OP1 (D, A, B, C, 5, 12, 0x4787c62a)
    .OP1 (C, D, A, B, 6, 17, 0xa8304613)
    .OP1 (B, C, D, A, 7, 22, 0xfd469501)
    .OP1 (A, B, C, D, 8,  7, 0x698098d8)
    .OP1 (D, A, B, C, 9, 12, 0x8b44f7af)
    .OP1 (C, D, A, B, 10,17, 0xffff5bb1)
    .OP1 (B, C, D, A, 11,22, 0x895cd7be)
    .OP1 (A, B, C, D, 12, 7, 0x6b901122)
    .OP1 (D, A, B, C, 13,12, 0xfd987193)
    .OP1 (C, D, A, B, 14,17, 0xa679438e)
    .OP1 (B, C, D, A, 15,22, 0x49b40821)

    # Round 2.
    .OP2 (A, B, C, D,  1,  5, 0xf61e2562)
    .OP2 (D, A, B, C,  6,  9, 0xc040b340)
    .OP2 (C, D, A, B, 11, 14, 0x265e5a51)
    .OP2 (B, C, D, A,  0, 20, 0xe9b6c7aa)
    .OP2 (A, B, C, D,  5,  5, 0xd62f105d)
    .OP2 (D, A, B, C, 10,  9, 0x02441453)
    .OP2 (C, D, A, B, 15, 14, 0xd8a1e681)
    .OP2 (B, C, D, A,  4, 20, 0xe7d3fbc8)
    .OP2 (A, B, C, D,  9,  5, 0x21e1cde6)
    .OP2 (D, A, B, C, 14,  9, 0xc33707d6)
    .OP2 (C, D, A, B,  3, 14, 0xf4d50d87)
    .OP2 (B, C, D, A,  8, 20, 0x455a14ed)
    .OP2 (A, B, C, D, 13,  5, 0xa9e3e905)
    .OP2 (D, A, B, C,  2,  9, 0xfcefa3f8)
    .OP2 (C, D, A, B,  7, 14, 0x676f02d9)
    .OP2 (B, C, D, A, 12, 20, 0x8d2a4c8a)

    # Round 3.
    .OP3 (A, B, C, D,  5,  4, 0xfffa3942)
    .OP3 (D, A, B, C,  8, 11, 0x8771f681)
    .OP3 (C, D, A, B, 11, 16, 0x6d9d6122)
    .OP3 (B, C, D, A, 14, 23, 0xfde5380c)
    .OP3 (A, B, C, D,  1,  4, 0xa4beea44)
    .OP3 (D, A, B, C,  4, 11, 0x4bdecfa9)
    .OP3 (C, D, A, B,  7, 16, 0xf6bb4b60)
    .OP3 (B, C, D, A, 10, 23, 0xbebfbc70)
    .OP3 (A, B, C, D, 13,  4, 0x289b7ec6)
    .OP3 (D, A, B, C,  0, 11, 0xeaa127fa)
    .OP3 (C, D, A, B,  3, 16, 0xd4ef3085)
    .OP3 (B, C, D, A,  6, 23, 0x04881d05)
    .OP3 (A, B, C, D,  9,  4, 0xd9d4d039)
    .OP3 (D, A, B, C, 12, 11, 0xe6db99e5)
    .OP3 (C, D, A, B, 15, 16, 0x1fa27cf8)
    .OP3 (B, C, D, A,  2, 23, 0xc4ac5665)

    # Round 4.
    .OP4 (A, B, C, D,  0,  6, 0xf4292244)
    .OP4 (D, A, B, C,  7, 10, 0x432aff97)
    .OP4 (C, D, A, B, 14, 15, 0xab9423a7)
    .OP4 (B, C, D, A,  5, 21, 0xfc93a039)
    .OP4 (A, B, C, D, 12,  6, 0x655b59c3)
    .OP4 (D, A, B, C,  3, 10, 0x8f0ccc92)
    .OP4 (C, D, A, B, 10, 15, 0xffeff47d)
    .OP4 (B, C, D, A,  1, 21, 0x85845dd1)
    .OP4 (A, B, C, D,  8,  6, 0x6fa87e4f)
    .OP4 (D, A, B, C, 15, 10, 0xfe2ce6e0)
    .OP4 (C, D, A, B,  6, 15, 0xa3014314)
    .OP4 (B, C, D, A, 13, 21, 0x4e0811a1)
    .OP4 (A, B, C, D,  4,  6, 0xf7537e82)
    .OP4 (D, A, B, C, 11, 10, 0xbd3af235)
    .OP4 (C, D, A, B,  2, 15, 0x2ad7d2bb)
    .OP4 (B, C, D, A,  9, 21, 0xeb86d391)

    A += A_save
    B += B_save
    C += C_save
    D += D_save

    idx += 16

    if idx < len goto loop

    context[0] = A
    context[1] = B
    context[2] = C
    context[3] = D

.end

###########################################################################

# Format four hex values

.sub _md5_format_vals
    .param int A
    .param int B
    .param int C
    .param int D

    $P0 = new 'FixedIntegerArray'
    $P0 = 4

    $P0[0] = A
    $P0[1] = B
    $P0[2] = C
    $P0[3] = D

    $S0 = sprintf "%08lx%08lx%08lx%08lx", $P0
    .return ($S0)
.end

###########################################################################

# Retrieve the final checksum as a string

.sub _md5_hex
    .param pmc context

    .local int A, B, C, D

    A = context[0]
    B = context[1]
    C = context[2]
    D = context[3]

    .swap (A)
    .swap (B)
    .swap (C)
    .swap (D)

    $S0 = _md5_format_vals (A,B,C,D)
    .return ($S0)
.end

# Convenience subroutine to print the MD5 hash for a string

.sub _md5_print
    .param pmc context

    $S0 = _md5_hex (context)
    print $S0

    .return ($S0)
.end


=head1 BUGS

Still has some limitations on input buffer size, largely due to memory
consumption which should be resolved soon.

=cut


# Local Variables:
#   mode: pir
#   fill-column: 100
# End:
# vim: expandtab shiftwidth=4 ft=pir:
