39 Commits

Author SHA1 Message Date
dependabot[bot] 1f85c4ab1e chore(deps): update mockall requirement (#365)
Updates the requirements on [mockall](https://github.com/asomers/mockall) to permit the latest version.

Updates `mockall` to 0.15.0
- [Changelog](https://github.com/asomers/mockall/blob/master/CHANGELOG.md)
- [Commits](https://github.com/asomers/mockall/compare/v0.14.0...v0.15.0)

---
updated-dependencies:
- dependency-name: mockall
  dependency-version: 0.15.0
  dependency-type: direct:production
  dependency-group: library-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-02 18:30:09 -07:00
Eric Seidel 8649c75206 perf: mmap libapp.so out of the APK instead of buffering in RAM (#354)
* perf: mmap libapp.so out of the APK instead of buffering in RAM

`open_base_lib` previously read the entire decompressed libapp.so into a
Vec<u8> via `read_to_end` and handed bipatch a Cursor over that buffer.
For large apps libapp.so can be tens of megabytes, and that allocation
happens immediately after the patch download is buffered to disk — a
plausible OOM trigger on memory-constrained devices (we have at least
one customer report on OnePlus where the patch install appears to halt
silently right after the download completes).

Modern AGP (3.6+) defaults to extractNativeLibs=false, which keeps
libapp.so STORED uncompressed inside the APK so the dynamic linker can
mmap it directly. When that's the case, do the same: find the entry's
data offset via the zip crate, drop the archive, reopen the APK, and
mmap the entry's slice. Cursor<Mmap> implements Read + Seek, which is
what bipatch's `Reader::new(patch, base)` requires.

When the entry isn't stored uncompressed (older builds, or builds that
explicitly compress native libs), fall back to the previous buffered
read so we always succeed.

Mmap doesn't change the peak working set when bipatch traverses the
whole base linearly, but file-backed mappings are clean and reclaimable
under memory pressure where an anonymous Vec is not, and we avoid the
~2x transient allocation peak from `read_to_end` growing the buffer.

Tests cover both paths (stored → mmap, deflated → buffered) on the host.

* ci: add mmap, memmap, SIGBUS to cspell dictionary
2026-05-05 17:33:22 -07:00
Eric Seidel 8072ed9ad7 feat: stage 1 integration test harness (#353)
* docs: integration tests design proposal

Adds docs/integration_tests.md proposing a desktop-only integration suite
that drives the Dart `ShorebirdUpdater` API against the real Rust core via
FFI, with a fake HTTP server and per-test tempdir state. Describes a new
`library_test_hooks` cdylib that reaches into `updater` via a `test-hooks`
Cargo feature, so production C API stays clean. Status: design exploration,
not a committed plan.

* feat: stage 1 integration test harness for shorebird_code_push

Lands the test_hooks crate, the Dart-side loader, and one trivial
end-to-end test, per docs/integration_tests.md stage 1.

Pieces:

- New `library_test_hooks` workspace crate (cdylib). Depends on `updater`
  with a new `test-hooks` Cargo feature that widens the visibility of
  internal items (currently `testing_reset_config`) for sibling-crate
  access only — production builds do not enable it. The crate also
  re-exports `updater::c_api::dart::*` and `updater::c_api::engine::*`,
  which keeps the rlib's `#[no_mangle]` symbols out of DCE so the
  resulting cdylib carries both the production C API and the new
  `shorebird_test_*` hooks.
- Workspace `default-members` excludes `library_test_hooks` so plain
  `cargo build` / `cargo test` invocations don't unify the `test-hooks`
  feature into production builds.
- `shorebird_code_push/test/integration/all_test.dart` (single file,
  with a header comment explaining why) loads the cdylib via
  `DynamicLibrary.open`, reassigns the existing `@visibleForTesting`
  `Updater.bindings` setter, and exercises both surfaces. The build
  helper shells out to `cargo build -p library_test_hooks`; if it
  fails, `markTestSkipped` keeps the suite green on machines without
  a working Rust toolchain.
- ffigen config (`ffigen_test_hooks.yaml`) generates the test-only
  Dart bindings under `test/integration/generated/`, not `lib/`.
- CI: `library_test_hooks` is added to the rust_crate matrix, and the
  shorebird_code_push job triggers on `library/**` and
  `library_test_hooks/**` so cdylib changes can't break the Dart-side
  integration suite without CI noticing.

Verified locally: 232 Rust unit tests + 42 Dart tests (41 existing + 1
integration) green; clippy/fmt/cspell clean; production cdylib does
not contain `shorebird_test_reset` (`nm` confirms feature isolation).

Stages 2 (FakePatchServer + golden path) and 3 (adversarial scenarios)
land separately.

* fix(integration test): early-return on skip and bump per-test timeout

Two issues caught by Shorebird CI on PR #353:

1. `markTestSkipped` does not abort test execution — it only flags the
   test as skipped on its way out. The body kept running and crashed on
   the `late testHooks` field when the cdylib build had failed in
   setUpAll. Move the skip check into the test body itself with an
   early return; drop the (no-op) skip in `setUp`.

2. Default per-test timeout (30s) also covers `setUpAll`. A cold
   `cargo build -p library_test_hooks` compiles `updater` and ~100
   transitive deps, which can run minutes on CI. Bump to 10 minutes
   via `@Timeout` on the library.

Verified locally: passes when cargo is on PATH (1 passed), reports a
clean skip and exit 0 when cargo is removed from PATH (1 skipped).

* refactor(integration test): drop ! by promoting testHooks to late final

`testHooks` was nullable so accessing it after the skipReason check
required `!`, and `markTestSkipped(skipReason!)` had the same smell.
Make `testHooks` `late final` (non-nullable, throws if read before
setUpAll assigns) and pull `skipReason` into a non-null local before
use. Same control flow, no bang operators.
2026-05-05 16:32:57 -07:00
Eric Seidel db99e24726 perf: set panic=abort and drop backtrace features (#342)
Reduces binary size of the updater staticlib when linked into the
Flutter engine. On host macOS arm64 the dylib shrinks from 3.07 MB
to 2.56 MB; the biggest iOS savings come from dropping the DWARF
unwind tables (__eh_frame, __gcc_except_tab, __unwind_info) that
panic=unwind generates.

- panic = "abort" in [profile.release]: kills unwind-table generation
  and eliminates dead panic-unwinding code paths. We don't use
  catch_unwind anywhere in the library. log-panics still runs its
  panic hook before abort, so panic messages continue to surface to
  logcat/oslog during development.
- Drop `backtrace` feature from anyhow and `with-backtrace` from
  log-panics: removes gimli + addr2line + rustc_demangle (~65 KB of
  symbolication machinery) that customers can't read anyway. A
  future in-engine crash reporter will symbolize native stacks,
  making these features redundant.
2026-04-21 19:01:26 +00:00
dependabot[bot] 08b91f49d2 chore(deps): bump the library-deps group in /library with 5 updates (#332)
* chore(deps): bump the library-deps group in /library with 5 updates

Updates the requirements on [sha2](https://github.com/RustCrypto/hashes), [zip](https://github.com/zip-rs/zip2), [mockall](https://github.com/asomers/mockall), [mock_instant](https://github.com/museun/mock_instant) and [cbindgen](https://github.com/mozilla/cbindgen) to permit the latest version.

Updates `sha2` to 0.11.0
- [Commits](https://github.com/RustCrypto/hashes/compare/streebog-v0.11.0-pre.0...sha2-v0.11.0)

Updates `zip` to 8.5.0
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/compare/v3.0.0...v8.5.0)

Updates `mockall` to 0.14.0
- [Changelog](https://github.com/asomers/mockall/blob/master/CHANGELOG.md)
- [Commits](https://github.com/asomers/mockall/compare/v0.13.1...v0.14.0)

Updates `mock_instant` to 0.6.0
- [Commits](https://github.com/museun/mock_instant/compare/v0.5.1...v0.6.0)

Updates `cbindgen` to 0.29.2
- [Release notes](https://github.com/mozilla/cbindgen/releases)
- [Changelog](https://github.com/mozilla/cbindgen/blob/main/CHANGES)
- [Commits](https://github.com/mozilla/cbindgen/compare/0.28.0...0.29.2)

---
updated-dependencies:
- dependency-name: sha2
  dependency-version: 0.11.0
  dependency-type: direct:production
  dependency-group: library-deps
- dependency-name: zip
  dependency-version: 8.5.0
  dependency-type: direct:production
  dependency-group: library-deps
- dependency-name: mockall
  dependency-version: 0.14.0
  dependency-type: direct:production
  dependency-group: library-deps
- dependency-name: mock_instant
  dependency-version: 0.6.0
  dependency-type: direct:production
  dependency-group: library-deps
- dependency-name: cbindgen
  dependency-version: 0.29.2
  dependency-type: direct:production
  dependency-group: library-deps
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: adapt sha2 0.11 hashing (no io::Write impl)

* refactor: reuse cache::hash_file in check_hash

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eric Seidel <eric@shorebird.dev>
2026-04-08 16:16:41 -07:00
Eric Seidel adacb4190c refactor: replace serde_yaml with hand-rolled parser (#326)
serde_yaml is deprecated and pulls in unnecessary dependencies
(unsafe-libyaml, indexmap, hashbrown) for parsing our simple
flat key-value config file. Replace with a minimal hand-rolled
parser that handles exactly what shorebird.yaml needs.

Size impact (release build, macOS arm64):
  .a  (staticlib): -674 KB (26.1 MB → 25.4 MB)
  .dylib (cdylib): -169 KB (3.7 MB → 3.5 MB)
2026-04-02 16:46:55 -07:00
Eric Seidel 2057fd4f46 fix: resolve all Dependabot security vulnerabilities (#316)
Run `cargo update` to bump transitive dependencies, fixing 10 of 11
alerts (h2, ring, idna, mio, tokio, bytes, time, quinn-proto,
rustls-webpki, unsafe-libyaml).

Replace deprecated `tempdir` dev-dependency with `tempfile` to
eliminate the `remove_dir_all` vulnerability (the last alert).
2026-04-01 15:36:12 +00:00
Eric Seidel c6647a2dfe refactor: replace reqwest with ureq to reduce binary size (#317)
* refactor: replace reqwest with ureq to reduce binary size

reqwest's blocking API is built on top of its async implementation,
pulling in tokio, hyper, futures, and ~84 other transitive dependencies
even though we only make simple synchronous HTTP calls.

ureq is a synchronous-only HTTP client that eliminates the async
runtime entirely. This reduces transitive dependencies from 227 to 143
and the linked dylib from 4.5 MB to 3.7 MB (-18%). The .a archive
drops from 28 MB to 26 MB, but real savings will be larger once
linked into libflutter with dead code stripping.

The network API surface is unchanged — three functions (patch check,
file download, event reporting) using POST/GET with JSON.

* chore: add ureq to spell check dictionary

* refactor: use into_body() instead of body_mut() where response is consumed

* fix: simplify network error matching to avoid fragile string checks

Consolidate HostNotFound, ConnectionFailed, and all Io errors into
a single network-error arm instead of pattern-matching on error
message strings that could change across OS versions or locales.

* chore: add TODO for misleading network error message
2026-04-01 08:23:48 -07:00
Eric Seidel 6d0e4a1193 chore(deps): bump Rust and Dart dependencies (#315)
* chore(deps): bump Rust and Dart dependencies

Bump Rust dependencies in library/ and patch/:
- comde: 0.2.3 → 0.3.1 (library), 0.2.3 → 0.3.0 (patch)
- zip: 0.6.4 → 3.0.0 (breaking: FileOptions → SimpleFileOptions)
- android_logger: 0.13.0 → 0.15.0
- mockall: 0.12.1 → 0.13.1
- serial_test: 2.0.0 → 3.2.0
- cbindgen: 0.24.0 → 0.28.0

Bump Dart dev dependency in shorebird_code_push/:
- ffigen: upper bound <17.0.0 → <19.0.0

Updated zip API usage (FileOptions → SimpleFileOptions) and
adjusted test assertion for changed error message.

Binary size impact (macOS release, arm64):
- libupdater.a: +83 KB (+0.28%)
- libupdater.dylib: +34 KB (+0.75%)

Closes #206, #271, #273.

* chore: add EOCD to cspell dictionary

The zip 3.0 crate changed its error message to reference "EOCD"
(End of Central Directory), which cspell doesn't recognize.
2026-03-30 15:50:44 -07:00
Bryan Oltman 76f005940d feat: add uuid to updater state, patch check request (#300)
* feat: add uuid to updater state

* add client_id to patch check request

* cleanup

* comments

* cleanup

* more comments

* delete commented-out code

* Update library/src/cache/updater_state.rs

Co-authored-by: Eric Seidel <eric@shorebird.dev>

* formatting

---------

Co-authored-by: Eric Seidel <eric@shorebird.dev>
2025-10-29 15:57:06 -04:00
Bryan Oltman a4a7255796 feat: configure logging on linux (#263) 2025-01-28 10:28:48 -05:00
Bryan Oltman 38aadee1c5 feat: enable updater logging on windows (#256) 2024-12-18 14:10:37 -08:00
Bryan Oltman b4775d30dd feat: support macOS (#247)
* feat: support macOS

* fix tests
2024-12-05 17:53:45 -05:00
Bryan Oltman 0988c854af Revert "feat: generate a rollout group number on UpdaterState creation (#227)" (#231)
This reverts commit 8e7ec5a9b6.
2024-11-07 09:34:32 -05:00
Bryan Oltman 8e7ec5a9b6 feat: generate a rollout group number on UpdaterState creation (#227)
* feat: generate a rollout group number on UpdaterState creation

* update test

* use inclusive range
2024-10-30 17:40:46 -04:00
Bryan Oltman 3da6c38ef4 feat: add timestamp to patch events (#179)
* feat: add timestamp to patch events

* fix test
2024-06-20 15:47:59 -04:00
Bryan Oltman 390aa49cba feat: update patch manager to check signature on boot (#171)
* feat: store patch signature on disk with patch metadata

* add note about explicit lifetime

* fix tests

* feat: update patch manager to check signature on boot

* remove unused imports

* fix tests

* add log when no public key detected

* clean up log message

* pr feedback

* rename test

* rename tests null -> none

* change signature field name to hash_signature

* Add comment

* update print statements for clearer device logs

* fix test

* improve docs

* cleanup of signing error handling
2024-05-23 14:41:02 -04:00
Bryan Oltman 4685789e59 feat: store patch signature on disk with patch metadata (#170)
* feat: store patch signature on disk with patch metadata

* add note about explicit lifetime

* fix tests
2024-05-22 10:00:36 -04:00
Eric Seidel fe465ea1b1 chore: remove unused package (#142) 2024-04-19 21:00:33 +00:00
Bryan Oltman d3b88ec4b6 fix: us OSLog framework for logging on iOS (#120)
* fix: us OSLog framework for logging on iOS

* cleanup
2024-02-06 16:52:04 -05:00
Bryan Oltman ed013eb257 feat: Update C API to consume Read+Seek callbacks (#111)
* Update C API to consume Read+Seek callbacks

* remove open and close functions

* update to reflect new interface

* Refactor posix file i/o to c_api (#113)

* Refactor posix file i/o to c_api

* fix comment

* rename ExternalFile to ReadSeek

* cleanup and comments

* Add SHOREBIRD_PATCH_BASE_FILENAME const

* fix lint

* add fake callbacks for c_api tests

* fix tests

* remove os_last_error

* remove todos, add comments

* cleanup

* remove params to open

* add c_api module, tests

* reorganize

* Return Err if CFileProvider open returns null

* add comments and docs
2024-01-16 14:28:04 -05:00
Bryan Oltman a70fe54668 test: use mockito crate to test network-interfacing code (#106)
* test: use mockito crate to test network-interfacing code

* tweak
2023-11-17 10:30:25 -05:00
Bryan Oltman b02610370a refactor: move patch management functionality out of UpdaterState (#93)
* non-compiling WIP

* update

* add cfg test

* address clippy issues

* minor consistency

* introduce patch manager, delegate patch management functionality from updater state

* cleanup

* Restructure patch state on disk, adds patch validation

* Simplify patches state, move patches to subdirectories of patches/ instead of having the artifacts as immediate children

* Tests

* delete unused enum

* disk manager tests

* Tests, docs

* rename

* delete unused file

* fix todo

* error logging

* cleanup

* cleanup

* comments and cleanup

* cleanup

* debug log

* boot from previous patch if next patch is bad

* fallback logic and test

* fix log

* context -> with_context

* remove get from get_next_boot_patch

* Log patch state load error

* renaming

* coverage

* capitalization

* coverage

* Replace unwrap with ? in disk_io

* Fix record boot success

* eq none -> .is_none

* Move delete_patch_artifacts failure logging into function

* rename patch_install_success_fn to report_event_fn

* Fix patch install reporting

* update comment
2023-10-03 13:58:23 -04:00
Eric Seidel e4b182d75d chore: fix 30 of the 54 warnings from clippy pedantic. (#81)
I just ran `cargo clippy -- -W clippy::pedantic` and fixed things.

These are more invasive that the default set and the remaining
warnings are mostly about our (abysmal) public docs missing
Error and Panic sections to explain errors and panicks.
2023-09-08 17:51:07 +00:00
Bryan Oltman 7417429d91 fix: log more friendly error message in case of no internet connection (#67)
* fix: log more friendly error message in case of no internet connection

* fix typo

* coverage
2023-08-17 08:36:10 -04:00
Bryan Oltman d321ad02de feat: add client_id to UpdaterState (#70)
* feat: add client_id to UpdaterState

* Ensure we save UpdaterState after it's created

* Save state if we assign it a client id on load

* Rename
2023-08-11 11:23:13 -04:00
Bryan Oltman ffabae1ae6 chore: sort deps in Cargo.toml (#69)
* chore: sort deps in Cargo.toml

* more sorting

* even more sorting

* remove unused lib
2023-08-10 09:50:44 -04:00
Eric Seidel 3af4081f55 fix: Only allow one update attempt at a time (#44)
This is all for shorebirdtech/shorebird#695

I ended up fixing a lot of things in our Rust code while I was in there, including:

* Using PathBuf instead of Sting wherever we are holding onto Path objects (this removed a bunch of code which was converting in and out of these).
* Made a variety of functions private (the default) that were previously mistakenly pub.
* Moved the android hacks around libapp_paths closer to the C++ code. Previously we were storing all of the libapp_paths on ResolvedConfig and handling the android hacks during patch install. Now we're doing the android path hacks on init, eventually we'll remove them entirely by making the C++ code pass us in the android app_dir or even the resolved .apk we need instead of the libapp_paths.
* Fixed several tests to use /dir/lib/arch/libapp.so paths instead of 'libapp.so' now that the init code applies the android hacks an expects the deeper paths. Again, this will go away when we move the android hacks into c++.
* Added a test which confirms that if we init twice we log instead of trying to init again (this was one behavior which FCM was triggering as part of fix: ANR when using Firebase Cloud Messaging with Shorebird shorebird#695
* Add a test which confirms that further calls to update while an update is ongoing just fail out quickly instead of hanging.
* Moved to serial_test instead of a separate per-thread config system for testing. This makes our tests operate more like the code does in the wild, at the cost of needing to annotate a test as #[serial] any time it needs to use shorebird_init.
* Removed ResolvedConfig and now call it UpdateConfig. ResolvedConfig previously had a bool on it to tell if it was initialized, that's now represented as Option instead which is more rusty.
* Changed how we handle NetworkHooks from being compile-time test switched to using function pointers held off of UpdateConfig.network_hooks. I think this makes more sense? I did this to remove the special ThreadConfig object which was used during unit tests, but didn't exist in production. I think the way we mock networking could still be improved.
2023-06-22 12:45:55 -07:00
Eric Seidel 4d2ec2e148 feat: Make updater build on iOS (#22)
Doesn't work yet, but does at least build (and log).
2023-05-31 14:44:35 -05:00
Eric Seidel b19eb16654 fix: Rewrite updater to read libapp.so from apk rather than expecting it on disk (#5)
Fixes shorebirdtech/shorebird#235 🤞

This moves us away from depending on libapp.so having been extracted from the APK and instead we always extract it ourselves.
2023-04-12 14:49:00 -07:00
Eric Seidel 241a1dfabf Autogenerate include/updater.h every time we build. (#3)
Turns out this was way easier than I thought.
https://github.com/shorebirdtech/shorebird/issues/121
Docs:
https://github.com/eqrion/cbindgen/blob/master/docs.md#buildrs
2023-04-06 17:14:49 +00:00
Eric Seidel 6da2bd453e feat: Teach rust side to validate hashes when installing (#171) 2023-03-24 17:53:40 -05:00
Eric Seidel 0d113ab094 feat: Add support for diff patches (#163)
Co-authored-by: Felix Angelov <felix@shorebird.dev>
2023-03-24 19:59:35 +00:00
Eric Seidel 1220e63f9e Expose "report_launch_failure" so engine can call it. (#97) 2023-03-20 17:32:03 -05:00
Eric Seidel e188d61085 feat: Start adding rust tests. (#81) 2023-03-16 12:01:44 -05:00
Eric Seidel b18ac7b947 feature: Teach the rust updater library about shorebird.yaml (#54) 2023-03-10 23:49:19 -06:00
Eric Seidel eacc7e8b87 Make updater library thread safe
This was needed so that it could be called from Dart as well as flutter_main/C++.

It turns out flutter_main does not run on the "ui thread", so when Dart was calling
into the updater it would panic due to thinking the updater (which was using a thread local) was not yet initialized.

Also added the log-panics crate on Android so that panics appear in adb logcat.
2023-03-08 13:03:13 -08:00
Eric Seidel 52ec7f3c4e refactor: split updater library into layers (#47) 2023-03-08 11:11:10 -06:00
Eric Seidel f757251162 chore: Fix the rust build (#27) 2023-03-06 15:06:31 -08:00