Files
AXP-Resurection/modules/intellirat/system/bin/intellirat
T

297 lines
8.4 KiB
Bash
Executable File

#!/system/bin/sh
set -u
STATE_DIR=${INTELLIRAT_STATE_DIR:-/data/local/tmp}
CONFIG_FILE=${INTELLIRAT_CONFIG:-$STATE_DIR/config.conf}
LOG_FILE=${INTELLIRAT_LOG:-$STATE_DIR/events.log}
CMD_BIN=${INTELLIRAT_CMD:-cmd}
DUMPSYS_BIN=${INTELLIRAT_DUMPSYS:-dumpsys}
DEFAULT_SLOT=0
DEFAULT_HIGH_MASK=00011101001110000111
DEFAULT_LOW_MASK=00011100001110000111
usage() {
cat <<'EOF'
IntelliRAT v0.1 manual prototype
Usage:
intellirat status [slot]
intellirat capture [slot]
intellirat high [slot]
intellirat low [slot]
intellirat restore [slot]
intellirat --dry-run high|low|restore [slot]
The prototype never changes RAT automatically. Capture the current state before
the first transition. Restore returns the captured user network mask.
EOF
}
fail() {
printf 'IntelliRAT: %s\n' "$*" >&2
exit 1
}
load_config() {
TARGET_SLOT=$DEFAULT_SLOT
HIGH_MASK=$DEFAULT_HIGH_MASK
LOW_MASK=$DEFAULT_LOW_MASK
if [ -f "$CONFIG_FILE" ]; then
# The file is created root-only by service.sh; reject unsafe ownership
# only when the platform exposes stat in the available shell.
# shellcheck disable=SC1090
. "$CONFIG_FILE"
fi
case "$TARGET_SLOT" in
''|*[!0-9]*) fail "invalid TARGET_SLOT" ;;
esac
case "$HIGH_MASK:$LOW_MASK" in
*[!01:]*|:*) fail "masks must be non-empty binary strings" ;;
esac
}
ensure_state_dir() {
mkdir -p "$STATE_DIR" || fail "cannot create $STATE_DIR"
if [ "$STATE_DIR" = /data/local/tmp ]; then
return
fi
chown 0 2000 "$STATE_DIR" 2>/dev/null || true
chmod 0770 "$STATE_DIR" 2>/dev/null || true
}
log_event() {
ensure_state_dir
printf '%s %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$*" >> "$LOG_FILE"
chmod 0600 "$LOG_FILE" 2>/dev/null || true
}
slot_or_default() {
if [ "${1:-}" = '' ]; then
printf '%s' "$TARGET_SLOT"
else
case "$1" in
''|*[!0-9]*) fail "invalid slot: $1" ;;
esac
printf '%s' "$1"
fi
}
state_file() {
printf '%s/intellirat-sub-%s.state' "$STATE_DIR" "$1"
}
read_user_names() {
slot=$1
result=$(
"$CMD_BIN" phone get-allowed-network-types-for-users -s "$slot" 2>&1
) || fail "cannot read network types for slot $slot: $result"
case "$result" in
*'No valid subscription found'*|*'No valid NETWORK_TYPES_BITMASK'*)
fail "no valid subscription for slot $slot: $result"
;;
esac
result=$(printf '%s' "$result" | tr -d '[:space:]\r\n')
[ -n "$result" ] || fail "empty network type response for slot $slot"
printf '%s' "$result"
}
network_names_to_decimal() {
names=$1
mask=0
old_ifs=$IFS
IFS='|'
# shellcheck disable=SC2086
for name in $names; do
case "$name" in
GPRS) mask=$((mask | 1)) ;;
EDGE) mask=$((mask | 2)) ;;
UMTS) mask=$((mask | 4)) ;;
CDMA) mask=$((mask | 8)) ;;
EVDO_0) mask=$((mask | 16)) ;;
EVDO_A) mask=$((mask | 32)) ;;
1xRTT) mask=$((mask | 64)) ;;
HSDPA) mask=$((mask | 128)) ;;
HSUPA) mask=$((mask | 256)) ;;
HSPA) mask=$((mask | 512)) ;;
HSPA+) mask=$((mask | 16384)) ;;
IDEN) mask=$((mask | 1024)) ;;
EVDO_B) mask=$((mask | 2048)) ;;
LTE) mask=$((mask | 4096)) ;;
EHRPD) mask=$((mask | 8192)) ;;
HSPAP) mask=$((mask | 16384)) ;;
GSM) mask=$((mask | 32768)) ;;
TD_SCDMA) mask=$((mask | 65536)) ;;
IWLAN) mask=$((mask | 131072)) ;;
LTE_CA) mask=$((mask | 4096)) ;;
NR) mask=$((mask | 524288)) ;;
UNKNOWN|NONE) ;;
*) IFS=$old_ifs; fail "unknown network type from telephony: $name" ;;
esac
done
IFS=$old_ifs
printf '%s' "$mask"
}
decimal_to_binary() {
mask=$1
output=
bit=19
while [ "$bit" -ge 0 ]; do
value=$((1 << bit))
if [ $((mask & value)) -ne 0 ]; then
output="${output}1"
else
output="${output}0"
fi
bit=$((bit - 1))
done
printf '%s' "$output"
}
read_user_binary() {
names=$(read_user_names "$1")
decimal=$(network_names_to_decimal "$names")
decimal_to_binary "$decimal"
}
call_in_progress() {
dump=$(
"$DUMPSYS_BIN" telecom 2>/dev/null || true
)
current_calls=$(printf '%s\n' "$dump" | awk '
/^ All calls:/ { in_calls=1; next }
/^ Active dialing, or connecting calls:/ { in_calls=1; next }
/^ Ringing calls:/ { in_calls=1; next }
/^ Holding calls:/ { in_calls=1; next }
/^[[:space:]]*[A-Za-z][A-Za-z _-]*:$/ { in_calls=0 }
in_calls { print }
')
printf '%s\n' "$current_calls" | grep -Eq \
'(^|[^A-Za-z])(ACTIVE|RINGING|DIALING|CONNECTING)([^A-Za-z]|$)|mCallState=(ACTIVE|RINGING|DIALING|CONNECTING)'
}
write_state() {
slot=$1
original_names=$2
original_binary=$3
file=$(state_file "$slot")
tmp="${file}.tmp.$$"
{
printf 'original_names=%s\n' "$original_names"
printf 'original_mask=%s\n' "$original_binary"
printf 'captured_at=%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
} > "$tmp" || fail "cannot write $file"
chmod 0600 "$tmp"
mv "$tmp" "$file" || fail "cannot commit $file"
}
capture() {
slot=$1
ensure_state_dir
names=$(read_user_names "$slot")
decimal=$(network_names_to_decimal "$names")
binary=$(decimal_to_binary "$decimal")
file=$(state_file "$slot")
if [ -f "$file" ] && [ "${INTELLIRAT_FORCE_CAPTURE:-0}" != 1 ]; then
fail "state already captured for slot $slot; set INTELLIRAT_FORCE_CAPTURE=1 to replace it"
fi
write_state "$slot" "$names" "$binary"
log_event "capture slot=$slot mask=$binary names=$names"
printf 'captured slot=%s mask=%s names=%s\n' "$slot" "$binary" "$names"
}
get_original_mask() {
file=$(state_file "$1")
[ -f "$file" ] || {
printf '%s' "$HIGH_MASK"
return
}
value=$(grep '^original_mask=' "$file" | cut -d= -f2-)
case "$value" in
''|*[!01]*) fail "invalid saved state for slot $1" ;;
esac
printf '%s' "$value"
}
apply_mask() {
slot=$1
target=$2
reason=$3
current=$(read_user_binary "$slot")
if [ "$current" = "$target" ]; then
log_event "noop slot=$slot state=$reason mask=$target"
printf 'already state=%s slot=%s mask=%s\n' "$reason" "$slot" "$target"
return 0
fi
if call_in_progress; then
fail "telephony call is active; transition refused"
fi
if [ "${INTELLIRAT_DRY_RUN:-0}" = 1 ]; then
printf 'dry-run cmd phone set-allowed-network-types-for-users -s %s %s\n' \
"$slot" "$target"
return 0
fi
result=$(
"$CMD_BIN" phone set-allowed-network-types-for-users -s "$slot" "$target" 2>&1
) || fail "network transition failed: $result"
case "$result" in
*completed*) ;;
*) fail "telephony did not confirm transition: $result" ;;
esac
actual=$(read_user_binary "$slot")
[ "$actual" = "$target" ] || fail "verification mismatch: requested=$target actual=$actual"
log_event "transition slot=$slot previous=$current state=$reason requested=$target actual=$actual"
printf 'transition slot=%s state=%s previous=%s actual=%s\n' \
"$slot" "$reason" "$current" "$actual"
}
status() {
slot=$1
names=$(read_user_names "$slot")
decimal=$(network_names_to_decimal "$names")
current=$(decimal_to_binary "$decimal")
state=UNKNOWN
[ "$current" = "$HIGH_MASK" ] && state=HIGH
[ "$current" = "$LOW_MASK" ] && state=LOW
saved=NO
[ -f "$(state_file "$slot")" ] && saved=YES
printf 'slot=%s state=%s mask=%s names=%s saved_original=%s\n' \
"$slot" "$state" "$current" "$names" "$saved"
}
main() {
dry_run=0
if [ "${1:-}" = '--dry-run' ]; then
dry_run=1
INTELLIRAT_DRY_RUN=1
export INTELLIRAT_DRY_RUN
shift
fi
load_config
ensure_state_dir
command=${1:-help}
shift || true
slot=$(slot_or_default "${1:-}")
case "$command" in
help|-h|--help) usage ;;
status) status "$slot" ;;
capture) capture "$slot" ;;
high) apply_mask "$slot" "$HIGH_MASK" HIGH ;;
low) apply_mask "$slot" "$LOW_MASK" LOW ;;
restore) apply_mask "$slot" "$(get_original_mask "$slot")" RESTORE ;;
*) usage >&2; exit 2 ;;
esac
}
main "$@"