Created
February 13, 2026 18:02
-
-
Save aydinnyunus/7beef428ca91fb7eb8aa2965086998d8 to your computer and use it in GitHub Desktop.
Revisions
-
aydinnyunus created this gist
Feb 13, 2026 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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