From d54596514c30343305b7cc4b8be36c712d536e7a Mon Sep 17 00:00:00 2001
From: root <>
Date: Sun, 11 May 2025 08:22:07 +0000
Subject: [PATCH 01/10] echo then close connection

---
 main.s     | 54 +++++++++++++++++++++++++++++++++++++++++++++++-------
 readline.s | 36 ++++++++++++++++++++++++++++++++++++
 2 files changed, 83 insertions(+), 7 deletions(-)
 create mode 100644 readline.s

diff --git a/main.s b/main.s
index 881a3d5..8905309 100644
--- a/main.s
+++ b/main.s
@@ -24,8 +24,10 @@ POLLIN          equ 1
 POLLOUT         equ 4
 SHUT_RDWR       equ 2
 
-pollfd_size equ 4 + 2 + 2  ; $ man 2 poll
-pollfds_capacity equ 100
+pollfd_size         equ 4 + 2 + 2  ; $ man 2 poll
+pollfds_capacity    equ 100
+client_size         equ 1 + 256 + 256  ; state, line buffer, uri
+clients_capacity    equ pollfds_capacity - 1
 
 SECTION .data
 server_path     db "server.sock", 0x00
@@ -33,15 +35,18 @@ server_path_len equ $ - server_path
 sockaddr_size   equ 2 + 108  ; $ man 7 unix
 goodbye         db "goodbye", 0x0a
 goodbye_len     equ $ - goodbye
+pollfds_len     dq 0
+clients_len     dq 0
 
 SECTION .bss
 sockaddr        resb sockaddr_size
 pollfds         resb pollfd_size * pollfds_capacity
-pollfds_len     resb 8
+clients         resb (4 + client_size) * clients_capacity
 
 SECTION .text
 
 %include "server.s"
+%include "readline.s"
 
 _start:
     call make_server
@@ -84,11 +89,27 @@ pollfds__scan__found__client:
     ; TODO check r10w, incl for POLLNVAL & POLLERR
     push rdi
 
+    mov rdi, r14
+    call clients__append
+
+    mov r12, [clients_len]
+    add r12, -1
+    imul r12, client_size
+    add r12, clients
+    add r12, 4
+
+    mov rdi, r14
+    mov rsi, r12
+    mov rdx, 256
+    call readline
+    cmp rax, 0  ; TODO
+    jl exit
+    ;push rax  ; TODO
+
+    mov rdx, rax
     mov rax, SYS_WRITE
-    mov rdi, 0
-    mov edi, r14d
-    mov rsi, goodbye
-    mov rdx, goodbye_len
+    mov rdi, r14
+    mov rsi, r12
     syscall
     cmp rax, 0
     jl exit  ; TODO handle this gracefully
@@ -132,6 +153,7 @@ pollfds__scan__found__server:
 ; edi - fd
 ; si - events
 pollfds__append:
+    ; TODO check against pollfds_capacity
     mov r10, [pollfds_len]
     mov [pollfds + r10 * pollfd_size], edi
     mov [pollfds + r10 * pollfd_size + 4], si
@@ -157,6 +179,24 @@ pollfds__clear:
     mov [pollfds_len], r10
     ret
 
+; esi - fd
+clients__append:
+    ; TODO check against client_capacity
+    mov r10, [clients_len]
+    imul r10, client_size
+    add r10, clients
+    mov [r10], esi
+    add r10, 4
+    mov r8b, 0
+    mov r11, 0
+
+clients__append__init:
+    cmp r11, client_size
+    jge return
+    mov [r10 + r11], r8b
+    add r11, 1
+    jmp clients__append__init
+
 return:
     ret
 
diff --git a/readline.s b/readline.s
new file mode 100644
index 0000000..aba4769
--- /dev/null
+++ b/readline.s
@@ -0,0 +1,36 @@
+; Returns length in rax
+; Errors (in rax):
+; -1024 - line too long
+; other negative - error from read(2)
+; Arguments:
+; rdi - fd
+; rsi - buffer
+; rdx - max length
+readline:
+    push rdi
+    push rsi
+    push rdx
+    mov rax, SYS_READ
+    syscall
+    pop rdx
+    pop rsi
+    pop rdi
+    cmp rax, 0
+    jl return
+    mov r10, 0
+
+readline__scan:
+    cmp r10, rax
+    jge readline__overflow
+    mov r11b, [rsi + r10]
+    add r10, 1
+    cmp r11b, 0x0a  ; '\n'
+    jne readline__scan
+
+readline__return:
+    mov rax, r10
+    ret
+
+readline__overflow:
+    mov rax, -1024
+    ret

From 29f54ba779b0ac2c89f88a3d5c5825b0b8ec197d Mon Sep 17 00:00:00 2001
From: root <>
Date: Tue, 13 May 2025 12:07:03 +0000
Subject: [PATCH 02/10] echo server, multiple clients, lines <256 bytes

