Capítulo 24 14 min de leitura

Codificação, criptografia e compressão

A biblioteca padrão do Glide fornece as primitivas de nível de byte que a maioria dos protocolos de rede precisa: codecs de texto Base64 e hex, um ByteBuffer expansível, hashing SHA-256/SHA-1 e HMAC-SHA-256, inflate gzip/zlib, e um extrator de tar em memória. Todos eles tratam uma string do Glide como uma sequência opaca de bytes (a mesma convenção em todo o grupo de módulos).

Import

import stdlib::base64::*;
import stdlib::hex::*;
import stdlib::bytes::*;
import stdlib::crypto::*;
import stdlib::compress::*;
import stdlib::tar::*;

Cada seção ## abaixo corresponde a um módulo; importe apenas os que você utilizar.

Visão geral da superfície pública

Módulo Itens
base64 b64_encode, b64_decode
hex hex_encode, hex_decode
bytes struct ByteBuffer + new/free/len/cap/push/push_str/at/clear/as_string
crypto sha256, sha256_hex, sha1, sha1_hex, hmac_sha256, hmac_sha256_hex
compress gzip_decode
tar tar_extract

base64

Base64 padrão (RFC 4648, com padding =). A codificação sempre retorna uma string; a decodificação retorna !string porque pode rejeitar entradas malformadas.

Função Assinatura Descrição
b64_encode pub fn b64_encode(s: string) -> string Codifica bytes em Base64. O comprimento da saída é sempre múltiplo de quatro.
b64_decode pub fn b64_decode(s: string) -> !string Decodifica Base64. Retorna err para caractere inválido no alfabeto, padding incorreto ou comprimento inválido.

b64_encode mapeia cada grupo de três bytes de entrada para quatro caracteres de saída, completando o grupo final com = para que a saída seja sempre múltiplo de quatro. b64_decode é sua inversa exata; valida o padding e o alfabeto e rejeita qualquer caractere estranho — incluindo espaços em branco. O alfabeto é o padrão (A-Z a-z 0-9 + /); não há variante URL-safe (- / _).

Erros de decodificação aparecem pelo resultado !string:

Condição Mensagem de err
Caractere fora do alfabeto Base64 "invalid base64 character"
Byte não-= após início do padding "invalid base64 padding"
Comprimento não-pad % 4 == 1 "invalid base64 length"
import stdlib::base64::*;

fn main() -> i32 {
    let enc: string = b64_encode("Hello");   // "SGVsbG8="
    println!(enc);
    println!(b64_encode("foobar"));          // "Zm9vYmFy"
    println!(b64_encode(""));                // "" (entrada vazia, saída vazia)

    let r: !string = b64_decode(enc);
    if r.ok {
        println!(r.val);                     // "Hello"
    }

    // Erros: um por modo de falha.
    let e1: !string = b64_decode("***");     // byte fora do alfabeto
    if !e1.ok { println!(e1.err); }          // "invalid base64 character"

    let e2: !string = b64_decode("AB=C");    // dado após byte de padding
    if !e2.ok { println!(e2.err); }          // "invalid base64 padding"

    let e3: !string = b64_decode("A");       // comprimento % 4 == 1
    if !e3.ok { println!(e3.err); }          // "invalid base64 length"

    // Valor padrão em caso de erro com `??`.
    let dec: string = b64_decode("not!valid") ?? "fallback";
    println!(dec);                           // "fallback"

    return 0;
}

hex

Codec hexadecimal em minúsculas — dois dígitos hex por byte de entrada. Assim como base64, a codificação é infalível e a decodificação retorna !string.

Função Assinatura Descrição
hex_encode pub fn hex_encode(s: string) -> string Hex em minúsculas de cada byte (dois dígitos cada).
hex_decode pub fn hex_decode(s: string) -> !string Decodifica hex. Aceita dígitos maiúsculos e minúsculos.

