diff options
| author | Murilo Ijanc <[email protected]> | 2026-07-27 20:42:22 -0300 |
|---|---|---|
| committer | Murilo Ijanc <[email protected]> | 2026-07-27 20:44:20 -0300 |
| commit | c3b525537e944064869a2cd6495733388144ce64 (patch) | |
| tree | a30fa16568ffe470876f670e6bd75873c201df1a /src | |
| parent | b7b08a7d29d84de187dd8a55e0c94de66b07a572 (diff) | |
| download | asm-main.tar.gz | |
A CEP that fails is almost never a random number. It is someone who knows
the city and guessed the last digits, and the guess is usually the -000 that
957 municipalities never received. The API answers those 404s with a faixa
object naming the municipality that owns the band, which turns "not found"
from a dead end into something the caller can act on - and the caller cannot
reach it through this library, because the 404 path never looked at the body
at all.
cepis_error is where a 404 already reports itself, so faixa goes there, as
inline storage rather than a pointer: the struct is documented as owning
nothing and declared on the caller's stack, and that stays true. It costs
sizeof(cepis_error), 272 bytes to 384, so 0.2.0 needs a recompile.
The parser needed one new primitive. cepis__json_field could only pull
scalars out of a flat object; splitting it over cepis__json_find, which
returns a pointer to a value of any shape, reaches one level down without
building a document tree - the same depth cepis__geo_parse already needs for
geo. Absence is the case that matters: no faixa, an empty one, or one that
is not an object all leave the error byte-for-byte what it was before.
cep_ini and cep_fim are deliberately not presented as a range. They are the
lowest and highest CEP that exist, the band is sparse - Areal's 118 CEPs are
scattered over ~4900 numbers - and ceps is the only honest count.
Diffstat (limited to 'src')
| -rw-r--r-- | src/client.S | 279 | ||||
| -rw-r--r-- | src/defs.inc | 14 |
2 files changed, 237 insertions, 56 deletions
diff --git a/src/client.S b/src/client.S index 469f0f2..5cc7c77 100644 --- a/src/client.S +++ b/src/client.S @@ -24,9 +24,10 @@ #define LM_J -168 #define LM_K -176 -#define JF_KEY -48 -#define JF_VAL -56 -#define JF_OUT -64 +#define FD_KEY -48 + +#define NF_NOTE -240 +#define NF_NOTELEN 192 .text @@ -56,10 +57,22 @@ FUNC cepis__err_reset movl $0, E_HTTP_STATUS(%rdi) movq $0, E_RETRY_AFTER(%rdi) movb $0, E_MESSAGE(%rdi) + jmp cepis__faixa_clear .Ler_out: ret ENDFUNC cepis__err_reset +/* Blank the faixa of the error at %rdi, which must not be NULL. Leaf. */ +FUNC cepis__faixa_clear + movl $0, E_FAIXA+F_PRESENT(%rdi) + movb $0, E_FAIXA+F_LOCALIDADE(%rdi) + movb $0, E_FAIXA+F_UF(%rdi) + movb $0, E_FAIXA+F_CEP_INI(%rdi) + movb $0, E_FAIXA+F_CEP_FIM(%rdi) + movq $0, E_FAIXA+F_CEPS(%rdi) + ret +ENDFUNC cepis__faixa_clear + FUNC cepis__err_set PROLOGUE 8 test %rdi, %rdi @@ -76,88 +89,237 @@ FUNC cepis__err_set ret ENDFUNC cepis__err_set -FUNC cepis__json_field - PROLOGUE 40 - mov %rsi, %r15 - movq $0, JF_KEY(%rbp) - movq $0, JF_VAL(%rbp) - movq $0, JF_OUT(%rbp) +/* + * cepis__json_find(json, key) - point at the value of `key` in the object + * starting at `json`, with leading whitespace already stepped over, or 0 when + * the object has no such key. The value is not copied and not validated: the + * caller decides what to do with whatever byte it finds there, which is how + * a nested object is reached without a document tree. + * + * Scanning stops at the first key that matches and at the closing brace, so + * calling it on an object embedded in a larger document is safe. + */ +FUNC cepis__json_find + PROLOGUE 24 + mov %rsi, %r13 + movq $0, FD_KEY(%rbp) call cepis__js_ws mov %rax, %r12 cmpb $'{', (%r12) - jne .Ljf_out + jne .Lfd_none lea 1(%r12), %rdi call cepis__js_ws mov %rax, %r12 cmpb $'}', (%r12) - je .Ljf_out -.Ljf_pair: + je .Lfd_none +.Lfd_pair: cmpb $'"', (%r12) - jne .Ljf_out + jne .Lfd_none mov %r12, %rdi - lea JF_KEY(%rbp), %rsi + lea FD_KEY(%rbp), %rsi call cepis__js_string test %rax, %rax - jz .Ljf_out + jz .Lfd_none mov %rax, %rdi call cepis__js_ws mov %rax, %r12 cmpb $':', (%r12) - jne .Ljf_out + jne .Lfd_none lea 1(%r12), %rdi call cepis__js_ws mov %rax, %r12 - cmpq $0, JF_OUT(%rbp) - jne .Ljf_skip - mov JF_KEY(%rbp), %rdi - mov %r15, %rsi + mov FD_KEY(%rbp), %rdi + mov %r13, %rsi call strcmp@PLT test %eax, %eax - jnz .Ljf_skip - movzbl (%r12), %eax - cmp $'{', %al - je .Ljf_skip - cmp $'[', %al - je .Ljf_skip - mov %r12, %rdi - lea JF_VAL(%rbp), %rsi - call cepis__js_scalar_text - test %rax, %rax - jz .Ljf_out - mov %rax, %r12 - mov JF_VAL(%rbp), %rax - mov %rax, JF_OUT(%rbp) - jmp .Ljf_next -.Ljf_skip: + jz .Lfd_hit mov %r12, %rdi call cepis__js_skip test %rax, %rax - jz .Ljf_out + jz .Lfd_none mov %rax, %r12 -.Ljf_next: - mov JF_KEY(%rbp), %rdi + mov FD_KEY(%rbp), %rdi call free@PLT - movq $0, JF_KEY(%rbp) + movq $0, FD_KEY(%rbp) mov %r12, %rdi call cepis__js_ws mov %rax, %r12 cmpb $',', (%r12) - jne .Ljf_out + jne .Lfd_none lea 1(%r12), %rdi call cepis__js_ws mov %rax, %r12 - jmp .Ljf_pair -.Ljf_out: - mov JF_KEY(%rbp), %rdi + jmp .Lfd_pair +.Lfd_hit: + mov FD_KEY(%rbp), %rdi + call free@PLT + movq $0, FD_KEY(%rbp) + mov %r12, %rax + EPILOGUE + ret +.Lfd_none: + mov FD_KEY(%rbp), %rdi test %rdi, %rdi - jz .Ljf_ret + jz .Lfd_ret call free@PLT -.Ljf_ret: - mov JF_OUT(%rbp), %rax +.Lfd_ret: + xor %eax, %eax + EPILOGUE + ret +ENDFUNC cepis__json_find + +/* + * cepis__json_field(json, key) - the scalar value of `key` as a fresh string + * the caller frees, or 0 when the key is absent or holds an object or array. + */ +FUNC cepis__json_field + PROLOGUE 24 + movq $0, LOCAL0(%rbp) + call cepis__json_find + test %rax, %rax + jz .Ljf_none + movzbl (%rax), %ecx + cmp $'{', %cl + je .Ljf_none + cmp $'[', %cl + je .Ljf_none + mov %rax, %rdi + lea LOCAL0(%rbp), %rsi + call cepis__js_scalar_text + mov LOCAL0(%rbp), %rax + EPILOGUE + ret +.Ljf_none: + xor %eax, %eax EPILOGUE ret ENDFUNC cepis__json_field +/* + * cepis__faixa_str(obj, key, dst, size) - copy the scalar `key` of `obj` into + * the fixed buffer `dst`, truncating rather than overflowing. `dst` is left + * untouched when the key is absent, so the caller's blank stands. + */ +FUNC cepis__faixa_str + PROLOGUE 8 + mov %rdx, %r12 + mov %rcx, %r13 + call cepis__json_field + test %rax, %rax + jz .Lfx_out + mov %rax, %rbx + mov %r12, %rdi + mov %r13, %rsi + lea .Ls_pct_s(%rip), %rdx + mov %rbx, %rcx + xor %eax, %eax + call snprintf@PLT + mov %rbx, %rdi + call free@PLT +.Lfx_out: + EPILOGUE + ret +ENDFUNC cepis__faixa_str + +/* + * cepis__err_notfound(err, cep, body) - fill `err` for a 404. `err` may be + * NULL and `body` may be NULL. + * + * The 404 body optionally carries a `faixa` object naming the municipality + * that owns the band the CEP falls in. It is context for the failure, not an + * address: the status stays CEPIS_ERR_NOT_FOUND either way. When the object + * is missing, empty, or not an object, the message is byte-for-byte the one + * this library has always produced. + */ +FUNC cepis__err_notfound + PROLOGUE 216 + test %rdi, %rdi + jz .Lnf_ret + mov %rdi, %rbx + mov %rsi, %r14 + mov %rdx, %r15 + movb $0, NF_NOTE(%rbp) + test %r15, %r15 + jz .Lnf_emit + mov %r15, %rdi + lea .Ls_faixa(%rip), %rsi + call cepis__json_find + test %rax, %rax + jz .Lnf_emit + mov %rax, %r12 + cmpb $'{', (%r12) + jne .Lnf_emit + mov %r12, %rdi + lea .Ls_k_localidade(%rip), %rsi + lea E_FAIXA+F_LOCALIDADE(%rbx), %rdx + mov $F_LOCALIDADE_MAX, %ecx + call cepis__faixa_str + mov %r12, %rdi + lea .Ls_k_uf(%rip), %rsi + lea E_FAIXA+F_UF(%rbx), %rdx + mov $F_UF_MAX, %ecx + call cepis__faixa_str + mov %r12, %rdi + lea .Ls_k_cep_ini(%rip), %rsi + lea E_FAIXA+F_CEP_INI(%rbx), %rdx + mov $F_CEP_MAX, %ecx + call cepis__faixa_str + mov %r12, %rdi + lea .Ls_k_cep_fim(%rip), %rsi + lea E_FAIXA+F_CEP_FIM(%rbx), %rdx + mov $F_CEP_MAX, %ecx + call cepis__faixa_str + mov %r12, %rdi + lea .Ls_k_ceps(%rip), %rsi + call cepis__json_find + test %rax, %rax + jz .Lnf_check + movzbl (%rax), %ecx + cmp $'{', %cl + je .Lnf_check + cmp $'[', %cl + je .Lnf_check + mov %rax, %rdi + lea LOCAL0(%rbp), %rsi + call cepis__js_number + test %rax, %rax + jz .Lnf_check + cvttsd2si LOCAL0(%rbp), %rax + mov %rax, E_FAIXA+F_CEPS(%rbx) +.Lnf_check: + cmpb $0, E_FAIXA+F_LOCALIDADE(%rbx) + je .Lnf_clear + movl $1, E_FAIXA+F_PRESENT(%rbx) + lea NF_NOTE(%rbp), %rdi + mov $NF_NOTELEN, %esi + lea .Lfmt_faixa(%rip), %rdx + lea E_FAIXA+F_LOCALIDADE(%rbx), %rcx + lea E_FAIXA+F_UF(%rbx), %r8 + mov E_FAIXA+F_CEPS(%rbx), %r9 + lea E_FAIXA+F_CEP_FIM(%rbx), %rax + push %rax + lea E_FAIXA+F_CEP_INI(%rbx), %rax + push %rax + xor %eax, %eax + call snprintf@PLT + add $16, %rsp + jmp .Lnf_emit +.Lnf_clear: + mov %rbx, %rdi + call cepis__faixa_clear + movb $0, NF_NOTE(%rbp) +.Lnf_emit: + mov %rbx, %rdi + mov $CEPIS_ERR_NOT_FOUND, %esi + lea .Lfmt_notfound(%rip), %rdx + mov %r14, %rcx + lea NF_NOTE(%rbp), %r8 + call cepis__err_set +.Lnf_ret: + EPILOGUE + ret +ENDFUNC cepis__err_notfound + PUBFUNC cepis_client_new PROLOGUE 40 mov %rsi, %r14 @@ -456,11 +618,9 @@ PUBFUNC cepis_lookup jmp .Llk_ret .Llk_notfound: mov %r15, %rdi - mov $CEPIS_ERR_NOT_FOUND, %esi - lea .Lfmt_notfound(%rip), %rdx - lea LK_TMP(%rbp), %rcx - xor %r8d, %r8d - call cepis__err_set + lea LK_TMP(%rbp), %rsi + mov LK_BODY(%rbp), %rdx + call cepis__err_notfound mov $CEPIS_ERR_NOT_FOUND, %r12d jmp .Llk_ret .Llk_badcep: @@ -830,16 +990,25 @@ PUBFUNC cepis_lookup_many ENDFUNC cepis_lookup_many .section .rodata -.Ls_version: .asciz "0.1.0" +.Ls_version: .asciz "0.2.0" .Ls_default_url: .asciz "https://cep.is" -.Ls_default_ua: .asciz "cepis-asm/0.1.0 (+https://cep.is)" +.Ls_default_ua: .asciz "cepis-asm/0.2.0 (+https://cep.is)" .Ls_health_path: .asciz "/health" .Ls_csv_path: .asciz "/csv?ceps=" .Ls_mensagem: .asciz "mensagem" +.Ls_faixa: .asciz "faixa" +.Ls_k_localidade: .asciz "localidade" +.Ls_k_uf: .asciz "uf" +.Ls_k_cep_ini: .asciz "cep_ini" +.Ls_k_cep_fim: .asciz "cep_fim" +.Ls_k_ceps: .asciz "ceps" +.Ls_pct_s: .asciz "%s" .Ls_rejected: .asciz "rejected by the API" .Ls_nullstr: .asciz "(null)" .Ls_unknown: .asciz "unknown error" -.Lfmt_notfound: .asciz "CEP %s does not exist in the DNE database" +.Lfmt_notfound: .asciz "CEP %s does not exist in the DNE database%s" +.Lfmt_faixa: + .asciz " (%s/%s has %lld CEPs, the lowest %s and the highest %s)" .Lfmt_badcep: .asciz "CEP %s rejected: %s" .Lfmt_notdigits: .asciz "CEP must be 8 digits, got \"%s\"" .Lfmt_badjson: .asciz "unparseable response from %s" diff --git a/src/defs.inc b/src/defs.inc index 4a64e0e..b0a38bf 100644 --- a/src/defs.inc +++ b/src/defs.inc @@ -43,12 +43,24 @@ #define H_RAW 40 #define H_SIZE 48 +#define F_PRESENT 0 +#define F_LOCALIDADE 4 +#define F_LOCALIDADE_MAX 72 +#define F_UF 76 +#define F_UF_MAX 3 +#define F_CEP_INI 79 +#define F_CEP_FIM 88 +#define F_CEP_MAX 9 +#define F_CEPS 104 +#define F_SIZE 112 + #define E_STATUS 0 #define E_HTTP_STATUS 4 #define E_RETRY_AFTER 8 #define E_MESSAGE 16 #define E_MSGLEN 256 -#define E_SIZE 272 +#define E_FAIXA 272 +#define E_SIZE 384 #define C_BASE_URL 0 #define C_TIMEOUT 8 |