---
 client.s    | 104 ++++++++++++++++++++++++++++
 clients.s   |  69 +++++++++++++++++++
 constants.c |   2 +
 main.s      | 192 +++++++++++++++++++++++-----------------------------
 pollfds.s   |  30 ++++++++
 readline.s  |  19 ++++--
 6 files changed, 304 insertions(+), 112 deletions(-)
 create mode 100644 client.s
 create mode 100644 clients.s
 create mode 100644 pollfds.s

diff --git a/client.s b/client.s
new file mode 100644
index 0000000..073f42c
--- /dev/null
+++ b/client.s
@@ -0,0 +1,104 @@
+; Errors (in rax):
+; 0 - EOF
+; -1024 - line too long
+; other negative - from read(2)
+; Arguments:
+; rdi - fd
+; rsi - address of client within clients array
+; rdx - pollfds index
+client__pollin:
+    push rdi
+    push rsi
+    push rdx
+
+    mov r10, 0
+    mov r10b, [rsi + 4 + 2]  ; buffer_len
+
+    mov rdx, 0
+    mov dl, 255
+    sub dl, r10b  ; rdx = 255 - buffer_len
+    add rsi, 8
+    add rsi, r10
+    call readline
+
+    mov r10, rdi
+
+    pop rdx
+    pop rsi
+    pop rdi
+
+    cmp rax, 0
+    jle return
+
+    add [rsi + 4 + 2], al  ; buffer_len += rax
+
+    cmp r10, 0
+    jg client__pollin__complete_line
+
+    add [rsi + 4 + 1], al  ; line_len += rax
+    ret
+
+client__pollin__complete_line:
+    add [rsi + 4 + 1], r10b  ; line_len += r10
+    mov word [pollfds + rdx * pollfd_size + 4], POLLOUT
+    ret
+
+; rdi - fd
+; rsi - address of client within clients array
+; rdx - pollfds index
+client__pollout:
+    push rdi
+    push rsi
+    push rdx
+
+    mov rax, SYS_WRITE
+    mov rdx, 0
+    mov dl, [rsi + 4 + 1]
+    add rsi, 8
+    syscall
+
+    pop rdx
+    pop rsi
+    pop rdi
+
+    cmp rax, 0
+    jl return
+
+    mov r10, rsi
+    add r10, 8
+    mov r11, 0
+    mov r11b, [rsi + 4 + 2]
+    sub r11, rax
+    add r11, r10
+
+client__pollout__shunt:
+    cmp r10, r11
+    jge client__pollout__shunt__finished
+    mov r8b, [r10 + rax]
+    mov [r10], r8b
+    add r10, 1
+    jmp client__pollout__shunt
+
+client__pollout__shunt__finished:
+    sub [rsi + 4 + 2], al  ; buffer_len -= rax
+    sub [rsi + 4 + 1], al  ; line_len -= rax
+    cmp byte [rsi + 4 + 1], 0
+    je client__pollout__wrote_line
+    ret
+
+client__pollout__wrote_line:
+    mov word [pollfds + rdx * pollfd_size + 4], POLLIN
+    ret
+
+; rdi - fd
+client__shutdown_close:
+    mov rax, SYS_SHUTDOWN
+    push rdi
+    mov rsi, SHUT_RDWR
+    syscall
+
+    mov rax, SYS_CLOSE
+    pop rdi
+    syscall
+
+    ret
diff --git a/clients.s b/clients.s
new file mode 100644
index 0000000..1af458b
--- /dev/null
+++ b/clients.s
@@ -0,0 +1,69 @@
+; edi - fd
+clients__append:
+    ; TODO check against client_capacity
+    mov r10, [clients_len]
+    imul r10, client_size
+    add r10, clients
+    mov [r10], edi
+
+    add qword [clients_len], 1
+
+    mov r11, 4
+
+clients__append__write_zeros:
+    cmp r11, 8
+    jge return
+    mov byte [r10 + r11], 0
+    add r11, 1
+    jmp clients__append__write_zeros
+
+; edi - fd
+clients__search:
+    mov r11, [clients_len]
+    imul r11, client_size
+    add r11, clients
+    mov r10, clients
+
+clients__search__loop:
+    cmp r10, r11
+    jge clients__search__fail
+    mov r8d, [r10]
+    cmp r8d, edi
+    je clients__search__succ
+    add r10, client_size
+    jmp clients__search__loop
+
+clients__search__fail:
+    mov r10, -1
+
+clients__search__succ:
+    mov rax, r10
+    ret
+
+; rdi - address of client within clients array
+clients__remove:
+    mov r10, [clients_len]
+    cmp r10, 1
+    jle clients__clear
+
+    add r10, -1
+    mov [clients_len], r10
+
+    mov r10, 0
+    mov r11, r10
+    imul r11, client_size
+    add r11, clients
+
+clients__remove__loop:
+    cmp r10, client_size
+    jge return
+    mov r8b, [r11 + r10]
+    mov [rdi + r10], r8b
+    add r10, 1
+    jmp clients__remove__loop
+
+; rdi - address of client within clients array
+clients__clear:
+    mov r10, 0
+    mov [clients_len], r10
+    ret
diff --git a/constants.c b/constants.c
index 2e7aab3..8239355 100644
--- a/constants.c
+++ b/constants.c
@@ -14,5 +14,7 @@ void main() {
     printf("sizeof(short)   %d\n", sizeof(short));
     printf("POLLIN          %d\n", POLLIN);
     printf("POLLOUT         %d\n", POLLOUT);
+    printf("POLLERR         %d\n", POLLERR);
+    printf("POLLNVAL        %d\n", POLLNVAL);
     printf("SHUT_RDWR       %d\n", SHUT_RDWR);
 }
