Skip to content

Instantly share code, notes, and snippets.

@aydinnyunus
Created February 13, 2026 18:02
Show Gist options
  • Select an option

  • Save aydinnyunus/7beef428ca91fb7eb8aa2965086998d8 to your computer and use it in GitHub Desktop.

Select an option

Save aydinnyunus/7beef428ca91fb7eb8aa2965086998d8 to your computer and use it in GitHub Desktop.

Revisions

  1. aydinnyunus created this gist Feb 13, 2026.
    906 changes: 906 additions & 0 deletions redis.asm
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,906 @@
    ; Redis-like Key-Value Store in x86-64 Assembly for macOS
    ; NASM syntax - Basic Redis server with SET, GET, DEL, PING commands

    extern _socket, _bind, _listen, _accept, _read, _write, _close, _exit, _printf, _htons
    extern _strcmp, _strlen, _strncmp, _sprintf

    section .data
    server_msg db "Redis-like server starting on port 6379...", 10
    db "Connect with: redis-cli -p 6379", 10
    db "Supported: PING, SET key value, GET key, DEL key, QUIT", 10, 0
    ; RESP responses
    resp_ok db "+OK", 13, 10
    resp_ok_len equ $ - resp_ok
    resp_pong db "+PONG", 13, 10
    resp_pong_len equ $ - resp_pong
    resp_nil db "$-1", 13, 10
    resp_nil_len equ $ - resp_nil
    resp_err_unknown db "-ERR unknown command", 13, 10
    resp_err_unknown_len equ $ - resp_err_unknown
    ; Commands
    cmd_ping db "PING", 0
    cmd_set db "SET", 0
    cmd_get db "GET", 0
    cmd_del db "DEL", 0
    cmd_quit db "QUIT", 0
    ; Socket address
    sockaddr:
    sin_family dw 2
    sin_port dw 0
    sin_addr dd 0
    sin_zero dq 0
    sockaddr_len equ $ - sockaddr
    ; Format strings (null-terminated!)
    fmt_bulk db "$%d", 13, 10, 0
    fmt_int db ":%d", 13, 10, 0
    crlf db 13, 10, 0

    section .bss
    sock_fd resq 1
    client_fd resq 1
    buffer resb 8192
    temp_key resb 256
    temp_value resb 1024
    response_buf resb 1024
    saved_read_len resq 1
    ; Simple key-value storage (100 entries)
    kv_keys resb 10000 ; Key storage (100 keys * 100 bytes)
    kv_values resb 100000 ; Value storage (100 values * 1000 bytes)
    kv_count resq 1 ; Number of entries

    section .text
    global _main

    %define AF_INET 2
    %define SOCK_STREAM 1
    %define IPPROTO_TCP 6
    %define PORT 6379
    %define MAX_KEYS 100

    _main:
    push rbp
    mov rbp, rsp
    ; Initialize
    mov qword [rel kv_count], 0
    ; Print message
    lea rdi, [rel server_msg]
    call _printf
    ; Setup port
    mov rdi, PORT
    call _htons
    mov [rel sockaddr + 2], ax
    ; Create socket
    mov rdi, AF_INET
    mov rsi, SOCK_STREAM
    mov rdx, IPPROTO_TCP
    call _socket
    cmp rax, 0
    jl error_exit
    mov [rel sock_fd], rax
    ; Bind
    mov rdi, [rel sock_fd]
    lea rsi, [rel sockaddr]
    mov rdx, sockaddr_len
    call _bind
    cmp rax, 0
    jl error_exit
    ; Listen
    mov rdi, [rel sock_fd]
    mov rsi, 5
    call _listen
    cmp rax, 0
    jl error_exit
    server_loop:
    ; Accept
    mov rdi, [rel sock_fd]
    mov rsi, 0
    mov rdx, 0
    call _accept
    cmp rax, 0
    jl server_loop
    mov [rel client_fd], rax
    ; Handle client
    call handle_client
    ; Close
    mov rdi, [rel client_fd]
    call _close
    jmp server_loop

    handle_client:
    push rbp
    mov rbp, rsp
    .client_loop:
    ; Read
    mov rdi, [rel client_fd]
    lea rsi, [rel buffer]
    mov rdx, 8192
    call _read
    cmp rax, 0
    jle .client_done
    ; Save read length and null terminate
    mov [rel saved_read_len], rax
    lea rbx, [rel buffer]
    add rbx, rax
    mov byte [rbx], 0
    ; Parse and execute
    call parse_resp
    jmp .client_loop
    .client_done:
    pop rbp
    ret

    parse_resp:
    push rbp
    mov rbp, rsp
    ; Check if RESP array (must start with '*')
    lea rbx, [rel buffer]
    cmp byte [rbx], '*'
    jne .parse_done
    ; Search for command strings in buffer (case-insensitive)
    ; Look for PING
    lea rdi, [rel buffer]
    mov rsi, [rel saved_read_len]
    lea rdx, [rel cmd_ping]
    call find_string_in_buffer
    cmp rax, 0
    jne .found_ping
    ; Look for SET
    lea rdi, [rel buffer]
    mov rsi, [rel saved_read_len]
    lea rdx, [rel cmd_set]
    call find_string_in_buffer
    cmp rax, 0
    jne .found_set
    ; Look for GET
    lea rdi, [rel buffer]
    mov rsi, [rel saved_read_len]
    lea rdx, [rel cmd_get]
    call find_string_in_buffer
    cmp rax, 0
    jne .found_get
    ; Look for DEL
    lea rdi, [rel buffer]
    mov rsi, [rel saved_read_len]
    lea rdx, [rel cmd_del]
    call find_string_in_buffer
    cmp rax, 0
    jne .found_del
    ; Look for QUIT
    lea rdi, [rel buffer]
    mov rsi, [rel saved_read_len]
    lea rdx, [rel cmd_quit]
    call find_string_in_buffer
    cmp rax, 0
    jne .found_quit
    ; Unknown command
    jmp .unknown_cmd

    .found_ping:
    jmp .do_ping
    .found_set:
    jmp .do_set
    .found_get:
    jmp .do_get
    .found_del:
    jmp .do_del
    .found_quit:
    jmp .do_quit

    .unknown_cmd:
    mov rdi, [rel client_fd]
    lea rsi, [rel resp_err_unknown]
    mov rdx, resp_err_unknown_len
    call _write
    jmp .parse_done

    .do_ping:
    mov rdi, [rel client_fd]
    lea rsi, [rel resp_pong]
    mov rdx, resp_pong_len
    call _write
    jmp .parse_done

    .do_set:
    ; Extract key and value from RESP array
    call find_resp_command
    mov rbx, rax
    cmp rbx, 0
    je .parse_done
    ; Find end of command string
    mov rcx, rbx
    .find_set_cmd_end:
    cmp byte [rcx], 13
    je .found_set_cmd_end
    cmp byte [rcx], 0
    je .parse_done
    inc rcx
    jmp .find_set_cmd_end

    .found_set_cmd_end:
    add rcx, 2 ; Skip \r\n
    ; Extract key (next bulk string)
    mov rdi, rcx
    call extract_resp_string_at
    cmp rax, 0
    je .parse_done
    mov rsi, rax
    lea rdi, [rel temp_key]
    call copy_until_crlf
    ; Extract value (next bulk string after key)
    mov rdi, rsi
    call find_string_end
    mov rdi, rax
    call extract_resp_string_at
    cmp rax, 0
    je .parse_done
    mov rsi, rax
    lea rdi, [rel temp_value]
    call copy_until_crlf
    ; Store key-value
    lea rdi, [rel temp_key]
    lea rsi, [rel temp_value]
    call kv_set
    mov rdi, [rel client_fd]
    lea rsi, [rel resp_ok]
    mov rdx, resp_ok_len
    call _write
    jmp .parse_done

    .do_get:
    call find_resp_command
    mov rbx, rax
    cmp rbx, 0
    je .parse_done
    mov rcx, rbx
    .find_get_cmd_end:
    cmp byte [rcx], 13
    je .found_get_cmd_end
    cmp byte [rcx], 0
    je .parse_done
    inc rcx
    jmp .find_get_cmd_end

    .found_get_cmd_end:
    add rcx, 2
    mov rdi, rcx
    call extract_resp_string_at
    cmp rax, 0
    je .parse_done
    mov rsi, rax
    lea rdi, [rel temp_key]
    call copy_until_crlf
    lea rdi, [rel temp_key]
    call kv_get
    cmp rax, 0
    je .get_not_found
    mov rdi, rax
    call send_bulk_string
    jmp .parse_done

    .get_not_found:
    mov rdi, [rel client_fd]
    lea rsi, [rel resp_nil]
    mov rdx, resp_nil_len
    call _write
    jmp .parse_done

    .do_del:
    call find_resp_command
    mov rbx, rax
    cmp rbx, 0
    je .parse_done
    mov rcx, rbx
    .find_del_cmd_end:
    cmp byte [rcx], 13
    je .found_del_cmd_end
    cmp byte [rcx], 0
    je .parse_done
    inc rcx
    jmp .find_del_cmd_end

    .found_del_cmd_end:
    add rcx, 2
    mov rdi, rcx
    call extract_resp_string_at
    cmp rax, 0
    je .parse_done
    mov rsi, rax
    lea rdi, [rel temp_key]
    call copy_until_crlf
    lea rdi, [rel temp_key]
    call kv_del
    call send_integer
    jmp .parse_done

    .do_quit:
    mov rdi, [rel client_fd]
    lea rsi, [rel resp_ok]
    mov rdx, resp_ok_len
    call _write
    jmp .parse_done

    .parse_done:
    pop rbp
    ret

    ; ============================================================
    ; Find string in buffer (case-insensitive)
    ; rdi = buffer, rsi = buffer_len, rdx = search_string
    ; Returns: pointer to match or 0 if not found
    ; ============================================================
    find_string_in_buffer:
    push rbp
    mov rbp, rsp
    push rbx
    push r12
    push r13
    push r14
    push r15
    mov r12, rdi ; buffer pointer (callee-saved)
    mov r13, rdx ; search string (callee-saved)
    mov r14, rsi ; buffer length (callee-saved)
    mov r15, 0 ; position (callee-saved)
    ; Get search string length once
    mov rdi, r13
    call _strlen
    mov rbx, rax ; search string length (callee-saved)
    .find_str_loop:
    cmp r15, r14
    jge .find_str_not_found
    ; Check if we have enough bytes left
    mov rax, r14
    sub rax, r15
    cmp rax, rbx
    jl .find_str_not_found
    ; Compare strings (case-insensitive)
    lea rdi, [r12 + r15]
    mov rsi, r13
    mov rdx, rbx
    call compare_strings_case_insensitive
    cmp rax, 0
    je .find_str_found
    inc r15
    jmp .find_str_loop

    .find_str_found:
    lea rax, [r12 + r15]
    jmp .find_str_done

    .find_str_not_found:
    mov rax, 0

    .find_str_done:
    pop r15
    pop r14
    pop r13
    pop r12
    pop rbx
    pop rbp
    ret

    ; ============================================================
    ; Compare strings case-insensitively
    ; rdi = str1, rsi = str2, rdx = length
    ; Returns: 0 if equal, 1 if not
    ; ============================================================
    compare_strings_case_insensitive:
    push rbp
    mov rbp, rsp
    mov r8, rdx ; length
    mov r9, 0 ; index

    .cmp_loop:
    cmp r9, r8
    jge .cmp_equal
    movzx eax, byte [rdi + r9]
    movzx ecx, byte [rsi + r9]
    ; Convert to uppercase
    cmp al, 'a'
    jl .al_ok
    cmp al, 'z'
    jg .al_ok
    sub al, 32
    .al_ok:
    cmp cl, 'a'
    jl .cl_ok
    cmp cl, 'z'
    jg .cl_ok
    sub cl, 32
    .cl_ok:
    cmp al, cl
    jne .cmp_not_equal
    inc r9
    jmp .cmp_loop

    .cmp_equal:
    mov rax, 0
    jmp .cmp_done

    .cmp_not_equal:
    mov rax, 1

    .cmp_done:
    pop rbp
    ret

    ; ============================================================
    ; Find command in RESP array
    ; Format: *N\r\n$len\r\ncommand\r\n...
    ; Returns: pointer to command string or 0
    ; ============================================================
    find_resp_command:
    push rbp
    mov rbp, rsp
    lea rbx, [rel buffer]
    cmp byte [rbx], '*'
    jne .not_found_cmd
    inc rbx
    ; Skip array count digits
    .skip_count:
    mov al, [rbx]
    cmp al, 13
    je .check_crlf_count
    inc rbx
    jmp .skip_count

    .check_crlf_count:
    inc rbx ; skip \r
    inc rbx ; skip \n
    ; Now at first '$'
    cmp byte [rbx], '$'
    jne .not_found_cmd
    inc rbx ; skip '$'
    ; Skip length digits
    .skip_length:
    mov al, [rbx]
    cmp al, 13
    je .check_crlf_len
    inc rbx
    jmp .skip_length

    .check_crlf_len:
    inc rbx ; skip \r
    inc rbx ; skip \n
    mov rax, rbx ; pointer to command string
    jmp .find_cmd_done

    .not_found_cmd:
    mov rax, 0

    .find_cmd_done:
    pop rbp
    ret

    ; ============================================================
    ; Extract RESP string at given position
    ; rdi = position in buffer (should point to '$')
    ; Returns: pointer to string data or 0
    ; ============================================================
    extract_resp_string_at:
    push rbp
    mov rbp, rsp
    mov rbx, rdi
    cmp byte [rbx], '$'
    jne .not_found_str
    inc rbx ; skip '$'
    ; Skip length digits
    .skip_len:
    mov al, [rbx]
    cmp al, 13
    je .check_crlf_str
    inc rbx
    jmp .skip_len

    .check_crlf_str:
    inc rbx ; skip \r
    inc rbx ; skip \n
    mov rax, rbx ; pointer to string data
    jmp .extract_done

    .not_found_str:
    mov rax, 0

    .extract_done:
    pop rbp
    ret

    ; ============================================================
    ; Find end of string (skip past \r\n)
    ; rdi = pointer to start of string
    ; Returns: pointer after \r\n
    ; ============================================================
    find_string_end:
    push rbp
    mov rbp, rsp
    mov rbx, rdi
    .find_end_loop:
    mov al, [rbx]
    cmp al, 13
    je .found_end
    cmp al, 0
    je .found_end_null
    inc rbx
    jmp .find_end_loop

    .found_end:
    add rbx, 2 ; skip \r\n
    mov rax, rbx
    jmp .find_end_done

    .found_end_null:
    mov rax, rbx

    .find_end_done:
    pop rbp
    ret

    ; ============================================================
    ; Copy string until \r\n or null
    ; rdi = destination, rsi = source
    ; ============================================================
    copy_until_crlf:
    push rbp
    mov rbp, rsp
    mov rbx, rsi
    mov rcx, rdi

    .copy_loop:
    mov al, [rbx]
    cmp al, 13
    je .copy_done
    cmp al, 0
    je .copy_done
    mov [rcx], al
    inc rbx
    inc rcx
    jmp .copy_loop

    .copy_done:
    mov byte [rcx], 0
    pop rbp
    ret

    ; ============================================================
    ; Key-value store operations
    ; ============================================================

    kv_set:
    push rbp
    mov rbp, rsp
    push r12
    push r13
    mov r12, rdi ; key
    mov r13, rsi ; value
    ; Check if key already exists
    mov rdi, r12
    call kv_find
    cmp rax, -1
    jne .kv_update
    ; New key
    mov rbx, [rel kv_count]
    cmp rbx, MAX_KEYS
    jge .kv_set_done
    ; Copy key to storage
    lea r8, [rel kv_keys]
    mov rax, rbx
    mov r9, 100 ; KEY_SIZE
    mul r9
    add r8, rax
    mov rdi, r8
    mov rsi, r12
    call copy_string_simple
    ; Copy value to storage
    lea r8, [rel kv_values]
    mov rax, rbx
    mov r9, 1000 ; VALUE_SIZE
    mul r9
    add r8, rax
    mov rdi, r8
    mov rsi, r13
    call copy_string_simple
    ; Increment count
    inc qword [rel kv_count]
    jmp .kv_set_done

    .kv_update:
    ; Update existing value
    lea r8, [rel kv_values]
    mov r9, 1000
    mul r9
    add r8, rax
    mov rdi, r8
    mov rsi, r13
    call copy_string_simple

    .kv_set_done:
    pop r13
    pop r12
    pop rbp
    ret

    copy_string_simple:
    push rbp
    mov rbp, rsp
    mov rcx, rdi
    mov rdx, rsi

    .copy_simple_loop:
    mov al, [rdx]
    cmp al, 0
    je .copy_simple_done
    mov [rcx], al
    inc rcx
    inc rdx
    jmp .copy_simple_loop

    .copy_simple_done:
    mov byte [rcx], 0
    pop rbp
    ret

    kv_get:
    push rbp
    mov rbp, rsp
    call kv_find
    cmp rax, -1
    je .kv_get_not_found
    ; Return pointer to value
    lea r8, [rel kv_values]
    mov rbx, rax
    mov r9, 1000
    mov rax, rbx
    mul r9
    add r8, rax
    mov rax, r8
    jmp .kv_get_done

    .kv_get_not_found:
    mov rax, 0

    .kv_get_done:
    pop rbp
    ret

    kv_del:
    push rbp
    mov rbp, rsp
    push r12
    call kv_find
    cmp rax, -1
    je .kv_del_not_found
    ; Delete by shifting entries down
    mov r12, rax ; index to delete
    mov rbx, [rel kv_count]
    dec rbx ; new count
    ; Clear the key (set first byte to 0)
    lea r8, [rel kv_keys]
    mov rax, r12
    mov r9, 100
    mul r9
    add r8, rax
    mov byte [r8], 0
    ; Clear the value
    lea r8, [rel kv_values]
    mov rax, r12
    mov r9, 1000
    mul r9
    add r8, rax
    mov byte [r8], 0
    mov rax, 1 ; 1 key deleted
    jmp .kv_del_done

    .kv_del_not_found:
    mov rax, 0

    .kv_del_done:
    pop r12
    pop rbp
    ret

    kv_find:
    push rbp
    mov rbp, rsp
    push r12
    mov r12, rdi ; key to find
    mov rbx, [rel kv_count]
    mov rcx, 0

    .find_loop:
    cmp rcx, rbx
    jge .find_not_found
    push rcx
    push rbx
    ; Get key pointer
    lea r8, [rel kv_keys]
    mov rax, rcx
    mov r9, 100
    mul r9
    add r8, rax
    ; Skip deleted entries (first byte = 0)
    cmp byte [r8], 0
    je .find_skip
    ; Compare
    mov rdi, r8
    mov rsi, r12
    call _strcmp
    pop rbx
    pop rcx
    cmp rax, 0
    je .find_found
    inc rcx
    jmp .find_loop

    .find_skip:
    pop rbx
    pop rcx
    inc rcx
    jmp .find_loop

    .find_found:
    mov rax, rcx
    jmp .find_done

    .find_not_found:
    mov rax, -1

    .find_done:
    pop r12
    pop rbp
    ret

    ; ============================================================
    ; Send bulk string: $len\r\nstring\r\n
    ; rdi = pointer to null-terminated string
    ; ============================================================
    send_bulk_string:
    push rbp
    mov rbp, rsp
    push r12
    mov r12, rdi ; string pointer
    ; Get string length
    call _strlen
    mov rbx, rax
    ; Format header: $len\r\n
    lea rdi, [rel response_buf]
    lea rsi, [rel fmt_bulk]
    mov rdx, rbx
    call _sprintf
    ; Send header
    lea rdi, [rel response_buf]
    call _strlen
    mov rdx, rax
    mov rdi, [rel client_fd]
    lea rsi, [rel response_buf]
    call _write
    ; Send string content
    mov rdi, r12
    call _strlen
    mov rdx, rax
    mov rdi, [rel client_fd]
    mov rsi, r12
    call _write
    ; Send trailing \r\n
    mov rdi, [rel client_fd]
    lea rsi, [rel crlf]
    mov rdx, 2
    call _write
    pop r12
    pop rbp
    ret

    ; ============================================================
    ; Send integer: :number\r\n
    ; rax = integer value
    ; ============================================================
    send_integer:
    push rbp
    mov rbp, rsp
    ; Format: :number\r\n
    lea rdi, [rel response_buf]
    lea rsi, [rel fmt_int]
    mov rdx, rax
    call _sprintf
    ; Send
    lea rdi, [rel response_buf]
    call _strlen
    mov rdx, rax
    mov rdi, [rel client_fd]
    lea rsi, [rel response_buf]
    call _write
    pop rbp
    ret

    error_exit:
    mov rdi, 1
    call _exit