; ==============================================================================
; hashline.asm - Berechnet djb2_64 Hashes zu jeder Zeile von stdin (32-Bit)
; reinhard@finalmedia.de
; public domain
;
; nasm -f elf32 hashline.asm -o hashline.o
; ld -m elf_i386 -s hashline.o -o hashline
; ==============================================================================

global _start

%define BUFSIZE 65536
%define SYS_EXIT  1
%define SYS_READ  3
%define SYS_WRITE 4

section .bss
    in_buf  resb BUFSIZE
    out_buf resb BUFSIZE
    out_pos resd 1

section .data
    ; nicht verwendet

section .rodata
    str_space   db " "
    str_newline db 10
    hex_chars   db "0123456789abcdef"

section .text

_start:
    mov dword [out_pos], 0
    xor ebp, ebp            ; ebp = rlen = 0
    xor esi, esi            ; esi = pos = 0

.main_loop:
    cmp esi, ebp
    jl .find_newline

    ; --- read(0, in_buf, BUFSIZE) ---
    mov eax, SYS_READ
    mov ebx, 0
    mov ecx, in_buf
    mov edx, BUFSIZE
    int 0x80

    cmp eax, 0
    jle .main_done          ; EOF oder Fehler -> Beenden

    mov ebp, eax            ; rlen = n
    xor esi, esi            ; pos = 0

.find_newline:
    mov edi, esi            ; edi = start = pos

.scan_loop:
    cmp esi, ebp
    jge .handle_no_newline
    mov al, [in_buf + esi]
    cmp al, 10              ; '\n'
    je .handle_newline
    inc esi
    jmp .scan_loop

.handle_newline:
    inc esi                 ; pos++
    ; Länge berechnen: ecx = pos - start
    mov ecx, esi
    sub ecx, edi

    ; Register sichern, da djb2_64 sie verändern könnte
    push ebp
    push esi
    push edi
    push ecx

    ; djb2_64(in_buf + start, pos - start)
    lea eax, [in_buf + edi]
    call djb2_64            ; liefert Hash in EDX:EAX

    ; 1. Hash ausgeben
    call put_hex64_buffered

    ; 2. Leerzeichen ausgeben
    mov eax, str_space
    mov ecx, 1
    call put_buffered

    ; 3. Zeile ausgeben (in_buf + start, pos - start)
    pop ecx                 ; Länge der Zeile wiederherstellen
    pop edi                 ; Start-Index wiederherstellen
    push edi                ; Für die äußeren Register-Pops erhalten
    push ecx

    lea eax, [in_buf + edi]
    call put_buffered

    pop ecx
    pop edi
    pop esi
    pop ebp
    jmp .main_loop

.handle_no_newline:
    ; --- Rest verarbeiten ---
    mov ebx, ebp
    sub ebx, edi            ; ebx = left = rlen - start

    test ebx, ebx
    jle .skip_move
    test edi, edi
    jle .skip_move

    ; in_buf-Inhalt nach vorne shiften
    xor ecx, ecx

.move_loop:
    mov al, [in_buf + edi + ecx]
    mov [in_buf + ecx], al
    inc ecx
    cmp ecx, ebx
    jl .move_loop

.skip_move:
    ; Wir müssen ebx (left) über den Syscall hinweg retten
    push ebx

    ; read(0, in_buf + left, BUFSIZE - left)
    mov edx, BUFSIZE
    sub edx, ebx            ; edx = BUFSIZE - left

    mov eax, SYS_READ
    lea ecx, [in_buf + ebx] ; ecx = in_buf + left
    mov ebx, 0              ; ebx = stdin (0)
    int 0x80

    pop ebx                 ; ebx (left) wiederherstellen

    cmp eax, 0
    jg .read_more_success

    ; EOF im "else"-Zweig
    test ebx, ebx
    jle .main_done

    push ebx
    mov eax, in_buf
    mov ecx, ebx
    call djb2_64

    call put_hex64_buffered

    mov eax, str_space
    mov ecx, 1
    call put_buffered

    pop ebx
    push ebx
    mov eax, in_buf
    mov ecx, ebx
    call put_buffered

    pop ebx
    mov al, [in_buf + ebx - 1]
    cmp al, 10
    je .main_done

    mov eax, str_newline
    mov ecx, 1
    call put_buffered
    jmp .main_done

.read_more_success:
    add ebx, eax            ; ebx = left + n (eax)
    mov ebp, ebx            ; rlen = left + n
    xor esi, esi            ; pos = 0
    jmp .main_loop

.main_done:
    call flush_out
    mov eax, SYS_EXIT
    mov ebx, 0
    int 0x80