diff --git a/main.s b/main.s
index 8905309..190f240 100644
--- a/main.s
+++ b/main.s
@@ -22,11 +22,19 @@ F_SETFL         equ 4
 O_NONBLOCK      equ 2048
 POLLIN          equ 1
 POLLOUT         equ 4
+POLLERR         equ 8
+POLLNVAL        equ 32
+; TODO(?) POLLPRI: see poll(2), tcp(7)
 SHUT_RDWR       equ 2
 
+;STATE_READING   equ 0
+;STATE_WRITING   equ 1
+
 pollfd_size         equ 4 + 2 + 2  ; $ man 2 poll
 pollfds_capacity    equ 100
-client_size         equ 1 + 256 + 256  ; state, line buffer, uri
+
+; fd, state, line_len, buffer_len, uri_len, buffer, uri
+client_size         equ 4 + 4 + 255 + 255
 clients_capacity    equ pollfds_capacity - 1
 
 SECTION .data
@@ -47,11 +55,14 @@ SECTION .text
 
 %include "server.s"
 %include "readline.s"
+%include "clients.s"
+%include "pollfds.s"
+%include "client.s"
 
 _start:
     call make_server
     mov rbx, rax  ; server fd
-    mov rdi, rax
+    mov rdi, rbx
     mov rsi, POLLIN
     call pollfds__append
 
@@ -63,144 +74,113 @@ poll:
     syscall
     cmp rax, 0
     je poll
-    jl exit  ; TODO handle this gracefully
-    mov rdi, rbx
+    jl exit  ; poll(2) returned error TODO
 
-; rdi - server fd
-pollfds__scan:
+scan:
     mov r15, 0
 
-pollfds__scan__loop:
+; Variables:
+; r15 - pollfds index
+; r14w - poll(2) revents
+; r13d - fd
+; r12 - client memory address
+scan__loop:
     cmp r15, [pollfds_len]
     jge poll
-    mov r10w, [pollfds + r15 * pollfd_size + 6]
-    cmp r10w, 0
-    jne pollfds__scan__found
+    mov r14w, [pollfds + r15 * pollfd_size + 6]
+    cmp r14w, 0
+    jne scan__found
     add r15, 1
-    jmp pollfds__scan__loop
+    jmp scan__loop
 
-pollfds__scan__found:
-    mov r14, 0
-    mov r14d, [pollfds + r15 * pollfd_size]
-    cmp r14d, edi
-    je pollfds__scan__found__server
+scan__found:
+    mov r13, 0
+    mov r13d, [pollfds + r15 * pollfd_size]
+    cmp r13d, ebx
+    je scan__found__server
 
-pollfds__scan__found__client:
-    ; TODO check r10w, incl for POLLNVAL & POLLERR
-    push rdi
+scan__found__client:
+    mov rdi, r13
+    call clients__search
+    mov r12, rax
+    cmp r12, 0
+    jl _client__not_stored
 
-    mov rdi, r14
-    call clients__append
+    mov r10w, r14w
+    and r10w, POLLERR | POLLNVAL
+    cmp r10w, 0
+    jne _client__error
 
-    mov r12, [clients_len]
-    add r12, -1
-    imul r12, client_size
-    add r12, clients
-    add r12, 4
+    mov r10w, r14w
+    and r10w, POLLIN
+    cmp r10w, 0
+    jne _client__pollin
 
-    mov rdi, r14
-    mov rsi, r12
-    mov rdx, 256
-    call readline
-    cmp rax, 0  ; TODO
-    jl exit
-    ;push rax  ; TODO
+    mov r10w, r14w
+    and r10w, POLLOUT
+    cmp r10w, 0
+    jne _client__pollout
 
-    mov rdx, rax
-    mov rax, SYS_WRITE
-    mov rdi, r14
-    mov rsi, r12
-    syscall
-    cmp rax, 0
-    jl exit  ; TODO handle this gracefully
+    ; TODO what did poll(2) detect in this case?
+    add r15, 1
+    jmp scan__loop
 
-    mov rax, SYS_SHUTDOWN
-    mov rdi, 0
-    mov edi, r14d
-    mov rsi, SHUT_RDWR
-    syscall
-
-    mov rax, SYS_CLOSE
-    mov rdi, 0
-    mov edi, r14d
-    syscall
+_client__error:
+    mov rdi, r12
+    call clients__remove
 