hex_encode sempre emite minúsculas (a-f). hex_decode aceita ambos os casos ("414243" e "414243".to_upper() decodificam para os mesmos bytes) e retorna err em:

Condição Mensagem de err
Entrada com comprimento ímpar "odd length"
Qualquer byte não-hex "non-hex char"
import stdlib::hex::*;

fn main() -> !i32 {
    let enc: string = hex_encode("ABC");     // "414243"
    println!(enc);
    println!(hex_encode(""));                // "" (vazio)

    let dec: string = hex_decode(enc)?;      // propaga em caso de erro
    println!(dec);                           // "ABC"

    // Decodificação sem distinção de maiúsculas/minúsculas.
    let up: string = hex_decode("414243")?;
    let lo: string = hex_decode("414243".to_lower())?;
    if up.eq(lo) { println!("case-insensitive ok"); }

    let e1: !string = hex_decode("abc");     // número ímpar de dígitos
    if !e1.ok { println!(e1.err); }          // "odd length"

    let e2: !string = hex_decode("xy");      // não são dígitos hex
    if !e2.ok { println!(e2.err); }          // "non-hex char"

    return ok(0);
}

bytes — ByteBuffer

Um buffer de bytes mutável e expansível, respaldado por memória alocada com malloc. Use-o para construir payloads binários (formatos de rede, corpos HTTP de saída) sem a alocação por-concat que os operadores de string disparam. A capacidade dobra no crescimento, portanto as inserções têm custo amortizado O(1).

Struct

pub struct ByteBuffer {
    pub data: *u8,   // armazenamento bruto de bytes (passe a syscall de escrita do lado C)
    pub len: i32,    // número de bytes válidos; sempre <= cap
    pub cap: i32,    // capacidade alocada; sempre >= len
}

data é público de propósito: protocolos binários (HTTP/2, TLS, formatos de rede personalizados) podem passar o ponteiro mais len diretamente para um write em C.

Métodos

Método Assinatura Descrição
new pub fn new() -> *ByteBuffer Aloca um buffer vazio com capacidade inicial de 16 bytes.
free pub fn free(self: *ByteBuffer) Libera o buffer de dados e a struct. O ponteiro fica inválido após isso.
len pub fn len(self: *ByteBuffer) -> i32 Bytes atualmente no buffer.
cap pub fn cap(self: *ByteBuffer) -> i32 Capacidade alocada (>= len()).
push pub fn push(self: *ByteBuffer, b: u8) Acrescenta um byte; realoca quando cheio.
push_str pub fn push_str(self: *ByteBuffer, s: string) Acrescenta cada byte de s (sem NUL ao final).
at pub fn at(self: *ByteBuffer, i: i32) -> u8 Lê o byte no índice i. Sem verificação de limites.
clear pub fn clear(self: *ByteBuffer) Redefine len para 0; capacidade preservada para reutilização.
as_string pub fn as_string(self: *ByteBuffer) -> string Empréstimo como string terminada em NUL que apelida data.

Construindo e inspecionando um buffer

import stdlib::bytes::*;

fn main() -> i32 {
    let b: *ByteBuffer = ByteBuffer::new();
    defer b.free();

    println!(b.cap());            // 16 (capacidade inicial)

    b.push_str("hello, ");
    b.push_str("world");
    b.push(33 as u8);             // '!'

    println!(b.len());            // 13

    let first: u8 = b.at(0);
    println!(first as i32);       // 104 = 'h'

    println!(b.as_string());      // "hello, world!"

    // Inserindo além da capacidade inicial para forçar realocação.
    let mut i: i32 = 0;
    while i < 50 { b.push(65 as u8); i = i + 1; }
    println!(b.len());            // 63
    println!(b.cap() >= b.len()); // true

    b.clear();
    println!(b.len());            // 0 (capacidade é preservada)

    return 0;
}

Passando o ponteiro bruto para uma syscall

data + len são exatamente o que um write em C espera — construa o payload uma vez, depois passe o ponteiro.

