Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build/bloom-typography.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
"unclassified_data": "unchanged",
"fragment_kinds": ["copy", "data"],
"maximum_fragments": 8,
"classified_copy_call_sites": 77,
"classified_mixed_call_sites": 12
"classified_copy_call_sites": 81,
"classified_mixed_call_sites": 13
}
}
2 changes: 1 addition & 1 deletion build/dependencies.lock
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ manifest = "build/legacy-manifest.json"
schema = 1
component_count = 332
file_count = 2131
byte_count = 314683902
byte_count = 314683965
source_build_components = 228
unresolved_components = 104
inherited_baseline = "07505ea58c7bba698d6b9220ff43946a43cac76b"
Expand Down
4 changes: 2 additions & 2 deletions build/legacy-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@
"kind": "runtime",
"path": "static/build/.tmp_update/runtime.sh",
"file_count": 1,
"byte_count": 45051,
"tree_sha256": "c6a9227bdbe235e730a9610bff62444889e975b7de8ba135f9c3c639948d657a",
"byte_count": 45114,
"tree_sha256": "1262e7dea822dd05d1c941707c52b989a9f5f0c550d775c4e45d6026c88ccda0",
"source": "https://github.com/boburning/BloomOS",
"source_revision": "release-commit",
"license": "GPL-3.0-only",
Expand Down
4 changes: 2 additions & 2 deletions docs/1.0_TEST_MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ shoulder button.
| Themes/localization | Appearance-only schema 2; schema-1 compatibility; Classic/Dense/Large Text/High Contrast authority; accessibility wins over custom themes; fallback on malformed input; exact developer-only pseudolocale opt-in; classified Bloom-copy expansion; typed mixed copy/data fragments with byte-exact runtime data; malformed, unbounded, copy-free, and capacity rejection; pseudolocale disabled in immutable Safe Mode/recovery UI |
| Critical lifecycle | Exact 640x480 and 752x560 built-in projections; source/target/path and legacy-file rejection; boot/shutdown/save/low-battery/recovery/Safe Mode ignore theme/config/media; charging reflects bounded actual level; installer uses one deterministic surface; missing optional text/font fallback; signed Full-update boot/charging/shutdown/recovery checkpoint |
| Settings | One sectioned surface; inline values; capability filtering; one-layer details; hidden Developer section; optional overlapping L1/R1 list paging |
| Quick Settings | START from core surfaces; immediate brightness/volume/mute; capability-gated Wi-Fi; qualified Plus remains toggleable while a powered-off radio has no `wlan0`; Off-to-On reaches the fixed backend; readable battery; B/START close |
| Quick Settings | START from core surfaces; immediate brightness/volume/mute; capability-gated Wi-Fi opens the dedicated detail surface; Wi-Fi left/right never mutates the radio; readable battery; B/START close |
| Network services | Desired/runtime separation; carrier without IPv4 remains acquiring/not-ready; canonical DHCP restart and bounded post-address listener reconciliation; SSH shell and SFTP independent key-only states; canonical transfer roots and modes; Saves read-only; private roots denied; traversal/symlink/path bounds; same-filesystem atomic publication; authorized-key import; quiesce/health; stale FTP/Telnet/FileBrowser/inherited-Samba/WPS/hotspot flags cannot activate listeners; process-aware listener audit; Samba hidden until a materially different source-build candidate passes feasibility |
| Wi-Fi profile store | JFFS2 private root; stdin-only bounded replacement and `bloomctl network profile upsert ID`; requested/document ID binding; explicit `network forget ID`; inactive-ID deletion with active-profile refusal; active forget only after a separately confirmed fallback connection; schema/unknown-field/count/size enforcement; WPA2-PSK, explicit open, and manual hidden WPA2; WEP and hidden open denial; unique IDs/SSIDs; redacted status; explicit local credential-free UI list with one active marker; mode-0600 atomic single-profile supplicant projection; symlink, occupied-slot, live-lock, invalid-profile, and prior-byte preservation |
| Wi-Fi scan API | Asynchronous start; redacted status; explicit ID-scoped local results; cancel and terminal discard; one active worker; five-second rate limit; 15-second runtime; 64-KiB raw ceiling; 32 unique printable SSIDs; eight-operation history; open/WPA2-PSK/unsupported classification; duplicate signal reconciliation; backend failure, timeout, oversize, unsafe-path, stale/live-lock, and unsupported-hardware handling |
| Wi-Fi UI | Original Mini hidden; exact profile/scan schemas; 16 profiles and 32 networks; unique bounded IDs/SSIDs; one active profile; fixed scan phases/errors; credential and unknown-field rejection; passwords absent from arguments, logs, screenshots, durable Shell state, and evidence |
| Wi-Fi UI | Original Mini hidden; Settings, Shell Quick Settings, and GameSwitcher Quick Settings converge on `--settings-wifi`; direct radio toggle removed; bounded fixed-argv profile-list load outside render; connection state and saved-network detail; exact profile/scan schemas; 16 profiles and 32 networks; unique bounded IDs/SSIDs; one active profile; fixed scan phases/errors; credential and unknown-field rejection; passwords absent from arguments, logs, screenshots, durable Shell state, and evidence |
| GameSwitcher | MENU open/toggle; Left/Right select; L1/R1 page; A resume; X actions; START Quick Settings; B Home; no inherited brightness/view/delete behavior |
| Apps/Tweaks | Every app disposition recorded; normal configuration requires no Tweaks; supervised launch remains bounded |
| Persistent state | Versioned atomic publication; exact restoration across Details/game/GameSwitcher/Quick Settings; malformed/oversized/symlink input defaults without Safe Mode |
Expand Down
9 changes: 8 additions & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,14 @@ model. It accepts only exact schema-1 service identities, 16 unique stored
profiles, 32 unique scan results, printable 32-byte SSIDs, fixed security and
operation states, one active profile, and one 18–32 byte scan ID. Unknown
fields—including any credential-like field—make the snapshot unusable. This
first model slice adds no direct radio toggle or arbitrary command boundary.
model adds no direct radio toggle or arbitrary command boundary. The Settings
Wi-Fi row and both Shell and GameSwitcher Quick Settings now open one dedicated
detail route instead of invoking `bloom-network request enable|disable`.
Opening the route performs one bounded fixed-argv `bloomctl network profile
list` read outside the render path; the surface shows connection state and
saved networks only. Left/right on the Wi-Fi quick row is deliberately inert.
Scan, association, credential entry, forget, and a recovery-qualified power
control remain later NET-05 slices.