+_client__not_stored:
+    mov rdi, r13
+    call client__shutdown_close
     mov rdi, r15
     call pollfds__remove
+    jmp scan__loop
 
-    pop rdi
-    jmp pollfds__scan__loop
+_client__pollin:
+    mov rdi, r13
+    mov rsi, r12
+    mov rdx, r15
+    call client__pollin
+    cmp rax, 0
+    jle _client__error
+    add r15, rax
+    jmp scan__loop
 
-pollfds__scan__found__server:
-    ; TODO check r10w, incl for POLLNVAL & POLLERR
-    push rdi
+_client__pollout:
+    mov rdi, r13
+    mov rsi, r12
+    mov rdx, r15
+    call client__pollout
+    add r15, 1
+    jmp scan__loop
+
+scan__found__server:
+    cmp r14w, POLLIN
+    jne exit
 
     mov rax, SYS_ACCEPT
+    mov rdi, r13
     mov rsi, 0
     mov rdx, 0
     syscall
     cmp rax, 0
-    jl exit  ; TODO handle this gracefully
+    jl exit  ; accept(2) returned error TODO
 
     mov rdi, rax
-    mov si, POLLIN | POLLOUT
+    push rdi
+    mov si, POLLIN
     call pollfds__append
 
     pop rdi
+    call clients__append
+
     add r15, 1
-    jmp pollfds__scan__loop
-
-; edi - fd
-; si - events
-pollfds__append:
-    ; TODO check against pollfds_capacity
-    mov r10, [pollfds_len]
-    mov [pollfds + r10 * pollfd_size], edi
-    mov [pollfds + r10 * pollfd_size + 4], si
-    add r10, 1
-    mov [pollfds_len], r10
-    ret
-
-; rdi - pollfds array index to remove
-pollfds__remove:
-    mov r11, [pollfds_len]
-    cmp rdi, r10
-    jge return  ; XXX index out of bounds, do some other error?
-    cmp r11, 1
-    jle pollfds__clear
-    mov r10, [pollfds + (r11 - 1) * pollfd_size]
-    mov [pollfds + rdi * pollfd_size], r10
-    sub r11, 1
-    mov [pollfds_len], r11
-    ret
-
-pollfds__clear:
-    mov r10, 0
-    mov [pollfds_len], r10
-    ret
-
-; esi - fd
-clients__append:
-    ; TODO check against client_capacity
-    mov r10, [clients_len]
-    imul r10, client_size
-    add r10, clients
-    mov [r10], esi
-    add r10, 4
-    mov r8b, 0
-    mov r11, 0
-
-clients__append__init:
-    cmp r11, client_size
-    jge return
-    mov [r10 + r11], r8b
-    add r11, 1
-    jmp clients__append__init
+    jmp scan__loop
 
 return:
     ret
 
 exit:
     mov rax, SYS_EXIT
-    mov rdi, 0
+    mov rdi, 255
     syscall
diff --git a/pollfds.s b/pollfds.s
new file mode 100644
index 0000000..4eed357
--- /dev/null
+++ b/pollfds.s
@@ -0,0 +1,30 @@
+; edi - fd
+; si - events
+pollfds__append:
+    ; TODO check against pollfds_capacity
+    mov r10, [pollfds_len]
+    mov [pollfds + r10 * pollfd_size], edi
+    mov [pollfds + r10 * pollfd_size + 4], si
+    mov r11w, 0
+    mov [pollfds + r10 * pollfd_size + 6], r11w
+    add r10, 1
+    mov [pollfds_len], r10
+    ret
+
+; rdi - pollfds array index to remove
+pollfds__remove:
+    mov r11, [pollfds_len]
+    cmp rdi, r11
+    jge return  ; XXX index out of bounds, do some other error?
+    cmp r11, 1
+    jle pollfds__clear
+    mov r10, [pollfds + (r11 - 1) * pollfd_size]
+    mov [pollfds + rdi * pollfd_size], r10
+    sub r11, 1
+    mov [pollfds_len], r11
+    ret
+
+pollfds__clear:
+    mov r10, 0
+    mov [pollfds_len], r10
+    ret
diff --git a/readline.s b/readline.s
index aba4769..35e30dd 100644
--- a/readline.s
+++ b/readline.s
@@ -1,34 +1,41 @@
-; Returns length in rax
+; Returns:
+; rax - read(2) length
+; rdi - if line is complete, line length;
+;       if line was incomplete, 0;
+;       if EOF or read(2) returned an error, undefined
 ; Errors (in rax):
 ; -1024 - line too long
 ; other negative - error from read(2)
+; 0 - EOF from read(2)
 ; Arguments:
 ; rdi - fd
 ; rsi - buffer
 ; rdx - max length
 readline:
-    push rdi
     push rsi
     push rdx
     mov rax, SYS_READ
     syscall
     pop rdx
     pop rsi
