#!/system/bin/sh
# cellguard — CLI driver for the cellguard kernel module.
#
# Invoked by the KernelSU-Next WebUI through ksu.exec("cellguard <cmd>").
# Every subcommand prints a single JSON object on stdout so the WebUI
# can JSON.parse() the result directly.

CONFIG_DIR="/data/adb/cellguard"
CONFIG_FILE="$CONFIG_DIR/config.sh"
DEV=/dev/cellguard

mkdir -p "$CONFIG_DIR"
[ -f "$CONFIG_FILE" ] || cat > "$CONFIG_FILE" << 'EOF'
ENABLED=1
BLOCK_2G=1
BLOCK_3G=1
BLOCK_NULL_CIPHER=1
POLL_MS=1500
CARRIER=none
EOF

. "$CONFIG_FILE"
[ -z "$CARRIER" ] && CARRIER=none

VALID_CARRIERS="none tmobile verizon att uscellular dish cricket metro googlefi generic_us"

# JSON escaping — strings are put through sed to escape \ and "
j_esc() {
  printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' \
                         -e ':a;N;$!ba;s/\n/\\n/g'
}

emit_error() {
  printf '{"ok":false,"error":"%s"}\n' "$(j_esc "$1")"
  exit 1
}

require_dev() {
  [ -c "$DEV" ] || emit_error "kernel module not loaded (/dev/cellguard missing)"
}

save_config() {
  cat > "$CONFIG_FILE" << EOF
ENABLED=$ENABLED
BLOCK_2G=$BLOCK_2G
BLOCK_3G=$BLOCK_3G
BLOCK_NULL_CIPHER=$BLOCK_NULL_CIPHER
POLL_MS=$POLL_MS
CARRIER=$CARRIER
EOF
}

#####################################################################
# status — read /proc/cellguard/status and reformat as JSON
#####################################################################
cmd_status() {
  require_dev
  if [ ! -f /proc/cellguard/status ]; then
    emit_error "/proc/cellguard/status missing"
  fi

  # /proc/cellguard/status is key: value, one per line
  awk -v cfg2g="$BLOCK_2G" -v cfg3g="$BLOCK_3G" \
      -v cfgnull="$BLOCK_NULL_CIPHER" -v cfgpoll="$POLL_MS" '
    BEGIN { FS=":"; printf "{" ; first=1 }
    {
      k=$1; sub(/^ +/,"",k); sub(/ +$/,"",k)
      v=$2; for (i=3;i<=NF;i++) v=v ":" $i
      sub(/^ +/,"",v); sub(/ +$/,"",v)
      gsub(/\\/,"\\\\",v); gsub(/"/,"\\\"",v)
      if (!first) printf ","
      first=0
      # numeric fields
      if (k=="enabled" || k=="rat_code" || k=="cipher_known" || \
          k=="cipher_null" || k=="block_2g" || k=="block_3g" || \
          k=="block_null" || k=="poll_ms" || k=="rsrp_dbm" || \
          k=="tx_pwr_dbm" || \
          k=="downgrades" || k=="relocks" || k=="polls" || \
          k=="bands_locked") {
        # rat field might be mixed — strip trailing junk
        n=v; sub(/[^-0-9].*/,"",n); if (n=="") n=0
        printf "\"%s\":%s", k, n
      } else if (k=="modem" || k=="rat" || k=="operator" || \
                 k=="carrier" || k=="bands_method") {
        printf "\"%s\":\"%s\"", k, v
      } else {
        printf "\"%s\":\"%s\"", k, v
      }
    }
    END { printf ",\"ok\":true}" }
  ' /proc/cellguard/status
  echo
}

#####################################################################
# on / off — flip the guard, persist to config
#####################################################################
cmd_on() {
  require_dev
  echo "on" > "$DEV" 2>/dev/null || emit_error "write /dev/cellguard failed"
  ENABLED=1
  save_config
  printf '{"ok":true,"enabled":1}\n'
}

