initialize, try dev on linux

This commit is contained in:
Pakin 2025-08-25 09:42:57 +07:00
commit 59b801dfe3
10 changed files with 841 additions and 0 deletions

32
src/adb.rs Normal file
View file

@ -0,0 +1,32 @@
use bytes::{Buf, BytesMut};
// little endian "OPEN"
pub const CMD_OPEN: u32 = u32::from_le_bytes(*b"NEPO");
pub struct AdbFrame {
pub cmd: u32,
pub payload: Vec<u8>,
}
pub fn try_parse(buf: &mut BytesMut) -> Option<AdbFrame> {
loop {
if buf.len() < 24 {
return None;
}
// header
let hdr = &buf[..24];
let cmd = u32::from_le_bytes(hdr[0..4].try_into().unwrap());
let data_len = u32::from_le_bytes(hdr[12..16].try_into().unwrap()) as usize;
let magic = u32::from_le_bytes(hdr[20..24].try_into().unwrap());
if (cmd ^ 0xFFFF_FFFF) != magic {
buf.advance(1);
continue;
}
if buf.len() < 24 + data_len {
return None;
}
let payload = buf[24..24 + data_len].to_vec();
buf.advance(24 + data_len);
return Some(AdbFrame { cmd, payload });
}
}

2
src/backend/mod.rs Normal file
View file

@ -0,0 +1,2 @@
#[cfg(all(feature = "ptrace", target_arch = "aarch64"))]
pub mod ptrace;

29
src/backend/ptrace.rs Normal file
View file

@ -0,0 +1,29 @@
use crate::{adb, broadcaster, rule::RuleSet};
use anyhow::Result;
use bytes::BytesMut;
use nix::{
libc,
sys::{
ptrace,
signal::Signal,
wait::{WaitPidFlag, WaitStatus, waitpid},
},
unistd::Pid,
};
use std::{
collections::HashMap,
time::{Duration, Instant},
};
const PTRACE_GETREGSET_FALLBACK: i32 = 0x4204;
const NT_PRSTATUS_FALLBACK: i32 = 1;
#[cfg(target_arch = "aarch64")]
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct UserAtRegs {
regs: [u64; 31],
sp: u64,
pc: u64,
pstate: u64,
}

17
src/broadcaster.rs Normal file
View file

@ -0,0 +1,17 @@
use std::process::Command;
pub fn broadcast(action: &str, service: &str, rule: &str) {
let _ = Command::new("am")
.args([
"broadcast",
"-a",
action,
"--es",
"service",
service,
"--es",
"rule",
rule,
])
.status();
}

43
src/config.rs Normal file
View file

@ -0,0 +1,43 @@
use anyhow::Ok;
use serde::Deserialize;
use std::{fs, time::Duration};
#[derive(Debug, Deserialize, Clone)]
pub struct Rule {
pub name: String,
pub when: String,
#[serde(default)]
pub contains: Vec<String>,
#[serde(default)]
pub pattern: Option<String>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct Config {
#[serde(default = "default_debounce")]
pub debounce_ms: u64,
#[serde(default = "default_peek")]
pub max_payload_peek: usize,
pub broadcast_action: String,
#[serde(default)]
pub rules: Vec<Rule>,
}
fn default_debounce() -> u64 {
3000
}
fn default_peek() -> usize {
256
}
impl Config {
pub fn load(path: &str) -> anyhow::Result<Self> {
let s = fs::read_to_string(path)?;
Ok(serde_json::from_str(&s)?)
}
pub fn debounce(&self) -> Duration {
Duration::from_millis(self.debounce_ms)
}
}

9
src/main.rs Normal file
View file

@ -0,0 +1,9 @@
mod adb;
mod backend;
mod broadcaster;
mod config;
mod rule;
fn main() {
println!("Hello, world!");
}

49
src/rule.rs Normal file
View file

@ -0,0 +1,49 @@
use crate::config::{Config, Rule};
use regex::Regex;
pub struct CompiledRule {
pub name: String,
pub contains: Vec<String>,
pub pattern: Option<Regex>,
}
pub struct RuleSet {
rules: Vec<CompiledRule>,
}
impl RuleSet {
pub fn from_cfg(cfg: &Config) -> Self {
let mut v = Vec::new();
for r in &cfg.rules {
let re = match &r.pattern {
Some(p) => Some(Regex::new(p).unwrap()),
None => None,
};
v.push(CompiledRule {
name: r.name.clone(),
contains: r.contains.clone(),
pattern: re,
});
}
Self { rules: v }
}
pub fn match_service<'a>(&'a self, service: &str) -> Option<&'a str> {
'outer: for r in &self.rules {
for c in &r.contains {
if !service.contains(c) {
continue 'outer;
}
}
if let Some(re) = &r.pattern {
if !re.is_match(service) {
continue;
}
}
return Some(&r.name);
}
None
}
}