-    pop rdi
     cmp rax, 0
     jl return
     mov r10, 0
 
 readline__scan:
-    cmp r10, rax
+    cmp r10, rdx
     jge readline__overflow
+    cmp r10, rax
+    jge readline__incomplete_line
     mov r11b, [rsi + r10]
     add r10, 1
     cmp r11b, 0x0a  ; '\n'
     jne readline__scan
+    mov rdi, r10
+    ret
 
-readline__return:
-    mov rax, r10
+readline__incomplete_line:
+    mov rdi, 0
     ret
 
 readline__overflow:

From 8ab750ba97160be5199589d3e82f6a8a35a65a10 Mon Sep 17 00:00:00 2001
From: root <>
Date: Tue, 13 May 2025 12:22:34 +0000
Subject: [PATCH 03/10] bugfix: removing pollfds[0] actually removed
 pollfds[-1]

this bug created this weird behaviour:

1. client 1 connects
2. client 2 connents
3. client 1 disconnects
4. client 2 sends a line
5. client 2 is disconnected by the server
---
 clients.s | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clients.s b/clients.s
index 1af458b..e87dde4 100644
--- a/clients.s
+++ b/clients.s
@@ -49,11 +49,12 @@ clients__remove:
     add r10, -1
     mov [clients_len], r10
 
-    mov r10, 0
     mov r11, r10
     imul r11, client_size
     add r11, clients
 
+    mov r10, 0
+
 clients__remove__loop:
     cmp r10, client_size
     jge return

From 484d81baa86e5151d15462a1153e65854f1abcfc Mon Sep 17 00:00:00 2001
From: root <>
Date: Sat, 17 May 2025 08:10:55 +0000
Subject: [PATCH 04/10] client__pollin: read as much as possible;
 client__pollout: do processing

---
 client.s   | 173 +++++++++++++++++++++++++++++++++++++----------------
 main.s     |  17 ++++--
 readline.s |  51 +++++-----------
 3 files changed, 149 insertions(+), 92 deletions(-)

diff --git a/client.s b/client.s
index 073f42c..557898f 100644
--- a/client.s
+++ b/client.s
@@ -1,75 +1,146 @@
+; Returns:
+; rax - 0
 ; Errors (in rax):
-; 0 - EOF
-; -1024 - line too long
-; other negative - from read(2)
+; rax - 0 - EOF
+; rax - -1024 - line too long
+; rax - other negative - from read(2)
 ; Arguments:
 ; rdi - fd
 ; rsi - address of client within clients array
 ; rdx - pollfds index
+; Variables:
+; rbx - pollfds index
+; r12 - client buffer
+; r13 - start of line scanning
+; r14 - client buffer end
 client__pollin:
-    push rdi
-    push rsi
-    push rdx
+    push rbx
+    push r12
+    push r13
+    push r14
 
-    mov r10, 0
-    mov r10b, [rsi + 4 + 2]  ; buffer_len
+    mov rbx, rdx
+    mov r12, rsi
+    add r12, OFFSET_CLIENT_BUFFER
+    mov r13, 0
+    mov r13b, [rsi + OFFSET_CLIENT_BUFFER_LEN]
+    add r13, r12
+    mov r14, r12
+    add r14, 255
 
-    mov rdx, 0
-    mov dl, 255
-    sub dl, r10b  ; rdx = 255 - buffer_len
-    add rsi, 8
-    add rsi, r10
-    call readline
-
-    mov r10, rdi
-
-    pop rdx
-    pop rsi
-    pop rdi
+    mov rax, SYS_READ
+    mov rsi, r13
+    mov rdx, 255
+    sub rdx, [rsi + OFFSET_CLIENT_BUFFER_LEN]
+    syscall
 
     cmp rax, 0
-    jle return
+    jle client__pollin__return  ; TODO verify this shit
 
-    add [rsi + 4 + 2], al  ; buffer_len += rax
+    ; buffer_len += read(2) length
+    add [rsi + OFFSET_CLIENT_BUFFER_LEN], al
 
-    cmp r10, 0
-    jg client__pollin__complete_line
+    mov rdi, r13
+    mov rsi, rax
+    call scanline
 
-    add [rsi + 4 + 1], al  ; line_len += rax
-    ret
+    cmp rax, 0
+    je client__pollin__no_line
 
-client__pollin__complete_line:
-    add [rsi + 4 + 1], r10b  ; line_len += r10
+    mov rax, 1
     mov word [pollfds + rdx * pollfd_size + 4], POLLOUT
+
+client__pollin__return:
+    pop r14
+    pop r13
+    pop r12
+    pop rbx
     ret
 
