API — sentry-protocol
Referência do sentry-protocol: DSL de schemas de protocolo, compilador, VM, guards de DoS e CLI
API — sentry-protocol
sentry-protocol descreve e valida protocolos não-HTTP em portas não padrão (F9). Um schema YAML descreve framing, dispatch por header, leitura de campos e constraints; a crate compila tudo em uma tabela de instruções executada por uma pequena VM de registradores — zero regex no caminho default (a única regex possível é compilada no load, linear-time). A crate é pura: sem I/O, sem async, sem depender de outras crates do Sentry (#![forbid(unsafe_code)]); o host (daemon/edge) possui carregamento de arquivos, hot-reload e converte Violation em sinais do pipeline.
Modelo do schema — módulo schema
ProtocolSchema
pub struct ProtocolSchema {
pub id: String,
pub mode: Mode,
pub transport: Transport,
pub on_message: Option<OnMessage>,
pub types: BTreeMap<String, TypeDef>,
pub policies: BTreeMap<String, Policy>,
pub messages: BTreeMap<String, MessageDef>,
}ProtocolSchema é a raiz (deny_unknown_fields — campo errado é erro de carga). O id vira label de métricas e sinais.
Transport e Mode
pub struct Transport {
pub protocol: TransportKind, // tcp | udp | ws
pub ports: Vec<u16>,
pub flags: BTreeMap<String, String>,
}Transport declara a porta(s) que o schema guarda — o wiring do host casa por porta. TransportKind aceita tcp, udp e ws. Mode define a postura: shadow (default) só sinaliza e loga; enforce autoriza o host a fechar a conexão na primeira violação.
types — macros de leitura definidas por protocolo
A crate só embarca átomos universais; formatos específicos (inteiros variável-length, strings com prefixo próprio) são definidos no schema, em TypeDef, de três formas:
- Sugar one-liner (
SugarRead):{prefix/fixed/terminator, endian, decode, max_len, mask_ok, range_ok, len_ok, regex, in/not_in, req, charsets}— desugara para steps no load. - Body de steps: mini-máquina de registradores com expressões infix (
acc: "(b0 and 0x38) >> 3"; ops palavraand/or/xor/shl/shr/add/sub/mul/not+ sugar& | ^ << >> + - *), I/Oread/decode/peek,while min!(expr, cap):com bound provável em compile-time,if <expr>:com bloco forward-only, statement macroscheck_mask!/check_range!/check_len!ereturn <reg>. - Composite: lista de leituras sequenciais de sub-campos nomeados.
Aninhamento de tipos em tipos é limitado a 8 níveis (MAX_TYPE_DEPTH).
messages e policies
messages é a tabela de dispatch: when obrigatório (a união dos when é uma allowlist implícita — overlap é erro de carga), after: [msg] (sequência), policy (severidade citada), keepalive {cadence, rate_limit} e validate (one-liners de constraint por campo). policies nomeia severidades: default é obrigatório; cada policy tem weight e on_repeat {count, window, escalate} para escalada por repetição.
Compilador — módulo compile
Compiled
pub struct Compiled {
pub protocols: Vec<CompiledProtocol>,
/* by_port: HashMap<u16, usize> */
}
pub fn add(&mut self, schema: ProtocolSchema, opts: &CompileOptions) -> Result<(), ProtocolError>;
pub fn for_port(&self, port: u16) -> Option<&CompiledProtocol>;Compiled é o conjunto compilado, trocável como um Arc por reload. add compila um schema e registra suas portas (primeiro schema que reclama a porta vence); for_port é o lookup do host. CompileOptions injeta datasets para ops not_in.
CompiledProtocol, FrameSpec e MessageProgram
Cada CompiledProtocol traz id, modo, portas, preamble (pipeline de on_message.run), programas por mensagem, tabela de dispatch (when por header vira HashMap; resto, linear) e policies por nome (PolicySpec: weight + janela de on_repeat). MessageProgram carrega o label, hash do nome (bookkeeping de after), policy, pré-requisitos e o programa linear de Instr. Macros customizadas desaguam na mesma Vec<Instr> — inlining, custo runtime zero.
Quando o schema usa check_len!, o compilador captura o framing em FrameSpec (offset, prefixo, endian, o que o length cobre, cap) — o host usa para dividir o stream TCP em frames antes de chamar a VM. A entrada é compile_schema.
VM — módulo engine
ProtocolEngine
pub struct ProtocolEngine { /* ArcSwap<Compiled> */ }
pub fn new(compiled: Compiled) -> Self;
pub fn swap(&self, compiled: Compiled); // hot-reload: 1 pointer swap
pub fn current(&self) -> Arc<Compiled>;
pub fn feed(&self, proto: &CompiledProtocol, state: &mut ConnectionState, bytes: &[u8]) -> Result<FrameInfo, Vec<Violation>>;ProtocolEngine embrulha o conjunto em ArcSwap: o reload é um swap atômico sem downtime — frames em curso terminam no programa anterior. feed valida um frame completo (framing é responsabilidade do host) e devolve FrameInfo ou a lista de violações (sempre ≥ 1). feed_on é a free function para hosts que seguram o próprio snapshot compilado.
ConnectionState
Estado por conexão, sem locks, dono pela task da conexão (ConnectionState): seen-set de mensagens (valida after), janelas de keepalive e contadores de repetição por policy (valida on_repeat). is_stale permite o prune de conexões mortas.
Guards contra DoS
Orçamentos fechados em compile-time e runtime:
- 16 registradores por protocolo (
MAX_REGS); nomes de ops/átomos são reservados. - 512 instruções por mensagem compilada (
MAX_INSTRS_PER_MESSAGE). whileexige bound provável em compile-time (min!(expr, cap)); iterações clampeiam;ifé branch só-para-frente — terminação garantida.- Aninhamento de tipos ≤ 8; caps de tamanho em toda leitura prefixada/terminada (
max_len,FrameSpec.max).
Erros — módulo error
ProtocolError cobre o ciclo todo, cada variante carregando o schema responsável: Yaml (documento inválido), Schema (viola regras do DSL), Parse (one-liner ou step malformado), Compile (duplicata de when, tipo/macro desconhecido, misuso de registrador) e MissingDataset (dataset de not_in não fornecido pelo host).
Expressões e instruções
expr é o parser Pratt das expressões infix (literal negativo dobra em compile-time); instr define as instruções da VM sobre Value (Bytes/Str/Int). Benches: frame compilado ~0,68 µs vs baseline regex ~20,8 µs (~30×); read_until com memchr 27 ns vs escalar 1,10 µs (~41×).
Feature simd
sentry-protocol tem uma feature opcional simd (Cargo.toml): scan de terminador via memchr e validação UTF-8 via simdutf8 — crates pure-Rust com fallback escalar. Ligada por default no daemon; desligue para forçar o caminho escalar. O bench comparativo simd_vs_scalar roda no mesmo executável com dev-deps sempre presentes.
CLI
sentry protocol validate [--dir <path>] # compila todo *.protocol.yaml do dir
sentry protocol list [--dir <path>] # id, portas, modo, mensagens
sentry protocol check <schema> --hex <bytes> # testa bytes reais contra o schemaImplementação em protocol_cmd.rs: compile_dir carrega e compila o diretório (all-or-nothing por arquivo). O watcher de hot-reload (notify, debounce + coalescência + safety poll) e o config [protocol] (enabled, dir, max_schemas) vivem no daemon; sentry config validate também compila os schemas.
Exemplo
Excerto do fixture game-relay.protocol.yaml — protocolo binário legacy com length-prefix big-endian, tipos definidos no próprio schema e política de escalada:
transport:
protocol: tcp
ports: [14901]
on_message:
run: "check_len! | parse_header!"
types:
LPStr: {prefix: u16, decode: utf8, max_len: 1024}
VLInt:
body:
- b0: "read u8"
- n: "(b0 and 0x38) >> 3"
- acc: "b0 and 0x03"
- while min!(n, 4):
- bi: "read u8"
- check_mask!(bi, 0xC0, 0x40)
- acc: "(acc << 6) or (bi and 0x3F)"
- if sign:
- acc: "-acc"
- return acc
policies:
default: {weight: 20}
unknown_header:
weight: 35
on_repeat: {count: 3, window: 60s, escalate: true}
messages:
sso_ticket_event:
when: {header: 400}
validate:
- sso_ticket: LPStr >2 <64 regex 'GAME-[a-zA-Z0-9]+-3324'
ping_event:
when: {header: 4096}
keepalive: {cadence: 30s, rate_limit: 2/60s}O fixture chat-relay.protocol.yaml mostra o mesmo DSL sobre WebSocket com payload JSON — sem macros customizadas, as ops de validate aplicam aos campos de texto decodificados.
API — sentry-edge
Referência da API pública do sentry-edge: EdgeRuntime, middleware/reverse proxy, challenge PoW, páginas de erro, TLS, compressão e validação de protocolo
API — sentry-storage
Referência do sentry-storage: pool Postgres, repositórios tipados, migrations, dedupe por payload_hash e LISTEN/NOTIFY