cmd_off() {
  require_dev
  echo "off" > "$DEV" 2>/dev/null || emit_error "write /dev/cellguard failed"
  ENABLED=0
  save_config
  printf '{"ok":true,"enabled":0}\n'
}

#####################################################################
# apply-config — push saved policy, then on/off accordingly
#   (called by service.sh at boot, and by any config-change flow)
#####################################################################
cmd_apply_config() {
  require_dev
  # Select the carrier profile first so that when we flip the guard on,
  # the kernel's full push_lock_policy() applies WS46 + band-lock in
  # one CFUN cycle. When the guard is off we still record the carrier
  # for next enable.
  if [ -n "$CARRIER" ] && [ "$CARRIER" != "none" ]; then
    echo "carrier $CARRIER" > "$DEV" 2>/dev/null
  fi
  if [ "$ENABLED" = "1" ]; then
    echo "on"  > "$DEV" 2>/dev/null
  else
    echo "off" > "$DEV" 2>/dev/null
  fi
  printf '{"ok":true,"applied":true,"enabled":%s,"carrier":"%s"}\n' \
    "$ENABLED" "$CARRIER"
}

#####################################################################
# relock — force an immediate policy push even if state looks fine
#####################################################################
cmd_relock() {
  require_dev
  echo "relock" > "$DEV" 2>/dev/null || emit_error "relock write failed"
  printf '{"ok":true,"relocked":true}\n'
}

#####################################################################
# scan — trigger AT+COPS=?, parse result, emit JSON list
#
# AT+COPS=? response:
#   +COPS: (stat,"long","short","numeric",AcT),(...),...
# stat: 0=unknown 1=available 2=current 3=forbidden
# AcT : 0/1/3/8 = 2G, 2/4-6 = 3G, 7/9/10 = LTE, 11/13 = NR
#####################################################################
cmd_scan() {
  require_dev
  # Kick the scan and wait. AT+COPS=? can take up to ~180s on a cold
  # SIM; the module runs it synchronously in the ioctl path.
  echo "scan" > "$DEV" 2>/dev/null || emit_error "scan write failed"

  # Poll /proc/cellguard/cells until it has a +COPS: line or we time out
  n=0
  while [ $n -lt 200 ]; do
    if grep -q "+COPS:" /proc/cellguard/cells 2>/dev/null; then
      break
    fi
    sleep 1
    n=$((n + 1))
  done

  raw=$(cat /proc/cellguard/cells 2>/dev/null)
  [ -z "$raw" ] && emit_error "scan produced no output"

  printf '%s' "$raw" | awk '
    BEGIN {
      printf "{\"ok\":true,\"cells\":["
      first=1
    }
    {
      line=$0
      # Work through each (...) tuple
      while (match(line, /\([^()]+\)/)) {
        tup=substr(line, RSTART+1, RLENGTH-2)
        line=substr(line, RSTART+RLENGTH)

        # Split tup on commas, but ignore commas inside quotes
        n=0
        buf=""
        inq=0
        for (i=1;i<=length(tup);i++) {
          c=substr(tup,i,1)
          if (c=="\"") { inq=1-inq; continue }
          if (c=="," && !inq) { n++; f[n]=buf; buf=""; continue }
          buf = buf c
        }
        n++; f[n]=buf

        # A scan tuple has 5 fields: stat,long,short,numeric,act
        # The trailing "supported modes" / "supported formats" tuples
        # have 1-2 numeric fields — skip those.
        if (n < 4) continue
        stat=f[1]; longn=f[2]; shortn=f[3]; num=f[4]; act=f[5]
        if (stat !~ /^[0-9]+$/) continue

        # Map AcT to RAT name and a security-ranking score
        act_n = act + 0
        rat="?"; score=0
        if (act_n==0||act_n==1||act_n==3||act_n==8) { rat="2G";  score=1 }
        else if (act_n==2||act_n==4||act_n==5||act_n==6) { rat="3G"; score=2 }
        else if (act_n==7||act_n==9||act_n==10) { rat="LTE"; score=3 }
        else if (act_n==11||act_n==13) { rat="5G-SA"; score=4 }

        avail="unknown"
        if      (stat=="0") avail="unknown"
        else if (stat=="1") avail="available"
        else if (stat=="2") avail="current"
        else if (stat=="3") avail="forbidden"

        # Escape
        gsub(/\\/,"\\\\",longn);  gsub(/"/,"\\\"",longn)
        gsub(/\\/,"\\\\",shortn); gsub(/"/,"\\\"",shortn)
        gsub(/\\/,"\\\\",num);    gsub(/"/,"\\\"",num)

        if (!first) printf ","
        first=0
        printf "{\"operator\":\"%s\",\"short\":\"%s\",\"mcc_mnc\":\"%s\",\"rat\":\"%s\",\"act\":%d,\"avail\":\"%s\",\"score\":%d}", \
               longn, shortn, num, rat, act_n, avail, score
      }
    }
    END { printf "]}\n" }
  '
}