import stdlib::bytes::*;

fn main() -> i32 {
    let req: *ByteBuffer = ByteBuffer::new();
    defer req.free();

    req.push_str("GET / HTTP/1.1\r\n");
    req.push_str("Host: example.com\r\n");
    req.push_str("\r\n");

    let p: *u8 = req.data;        // passe `p` + `req.len` para um write()
    let n: i32 = req.len;
    println!(n);

    return 0;
}

crypto — hashes & HMAC

SHA-256 (RFC 6234), SHA-1 (RFC 3174) e HMAC-SHA-256 (RFC 2104). A implementação é C escrito à mão dentro de um bloco c_raw! (sem dependência do OpenSSL). Cada algoritmo vem em dois sabores: uma variante bruta que retorna o digest como bytes brutos compactados em uma string, e uma variante `_hex` que retorna hex em minúsculas.

Função Assinatura Saída
sha256 pub fn sha256(data: string) -> string 32 bytes brutos
sha256_hex pub fn sha256_hex(data: string) -> string 64 caracteres hex em minúsculas
sha1 pub fn sha1(data: string) -> string 20 bytes brutos
sha1_hex pub fn sha1_hex(data: string) -> string 40 caracteres hex em minúsculas
hmac_sha256 pub fn hmac_sha256(key: string, msg: string) -> string 32 bytes brutos
hmac_sha256_hex pub fn hmac_sha256_hex(key: string, msg: string) -> string 64 caracteres hex em minúsculas

Os sabores _hex e bruto fazem hashing dos mesmos bytes, então hex_encode(sha256(x)) equivale a sha256_hex(x) (e da mesma forma para SHA-1 / HMAC).

import stdlib::crypto::*;
import stdlib::hex::*;

fn main() -> i32 {
    // SHA-256
    println!(sha256_hex(""));
    // e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
    println!(sha256_hex("abc"));
    // ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad

    // Digest bruto -> hex equivale à variante _hex.
    let raw: string = sha256("hello");
    println!(hex_encode(raw));
    // 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

    // SHA-1
    println!(sha1_hex(""));      // da39a3ee5e6b4b0d3255bfef95601890afd80709
    println!(sha1_hex("abc"));   // a9993e364706816aba3e25717850c26c9cd0d89d
    println!(hex_encode(sha1("abc")));

    // HMAC-SHA-256
    let mac: string = hmac_sha256_hex(
        "key", "The quick brown fox jumps over the lazy dog");
    println!(mac);
    // f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8

    let macraw: string = hmac_sha256("secret", "payload");
    println!(hex_encode(macraw));
    // b82fcb791acec57859b989b430a826488ce2e479fdf92326bd0a2e8375a42ba4

    return 0;
}

Digests de referência conhecidos:

Chamada Resultado
sha256_hex("") e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
sha256_hex("abc") ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
sha1_hex("") da39a3ee5e6b4b0d3255bfef95601890afd80709
sha1_hex("abc") a9993e364706816aba3e25717850c26c9cd0d89d
hmac_sha256_hex("key", "The quick brown fox jumps over the lazy dog") f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8

Exemplo prático: o handshake WebSocket da RFC 6455

O único lugar em que SHA-1 ainda é obrigatório: um servidor computa Sec-WebSocket-Accept como base64(sha1(client_key + magic_guid)).

import stdlib::crypto::*;
import stdlib::base64::*;

const WS_GUID: string = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

fn ws_accept(client_key: string) -> string {
    let combined: string = client_key.concat(WS_GUID);
    let digest: string = sha1(combined);     // 20 bytes brutos
    return b64_encode(digest);
}

fn main() -> i32 {
    // Vetor de exemplo da RFC 6455.
    println!(ws_accept("dGhlIHNhbXBsZSBub25jZQ=="));
    // s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
    return 0;
}

compress — inflate gzip / zlib

