mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-27 06:52:51 +08:00
1045 lines
34 KiB
Bash
Executable File
1045 lines
34 KiB
Bash
Executable File
#!/bin/sh
|
|
# shellcheck shell=sh
|
|
|
|
ss_case_refreshd_wait() (
|
|
set -eu
|
|
TMP="$(ss_spec_tmpdir)"
|
|
WAIT_PID=''
|
|
trap 'if [ -n "$WAIT_PID" ]; then kill "$WAIT_PID" 2>/dev/null || true; wait "$WAIT_PID" 2>/dev/null || true; fi; rm -rf "$TMP"' EXIT HUP INT TERM
|
|
REFRESHD="$SS_SPEC_ROOT/files/usr/libexec/safeshield-refreshd"
|
|
HARNESS="$TMP/refreshd-wait.sh"
|
|
mkdir -p "$TMP/bin"
|
|
awk '
|
|
/^ss_should_terminate=0$/ { print }
|
|
/^ss_sleep_pid=/ { print }
|
|
/^handle_term\(\) \{$/, /^}$/ { print }
|
|
/^sleep_seconds\(\) \{$/, /^}$/ { print }
|
|
' "$REFRESHD" >"$HARNESS"
|
|
# shellcheck disable=SC1090
|
|
. "$HARNESS"
|
|
REAL_SLEEP="$(command -v sleep)"
|
|
SLEEP_CALLS="$TMP/sleep.calls"
|
|
SLEEP_PID_FILE="$TMP/sleep.pid"
|
|
cat >"$TMP/bin/sleep" <<'EOF_SLEEP'
|
|
#!/bin/sh
|
|
printf '%s\n' "$*" >>"$SLEEP_CALLS"
|
|
printf '%s\n' "$$" >"$SLEEP_PID_FILE"
|
|
exec "$REAL_SLEEP" "$@"
|
|
EOF_SLEEP
|
|
chmod 755 "$TMP/bin/sleep"
|
|
export REAL_SLEEP SLEEP_CALLS SLEEP_PID_FILE
|
|
(
|
|
trap 'handle_term' TERM INT
|
|
PATH="$TMP/bin:$PATH"
|
|
export PATH
|
|
sleep_seconds 30
|
|
) &
|
|
WAIT_PID=$!
|
|
tries=0
|
|
while [ ! -s "$SLEEP_PID_FILE" ]; do
|
|
tries=$((tries + 1))
|
|
[ "$tries" -lt 100 ] || return 1
|
|
sleep 0.02
|
|
done
|
|
SLEEP_PID="$(cat "$SLEEP_PID_FILE")"
|
|
kill -TERM "$WAIT_PID"
|
|
tries=0
|
|
while kill -0 "$WAIT_PID" 2>/dev/null; do
|
|
tries=$((tries + 1))
|
|
[ "$tries" -lt 100 ] || return 1
|
|
sleep 0.02
|
|
done
|
|
wait "$WAIT_PID" 2>/dev/null || true
|
|
WAIT_PID=''
|
|
ss_spec_assert_eq "$(cat "$SLEEP_CALLS")" '30'
|
|
tries=0
|
|
while kill -0 "$SLEEP_PID" 2>/dev/null; do
|
|
tries=$((tries + 1))
|
|
[ "$tries" -lt 100 ] || return 1
|
|
sleep 0.02
|
|
done
|
|
)
|
|
|
|
ss_case_statistics_collector() (
|
|
set -eu
|
|
TMP="$(ss_spec_tmpdir)"
|
|
STATSD_PID=''
|
|
trap 'if [ -n "$STATSD_PID" ]; then kill "$STATSD_PID" 2>/dev/null || true; wait "$STATSD_PID" 2>/dev/null || true; fi; rm -rf "$TMP"' EXIT HUP INT TERM
|
|
mkdir -p "$TMP/bin" "$TMP/statistics"
|
|
cat >"$TMP/functions.sh" <<'EOF_FUNCTIONS'
|
|
# test stub
|
|
EOF_FUNCTIONS
|
|
cat >"$TMP/core.sh" <<EOF_CORE
|
|
ss_enabled=1
|
|
ss_statistics_enabled=1
|
|
ss_statistics_snapshot_interval_s=60
|
|
ss_statistics_retention_hours=168
|
|
SS_STATISTICS_DIR='$TMP/statistics'
|
|
SS_STATISTICS_STATE_FILE='$TMP/statistics/state.tsv'
|
|
SS_STATISTICS_JSON_FILE='$TMP/statistics/statistics.json'
|
|
SS_STATISTICS_UPLOAD_JSON_FILE='$TMP/statistics/upload.json'
|
|
SS_IDENTITY_PROFILE='gl_mt300n_v2'
|
|
. '$SS_SPEC_ROOT/files/usr/lib/safeshield/statistics.sh'
|
|
ss_load_config() { return 0; }
|
|
ss_dnsmasq_safeshield_block_supported() { return 0; }
|
|
command_exists() { command -v "\$1" >/dev/null 2>&1; }
|
|
log_info() { :; }
|
|
log_warn() { :; }
|
|
log_error() { printf '%s\n' "\$*" >&2; }
|
|
EOF_CORE
|
|
cat >"$TMP/bin/stats-poll" <<'EOF_POLL'
|
|
#!/bin/sh
|
|
printf '%s\n' '1' >>"$POLL_COUNT_FILE"
|
|
printf '%s\n' 'snapshot runtime-instance udp 128 1 0 0 10 2'
|
|
printf '%s\n' 'client 192.168.1.2 10 2'
|
|
printf '%s\n' 'commit'
|
|
EOF_POLL
|
|
cat >"$TMP/bin/awk" <<'EOF_AWK'
|
|
#!/bin/sh
|
|
printf '%s\n' "$$" >"$AWK_PID_FILE"
|
|
printf '%s\n' "$@" >"$AWK_ARGS_FILE"
|
|
while IFS= read -r line; do
|
|
printf '%s\n' "$line" >>"$AWK_INPUT_FILE"
|
|
done
|
|
EOF_AWK
|
|
cat >"$TMP/bin/ip" <<'EOF_IP'
|
|
#!/bin/sh
|
|
exit 0
|
|
EOF_IP
|
|
chmod 755 "$TMP/bin/stats-poll" "$TMP/bin/awk" "$TMP/bin/ip"
|
|
POLL_COUNT_FILE="$TMP/poll-count"
|
|
AWK_PID_FILE="$TMP/awk.pid"
|
|
AWK_ARGS_FILE="$TMP/awk.args"
|
|
AWK_INPUT_FILE="$TMP/awk.input"
|
|
export POLL_COUNT_FILE AWK_PID_FILE AWK_ARGS_FILE AWK_INPUT_FILE
|
|
PATH="$TMP/bin:$PATH" \
|
|
SS_STATSD_FUNCTIONS_LIB="$TMP/functions.sh" \
|
|
SS_STATSD_CORE_LIB="$TMP/core.sh" \
|
|
SS_STATSD_AWK_DIR="$SS_SPEC_ROOT/files/usr/lib/safeshield/statistics" \
|
|
SS_STATSD_POLL_COMMAND="$TMP/bin/stats-poll" \
|
|
SS_STATSD_GENERATION_ID='runtime-generation' \
|
|
SS_STATSD_PERSISTENT_STATE_FILE="$TMP/flash/statistics-state.tsv" \
|
|
SS_STATSD_PERSISTENT_JOURNAL_FILE="$TMP/flash/statistics-journal.tsv" \
|
|
sh "$SS_SPEC_ROOT/files/usr/libexec/safeshield-statsd" &
|
|
STATSD_PID=$!
|
|
tries=0
|
|
while [ ! -s "$AWK_PID_FILE" ] || ! grep -Fx 'commit' "$AWK_INPUT_FILE" >/dev/null 2>&1; do
|
|
tries=$((tries + 1))
|
|
[ "$tries" -lt 100 ] || return 1
|
|
sleep 0.02
|
|
done
|
|
AWK_PID="$(cat "$AWK_PID_FILE")"
|
|
ss_spec_assert_file_line "$AWK_ARGS_FILE" 'snapshot_interval=300'
|
|
ss_spec_assert_file_line "$AWK_ARGS_FILE" 'identity_cache_ttl=60'
|
|
ss_spec_assert_file_line "$AWK_ARGS_FILE" 'generation_seed=runtime-generation'
|
|
ss_spec_assert_file_line "$AWK_ARGS_FILE" 'force_rebaseline=0'
|
|
ss_spec_assert_file_line "$AWK_ARGS_FILE" "rebaseline_file=$TMP/statistics/rebaseline"
|
|
ss_spec_assert_file_line "$AWK_ARGS_FILE" 'ipv6_neigh_command=ip -6 neigh show 2>/dev/null'
|
|
ss_spec_assert_file_line "$AWK_ARGS_FILE" "$SS_SPEC_ROOT/files/usr/lib/safeshield/statistics/20-identity.awk"
|
|
ss_spec_assert_file_line "$AWK_ARGS_FILE" "$SS_SPEC_ROOT/files/usr/lib/safeshield/statistics/90-main.awk"
|
|
ss_spec_assert_file_line "$AWK_ARGS_FILE" 'persistent_state_file='
|
|
ss_spec_assert_file_line "$AWK_ARGS_FILE" 'persistent_journal_file='
|
|
ss_spec_assert_file_line "$AWK_ARGS_FILE" "upload_json_file=$TMP/statistics/upload.json"
|
|
ss_spec_assert_file_line "$AWK_ARGS_FILE" 'upload_window_hours=2'
|
|
ss_spec_assert_file_line "$AWK_INPUT_FILE" 'snapshot runtime-instance udp 128 1 0 0 10 2'
|
|
ss_spec_assert_file_line "$AWK_INPUT_FILE" 'client 192.168.1.2 10 2'
|
|
ss_spec_assert_eq "$(cat "$POLL_COUNT_FILE")" '1'
|
|
[ ! -e "$TMP/flash" ]
|
|
kill -TERM "$STATSD_PID"
|
|
wait "$STATSD_PID" 2>/dev/null || true
|
|
STATSD_PID=''
|
|
sleep 0.05
|
|
! kill -0 "$AWK_PID" 2>/dev/null
|
|
[ ! -e "$TMP/statistics/collector.lock" ]
|
|
! find "$TMP/statistics" -maxdepth 1 -name 'events.*' | grep . >/dev/null
|
|
)
|
|
|
|
ss_case_statistics_reconcile() (
|
|
set -eu
|
|
TMP="$(ss_spec_tmpdir)"
|
|
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
|
SS_DNSMASQ_DIR="$TMP/dnsmasq.d"
|
|
SS_STATISTICS_DIR="$TMP/statistics"
|
|
SS_STATISTICS_DNSMASQ_CONF="$SS_DNSMASQ_DIR/safeshield.statistics.conf"
|
|
SS_STATISTICS_POLL_COMMAND="$TMP/stats-poll"
|
|
SS_STATISTICS_REBASELINE_FILE="$SS_STATISTICS_DIR/rebaseline"
|
|
PKG_NAME='safeshield'
|
|
ss_enabled=1
|
|
ss_statistics_enabled=0
|
|
export SS_DNSMASQ_DIR SS_STATISTICS_DIR SS_STATISTICS_DNSMASQ_CONF SS_STATISTICS_POLL_COMMAND SS_STATISTICS_REBASELINE_FILE PKG_NAME
|
|
export ss_enabled ss_statistics_enabled
|
|
cat >"$SS_STATISTICS_POLL_COMMAND" <<'EOF_POLL'
|
|
#!/bin/sh
|
|
exit 0
|
|
EOF_POLL
|
|
chmod 755 "$SS_STATISTICS_POLL_COMMAND"
|
|
# shellcheck disable=SC1091
|
|
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/statistics.sh"
|
|
CALLS="$TMP/calls"
|
|
record_call() { printf '%s\n' "$*" >>"$CALLS"; }
|
|
log_error() { :; }
|
|
log_warn() { :; }
|
|
ss_status_set() { :; }
|
|
ss_status_add_warning() { :; }
|
|
DNSMASQ_PATCHED=1
|
|
ss_dnsmasq_safeshield_block_supported() { [ "$DNSMASQ_PATCHED" = '1' ]; }
|
|
uci() {
|
|
record_call "uci $*"
|
|
return 0
|
|
}
|
|
ss_require_supported_dnsmasq() {
|
|
record_call require_supported_dnsmasq
|
|
return 0
|
|
}
|
|
dnsmasq_restart() {
|
|
record_call dnsmasq_restart
|
|
return 0
|
|
}
|
|
procd_kill() {
|
|
record_call "procd_kill $*"
|
|
return 0
|
|
}
|
|
procd_open_service() { record_call "procd_open_service $*"; }
|
|
procd_open_instance() { record_call "procd_open_instance $*"; }
|
|
procd_set_param() { record_call "procd_set_param $*"; }
|
|
procd_close_instance() { record_call procd_close_instance; }
|
|
procd_close_service() { record_call "procd_close_service $*"; }
|
|
mkdir -p "$SS_DNSMASQ_DIR"
|
|
cat >"$SS_STATISTICS_DNSMASQ_CONF" <<'CONFIG'
|
|
# Legacy SafeShield statistics logging
|
|
log-queries=extra
|
|
log-async=25
|
|
CONFIG
|
|
: >"$CALLS"
|
|
ss_statistics_reconcile_runtime
|
|
[ ! -e "$SS_STATISTICS_DNSMASQ_CONF" ]
|
|
[ -f "$SS_STATISTICS_REBASELINE_FILE" ]
|
|
ss_spec_assert_file_line "$CALLS" 'procd_kill safeshield statistics'
|
|
ss_spec_assert_file_line "$CALLS" 'procd_kill safeshield statistics-upload'
|
|
ss_spec_assert_file_line "$CALLS" 'dnsmasq_restart'
|
|
! grep -Fx 'procd_kill safeshield' "$CALLS" >/dev/null
|
|
|
|
ss_statistics_enabled=1
|
|
: >"$CALLS"
|
|
ss_statistics_reconcile_runtime
|
|
[ ! -e "$SS_STATISTICS_DNSMASQ_CONF" ]
|
|
ss_spec_assert_file_line "$CALLS" 'require_supported_dnsmasq'
|
|
ss_spec_assert_file_line "$CALLS" 'procd_open_service safeshield'
|
|
ss_spec_assert_file_line "$CALLS" 'procd_open_instance statistics'
|
|
ss_spec_assert_file_line "$CALLS" 'procd_open_instance statistics-upload'
|
|
ss_spec_assert_file_line "$CALLS" 'procd_set_param command /usr/libexec/safeshield-statistics-uploader'
|
|
ss_spec_assert_file_line "$CALLS" 'procd_close_service add'
|
|
! grep -Fx 'dnsmasq_restart' "$CALLS" >/dev/null
|
|
! grep -F 'procd_kill safeshield' "$CALLS" >/dev/null
|
|
|
|
: >"$CALLS"
|
|
ss_statistics_reconcile_runtime
|
|
! grep -Fx 'dnsmasq_restart' "$CALLS" >/dev/null
|
|
ss_spec_assert_file_line "$CALLS" 'procd_open_instance statistics'
|
|
ss_spec_assert_file_line "$CALLS" 'procd_open_instance statistics-upload'
|
|
|
|
DNSMASQ_PATCHED=0
|
|
ss_statistics_enabled=1
|
|
: >"$CALLS"
|
|
ss_statistics_reconcile_runtime
|
|
ss_spec_assert_eq "$ss_statistics_enabled" '0'
|
|
[ -f "$SS_STATISTICS_REBASELINE_FILE" ]
|
|
! grep -F 'uci ' "$CALLS" >/dev/null
|
|
ss_spec_assert_file_line "$CALLS" 'procd_kill safeshield statistics'
|
|
ss_spec_assert_file_line "$CALLS" 'procd_kill safeshield statistics-upload'
|
|
! grep -F 'procd_open_instance statistics' "$CALLS" >/dev/null
|
|
|
|
ss_spec_assert_file_contains "$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield/config.uc" "changed_names[0] == 'statistics_enabled'"
|
|
ss_spec_assert_file_contains "$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield/config.uc" "run_service_action('reconcile_statistics', 60000)"
|
|
)
|
|
|
|
ss_case_statistics_uploader() (
|
|
set -eu
|
|
UPLOADER="$SS_SPEC_ROOT/files/usr/libexec/safeshield-statistics-uploader"
|
|
ss_spec_assert_file_contains "$UPLOADER" 'SS_STATS_UPLOAD_INTERVAL_S="${SS_STATS_UPLOAD_INTERVAL_S:-1800}"'
|
|
ss_spec_assert_file_contains "$UPLOADER" 'SS_STATS_UPLOAD_FULL_EVERY="${SS_STATS_UPLOAD_FULL_EVERY:-23}"'
|
|
TMP="$(ss_spec_tmpdir)"
|
|
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
|
mkdir -p "$TMP/statistics"
|
|
cat >"$TMP/functions.sh" <<'EOF_FUNCTIONS'
|
|
# test stub
|
|
EOF_FUNCTIONS
|
|
cat >"$TMP/core.sh" <<'EOF_CORE'
|
|
SS_STATISTICS_DIR="$TMP/statistics"
|
|
SS_STATISTICS_JSON_FILE="$SS_STATISTICS_DIR/statistics.json"
|
|
SS_STATISTICS_UPLOAD_JSON_FILE="$SS_STATISTICS_DIR/upload.json"
|
|
SS_STATISTICS_UPLOAD_CREDENTIALS_FILE="$SS_STATISTICS_DIR/upload.credentials"
|
|
SS_STATISTICS_UPLOAD_ENTITLEMENT_FILE="$SS_STATISTICS_DIR/upload.entitlement"
|
|
SS_STATISTICS_UPLOAD_PENDING_FILE="$SS_STATISTICS_DIR/upload.pending.json"
|
|
SS_STATISTICS_UPLOAD_PENDING_META_FILE="$SS_STATISTICS_DIR/upload.pending.meta"
|
|
SS_STATISTICS_UPLOAD_STATE_FILE="$SS_STATISTICS_DIR/upload.state"
|
|
ss_enabled=1
|
|
ss_statistics_enabled=1
|
|
ss_download_retry=3
|
|
ss_download_timeout=10
|
|
SS_HTTP_STATUS=''
|
|
SS_STATISTICS_UPLOAD_URL='https://www.smartsafehub.com/api/v1/statistics'
|
|
SS_STATISTICS_UPLOAD_TOKEN='token-old'
|
|
SS_STATISTICS_UPLOAD_TOKEN_EXPIRES_IN_S=172800
|
|
MOCK_UPLOAD_ENTITLEMENT='allowed'
|
|
MOCK_REFRESH_RESULT='allowed'
|
|
MOCK_DENIED_RECHECK_DUE='0'
|
|
|
|
is_valid_integer() {
|
|
case "$1" in
|
|
'' | *[!0-9]*) return 1 ;;
|
|
esac
|
|
[ "$1" -ge 0 ] 2>/dev/null
|
|
}
|
|
ss_load_config() { return 0; }
|
|
ss_status_set() { :; }
|
|
ss_status_set_now() { :; }
|
|
log_info() { :; }
|
|
log_warn() { :; }
|
|
log_error() { :; }
|
|
ss_statistics_upload_denied() {
|
|
[ "$MOCK_UPLOAD_ENTITLEMENT" = 'denied' ]
|
|
}
|
|
ss_statistics_upload_denied_recheck_due() {
|
|
[ "$MOCK_DENIED_RECHECK_DUE" = '1' ]
|
|
}
|
|
ss_statistics_set_upload_entitlement() {
|
|
MOCK_UPLOAD_ENTITLEMENT="$1"
|
|
}
|
|
ss_statistics_load_upload_credentials() {
|
|
ss_statistics_upload_denied && return 1
|
|
[ -n "$SS_STATISTICS_UPLOAD_TOKEN" ]
|
|
}
|
|
ss_statistics_clear_upload_credentials() {
|
|
SS_STATISTICS_UPLOAD_TOKEN=''
|
|
}
|
|
ss_statistics_disable_cloud_upload() {
|
|
MOCK_UPLOAD_ENTITLEMENT='denied'
|
|
SS_STATISTICS_UPLOAD_TOKEN=''
|
|
rm -f "$SS_STATISTICS_UPLOAD_PENDING_FILE" "$SS_STATISTICS_UPLOAD_PENDING_META_FILE" "$SS_STATISTICS_UPLOAD_STATE_FILE"
|
|
}
|
|
ss_statistics_refresh_upload_credentials() {
|
|
printf '%s\n' "$MOCK_REFRESH_RESULT" >>"$REFRESH_CALLS"
|
|
if [ "$MOCK_REFRESH_RESULT" = 'denied' ]; then
|
|
MOCK_UPLOAD_ENTITLEMENT='denied'
|
|
SS_STATISTICS_UPLOAD_TOKEN=''
|
|
rm -f "$SS_STATISTICS_UPLOAD_PENDING_FILE" "$SS_STATISTICS_UPLOAD_PENDING_META_FILE" "$SS_STATISTICS_UPLOAD_STATE_FILE"
|
|
return 2
|
|
fi
|
|
MOCK_UPLOAD_ENTITLEMENT='allowed'
|
|
SS_STATISTICS_UPLOAD_URL='https://www.smartsafehub.com/api/v1/statistics'
|
|
SS_STATISTICS_UPLOAD_TOKEN='token-new'
|
|
return 0
|
|
}
|
|
ss_json_get_file() {
|
|
local file="$1"
|
|
local expr="$2"
|
|
case "$expr" in
|
|
'@.schema.name') sed -n 's/.*"schema":{"name":"\([^"]*\)".*/\1/p' "$file" ;;
|
|
'@.schema.version') sed -n 's/.*"schema":{"name":"[^"]*","version":\([0-9][0-9]*\)}.*/\1/p' "$file" ;;
|
|
'@.generation_id') sed -n 's/.*"generation_id":"\([^"]*\)".*/\1/p' "$file" ;;
|
|
'@.snapshot_seq') sed -n 's/.*"snapshot_seq":\([0-9][0-9]*\).*/\1/p' "$file" ;;
|
|
'@.status') sed -n 's/.*"status":"\([^"]*\)".*/\1/p' "$file" ;;
|
|
'@.last_snapshot_seq') sed -n 's/.*"last_snapshot_seq":\([0-9][0-9]*\).*/\1/p' "$file" ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
ss_http_post_json() {
|
|
local url="$1"
|
|
local payload="$2"
|
|
local out="$3"
|
|
local authorization="${4:-}"
|
|
local call generation seq response_status
|
|
call=0
|
|
[ -s "$HTTP_CALLS" ] && call="$(wc -l <"$HTTP_CALLS" | tr -d ' ')"
|
|
call=$((call + 1))
|
|
printf '%s\n' "$call" >>"$HTTP_CALLS"
|
|
cksum "$payload" | awk '{print $1 ":" $2}' >>"$HTTP_CHECKSUMS"
|
|
printf '%s\n' "$authorization" >>"$HTTP_AUTH"
|
|
|
|
case "$MOCK_HTTP_MODE" in
|
|
fail-once)
|
|
if [ "$call" -eq 1 ]; then
|
|
SS_HTTP_STATUS='503'
|
|
return 1
|
|
fi
|
|
;;
|
|
always-fail)
|
|
SS_HTTP_STATUS='503'
|
|
return 1
|
|
;;
|
|
unauthorized-once)
|
|
if [ "$call" -eq 1 ]; then
|
|
SS_HTTP_STATUS='401'
|
|
return 1
|
|
fi
|
|
;;
|
|
bad-request)
|
|
SS_HTTP_STATUS='400'
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
generation="$(ss_json_get_file "$payload" '@.generation_id')"
|
|
seq="$(ss_json_get_file "$payload" '@.snapshot_seq')"
|
|
case "$MOCK_HTTP_MODE" in
|
|
stale) response_status='stale' ;;
|
|
duplicate) response_status='duplicate' ;;
|
|
*) response_status='applied' ;;
|
|
esac
|
|
printf '{"status":"%s","generation_id":"%s","last_snapshot_seq":%s}\n' "$response_status" "$generation" "$seq" >"$out"
|
|
SS_HTTP_STATUS='200'
|
|
return 0
|
|
}
|
|
EOF_CORE
|
|
|
|
# Expand only the test-root path while preserving shell variables for runtime.
|
|
sed -i.bak "s|\$TMP|$TMP|g" "$TMP/core.sh" 2>/dev/null || {
|
|
sed "s|\$TMP|$TMP|g" "$TMP/core.sh" >"$TMP/core.expanded"
|
|
mv "$TMP/core.expanded" "$TMP/core.sh"
|
|
}
|
|
rm -f "$TMP/core.sh.bak"
|
|
|
|
HTTP_CALLS="$TMP/http.calls"
|
|
HTTP_CHECKSUMS="$TMP/http.checksums"
|
|
HTTP_AUTH="$TMP/http.auth"
|
|
REFRESH_CALLS="$TMP/refresh.calls"
|
|
MOCK_HTTP_MODE='success'
|
|
export HTTP_CALLS HTTP_CHECKSUMS HTTP_AUTH REFRESH_CALLS MOCK_HTTP_MODE MOCK_DENIED_RECHECK_DUE
|
|
: >"$HTTP_CALLS"
|
|
: >"$HTTP_CHECKSUMS"
|
|
: >"$HTTP_AUTH"
|
|
: >"$REFRESH_CALLS"
|
|
|
|
SS_STATS_UPLOAD_FUNCTIONS_LIB="$TMP/functions.sh"
|
|
SS_STATS_UPLOAD_CORE_LIB="$TMP/core.sh"
|
|
SS_STATS_UPLOAD_LIBRARY_ONLY=1
|
|
SS_STATS_UPLOAD_RETRY_DELAY_S=0
|
|
SS_STATS_UPLOAD_FULL_EVERY=23
|
|
SS_STATS_UPLOAD_RESPONSE_FILE="$TMP/statistics/upload.response.json"
|
|
export SS_STATS_UPLOAD_FUNCTIONS_LIB SS_STATS_UPLOAD_CORE_LIB SS_STATS_UPLOAD_LIBRARY_ONLY
|
|
export SS_STATS_UPLOAD_RETRY_DELAY_S SS_STATS_UPLOAD_FULL_EVERY SS_STATS_UPLOAD_RESPONSE_FILE
|
|
# shellcheck disable=SC1090
|
|
. "$SS_SPEC_ROOT/files/usr/libexec/safeshield-statistics-uploader"
|
|
|
|
write_payload() {
|
|
path="$1"
|
|
seq="$2"
|
|
printf '{"schema":{"name":"safeshield.statistics","version":3},"generation_id":"generation-upload","snapshot_seq":%s}\n' "$seq" >"$path"
|
|
}
|
|
|
|
write_payload "$SS_STATISTICS_JSON_FILE" 1
|
|
write_payload "$SS_STATISTICS_UPLOAD_JSON_FILE" 1
|
|
upload_pending_create
|
|
ss_spec_assert_file_contains "$SS_STATISTICS_UPLOAD_PENDING_META_FILE" "$(printf 'full\tgeneration-upload\t1')"
|
|
|
|
MOCK_HTTP_MODE='fail-once'
|
|
export MOCK_HTTP_MODE
|
|
: >"$HTTP_CALLS"
|
|
: >"$HTTP_CHECKSUMS"
|
|
upload_send_pending
|
|
ss_spec_assert_eq "$(wc -l <"$HTTP_CALLS" | tr -d ' ')" '2'
|
|
ss_spec_assert_eq "$(sort -u "$HTTP_CHECKSUMS" | wc -l | tr -d ' ')" '1'
|
|
[ ! -e "$SS_STATISTICS_UPLOAD_PENDING_FILE" ]
|
|
upload_state_load
|
|
ss_spec_assert_eq "$upload_state_last_full_seq" '1'
|
|
ss_spec_assert_eq "$upload_state_needs_full" '0'
|
|
|
|
write_payload "$SS_STATISTICS_JSON_FILE" 2
|
|
write_payload "$SS_STATISTICS_UPLOAD_JSON_FILE" 1
|
|
if upload_pending_create; then
|
|
return 1
|
|
fi
|
|
[ ! -e "$SS_STATISTICS_UPLOAD_PENDING_FILE" ]
|
|
write_payload "$SS_STATISTICS_UPLOAD_JSON_FILE" 2
|
|
upload_pending_create
|
|
ss_spec_assert_file_contains "$SS_STATISTICS_UPLOAD_PENDING_META_FILE" "$(printf 'recent\tgeneration-upload\t2')"
|
|
MOCK_HTTP_MODE='always-fail'
|
|
export MOCK_HTTP_MODE
|
|
ss_download_retry=2
|
|
: >"$HTTP_CALLS"
|
|
if upload_send_pending; then
|
|
return 1
|
|
fi
|
|
[ -s "$SS_STATISTICS_UPLOAD_PENDING_FILE" ]
|
|
upload_state_load
|
|
ss_spec_assert_eq "$upload_state_needs_full" '1'
|
|
|
|
MOCK_HTTP_MODE='success'
|
|
export MOCK_HTTP_MODE
|
|
: >"$HTTP_CALLS"
|
|
upload_send_pending
|
|
[ ! -e "$SS_STATISTICS_UPLOAD_PENDING_FILE" ]
|
|
upload_state_load
|
|
ss_spec_assert_eq "$upload_state_needs_full" '1'
|
|
|
|
write_payload "$SS_STATISTICS_JSON_FILE" 3
|
|
write_payload "$SS_STATISTICS_UPLOAD_JSON_FILE" 3
|
|
upload_pending_create
|
|
ss_spec_assert_file_contains "$SS_STATISTICS_UPLOAD_PENDING_META_FILE" "$(printf 'full\tgeneration-upload\t3')"
|
|
upload_pending_clear
|
|
|
|
# A 401 refreshes credentials once and retries the exact pending snapshot.
|
|
upload_state_needs_full=0
|
|
upload_state_last_full_seq=1
|
|
upload_state_last_success_seq=2
|
|
upload_state_save
|
|
write_payload "$SS_STATISTICS_JSON_FILE" 4
|
|
write_payload "$SS_STATISTICS_UPLOAD_JSON_FILE" 4
|
|
upload_pending_create
|
|
MOCK_HTTP_MODE='unauthorized-once'
|
|
export MOCK_HTTP_MODE
|
|
SS_STATISTICS_UPLOAD_TOKEN='token-old'
|
|
ss_download_retry=3
|
|
: >"$HTTP_CALLS"
|
|
: >"$HTTP_AUTH"
|
|
upload_send_pending
|
|
ss_spec_assert_eq "$(sed -n '1p' "$HTTP_AUTH")" 'Bearer token-old'
|
|
ss_spec_assert_eq "$(sed -n '2p' "$HTTP_AUTH")" 'Bearer token-new'
|
|
[ ! -e "$SS_STATISTICS_UPLOAD_PENDING_FILE" ]
|
|
|
|
# A revoked/expired entitlement stops the 401 re-authentication loop and
|
|
# discards only the pending cloud payload; local statistics remain untouched.
|
|
write_payload "$SS_STATISTICS_JSON_FILE" 5
|
|
write_payload "$SS_STATISTICS_UPLOAD_JSON_FILE" 5
|
|
upload_pending_create
|
|
MOCK_HTTP_MODE='unauthorized-once'
|
|
MOCK_REFRESH_RESULT='denied'
|
|
MOCK_UPLOAD_ENTITLEMENT='allowed'
|
|
SS_STATISTICS_UPLOAD_TOKEN='token-old'
|
|
export MOCK_HTTP_MODE MOCK_REFRESH_RESULT MOCK_UPLOAD_ENTITLEMENT
|
|
: >"$HTTP_CALLS"
|
|
: >"$REFRESH_CALLS"
|
|
DENIED_RC=0
|
|
upload_send_pending || DENIED_RC=$?
|
|
ss_spec_assert_eq "$DENIED_RC" '2'
|
|
ss_spec_assert_eq "$(wc -l <"$HTTP_CALLS" | tr -d ' ')" '1'
|
|
ss_spec_assert_eq "$(wc -l <"$REFRESH_CALLS" | tr -d ' ')" '1'
|
|
ss_spec_assert_eq "$MOCK_UPLOAD_ENTITLEMENT" 'denied'
|
|
[ ! -e "$SS_STATISTICS_UPLOAD_PENDING_FILE" ]
|
|
|
|
# Explicit denial prevents repeated credential resolves from the uploader.
|
|
ENSURE_RC=0
|
|
upload_ensure_credentials || ENSURE_RC=$?
|
|
ss_spec_assert_eq "$ENSURE_RC" '2'
|
|
ss_spec_assert_eq "$(wc -l <"$REFRESH_CALLS" | tr -d ' ')" '1'
|
|
|
|
# Denied entitlement stays quiet until the 12-hour recheck becomes due.
|
|
MOCK_UPLOAD_ENTITLEMENT='denied'
|
|
MOCK_REFRESH_RESULT='allowed'
|
|
MOCK_DENIED_RECHECK_DUE='0'
|
|
SS_STATISTICS_UPLOAD_TOKEN=''
|
|
ss_license_key='paid-license-key'
|
|
ss_should_terminate=0
|
|
: >"$REFRESH_CALLS"
|
|
upload_sleep() {
|
|
ss_should_terminate=1
|
|
return 1
|
|
}
|
|
main
|
|
ss_spec_assert_eq "$(wc -l <"$REFRESH_CALLS" | tr -d ' ')" '0'
|
|
|
|
# Once the 12-hour check is due, re-resolve entitlement immediately. A newly
|
|
# entitled device resumes with a full reconciliation because denial cleared
|
|
# the previous upload state.
|
|
MOCK_UPLOAD_ENTITLEMENT='denied'
|
|
MOCK_DENIED_RECHECK_DUE='1'
|
|
MOCK_HTTP_MODE='success'
|
|
SS_STATISTICS_UPLOAD_TOKEN=''
|
|
ss_should_terminate=0
|
|
write_payload "$SS_STATISTICS_JSON_FILE" 6
|
|
write_payload "$SS_STATISTICS_UPLOAD_JSON_FILE" 6
|
|
: >"$REFRESH_CALLS"
|
|
: >"$HTTP_CALLS"
|
|
main
|
|
ss_spec_assert_eq "$(wc -l <"$REFRESH_CALLS" | tr -d ' ')" '1'
|
|
ss_spec_assert_eq "$(wc -l <"$HTTP_CALLS" | tr -d ' ')" '1'
|
|
ss_spec_assert_eq "$MOCK_UPLOAD_ENTITLEMENT" 'allowed'
|
|
|
|
# Corrupted pending metadata must never be uploaded. It is discarded so the
|
|
# next loop can rebuild a payload from an atomic collector snapshot.
|
|
printf '%s\n' '{"schema":{"name":"safeshield.statistics","version":3},"generation_id":"generation-upload","snapshot_seq":7}' >"$SS_STATISTICS_UPLOAD_PENDING_FILE"
|
|
printf '%s\n' 'invalid-mode\tgeneration-upload\t7' >"$SS_STATISTICS_UPLOAD_PENDING_META_FILE"
|
|
if upload_pending_load; then
|
|
return 1
|
|
fi
|
|
[ ! -e "$SS_STATISTICS_UPLOAD_PENDING_FILE" ]
|
|
[ ! -e "$SS_STATISTICS_UPLOAD_PENDING_META_FILE" ]
|
|
|
|
# A stale ACK is successful transport-wise but forces the next upload to be a
|
|
# full reconciliation. A duplicate full ACK can then clear that requirement.
|
|
MOCK_UPLOAD_ENTITLEMENT='allowed'
|
|
MOCK_REFRESH_RESULT='allowed'
|
|
SS_STATISTICS_UPLOAD_TOKEN='token-current'
|
|
upload_state_generation='generation-upload'
|
|
upload_state_last_success_seq=6
|
|
upload_state_last_full_seq=6
|
|
upload_state_recent_successes=0
|
|
upload_state_needs_full=0
|
|
upload_state_save
|
|
write_payload "$SS_STATISTICS_JSON_FILE" 7
|
|
write_payload "$SS_STATISTICS_UPLOAD_JSON_FILE" 7
|
|
upload_pending_create
|
|
ss_spec_assert_file_contains "$SS_STATISTICS_UPLOAD_PENDING_META_FILE" "$(printf 'recent\tgeneration-upload\t7')"
|
|
MOCK_HTTP_MODE='stale'
|
|
export MOCK_HTTP_MODE
|
|
upload_send_pending
|
|
upload_state_load
|
|
ss_spec_assert_eq "$upload_state_needs_full" '1'
|
|
ss_spec_assert_eq "$upload_state_last_success_seq" '7'
|
|
|
|
write_payload "$SS_STATISTICS_JSON_FILE" 8
|
|
write_payload "$SS_STATISTICS_UPLOAD_JSON_FILE" 8
|
|
upload_pending_create
|
|
ss_spec_assert_file_contains "$SS_STATISTICS_UPLOAD_PENDING_META_FILE" "$(printf 'full\tgeneration-upload\t8')"
|
|
MOCK_HTTP_MODE='duplicate'
|
|
export MOCK_HTTP_MODE
|
|
upload_send_pending
|
|
upload_state_load
|
|
ss_spec_assert_eq "$upload_state_needs_full" '0'
|
|
ss_spec_assert_eq "$upload_state_last_full_seq" '8'
|
|
|
|
# The 12-hour cadence threshold selects a full snapshot even when no failure
|
|
# occurred, and a permanent 400 rejects/discards the payload while requiring
|
|
# a later full reconciliation.
|
|
upload_state_recent_successes="$SS_STATS_UPLOAD_FULL_EVERY"
|
|
upload_state_save
|
|
write_payload "$SS_STATISTICS_JSON_FILE" 9
|
|
write_payload "$SS_STATISTICS_UPLOAD_JSON_FILE" 9
|
|
upload_pending_create
|
|
ss_spec_assert_file_contains "$SS_STATISTICS_UPLOAD_PENDING_META_FILE" "$(printf 'full\tgeneration-upload\t9')"
|
|
upload_pending_clear
|
|
|
|
upload_state_recent_successes=0
|
|
upload_state_needs_full=0
|
|
upload_state_last_success_seq=8
|
|
upload_state_save
|
|
upload_pending_create
|
|
ss_spec_assert_file_contains "$SS_STATISTICS_UPLOAD_PENDING_META_FILE" "$(printf 'recent\tgeneration-upload\t9')"
|
|
MOCK_HTTP_MODE='bad-request'
|
|
export MOCK_HTTP_MODE
|
|
BAD_REQUEST_RC=0
|
|
upload_send_pending || BAD_REQUEST_RC=$?
|
|
ss_spec_assert_eq "$BAD_REQUEST_RC" '2'
|
|
[ ! -e "$SS_STATISTICS_UPLOAD_PENDING_FILE" ]
|
|
upload_state_load
|
|
ss_spec_assert_eq "$upload_state_needs_full" '1'
|
|
|
|
# An unlicensed device is blocked locally without asking the Hub for a
|
|
# statistics credential. The long-lived uploader can remain idle so a later
|
|
# license update can re-enable it without restarting the collector.
|
|
MOCK_UPLOAD_ENTITLEMENT='allowed'
|
|
MOCK_REFRESH_RESULT='allowed'
|
|
MOCK_DENIED_RECHECK_DUE='1'
|
|
SS_STATISTICS_UPLOAD_TOKEN='token-old'
|
|
ss_license_key=''
|
|
ss_should_terminate=0
|
|
: >"$REFRESH_CALLS"
|
|
upload_sleep() {
|
|
ss_should_terminate=1
|
|
return 1
|
|
}
|
|
main
|
|
ss_spec_assert_eq "$MOCK_UPLOAD_ENTITLEMENT" 'denied'
|
|
ss_spec_assert_eq "$(wc -l <"$REFRESH_CALLS" | tr -d ' ')" '0'
|
|
ss_spec_assert_eq "$SS_STATISTICS_UPLOAD_TOKEN" ''
|
|
)
|
|
|
|
ss_case_statistics_upload_credentials() (
|
|
set -eu
|
|
TMP="$(ss_spec_tmpdir)"
|
|
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
|
mkdir -p "$TMP/statistics"
|
|
SS_STATISTICS_DIR="$TMP/statistics"
|
|
SS_STATISTICS_UPLOAD_CREDENTIALS_FILE="$SS_STATISTICS_DIR/upload.credentials"
|
|
SS_STATISTICS_UPLOAD_ENTITLEMENT_FILE="$SS_STATISTICS_DIR/upload.entitlement"
|
|
SS_STATISTICS_UPLOAD_PENDING_FILE="$SS_STATISTICS_DIR/upload.pending.json"
|
|
SS_STATISTICS_UPLOAD_PENDING_META_FILE="$SS_STATISTICS_DIR/upload.pending.meta"
|
|
SS_STATISTICS_UPLOAD_STATE_FILE="$SS_STATISTICS_DIR/upload.state"
|
|
SS_STATISTICS_POLL_COMMAND="$TMP/stats-poll"
|
|
SS_STATISTICS_REBASELINE_FILE="$SS_STATISTICS_DIR/rebaseline"
|
|
# shellcheck disable=SC1091
|
|
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/statistics.sh"
|
|
|
|
is_valid_integer() {
|
|
case "$1" in
|
|
'' | *[!0-9]*) return 1 ;;
|
|
esac
|
|
[ "$1" -ge 0 ] 2>/dev/null
|
|
}
|
|
|
|
MOCK_NOW=1000
|
|
date() {
|
|
[ "${1:-}" = '+%s' ] || return 1
|
|
printf '%s\n' "$MOCK_NOW"
|
|
}
|
|
|
|
RESPONSE="$TMP/resolve.json"
|
|
printf '%s\n' '{}' >"$RESPONSE"
|
|
MOCK_URL='https://www.smartsafehub.com/api/v1/statistics'
|
|
MOCK_TOKEN='statistics-token'
|
|
MOCK_TTL='172800'
|
|
ss_json_get_file() {
|
|
case "$2" in
|
|
'@.statistics.upload_url') printf '%s\n' "$MOCK_URL" ;;
|
|
'@.statistics.token') printf '%s\n' "$MOCK_TOKEN" ;;
|
|
'@.statistics.token_expires_in_s') printf '%s\n' "$MOCK_TTL" ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
BEFORE_UMASK="$(umask)"
|
|
ss_statistics_store_upload_credentials "$RESPONSE"
|
|
ss_spec_assert_eq "$(umask)" "$BEFORE_UMASK"
|
|
[ -s "$SS_STATISTICS_UPLOAD_CREDENTIALS_FILE" ]
|
|
ss_spec_assert_eq "$(sed -n '1p' "$SS_STATISTICS_UPLOAD_CREDENTIALS_FILE")" "$MOCK_URL"
|
|
ss_spec_assert_eq "$(sed -n '2p' "$SS_STATISTICS_UPLOAD_CREDENTIALS_FILE")" "$MOCK_TOKEN"
|
|
ss_spec_assert_eq "$(sed -n '3p' "$SS_STATISTICS_UPLOAD_CREDENTIALS_FILE")" "$MOCK_TTL"
|
|
MODE="$(stat -c '%a' "$SS_STATISTICS_UPLOAD_CREDENTIALS_FILE" 2>/dev/null || stat -f '%Lp' "$SS_STATISTICS_UPLOAD_CREDENTIALS_FILE")"
|
|
ss_spec_assert_eq "$MODE" '600'
|
|
|
|
SS_STATISTICS_UPLOAD_URL=''
|
|
SS_STATISTICS_UPLOAD_TOKEN=''
|
|
SS_STATISTICS_UPLOAD_TOKEN_EXPIRES_IN_S='0'
|
|
ss_statistics_load_upload_credentials
|
|
ss_spec_assert_eq "$SS_STATISTICS_UPLOAD_URL" "$MOCK_URL"
|
|
ss_spec_assert_eq "$SS_STATISTICS_UPLOAD_TOKEN" "$MOCK_TOKEN"
|
|
ss_spec_assert_eq "$SS_STATISTICS_UPLOAD_TOKEN_EXPIRES_IN_S" "$MOCK_TTL"
|
|
ss_spec_assert_eq "$(ss_statistics_upload_entitlement)" 'allowed'
|
|
|
|
# `statistics: null` is authoritative: clear secrets/pending cloud state and
|
|
# remember the denial in tmpfs so the uploader does not re-resolve in a loop.
|
|
printf '%s\n' pending >"$SS_STATISTICS_UPLOAD_PENDING_FILE"
|
|
printf '%s\n' meta >"$SS_STATISTICS_UPLOAD_PENDING_META_FILE"
|
|
printf '%s\n' state >"$SS_STATISTICS_UPLOAD_STATE_FILE"
|
|
MOCK_URL=''
|
|
MOCK_TOKEN=''
|
|
DENIED_RC=0
|
|
ss_statistics_sync_upload_entitlement "$RESPONSE" || DENIED_RC=$?
|
|
ss_spec_assert_eq "$DENIED_RC" '2'
|
|
ss_spec_assert_eq "$(ss_statistics_upload_entitlement)" 'denied'
|
|
[ ! -e "$SS_STATISTICS_UPLOAD_CREDENTIALS_FILE" ]
|
|
[ ! -e "$SS_STATISTICS_UPLOAD_PENDING_FILE" ]
|
|
[ ! -e "$SS_STATISTICS_UPLOAD_PENDING_META_FILE" ]
|
|
[ ! -e "$SS_STATISTICS_UPLOAD_STATE_FILE" ]
|
|
ss_spec_assert_eq "$(sed -n '2p' "$SS_STATISTICS_UPLOAD_ENTITLEMENT_FILE")" '1000'
|
|
if ss_statistics_upload_denied_recheck_due 43200; then
|
|
return 1
|
|
fi
|
|
MOCK_NOW=44199
|
|
if ss_statistics_upload_denied_recheck_due 43200; then
|
|
return 1
|
|
fi
|
|
MOCK_NOW=44200
|
|
ss_statistics_upload_denied_recheck_due 43200
|
|
MOCK_NOW=999
|
|
ss_statistics_upload_denied_recheck_due 43200
|
|
MOCK_NOW=44200
|
|
if ss_statistics_load_upload_credentials; then
|
|
return 1
|
|
fi
|
|
|
|
# A later entitled resolve restores upload without changing local collection.
|
|
MOCK_URL='https://www.smartsafehub.com/api/v1/statistics'
|
|
MOCK_TOKEN='statistics-token-renewed'
|
|
ss_statistics_sync_upload_entitlement "$RESPONSE"
|
|
ss_spec_assert_eq "$(ss_statistics_upload_entitlement)" 'allowed'
|
|
ss_statistics_load_upload_credentials
|
|
ss_spec_assert_eq "$SS_STATISTICS_UPLOAD_TOKEN" 'statistics-token-renewed'
|
|
|
|
MOCK_URL='https://example.invalid/api/v1/statistics'
|
|
if ss_statistics_store_upload_credentials "$RESPONSE"; then
|
|
return 1
|
|
fi
|
|
MOCK_URL='https://www.smartsafehub.com/api/v1/statistics'
|
|
MOCK_TOKEN='token with whitespace'
|
|
if ss_statistics_store_upload_credentials "$RESPONSE"; then
|
|
return 1
|
|
fi
|
|
)
|
|
|
|
ss_case_statistics_http_auth() (
|
|
set -eu
|
|
TMP="$(ss_spec_tmpdir)"
|
|
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
|
mkdir -p "$TMP/bin"
|
|
PAYLOAD="$TMP/payload.json"
|
|
OUTPUT="$TMP/response.json"
|
|
CURL_ARGS="$TMP/curl.args"
|
|
printf '%s\n' '{}' >"$PAYLOAD"
|
|
|
|
cat >"$TMP/bin/curl" <<'EOF_CURL'
|
|
#!/bin/sh
|
|
out=''
|
|
prev=''
|
|
for arg in "$@"; do
|
|
printf '%s\n' "$arg" >>"$CURL_ARGS"
|
|
if [ "$prev" = '-o' ]; then
|
|
out="$arg"
|
|
fi
|
|
prev="$arg"
|
|
done
|
|
[ -n "$out" ] && printf '%s\n' '{}' >"$out"
|
|
printf '%s' '200'
|
|
EOF_CURL
|
|
chmod 755 "$TMP/bin/curl"
|
|
export CURL_ARGS
|
|
|
|
ss_download_timeout=10
|
|
command_exists() { command -v "$1" >/dev/null 2>&1; }
|
|
# shellcheck disable=SC1091
|
|
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
|
|
|
|
PATH="$TMP/bin:$PATH"
|
|
export PATH
|
|
ss_http_post_json 'https://www.smartsafehub.com/api/v1/statistics' "$PAYLOAD" "$OUTPUT" 'Bearer statistics-token'
|
|
ss_spec_assert_file_line "$CURL_ARGS" 'Authorization: Bearer statistics-token'
|
|
ss_spec_assert_file_line "$CURL_ARGS" "@${PAYLOAD}"
|
|
ss_spec_assert_eq "$SS_HTTP_STATUS" '200'
|
|
[ -s "$OUTPUT" ]
|
|
)
|
|
|
|
ss_case_http_uclient_transport() (
|
|
set -eu
|
|
TMP="$(ss_spec_tmpdir)"
|
|
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
|
mkdir -p "$TMP/bin"
|
|
PAYLOAD="$TMP/payload.json"
|
|
OUTPUT="$TMP/response.json"
|
|
UCLIENT_ARGS="$TMP/uclient.args"
|
|
UCLIENT_CALLS="$TMP/uclient.calls"
|
|
printf '%s\n' '{"hello":"world"}' >"$PAYLOAD"
|
|
: >"$UCLIENT_ARGS"
|
|
: >"$UCLIENT_CALLS"
|
|
|
|
cat >"$TMP/bin/uclient-fetch" <<'EOF_UCLIENT'
|
|
#!/bin/sh
|
|
if [ "${1:-}" = '--help' ]; then
|
|
printf '%s\n' 'Usage: uclient-fetch [--post-file=FILE] [--header=HEADER]'
|
|
exit 0
|
|
fi
|
|
printf '%s\n' 'call' >>"$UCLIENT_CALLS"
|
|
printf '%s\n' "$@" >>"$UCLIENT_ARGS"
|
|
out=''
|
|
prev=''
|
|
for arg in "$@"; do
|
|
if [ "$prev" = '-O' ]; then
|
|
out="$arg"
|
|
fi
|
|
prev="$arg"
|
|
done
|
|
case "${UCLIENT_MODE:-success}" in
|
|
success)
|
|
[ -n "$out" ] && printf '%s\n' '{}' >"$out"
|
|
exit 0
|
|
;;
|
|
unauthorized)
|
|
printf '%s\n' 'HTTP error 401' >&2
|
|
exit 8
|
|
;;
|
|
esac
|
|
exit 1
|
|
EOF_UCLIENT
|
|
chmod 755 "$TMP/bin/uclient-fetch"
|
|
export UCLIENT_ARGS UCLIENT_CALLS
|
|
|
|
ss_download_timeout=10
|
|
command_exists() {
|
|
[ "$1" = 'uclient-fetch' ]
|
|
}
|
|
# shellcheck disable=SC1091
|
|
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
|
|
|
|
PATH="$TMP/bin:$PATH"
|
|
export PATH
|
|
UCLIENT_MODE='success'
|
|
export UCLIENT_MODE
|
|
ss_http_post_json 'https://www.smartsafehub.com/api/v1/statistics' "$PAYLOAD" "$OUTPUT" 'Bearer statistics-token'
|
|
ss_spec_assert_file_line "$UCLIENT_ARGS" '--header=Content-Type: application/json'
|
|
ss_spec_assert_file_line "$UCLIENT_ARGS" '--header=Accept: application/json'
|
|
ss_spec_assert_file_line "$UCLIENT_ARGS" '--header=Authorization: Bearer statistics-token'
|
|
ss_spec_assert_file_line "$UCLIENT_ARGS" "--post-file=$PAYLOAD"
|
|
ss_spec_assert_eq "$SS_HTTP_STATUS" ''
|
|
[ -s "$OUTPUT" ]
|
|
|
|
: >"$UCLIENT_ARGS"
|
|
UCLIENT_MODE='unauthorized'
|
|
export UCLIENT_MODE
|
|
if ss_http_post_json 'https://www.smartsafehub.com/api/v1/statistics' "$PAYLOAD" "$OUTPUT" 'Bearer statistics-token' 2>/dev/null; then
|
|
return 1
|
|
fi
|
|
ss_spec_assert_eq "$SS_HTTP_STATUS" '401'
|
|
[ ! -e "${OUTPUT}.uclient.txt" ]
|
|
|
|
cat >"$TMP/bin/uclient-fetch" <<'EOF_NO_HEADER'
|
|
#!/bin/sh
|
|
if [ "${1:-}" = '--help' ]; then
|
|
printf '%s\n' 'Usage: uclient-fetch [--post-file=FILE]'
|
|
exit 0
|
|
fi
|
|
printf '%s\n' 'network-call' >>"$UCLIENT_CALLS"
|
|
exit 0
|
|
EOF_NO_HEADER
|
|
chmod 755 "$TMP/bin/uclient-fetch"
|
|
: >"$UCLIENT_CALLS"
|
|
if ss_http_post_json 'https://www.smartsafehub.com/api/v1/statistics' "$PAYLOAD" "$OUTPUT" 'Bearer statistics-token'; then
|
|
return 1
|
|
fi
|
|
[ ! -s "$UCLIENT_CALLS" ]
|
|
)
|
|
|
|
ss_case_refreshd_scheduler() (
|
|
set -eu
|
|
TMP="$(ss_spec_tmpdir)"
|
|
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
|
REFRESHD="$SS_SPEC_ROOT/files/usr/libexec/safeshield-refreshd"
|
|
HARNESS="$TMP/refreshd-scheduler.sh"
|
|
awk '
|
|
/^ss_should_terminate=0$/ { capture=1 }
|
|
capture && /^trap "handle_term"/ { exit }
|
|
capture { print }
|
|
' "$REFRESHD" >"$HARNESS"
|
|
# shellcheck disable=SC1090
|
|
. "$HARNESS"
|
|
|
|
CALLS="$TMP/calls"
|
|
: >"$CALLS"
|
|
MOCK_LAST_SUCCESS=0
|
|
MOCK_LAST_ATTEMPT=0
|
|
NOW=1000
|
|
json() {
|
|
[ "${1:-}" = 'get' ] || return 1
|
|
case "${2:-}" in
|
|
last_success) printf '%s\n' "$MOCK_LAST_SUCCESS" ;;
|
|
last_attempt) printf '%s\n' "$MOCK_LAST_ATTEMPT" ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
is_valid_integer() { case "$1" in '' | *[!0-9]*) return 1 ;; *) return 0 ;; esac }
|
|
date() { [ "${1:-}" = '+%s' ] && printf '%s\n' "$NOW"; }
|
|
ss_status_set() { printf '%s=%s\n' "$1" "${2:-}" >>"$CALLS"; }
|
|
log_info() { :; }
|
|
|
|
MOCK_LAST_SUCCESS=900
|
|
MOCK_LAST_ATTEMPT=950
|
|
ss_spec_assert_eq "$(schedule_base_epoch 1000)" '950'
|
|
MOCK_LAST_ATTEMPT=1100
|
|
ss_spec_assert_eq "$(schedule_base_epoch 1000)" '900'
|
|
MOCK_LAST_SUCCESS='invalid'
|
|
MOCK_LAST_ATTEMPT='invalid'
|
|
ss_spec_assert_eq "$(schedule_base_epoch 1000)" '0'
|
|
|
|
MOCK_LAST_SUCCESS=950
|
|
NOW=1000
|
|
has_recent_success 100
|
|
MOCK_LAST_SUCCESS=900
|
|
! has_recent_success 100
|
|
MOCK_LAST_SUCCESS=1100
|
|
! has_recent_success 100
|
|
|
|
MOCK_LAST_SUCCESS=900
|
|
MOCK_LAST_ATTEMPT=0
|
|
NOW=950
|
|
SLEEPS="$TMP/sleeps"
|
|
: >"$SLEEPS"
|
|
SLEEP_CALL=0
|
|
sleep_seconds() {
|
|
SLEEP_CALL=$((SLEEP_CALL + 1))
|
|
printf '%s\n' "$1" >>"$SLEEPS"
|
|
if [ "$SLEEP_CALL" -eq 1 ]; then
|
|
MOCK_LAST_SUCCESS=970
|
|
NOW=1000
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
: >"$CALLS"
|
|
# Defined by the dynamically sourced refreshd harness above.
|
|
# shellcheck disable=SC2218
|
|
! wait_until_refresh_due 100 900
|
|
ss_spec_assert_eq "$(cat "$SLEEPS")" '50
|
|
70'
|
|
ss_spec_assert_file_line "$CALLS" 'next_refresh_at=1000'
|
|
ss_spec_assert_file_line "$CALLS" 'next_refresh_at=1070'
|
|
|
|
MOCK_LAST_SUCCESS=900
|
|
MOCK_LAST_ATTEMPT=0
|
|
NOW=1100
|
|
: >"$CALLS"
|
|
# Defined by the dynamically sourced refreshd harness above.
|
|
# shellcheck disable=SC2218
|
|
wait_until_refresh_due 100 900
|
|
ss_spec_assert_file_line "$CALLS" 'next_refresh_at=0'
|
|
|
|
REQUIRE_WAN=1
|
|
ss_config_get() {
|
|
case "$2" in
|
|
require_wan) printf '%s\n' "$REQUIRE_WAN" ;;
|
|
*) printf '%s\n' "${3:-}" ;;
|
|
esac
|
|
}
|
|
ubus() {
|
|
printf 'ubus %s\n' "$*" >>"$CALLS"
|
|
return 0
|
|
}
|
|
: >"$CALLS"
|
|
# Defined by the dynamically sourced refreshd harness above.
|
|
# shellcheck disable=SC2218
|
|
wait_for_wan_if_needed
|
|
ss_spec_assert_file_line "$CALLS" 'ubus -t 30 wait_for network.interface.wan'
|
|
REQUIRE_WAN=0
|
|
: >"$CALLS"
|
|
# Defined by the dynamically sourced refreshd harness above.
|
|
# shellcheck disable=SC2218
|
|
wait_for_wan_if_needed
|
|
[ ! -s "$CALLS" ]
|
|
|
|
refreshd_lock() { return 0; }
|
|
refreshd_unlock() { :; }
|
|
RUNNING_STATUS_FILE="$TMP/status.json"
|
|
printf '%s\n' '{}' >"$RUNNING_STATUS_FILE"
|
|
ss_status_reset() { printf '%s\n' status_reset >>"$CALLS"; }
|
|
ss_sync_blocklist_status() { printf '%s\n' sync_blocklist >>"$CALLS"; }
|
|
wait_for_wan_if_needed() { :; }
|
|
wait_until_refresh_due() { return 1; }
|
|
safeshield_force_download() { printf '%s\n' refresh >>"$CALLS"; }
|
|
ss_load_config() {
|
|
case "${MODE:-}" in
|
|
disabled) ss_enabled=0 ;;
|
|
*) ss_enabled=1 ;;
|
|
esac
|
|
return 0
|
|
}
|
|
ss_config_get() {
|
|
case "$2" in
|
|
refresh_interval_s) printf '%s\n' '100' ;;
|
|
boot_start_delay_s) printf '%s\n' '0' ;;
|
|
refresh_on_boot) printf '%s\n' '1' ;;
|
|
require_wan) printf '%s\n' '0' ;;
|
|
*) printf '%s\n' "${3:-}" ;;
|
|
esac
|
|
}
|
|
has_recent_success() { return 0; }
|
|
sleep_seconds() { return 0; }
|
|
MODE='boot-skip'
|
|
NOW=1000
|
|
: >"$CALLS"
|
|
main
|
|
ss_spec_assert_file_line "$CALLS" 'stage=boot_refresh_skipped'
|
|
ss_spec_assert_file_line "$CALLS" 'stage=scheduled_wait'
|
|
! grep -Fx 'refresh' "$CALLS" >/dev/null
|
|
|
|
MODE='disabled'
|
|
SLEEP_CALL=0
|
|
sleep_seconds() {
|
|
SLEEP_CALL=$((SLEEP_CALL + 1))
|
|
[ "$SLEEP_CALL" -eq 1 ]
|
|
}
|
|
: >"$CALLS"
|
|
main
|
|
ss_spec_assert_file_line "$CALLS" 'status=disabled'
|
|
ss_spec_assert_file_line "$CALLS" 'stage=disabled'
|
|
ss_spec_assert_file_line "$CALLS" 'next_refresh_at=0'
|
|
! grep -Fx 'refresh' "$CALLS" >/dev/null
|
|
)
|