+client__pollin__no_line:
+    mov r10, r13
+    add r10, rax
+    cmp r10, r14
+    jge client__pollin__line_too_long
+    mov rax, 1
+    jmp client__pollin__return
+
+client__pollin__line_too_long:
+    mov rax, -1024
+    jmp client__pollin__return
+
+client__pollin__line:
+    pop rdx
+    mov word [pollfds + rdx * pollfd_size + 4], POLLOUT
+    push rdx
+    ; TODO do something to the line
+    ; problem: we can't write here because the socket may
+    ; block;
+    ; how do we deal with multiple lines?
+    ; XXX call read(2) trying to fill the whole buffer
+    ; - if we have at least 1 line,
+    ;   update the state and go back to main loop;
+    ;   do whatever logic is necessary until we need to
+    ;   call read(2) or write(2) next, ie nothing;
+    ;   set POLLOUT and process line(s) in write(2) block
+    ; - if we have no line and we filled the buffer,
+    ;   return -1024
+    ; - if we have no line, go back to main loop
+    ; we could have multiple lines, and that's okay
+    ; XXX so this plan will work actually
+    ; XXX should we do logic here or in the write(2) block?
+    ; probably there, so here we just accumulate lines
+    ret
+
+; Returns:
+; rax - non-negative from write(2)
+; Errors:
+; rax - -1024 - no line was buffered
+; rax - other negative - from write(2)
+; Arguments:
 ; rdi - fd
 ; rsi - address of client within clients array
 ; rdx - pollfds index
+; Variables:
+; rbx - pollfds index
+; r12 - address of client within clients array
+; r13 - line length
+; r11 - client buffer end of text after call to write(2)
 client__pollout:
-    push rdi
-    push rsi
-    push rdx
+    push rbx
+    push r12
+    push r13
+
+    mov rbx, rdx
+    mov r12, rsi
+
+    mov rdi, r12
+    mov rsi, 255
+    call scanline
+
+    ; poll(2)'d for POLLOUT but we had no line buffered;
+    ; should be impossible
+    cmp rax, 0
+    mov rax, -1024
+    je client__pollout__return
+
+    mov r13, rax
 
     mov rax, SYS_WRITE
-    mov rdx, 0
-    mov dl, [rsi + 4 + 1]
-    add rsi, 8
+    mov rsi, r12
+    add rsi, OFFSET_CLIENT_BUFFER
+    mov rdx, r13
     syscall
 
-    pop rdx
-    pop rsi
-    pop rdi
-
+    ; error from write(2)
     cmp rax, 0
-    jl return
+    jl client__pollout__return
 
-    mov r10, rsi
-    add r10, 8
+    mov r10, r12
     mov r11, 0
-    mov r11b, [rsi + 4 + 2]
+    mov r11b, [r12 + OFFSET_CLIENT_BUFFER_LEN]
+    add r11, r12
     sub r11, rax
-    add r11, r10
 
 client__pollout__shunt:
     cmp r10, r11
@@ -80,14 +151,16 @@ client__pollout__shunt:
     jmp client__pollout__shunt
 
 client__pollout__shunt__finished:
-    sub [rsi + 4 + 2], al  ; buffer_len -= rax
-    sub [rsi + 4 + 1], al  ; line_len -= rax
-    cmp byte [rsi + 4 + 1], 0
-    je client__pollout__wrote_line
-    ret
+    ; buffer_len -= rax
+    sub [r12 + OFFSET_CLIENT_BUFFER_LEN], al
+    cmp rax, r13
+    jl client__pollout__return
+    mov word [pollfds + rbx * pollfd_size + 4], POLLIN
 
-client__pollout__wrote_line:
-    mov word [pollfds + rdx * pollfd_size + 4], POLLIN
+client__pollout__return:
+    pop r13
+    pop r12
+    pop rbx
     ret
 
 ; rdi - fd
diff --git a/main.s b/main.s
index 190f240..3331bd8 100644
--- a/main.s
+++ b/main.s
@@ -30,11 +30,18 @@ SHUT_RDWR       equ 2
 ;STATE_READING   equ 0
 ;STATE_WRITING   equ 1
 
+; offsets into client struct
+OFFSET_CLIENT_FD           equ 0
+OFFSET_CLIENT_STATE        equ 4
+OFFSET_CLIENT_BUFFER_LEN   equ 5
+OFFSET_CLIENT_BUFFER       equ 6
+
 pollfd_size         equ 4 + 2 + 2  ; $ man 2 poll
 pollfds_capacity    equ 100
 
-; fd, state, line_len, buffer_len, uri_len, buffer, uri
-client_size         equ 4 + 4 + 255 + 255
+client_buffer_size  equ 255
+; fd, state, buffer_len, buffer
+client_size         equ 4 + 2 + client_buffer_size
 clients_capacity    equ pollfds_capacity - 1
 
 SECTION .data
@@ -109,7 +116,7 @@ scan__found__client:
     mov r10w, r14w
     and r10w, POLLERR | POLLNVAL
     cmp r10w, 0
-    jne _client__error
+    jne _client__error_or_eof
 
     mov r10w, r14w
     and r10w, POLLIN
@@ -125,7 +132,7 @@ scan__found__client:
     add r15, 1
     jmp scan__loop
 
-_client__error:
+_client__error_or_eof:
     mov rdi, r12
     call clients__remove
 