Uma única função de decodificação respaldada por zlib (-lz). Ela detecta automaticamente um wrapper gzip ou zlib (inflateInit2(&zs, 15 + 32)). Não há contrapartida de codificação neste módulo — ele existe para descomprimir tarballs baixados.

Função Assinatura Descrição
gzip_decode pub fn gzip_decode(input: string, in_len: i32, out_len: *i32) -> *void Descomprime um buffer gzip/zlib. Retorna um buffer alocado com malloc; a contagem de bytes decodificados é escrita através de out_len.

O ponteiro retornado é null em qualquer erro de decodificação (e out_len é definido como 0). Em caso de sucesso, o chamador é dono do buffer: libere-o com free, ou envolva-o com o interno __glide_string_from_buf(buf, out_len) (que copia para memória rastreada pela arena) e libere o original.

import stdlib::compress::*;
import stdlib::fs::*;

extern fn __glide_string_from_buf(buf: *void, n: i32) -> string;

fn main() -> i32 {
    let mut raw_len: i32 = 0;
    let payload: string = fs_read_bytes("archive.gz", &raw_len);
    if raw_len <= 0 { return 1; }

    let mut out_len: i32 = 0;
    let buf: *void = gzip_decode(payload, raw_len, &out_len);
    if buf == null { return 1; }             // null em qualquer erro de decodificação

    let decoded: string = __glide_string_from_buf(buf, out_len);
    free(buf);
    println!(out_len);

    return 0;
}

tar — extração em memória

Um leitor USTAR / GNU tar. Ele recebe um arquivo compactado mantido em memória como uma string do Glide (seguro para binários quando produzido por __glide_string_from_buf) e escreve cada entrada em out_dir.

Função Assinatura Descrição
tar_extract pub fn tar_extract(data: string, n: i32, out_dir: string, strip_components: i32) -> i32 Extrai n bytes de data tar para out_dir. Retorna a contagem de arquivos regulares escritos, ou -1 se uma escrita falhar.

strip_components corresponde ao --strip-components do GNU tar: descarta esse número de segmentos de caminho separados por / (ou \) iniciais de cada nome de entrada antes de escrever. Os tarballs do GitHub codeload envolvem tudo sob um diretório de nível superior <repo>-<sha>/, então chamadores que baixam de lá passam strip_components = 1. Uma entrada com menos segmentos do que strip_components é silenciosamente ignorada.

Tipos de entrada reconhecidos:

Byte de tipo Significado Ação
'0' / 0x00 Arquivo regular Escrito no disco (diretórios pai criados)
'5' Diretório mkdir -p
'L' Nome longo GNU Armazenado em buffer para o próximo cabeçalho
'x' / 'g' Cabeçalho pax estendido Ignorado

O pipeline típico é fetch -> gzip_decode -> tar_extract:

import stdlib::compress::*;
import stdlib::tar::*;
import stdlib::fs::*;

extern fn __glide_string_from_buf(buf: *void, n: i32) -> string;

fn main() -> i32 {
    let mut raw_len: i32 = 0;
    let payload: string = fs_read_bytes("repo.tar.gz", &raw_len);
    if raw_len <= 0 { return 1; }

    // Descomprime o wrapper gzip.
    let mut out_len: i32 = 0;
    let buf: *void = gzip_decode(payload, raw_len, &out_len);
    if buf == null { return 1; }
    let decoded: string = __glide_string_from_buf(buf, out_len);
    free(buf);

    // Extrai, removendo o diretório inicial <repo>-<sha>/.
    let n: i32 = tar_extract(decoded, out_len, "/tmp/extract", 1);
    if n < 0 { return 1; }                   // -1 = falha na escrita de um arquivo
    println!(n);                             // arquivos regulares escritos

    // Ou mantém os caminhos completos com strip_components = 0.
    let m: i32 = tar_extract(decoded, out_len, "/tmp/full", 0);
    println!(m);

    return 0;
}