RadioControl v1.0.0 — KernelSU-Next module for radio engineering

Shannon 5400 AT command terminal, BCM4390 WiFi mode switching,
carrier config override, debugfs browser, RF thermal monitoring,
CP debug, Thread/Wonder radio, satellite/NTN test support.

Verified on Pixel 10 Pro Fold (Tensor G5 / laguna).
This commit is contained in:
sssnake
2026-03-31 04:27:24 -07:00
commit bb8f2aae2a
16 changed files with 4153 additions and 0 deletions

726
system/bin/radiocontrol Executable file
View File

@@ -0,0 +1,726 @@
#!/system/bin/sh
# RadioControl WebUI — HTTP server with hardware interface APIs
WEBROOT="/data/adb/modules/radiocontrol/webroot"
CONFIG_DIR="/data/adb/radiocontrol"
CONFIG_FILE="$CONFIG_DIR/config.sh"
PORT=8088
mkdir -p "$CONFIG_DIR"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [webui] $1"; }
###########################
# Hardware interface helpers
###########################
# Send AT command to modem — tries rc_shannon first, then raw devices
send_at_cmd() {
local cmd="$1"
local timeout="${2:-3}"
local result=""
# Try our kernel module device first
if [ -c /dev/rc_shannon ]; then
echo -e "${cmd}\r" > /dev/rc_shannon
result=$(timeout "$timeout" cat /dev/rc_shannon 2>/dev/null)
[ -n "$result" ] && echo "$result" && return 0
fi
# Try available AT devices — umts_router is confirmed working on Tensor/Shannon 5400
for dev in /dev/umts_router /dev/umts_atc0 /dev/nr_atc0 /dev/umts_atc1 /dev/smd7 /dev/ttyHS0; do
if [ -c "$dev" ]; then
result=$(
exec 3<>"$dev" 2>/dev/null
if [ $? -eq 0 ]; then
printf "${cmd}\r\n" >&3
sleep "$timeout"
local buf=""
while read -t 1 line <&3 2>/dev/null; do
buf="${buf}${line}\n"
done
echo -e "$buf"
exec 3>&-
fi
)
[ -n "$result" ] && echo "$result" && return 0
fi
done
echo "ERROR: No modem AT interface available"
return 1
}
# Read a debugfs or sysfs path safely
read_sys_path() {
local path="$1"
local max_bytes="${2:-4096}"
# Validate path — only allow /sys and /proc
case "$path" in
/sys/kernel/debug/*|/sys/class/*|/sys/devices/*|/sys/module/*|/sys/bus/*|/proc/*)
;;
*)
echo "ERROR: path not allowed"
return 1
;;
esac
if [ -f "$path" ]; then
head -c "$max_bytes" "$path" 2>/dev/null
elif [ -d "$path" ]; then
ls -la "$path" 2>/dev/null
else
echo "ERROR: not found"
return 1
fi
}
# Write to a sysfs path
write_sys_path() {
local path="$1"
local value="$2"
case "$path" in
/sys/class/*|/sys/devices/*|/sys/module/*)
;;
*)
echo "ERROR: write path not allowed"
return 1
;;
esac
if [ -w "$path" ]; then
echo "$value" > "$path" 2>/dev/null && echo "OK" || echo "ERROR: write failed"
else
echo "ERROR: not writable"
return 1
fi
}
# List directory contents as JSON
list_dir_json() {
local dir="$1"
local result="["
local first=1
if [ ! -d "$dir" ]; then
echo "[]"
return
fi
for entry in "$dir"/*; do
[ ! -e "$entry" ] && continue
local name=$(basename "$entry")
local type="file"
[ -d "$entry" ] && type="dir"
local readable="false"
[ -r "$entry" ] && readable="true"
local writable="false"
[ -w "$entry" ] && writable="true"
if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
result="$result{\"name\":\"$name\",\"type\":\"$type\",\"readable\":$readable,\"writable\":$writable}"
done
echo "${result}]"
}
# Get WiFi interface details
get_wifi_details() {
cat "$CONFIG_DIR/wifi_info" 2>/dev/null
}
# Get loaded kernel modules status
get_kmod_status() {
local result="{"
local mon="false" shan="false" diag="false"
lsmod 2>/dev/null | grep -q rc_wifi_mon && mon="true"
lsmod 2>/dev/null | grep -q rc_shannon_cmd && shan="true"
lsmod 2>/dev/null | grep -q rc_diag_bridge && diag="true"
echo "{\"wifi_mon\":$mon,\"shannon_cmd\":$shan,\"diag_bridge\":$diag}"
}
# Get thermal zones related to radios
get_thermal_info() {
local result="["
local first=1
for tz in /sys/class/thermal/thermal_zone*; do
local type=$(cat "$tz/type" 2>/dev/null)
case "$type" in
*modem*|*pa*|*wlan*|*wifi*|*rf*|*mdm*|*qfe*|*battery*)
local temp=$(cat "$tz/temp" 2>/dev/null)
if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
result="$result{\"zone\":\"$(basename $tz)\",\"type\":\"$type\",\"temp\":$temp}"
;;
esac
done
echo "${result}]"
}
# Get current config
get_current_config() {
source "$CONFIG_FILE" 2>/dev/null
local soc=$(cat "$CONFIG_DIR/detected_soc" 2>/dev/null)
cat << CONF
{
"engineering_mode": "${ENGINEERING_MODE:-0}",
"factory_test_mode": "${FACTORY_TEST_MODE:-0}",
"usb_diag_mode": "${USB_DIAG_MODE:-0}",
"hidden_menus": "${HIDDEN_MENUS:-0}",
"modem_log": "${MODEM_LOG:-0}",
"wifi_mode": "${WIFI_MODE:-managed}",
"load_modules": "${LOAD_MODULES:-}",
"detected_soc": "$soc"
}
CONF
}
# Get available modem interfaces
get_modem_interfaces() {
local result="["
local first=1
for dev in /dev/umts_atc0 /dev/umts_atc1 /dev/umts_router0 /dev/umts_ipc0 \
/dev/umts_dm0 /dev/umts_boot0 /dev/umts_ramdump0 /dev/umts_rfs0 \
/dev/nr_atc0 /dev/nr_ipc0 \
/dev/diag /dev/smd7 /dev/ttyHS0 /dev/ttyMSM0 /dev/at_mdm0 \
/dev/rc_shannon /dev/rc_diag; do
if [ -c "$dev" ]; then
local perms=$(ls -la "$dev" 2>/dev/null | awk '{print $1}')
if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
result="$result{\"path\":\"$dev\",\"perms\":\"$perms\"}"
fi
done
echo "${result}]"
}
# Get radio info from props and modem
get_radio_info() {
local baseband=$(getprop gsm.version.baseband 2>/dev/null)
local ril=$(getprop gsm.version.ril-impl 2>/dev/null)
local nettype=$(getprop gsm.network.type 2>/dev/null)
local simstate=$(getprop gsm.sim.state 2>/dev/null)
local operator=$(getprop gsm.operator.alpha 2>/dev/null)
local chipset=$(getprop ro.board.platform 2>/dev/null)
local hardware=$(getprop ro.hardware 2>/dev/null)
local soc=$(cat "$CONFIG_DIR/detected_soc" 2>/dev/null)
cat << INFO
{
"baseband":"$baseband",
"ril":"$ril",
"network_type":"$nettype",
"sim_state":"$simstate",
"operator":"$operator",
"chipset":"$chipset",
"hardware":"$hardware",
"soc_family":"$soc"
}
INFO
}
# Get system flags
get_system_flags() {
local result="["
local first=1
for prop in \
ro.build.type ro.debuggable ro.secure ro.adb.secure \
persist.sys.usb.config persist.sys.factorytest ro.factorytest \
persist.radio.fieldtest ro.telephony.hidden_menu \
persist.radio.hidden_menu persist.mtk.engineer \
persist.vendor.radio.adb_log_on persist.radio.ramdump \
persist.vendor.radio.enableadvanced persist.radio.field_test \
persist.vendor.radio.ca_info persist.vendor.radio.nr5g \
persist.vendor.radio.modem_log persist.cp.log persist.cp.rat \
ro.board.platform ro.hardware ro.hardware.chipname \
gsm.version.baseband gsm.version.ril-impl; do
val=$(getprop "$prop" 2>/dev/null)
if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
result="$result{\"prop\":\"$prop\",\"value\":\"$val\"}"
done
echo "${result}]"
}
# Update config key
update_config() {
local key="$1" val="$2"
case "$key" in
ENGINEERING_MODE|FACTORY_TEST_MODE|USB_DIAG_MODE|HIDDEN_MENUS|MODEM_LOG)
case "$val" in 0|1) ;; *) echo '{"ok":false,"error":"invalid value"}'; return ;; esac ;;
WIFI_MODE)
case "$val" in managed|monitor|injection|mesh|ap) ;; *) echo '{"ok":false,"error":"invalid mode"}'; return ;; esac ;;
LOAD_MODULES)
;; # freeform
*) echo '{"ok":false,"error":"invalid key"}'; return ;;
esac
if grep -q "^${key}=" "$CONFIG_FILE" 2>/dev/null; then
sed -i "s|^${key}=.*|${key}=${val}|" "$CONFIG_FILE"
else
echo "${key}=${val}" >> "$CONFIG_FILE"
fi
echo "{\"ok\":true}"
}
# Set WiFi mode immediately
set_wifi_mode() {
local mode="$1"
local iface=""
for candidate in wlan0 wlan1 wifi0; do
[ -d "/sys/class/net/$candidate" ] && iface="$candidate" && break
done
[ -z "$iface" ] && echo '{"ok":false,"error":"no wifi interface"}' && return
ip link set "$iface" down 2>/dev/null
case "$mode" in
monitor) iw dev "$iface" set type monitor 2>/dev/null ;;
injection) iw dev "$iface" set type monitor 2>/dev/null
iw dev "$iface" set monitor fcsfail otherbss 2>/dev/null ;;
mesh) iw dev "$iface" set type mesh 2>/dev/null ;;
ap) iw dev "$iface" set type __ap 2>/dev/null ;;
managed|*) iw dev "$iface" set type managed 2>/dev/null; mode="managed" ;;
esac
ip link set "$iface" up 2>/dev/null
sed -i "s/^WIFI_MODE=.*/WIFI_MODE=$mode/" "$CONFIG_FILE" 2>/dev/null
echo "{\"ok\":true,\"mode\":\"$mode\",\"iface\":\"$iface\"}"
}
# Get BCM4390 driver parameters
get_wifi_params() {
local result="["
local first=1
local pdir="/sys/module/bcmdhd4390/parameters"
[ ! -d "$pdir" ] && echo "[]" && return
for p in "$pdir"/*; do
local name=$(basename "$p")
local val=$(cat "$p" 2>/dev/null)
local writable="false"
[ -w "$p" ] && writable="true"
# Escape val for JSON
val=$(echo "$val" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ')
if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
result="$result{\"name\":\"$name\",\"value\":\"$val\",\"writable\":$writable}"
done
echo "${result}]"
}
# Set BCM4390 driver parameter
set_wifi_param() {
local name="$1" val="$2"
local path="/sys/module/bcmdhd4390/parameters/$name"
# Validate name (no path traversal)
case "$name" in */*|*..*) echo '{"ok":false,"error":"invalid param name"}'; return ;; esac
if [ -w "$path" ]; then
echo "$val" > "$path" 2>/dev/null
local newval=$(cat "$path" 2>/dev/null)
echo "{\"ok\":true,\"value\":\"$newval\"}"
else
echo "{\"ok\":false,\"error\":\"param not writable\"}"
fi
}
# Get WiFi firmware info
get_wifi_firmware() {
local result="["
local first=1
for fw in /vendor/firmware/fw_bcmdhd* /vendor/firmware/bcmdhd*; do
[ ! -f "$fw" ] && continue
local name=$(basename "$fw")
local size=$(stat -c%s "$fw" 2>/dev/null || ls -la "$fw" | awk '{print $5}')
if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
result="$result{\"path\":\"$fw\",\"name\":\"$name\",\"size\":$size}"
done
# Current loaded firmware
local cur_fw=$(cat /sys/module/bcmdhd4390/parameters/firmware_path 2>/dev/null)
local cur_nv=$(cat /sys/module/bcmdhd4390/parameters/nvram_path 2>/dev/null)
local info=$(cat /sys/module/bcmdhd4390/parameters/info_string 2>/dev/null | sed 's/"/\\"/g' | tr '\n' ' ')
echo "${result}],\"current_fw\":\"$cur_fw\",\"current_nvram\":\"$cur_nv\",\"info\":\"$info\"}"
}
# Get CP (modem processor) debug info
get_cp_debug() {
local state=$(cat /sys/devices/platform/cpif/modem_state 2>/dev/null)
local pcie=""
local sbb=""
[ -f /sys/devices/platform/cpif/modem/pcie_event_stats ] && \
pcie=$(cat /sys/devices/platform/cpif/modem/pcie_event_stats 2>/dev/null | sed 's/"/\\"/g' | tr '\n' '|' | sed 's/|/\\n/g')
[ -f /sys/devices/platform/cpif/modem/sbb_debug ] && \
sbb=$(cat /sys/devices/platform/cpif/modem/sbb_debug 2>/dev/null | sed 's/"/\\"/g' | tr '\n' '|' | sed 's/|/\\n/g')
[ -f /sys/devices/platform/cpif/modem/power_stats ] && \
power=$(cat /sys/devices/platform/cpif/modem/power_stats 2>/dev/null | sed 's/"/\\"/g' | tr '\n' '|' | sed 's/|/\\n/g')
echo "{\"state\":\"$state\",\"pcie_stats\":\"$pcie\",\"sbb_debug\":\"$sbb\",\"power_stats\":\"$power\"}"
}
# Trigger CP crash dump
trigger_cp_crash() {
if [ -f /sys/devices/platform/cpif/do_cp_crash ]; then
echo 1 > /sys/devices/platform/cpif/do_cp_crash 2>/dev/null
echo '{"ok":true,"msg":"CP crash triggered — ramdump will be saved to /data/vendor/log/cbd/"}'
else
echo '{"ok":false,"error":"do_cp_crash not available"}'
fi
}
# Carrier config — read current settings
get_carrier_config() {
local volte=$(settings get global enhanced_4g_mode_enabled 2>/dev/null)
local wfc=$(settings get global wifi_calling_enabled 2>/dev/null)
local netsel=$(getprop persist.dbg.hide_preferred_network_type 2>/dev/null)
echo "{\"volte\":\"$volte\",\"wfc\":\"$wfc\",\"hide_network_type\":\"$netsel\"}"
}
# Carrier config — set a carrier override
set_carrier_flag() {
local flag="$1" val="$2"
case "$flag" in
volte)
settings put global enhanced_4g_mode_enabled "$val" 2>/dev/null
echo '{"ok":true}'
;;
vonr)
settings put global vonr_enabled "$val" 2>/dev/null
resetprop persist.vendor.radio.vonr_enabled "$val" 2>/dev/null
echo '{"ok":true}'
;;
wfc)
settings put global wifi_calling_enabled "$val" 2>/dev/null
echo '{"ok":true}'
;;
vt)
settings put global vt_ims_enabled "$val" 2>/dev/null
echo '{"ok":true}'
;;
apn)
resetprop persist.dbg.allow_adding_apns 1 2>/dev/null
settings put global allow_adding_apns "$val" 2>/dev/null
echo '{"ok":true}'
;;
nradv)
resetprop persist.vendor.radio.nr_advanced "$val" 2>/dev/null
echo '{"ok":true}'
;;
nettype)
# val=1 means SHOW (so hide=0)
resetprop persist.dbg.hide_preferred_network_type "$( [ "$val" = "1" ] && echo 0 || echo 1)" 2>/dev/null
echo '{"ok":true}'
;;
*) echo '{"ok":false,"error":"unknown flag"}' ;;
esac
}
# List carrier settings protobuf files
get_carrier_files() {
local result="["
local first=1
local csdir="/product/etc/CarrierSettings"
if [ -d "$csdir" ]; then
for f in "$csdir"/*; do
[ ! -f "$f" ] && continue
local name=$(basename "$f")
local size=$(stat -c%s "$f" 2>/dev/null || ls -la "$f" | awk '{print $5}')
if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
result="$result{\"name\":\"$name\",\"size\":$size}"
done
fi
echo "${result}]"
}
# Dump carrier_config service
dump_carrier_config() {
dumpsys carrier_config 2>/dev/null | head -500
}
# Thread radio (Wonder) info
get_thread_info() {
local result="{"
local has_wonder="false"
if [ -d /sys/class/ieee80211/wonder ]; then
has_wonder="true"
local mac=$(cat /sys/class/ieee80211/wonder/macaddress 2>/dev/null)
local name=$(cat /sys/class/ieee80211/wonder/name 2>/dev/null)
local idx=$(cat /sys/class/ieee80211/wonder/index 2>/dev/null)
result="$result\"present\":true,\"mac\":\"$mac\",\"name\":\"$name\",\"index\":\"$idx\""
# debugfs
local dbg_entries=""
if [ -d /sys/kernel/debug/wonder ]; then
dbg_entries=$(ls /sys/kernel/debug/wonder/ 2>/dev/null | tr '\n' ',' | sed 's/,$//')
fi
result="$result,\"debugfs_entries\":\"$dbg_entries\""
# force_stop_tx status
if [ -f /sys/kernel/debug/wonder/force_stop_tx ]; then
local fstx=$(cat /sys/kernel/debug/wonder/force_stop_tx 2>/dev/null)
result="$result,\"force_stop_tx\":\"$fstx\""
fi
# thread-wpan interface
if [ -d /sys/class/net/thread-wpan ]; then
result="$result,\"wpan_iface\":true"
else
result="$result,\"wpan_iface\":false"
fi
else
result="$result\"present\":false"
fi
echo "${result}}"
}
# Load/unload kernel module
manage_kmod() {
local action="$1" mod="$2"
local kmod_dir="/data/adb/modules/radiocontrol/common/kmod"
case "$mod" in
rc_wifi_mon|rc_shannon_cmd|rc_diag_bridge) ;;
*) echo '{"ok":false,"error":"unknown module"}'; return ;;
esac
if [ "$action" = "load" ]; then
if lsmod 2>/dev/null | grep -q "$mod"; then
echo '{"ok":true,"status":"already loaded"}'
elif [ -f "$kmod_dir/${mod}.ko" ]; then
insmod "$kmod_dir/${mod}.ko" 2>/dev/null
if [ $? -eq 0 ]; then
echo '{"ok":true,"status":"loaded"}'
else
echo '{"ok":false,"error":"insmod failed — kernel version mismatch?"}'
fi
else
echo '{"ok":false,"error":"module .ko not found"}'
fi
elif [ "$action" = "unload" ]; then
rmmod "$mod" 2>/dev/null
echo '{"ok":true,"status":"unloaded"}'
fi
}
###########################
# HTTP server
###########################
get_mime() {
case "$1" in
*.html) echo "text/html" ;; *.css) echo "text/css" ;;
*.js) echo "text/javascript" ;; *.json) echo "application/json" ;;
*) echo "text/plain" ;;
esac
}
send_response() {
local code="$1" ct="$2" body="$3"
printf "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %d\r\nConnection: close\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: GET,POST,OPTIONS\r\nAccess-Control-Allow-Headers: Content-Type\r\n\r\n%s" \
"$code" "$ct" "${#body}" "$body"
}
send_file() {
local f="$1"
if [ -f "$f" ]; then
send_response "200 OK" "$(get_mime "$f")" "$(cat "$f")"
else
send_response "404 Not Found" "text/plain" "Not Found"
fi
}
# JSON body parser — extract value for key
json_val() {
echo "$1" | sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p"
}
handle_request() {
local method path query body content_length
read -r request_line
method=$(echo "$request_line" | awk '{print $1}')
local full_path=$(echo "$request_line" | awk '{print $2}')
path=$(echo "$full_path" | cut -d'?' -f1)
query=$(echo "$full_path" | grep -o '?.*' | cut -c2-)
while read -r header; do
header=$(echo "$header" | tr -d '\r')
[ -z "$header" ] && break
case "$header" in Content-Length:*) content_length=$(echo "$header" | awk '{print $2}') ;; esac
done
body=""
if [ "$method" = "POST" ] && [ -n "$content_length" ] && [ "$content_length" -gt 0 ] 2>/dev/null; then
body=$(dd bs=1 count="$content_length" 2>/dev/null)
fi
# Handle OPTIONS for CORS
if [ "$method" = "OPTIONS" ]; then
send_response "200 OK" "text/plain" ""
return
fi
log "$method $path"
case "$method $path" in
# Static files
"GET /") send_file "$WEBROOT/index.html" ;;
"GET /css/"*|"GET /js/"*) send_file "$WEBROOT$path" ;;
# Status & info APIs
"GET /api/status") send_response "200 OK" "application/json" "$(get_current_config)" ;;
"GET /api/radio") send_response "200 OK" "application/json" "$(get_radio_info)" ;;
"GET /api/flags") send_response "200 OK" "application/json" "$(get_system_flags)" ;;
"GET /api/thermal") send_response "200 OK" "application/json" "$(get_thermal_info)" ;;
"GET /api/kmod") send_response "200 OK" "application/json" "$(get_kmod_status)" ;;
"GET /api/modem/interfaces") send_response "200 OK" "application/json" "$(get_modem_interfaces)" ;;
"GET /api/wifi/info") send_response "200 OK" "text/plain" "$(get_wifi_details)" ;;
"GET /api/wifi/params") send_response "200 OK" "application/json" "$(get_wifi_params)" ;;
"GET /api/wifi/firmware")
local fwdata=$(get_wifi_firmware)
send_response "200 OK" "application/json" "{\"files\":$fwdata}"
;;
"GET /api/cp") send_response "200 OK" "application/json" "$(get_cp_debug)" ;;
"GET /api/carrier/config") send_response "200 OK" "application/json" "$(get_carrier_config)" ;;
"GET /api/carrier/files") send_response "200 OK" "application/json" "$(get_carrier_files)" ;;
"GET /api/carrier/dump")
local dump=$(dump_carrier_config | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' '|' | sed 's/|/\\n/g')
send_response "200 OK" "application/json" "{\"dump\":\"$dump\"}"
;;
"GET /api/thread") send_response "200 OK" "application/json" "$(get_thread_info)" ;;
# AT command terminal
"POST /api/at")
local cmd=$(json_val "$body" "cmd")
local timeout=$(json_val "$body" "timeout")
[ -z "$timeout" ] && timeout=3
local resp=$(send_at_cmd "$cmd" "$timeout")
# Escape for JSON
resp=$(echo "$resp" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' '|' | sed 's/|/\\n/g')
send_response "200 OK" "application/json" "{\"ok\":true,\"response\":\"$resp\"}"
;;
# debugfs/sysfs browser
"POST /api/fs/read")
local fspath=$(json_val "$body" "path")
if [ -d "$fspath" ]; then
local listing=$(list_dir_json "$fspath")
send_response "200 OK" "application/json" "{\"type\":\"dir\",\"entries\":$listing}"
else
local content=$(read_sys_path "$fspath" 8192)
content=$(echo "$content" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' '|' | sed 's/|/\\n/g')
send_response "200 OK" "application/json" "{\"type\":\"file\",\"content\":\"$content\"}"
fi
;;
"POST /api/fs/write")
local fspath=$(json_val "$body" "path")
local fsval=$(json_val "$body" "value")
local result=$(write_sys_path "$fspath" "$fsval")
send_response "200 OK" "application/json" "{\"ok\":true,\"result\":\"$result\"}"
;;
# debugfs paths list
"GET /api/debugfs")
local paths=$(cat "$CONFIG_DIR/debugfs_paths" 2>/dev/null)
local result="["
local first=1
for p in $paths; do
[ -z "$p" ] && continue
if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
result="$result\"$p\""
done
send_response "200 OK" "application/json" "${result}]"
;;
# Config update
"POST /api/config")
local key=$(json_val "$body" "key")
local val=$(json_val "$body" "value")
send_response "200 OK" "application/json" "$(update_config "$key" "$val")"
;;
# WiFi mode switch
"POST /api/wifi/mode")
local mode=$(json_val "$body" "mode")
send_response "200 OK" "application/json" "$(set_wifi_mode "$mode")"
;;
# Kernel module management
"POST /api/kmod/load")
local mod=$(json_val "$body" "module")
send_response "200 OK" "application/json" "$(manage_kmod load "$mod")"
;;
"POST /api/kmod/unload")
local mod=$(json_val "$body" "module")
send_response "200 OK" "application/json" "$(manage_kmod unload "$mod")"
;;
# Carrier config flag
"POST /api/carrier/set")
local flag=$(json_val "$body" "flag")
local val=$(json_val "$body" "value")
send_response "200 OK" "application/json" "$(set_carrier_flag "$flag" "$val")"
;;
# WiFi driver param
"POST /api/wifi/param")
local pname=$(json_val "$body" "name")
local pval=$(json_val "$body" "value")
send_response "200 OK" "application/json" "$(set_wifi_param "$pname" "$pval")"
;;
# CP crash dump
"POST /api/cp/crash")
send_response "200 OK" "application/json" "$(trigger_cp_crash)"
;;
# Set prop at runtime
"POST /api/prop")
local prop=$(json_val "$body" "prop")
local val=$(json_val "$body" "value")
case "$prop" in
persist.*|ro.debuggable|ro.build.type|ro.secure|ro.adb.secure|service.adb.root)
resetprop "$prop" "$val" 2>/dev/null
send_response "200 OK" "application/json" "{\"ok\":true}"
;;
*) send_response "200 OK" "application/json" "{\"ok\":false,\"error\":\"not in allowlist\"}" ;;
esac
;;
# Reboot
"POST /api/reboot")
send_response "200 OK" "application/json" '{"ok":true}'
sleep 1; reboot ;;
*) send_response "404 Not Found" "application/json" '{"error":"not found"}' ;;
esac
}
###########################
# Main
###########################
log "Starting HTTP server on port $PORT"
while true; do
handle_request | busybox nc -ll -p "$PORT" -s 127.0.0.1 2>/dev/null \
|| handle_request | toybox nc -L -p "$PORT" -s 127.0.0.1 2>/dev/null \
|| { log "ERROR: No network listener available"; exit 1; }
done