#####################################################################
# carrier — set the band-lock profile
#   cellguard carrier list
#   cellguard carrier <name>
#####################################################################
cmd_carrier() {
  name="$1"
  if [ -z "$name" ] || [ "$name" = "list" ]; then
    printf '{"ok":true,"current":"%s","carriers":[' "$CARRIER"
    first=1
    for c in $VALID_CARRIERS; do
      if [ "$first" = "1" ]; then first=0; else printf ','; fi
      printf '"%s"' "$c"
    done
    printf ']}\n'
    return
  fi

  ok=0
  for c in $VALID_CARRIERS; do
    [ "$c" = "$name" ] && ok=1
  done
  [ "$ok" = "1" ] || emit_error "unknown carrier: $name"

  require_dev
  echo "carrier $name" > "$DEV" 2>/dev/null || emit_error "write failed"
  CARRIER="$name"
  save_config
  printf '{"ok":true,"carrier":"%s"}\n' "$name"
}

#####################################################################
# config — read/write persisted policy from the WebUI
#
# Usage:
#   cellguard config get
#   cellguard config set block_2g=1 block_3g=0 block_null_cipher=1 poll_ms=2000
#####################################################################
cmd_config() {
  sub="$1"; shift
  case "$sub" in
    get|"")
      printf '{"ok":true,"enabled":%s,"block_2g":%s,"block_3g":%s,"block_null_cipher":%s,"poll_ms":%s,"carrier":"%s"}\n' \
        "$ENABLED" "$BLOCK_2G" "$BLOCK_3G" "$BLOCK_NULL_CIPHER" "$POLL_MS" "$CARRIER"
      ;;
    set)
      while [ -n "$1" ]; do
        k="${1%%=*}"
        v="${1#*=}"
        case "$k" in
          enabled)            ENABLED="$v" ;;
          block_2g)           BLOCK_2G="$v" ;;
          block_3g)           BLOCK_3G="$v" ;;
          block_null_cipher)  BLOCK_NULL_CIPHER="$v" ;;
          poll_ms)            POLL_MS="$v" ;;
          *) emit_error "unknown key: $k" ;;
        esac
        shift
      done
      save_config
      cmd_apply_config
      ;;
    *)
      emit_error "config: unknown subcommand '$sub'"
      ;;
  esac
}

#####################################################################
# dispatch
#####################################################################
case "$1" in
  status)         cmd_status ;;
  on)             cmd_on ;;
  off)            cmd_off ;;
  relock)         cmd_relock ;;
  scan)           cmd_scan ;;
  carrier)        shift; cmd_carrier "$@" ;;
  apply-config)   cmd_apply_config ;;
  config)         shift; cmd_config "$@" ;;
  *)
    cat << USAGE
cellguard — driver for /dev/cellguard

  cellguard status               show current RAT, operator, cipher, counters
  cellguard on                   enable the guard (force LTE/NR, re-lock on drop)
  cellguard off                  disable and release the modem
  cellguard relock               force one relock pass now
  cellguard scan                 full AT+COPS=? scan, JSON list of visible networks
  cellguard config get|set       read or update persisted policy

All subcommands emit a single-line JSON object on stdout.
USAGE
    ;;
esac
