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;
}