@@ -142,7 +149,7 @@ _client__pollin:
     mov rdx, r15
     call client__pollin
     cmp rax, 0
-    jle _client__error
+    jle _client__error_or_eof
     add r15, rax
     jmp scan__loop
 
diff --git a/readline.s b/readline.s
index 35e30dd..b1698a0 100644
--- a/readline.s
+++ b/readline.s
@@ -1,43 +1,20 @@
 ; Returns:
-; rax - read(2) length
-; rdi - if line is complete, line length;
-;       if line was incomplete, 0;
-;       if EOF or read(2) returned an error, undefined
-; Errors (in rax):
-; -1024 - line too long
-; other negative - error from read(2)
-; 0 - EOF from read(2)
+; rax - if line: line length; otherwise: 0
 ; Arguments:
-; rdi - fd
-; rsi - buffer
-; rdx - max length
-readline:
-    push rsi
-    push rdx
-    mov rax, SYS_READ
-    syscall
-    pop rdx
-    pop rsi
-    cmp rax, 0
-    jl return
-    mov r10, 0
+; rdi - buffer
+; rsi - max length
+scanline:
+    mov rax, 0
 
-readline__scan:
-    cmp r10, rdx
-    jge readline__overflow
-    cmp r10, rax
-    jge readline__incomplete_line
-    mov r11b, [rsi + r10]
-    add r10, 1
-    cmp r11b, 0x0a  ; '\n'
-    jne readline__scan
-    mov rdi, r10
+scanline__loop:
+    cmp rax, rsi
+    jge scanline__incomplete_line
+    mov r10b, [rdi + rax]
+    add rax, 1
+    cmp r10b, 0x0a  ; '\n'
+    jne scanline__loop
     ret
 
-readline__incomplete_line:
-    mov rdi, 0
-    ret
-
-readline__overflow:
-    mov rax, -1024
+scanline__incomplete_line:
+    mov rax, 0
     ret

From cbde04fc7c1d0237af2faad74db7bbb1fd97c89d Mon Sep 17 00:00:00 2001
From: root <>
Date: Sat, 17 May 2025 08:15:07 +0000
Subject: [PATCH 05/10] client__pollin diagram

---
 client-pollin.txt | 50 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 50 insertions(+)
 create mode 100644 client-pollin.txt

diff --git a/client-pollin.txt b/client-pollin.txt
new file mode 100644
index 0000000..2e0f6b1
--- /dev/null
+++ b/client-pollin.txt
@@ -0,0 +1,50 @@
+client buffer
+
+##########++++++++A++++++A+++A++..........
+^         ^       ^      ^   ^  ^         ^
+|         |       |      |   |  |         |
+|         |       |      |   |  |         +-- end of buffer
+|         |       |      |   |  |
+|         |       |      |   |  +-- end of read(2) bytes
+|         |       |      |   |
+|         |       +-- newlines
+|         |              
+|         +-- start of line scanning
+|
++-- start of line
+
+
+# original idea
+
+- consume many lines in client__pollin
+- problem: in the echo server case, we want to write each line but a call to
+  write(2) may block, so we can't do line processing in client__pollin
+- solution: process lines in client__pollout
+
+i = scan_start
+while i < buffer_end:
+    byte = mem[i]
+    i += 1
+    if byte == '\n':
+        handle(line_start, i - line_start)
+        line_start = i
+        scan_start = i
+
+if line_start + read(2)_length >= buffer_end:
+    out_of_memory()
+    return
+
+i = line_start
+while i < line_start + read(2)_length:
+    mem[i - line_start + buffer_start] = mem[i]
+    i += 1
+
+
+# better idea
+
+in pollin handler, just read once
+indicate to the caller:
+- we read a complete line (ie 0x0a is present)
+- we read an incomplete non-empty line
+- we read nothing (EOF)
+- error from read(2)

From 05f614b224dd13623e240a9075b0ade2b312b982 Mon Sep 17 00:00:00 2001
From: root <>
Date: Sat, 17 May 2025 08:20:30 +0000
Subject: [PATCH 06/10] typo: writing POLLOUT beyond pollfds array bounds

---
 client.s | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/client.s b/client.s
index 557898f..a8d670f 100644
--- a/client.s
+++ b/client.s
@@ -48,7 +48,7 @@ client__pollin:
     je client__pollin__no_line
 
     mov rax, 1
-    mov word [pollfds + rdx * pollfd_size + 4], POLLOUT
+    mov word [pollfds + rbx * pollfd_size + 4], POLLOUT
 
 client__pollin__return:
     pop r14

From 65dc844f54aaa51aebd4db2078e138ac7aca6020 Mon Sep 17 00:00:00 2001
From: root <>
Date: Sat, 17 May 2025 08:27:15 +0000
Subject: [PATCH 07/10] typo: writing buffer_len to somewhere undefined

---
 client.s | 24 +-----------------------
 1 file changed, 1 insertion(+), 23 deletions(-)