; ==============================================================================
; uint64 djb2_64(const char *s [eax], int len [ecx]) -> EDX:EAX
; ==============================================================================
djb2_64:
    push ebx
    push esi
    push edi

    mov edx, eax            ; Adresse nach EDX retten

    ; 5381 als 64-Bit Wert initialisieren
    mov esi, 5381           ; esi = hash_low
    xor edi, edi            ; edi = hash_high

    test ecx, ecx
    jz .done

.loop:
    ; 64-Bit Shift nach links um 5: (hash << 5)
    mov eax, esi
    mov ebx, edi

    shld edi, esi, 5
    shl esi, 5

    ; add hash, (hash << 5)
    add esi, eax
    adc edi, ebx

    ; Zeichen einlesen
    xor ebx, ebx
    mov bl, [edx]

    ; add hash, c
    add esi, ebx
    adc edi, 0

    inc edx
    dec ecx
    jnz .loop

.done:
    ; Ergebnis nach EDX:EAX schreiben
    mov eax, esi
    mov edx, edi

    pop edi
    pop esi
    pop ebx
    ret

; ==============================================================================
; void flush_out(void)
; ==============================================================================
flush_out:
    mov ebx, [out_pos]
    test ebx, ebx
    jz .done

    xor edi, edi            ; edi = pos = 0

.loop:
    cmp edi, [out_pos]
    jge .clear

    mov edx, [out_pos]
    sub edx, edi            ; edx = out_pos - pos

    mov eax, SYS_WRITE
    mov ebx, 1              ; stdout
    lea ecx, [out_buf + edi]
    int 0x80

    cmp eax, 0
    jle .error

    add edi, eax            ; pos += w
    jmp .loop

.clear:
    mov dword [out_pos], 0
.done:
    ret
.error:
    mov eax, SYS_EXIT
    mov ebx, 111
    int 0x80

; ==============================================================================
; void put_buffered(const char *s [eax], int len [ecx])
; ==============================================================================
put_buffered:
    push eax                ; Wir sichern das originale eax und ecx für den Caller
    push ecx

    test ecx, ecx
    jle .done
.loop:
    test ecx, ecx
    jle .done

    mov edx, BUFSIZE
    sub edx, [out_pos]      ; edx = space
    jnz .has_space

    push eax
    push ecx
    call flush_out
    pop ecx
    pop eax
    mov edx, BUFSIZE

.has_space:
    mov ebx, ecx
    cmp ebx, edx
    jle .copy
    mov ebx, edx            ; ebx = chunk

.copy:
    sub ecx, ebx            ; len -= chunk
    mov edx, [out_pos]
    lea edx, [out_buf + edx]

    push ecx                ; ecx sichern (restliche Gesamtlänge)
    mov ecx, ebx            ; Loop-Zähler für den aktuellen Chunk

.copy_bytes:
    mov bl, [eax]           ; Byte aus der Quelle lesen
    mov [edx], bl           ; Byte in den out_buf schreiben
    inc eax
    inc edx
    inc dword [out_pos]
    dec ecx
    jnz .copy_bytes

    pop ecx                 ; Ursprüngliches restliches 'len' wiederherstellen
    jmp .loop
.done:
    pop ecx
    pop eax
    ret

; ==============================================================================
; void put_hex64_buffered(uint64 n [EDX:EAX])
; ==============================================================================
put_hex64_buffered:
    sub esp, 16             ; 16 Bytes Platz auf dem Stack reservieren
    push ebp
    push esi
    push ebx

    mov esi, eax            ; esi = low dword
    mov ebp, edx            ; ebp = high dword

    mov ecx, 16
.loop:
    dec ecx

    ; Wenn ecx >= 8, arbeiten wir auf dem High-Dword, sonst Low-Dword
    cmp ecx, 8
    jae .use_high

    mov eax, esi
    jmp .extract
.use_high:
    mov eax, ebp
.extract:
    ; Berechne die Verschiebung: (ecx % 8) * 4
    mov edx, ecx
    and edx, 7
    shl edx, 2

    mov ebx, ecx            ; Rette Schleifenzähler nach ebx
    mov ecx, edx
    shr eax, cl
    mov ecx, ebx            ; Schleifenzähler zurückholen

    and eax, 15
    mov al, [hex_chars + eax]
    mov [esp + 12 + ecx], al ; +12 wegen den 3 gepushten Registern

    test ecx, ecx
    jnz .loop

    pop ebx
    pop esi
    pop ebp

    mov eax, esp
    mov ecx, 16
    call put_buffered

    add esp, 16
    ret
