mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-30 06:25:15 +08:00
🎉 Sync 2026-09-27 21:03:26
This commit is contained in:
1 parent
f4559bbed1
commit
065763f7fd
673 files changed
+261285
-34160
No files matched your search
Executable
+445
@@ -0,0 +1,445 @@
|
||||
#!/bin/sh
|
||||
# shellcheck shell=sh
|
||||
|
||||
ss_case_blocklist_format() (
|
||||
set -eu
|
||||
TMP_DIR="$(ss_spec_tmpdir)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM
|
||||
SS_TMP_DIR="$TMP_DIR/tmp"
|
||||
SS_DNSMASQ_DIR="$TMP_DIR/dnsmasq.d"
|
||||
SS_BLOCKLIST_FILE="$SS_DNSMASQ_DIR/safeshield.blocklist"
|
||||
ss_min_valid_line_count=0
|
||||
ss_max_blocklist_file_size_kb=1024
|
||||
ss_dnsmasq_sanity_check=0
|
||||
ss_valid_line_count=0
|
||||
mkdir -p "$SS_TMP_DIR" "$SS_DNSMASQ_DIR"
|
||||
ss_should_stop() { return 1; }
|
||||
ss_status_set() { :; }
|
||||
ss_status_add_error() { :; }
|
||||
log_info() { :; }
|
||||
log_error() { :; }
|
||||
log_warn() { :; }
|
||||
ss_sync_path() { :; }
|
||||
ss_blocklist_tmp_path() { printf '%s/.safeshield.blocklist.tmp.%s\n' "$SS_DNSMASQ_DIR" "$$"; }
|
||||
ss_install_blocklist_atomic() { mv -f "$1" "$2"; }
|
||||
DNSMASQ_PATCHED=1
|
||||
ss_dnsmasq_safeshield_block_supported() { [ "$DNSMASQ_PATCHED" = '1' ]; }
|
||||
# shellcheck disable=SC1091
|
||||
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
|
||||
ss_spec_assert_file_not_contains "$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh" 'smartsafehub-block='
|
||||
|
||||
normalized="$(printf '%s\n' \
|
||||
'address=/ads.example/#' \
|
||||
'address=/legacy-v4.example/0.0.0.0' \
|
||||
'address=/legacy-v6.example/::' \
|
||||
'safeshield-block=/native.example/' | ss_normalize_domains)"
|
||||
expected='ads.example
|
||||
legacy-v4.example
|
||||
legacy-v6.example
|
||||
native.example'
|
||||
ss_spec_assert_eq "$normalized" "$expected"
|
||||
|
||||
cat >"$SS_TMP_DIR/api.0.block.txt" <<'DATA'
|
||||
ads.example
|
||||
tracker.example
|
||||
ads.example
|
||||
DATA
|
||||
cat >"$SS_TMP_DIR/api.1.block.txt" <<'DATA'
|
||||
phishing.example
|
||||
tracker.example
|
||||
DATA
|
||||
cat >"$SS_TMP_DIR/api.2.allow.txt" <<'DATA'
|
||||
tracker.example
|
||||
remote-allow.example
|
||||
DATA
|
||||
cat >"$SS_TMP_DIR/local.block.txt" <<'DATA'
|
||||
remote-allow.example
|
||||
local-block.example
|
||||
DATA
|
||||
cat >"$SS_TMP_DIR/local.allow.txt" <<'DATA'
|
||||
local-block.example
|
||||
DATA
|
||||
ss_merge_lists
|
||||
expected='safeshield-block=/ads.example/
|
||||
safeshield-block=/phishing.example/
|
||||
safeshield-block=/remote-allow.example/
|
||||
server=/local-block.example/#
|
||||
server=/tracker.example/#'
|
||||
ss_spec_assert_eq "$(cat "$SS_BLOCKLIST_FILE")" "$expected"
|
||||
ss_spec_assert_eq "$(grep -c . "$SS_BLOCKLIST_FILE")" '5'
|
||||
check_blocklist_rule_present 'ads.example'
|
||||
ss_spec_assert_eq "$(find_test_domains 1)" 'ads.example'
|
||||
|
||||
rm -f "$SS_TMP_DIR"/api.*.block.txt "$SS_TMP_DIR"/api.*.allow.txt
|
||||
printf '%s\n' 'example.com' >"$SS_TMP_DIR/api.0.block.txt"
|
||||
printf '%s\n' 'good.example.com' >"$SS_TMP_DIR/api.1.allow.txt"
|
||||
printf '%s\n' 'blocked.good.example.com' >"$SS_TMP_DIR/local.block.txt"
|
||||
printf '%s\n' 'allowed.blocked.good.example.com' >"$SS_TMP_DIR/local.allow.txt"
|
||||
ss_merge_lists
|
||||
expected='safeshield-block=/blocked.good.example.com/
|
||||
safeshield-block=/example.com/
|
||||
server=/allowed.blocked.good.example.com/#
|
||||
server=/good.example.com/#'
|
||||
ss_spec_assert_eq "$(cat "$SS_BLOCKLIST_FILE")" "$expected"
|
||||
|
||||
cat >"$SS_BLOCKLIST_FILE" <<'DATA'
|
||||
address=/migrate-one.example/#
|
||||
address=/migrate-two.example/0.0.0.0
|
||||
server=/allowed.example/#
|
||||
DATA
|
||||
ss_reconcile_blocklist_file_for_dnsmasq "$SS_BLOCKLIST_FILE"
|
||||
expected='safeshield-block=/migrate-one.example/
|
||||
safeshield-block=/migrate-two.example/
|
||||
server=/allowed.example/#'
|
||||
ss_spec_assert_eq "$(cat "$SS_BLOCKLIST_FILE")" "$expected"
|
||||
|
||||
DNSMASQ_PATCHED=0
|
||||
ss_reconcile_blocklist_file_for_dnsmasq "$SS_BLOCKLIST_FILE"
|
||||
expected='address=/migrate-one.example/#
|
||||
address=/migrate-two.example/#
|
||||
server=/allowed.example/#'
|
||||
ss_spec_assert_eq "$(cat "$SS_BLOCKLIST_FILE")" "$expected"
|
||||
|
||||
rm -f \
|
||||
"$SS_TMP_DIR"/api.*.block.txt \
|
||||
"$SS_TMP_DIR"/api.*.allow.txt \
|
||||
"$SS_TMP_DIR"/local.block.txt \
|
||||
"$SS_TMP_DIR"/local.allow.txt
|
||||
printf '%s\n' 'fallback.example' >"$SS_TMP_DIR/api.0.block.txt"
|
||||
ss_merge_lists
|
||||
ss_spec_assert_eq "$(cat "$SS_BLOCKLIST_FILE")" 'address=/fallback.example/#'
|
||||
DNSMASQ_PATCHED=1
|
||||
|
||||
cat >"$SS_BLOCKLIST_FILE" <<'DATA'
|
||||
address=/legacy.example/0.0.0.0
|
||||
address=/legacy.example/::
|
||||
DATA
|
||||
check_blocklist_rule_present 'legacy.example'
|
||||
|
||||
cat >"$SS_BLOCKLIST_FILE" <<'DATA'
|
||||
address=/sample-one.example/#
|
||||
address=/sample-two.example/#
|
||||
address=/sample-three.example/#
|
||||
DATA
|
||||
sampled="$(find_test_domains 2)"
|
||||
expected_sample='sample-one.example
|
||||
sample-two.example'
|
||||
ss_spec_assert_eq "$sampled" "$expected_sample"
|
||||
RULE_CHECK_COUNT=0
|
||||
DNS_CHECK_COUNT=0
|
||||
check_blocklist_rule_present() {
|
||||
RULE_CHECK_COUNT=$((RULE_CHECK_COUNT + 1))
|
||||
return 1
|
||||
}
|
||||
check_domain_blocked() {
|
||||
DNS_CHECK_COUNT=$((DNS_CHECK_COUNT + 1))
|
||||
return 0
|
||||
}
|
||||
check_blocklist_applied_multi_with_stats 2 2
|
||||
ss_spec_assert_eq "$RULE_CHECK_COUNT" '0'
|
||||
ss_spec_assert_eq "$DNS_CHECK_COUNT" '2'
|
||||
)
|
||||
|
||||
ss_case_multi_artifact() (
|
||||
set -eu
|
||||
TMP_DIR="$(ss_spec_tmpdir)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM
|
||||
SS_TMP_DIR="$TMP_DIR/tmp"
|
||||
SS_API_PAYLOAD="$SS_TMP_DIR/resolve-request.json"
|
||||
SS_API_RESPONSE="$SS_TMP_DIR/resolve-response.json"
|
||||
SS_RESOLVED_SOURCES="$SS_TMP_DIR/resolved-sources.tsv"
|
||||
SS_ARTIFACT_CACHE_STATE="$SS_TMP_DIR/artifact-sources.state"
|
||||
SS_MAX_ARTIFACT_SOURCES=16
|
||||
ss_license_key='test-license'
|
||||
ss_download_retry=1
|
||||
ss_download_timeout=10
|
||||
ss_max_blocklist_file_size_kb=1024
|
||||
mkdir -p "$SS_TMP_DIR"
|
||||
ss_should_stop() { return 1; }
|
||||
ss_status_set() { :; }
|
||||
ss_status_add_error() { :; }
|
||||
log_info() { :; }
|
||||
log_error() { :; }
|
||||
log_ok() { :; }
|
||||
command_exists() { command -v "$1" >/dev/null 2>&1; }
|
||||
# shellcheck disable=SC1091
|
||||
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
|
||||
ss_write_resolve_payload() { printf '{}\n' >"$1"; }
|
||||
ss_http_post_json() { printf '{}\n' >"$3"; }
|
||||
ss_json_get_file() {
|
||||
case "$2" in
|
||||
'@.artifact.sources[0].download_url') printf '%s\n' 'https://example.invalid/hagezi.txt' ;;
|
||||
'@.artifact.sources[0].action') printf '%s\n' 'block' ;;
|
||||
'@.artifact.sources[0].id') printf '%s\n' 'hagezi-pro' ;;
|
||||
'@.artifact.sources[0].sha256') printf '%s\n' "$HAGEZI_SHA" ;;
|
||||
'@.artifact.sources[1].download_url') printf '%s\n' 'https://example.invalid/smartsafehub-block.txt' ;;
|
||||
'@.artifact.sources[1].action') printf '%s\n' 'block' ;;
|
||||
'@.artifact.sources[1].id') printf '%s\n' 'smartsafehub-pro' ;;
|
||||
'@.artifact.sources[1].sha256') printf '%s\n' "$SMART_BLOCK_SHA" ;;
|
||||
'@.artifact.sources[2].download_url') printf '%s\n' 'https://example.invalid/smartsafehub-allow.txt' ;;
|
||||
'@.artifact.sources[2].action') printf '%s\n' 'allow' ;;
|
||||
'@.artifact.sources[2].id') printf '%s\n' 'smartsafehub-allow' ;;
|
||||
'@.artifact.sources[2].sha256') printf '%s\n' "$SMART_ALLOW_SHA" ;;
|
||||
'@.artifact.sources[3].download_url' | '@.artifact.sources[16].download_url' | '@.artifact.sha256') : ;;
|
||||
'@.artifact.tier') printf '%s\n' 'pro' ;;
|
||||
'@.artifact.version') printf '%s\n' '20260830T120000Z' ;;
|
||||
'@.artifact.unique_domains' | '@.artifact.rules') printf '%s\n' '4' ;;
|
||||
'@.license.plan') printf '%s\n' 'pro' ;;
|
||||
'@.license.status') printf '%s\n' 'active' ;;
|
||||
'@.device.profile') printf '%s\n' 'test-profile' ;;
|
||||
*) : ;;
|
||||
esac
|
||||
}
|
||||
cat >"$TMP_DIR/hagezi.txt" <<'DATA'
|
||||
address=/ads.example/#
|
||||
address=/shared.example/#
|
||||
DATA
|
||||
cat >"$TMP_DIR/smartsafehub-block.txt" <<'DATA'
|
||||
phishing.example
|
||||
shared.example
|
||||
DATA
|
||||
printf '%s\n' 'shared.example' >"$TMP_DIR/smartsafehub-allow.txt"
|
||||
HAGEZI_SHA="$(sha256sum "$TMP_DIR/hagezi.txt" | awk '{print $1}')"
|
||||
SMART_BLOCK_SHA="$(sha256sum "$TMP_DIR/smartsafehub-block.txt" | awk '{print $1}')"
|
||||
SMART_ALLOW_SHA="$(sha256sum "$TMP_DIR/smartsafehub-allow.txt" | awk '{print $1}')"
|
||||
export HAGEZI_SHA SMART_BLOCK_SHA SMART_ALLOW_SHA
|
||||
ss_resolve_artifact
|
||||
expected_sources="0|block|hagezi-pro|https://example.invalid/hagezi.txt|$HAGEZI_SHA
|
||||
1|block|smartsafehub-pro|https://example.invalid/smartsafehub-block.txt|$SMART_BLOCK_SHA
|
||||
2|allow|smartsafehub-allow|https://example.invalid/smartsafehub-allow.txt|$SMART_ALLOW_SHA"
|
||||
ss_spec_assert_eq "$(cat "$SS_RESOLVED_SOURCES")" "$expected_sources"
|
||||
ss_http_get_file() {
|
||||
case "$1" in
|
||||
'https://example.invalid/hagezi.txt') cp "$TMP_DIR/hagezi.txt" "$2" ;;
|
||||
'https://example.invalid/smartsafehub-block.txt') cp "$TMP_DIR/smartsafehub-block.txt" "$2" ;;
|
||||
'https://example.invalid/smartsafehub-allow.txt') cp "$TMP_DIR/smartsafehub-allow.txt" "$2" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
ss_download_api_artifacts
|
||||
ss_cached_api_sources_available
|
||||
ss_spec_assert_eq "$(cat "$SS_TMP_DIR/api.0.block.txt")" 'ads.example
|
||||
shared.example'
|
||||
ss_spec_assert_eq "$(cat "$SS_TMP_DIR/api.1.block.txt")" 'phishing.example
|
||||
shared.example'
|
||||
ss_spec_assert_eq "$(cat "$SS_TMP_DIR/api.2.allow.txt")" 'shared.example'
|
||||
ss_json_get_file() {
|
||||
case "$2" in
|
||||
'@.artifact.sources[0].download_url') : ;;
|
||||
'@.artifact.download_url') printf '%s\n' 'https://example.invalid/legacy.txt' ;;
|
||||
'@.artifact.sha256') printf '%s\n' 'legacy-sha' ;;
|
||||
*) : ;;
|
||||
esac
|
||||
}
|
||||
ss_resolve_artifact_sources "$SS_API_RESPONSE"
|
||||
ss_spec_assert_eq "$(cat "$SS_RESOLVED_SOURCES")" '0|block|legacy|https://example.invalid/legacy.txt|legacy-sha'
|
||||
)
|
||||
|
||||
ss_case_performance_paths() (
|
||||
set -eu
|
||||
TMP="$(ss_spec_tmpdir)"
|
||||
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
||||
SS_TMP_DIR="$TMP"
|
||||
export SS_TMP_DIR
|
||||
# shellcheck disable=SC1091
|
||||
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
|
||||
cat >"$TMP/api.0.block.txt" <<'EOF_BLOCK_0'
|
||||
alpha.example
|
||||
shared.example
|
||||
EOF_BLOCK_0
|
||||
cat >"$TMP/api.1.block.txt" <<'EOF_BLOCK_1'
|
||||
beta.example
|
||||
shared.example
|
||||
EOF_BLOCK_1
|
||||
ss_merge_sorted_api_sources block "$TMP/merged-blocks.txt"
|
||||
expected='alpha.example
|
||||
beta.example
|
||||
shared.example'
|
||||
ss_spec_assert_eq "$(cat "$TMP/merged-blocks.txt")" "$expected"
|
||||
rm -f "$TMP"/api.*.block.txt
|
||||
ss_merge_sorted_api_sources block "$TMP/empty-blocks.txt"
|
||||
[ ! -s "$TMP/empty-blocks.txt" ]
|
||||
BLOCKLIST="$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
|
||||
STATISTICS_AGGREGATE="$SS_SPEC_ROOT/files/usr/lib/safeshield/statistics/30-aggregate.awk"
|
||||
STATISTICS_OUTPUT="$SS_SPEC_ROOT/files/usr/lib/safeshield/statistics/50-output.awk"
|
||||
ss_spec_assert_file_contains "$BLOCKLIST" 'sort -m -u "$@" >"$out"'
|
||||
ss_spec_assert_file_contains "$STATISTICS_AGGREGATE" 'delete device_first_bucket[key]'
|
||||
ss_spec_assert_file_contains "$STATISTICS_OUTPUT" 'device_first_hour = device_first_bucket[key] + 0'
|
||||
ss_spec_assert_file_contains "$STATISTICS_OUTPUT" 'for (bucket = device_first_hour; bucket <= device_last_hour; bucket += 3600)'
|
||||
)
|
||||
|
||||
ss_case_upgrade_required() (
|
||||
set -eu
|
||||
TMP_DIR="$(ss_spec_tmpdir)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM
|
||||
SS_TMP_DIR="$TMP_DIR/tmp"
|
||||
SS_API_PAYLOAD="$SS_TMP_DIR/resolve-request.json"
|
||||
SS_API_RESPONSE="$SS_TMP_DIR/resolve-response.json"
|
||||
SS_RESOLVED_SOURCES="$SS_TMP_DIR/resolved-sources.tsv"
|
||||
SS_ARTIFACT_CACHE_STATE="$SS_TMP_DIR/artifact-sources.state"
|
||||
ss_license_key='test-license'
|
||||
ss_download_retry=3
|
||||
ss_download_timeout=10
|
||||
ss_max_blocklist_file_size_kb=1024
|
||||
mkdir -p "$SS_TMP_DIR"
|
||||
ss_should_stop() { return 1; }
|
||||
ss_status_set() { printf '%s=%s\n' "$1" "$2" >>"$TMP_DIR/status"; }
|
||||
ss_status_add_error() { :; }
|
||||
ss_status_add_warning() { :; }
|
||||
log_info() { :; }
|
||||
log_error() { :; }
|
||||
log_ok() { :; }
|
||||
command_exists() { return 1; }
|
||||
sleep() { printf 'sleep\n' >>"$TMP_DIR/sleep"; }
|
||||
# shellcheck disable=SC1091
|
||||
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
|
||||
ss_write_resolve_payload() { printf '{}\n' >"$1"; }
|
||||
|
||||
printf '%s\n' 'HTTP error 426' >"$TMP_DIR/uclient.log"
|
||||
ss_spec_assert_eq "$(ss_http_status_from_uclient_log "$TMP_DIR/uclient.log")" '426'
|
||||
|
||||
command_exists() { [ "$1" = 'curl' ]; }
|
||||
curl() {
|
||||
out=''
|
||||
while [ "$#" -gt 0 ]; do
|
||||
if [ "$1" = '-o' ]; then
|
||||
shift
|
||||
out="$1"
|
||||
fi
|
||||
shift
|
||||
done
|
||||
[ -z "$out" ] || printf '%s\n' '{"code":"safeshield_upgrade_required"}' >"$out"
|
||||
printf '%s' '426'
|
||||
return 0
|
||||
}
|
||||
if ss_http_post_json 'https://example.invalid/resolve' "$SS_API_PAYLOAD" "$SS_API_RESPONSE"; then
|
||||
return 1
|
||||
fi
|
||||
ss_spec_assert_eq "$SS_HTTP_STATUS" '426'
|
||||
if ss_http_get_file 'https://example.invalid/artifact' "$SS_TMP_DIR/artifact.raw"; then
|
||||
return 1
|
||||
fi
|
||||
ss_spec_assert_eq "$SS_HTTP_STATUS" '426'
|
||||
|
||||
command_exists() { return 1; }
|
||||
RESOLVE_CALLS=0
|
||||
ss_http_post_json() {
|
||||
RESOLVE_CALLS=$((RESOLVE_CALLS + 1))
|
||||
SS_HTTP_STATUS='426'
|
||||
return 1
|
||||
}
|
||||
if ss_resolve_artifact; then
|
||||
return 1
|
||||
fi
|
||||
ss_spec_assert_eq "$RESOLVE_CALLS" '1'
|
||||
ss_spec_assert_eq "$SS_RESOLVE_ERROR_CODE" 'safeshield_upgrade_required'
|
||||
ss_spec_assert_file_contains "$TMP_DIR/status" 'health_api_resolve=0'
|
||||
ss_spec_assert_not_exists "$TMP_DIR/sleep"
|
||||
|
||||
printf '%s\n' '0|block|test-source|https://example.invalid/artifact.txt|' >"$SS_RESOLVED_SOURCES"
|
||||
DOWNLOAD_CALLS=0
|
||||
ss_http_get_file() {
|
||||
DOWNLOAD_CALLS=$((DOWNLOAD_CALLS + 1))
|
||||
SS_HTTP_STATUS='426'
|
||||
return 1
|
||||
}
|
||||
if ss_download_api_artifacts; then
|
||||
return 1
|
||||
fi
|
||||
ss_spec_assert_eq "$DOWNLOAD_CALLS" '1'
|
||||
ss_spec_assert_eq "$SS_ARTIFACT_DOWNLOAD_ERROR_CODE" 'safeshield_upgrade_required'
|
||||
ss_spec_assert_file_contains "$TMP_DIR/status" 'health_artifact_download=0'
|
||||
ss_spec_assert_not_exists "$TMP_DIR/sleep"
|
||||
)
|
||||
|
||||
ss_case_artifact_integrity() (
|
||||
set -eu
|
||||
TMP="$(ss_spec_tmpdir)"
|
||||
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
||||
SS_TMP_DIR="$TMP/runtime"
|
||||
SS_RESOLVED_SOURCES="$SS_TMP_DIR/resolved-sources.tsv"
|
||||
SS_ARTIFACT_CACHE_STATE="$SS_TMP_DIR/artifact-sources.state"
|
||||
ss_download_retry=2
|
||||
ss_download_timeout=10
|
||||
ss_max_blocklist_file_size_kb=1024
|
||||
mkdir -p "$SS_TMP_DIR"
|
||||
STATUS="$TMP/status"
|
||||
: >"$STATUS"
|
||||
ss_should_stop() { return 1; }
|
||||
ss_status_set() { printf 'set %s %s\n' "$1" "${2:-}" >>"$STATUS"; }
|
||||
ss_status_add_error() { printf 'error %s\n' "$1" >>"$STATUS"; }
|
||||
ss_status_add_warning() { printf 'warning %s\n' "$1" >>"$STATUS"; }
|
||||
log_info() { :; }
|
||||
log_error() { :; }
|
||||
log_ok() { :; }
|
||||
sleep() { :; }
|
||||
command_exists() { command -v "$1" >/dev/null 2>&1; }
|
||||
# shellcheck disable=SC1091
|
||||
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
|
||||
|
||||
printf '%s\n' 'ads.example' 'shared.example' >"$TMP/source0.txt"
|
||||
printf '%s\n' 'shared.example' >"$TMP/source1.txt"
|
||||
SOURCE0_SHA="$(sha256sum "$TMP/source0.txt" | awk '{print $1}')"
|
||||
printf '0|block|source-0|https://example.invalid/source0|%s\n' "$SOURCE0_SHA" >"$SS_RESOLVED_SOURCES"
|
||||
printf '%s\n' '1|allow|source-1|https://example.invalid/source1|' >>"$SS_RESOLVED_SOURCES"
|
||||
|
||||
GET_CALLS=0
|
||||
ss_http_get_file() {
|
||||
GET_CALLS=$((GET_CALLS + 1))
|
||||
SS_HTTP_STATUS=''
|
||||
case "$1" in
|
||||
https://example.invalid/source0)
|
||||
if [ "$GET_CALLS" -eq 1 ]; then
|
||||
return 1
|
||||
fi
|
||||
cp "$TMP/source0.txt" "$2"
|
||||
;;
|
||||
https://example.invalid/source1)
|
||||
cp "$TMP/source1.txt" "$2"
|
||||
;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
ss_download_api_artifacts
|
||||
ss_spec_assert_eq "$GET_CALLS" '3'
|
||||
ss_cached_api_sources_available
|
||||
ss_spec_assert_file_line "$STATUS" 'set health_artifact_download 1'
|
||||
ss_spec_assert_file_line "$STATUS" 'set health_artifact_sha256 '
|
||||
ss_spec_assert_file_line "$STATUS" 'warning artifact_sha256_partial'
|
||||
ss_spec_assert_eq "$(cat "$SS_TMP_DIR/api.0.block.txt")" 'ads.example
|
||||
shared.example'
|
||||
ss_spec_assert_eq "$(cat "$SS_TMP_DIR/api.1.allow.txt")" 'shared.example'
|
||||
|
||||
: >"$STATUS"
|
||||
printf '%s\n' '0|block|bad-sha|https://example.invalid/source0|0000000000000000000000000000000000000000000000000000000000000000' >"$SS_RESOLVED_SOURCES"
|
||||
GET_CALLS=0
|
||||
ss_http_get_file() {
|
||||
GET_CALLS=$((GET_CALLS + 1))
|
||||
SS_HTTP_STATUS=''
|
||||
cp "$TMP/source0.txt" "$2"
|
||||
}
|
||||
! ss_download_api_artifacts
|
||||
ss_spec_assert_file_line "$STATUS" 'set health_artifact_sha256 0'
|
||||
ss_spec_assert_file_line "$STATUS" 'error artifact_sha256_mismatch'
|
||||
ss_spec_assert_not_exists "$SS_ARTIFACT_CACHE_STATE"
|
||||
|
||||
: >"$STATUS"
|
||||
command_exists() { return 1; }
|
||||
! ss_verify_artifact_sha256 "$TMP/source0.txt" "$SOURCE0_SHA"
|
||||
ss_spec_assert_file_line "$STATUS" 'set health_artifact_sha256 0'
|
||||
ss_spec_assert_file_line "$STATUS" 'error sha256sum_not_found'
|
||||
command_exists() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
: >"$STATUS"
|
||||
printf '%s\n' '0|block|too-large|https://example.invalid/source0|' >"$SS_RESOLVED_SOURCES"
|
||||
ss_max_blocklist_file_size_kb=1
|
||||
ss_http_get_file() {
|
||||
SS_HTTP_STATUS=''
|
||||
cp "$TMP/source0.txt" "$2"
|
||||
}
|
||||
du() { printf '2\t%s\n' "$2"; }
|
||||
! ss_download_api_artifacts
|
||||
ss_spec_assert_file_line "$STATUS" 'set health_artifact_download 0'
|
||||
ss_spec_assert_file_line "$STATUS" 'error artifact_too_large'
|
||||
ss_spec_assert_not_exists "$SS_ARTIFACT_CACHE_STATE"
|
||||
)
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
# shellcheck shell=sh
|
||||
|
||||
SS_SPEC_ROOT="${SS_SPEC_ROOT:-$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)}"
|
||||
|
||||
ss_spec_tmpdir() {
|
||||
mktemp -d
|
||||
}
|
||||
|
||||
ss_spec_assert_eq() {
|
||||
[ "$1" = "$2" ]
|
||||
}
|
||||
|
||||
ss_spec_assert_ne() {
|
||||
[ "$1" != "$2" ]
|
||||
}
|
||||
|
||||
ss_spec_assert_file_contains() {
|
||||
grep -Fq -- "$2" "$1"
|
||||
}
|
||||
|
||||
ss_spec_assert_file_not_contains() {
|
||||
! grep -Fq -- "$2" "$1"
|
||||
}
|
||||
|
||||
ss_spec_assert_file_line() {
|
||||
grep -Fxq -- "$2" "$1"
|
||||
}
|
||||
|
||||
ss_spec_assert_exists() {
|
||||
[ -e "$1" ]
|
||||
}
|
||||
|
||||
ss_spec_assert_not_exists() {
|
||||
[ ! -e "$1" ]
|
||||
}
|
||||
|
||||
ss_spec_assert_nonempty() {
|
||||
[ -s "$1" ]
|
||||
}
|
||||
Executable
+449
@@ -0,0 +1,449 @@
|
||||
#!/bin/sh
|
||||
# shellcheck shell=sh
|
||||
|
||||
ss_case_utils() (
|
||||
set -eu
|
||||
PKG_NAME='safeshield-test'
|
||||
export PKG_NAME
|
||||
# shellcheck disable=SC1091
|
||||
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/utils.sh"
|
||||
|
||||
command_exists sh
|
||||
! command_exists safeshield-command-that-does-not-exist
|
||||
ss_spec_assert_eq "$(ss_mask_secret '')" ''
|
||||
ss_spec_assert_eq "$(ss_mask_secret '12345678')" '********'
|
||||
ss_spec_assert_eq "$(ss_mask_secret 'abcd1234wxyz')" 'abcd****wxyz'
|
||||
is_valid_integer 0
|
||||
is_valid_integer 42
|
||||
for value in '' -1 1.5 12x; do
|
||||
! is_valid_integer "$value"
|
||||
done
|
||||
is_greater_equal 2.90 2.90
|
||||
is_greater_equal 2.91 2.90
|
||||
! is_greater_equal 2.79 2.80
|
||||
)
|
||||
|
||||
ss_case_config_validation() (
|
||||
set -eu
|
||||
PKG_NAME='safeshield-test'
|
||||
export PKG_NAME
|
||||
# shellcheck disable=SC1091
|
||||
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/utils.sh"
|
||||
|
||||
WARNINGS=''
|
||||
CONFIG_GET_VALUE=''
|
||||
log_warn() { :; }
|
||||
ss_status_add_warning() {
|
||||
WARNINGS="${WARNINGS}${WARNINGS:+ }$1"
|
||||
}
|
||||
config_get() {
|
||||
eval "$1='${CONFIG_GET_VALUE}'"
|
||||
}
|
||||
# shellcheck disable=SC1091
|
||||
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/config.sh"
|
||||
|
||||
CONFIG_GET_VALUE='configured'
|
||||
ss_spec_assert_eq "$(ss_config_get config enabled default)" 'configured'
|
||||
CONFIG_GET_VALUE=''
|
||||
ss_spec_assert_eq "$(ss_config_get config enabled default)" 'default'
|
||||
|
||||
ss_download_timeout='invalid'
|
||||
ss_download_retry='-1'
|
||||
ss_max_blocklist_file_size_kb='1.5'
|
||||
ss_min_valid_line_count=''
|
||||
ss_pause_timeout='invalid'
|
||||
ss_boot_start_delay_s='-5'
|
||||
ss_statistics_snapshot_interval_s='9'
|
||||
ss_statistics_retention_hours='169'
|
||||
ss_compress_blocklist='2'
|
||||
ss_initial_dnsmasq_restart='yes'
|
||||
ss_dnsmasq_sanity_check='no'
|
||||
ss_apply_local_overrides='2'
|
||||
ss_statistics_enabled='off'
|
||||
WARNINGS=''
|
||||
ss_validate_config
|
||||
|
||||
ss_spec_assert_eq "$ss_download_timeout" '10'
|
||||
ss_spec_assert_eq "$ss_download_retry" '3'
|
||||
ss_spec_assert_eq "$ss_max_blocklist_file_size_kb" '30000'
|
||||
ss_spec_assert_eq "$ss_min_valid_line_count" '3000'
|
||||
ss_spec_assert_eq "$ss_pause_timeout" '20'
|
||||
ss_spec_assert_eq "$ss_boot_start_delay_s" '30'
|
||||
ss_spec_assert_eq "$ss_statistics_snapshot_interval_s" '60'
|
||||
ss_spec_assert_eq "$ss_statistics_retention_hours" '168'
|
||||
ss_spec_assert_eq "$ss_compress_blocklist" '0'
|
||||
ss_spec_assert_eq "$ss_initial_dnsmasq_restart" '0'
|
||||
ss_spec_assert_eq "$ss_dnsmasq_sanity_check" '1'
|
||||
ss_spec_assert_eq "$ss_apply_local_overrides" '1'
|
||||
ss_spec_assert_eq "$ss_statistics_enabled" '1'
|
||||
|
||||
for code in \
|
||||
invalid_download_timeout \
|
||||
invalid_download_retry \
|
||||
invalid_max_blocklist_file_size_kb \
|
||||
invalid_min_valid_line_count \
|
||||
invalid_pause_timeout \
|
||||
invalid_boot_start_delay_s \
|
||||
invalid_statistics_snapshot_interval_s \
|
||||
invalid_statistics_retention_hours \
|
||||
invalid_compress_blocklist \
|
||||
invalid_initial_dnsmasq_restart \
|
||||
invalid_dnsmasq_sanity_check \
|
||||
invalid_apply_local_overrides \
|
||||
invalid_statistics_enabled; do
|
||||
case " $WARNINGS " in
|
||||
*" $code "*) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
ss_download_timeout='1'
|
||||
ss_download_retry='0'
|
||||
ss_max_blocklist_file_size_kb='1'
|
||||
ss_min_valid_line_count='0'
|
||||
ss_pause_timeout='0'
|
||||
ss_boot_start_delay_s='0'
|
||||
ss_statistics_snapshot_interval_s='10'
|
||||
ss_statistics_retention_hours='168'
|
||||
ss_compress_blocklist='1'
|
||||
ss_initial_dnsmasq_restart='1'
|
||||
ss_dnsmasq_sanity_check='0'
|
||||
ss_apply_local_overrides='0'
|
||||
ss_statistics_enabled='0'
|
||||
WARNINGS=''
|
||||
ss_validate_config
|
||||
[ -z "$WARNINGS" ]
|
||||
ss_spec_assert_eq "$ss_statistics_snapshot_interval_s" '10'
|
||||
ss_spec_assert_eq "$ss_statistics_retention_hours" '168'
|
||||
)
|
||||
|
||||
ss_case_dnsmasq_version() (
|
||||
set -eu
|
||||
TMP_DIR="$(ss_spec_tmpdir)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM
|
||||
PKG_NAME='safeshield'
|
||||
export PKG_NAME
|
||||
# shellcheck disable=SC1091
|
||||
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/utils.sh"
|
||||
DNSMASQ_FEATURE_HEALTH=''
|
||||
ss_status_set() {
|
||||
[ "$1" = 'health_dnsmasq_features' ] && DNSMASQ_FEATURE_HEALTH="$2"
|
||||
return 0
|
||||
}
|
||||
log_error() { :; }
|
||||
# shellcheck disable=SC1091
|
||||
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/dns.sh"
|
||||
ss_spec_assert_file_not_contains "$SS_SPEC_ROOT/files/usr/lib/safeshield/dns.sh" 'smartsafehub'
|
||||
|
||||
make_dnsmasq() {
|
||||
version="$1"
|
||||
feature_rc="${2:-0}"
|
||||
cat >"$TMP_DIR/dnsmasq" <<SCRIPT
|
||||
#!/bin/sh
|
||||
case "\${1:-}" in
|
||||
--version)
|
||||
printf '%s\n' 'Dnsmasq version ${version} Copyright (c) 2000-2026 Simon Kelley'
|
||||
exit 0
|
||||
;;
|
||||
--test)
|
||||
exit ${feature_rc}
|
||||
;;
|
||||
esac
|
||||
exit 0
|
||||
SCRIPT
|
||||
chmod +x "$TMP_DIR/dnsmasq"
|
||||
}
|
||||
|
||||
PATH="$TMP_DIR:$PATH"
|
||||
export PATH
|
||||
make_dnsmasq '2.93'
|
||||
ss_spec_assert_eq "$(ss_dnsmasq_version)" '2.93'
|
||||
ss_require_supported_dnsmasq
|
||||
make_dnsmasq '2.94'
|
||||
ss_require_supported_dnsmasq
|
||||
make_dnsmasq '2.93rc1'
|
||||
ss_spec_assert_eq "$(ss_dnsmasq_version)" '2.93'
|
||||
ss_require_supported_dnsmasq
|
||||
make_dnsmasq '2.92'
|
||||
! ss_require_supported_dnsmasq
|
||||
ss_spec_assert_eq "$(ss_dnsmasq_check_error)" 'dnsmasq_version_unsupported'
|
||||
make_dnsmasq '2.93' 1
|
||||
ss_require_supported_dnsmasq
|
||||
ss_spec_assert_eq "$(ss_dnsmasq_check_error)" ''
|
||||
ss_spec_assert_eq "$DNSMASQ_FEATURE_HEALTH" '0'
|
||||
cat >"$TMP_DIR/dnsmasq" <<'SCRIPT'
|
||||
#!/bin/sh
|
||||
printf '%s\n' 'unexpected version output'
|
||||
SCRIPT
|
||||
chmod +x "$TMP_DIR/dnsmasq"
|
||||
! ss_require_supported_dnsmasq
|
||||
ss_spec_assert_eq "$(ss_dnsmasq_check_error)" 'dnsmasq_version_unknown'
|
||||
rm -f "$TMP_DIR/dnsmasq"
|
||||
ORIGINAL_PATH="$PATH"
|
||||
PATH="$TMP_DIR"
|
||||
! ss_require_supported_dnsmasq
|
||||
PATH="$ORIGINAL_PATH"
|
||||
ss_spec_assert_eq "$(ss_dnsmasq_check_error)" 'dnsmasq_binary_not_found'
|
||||
)
|
||||
|
||||
ss_case_identity() (
|
||||
set -eu
|
||||
TMP_DIR="$(ss_spec_tmpdir)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM
|
||||
SS_IDENTITY_DIR="$TMP_DIR/identity"
|
||||
SS_IDENTITY_FILE="$SS_IDENTITY_DIR/identity.env"
|
||||
export SS_IDENTITY_DIR SS_IDENTITY_FILE
|
||||
# shellcheck disable=SC1091
|
||||
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/identity.sh"
|
||||
|
||||
ss_spec_assert_eq "$(ss_identity_normalize_mac 'MAC=AA:BB:CC:DD:EE:FF')" 'aa:bb:cc:dd:ee:ff'
|
||||
ss_identity_valid_mac 'aa:bb:cc:dd:ee:ff'
|
||||
for mac in '' '00:00:00:00:00:00' 'ff:ff:ff:ff:ff:ff' 'aa:bb:cc:dd:ee'; do
|
||||
! ss_identity_valid_mac "$mac"
|
||||
done
|
||||
ss_spec_assert_eq "$(ss_identity_hex_to_mac 'AABBCCDDEEFF')" 'aa:bb:cc:dd:ee:ff'
|
||||
ss_spec_assert_eq "$(ss_identity_profile_code 'iptime,ax3000sm')" 'iptime_ax3000sm'
|
||||
ss_spec_assert_eq "$(ss_identity_profile_code 'glinet,gl-mt300n-v2')" 'gl_mt300n_v2'
|
||||
ss_spec_assert_eq "$(ss_identity_profile_code 'xiaomi,mi-router-ax3000t')" 'xiaomi_mi_router_ax3000t'
|
||||
ss_spec_assert_eq "$(ss_identity_profile_code 'smartsafehub,router')" 'smartsafehub'
|
||||
ss_spec_assert_eq "$(ss_identity_device_code_from_board 'iptime,ax3000sm')" 'iptime-ax3000sm'
|
||||
ss_spec_assert_eq "$(ss_identity_device_code_from_board 'glinet,gl-mt300n-v2')" 'gl-mt300n-v2'
|
||||
ss_spec_assert_eq "$(ss_identity_device_code_from_board 'xiaomi,mi-router-ax3000t')" 'xiaomi-ax3000t'
|
||||
ss_spec_assert_eq "$(ss_identity_device_code_from_board 'xiaomi,mi-router-ax3000t-ubootmod')" 'xiaomi-ax3000t'
|
||||
! ss_identity_device_code_from_board 'vendor,unsupported-router'
|
||||
ss_identity_valid_device_code 'xiaomi-ax3000t'
|
||||
! ss_identity_valid_device_code 'Xiaomi AX3000T'
|
||||
|
||||
SS_SMARTSAFEHUB_FIRMWARE_JSON="$TMP_DIR/firmware.json"
|
||||
export SS_SMARTSAFEHUB_FIRMWARE_JSON
|
||||
printf '%s\n' '{"device_code":"xiaomi-ax3000t"}' >"$SS_SMARTSAFEHUB_FIRMWARE_JSON"
|
||||
MOCK_DEVICE_CODE='xiaomi-ax3000t'
|
||||
jsonfilter() { printf '%s\n' "$MOCK_DEVICE_CODE"; }
|
||||
ss_identity_board_name() { printf '%s' 'iptime,ax3000sm'; }
|
||||
ss_identity_refresh_device_code
|
||||
ss_spec_assert_eq "$SS_DEVICE_CODE" 'xiaomi-ax3000t'
|
||||
ss_spec_assert_eq "$SS_DEVICE_CODE_SOURCE" 'smartsafehub_firmware'
|
||||
|
||||
MOCK_DEVICE_CODE='invalid device code'
|
||||
ss_identity_refresh_device_code
|
||||
ss_spec_assert_eq "$SS_DEVICE_CODE" 'iptime-ax3000sm'
|
||||
ss_spec_assert_eq "$SS_DEVICE_CODE_SOURCE" 'board'
|
||||
|
||||
rm -f "$SS_SMARTSAFEHUB_FIRMWARE_JSON"
|
||||
ss_identity_board_name() { printf '%s' 'vendor,unsupported-router'; }
|
||||
ss_identity_refresh_device_code
|
||||
ss_spec_assert_eq "$SS_DEVICE_CODE" ''
|
||||
ss_spec_assert_eq "$SS_DEVICE_CODE_SOURCE" 'unknown'
|
||||
|
||||
ss_identity_board_name() { printf '%s' 'iptime,ax3000sm'; }
|
||||
ss_identity_try_mtd_factory_mac() { printf '%s' 'aa:bb:cc:dd:ee:ff|mtd:Factory:0x4:mac'; }
|
||||
ss_identity_detect_primary_mac() { return 1; }
|
||||
ss_identity_compute_physical 'ipTIME AX3000SM' 'aarch64'
|
||||
ss_spec_assert_eq "$SS_IDENTITY_PROVIDER" 'factory_mac'
|
||||
ss_spec_assert_eq "$SS_IDENTITY_SOURCE" 'mtd:Factory:0x4:mac'
|
||||
ss_spec_assert_eq "$SS_IDENTITY_STRENGTH" 'hardware_soft'
|
||||
ss_spec_assert_eq "$SS_IDENTITY_PROFILE" 'iptime_ax3000sm'
|
||||
ss_identity_valid_sha256 "$SS_IDENTITY_VALUE_SHA256"
|
||||
ss_identity_valid_sha256 "$SS_PHYSICAL_FINGERPRINT"
|
||||
|
||||
SS_INSTALLATION_ID='11111111-2222-3333-4444-555555555555'
|
||||
SS_INSTALLATION_SECRET='0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
|
||||
SS_IDENTITY_CREATED_AT='1788090000'
|
||||
SS_IDENTITY_UPDATED_AT='1788090001'
|
||||
ss_identity_write_env
|
||||
ss_spec_assert_nonempty "$SS_IDENTITY_FILE"
|
||||
identity_mode="$(LC_ALL=C ls -ld "$SS_IDENTITY_FILE" | awk '{ print substr($1, 2, 9) }')"
|
||||
ss_spec_assert_eq "$identity_mode" 'rw-------'
|
||||
expected_fingerprint="$SS_PHYSICAL_FINGERPRINT"
|
||||
SS_PHYSICAL_FINGERPRINT=''
|
||||
SS_IDENTITY_PROVIDER=''
|
||||
SS_IDENTITY_SOURCE=''
|
||||
SS_IDENTITY_STRENGTH=''
|
||||
SS_IDENTITY_PROFILE=''
|
||||
SS_INSTALLATION_ID=''
|
||||
ss_identity_load
|
||||
ss_spec_assert_eq "$SS_PHYSICAL_FINGERPRINT" "$expected_fingerprint"
|
||||
ss_spec_assert_eq "$SS_IDENTITY_PROVIDER" 'factory_mac'
|
||||
ss_spec_assert_eq "$SS_INSTALLATION_ID" '11111111-2222-3333-4444-555555555555'
|
||||
|
||||
SYNCED=0
|
||||
STATUS_SYNCED=0
|
||||
ss_identity_sync_uci() { SYNCED=1; }
|
||||
ss_identity_status_set() { STATUS_SYNCED=1; }
|
||||
ss_identity_create() { return 97; }
|
||||
ss_identity_ensure 'ipTIME AX3000SM' 'aarch64'
|
||||
ss_spec_assert_eq "$SYNCED" '1'
|
||||
ss_spec_assert_eq "$STATUS_SYNCED" '1'
|
||||
)
|
||||
|
||||
ss_case_resolve_payload() (
|
||||
set -eu
|
||||
TMP_DIR="$(ss_spec_tmpdir)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM
|
||||
# shellcheck disable=SC1091
|
||||
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
|
||||
ss_detect_device_model() { printf '%s' 'Test Router'; }
|
||||
ss_detect_device_vendor() { printf '%s' 'TestVendor'; }
|
||||
ss_detect_device_arch() { printf '%s' 'test_arch'; }
|
||||
ss_detect_device_memory_mb() { printf '%s' '128'; }
|
||||
SS_PHYSICAL_FINGERPRINT='test-fingerprint'
|
||||
SS_FINGERPRINT_VERSION='1'
|
||||
SS_IDENTITY_PROVIDER='factory_mac'
|
||||
SS_IDENTITY_SOURCE='test-source'
|
||||
SS_IDENTITY_STRENGTH='hardware_soft'
|
||||
SS_IDENTITY_PROFILE='test-profile'
|
||||
SS_DEVICE_CODE='test-router'
|
||||
SS_DEVICE_CODE_SOURCE='board'
|
||||
SS_INSTALLATION_ID='test-installation'
|
||||
ss_identity_ensure() { return 0; }
|
||||
is_valid_integer() {
|
||||
case "$1" in
|
||||
'' | *[!0-9]*) return 1 ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
}
|
||||
ss_status_set() { :; }
|
||||
ss_license_key='test-license'
|
||||
SS_VERSION_FILE="$TMP_DIR/version"
|
||||
export SS_VERSION_FILE
|
||||
printf '%s\n' '0.3.18-r1' >"$SS_VERSION_FILE"
|
||||
payload="$TMP_DIR/resolve-request.json"
|
||||
ss_write_resolve_payload "$payload"
|
||||
ss_spec_assert_file_contains "$payload" ' "safeshield_version": "0.3.18-r1"'
|
||||
! grep -Eq '^ "safeshield_version"' "$payload"
|
||||
ss_spec_assert_file_contains "$payload" '"license_key": "test-license"'
|
||||
ss_spec_assert_file_contains "$payload" '"physical_fingerprint": "test-fingerprint"'
|
||||
ss_spec_assert_file_contains "$payload" '"fingerprint_version": 1'
|
||||
ss_spec_assert_file_contains "$payload" '"identity_provider": "factory_mac"'
|
||||
ss_spec_assert_file_contains "$payload" '"identity_source": "test-source"'
|
||||
ss_spec_assert_file_contains "$payload" '"identity_strength": "hardware_soft"'
|
||||
ss_spec_assert_file_contains "$payload" '"identity_profile": "test-profile"'
|
||||
ss_spec_assert_file_contains "$payload" '"device_code": "test-router"'
|
||||
ss_spec_assert_file_contains "$payload" '"device_code_source": "board"'
|
||||
ss_spec_assert_file_contains "$payload" '"installation_id": "test-installation"'
|
||||
ss_spec_assert_file_contains "$payload" '"vendor": "TestVendor"'
|
||||
ss_spec_assert_file_contains "$payload" '"model": "Test Router"'
|
||||
ss_spec_assert_file_contains "$payload" '"arch": "test_arch"'
|
||||
ss_spec_assert_file_contains "$payload" '"memory_mb": 128'
|
||||
|
||||
ss_detect_device_memory_mb() { printf '%s' 'invalid-memory'; }
|
||||
invalid_memory_payload="$TMP_DIR/resolve-request-invalid-memory.json"
|
||||
ss_write_resolve_payload "$invalid_memory_payload"
|
||||
ss_spec_assert_file_contains "$invalid_memory_payload" '"memory_mb": 0'
|
||||
|
||||
SS_VERSION_FILE="$TMP_DIR/missing-version"
|
||||
export SS_VERSION_FILE
|
||||
fallback_payload="$TMP_DIR/resolve-request-fallback.json"
|
||||
ss_write_resolve_payload "$fallback_payload"
|
||||
ss_spec_assert_file_contains "$fallback_payload" ' "safeshield_version": "unknown"'
|
||||
! grep -Eq '^ "safeshield_version"' "$fallback_payload"
|
||||
)
|
||||
|
||||
ss_case_rpcd_contract() (
|
||||
set -eu
|
||||
ENTRY="$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield.uc"
|
||||
ACL="$SS_SPEC_ROOT/files/usr/share/rpcd/acl.d/safeshield.json"
|
||||
MAKEFILE="$SS_SPEC_ROOT/Makefile"
|
||||
STATISTICS_MODULE="$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield/statistics.uc"
|
||||
[ -f "$ENTRY" ]
|
||||
[ -f "$ACL" ]
|
||||
ss_spec_assert_file_contains "$ENTRY" "unshift(REQUIRE_SEARCH_PATH, '/usr/share/rpcd/ucode/safeshield/*.uc');"
|
||||
for module in status config refresh rules license statistics; do
|
||||
ss_spec_assert_file_contains "$ENTRY" "let $module = require('$module');"
|
||||
done
|
||||
for method in status config statistics config_update set_enabled refresh rules_list rule_add rule_delete license_get license_update; do
|
||||
ss_spec_assert_file_contains "$ENTRY" " $method: {"
|
||||
ss_spec_assert_file_contains "$ACL" "\"$method\""
|
||||
done
|
||||
ss_spec_assert_file_contains "$MAKEFILE" './files/usr/share/rpcd/ucode/safeshield/*.uc'
|
||||
ss_spec_assert_file_contains "$MAKEFILE" './files/usr/share/rpcd/acl.d/safeshield.json'
|
||||
ss_spec_assert_file_contains "$STATISTICS_MODULE" 'effective_snapshot_interval_s: to_int(data.snapshot_interval_s, snapshot_interval_s)'
|
||||
)
|
||||
|
||||
ss_case_status_state() (
|
||||
set -eu
|
||||
TMP_DIR="$(ss_spec_tmpdir)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM
|
||||
RUNNING_STATUS_FILE="$TMP_DIR/status.json"
|
||||
SS_DNSMASQ_DIR="$TMP_DIR/dnsmasq.d"
|
||||
SS_BLOCKLIST_FILE="$SS_DNSMASQ_DIR/safeshield.blocklist"
|
||||
SS_PREV_BLOCKLIST_GZ="$TMP_DIR/previous.gz"
|
||||
export RUNNING_STATUS_FILE SS_DNSMASQ_DIR SS_BLOCKLIST_FILE SS_PREV_BLOCKLIST_GZ
|
||||
# shellcheck disable=SC1091
|
||||
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/status.sh"
|
||||
CALLS="$TMP_DIR/calls"
|
||||
: >"$CALLS"
|
||||
record() { printf '%s\t%s\t%s\n' "$1" "${2:-}" "${3:-}" >>"$CALLS"; }
|
||||
ss_status_set() { record set "$1" "$2"; }
|
||||
ss_status_add_error() { record error "$1"; }
|
||||
ss_status_apply() {
|
||||
record apply "$1" "${2:-}"
|
||||
return 0
|
||||
}
|
||||
ss_now() { printf '%s' '1000'; }
|
||||
is_valid_integer() {
|
||||
case "$1" in '' | *[!0-9]*) return 1 ;; esac
|
||||
[ "$1" -ge 0 ] 2>/dev/null
|
||||
}
|
||||
ss_status_mark_failure 'artifact_download_failed'
|
||||
grep -F "$(printf 'set\tstatus\terror')" "$CALLS" >/dev/null
|
||||
grep -F "$(printf 'set\tlast_failure\t1000')" "$CALLS" >/dev/null
|
||||
grep -F "$(printf 'set\tlast_result\terror')" "$CALLS" >/dev/null
|
||||
grep -F "$(printf 'set\tlast_error_code\tartifact_download_failed')" "$CALLS" >/dev/null
|
||||
grep -F "$(printf 'error\tartifact_download_failed')" "$CALLS" >/dev/null
|
||||
: >"$CALLS"
|
||||
ss_status_mark_success 300
|
||||
grep -F "$(printf 'apply\tclear_messages')" "$CALLS" >/dev/null
|
||||
grep -F "$(printf 'set\tstatus\tready')" "$CALLS" >/dev/null
|
||||
grep -F "$(printf 'set\tstage\tdone')" "$CALLS" >/dev/null
|
||||
grep -F "$(printf 'set\tlast_success\t1000')" "$CALLS" >/dev/null
|
||||
grep -F "$(printf 'set\tnext_refresh_at\t1300')" "$CALLS" >/dev/null
|
||||
: >"$CALLS"
|
||||
ss_status_mark_success invalid
|
||||
! grep -F "$(printf 'set\tnext_refresh_at')" "$CALLS" >/dev/null
|
||||
: >"$CALLS"
|
||||
ss_status_reset_health_fields
|
||||
for key in health_dnsmasq_binary health_dns_runtime health_artifact_download dnsmasq_min_version; do
|
||||
grep -F "$(printf 'set\t%s\t' "$key")" "$CALLS" >/dev/null
|
||||
done
|
||||
: >"$CALLS"
|
||||
ss_status_reset_blocklist_fields
|
||||
for key in blocklist_installed blocklist_file_size_kb blocklist_verification_ok blocklist_backup_available; do
|
||||
grep -F "$(printf 'set\t%s\t' "$key")" "$CALLS" >/dev/null
|
||||
done
|
||||
: >"$CALLS"
|
||||
ss_status_reset_artifact_fields
|
||||
for key in license_plan artifact_tier artifact_source_count artifact_allow_source_count; do
|
||||
grep -F "$(printf 'set\t%s\t' "$key")" "$CALLS" >/dev/null
|
||||
done
|
||||
mkdir -p "$SS_DNSMASQ_DIR"
|
||||
printf '%s\n' 'address=/ads.example/#' >"$TMP_DIR/new.blocklist"
|
||||
ss_sync_path() { :; }
|
||||
ss_install_blocklist_atomic "$TMP_DIR/new.blocklist" "$SS_BLOCKLIST_FILE"
|
||||
ss_spec_assert_eq "$(cat "$SS_BLOCKLIST_FILE")" 'address=/ads.example/#'
|
||||
command_exists() { command -v "$1" >/dev/null 2>&1; }
|
||||
ss_export_existing_blocklist
|
||||
ss_spec_assert_nonempty "$SS_PREV_BLOCKLIST_GZ"
|
||||
rm -f "$SS_BLOCKLIST_FILE"
|
||||
ss_restore_previous_blocklist
|
||||
ss_spec_assert_eq "$(cat "$SS_BLOCKLIST_FILE")" 'address=/ads.example/#'
|
||||
)
|
||||
|
||||
ss_case_status_version() (
|
||||
set -eu
|
||||
make_version="$(sed -n 's/^PKG_VERSION:=//p' "$SS_SPEC_ROOT/Makefile")"
|
||||
make_release="$(sed -n 's/^PKG_RELEASE:=//p' "$SS_SPEC_ROOT/Makefile")"
|
||||
init_version="$(sed -n "s/^readonly PKG_VERSION='\([^']*\)'/\1/p" "$SS_SPEC_ROOT/files/etc/init.d/safeshield")"
|
||||
[ -n "$make_version" ]
|
||||
[ -n "$make_release" ]
|
||||
[ -n "$init_version" ]
|
||||
ss_spec_assert_eq "$init_version" "${make_version}-r${make_release}"
|
||||
ss_spec_assert_file_contains "$SS_SPEC_ROOT/files/etc/init.d/safeshield" "printf '%s\\n' \"\$PKG_VERSION\""
|
||||
ss_spec_assert_file_contains "$SS_SPEC_ROOT/Makefile" "echo '\$(PKG_VERSION)-r\$(PKG_RELEASE)' > \$(1)/usr/lib/safeshield/version"
|
||||
ss_spec_assert_file_contains "$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield/core.uc" 'fs.readfile(`/usr/lib/${PKG_NAME}/version`)'
|
||||
ss_spec_assert_file_contains "$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield/status.uc" 'version: PKG_VERSION,'
|
||||
ss_spec_assert_file_contains "$SS_SPEC_ROOT/files/usr/lib/safeshield/core.sh" 'safeshield_upgrade_required'
|
||||
ss_spec_assert_file_contains "$SS_SPEC_ROOT/files/usr/lib/safeshield/core.sh" 'failure_code="$(ss_resolve_error_code)"'
|
||||
ss_spec_assert_file_contains "$SS_SPEC_ROOT/files/usr/lib/safeshield/core.sh" 'failure_code="$(ss_artifact_download_error_code)"'
|
||||
ss_spec_assert_file_contains "$SS_SPEC_ROOT/files/usr/lib/safeshield/core.sh" 'if [ "$failure_code" != "safeshield_upgrade_required" ]; then'
|
||||
)
|
||||
Executable
+535
@@ -0,0 +1,535 @@
|
||||
#!/bin/sh
|
||||
# shellcheck shell=sh
|
||||
|
||||
ss_case_refresh_state_machine() (
|
||||
set -eu
|
||||
TMP="$(ss_spec_tmpdir)"
|
||||
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
||||
CORE="$SS_SPEC_ROOT/files/usr/lib/safeshield/core.sh"
|
||||
HARNESS="$TMP/core-state-machine.sh"
|
||||
sed -n '/^safeshield_apply_local_rules() {/,${p;}' "$CORE" >"$HARNESS"
|
||||
# shellcheck disable=SC1090
|
||||
. "$HARNESS"
|
||||
|
||||
SS_TMP_DIR="$TMP/runtime"
|
||||
SS_DNSMASQ_DIR="$TMP/dnsmasq.d"
|
||||
SS_BLOCKLIST_FILE="$SS_DNSMASQ_DIR/safeshield.blocklist"
|
||||
SS_INACTIVE_BLOCKLIST_FILE="$SS_TMP_DIR/inactive.blocklist"
|
||||
SS_PREV_BLOCKLIST_GZ="$TMP/previous.gz"
|
||||
mkdir -p "$SS_TMP_DIR" "$SS_DNSMASQ_DIR"
|
||||
CALLS="$TMP/calls"
|
||||
: >"$CALLS"
|
||||
|
||||
record() { printf '%s\n' "$*" >>"$CALLS"; }
|
||||
log_info() { :; }
|
||||
log_warn() { :; }
|
||||
log_error() { :; }
|
||||
log_ok() { :; }
|
||||
ss_status_prepare_refresh() { record status_prepare_refresh; }
|
||||
ss_status_set() { record "status_set $1 ${2:-}"; }
|
||||
ss_status_set_now() { record "status_set_now $1"; }
|
||||
ss_status_add_error() { record "status_error $1"; }
|
||||
ss_status_mark_failure() { record "failure $1"; }
|
||||
ss_status_mark_success() { record "success $1"; }
|
||||
ss_load_config() {
|
||||
ss_enabled=1
|
||||
export ss_initial_dnsmasq_restart=0
|
||||
ss_apply_local_overrides=1
|
||||
return 0
|
||||
}
|
||||
ss_config_get() {
|
||||
case "$2" in
|
||||
refresh_interval_s) printf '%s\n' '300' ;;
|
||||
*) printf '%s\n' "${3:-}" ;;
|
||||
esac
|
||||
}
|
||||
is_valid_integer() { case "$1" in '' | *[!0-9]*) return 1 ;; *) return 0 ;; esac }
|
||||
ss_sync_detected_device_config() { record sync_device_config; }
|
||||
ss_identity_ensure() {
|
||||
record identity_ensure
|
||||
[ "${FAIL_STAGE:-}" != 'identity' ]
|
||||
}
|
||||
ss_detect_device_model() { printf '%s\n' 'Test Router'; }
|
||||
ss_detect_device_arch() { printf '%s\n' 'test-arch'; }
|
||||
ss_should_stop() { return 1; }
|
||||
ss_mkdirs() {
|
||||
record mkdirs
|
||||
[ "${FAIL_STAGE:-}" != 'mkdir' ]
|
||||
}
|
||||
ss_clean_tmp() { record clean_tmp; }
|
||||
ss_require_supported_dnsmasq() {
|
||||
record require_dnsmasq
|
||||
[ "${FAIL_STAGE:-}" != 'dnsmasq_version' ]
|
||||
}
|
||||
ss_dnsmasq_check_error() { printf '%s\n' 'dnsmasq_version_unsupported'; }
|
||||
ss_ensure_dnsmasq_confdir() {
|
||||
record ensure_confdir
|
||||
[ "${FAIL_STAGE:-}" != 'confdir' ]
|
||||
}
|
||||
ss_export_existing_blocklist() {
|
||||
record export_blocklist
|
||||
printf '%s\n' 'backup' >"$SS_PREV_BLOCKLIST_GZ"
|
||||
return 0
|
||||
}
|
||||
DNSMASQ_CALL=0
|
||||
dnsmasq_restart() {
|
||||
DNSMASQ_CALL=$((DNSMASQ_CALL + 1))
|
||||
record "dnsmasq_restart $DNSMASQ_CALL"
|
||||
[ "${FAIL_STAGE:-}" != 'restart' ]
|
||||
}
|
||||
ss_resolve_artifact() {
|
||||
record resolve_artifact
|
||||
[ "${FAIL_STAGE:-}" != 'resolve' ]
|
||||
}
|
||||
ss_resolve_error_code() { printf '%s\n' "${RESOLVE_ERROR_CODE:-artifact_resolve_failed}"; }
|
||||
ss_download_api_artifacts() {
|
||||
record download_artifacts
|
||||
[ "${FAIL_STAGE:-}" != 'download' ]
|
||||
}
|
||||
ss_artifact_download_error_code() { printf '%s\n' "${DOWNLOAD_ERROR_CODE:-artifact_download_failed}"; }
|
||||
ss_prepare_local_rule_files() {
|
||||
record prepare_local_rules
|
||||
[ "${FAIL_STAGE:-}" != 'local_rules' ]
|
||||
}
|
||||
ss_merge_lists() {
|
||||
record merge_lists
|
||||
[ "${FAIL_STAGE:-}" != 'merge' ]
|
||||
}
|
||||
ss_install_blocklist() {
|
||||
record install_blocklist
|
||||
[ "${FAIL_STAGE:-}" != 'install' ]
|
||||
}
|
||||
check_dns_runtime() {
|
||||
record check_dns_runtime
|
||||
[ "${FAIL_STAGE:-}" != 'runtime' ]
|
||||
}
|
||||
check_blocklist_applied_multi_with_stats() {
|
||||
record "verify_blocklist $*"
|
||||
[ "${FAIL_STAGE:-}" != 'verify' ]
|
||||
}
|
||||
ss_restore_and_restart() { record restore_and_restart; }
|
||||
ss_refresh_lock_open() {
|
||||
record lock_open
|
||||
[ "${LOCK_AVAILABLE:-1}" = '1' ]
|
||||
}
|
||||
ss_refresh_lock_close() { record lock_close; }
|
||||
ss_local_rules_fingerprint_from_tmp() { printf '%s\n' 'fingerprint-new'; }
|
||||
ss_local_rules_state_write() { record "local_state_write $1"; }
|
||||
ss_abort_refresh() {
|
||||
record abort_refresh
|
||||
return 130
|
||||
}
|
||||
|
||||
assert_refresh_failure() {
|
||||
stage="$1"
|
||||
code="$2"
|
||||
expect_restore="$3"
|
||||
FAIL_STAGE="$stage"
|
||||
RESOLVE_ERROR_CODE='artifact_resolve_failed'
|
||||
DOWNLOAD_ERROR_CODE='artifact_download_failed'
|
||||
DNSMASQ_CALL=0
|
||||
: >"$CALLS"
|
||||
if safeshield_force_download; then
|
||||
return 1
|
||||
fi
|
||||
ss_spec_assert_file_line "$CALLS" "failure $code"
|
||||
ss_spec_assert_file_line "$CALLS" 'lock_close'
|
||||
if [ "$expect_restore" = '1' ]; then
|
||||
ss_spec_assert_file_line "$CALLS" 'restore_and_restart'
|
||||
else
|
||||
! grep -Fx 'restore_and_restart' "$CALLS" >/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
FAIL_STAGE=''
|
||||
LOCK_AVAILABLE=1
|
||||
: >"$CALLS"
|
||||
safeshield_force_download
|
||||
ss_spec_assert_file_line "$CALLS" 'status_set stage resolve_api'
|
||||
ss_spec_assert_file_line "$CALLS" 'status_set stage download_artifact'
|
||||
ss_spec_assert_file_line "$CALLS" 'status_set stage merge'
|
||||
ss_spec_assert_file_line "$CALLS" 'status_set stage install'
|
||||
ss_spec_assert_file_line "$CALLS" 'status_set stage restart_dnsmasq'
|
||||
ss_spec_assert_file_line "$CALLS" 'status_set stage runtime_check'
|
||||
ss_spec_assert_file_line "$CALLS" 'status_set stage blocklist_verify'
|
||||
ss_spec_assert_file_line "$CALLS" 'success 300'
|
||||
ss_spec_assert_file_line "$CALLS" 'local_state_write fingerprint-new'
|
||||
ss_spec_assert_file_line "$CALLS" 'lock_close'
|
||||
! grep -Fx 'restore_and_restart' "$CALLS" >/dev/null
|
||||
|
||||
assert_refresh_failure merge merge_failed 1
|
||||
assert_refresh_failure install install_failed 1
|
||||
assert_refresh_failure restart dnsmasq_restart_failed 1
|
||||
assert_refresh_failure runtime dns_runtime_check_failed 1
|
||||
assert_refresh_failure verify blocklist_verification_failed 1
|
||||
|
||||
FAIL_STAGE='resolve'
|
||||
RESOLVE_ERROR_CODE='safeshield_upgrade_required'
|
||||
: >"$CALLS"
|
||||
! safeshield_force_download
|
||||
ss_spec_assert_file_line "$CALLS" 'failure safeshield_upgrade_required'
|
||||
! grep -Fx 'restore_and_restart' "$CALLS" >/dev/null
|
||||
|
||||
FAIL_STAGE='download'
|
||||
DOWNLOAD_ERROR_CODE='safeshield_upgrade_required'
|
||||
: >"$CALLS"
|
||||
! safeshield_force_download
|
||||
ss_spec_assert_file_line "$CALLS" 'failure safeshield_upgrade_required'
|
||||
! grep -Fx 'restore_and_restart' "$CALLS" >/dev/null
|
||||
|
||||
FAIL_STAGE=''
|
||||
LOCK_AVAILABLE=0
|
||||
: >"$CALLS"
|
||||
safeshield_force_download
|
||||
ss_spec_assert_file_line "$CALLS" 'lock_open'
|
||||
! grep -F 'status_prepare_refresh' "$CALLS" >/dev/null
|
||||
)
|
||||
|
||||
ss_case_local_apply_state_machine() (
|
||||
set -eu
|
||||
TMP="$(ss_spec_tmpdir)"
|
||||
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
||||
CORE="$SS_SPEC_ROOT/files/usr/lib/safeshield/core.sh"
|
||||
HARNESS="$TMP/local-apply.sh"
|
||||
sed -n '/^safeshield_apply_local_rules() {/,/^safeshield_force_download() {/p' "$CORE" | sed '$d' >"$HARNESS"
|
||||
# shellcheck disable=SC1090
|
||||
. "$HARNESS"
|
||||
|
||||
SS_TMP_DIR="$TMP/runtime"
|
||||
SS_DNSMASQ_DIR="$TMP/dnsmasq.d"
|
||||
SS_BLOCKLIST_FILE="$SS_DNSMASQ_DIR/safeshield.blocklist"
|
||||
SS_PREV_BLOCKLIST_GZ="$TMP/previous.gz"
|
||||
SS_INACTIVE_BLOCKLIST_FILE="$SS_TMP_DIR/inactive.blocklist"
|
||||
mkdir -p "$SS_TMP_DIR" "$SS_DNSMASQ_DIR"
|
||||
CALLS="$TMP/calls"
|
||||
: >"$CALLS"
|
||||
|
||||
record() { printf '%s\n' "$*" >>"$CALLS"; }
|
||||
log_info() { :; }
|
||||
log_warn() { :; }
|
||||
log_error() { :; }
|
||||
log_ok() { :; }
|
||||
sleep() { :; }
|
||||
ss_load_config() {
|
||||
ss_enabled="${MOCK_ENABLED:-1}"
|
||||
export ss_apply_local_overrides="${MOCK_OVERRIDES:-1}"
|
||||
return 0
|
||||
}
|
||||
ss_mkdirs() { return 0; }
|
||||
ss_refresh_lock_open_wait() {
|
||||
record "lock_wait $1"
|
||||
return "${LOCK_RC:-0}"
|
||||
}
|
||||
ss_refresh_lock_close() { record lock_close; }
|
||||
ss_cached_api_sources_available() { [ "${CACHE_AVAILABLE:-1}" = '1' ]; }
|
||||
ss_status_set() { record "status_set $1 ${2:-}"; }
|
||||
ss_status_set_now() { record "status_set_now $1"; }
|
||||
ss_status_add_error() { record "status_error $1"; }
|
||||
ss_prepare_local_rule_files() {
|
||||
record prepare_local_rules
|
||||
[ "${LOCAL_FAIL:-}" != 'local_rules' ]
|
||||
}
|
||||
ss_local_rules_fingerprint_from_tmp() { printf '%s\n' "${NEW_FINGERPRINT:-fingerprint-new}"; }
|
||||
ss_local_rules_state_read() { [ -n "${APPLIED_FINGERPRINT:-}" ] && printf '%s\n' "$APPLIED_FINGERPRINT"; }
|
||||
ss_local_rules_state_write() { record "local_state_write $1"; }
|
||||
ss_require_supported_dnsmasq() { [ "${LOCAL_FAIL:-}" != 'dnsmasq_version' ]; }
|
||||
ss_dnsmasq_check_error() { printf '%s\n' 'dnsmasq_version_unsupported'; }
|
||||
ss_ensure_dnsmasq_confdir() {
|
||||
record ensure_confdir
|
||||
[ "${LOCAL_FAIL:-}" != 'confdir' ]
|
||||
}
|
||||
ss_export_existing_blocklist() {
|
||||
printf '%s\n' backup >"$SS_PREV_BLOCKLIST_GZ"
|
||||
return 0
|
||||
}
|
||||
ss_merge_lists() {
|
||||
record merge_lists
|
||||
[ "${LOCAL_FAIL:-}" != 'merge' ]
|
||||
}
|
||||
dnsmasq_restart() {
|
||||
record dnsmasq_restart
|
||||
[ "${LOCAL_FAIL:-}" != 'restart' ]
|
||||
}
|
||||
check_dns_runtime() {
|
||||
record check_dns_runtime
|
||||
[ "${LOCAL_FAIL:-}" != 'runtime' ]
|
||||
}
|
||||
check_blocklist_applied_multi_with_stats() {
|
||||
record "verify_blocklist $*"
|
||||
[ "${LOCAL_FAIL:-}" != 'verify' ]
|
||||
}
|
||||
ss_restore_and_restart() { record restore_and_restart; }
|
||||
ss_mark_local_apply_failure() { record "local_failure $1"; }
|
||||
ss_mark_local_apply_success() { record local_success; }
|
||||
FALLBACK_RC=0
|
||||
safeshield_force_download() {
|
||||
record fallback_refresh
|
||||
return "$FALLBACK_RC"
|
||||
}
|
||||
|
||||
CACHE_AVAILABLE=0
|
||||
LOCK_RC=0
|
||||
LOCAL_FAIL=''
|
||||
: >"$CALLS"
|
||||
safeshield_apply_local_rules
|
||||
ss_spec_assert_file_line "$CALLS" 'fallback_refresh'
|
||||
ss_spec_assert_file_line "$CALLS" 'lock_close'
|
||||
|
||||
CACHE_AVAILABLE=1
|
||||
LOCK_RC=1
|
||||
: >"$CALLS"
|
||||
! safeshield_apply_local_rules
|
||||
ss_spec_assert_file_line "$CALLS" 'local_failure local_apply_lock_timeout'
|
||||
|
||||
LOCK_RC=130
|
||||
: >"$CALLS"
|
||||
set +e
|
||||
safeshield_apply_local_rules
|
||||
rc=$?
|
||||
set -e
|
||||
ss_spec_assert_eq "$rc" '130'
|
||||
! grep -F 'local_failure' "$CALLS" >/dev/null
|
||||
|
||||
LOCK_RC=0
|
||||
APPLIED_FINGERPRINT='fingerprint-new'
|
||||
NEW_FINGERPRINT='fingerprint-new'
|
||||
printf '%s\n' 'safeshield-block=/cached.example/' >"$SS_BLOCKLIST_FILE"
|
||||
: >"$CALLS"
|
||||
safeshield_apply_local_rules
|
||||
ss_spec_assert_file_line "$CALLS" 'status_set stage done'
|
||||
! grep -Fx 'merge_lists' "$CALLS" >/dev/null
|
||||
! grep -Fx 'dnsmasq_restart' "$CALLS" >/dev/null
|
||||
|
||||
APPLIED_FINGERPRINT='fingerprint-old'
|
||||
NEW_FINGERPRINT='fingerprint-new'
|
||||
for scenario in merge restart runtime verify; do
|
||||
LOCAL_FAIL="$scenario"
|
||||
: >"$CALLS"
|
||||
! safeshield_apply_local_rules
|
||||
case "$scenario" in
|
||||
merge) code='local_merge_failed' ;;
|
||||
restart) code='local_dnsmasq_restart_failed' ;;
|
||||
runtime) code='local_dns_runtime_check_failed' ;;
|
||||
verify) code='local_blocklist_verification_failed' ;;
|
||||
esac
|
||||
ss_spec_assert_file_line "$CALLS" "local_failure $code"
|
||||
ss_spec_assert_file_line "$CALLS" 'restore_and_restart'
|
||||
ss_spec_assert_file_line "$CALLS" 'lock_close'
|
||||
done
|
||||
|
||||
LOCAL_FAIL=''
|
||||
: >"$CALLS"
|
||||
safeshield_apply_local_rules
|
||||
ss_spec_assert_file_line "$CALLS" 'merge_lists'
|
||||
ss_spec_assert_file_line "$CALLS" 'dnsmasq_restart'
|
||||
ss_spec_assert_file_line "$CALLS" 'check_dns_runtime'
|
||||
ss_spec_assert_file_line "$CALLS" 'verify_blocklist 5 3'
|
||||
ss_spec_assert_file_line "$CALLS" 'local_state_write fingerprint-new'
|
||||
ss_spec_assert_file_line "$CALLS" 'local_success'
|
||||
! grep -Fx 'restore_and_restart' "$CALLS" >/dev/null
|
||||
)
|
||||
|
||||
ss_case_refresh_locking() (
|
||||
set -eu
|
||||
TMP="$(ss_spec_tmpdir)"
|
||||
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
||||
RUNNING_STATUS_FILE="$TMP/status.json"
|
||||
SS_REFRESH_LOCK="$TMP/safeshield-refresh.lock"
|
||||
SS_REFRESH_LOCK_FD=9
|
||||
MOCK_FLOCK_DIR="$TMP/mock-flock.lock"
|
||||
|
||||
# Keep this regression test portable on development hosts such as macOS,
|
||||
# where flock(1) is not installed by default. The production code still
|
||||
# invokes flock -n/-u; this shim emulates those operations with atomic
|
||||
# mkdir/rmdir so contention, release, timeout, and termination paths are
|
||||
# exercised without depending on a host-specific flock binary.
|
||||
flock() {
|
||||
case "${1:-}" in
|
||||
-n)
|
||||
mkdir "$MOCK_FLOCK_DIR" 2>/dev/null
|
||||
;;
|
||||
-u)
|
||||
rmdir "$MOCK_FLOCK_DIR" 2>/dev/null || true
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 2
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/status.sh"
|
||||
is_valid_integer() { case "$1" in '' | *[!0-9]*) return 1 ;; *) return 0 ;; esac }
|
||||
ss_should_stop() { return 1; }
|
||||
|
||||
mkdir "$MOCK_FLOCK_DIR"
|
||||
! ss_refresh_lock_open
|
||||
! ss_refresh_lock_open_wait 0
|
||||
rmdir "$MOCK_FLOCK_DIR"
|
||||
|
||||
ss_refresh_lock_open
|
||||
ss_spec_assert_exists "$MOCK_FLOCK_DIR"
|
||||
ss_refresh_lock_close
|
||||
ss_spec_assert_not_exists "$MOCK_FLOCK_DIR"
|
||||
|
||||
mkdir "$MOCK_FLOCK_DIR"
|
||||
ss_should_stop() { return 0; }
|
||||
set +e
|
||||
ss_refresh_lock_open_wait 10
|
||||
rc=$?
|
||||
set -e
|
||||
rmdir "$MOCK_FLOCK_DIR"
|
||||
ss_spec_assert_eq "$rc" '130'
|
||||
)
|
||||
|
||||
ss_case_service_lifecycle() (
|
||||
set -eu
|
||||
TMP="$(ss_spec_tmpdir)"
|
||||
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
|
||||
INIT="$SS_SPEC_ROOT/files/etc/init.d/safeshield"
|
||||
HARNESS="$TMP/init-lifecycle.sh"
|
||||
sed -n '/^start_service() {/,/^reconcile_statistics() {/p' "$INIT" | sed '$d' >"$HARNESS"
|
||||
# shellcheck disable=SC1090
|
||||
. "$HARNESS"
|
||||
|
||||
export PKG_NAME='safeshield'
|
||||
SS_BLOCKLIST_FILE="$TMP/dnsmasq.d/safeshield.blocklist"
|
||||
export SS_INACTIVE_BLOCKLIST_FILE="$TMP/inactive.blocklist"
|
||||
mkdir -p "${SS_BLOCKLIST_FILE%/*}"
|
||||
CALLS="$TMP/calls"
|
||||
: >"$CALLS"
|
||||
record() { printf '%s\n' "$*" >>"$CALLS"; }
|
||||
log_error() { :; }
|
||||
log_warn() { :; }
|
||||
ss_load_config() {
|
||||
export ss_enabled="${MOCK_ENABLED:-1}"
|
||||
export ss_statistics_enabled="${MOCK_STATISTICS:-1}"
|
||||
return "${LOAD_RC:-0}"
|
||||
}
|
||||
ss_identity_ensure() {
|
||||
record identity_ensure
|
||||
return 0
|
||||
}
|
||||
ss_detect_device_model() { printf '%s\n' 'Test Router'; }
|
||||
ss_detect_device_arch() { printf '%s\n' 'test-arch'; }
|
||||
ss_mkdirs() {
|
||||
record mkdirs
|
||||
return 0
|
||||
}
|
||||
ss_statistics_request_rebaseline() {
|
||||
record request_rebaseline
|
||||
return 0
|
||||
}
|
||||
ss_statistics_configure_dnsmasq() {
|
||||
record "statistics_configure $1"
|
||||
printf '%s\n' "${STATISTICS_CHANGED:-0}"
|
||||
}
|
||||
ss_runtime_deactivate_blocklist() {
|
||||
record deactivate_blocklist
|
||||
return "${DEACTIVATE_RC:-0}"
|
||||
}
|
||||
ss_runtime_activate_blocklist() {
|
||||
record activate_blocklist
|
||||
return "${ACTIVATE_RC:-0}"
|
||||
}
|
||||
ss_status_set() { record "status_set $1 ${2:-}"; }
|
||||
ss_status_mark_failure() { record "failure $1"; }
|
||||
ss_require_supported_dnsmasq() {
|
||||
record require_dnsmasq
|
||||
return 0
|
||||
}
|
||||
ss_dnsmasq_check_error() { printf '%s\n' 'dnsmasq_version_unsupported'; }
|
||||
ss_statistics_disable_if_dnsmasq_unpatched() {
|
||||
record statistics_guard
|
||||
return 0
|
||||
}
|
||||
ss_reconcile_installed_blocklists_for_dnsmasq() {
|
||||
record reconcile_blocklists
|
||||
return "${MIGRATION_RC:-0}"
|
||||
}
|
||||
ss_ensure_dnsmasq_confdir() {
|
||||
record ensure_confdir
|
||||
return 0
|
||||
}
|
||||
dnsmasq_restart() {
|
||||
record dnsmasq_restart
|
||||
return "${DNSMASQ_RC:-0}"
|
||||
}
|
||||
procd_open_instance() { record procd_open_instance; }
|
||||
procd_set_param() { record "procd_set_param $*"; }
|
||||
procd_close_instance() { record procd_close_instance; }
|
||||
ss_statistics_add_procd_instance() { record add_statistics_instance; }
|
||||
ss_statistics_add_upload_procd_instance() { record add_statistics_upload_instance; }
|
||||
|
||||
MOCK_ENABLED=0
|
||||
MOCK_STATISTICS=1
|
||||
STATISTICS_CHANGED=1
|
||||
MIGRATION_RC=0
|
||||
DNSMASQ_RC=0
|
||||
: >"$CALLS"
|
||||
start_service
|
||||
ss_spec_assert_file_line "$CALLS" 'request_rebaseline'
|
||||
ss_spec_assert_file_line "$CALLS" 'statistics_configure 0'
|
||||
ss_spec_assert_file_line "$CALLS" 'deactivate_blocklist'
|
||||
ss_spec_assert_file_line "$CALLS" 'dnsmasq_restart'
|
||||
ss_spec_assert_file_line "$CALLS" 'status_set stage disabled'
|
||||
! grep -Fx 'procd_open_instance' "$CALLS" >/dev/null
|
||||
|
||||
printf '%s\n' 'safeshield-block=/active.example/' >"$SS_BLOCKLIST_FILE"
|
||||
: >"$CALLS"
|
||||
start_service
|
||||
ss_spec_assert_file_line "$CALLS" 'deactivate_blocklist'
|
||||
! grep -Fx 'dnsmasq_restart' "$CALLS" >/dev/null
|
||||
rm -f "$SS_BLOCKLIST_FILE"
|
||||
|
||||
MOCK_ENABLED=1
|
||||
MOCK_STATISTICS=1
|
||||
STATISTICS_CHANGED=0
|
||||
MIGRATION_RC=0
|
||||
: >"$CALLS"
|
||||
start_service
|
||||
ss_spec_assert_file_line "$CALLS" 'require_dnsmasq'
|
||||
ss_spec_assert_file_line "$CALLS" 'statistics_guard'
|
||||
ss_spec_assert_file_line "$CALLS" 'reconcile_blocklists'
|
||||
ss_spec_assert_file_line "$CALLS" 'statistics_configure 1'
|
||||
ss_spec_assert_file_line "$CALLS" 'activate_blocklist'
|
||||
ss_spec_assert_file_line "$CALLS" 'procd_set_param command /usr/libexec/safeshield-refreshd'
|
||||
ss_spec_assert_file_line "$CALLS" 'add_statistics_instance'
|
||||
ss_spec_assert_file_line "$CALLS" 'add_statistics_upload_instance'
|
||||
|
||||
MIGRATION_RC=1
|
||||
: >"$CALLS"
|
||||
! start_service
|
||||
ss_spec_assert_file_line "$CALLS" 'failure blocklist_directive_migration_failed'
|
||||
! grep -Fx 'activate_blocklist' "$CALLS" >/dev/null
|
||||
MIGRATION_RC=0
|
||||
|
||||
MOCK_ENABLED=1
|
||||
STATISTICS_CHANGED=1
|
||||
rm -f "$SS_BLOCKLIST_FILE"
|
||||
: >"$CALLS"
|
||||
stop_service
|
||||
ss_spec_assert_file_line "$CALLS" 'statistics_configure 0'
|
||||
ss_spec_assert_file_line "$CALLS" 'deactivate_blocklist'
|
||||
ss_spec_assert_file_line "$CALLS" 'dnsmasq_restart'
|
||||
ss_spec_assert_file_line "$CALLS" 'status_set stage stopped'
|
||||
|
||||
printf '%s\n' 'safeshield-block=/active.example/' >"$SS_BLOCKLIST_FILE"
|
||||
: >"$CALLS"
|
||||
stop_service
|
||||
ss_spec_assert_file_line "$CALLS" 'deactivate_blocklist'
|
||||
! grep -Fx 'dnsmasq_restart' "$CALLS" >/dev/null
|
||||
rm -f "$SS_BLOCKLIST_FILE"
|
||||
|
||||
MOCK_ENABLED=0
|
||||
STATISTICS_CHANGED=0
|
||||
: >"$CALLS"
|
||||
stop_service
|
||||
ss_spec_assert_file_line "$CALLS" 'status_set status disabled'
|
||||
ss_spec_assert_file_line "$CALLS" 'status_set last_result disabled'
|
||||
)
|
||||
|
||||
reload_service() {
|
||||
restart
|
||||
}
|
||||
Executable
+1044
File diff suppressed because it is too large.
Load diff
Executable
+1274
File diff suppressed because it is too large.
Load diff
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/bin/sh
|
||||
# shellcheck shell=sh
|
||||
|
||||
ss_case_tooling() (
|
||||
set -eu
|
||||
[ -f "$SS_SPEC_ROOT/.pre-commit-config.yaml" ]
|
||||
[ -f "$SS_SPEC_ROOT/.shellspec" ]
|
||||
[ -f "$SS_SPEC_ROOT/spec/spec_helper.sh" ]
|
||||
[ -f "$SS_SPEC_ROOT/spec/core_spec.sh" ]
|
||||
[ -f "$SS_SPEC_ROOT/spec/blocklist_spec.sh" ]
|
||||
[ -f "$SS_SPEC_ROOT/spec/runtime_spec.sh" ]
|
||||
[ -f "$SS_SPEC_ROOT/spec/statistics_spec.sh" ]
|
||||
[ -f "$SS_SPEC_ROOT/spec/ucode_spec.sh" ]
|
||||
[ -f "$SS_SPEC_ROOT/spec/tooling_spec.sh" ]
|
||||
[ -f "$SS_SPEC_ROOT/scripts/lint.sh" ]
|
||||
[ -f "$SS_SPEC_ROOT/.github/workflows/lint-shell.yml" ]
|
||||
ss_spec_assert_file_contains "$SS_SPEC_ROOT/.pre-commit-config.yaml" 'entry: sh scripts/lint.sh'
|
||||
ss_spec_assert_file_contains "$SS_SPEC_ROOT/.pre-commit-config.yaml" 'always_run: true'
|
||||
ss_spec_assert_file_contains "$SS_SPEC_ROOT/.github/workflows/lint-shell.yml" 'sh scripts/lint.sh'
|
||||
ss_spec_assert_file_contains "$SS_SPEC_ROOT/.github/workflows/lint-shell.yml" 'SHELLSPEC_VERSION: 0.28.1'
|
||||
ss_spec_assert_file_contains "$SS_SPEC_ROOT/.github/workflows/lint-shell.yml" 'run: REQUIRE_UCODE=1 shellspec'
|
||||
[ ! -e "$SS_SPEC_ROOT/tests/run.sh" ]
|
||||
ss_spec_assert_file_contains "$SS_SPEC_ROOT/scripts/lint.sh" 'spec/*_spec.sh)'
|
||||
ss_spec_assert_file_contains "$SS_SPEC_ROOT/scripts/lint.sh" 'shfmt -d -ci'
|
||||
ss_spec_assert_file_contains "$SS_SPEC_ROOT/scripts/lint.sh" 'shellcheck -x -S warning'
|
||||
ss_spec_assert_file_contains "$SS_SPEC_ROOT/scripts/lint.sh" 'sh -n "$file"'
|
||||
[ ! -e "$SS_SPEC_ROOT/spec/legacy_tests_spec.sh" ]
|
||||
)
|
||||
|
||||
ss_case_dead_code_contract() (
|
||||
set -eu
|
||||
|
||||
UTILS="$SS_SPEC_ROOT/files/usr/lib/safeshield/utils.sh"
|
||||
STATUS="$SS_SPEC_ROOT/files/usr/lib/safeshield/status.sh"
|
||||
LOG="$SS_SPEC_ROOT/files/usr/lib/safeshield/log.sh"
|
||||
DNS="$SS_SPEC_ROOT/files/usr/lib/safeshield/dns.sh"
|
||||
BLOCKLIST="$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
|
||||
CONFIG="$SS_SPEC_ROOT/files/usr/lib/safeshield/config.sh"
|
||||
INIT="$SS_SPEC_ROOT/files/etc/init.d/safeshield"
|
||||
|
||||
for symbol in \
|
||||
str_contains \
|
||||
str_contains_word \
|
||||
str_first_word \
|
||||
str_to_lower \
|
||||
str_to_upper \
|
||||
str_replace \
|
||||
is_greater; do
|
||||
! grep -Eq "^${symbol}\\(\\)[[:space:]]*\\{" "$UTILS"
|
||||
done
|
||||
|
||||
for symbol in is_enabled is_active; do
|
||||
! grep -Eq "^${symbol}\\(\\)[[:space:]]*\\{" "$STATUS"
|
||||
done
|
||||
|
||||
for symbol in log_fail log_debug; do
|
||||
! grep -Eq "^${symbol}\\(\\)[[:space:]]*\\{" "$LOG"
|
||||
done
|
||||
|
||||
for symbol in ss_clear_active_blocklist dnsmasq_kill; do
|
||||
! grep -Eq "^${symbol}\\(\\)[[:space:]]*\\{" "$DNS"
|
||||
done
|
||||
|
||||
! grep -Eq '^ss_detect_primary_mac\\(\\)[[:space:]]*\\{' "$BLOCKLIST"
|
||||
! grep -F 'ss_debug=' "$CONFIG"
|
||||
! grep -F '/lib/functions/network.sh' "$INIT"
|
||||
|
||||
# Keep the replacement/runtime contracts that made the retired helpers obsolete.
|
||||
ss_spec_assert_file_contains "$BLOCKLIST" 'ss_identity_ensure "$model" "$arch"'
|
||||
ss_spec_assert_file_contains "$DNS" '/etc/init.d/dnsmasq restart'
|
||||
ss_spec_assert_file_contains "$INIT" "procd_add_reload_interface_trigger 'wan'"
|
||||
)
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/bin/sh
|
||||
# shellcheck shell=sh
|
||||
|
||||
ss_case_ucode() (
|
||||
set -eu
|
||||
MODULE_DIR="$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield"
|
||||
ENTRY="$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield.uc"
|
||||
STATUS_STORE="$SS_SPEC_ROOT/files/usr/lib/safeshield/status-store.uc"
|
||||
UCODE_BIN="${UCODE:-ucode}"
|
||||
if ! command -v "$UCODE_BIN" >/dev/null 2>&1; then
|
||||
[ "${REQUIRE_UCODE:-0}" != '1' ]
|
||||
return 0
|
||||
fi
|
||||
TMPDIR="$(ss_spec_tmpdir)"
|
||||
trap 'rm -rf "$TMPDIR"' EXIT HUP INT TERM
|
||||
compile_ucode() {
|
||||
file="$1"
|
||||
output="$TMPDIR/$(basename -- "$file").ucb"
|
||||
"$UCODE_BIN" -c -o "$output" "$file" >/dev/null
|
||||
}
|
||||
compile_ucode "$ENTRY"
|
||||
compile_ucode "$STATUS_STORE"
|
||||
for file in "$MODULE_DIR"/*.uc; do
|
||||
compile_ucode "$file"
|
||||
done
|
||||
run_ucode_test() {
|
||||
name="$1"
|
||||
mock_dir="$SS_SPEC_ROOT/tests/ucode/mocks/$name"
|
||||
test_file="$SS_SPEC_ROOT/tests/ucode/test_$name.uc"
|
||||
test_tmp="$TMPDIR/$name"
|
||||
test_modules="$TMPDIR/modules/$name"
|
||||
mkdir -p "$test_tmp" "$test_modules"
|
||||
cp "$MODULE_DIR/$name.uc" "$test_modules/$name.uc"
|
||||
cp "$mock_dir"/*.uc "$test_modules/"
|
||||
"$UCODE_BIN" -L "$test_modules" -D "TEST_TMP=\"$test_tmp\"" "$test_file" >/dev/null
|
||||
}
|
||||
for name in core config license refresh rules statistics status runtime; do
|
||||
run_ucode_test "$name"
|
||||
done
|
||||
|
||||
status_file="$TMPDIR/status-store.json"
|
||||
printf '%s\n' 'not-json' >"$status_file"
|
||||
out="$("$UCODE_BIN" "$STATUS_STORE" "$status_file" dump)"
|
||||
printf '%s\n' "$out" | grep -F '"data":{}' >/dev/null
|
||||
printf '%s\n' "$out" | grep -F '"warnings":[]' >/dev/null
|
||||
printf '%s\n' "$out" | grep -F '"errors":[]' >/dev/null
|
||||
|
||||
out="$("$UCODE_BIN" "$STATUS_STORE" "$status_file" set stage running)"
|
||||
printf '%s\n' "$out" >"$status_file"
|
||||
printf '%s\n' "$out" | grep -F '"stage":"running"' >/dev/null
|
||||
out="$("$UCODE_BIN" "$STATUS_STORE" "$status_file" add_warning dnsmasq_warning)"
|
||||
printf '%s\n' "$out" >"$status_file"
|
||||
printf '%s\n' "$out" | grep -F '"code":"dnsmasq_warning"' >/dev/null
|
||||
out="$("$UCODE_BIN" "$STATUS_STORE" "$status_file" add_error artifact_download_failed)"
|
||||
printf '%s\n' "$out" >"$status_file"
|
||||
printf '%s\n' "$out" | grep -F '"code":"artifact_download_failed"' >/dev/null
|
||||
out="$("$UCODE_BIN" "$STATUS_STORE" "$status_file" clear_messages)"
|
||||
printf '%s\n' "$out" | grep -F '"warnings":[]' >/dev/null
|
||||
printf '%s\n' "$out" | grep -F '"errors":[]' >/dev/null
|
||||
out="$("$UCODE_BIN" "$STATUS_STORE" "$status_file" reset)"
|
||||
printf '%s\n' "$out" | grep -F '"data":{}' >/dev/null
|
||||
if "$UCODE_BIN" "$STATUS_STORE" "$status_file" set >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
if "$UCODE_BIN" "$STATUS_STORE" "$status_file" unknown >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
)
|
||||
Reference in new issue
Block a user