diff --git a/client.s b/client.s
index a8d670f..85fed8d 100644
--- a/client.s
+++ b/client.s
@@ -38,7 +38,7 @@ client__pollin:
     jle client__pollin__return  ; TODO verify this shit
 
     ; buffer_len += read(2) length
-    add [rsi + OFFSET_CLIENT_BUFFER_LEN], al
+    add [r12 - OFFSET_CLIENT_BUFFER + OFFSET_CLIENT_BUFFER_LEN], al
 
     mov rdi, r13
     mov rsi, rax
@@ -69,28 +69,6 @@ client__pollin__line_too_long:
     mov rax, -1024
     jmp client__pollin__return
 
-client__pollin__line:
-    pop rdx
-    mov word [pollfds + rdx * pollfd_size + 4], POLLOUT
-    push rdx
-    ; TODO do something to the line
-    ; problem: we can't write here because the socket may
-    ; block;
-    ; how do we deal with multiple lines?
-    ; XXX call read(2) trying to fill the whole buffer
-    ; - if we have at least 1 line,
-    ;   update the state and go back to main loop;
-    ;   do whatever logic is necessary until we need to
-    ;   call read(2) or write(2) next, ie nothing;
-    ;   set POLLOUT and process line(s) in write(2) block
-    ; - if we have no line and we filled the buffer,
-    ;   return -1024
-    ; - if we have no line, go back to main loop
-    ; we could have multiple lines, and that's okay
-    ; XXX so this plan will work actually
-    ; XXX should we do logic here or in the write(2) block?
-    ; probably there, so here we just accumulate lines
-    ret
 
 ; Returns:
 ; rax - non-negative from write(2)

From 76b53ad30c5b9d1defa63bc3ef91b378e5e27c92 Mon Sep 17 00:00:00 2001
From: root <>
Date: Sat, 17 May 2025 08:37:23 +0000
Subject: [PATCH 08/10] fix: negative line length, write(2) to bad fd

---
 client.s | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/client.s b/client.s
index 85fed8d..fa59078 100644
--- a/client.s
+++ b/client.s
@@ -92,15 +92,17 @@ client__pollout:
     mov rbx, rdx
     mov r12, rsi
 
+    push rdi
     mov rdi, r12
+    add rdi, OFFSET_CLIENT_BUFFER
     mov rsi, 255
     call scanline
+    pop rdi
 
     ; poll(2)'d for POLLOUT but we had no line buffered;
     ; should be impossible
     cmp rax, 0
-    mov rax, -1024
-    je client__pollout__return
+    je client__pollout__no_line
 
     mov r13, rax
 
@@ -135,6 +137,9 @@ client__pollout__shunt__finished:
     jl client__pollout__return
     mov word [pollfds + rbx * pollfd_size + 4], POLLIN
 
+client__pollout__no_line:
+    mov rax, -1024
+
 client__pollout__return:
     pop r13
     pop r12

From c90622c5647bd45a1ee21b5453cbdf2192873f46 Mon Sep 17 00:00:00 2001
From: root <>
Date: Sat, 17 May 2025 08:46:26 +0000
Subject: [PATCH 09/10] fix: client__pollout always returning error, not
 handling error

---
 client.s | 7 ++++---
 main.s   | 2 ++
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/client.s b/client.s
index fa59078..38e08cb 100644
--- a/client.s
+++ b/client.s
@@ -137,15 +137,16 @@ client__pollout__shunt__finished:
     jl client__pollout__return
     mov word [pollfds + rbx * pollfd_size + 4], POLLIN
 
-client__pollout__no_line:
-    mov rax, -1024
-
 client__pollout__return:
     pop r13
     pop r12
     pop rbx
     ret
 
+client__pollout__no_line:
+    mov rax, -1024
+    jmp client__pollout__return
+
 ; rdi - fd
 client__shutdown_close:
     mov rax, SYS_SHUTDOWN
diff --git a/main.s b/main.s
index 3331bd8..ffa5496 100644
--- a/main.s
+++ b/main.s
@@ -158,6 +158,8 @@ _client__pollout:
     mov rsi, r12
     mov rdx, r15
     call client__pollout
+    cmp rax, 0
+    jle _client__error_or_eof
     add r15, 1
     jmp scan__loop
 

From 44cf92c998c46811753c790c40157e490a5e0388 Mon Sep 17 00:00:00 2001
From: root <>
Date: Sat, 17 May 2025 09:07:03 +0000
Subject: [PATCH 10/10] fix: read(2)ing too many bytes

---
 client.s | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/client.s b/client.s
index 38e08cb..d3a1e7a 100644
--- a/client.s
+++ b/client.s
@@ -31,7 +31,7 @@ client__pollin:
     mov rax, SYS_READ
     mov rsi, r13
     mov rdx, 255
-    sub rdx, [rsi + OFFSET_CLIENT_BUFFER_LEN]
+    sub dl, [rsi + OFFSET_CLIENT_BUFFER_LEN]
     syscall
 
     cmp rax, 0