Profile association reuses the one guarded network-transaction authority rather
than adding a second recovery mechanism. `begin` copies the exact current
Expand Down
7 changes: 7 additions & 0 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,13 @@ unknown fields. Keep its output out of shell history, screenshots, logs,
support bundles, and validation evidence. Exercise it only with synthetic
profiles in host or `/tmp` fixtures until the NET-05 UI gate is reached.

The NET-05 saved-network surface is entered through `bloom-shell
--settings-wifi`. Settings A and both Quick Settings A paths navigate there;
left/right on the Wi-Fi quick row is a no-op. Do not restore a direct
`bloom-network request enable|disable` call to any of those handlers. Profile
loading must remain a single bounded fixed-argv read outside `draw()`, and UI
tests must use synthetic SSIDs only.

The NET-04 scan slice is asynchronous and keeps result data only under `/tmp`:

```sh
Expand Down
8 changes: 8 additions & 0 deletions docs/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ fields so a credential-bearing response cannot cross into UI state. Do not
capture this local SSID list in diagnostics, screenshots, logs, or durable
evidence.

Bloom Shell consumes that list only when the user opens the Wi-Fi detail
surface. The child process receives the fixed arguments `network profile list`,
the JSON is capped at 4 KiB, and parsing fails closed before any SSID reaches
render state. Settings and both Quick Settings implementations have no direct
radio enable/disable adapter: A navigates to the detail surface and left/right
does not mutate Wi-Fi. This prevents an ordinary UI edge from severing the
only automation route or bypassing the guarded network transaction authority.

Wi-Fi scan status and logs never contain SSIDs. An explicit local results call
may return at most 32 printable SSIDs for the future on-device UI, but that
ephemeral mode-0600 result is excluded from diagnostics, support exports,
Expand Down
10 changes: 7 additions & 3 deletions docs/program/BloomOS_Work_Program_v3.json
Original file line number Diff line number Diff line change
Expand Up @@ -880,15 +880,19 @@
"NET-04",
"UI-05"
],
"github_prs": [],
"github_prs": [
545
],
"risk_tier": 1,
"deployment_tier": "hot",
"device_gate_required": true,
"last_audited_main": "dbe30da0a2fe9ee2a5211ce471988f132026e759",
"last_audited_main": "3b0a13a30d34733f03831b0d041785c4fc6dc3ae",
"evidence": [
"issue #544 tracks NET-05 on current main after merged NET-04A-D and UI-05; the audit found that Bloom Shell still treats Wi-Fi as an inline toggle whose A and left/right handlers synchronously call bloom-network enable or disable instead of opening the bounded profile and scan UI",
"the first host-safe slice adds an explicit local credential-free projection of at most 16 validated profile IDs, printable SSIDs, security classes, hidden flags, and one active marker; the private service never returns a PSK and the Shell model rejects unknown or credential-bearing fields",
"the bounded Shell model accepts at most 32 unique scan results, one 18-32 byte operation ID, fixed scan phases and errors, integral timestamps and signal values, and exact schema-1 service identities; this slice adds no rendered surface, radio toggle, scan, association, profile transition, listener mutation, reboot, update, or device mutation"
"the bounded Shell model accepts at most 32 unique scan results, one 18-32 byte operation ID, fixed scan phases and errors, integral timestamps and signal values, and exact schema-1 service identities; this slice adds no rendered surface, radio toggle, scan, association, profile transition, listener mutation, reboot, update, or device mutation",
"GitHub PR #545 passed format in 16 seconds, native in 1 minute 24 seconds, the affected ARM build in 1 minute 26 seconds, AddressSanitizer in 1 minute 55 seconds, the 736-test shell gate in 2 minutes 3 seconds, and static analysis in 2 minutes 4 seconds; it merged by squash as 3b0a13a30d34733f03831b0d041785c4fc6dc3ae",
"the dependent rendered-surface slice changes Wi-Fi from a toggle to a detail row, converges Settings and both Quick Settings implementations on the exact --settings-wifi route, removes the direct radio mutation helper, makes Wi-Fi left/right inert, and loads only the 4-KiB-bounded credential-free saved-profile projection outside the render path; scan, association, credential entry, forget, and recovery-qualified radio power remain pending"
]
},
{
Expand Down
90 changes: 12 additions & 78 deletions src/bloomShell/bloom_shell_settings.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ static const BloomShellSettingsDefinition settings_rows[] = {
0,
0},
{{BLOOM_SHELL_SETTINGS_NETWORK_SECTION, BLOOM_SHELL_SETTINGS_ROW_SECTION, "NETWORK"}, 1, 0},
{{BLOOM_SHELL_SETTINGS_WIFI, BLOOM_SHELL_SETTINGS_ROW_TOGGLE, "Wi-Fi"}, 1, 0},
{{BLOOM_SHELL_SETTINGS_WIFI, BLOOM_SHELL_SETTINGS_ROW_DETAIL, "Wi-Fi"}, 1, 0},
{{BLOOM_SHELL_SETTINGS_SSH, BLOOM_SHELL_SETTINGS_ROW_TOGGLE, "SSH"}, 1, 0},
{{BLOOM_SHELL_SETTINGS_SFTP, BLOOM_SHELL_SETTINGS_ROW_TOGGLE, "SFTP"}, 1, 0},
{{BLOOM_SHELL_SETTINGS_SAMBA, BLOOM_SHELL_SETTINGS_ROW_TOGGLE, "Samba"}, 1, 0, 1},
Expand Down Expand Up @@ -168,7 +168,7 @@ int bloom_shell_settings_row_format(const BloomShellCapabilities *capabilities,
: snprintf(label, label_size, "Mute unavailable");
break;
case BLOOM_SHELL_SETTINGS_WIFI:
length = values->ready ? snprintf(label, label_size, "Wi-Fi %s",
length = values->ready ? snprintf(label, label_size, "Wi-Fi %s >",
!values->wifi_enabled
? "Off"
: !values->wifi_status_ready
Expand Down Expand Up @@ -729,58 +729,6 @@ static int run_request(const char *path, const char *first, const char *second,
return wait_child(child);
}

static int run_network_request(const char *path, const char *operation)
{
if (path == NULL || path[0] != '/' || operation == NULL)
return -1;
pid_t child = fork();
if (child < 0)
return -1;
if (child == 0) {
if (setpgid(0, 0) != 0)
_exit(127);
FILE *null = fopen("/dev/null", "w");
if (null != NULL) {
dup2(fileno(null), STDOUT_FILENO);
dup2(fileno(null), STDERR_FILENO);
}
execl(path, path, "request", operation, (char *)NULL);
_exit(127);
}
if (setpgid(child, child) != 0 && errno != EACCES && errno != ESRCH) {
kill(child, SIGKILL);
wait_child(child);
return -1;
}
int status = 0;
for (int attempt = 0; attempt < 100; ++attempt) {
pid_t result = waitpid(child, &status, WNOHANG);
if (result == child)
return WIFEXITED(status) && WEXITSTATUS(status) == 0 ? 0 : -1;
if (result < 0 && errno != EINTR)
return -1;
poll(NULL, 0, 50);
}
kill(-child, SIGTERM);
kill(child, SIGTERM);
for (int attempt = 0; attempt < 10; ++attempt) {
pid_t result = waitpid(child, &status, WNOHANG);
if (result == child) {
kill(-child, SIGKILL);
return -1;
}
if (result < 0 && errno != EINTR)
return -1;
poll(NULL, 0, 50);
}
kill(-child, SIGKILL);
kill(child, SIGKILL);
while (waitpid(child, &status, 0) < 0)
if (errno != EINTR)
break;
return -1;
}

int bloom_shell_network_status_parse(const char *json, BloomShellQuickValues *values)
{
if (json == NULL || values == NULL)
Expand Down Expand Up @@ -994,7 +942,7 @@ int bloom_shell_theme_variant_change(BloomShellQuickValues *values, int directio

int bloom_shell_quick_settings_adjust(const BloomShellCapabilities *capabilities,
BloomShellQuickValues *values, size_t row, int direction,
const char *controls_path, const char *network_path)
const char *controls_path)
{
if (capabilities == NULL || values == NULL || !values->ready ||
(direction != -1 && direction != 1))
Expand Down Expand Up @@ -1029,20 +977,6 @@ int bloom_shell_quick_settings_adjust(const BloomShellCapabilities *capabilities
values->mute = 0;
return 0;
}
if (capabilities->wifi && row == 2) {
int next = direction > 0;
if (next == values->wifi_enabled)
return 0;
if (run_network_request(network_path, next ? "enable" : "disable") != 0) {
bloom_shell_network_status_load(network_path, values);
return -1;
}
values->wifi_enabled = next;
values->wifi_status_ready = 0;
values->wifi_associated = 0;
bloom_shell_network_status_load(network_path, values);
return 0;
}
return -1;
}

Expand All @@ -1066,21 +1000,21 @@ int bloom_shell_mute_toggle(BloomShellQuickValues *values, const char *controls_

int bloom_shell_quick_settings_activate(const BloomShellCapabilities *capabilities,
BloomShellQuickValues *values, size_t row,
const char *controls_path, const char *network_path,
int *open_settings)
const char *controls_path,
BloomShellQuickOpen *open)
{
if (capabilities == NULL || values == NULL || open_settings == NULL)
if (capabilities == NULL || values == NULL || open == NULL)
return -1;
*open_settings = 0;
*open = BLOOM_SHELL_QUICK_OPEN_NONE;
if (row == 1)
return bloom_shell_mute_toggle(values, controls_path);
if (capabilities->wifi && row == 2)
return bloom_shell_quick_settings_adjust(capabilities, values, row,
values->wifi_enabled ? -1 : 1, controls_path,
network_path);
if (capabilities->wifi && row == 2) {
*open = BLOOM_SHELL_QUICK_OPEN_WIFI;
return 0;
}
size_t battery_row = capabilities->wifi ? 3 : 2;
if (row == battery_row + 1) {
*open_settings = 1;
*open = BLOOM_SHELL_QUICK_OPEN_SETTINGS;
return 0;
}
return row == 0 || row == battery_row ? 0 : -1;
Expand Down
12 changes: 9 additions & 3 deletions src/bloomShell/bloom_shell_settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ typedef enum {
BLOOM_SHELL_SETTINGS_DIAGNOSTICS,
} BloomShellSettingsRowId;

typedef enum {
BLOOM_SHELL_QUICK_OPEN_NONE = 0,
BLOOM_SHELL_QUICK_OPEN_SETTINGS,
BLOOM_SHELL_QUICK_OPEN_WIFI,
} BloomShellQuickOpen;

typedef struct {
BloomShellSettingsRowId id;
BloomShellSettingsRowKind kind;
Expand Down Expand Up @@ -139,12 +145,12 @@ int bloom_shell_quick_settings_format(const BloomShellCapabilities *capabilities
char *label, size_t label_size);
int bloom_shell_quick_settings_adjust(const BloomShellCapabilities *capabilities,
BloomShellQuickValues *values, size_t row, int direction,
const char *controls_path, const char *network_path);
const char *controls_path);
int bloom_shell_mute_toggle(BloomShellQuickValues *values, const char *controls_path);
int bloom_shell_quick_settings_activate(const BloomShellCapabilities *capabilities,
BloomShellQuickValues *values, size_t row,
const char *controls_path, const char *network_path,
int *open_settings);
const char *controls_path,
BloomShellQuickOpen *open);
int bloom_shell_first_run_parse(const char *json, BloomShellFirstRun *first_run);
int bloom_shell_first_run_load(const char *settings_path, BloomShellFirstRun *first_run);
int bloom_shell_first_run_finish(const char *settings_path, BloomShellFirstRun *first_run,
Expand Down
Loading
Loading