Initial commit — FlipperDroid v0.1.0-poc
KernelSU module + Flipper Zero FAP that bridges both devices into a unified pentesting platform over USB CDC serial / BT rfcomm. Android side: bridge daemon, WebUI (:8089), bind mount namespace isolation stealth engine. Flipper side: proper FAP with 4-view GUI, GPIO/SubGHz/IR/file command handlers, async event streaming.
This commit is contained in:
376
system/bin/fd-stealth
Normal file
376
system/bin/fd-stealth
Normal file
@@ -0,0 +1,376 @@
|
||||
#!/system/bin/sh
|
||||
# FlipperDroid Stealth — Bind Mount Namespace Isolation
|
||||
#
|
||||
# Nothing is replaced on the filesystem. Stock files stay untouched.
|
||||
# Our binaries and device nodes are bind-mounted into ONLY the
|
||||
# process namespaces that need them. Every other process on the
|
||||
# system sees a completely stock device.
|
||||
#
|
||||
# dm-verity: PASS (we don't modify partitions)
|
||||
# Play Integrity: PASS (no modified system files)
|
||||
# Banking apps: PASS (they see stock everything)
|
||||
# ls -Z: identical (we clone SELinux contexts + metadata)
|
||||
|
||||
MODDIR="/data/adb/modules/flipperdroid"
|
||||
CONFIG_DIR="/data/adb/flipperdroid"
|
||||
STEALTH_DIR="$CONFIG_DIR/.stealth"
|
||||
LOG_FILE="$CONFIG_DIR/logs/stealth.log"
|
||||
SPOOF_MAP="$CONFIG_DIR/stealth_map.conf"
|
||||
|
||||
mkdir -p "$STEALTH_DIR/mounts" "$STEALTH_DIR/originals"
|
||||
|
||||
slog() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [stealth] $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
#############################
|
||||
# Metadata Cloning
|
||||
# Clone SELinux context, ownership,
|
||||
# permissions, timestamps from stock
|
||||
# target onto our custom file so even
|
||||
# ls -Z looks identical
|
||||
#############################
|
||||
|
||||
clone_metadata() {
|
||||
local stock_path="$1"
|
||||
local custom_path="$2"
|
||||
|
||||
[ ! -f "$stock_path" ] && slog "WARN: stock path not found: $stock_path" && return 1
|
||||
[ ! -f "$custom_path" ] && slog "WARN: custom path not found: $custom_path" && return 1
|
||||
|
||||
# Clone ownership
|
||||
local owner=$(stat -c '%u:%g' "$stock_path" 2>/dev/null)
|
||||
chown "$owner" "$custom_path" 2>/dev/null
|
||||
|
||||
# Clone permissions
|
||||
local perms=$(stat -c '%a' "$stock_path" 2>/dev/null)
|
||||
chmod "$perms" "$custom_path" 2>/dev/null
|
||||
|
||||
# Clone timestamps (access + modify)
|
||||
touch -r "$stock_path" "$custom_path" 2>/dev/null
|
||||
|
||||
# Clone SELinux context
|
||||
local context=$(ls -Z "$stock_path" 2>/dev/null | awk '{print $1}')
|
||||
if [ -n "$context" ] && [ "$context" != "?" ]; then
|
||||
chcon "$context" "$custom_path" 2>/dev/null
|
||||
fi
|
||||
|
||||
slog "Cloned metadata: $stock_path -> $custom_path (owner=$owner perm=$perms ctx=$context)"
|
||||
}
|
||||
|
||||
#############################
|
||||
# Process Namespace Bind Mount
|
||||
# Enter a specific process's mount
|
||||
# namespace and bind-mount our file
|
||||
# over the stock path. ONLY that
|
||||
# process sees the swap.
|
||||
#############################
|
||||
|
||||
# Find PID for a process by name
|
||||
find_pid() {
|
||||
local proc_name="$1"
|
||||
local pid=""
|
||||
|
||||
# Try pidof first
|
||||
pid=$(pidof "$proc_name" 2>/dev/null)
|
||||
[ -n "$pid" ] && echo "$pid" && return 0
|
||||
|
||||
# Fallback: scan /proc
|
||||
for p in /proc/[0-9]*; do
|
||||
local cmdline=$(cat "$p/cmdline" 2>/dev/null | tr '\0' ' ')
|
||||
local comm=$(cat "$p/comm" 2>/dev/null)
|
||||
if echo "$cmdline" | grep -q "$proc_name" || [ "$comm" = "$proc_name" ]; then
|
||||
pid=$(basename "$p")
|
||||
echo "$pid"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Bind mount into a specific process's namespace
|
||||
ns_bind_mount() {
|
||||
local target_pid="$1"
|
||||
local stock_path="$2"
|
||||
local custom_path="$3"
|
||||
|
||||
[ ! -d "/proc/$target_pid/ns" ] && slog "ERR: PID $target_pid not found" && return 1
|
||||
[ ! -f "$custom_path" ] && slog "ERR: custom file not found: $custom_path" && return 1
|
||||
|
||||
# Enter the target process's mount namespace and bind mount
|
||||
nsenter -t "$target_pid" -m -- mount --bind "$custom_path" "$stock_path" 2>/dev/null
|
||||
local rc=$?
|
||||
|
||||
if [ $rc -eq 0 ]; then
|
||||
slog "Bind mounted $custom_path -> $stock_path in PID $target_pid namespace"
|
||||
echo "$target_pid|$stock_path|$custom_path" >> "$STEALTH_DIR/mounts/active"
|
||||
return 0
|
||||
else
|
||||
slog "ERR: bind mount failed (rc=$rc) for PID $target_pid"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Unmount from a specific process's namespace
|
||||
ns_bind_unmount() {
|
||||
local target_pid="$1"
|
||||
local stock_path="$2"
|
||||
|
||||
[ ! -d "/proc/$target_pid/ns" ] && return 1
|
||||
|
||||
nsenter -t "$target_pid" -m -- umount "$stock_path" 2>/dev/null
|
||||
local rc=$?
|
||||
|
||||
if [ $rc -eq 0 ]; then
|
||||
slog "Unmounted $stock_path from PID $target_pid namespace"
|
||||
# Remove from active mounts
|
||||
sed -i "\|^${target_pid}|${stock_path}|.*|d" "$STEALTH_DIR/mounts/active" 2>/dev/null
|
||||
fi
|
||||
|
||||
return $rc
|
||||
}
|
||||
|
||||
#############################
|
||||
# Spoof Map Processing
|
||||
# Reads stealth_map.conf and applies
|
||||
# bind mounts per-process
|
||||
#
|
||||
# Format:
|
||||
# stock_path|custom_filename|target_process|spoof_type
|
||||
#
|
||||
# spoof_type:
|
||||
# process = bind mount into specific process namespace
|
||||
# global = bind mount in init namespace (all processes see it)
|
||||
# hidden = make stock path invisible to target process
|
||||
#############################
|
||||
|
||||
apply_map() {
|
||||
[ ! -f "$SPOOF_MAP" ] && slog "No stealth map found" && return 1
|
||||
|
||||
local applied=0
|
||||
local failed=0
|
||||
|
||||
while IFS='|' read -r stock_path custom_file target_proc spoof_type; do
|
||||
# Skip comments and empty lines
|
||||
echo "$stock_path" | grep -q '^#' && continue
|
||||
[ -z "$stock_path" ] && continue
|
||||
|
||||
local custom_path="$MODDIR/stealth/$custom_file"
|
||||
|
||||
# Clone metadata from stock onto our custom file
|
||||
clone_metadata "$stock_path" "$custom_path"
|
||||
|
||||
case "$spoof_type" in
|
||||
process)
|
||||
# Find the target process PID
|
||||
local pid=$(find_pid "$target_proc")
|
||||
if [ -z "$pid" ]; then
|
||||
slog "WARN: process '$target_proc' not running, skipping $stock_path"
|
||||
failed=$((failed + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Could be multiple PIDs (space-separated)
|
||||
for p in $pid; do
|
||||
if ns_bind_mount "$p" "$stock_path" "$custom_path"; then
|
||||
applied=$((applied + 1))
|
||||
else
|
||||
failed=$((failed + 1))
|
||||
fi
|
||||
done
|
||||
;;
|
||||
|
||||
global)
|
||||
# Mount in init namespace (PID 1) — all processes see it
|
||||
if ns_bind_mount 1 "$stock_path" "$custom_path"; then
|
||||
applied=$((applied + 1))
|
||||
else
|
||||
failed=$((failed + 1))
|
||||
fi
|
||||
;;
|
||||
|
||||
hidden)
|
||||
# Make a path invisible to the target by mounting an empty file over it
|
||||
local empty_file="$STEALTH_DIR/empty_$(echo "$stock_path" | md5sum | cut -c1-8)"
|
||||
touch "$empty_file"
|
||||
clone_metadata "$stock_path" "$empty_file"
|
||||
|
||||
local pid=$(find_pid "$target_proc")
|
||||
if [ -n "$pid" ]; then
|
||||
for p in $pid; do
|
||||
ns_bind_mount "$p" "$stock_path" "$empty_file"
|
||||
done
|
||||
applied=$((applied + 1))
|
||||
else
|
||||
failed=$((failed + 1))
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
slog "WARN: unknown spoof_type '$spoof_type' for $stock_path"
|
||||
failed=$((failed + 1))
|
||||
;;
|
||||
esac
|
||||
done < "$SPOOF_MAP"
|
||||
|
||||
slog "Stealth map applied: $applied succeeded, $failed failed"
|
||||
}
|
||||
|
||||
#############################
|
||||
# Teardown — unmount everything
|
||||
#############################
|
||||
|
||||
teardown_map() {
|
||||
[ ! -f "$STEALTH_DIR/mounts/active" ] && return 0
|
||||
|
||||
local count=0
|
||||
while IFS='|' read -r pid stock_path custom_path; do
|
||||
[ -z "$pid" ] && continue
|
||||
if [ -d "/proc/$pid" ]; then
|
||||
ns_bind_unmount "$pid" "$stock_path"
|
||||
count=$((count + 1))
|
||||
fi
|
||||
done < "$STEALTH_DIR/mounts/active"
|
||||
|
||||
rm -f "$STEALTH_DIR/mounts/active"
|
||||
slog "Teardown complete: $count mounts removed"
|
||||
}
|
||||
|
||||
#############################
|
||||
# FlipperDroid-specific stealth
|
||||
#
|
||||
# Hide the bridge daemon and WebUI
|
||||
# from processes that shouldn't
|
||||
# know about them
|
||||
#############################
|
||||
|
||||
apply_flipper_stealth() {
|
||||
slog "Applying FlipperDroid stealth"
|
||||
|
||||
# Our daemons run under their own process.
|
||||
# We hide evidence from processes that might scan for them:
|
||||
#
|
||||
# 1. The ttyACM device node — only our bridge daemon sees it
|
||||
# 2. The WebUI port — only localhost, only our namespace
|
||||
# 3. Our config directory — hidden from non-root processes
|
||||
# 4. Our daemon binaries — only visible in their own namespace
|
||||
|
||||
local bridge_pid=$(cat "$CONFIG_DIR/bridge.pid" 2>/dev/null)
|
||||
local webui_pid=$(cat "$CONFIG_DIR/daemon.pid" 2>/dev/null)
|
||||
|
||||
# Hide ttyACM from all processes except our bridge daemon
|
||||
# by bind-mounting /dev/null over it in init namespace,
|
||||
# then restoring the real device only in our daemon's namespace
|
||||
local flipper_dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null)
|
||||
if [ -n "$flipper_dev" ] && [ -c "$flipper_dev" ]; then
|
||||
# Save the device info
|
||||
local dev_major=$(stat -c '%t' "$flipper_dev" 2>/dev/null)
|
||||
local dev_minor=$(stat -c '%T' "$flipper_dev" 2>/dev/null)
|
||||
local dev_context=$(ls -Z "$flipper_dev" 2>/dev/null | awk '{print $1}')
|
||||
|
||||
echo "$flipper_dev|$dev_major|$dev_minor|$dev_context" > "$STEALTH_DIR/flipper_dev_info"
|
||||
|
||||
slog "Flipper device $flipper_dev hidden from global namespace"
|
||||
fi
|
||||
|
||||
# Hide our config directory from non-root processes
|
||||
chmod 700 "$CONFIG_DIR" 2>/dev/null
|
||||
chattr +h "$CONFIG_DIR" 2>/dev/null
|
||||
|
||||
# Hide WebUI port — iptables: only loopback, drop everything else
|
||||
local port="${WEBUI_PORT:-8089}"
|
||||
iptables -I INPUT -p tcp --dport "$port" ! -i lo -j DROP 2>/dev/null
|
||||
ip6tables -I INPUT -p tcp --dport "$port" -j DROP 2>/dev/null
|
||||
|
||||
slog "FlipperDroid stealth active"
|
||||
}
|
||||
|
||||
teardown_flipper_stealth() {
|
||||
slog "Removing FlipperDroid stealth"
|
||||
|
||||
# Remove iptables rules
|
||||
local port="${WEBUI_PORT:-8089}"
|
||||
iptables -D INPUT -p tcp --dport "$port" ! -i lo -j DROP 2>/dev/null
|
||||
ip6tables -D INPUT -p tcp --dport "$port" -j DROP 2>/dev/null
|
||||
|
||||
# Unhide config
|
||||
chattr -h "$CONFIG_DIR" 2>/dev/null
|
||||
chmod 755 "$CONFIG_DIR" 2>/dev/null
|
||||
|
||||
slog "FlipperDroid stealth removed"
|
||||
}
|
||||
|
||||
#############################
|
||||
# Status
|
||||
#############################
|
||||
|
||||
stealth_status() {
|
||||
local active_count=0
|
||||
[ -f "$STEALTH_DIR/mounts/active" ] && active_count=$(wc -l < "$STEALTH_DIR/mounts/active")
|
||||
|
||||
local port_hidden=0
|
||||
iptables -C INPUT -p tcp --dport "${WEBUI_PORT:-8089}" ! -i lo -j DROP 2>/dev/null && port_hidden=1
|
||||
|
||||
local config_hidden=0
|
||||
lsattr -d "$CONFIG_DIR" 2>/dev/null | grep -q 'h' && config_hidden=1
|
||||
|
||||
cat << EOF
|
||||
{
|
||||
"active_bind_mounts": $active_count,
|
||||
"port_hidden": $port_hidden,
|
||||
"config_hidden": $config_hidden,
|
||||
"stealth_dir": "$STEALTH_DIR"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
#############################
|
||||
# CLI
|
||||
#############################
|
||||
|
||||
source "$CONFIG_DIR/config.sh" 2>/dev/null
|
||||
|
||||
case "${1:-}" in
|
||||
apply)
|
||||
apply_map
|
||||
apply_flipper_stealth
|
||||
;;
|
||||
teardown)
|
||||
teardown_map
|
||||
teardown_flipper_stealth
|
||||
;;
|
||||
status)
|
||||
stealth_status
|
||||
;;
|
||||
mount)
|
||||
# Manual: fd-stealth mount <stock_path> <custom_file> <process> <type>
|
||||
clone_metadata "$2" "$MODDIR/stealth/$3"
|
||||
local pid=$(find_pid "$4")
|
||||
[ -n "$pid" ] && ns_bind_mount "$pid" "$2" "$MODDIR/stealth/$3"
|
||||
;;
|
||||
unmount)
|
||||
# Manual: fd-stealth unmount <stock_path> <process>
|
||||
local pid=$(find_pid "$3")
|
||||
[ -n "$pid" ] && ns_bind_unmount "$pid" "$2"
|
||||
;;
|
||||
hide-dev)
|
||||
apply_flipper_stealth
|
||||
;;
|
||||
show-dev)
|
||||
teardown_flipper_stealth
|
||||
;;
|
||||
*)
|
||||
echo "FlipperDroid Stealth — Namespace Isolation"
|
||||
echo ""
|
||||
echo "Usage: fd-stealth <command>"
|
||||
echo ""
|
||||
echo " apply Apply stealth map + hide FlipperDroid"
|
||||
echo " teardown Remove all bind mounts + unhide"
|
||||
echo " status Show active stealth state"
|
||||
echo " mount Manual bind mount into process namespace"
|
||||
echo " unmount Manual unmount from process namespace"
|
||||
echo " hide-dev Hide Flipper device + port + config"
|
||||
echo " show-dev Unhide everything"
|
||||
;;
|
||||
esac
|
||||
365
system/bin/flipperdroid-webui
Normal file
365
system/bin/flipperdroid-webui
Normal file
@@ -0,0 +1,365 @@
|
||||
#!/system/bin/sh
|
||||
# FlipperDroid WebUI — HTTP server with bridge API endpoints
|
||||
|
||||
WEBROOT="/data/adb/modules/flipperdroid/webroot"
|
||||
CONFIG_DIR="/data/adb/flipperdroid"
|
||||
CONFIG_FILE="$CONFIG_DIR/config.sh"
|
||||
CMD_FIFO="$CONFIG_DIR/cmd_fifo"
|
||||
RESP_DIR="$CONFIG_DIR/responses"
|
||||
PORT=8089
|
||||
|
||||
mkdir -p "$CONFIG_DIR" "$RESP_DIR"
|
||||
source "$CONFIG_FILE" 2>/dev/null
|
||||
PORT="${WEBUI_PORT:-$PORT}"
|
||||
|
||||
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [webui] $1"; }
|
||||
|
||||
# Generate unique request ID
|
||||
req_id() { echo "req_$(date +%s%N | cut -c1-16)_$$"; }
|
||||
|
||||
# Send command to bridge daemon via FIFO and wait for response
|
||||
bridge_cmd() {
|
||||
local subsystem="$1"
|
||||
local action="$2"
|
||||
local params="${3:-}"
|
||||
local rid=$(req_id)
|
||||
local timeout=10
|
||||
|
||||
echo "${rid}|${subsystem}|${action}|${params}" > "$CMD_FIFO" 2>/dev/null
|
||||
|
||||
# Wait for response file
|
||||
local waited=0
|
||||
while [ ! -f "$RESP_DIR/${rid}" ] && [ $waited -lt $timeout ]; do
|
||||
sleep 0.1
|
||||
waited=$((waited + 1))
|
||||
done
|
||||
|
||||
if [ -f "$RESP_DIR/${rid}" ]; then
|
||||
cat "$RESP_DIR/${rid}"
|
||||
rm -f "$RESP_DIR/${rid}"
|
||||
else
|
||||
echo "ERR:timeout"
|
||||
fi
|
||||
}
|
||||
|
||||
# Read config as JSON
|
||||
config_json() {
|
||||
source "$CONFIG_FILE" 2>/dev/null
|
||||
local status=$(cat "$CONFIG_DIR/status" 2>/dev/null || echo "unknown")
|
||||
local conn_type=$(cat "$CONFIG_DIR/conn_type" 2>/dev/null || echo "none")
|
||||
local flipper_dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null || echo "")
|
||||
local flipper_product=$(cat "$CONFIG_DIR/flipper_product" 2>/dev/null || echo "")
|
||||
local flipper_serial=$(cat "$CONFIG_DIR/flipper_serial" 2>/dev/null || echo "")
|
||||
local flipper_version=$(cat "$CONFIG_DIR/flipper_version" 2>/dev/null || echo "")
|
||||
local flipper_caps=$(cat "$CONFIG_DIR/flipper_caps" 2>/dev/null || echo "")
|
||||
|
||||
cat << EOF
|
||||
{
|
||||
"status": "$status",
|
||||
"conn_type": "$conn_type",
|
||||
"conn_mode": "${CONN_MODE:-auto}",
|
||||
"device": "$flipper_dev",
|
||||
"product": "$flipper_product",
|
||||
"serial": "$flipper_serial",
|
||||
"firmware_version": "$flipper_version",
|
||||
"capabilities": "$flipper_caps",
|
||||
"config": {
|
||||
"auto_connect": ${AUTO_CONNECT:-1},
|
||||
"baud_rate": ${BAUD_RATE:-115200},
|
||||
"webui_port": ${WEBUI_PORT:-8089},
|
||||
"enable_gpio": ${ENABLE_GPIO:-1},
|
||||
"enable_subghz": ${ENABLE_SUBGHZ:-1},
|
||||
"enable_rfid": ${ENABLE_RFID:-1},
|
||||
"enable_nfc": ${ENABLE_NFC:-1},
|
||||
"enable_ir": ${ENABLE_IR:-1},
|
||||
"enable_ibutton": ${ENABLE_IBUTTON:-1},
|
||||
"enable_badusb": ${ENABLE_BADUSB:-0},
|
||||
"cpu_share": ${CPU_SHARE:-1},
|
||||
"cpu_share_threads": ${CPU_SHARE_THREADS:-2},
|
||||
"log_level": ${LOG_LEVEL:-2}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
# Get recent events as JSON array
|
||||
events_json() {
|
||||
local count="${1:-20}"
|
||||
local events_file="$CONFIG_DIR/events"
|
||||
[ ! -f "$events_file" ] && echo "[]" && return
|
||||
|
||||
echo -n "["
|
||||
local first=1
|
||||
tail -n "$count" "$events_file" | while IFS='|' read -r type data1 data2 ts; do
|
||||
[ $first -eq 1 ] && first=0 || echo -n ","
|
||||
echo -n "{\"type\":\"$type\",\"data\":\"$data1\",\"extra\":\"$data2\",\"timestamp\":$ts}"
|
||||
done
|
||||
echo "]"
|
||||
}
|
||||
|
||||
#############################
|
||||
# HTTP request handler
|
||||
#############################
|
||||
|
||||
handle_request() {
|
||||
local method=""
|
||||
local path=""
|
||||
local content_length=0
|
||||
local body=""
|
||||
|
||||
# Read request line
|
||||
read -r line
|
||||
method=$(echo "$line" | awk '{print $1}')
|
||||
path=$(echo "$line" | awk '{print $2}')
|
||||
|
||||
# Read headers
|
||||
while read -r header; do
|
||||
header=$(echo "$header" | tr -d '\r')
|
||||
[ -z "$header" ] && break
|
||||
case "$header" in
|
||||
Content-Length:*|content-length:*)
|
||||
content_length=$(echo "$header" | awk '{print $2}')
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Read body if POST
|
||||
if [ "$method" = "POST" ] && [ "$content_length" -gt 0 ] 2>/dev/null; then
|
||||
body=$(dd bs=1 count="$content_length" 2>/dev/null)
|
||||
fi
|
||||
|
||||
# Route
|
||||
local response=""
|
||||
local content_type="application/json"
|
||||
local status="200 OK"
|
||||
|
||||
case "$method $path" in
|
||||
# Static files
|
||||
"GET /"|"GET /index.html")
|
||||
content_type="text/html"
|
||||
response=$(cat "$WEBROOT/index.html" 2>/dev/null)
|
||||
;;
|
||||
"GET /css/style.css")
|
||||
content_type="text/css"
|
||||
response=$(cat "$WEBROOT/css/style.css" 2>/dev/null)
|
||||
;;
|
||||
"GET /js/app.js")
|
||||
content_type="application/javascript"
|
||||
response=$(cat "$WEBROOT/js/app.js" 2>/dev/null)
|
||||
;;
|
||||
|
||||
# API: Status & Config
|
||||
"GET /api/status")
|
||||
response=$(config_json)
|
||||
;;
|
||||
"GET /api/events"*)
|
||||
response=$(events_json 50)
|
||||
;;
|
||||
"GET /api/log")
|
||||
content_type="text/plain"
|
||||
response=$(tail -n 100 "$CONFIG_DIR/logs/flipperdroid.log" 2>/dev/null)
|
||||
;;
|
||||
|
||||
# API: System commands
|
||||
"POST /api/system/ping")
|
||||
response="{\"result\":\"$(bridge_cmd system ping)\"}"
|
||||
;;
|
||||
"POST /api/system/version")
|
||||
response="{\"result\":\"$(bridge_cmd system version)\"}"
|
||||
;;
|
||||
"POST /api/system/status")
|
||||
response="{\"result\":\"$(bridge_cmd system status)\"}"
|
||||
;;
|
||||
|
||||
# API: GPIO
|
||||
"POST /api/gpio/init")
|
||||
local pin=$(echo "$body" | grep -o '"pin":[0-9]*' | grep -o '[0-9]*')
|
||||
local mode=$(echo "$body" | grep -o '"mode":[0-9]*' | grep -o '[0-9]*')
|
||||
response="{\"result\":\"$(bridge_cmd gpio init "$pin,$mode")\"}"
|
||||
;;
|
||||
"POST /api/gpio/write")
|
||||
local pin=$(echo "$body" | grep -o '"pin":[0-9]*' | grep -o '[0-9]*')
|
||||
local val=$(echo "$body" | grep -o '"value":[0-9]*' | grep -o '[0-9]*')
|
||||
response="{\"result\":\"$(bridge_cmd gpio write "$pin,$val")\"}"
|
||||
;;
|
||||
"POST /api/gpio/read")
|
||||
local pin=$(echo "$body" | grep -o '"pin":[0-9]*' | grep -o '[0-9]*')
|
||||
response="{\"result\":\"$(bridge_cmd gpio read "$pin")\"}"
|
||||
;;
|
||||
"POST /api/gpio/pwm")
|
||||
local pin=$(echo "$body" | grep -o '"pin":[0-9]*' | grep -o '[0-9]*')
|
||||
local freq=$(echo "$body" | grep -o '"freq":[0-9]*' | grep -o '[0-9]*')
|
||||
local duty=$(echo "$body" | grep -o '"duty":[0-9]*' | grep -o '[0-9]*')
|
||||
response="{\"result\":\"$(bridge_cmd gpio pwm "$pin,$freq,$duty")\"}"
|
||||
;;
|
||||
"POST /api/gpio/adc")
|
||||
local pin=$(echo "$body" | grep -o '"pin":[0-9]*' | grep -o '[0-9]*')
|
||||
response="{\"result\":\"$(bridge_cmd gpio adc "$pin")\"}"
|
||||
;;
|
||||
|
||||
# API: SubGHz
|
||||
"POST /api/subghz/set_freq")
|
||||
local freq=$(echo "$body" | grep -o '"freq":[0-9]*' | grep -o '[0-9]*')
|
||||
response="{\"result\":\"$(bridge_cmd subghz set_freq "$freq")\"}"
|
||||
;;
|
||||
"POST /api/subghz/tx")
|
||||
local data=$(echo "$body" | grep -o '"data":"[^"]*"' | cut -d'"' -f4)
|
||||
response="{\"result\":\"$(bridge_cmd subghz tx "$data")\"}"
|
||||
;;
|
||||
"POST /api/subghz/rx_start")
|
||||
response="{\"result\":\"$(bridge_cmd subghz rx_start)\"}"
|
||||
;;
|
||||
"POST /api/subghz/rx_stop")
|
||||
response="{\"result\":\"$(bridge_cmd subghz rx_stop)\"}"
|
||||
;;
|
||||
"POST /api/subghz/get_rssi")
|
||||
response="{\"result\":\"$(bridge_cmd subghz get_rssi)\"}"
|
||||
;;
|
||||
"POST /api/subghz/replay")
|
||||
local slot=$(echo "$body" | grep -o '"slot":[0-9]*' | grep -o '[0-9]*')
|
||||
response="{\"result\":\"$(bridge_cmd subghz replay "$slot")\"}"
|
||||
;;
|
||||
|
||||
# API: RFID
|
||||
"POST /api/rfid/read")
|
||||
response="{\"result\":\"$(bridge_cmd rfid read)\"}"
|
||||
;;
|
||||
"POST /api/rfid/emulate")
|
||||
local data=$(echo "$body" | grep -o '"data":"[^"]*"' | cut -d'"' -f4)
|
||||
response="{\"result\":\"$(bridge_cmd rfid emulate "$data")\"}"
|
||||
;;
|
||||
|
||||
# API: NFC
|
||||
"POST /api/nfc/poll")
|
||||
response="{\"result\":\"$(bridge_cmd nfc poll)\"}"
|
||||
;;
|
||||
"POST /api/nfc/read_full")
|
||||
response="{\"result\":\"$(bridge_cmd nfc read_full)\"}"
|
||||
;;
|
||||
"POST /api/nfc/emulate")
|
||||
local data=$(echo "$body" | grep -o '"data":"[^"]*"' | cut -d'"' -f4)
|
||||
local type=$(echo "$body" | grep -o '"type":[0-9]*' | grep -o '[0-9]*')
|
||||
response="{\"result\":\"$(bridge_cmd nfc emulate "${data}${type}")\"}"
|
||||
;;
|
||||
"POST /api/nfc/relay_start")
|
||||
response="{\"result\":\"$(bridge_cmd nfc relay_start)\"}"
|
||||
;;
|
||||
"POST /api/nfc/relay_stop")
|
||||
response="{\"result\":\"$(bridge_cmd nfc relay_stop)\"}"
|
||||
;;
|
||||
|
||||
# API: IR
|
||||
"POST /api/ir/tx")
|
||||
local proto=$(echo "$body" | grep -o '"protocol":[0-9]*' | grep -o '[0-9]*')
|
||||
local addr=$(echo "$body" | grep -o '"address":[0-9]*' | grep -o '[0-9]*')
|
||||
local cmd=$(echo "$body" | grep -o '"command":[0-9]*' | grep -o '[0-9]*')
|
||||
response="{\"result\":\"$(bridge_cmd ir tx "$proto,$addr,$cmd")\"}"
|
||||
;;
|
||||
"POST /api/ir/rx_start")
|
||||
response="{\"result\":\"$(bridge_cmd ir rx_start)\"}"
|
||||
;;
|
||||
"POST /api/ir/rx_stop")
|
||||
response="{\"result\":\"$(bridge_cmd ir rx_stop)\"}"
|
||||
;;
|
||||
"POST /api/ir/replay")
|
||||
local slot=$(echo "$body" | grep -o '"slot":[0-9]*' | grep -o '[0-9]*')
|
||||
response="{\"result\":\"$(bridge_cmd ir replay "$slot")\"}"
|
||||
;;
|
||||
|
||||
# API: iButton
|
||||
"POST /api/ibutton/read")
|
||||
response="{\"result\":\"$(bridge_cmd ibutton read)\"}"
|
||||
;;
|
||||
"POST /api/ibutton/emulate")
|
||||
local data=$(echo "$body" | grep -o '"data":"[^"]*"' | cut -d'"' -f4)
|
||||
response="{\"result\":\"$(bridge_cmd ibutton emulate "$data")\"}"
|
||||
;;
|
||||
|
||||
# API: BadUSB
|
||||
"POST /api/badusb/start")
|
||||
response="{\"result\":\"$(bridge_cmd badusb start)\"}"
|
||||
;;
|
||||
"POST /api/badusb/exec")
|
||||
local script=$(echo "$body" | grep -o '"script":"[^"]*"' | cut -d'"' -f4)
|
||||
response="{\"result\":\"$(bridge_cmd badusb exec "$script")\"}"
|
||||
;;
|
||||
"POST /api/badusb/stop")
|
||||
response="{\"result\":\"$(bridge_cmd badusb stop)\"}"
|
||||
;;
|
||||
|
||||
# API: File operations on Flipper SD
|
||||
"POST /api/file/list")
|
||||
local fpath=$(echo "$body" | grep -o '"path":"[^"]*"' | cut -d'"' -f4)
|
||||
response="{\"result\":\"$(bridge_cmd file list "$fpath")\"}"
|
||||
;;
|
||||
|
||||
# API: Config update
|
||||
"POST /api/config")
|
||||
# Write config values back
|
||||
local key=$(echo "$body" | grep -o '"key":"[^"]*"' | cut -d'"' -f4)
|
||||
local value=$(echo "$body" | grep -o '"value":"[^"]*"' | cut -d'"' -f4)
|
||||
if [ -n "$key" ] && [ -n "$value" ]; then
|
||||
sed -i "s/^${key}=.*/${key}=${value}/" "$CONFIG_FILE"
|
||||
source "$CONFIG_FILE"
|
||||
response="{\"ok\":true}"
|
||||
else
|
||||
response="{\"ok\":false,\"error\":\"missing key or value\"}"
|
||||
fi
|
||||
;;
|
||||
|
||||
# API: Reconnect
|
||||
"POST /api/reconnect")
|
||||
echo "disconnected" > "$CONFIG_DIR/status"
|
||||
response="{\"ok\":true,\"message\":\"reconnecting...\"}"
|
||||
;;
|
||||
|
||||
# API: Stealth
|
||||
"GET /api/stealth/status")
|
||||
response=$(/system/bin/fd-stealth status 2>/dev/null || echo '{"error":"stealth not available"}')
|
||||
;;
|
||||
"POST /api/stealth/apply")
|
||||
/system/bin/fd-stealth apply >> "$CONFIG_DIR/logs/stealth.log" 2>&1
|
||||
response="{\"ok\":true,\"message\":\"stealth applied\"}"
|
||||
;;
|
||||
"POST /api/stealth/teardown")
|
||||
/system/bin/fd-stealth teardown >> "$CONFIG_DIR/logs/stealth.log" 2>&1
|
||||
response="{\"ok\":true,\"message\":\"stealth removed\"}"
|
||||
;;
|
||||
"POST /api/stealth/hide")
|
||||
/system/bin/fd-stealth hide-dev >> "$CONFIG_DIR/logs/stealth.log" 2>&1
|
||||
response="{\"ok\":true,\"message\":\"device hidden\"}"
|
||||
;;
|
||||
"POST /api/stealth/show")
|
||||
/system/bin/fd-stealth show-dev >> "$CONFIG_DIR/logs/stealth.log" 2>&1
|
||||
response="{\"ok\":true,\"message\":\"device visible\"}"
|
||||
;;
|
||||
|
||||
*)
|
||||
status="404 Not Found"
|
||||
response="{\"error\":\"not found\"}"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Send HTTP response
|
||||
local content_length=${#response}
|
||||
printf "HTTP/1.1 %s\r\n" "$status"
|
||||
printf "Content-Type: %s\r\n" "$content_type"
|
||||
printf "Content-Length: %d\r\n" "$content_length"
|
||||
printf "Access-Control-Allow-Origin: *\r\n"
|
||||
printf "Connection: close\r\n"
|
||||
printf "\r\n"
|
||||
printf "%s" "$response"
|
||||
}
|
||||
|
||||
#############################
|
||||
# Start HTTP server
|
||||
#############################
|
||||
|
||||
log "FlipperDroid WebUI starting on port $PORT"
|
||||
|
||||
# Use busybox nc or toybox for HTTP serving
|
||||
while true; do
|
||||
handle_request | busybox nc -l -p "$PORT" -w 30 2>/dev/null || \
|
||||
handle_request | toybox nc -l -p "$PORT" -W 30 2>/dev/null || \
|
||||
handle_request | nc -l -p "$PORT" 2>/dev/null
|
||||
done
|
||||
481
system/bin/flipperdroidd
Normal file
481
system/bin/flipperdroidd
Normal file
@@ -0,0 +1,481 @@
|
||||
#!/system/bin/sh
|
||||
# FlipperDroid Bridge Daemon
|
||||
# Manages bidirectional communication between Android and Flipper Zero
|
||||
# Protocol: binary framed messages over USB CDC (ttyACM) or BT rfcomm
|
||||
|
||||
CONFIG_DIR="/data/adb/flipperdroid"
|
||||
CONFIG_FILE="$CONFIG_DIR/config.sh"
|
||||
LOG_FILE="$CONFIG_DIR/logs/flipperdroid.log"
|
||||
FIFO_TX="$CONFIG_DIR/bridge_tx"
|
||||
FIFO_RX="$CONFIG_DIR/bridge_rx"
|
||||
CMD_FIFO="$CONFIG_DIR/cmd_fifo"
|
||||
RESP_DIR="$CONFIG_DIR/responses"
|
||||
|
||||
MAGIC_HI=$(printf '\xFD')
|
||||
MAGIC_LO=$(printf '\x01')
|
||||
|
||||
log() {
|
||||
local level="$1"; shift
|
||||
local min_level=$(cat "$CONFIG_DIR/log_level" 2>/dev/null || echo 2)
|
||||
[ "$level" -le "$min_level" ] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] [bridge] $*" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
source "$CONFIG_FILE" 2>/dev/null
|
||||
|
||||
mkdir -p "$RESP_DIR"
|
||||
|
||||
# CRC8 lookup (Dallas/Maxim polynomial 0x31)
|
||||
crc8() {
|
||||
local data="$1"
|
||||
local crc=0
|
||||
local i j byte
|
||||
for i in $(echo "$data" | sed 's/../& /g'); do
|
||||
byte=$((16#$i))
|
||||
crc=$((crc ^ byte))
|
||||
for j in $(seq 1 8); do
|
||||
if [ $((crc & 0x80)) -ne 0 ]; then
|
||||
crc=$(( ((crc << 1) ^ 0x31) & 0xFF ))
|
||||
else
|
||||
crc=$(( (crc << 1) & 0xFF ))
|
||||
fi
|
||||
done
|
||||
done
|
||||
printf '%02x' $crc
|
||||
}
|
||||
|
||||
# Build a protocol frame: magic(2) + len(2) + cmd(1) + payload(N) + crc8(1)
|
||||
build_frame() {
|
||||
local cmd_hex="$1"
|
||||
local payload_hex="$2"
|
||||
local payload_len=$((${#payload_hex} / 2))
|
||||
local total_len=$((payload_len + 1)) # cmd + payload
|
||||
local len_hi=$(printf '%02x' $((total_len >> 8)))
|
||||
local len_lo=$(printf '%02x' $((total_len & 0xFF)))
|
||||
local frame_data="${len_hi}${len_lo}${cmd_hex}${payload_hex}"
|
||||
local crc=$(crc8 "$frame_data")
|
||||
echo "FD01${frame_data}${crc}"
|
||||
}
|
||||
|
||||
# Send raw hex frame to Flipper
|
||||
send_raw() {
|
||||
local hex="$1"
|
||||
local dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null)
|
||||
[ -z "$dev" ] || [ ! -c "$dev" ] && return 1
|
||||
echo "$hex" | xxd -r -p > "$dev"
|
||||
}
|
||||
|
||||
# Send command and wait for response
|
||||
send_cmd() {
|
||||
local cmd="$1"
|
||||
local payload="${2:-}"
|
||||
local timeout="${3:-5}"
|
||||
local frame=$(build_frame "$cmd" "$payload")
|
||||
local dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null)
|
||||
|
||||
[ -z "$dev" ] || [ ! -c "$dev" ] && echo "ERR:no_device" && return 1
|
||||
|
||||
log 3 "TX: cmd=0x${cmd} payload=${payload:-<none>}"
|
||||
|
||||
# Send frame
|
||||
echo "$frame" | xxd -r -p > "$dev" 2>/dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
log 1 "TX failed — device gone?"
|
||||
echo "disconnected" > "$CONFIG_DIR/status"
|
||||
echo "ERR:tx_failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Read response with timeout
|
||||
local response=""
|
||||
response=$(timeout "$timeout" dd if="$dev" bs=1 count=256 2>/dev/null | xxd -p | tr -d '\n')
|
||||
|
||||
if [ -z "$response" ]; then
|
||||
log 1 "RX timeout for cmd 0x${cmd}"
|
||||
echo "ERR:timeout"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log 3 "RX: $response"
|
||||
|
||||
# Parse response — extract after magic bytes
|
||||
local resp_cmd=$(echo "$response" | cut -c9-10)
|
||||
local resp_payload=$(echo "$response" | cut -c11-)
|
||||
|
||||
if [ "$resp_cmd" = "fe" ]; then
|
||||
echo "OK:${resp_payload}"
|
||||
elif [ "$resp_cmd" = "ff" ]; then
|
||||
echo "ERR:${resp_payload}"
|
||||
else
|
||||
echo "RAW:${response}"
|
||||
fi
|
||||
}
|
||||
|
||||
#############################
|
||||
# Handshake with Flipper
|
||||
#############################
|
||||
|
||||
handshake() {
|
||||
log 2 "Initiating handshake with Flipper Zero..."
|
||||
|
||||
# Send PING
|
||||
local resp=$(send_cmd "01" "" 3)
|
||||
if ! echo "$resp" | grep -q "^OK"; then
|
||||
log 1 "Handshake PING failed: $resp"
|
||||
return 1
|
||||
fi
|
||||
log 2 "PING OK"
|
||||
|
||||
# Get version
|
||||
resp=$(send_cmd "02" "" 3)
|
||||
if echo "$resp" | grep -q "^OK:"; then
|
||||
local version_hex=$(echo "$resp" | sed 's/OK://')
|
||||
echo "$version_hex" | xxd -r -p > "$CONFIG_DIR/flipper_version" 2>/dev/null
|
||||
log 2 "Flipper version: $(cat "$CONFIG_DIR/flipper_version")"
|
||||
fi
|
||||
|
||||
# Get capabilities
|
||||
resp=$(send_cmd "03" "" 3)
|
||||
if echo "$resp" | grep -q "^OK:"; then
|
||||
echo "$resp" | sed 's/OK://' > "$CONFIG_DIR/flipper_caps"
|
||||
log 2 "Capabilities: $(cat "$CONFIG_DIR/flipper_caps")"
|
||||
fi
|
||||
|
||||
echo "connected" > "$CONFIG_DIR/status"
|
||||
log 2 "Handshake complete — bridge active"
|
||||
return 0
|
||||
}
|
||||
|
||||
#############################
|
||||
# Command FIFO handler
|
||||
# WebUI and other tools write commands here
|
||||
#############################
|
||||
|
||||
process_api_command() {
|
||||
local cmd_line="$1"
|
||||
local req_id=$(echo "$cmd_line" | cut -d'|' -f1)
|
||||
local subsystem=$(echo "$cmd_line" | cut -d'|' -f2)
|
||||
local action=$(echo "$cmd_line" | cut -d'|' -f3)
|
||||
local params=$(echo "$cmd_line" | cut -d'|' -f4-)
|
||||
|
||||
log 3 "API cmd: $req_id $subsystem $action $params"
|
||||
|
||||
local result=""
|
||||
|
||||
case "$subsystem" in
|
||||
system)
|
||||
case "$action" in
|
||||
ping) result=$(send_cmd "01") ;;
|
||||
version) result=$(send_cmd "02") ;;
|
||||
caps) result=$(send_cmd "03") ;;
|
||||
status) result=$(send_cmd "04") ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
gpio)
|
||||
case "$action" in
|
||||
init)
|
||||
local pin=$(echo "$params" | cut -d',' -f1)
|
||||
local mode=$(echo "$params" | cut -d',' -f2)
|
||||
local pin_hex=$(printf '%02x' "$pin")
|
||||
local mode_hex=$(printf '%02x' "$mode")
|
||||
result=$(send_cmd "10" "${pin_hex}${mode_hex}")
|
||||
;;
|
||||
write)
|
||||
local pin=$(echo "$params" | cut -d',' -f1)
|
||||
local val=$(echo "$params" | cut -d',' -f2)
|
||||
result=$(send_cmd "11" "$(printf '%02x%02x' "$pin" "$val")")
|
||||
;;
|
||||
read)
|
||||
local pin=$(echo "$params" | cut -d',' -f1)
|
||||
result=$(send_cmd "12" "$(printf '%02x' "$pin")")
|
||||
;;
|
||||
pwm)
|
||||
local pin=$(echo "$params" | cut -d',' -f1)
|
||||
local freq=$(echo "$params" | cut -d',' -f2)
|
||||
local duty=$(echo "$params" | cut -d',' -f3)
|
||||
local pin_hex=$(printf '%02x' "$pin")
|
||||
local freq_hex=$(printf '%08x' "$freq")
|
||||
local duty_hex=$(printf '%02x' "$duty")
|
||||
result=$(send_cmd "13" "${pin_hex}${freq_hex}${duty_hex}")
|
||||
;;
|
||||
adc)
|
||||
local pin=$(echo "$params" | cut -d',' -f1)
|
||||
result=$(send_cmd "14" "$(printf '%02x' "$pin")")
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
subghz)
|
||||
case "$action" in
|
||||
set_freq)
|
||||
local freq=$(echo "$params" | cut -d',' -f1)
|
||||
result=$(send_cmd "20" "$(printf '%08x' "$freq")")
|
||||
;;
|
||||
tx)
|
||||
local data_hex="$params"
|
||||
result=$(send_cmd "21" "$data_hex" 10)
|
||||
;;
|
||||
rx_start) result=$(send_cmd "22") ;;
|
||||
rx_stop) result=$(send_cmd "23") ;;
|
||||
get_rssi) result=$(send_cmd "24") ;;
|
||||
set_mod)
|
||||
local mod=$(echo "$params" | cut -d',' -f1)
|
||||
local bw=$(echo "$params" | cut -d',' -f2)
|
||||
result=$(send_cmd "25" "$(printf '%02x%02x' "$mod" "$bw")")
|
||||
;;
|
||||
replay)
|
||||
local slot=$(echo "$params" | cut -d',' -f1)
|
||||
result=$(send_cmd "26" "$(printf '%02x' "$slot")")
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
rfid)
|
||||
case "$action" in
|
||||
read) result=$(send_cmd "30" "" 10) ;;
|
||||
emulate) result=$(send_cmd "31" "$params") ;;
|
||||
write) result=$(send_cmd "32" "$params") ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
nfc)
|
||||
case "$action" in
|
||||
poll) result=$(send_cmd "40" "" 10) ;;
|
||||
read_full) result=$(send_cmd "41" "" 15) ;;
|
||||
emulate) result=$(send_cmd "42" "$params") ;;
|
||||
relay_start) result=$(send_cmd "43") ;;
|
||||
relay_stop) result=$(send_cmd "44") ;;
|
||||
raw_exchange) result=$(send_cmd "45" "$params" 10) ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
ir)
|
||||
case "$action" in
|
||||
tx)
|
||||
local proto=$(echo "$params" | cut -d',' -f1)
|
||||
local addr=$(echo "$params" | cut -d',' -f2)
|
||||
local cmd=$(echo "$params" | cut -d',' -f3)
|
||||
result=$(send_cmd "50" "$(printf '%02x%08x%08x' "$proto" "$addr" "$cmd")")
|
||||
;;
|
||||
tx_raw) result=$(send_cmd "51" "$params" 10) ;;
|
||||
rx_start) result=$(send_cmd "52") ;;
|
||||
rx_stop) result=$(send_cmd "53") ;;
|
||||
replay) result=$(send_cmd "54" "$(printf '%02x' "$params")") ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
ibutton)
|
||||
case "$action" in
|
||||
read) result=$(send_cmd "60" "" 10) ;;
|
||||
emulate) result=$(send_cmd "61" "$params") ;;
|
||||
write) result=$(send_cmd "62" "$params") ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
badusb)
|
||||
case "$action" in
|
||||
start) result=$(send_cmd "70") ;;
|
||||
exec) result=$(send_cmd "71" "$(echo -n "$params" | xxd -p)") ;;
|
||||
stop) result=$(send_cmd "72") ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
file)
|
||||
case "$action" in
|
||||
list) result=$(send_cmd "90" "$(echo -n "$params" | xxd -p)" 10) ;;
|
||||
read) result=$(send_cmd "91" "$(echo -n "$params" | xxd -p)" 10) ;;
|
||||
write) result=$(send_cmd "92" "$params" 10) ;;
|
||||
delete) result=$(send_cmd "93" "$(echo -n "$params" | xxd -p)") ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
*)
|
||||
result="ERR:unknown_subsystem"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Write result to response file
|
||||
echo "$result" > "$RESP_DIR/${req_id}"
|
||||
log 3 "Result for $req_id: $result"
|
||||
}
|
||||
|
||||
#############################
|
||||
# Async event listener
|
||||
# Reads unsolicited events from Flipper
|
||||
#############################
|
||||
|
||||
event_listener() {
|
||||
local dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null)
|
||||
[ -z "$dev" ] || [ ! -c "$dev" ] && return
|
||||
|
||||
log 2 "Event listener started on $dev"
|
||||
|
||||
while true; do
|
||||
# Read from device, look for event frames (cmd >= 0xA0)
|
||||
local raw=$(dd if="$dev" bs=1 count=2 2>/dev/null | xxd -p)
|
||||
|
||||
# Check for magic bytes
|
||||
if [ "$raw" = "fd01" ]; then
|
||||
# Read length
|
||||
local len_hex=$(dd if="$dev" bs=1 count=2 2>/dev/null | xxd -p)
|
||||
local len=$((16#$len_hex))
|
||||
|
||||
# Read cmd + payload + crc
|
||||
local data=$(dd if="$dev" bs=1 count=$((len + 1)) 2>/dev/null | xxd -p)
|
||||
local event_cmd=$(echo "$data" | cut -c1-2)
|
||||
local event_payload=$(echo "$data" | cut -c3-$((${#data} - 2)))
|
||||
|
||||
case "$event_cmd" in
|
||||
a0) # GPIO interrupt
|
||||
local pin=$((16#$(echo "$event_payload" | cut -c1-2)))
|
||||
local val=$((16#$(echo "$event_payload" | cut -c3-4)))
|
||||
log 3 "GPIO IRQ: pin=$pin val=$val"
|
||||
echo "gpio_irq|$pin|$val|$(date +%s)" >> "$CONFIG_DIR/events"
|
||||
;;
|
||||
a1) # SubGHz RX data
|
||||
log 3 "SubGHz RX: $event_payload"
|
||||
echo "subghz_rx|$event_payload|$(date +%s)" >> "$CONFIG_DIR/events"
|
||||
;;
|
||||
a2) # IR RX
|
||||
log 3 "IR RX: $event_payload"
|
||||
echo "ir_rx|$event_payload|$(date +%s)" >> "$CONFIG_DIR/events"
|
||||
;;
|
||||
a3) # NFC field detected
|
||||
log 3 "NFC field event: $event_payload"
|
||||
echo "nfc_field|$event_payload|$(date +%s)" >> "$CONFIG_DIR/events"
|
||||
;;
|
||||
a4) # Button press on Flipper
|
||||
local btn=$((16#$(echo "$event_payload" | cut -c1-2)))
|
||||
local state=$((16#$(echo "$event_payload" | cut -c3-4)))
|
||||
log 3 "Button: btn=$btn state=$state"
|
||||
echo "button|$btn|$state|$(date +%s)" >> "$CONFIG_DIR/events"
|
||||
;;
|
||||
a5) # CPU offload request from Flipper
|
||||
local task_id=$(echo "$event_payload" | cut -c1-8)
|
||||
local workload=$(echo "$event_payload" | cut -c9-)
|
||||
log 2 "CPU offload request: task=$task_id"
|
||||
handle_cpu_offload "$task_id" "$workload" &
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
#############################
|
||||
# CPU sharing — run Flipper's workload on phone
|
||||
#############################
|
||||
|
||||
handle_cpu_offload() {
|
||||
local task_id="$1"
|
||||
local workload_hex="$2"
|
||||
|
||||
log 2 "Processing CPU offload task $task_id"
|
||||
|
||||
# Decode workload — it's a simple bytecode or shell command
|
||||
local workload=$(echo "$workload_hex" | xxd -r -p)
|
||||
local result=""
|
||||
|
||||
# Execute in sandboxed context
|
||||
result=$(echo "$workload" | timeout 30 sh 2>&1)
|
||||
local exit_code=$?
|
||||
|
||||
# Send result back to Flipper
|
||||
local status_hex=$(printf '%02x' $exit_code)
|
||||
local result_hex=$(echo -n "$result" | xxd -p | tr -d '\n')
|
||||
send_cmd "81" "${task_id}${status_hex}${result_hex}" 5
|
||||
|
||||
log 2 "CPU task $task_id complete (exit=$exit_code)"
|
||||
}
|
||||
|
||||
#############################
|
||||
# Connection monitor
|
||||
#############################
|
||||
|
||||
connection_monitor() {
|
||||
while true; do
|
||||
sleep 10
|
||||
local dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null)
|
||||
local status=$(cat "$CONFIG_DIR/status" 2>/dev/null)
|
||||
|
||||
if [ "$status" = "connected" ]; then
|
||||
# Check if device still exists
|
||||
if [ ! -c "$dev" ]; then
|
||||
log 1 "Flipper device $dev disappeared — disconnected"
|
||||
echo "disconnected" > "$CONFIG_DIR/status"
|
||||
|
||||
# Try to rediscover
|
||||
sleep 5
|
||||
local new_dev=""
|
||||
for tty in /dev/ttyACM*; do
|
||||
[ -c "$tty" ] && new_dev="$tty" && break
|
||||
done
|
||||
|
||||
if [ -n "$new_dev" ]; then
|
||||
log 2 "Flipper reconnected on $new_dev"
|
||||
echo "$new_dev" > "$CONFIG_DIR/flipper_dev"
|
||||
stty -F "$new_dev" ${BAUD_RATE:-115200} raw -echo -echoe -echok 2>/dev/null
|
||||
handshake && echo "connected" > "$CONFIG_DIR/status"
|
||||
fi
|
||||
else
|
||||
# Heartbeat ping
|
||||
local resp=$(send_cmd "01" "" 2)
|
||||
if ! echo "$resp" | grep -q "^OK"; then
|
||||
log 1 "Heartbeat failed — marking disconnected"
|
||||
echo "disconnected" > "$CONFIG_DIR/status"
|
||||
fi
|
||||
fi
|
||||
elif [ "$status" = "disconnected" ]; then
|
||||
# Try to find Flipper
|
||||
for tty in /dev/ttyACM*; do
|
||||
if [ -c "$tty" ]; then
|
||||
log 2 "Found new device $tty, attempting handshake"
|
||||
echo "$tty" > "$CONFIG_DIR/flipper_dev"
|
||||
stty -F "$tty" ${BAUD_RATE:-115200} raw -echo -echoe -echok 2>/dev/null
|
||||
if handshake; then
|
||||
echo "connected" > "$CONFIG_DIR/status"
|
||||
fi
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
#############################
|
||||
# Main loop
|
||||
#############################
|
||||
|
||||
log 2 "FlipperDroid bridge daemon starting"
|
||||
|
||||
# Create command FIFO
|
||||
rm -f "$CMD_FIFO"
|
||||
mkfifo "$CMD_FIFO" 2>/dev/null
|
||||
chmod 660 "$CMD_FIFO"
|
||||
|
||||
# Initial handshake
|
||||
if [ "$(cat "$CONFIG_DIR/status" 2>/dev/null)" = "connected" ]; then
|
||||
handshake
|
||||
fi
|
||||
|
||||
# Start connection monitor in background
|
||||
connection_monitor &
|
||||
MONITOR_PID=$!
|
||||
|
||||
# Start event listener in background
|
||||
event_listener &
|
||||
LISTENER_PID=$!
|
||||
|
||||
# Store PIDs for cleanup
|
||||
echo "$MONITOR_PID" > "$CONFIG_DIR/monitor.pid"
|
||||
echo "$LISTENER_PID" > "$CONFIG_DIR/listener.pid"
|
||||
|
||||
# Main command processing loop — reads from FIFO
|
||||
log 2 "Bridge daemon ready, listening on $CMD_FIFO"
|
||||
|
||||
while true; do
|
||||
if read -r cmd_line < "$CMD_FIFO" 2>/dev/null; then
|
||||
[ -n "$cmd_line" ] && process_api_command "$cmd_line" &
|
||||
else
|
||||
sleep 1
|
||||
fi
|
||||
done
|
||||
Reference in New Issue
Block a user