From 83be9baeeb7b3537511a8ab3dacdee24a73404bf Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Tue, 5 May 2026 05:45:36 -0600 Subject: [PATCH 01/37] Add peer-share toggle to lantern-core (Share My Connection PR 3/4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR 3 of 4 implementing the lantern-side wiring for "Share My Connection". Bumps radiance to fisk/peer-localbackend tip so we can reference the new PeerShareEnabledKey setting; that bump is provisional and should be re-pinned to a release tag once radiance #460 merges. * lantern-core/core.go: new PeerShare interface (mirrors Ads / SmartRouting), embedded in Core. SetPeerShareEnabled patches PeerShareEnabledKey via the radiance ipc client; IsPeerShareEnabled reads the snapshot. * lantern-core/ffi/ffi.go: new //export setPeerProxyEnabled and //export isPeerProxyEnabled, mirroring setBlockAdsEnabled exactly. The Dart FFI binding name uses "PeerProxy" to match the existing user-facing naming in the lantern repo (vpn_setting.dart toggle was drafted as "Peer Proxy"). * lantern-core/mobile/mobile.go: SetPeerShareEnabled / IsPeerShareEnabled for the gomobile-bind surface so Android can toggle once Dart wires it up in PR 4. The lifecycle path: Dart toggle → setPeerProxyEnabled(enabled) → LanternCore.SetPeerShareEnabled → ipc.Client.PatchSettings({PeerShareEnabledKey: ...}) → radiance LocalBackend.PatchSettings dispatch → peer.Client.Start / Stop ffigen regen for the Dart bindings happens in PR 4 alongside the Dart wire-through and rollback logic. go test ./lantern-core/... and golangci-lint --new-from-rev=origin/main both clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- lantern-core/core.go | 16 ++++++++++++++++ lantern-core/ffi/ffi.go | 23 +++++++++++++++++++++++ lantern-core/mobile/mobile.go | 17 +++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/lantern-core/core.go b/lantern-core/core.go index 7b2cf30f21..4c4669e9f5 100644 --- a/lantern-core/core.go +++ b/lantern-core/core.go @@ -153,6 +153,11 @@ type SmartRouting interface { IsSmartRoutingEnabled() bool } +type PeerShare interface { + SetPeerShareEnabled(bool) error + IsPeerShareEnabled() bool +} + type VPN interface { ConnectVPN(tag string) error SelectServer(tag string) error @@ -169,6 +174,7 @@ type Core interface { SplitTunnel Ads SmartRouting + PeerShare VPN Client() *ipc.Client } @@ -454,6 +460,16 @@ func (lc *LanternCore) IsSmartRoutingEnabled() bool { return b } +func (lc *LanternCore) SetPeerShareEnabled(enabled bool) error { + _, err := lc.client.PatchSettings(lc.ctx, settings.Settings{settings.PeerShareEnabledKey: enabled}) + return err +} + +func (lc *LanternCore) IsPeerShareEnabled() bool { + b, _ := lc.settings()[settings.PeerShareEnabledKey].(bool) + return b +} + func (lc *LanternCore) IsTelemetryEnabled() bool { b, _ := lc.settings()[settings.TelemetryKey].(bool) return b diff --git a/lantern-core/ffi/ffi.go b/lantern-core/ffi/ffi.go index d10403cb72..ca67526fdb 100644 --- a/lantern-core/ffi/ffi.go +++ b/lantern-core/ffi/ffi.go @@ -1352,6 +1352,29 @@ func isSmartRoutingEnabled() C.int { return 0 } +//export setPeerProxyEnabled +func setPeerProxyEnabled(enabled C.int) *C.char { + return runOnGoStack(func() *C.char { + c, errStr := requireCore() + if errStr != nil { + return errStr + } + if err := c.SetPeerShareEnabled(enabled != 0); err != nil { + return SendError(err) + } + return C.CString("ok") + }) +} + +//export isPeerProxyEnabled +func isPeerProxyEnabled() C.int { + c, _ := requireCore() + if c != nil && c.IsPeerShareEnabled() { + return 1 + } + return 0 +} + //export getSplitTunnelState func getSplitTunnelState() *C.char { return runOnGoStack(func() *C.char { diff --git a/lantern-core/mobile/mobile.go b/lantern-core/mobile/mobile.go index 75c4fae397..c04c2929e0 100644 --- a/lantern-core/mobile/mobile.go +++ b/lantern-core/mobile/mobile.go @@ -203,6 +203,23 @@ func SetSmartRoutingEnabled(enabled bool) error { }) } +func SetPeerShareEnabled(enabled bool) error { + slog.Info("peer-share: SetPeerShareEnabled", "enabled", enabled) + return withCore(func(c lanterncore.Core) error { + return c.SetPeerShareEnabled(enabled) + }) +} + +func IsPeerShareEnabled() bool { + ok, err := withCoreR(func(c lanterncore.Core) (bool, error) { + return c.IsPeerShareEnabled(), nil + }) + if err != nil { + return false + } + return ok +} + func IsSmartRoutingEnabled() bool { ok, err := withCoreR(func(c lanterncore.Core) (bool, error) { return c.IsSmartRoutingEnabled(), nil From b742d9b9b6e940f1999566bab39c93bd08f1b338 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Tue, 5 May 2026 06:06:10 -0600 Subject: [PATCH 02/37] Wire Share My Connection toggle in Dart UI (PR 4/4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Final PR in the four-PR stack. Stacks on lantern #8729 (FFI exports); combined with radiance #458 / #460 / lantern-cloud #2678-#2681 this ships a feature-complete Phase 1 of "Share My Connection" for desktop (macOS + Linux + Windows). * lantern_generated_bindings.dart: add setPeerProxyEnabled + isPeerProxyEnabled. Manually inserted to match the existing pattern rather than regenerating the whole file (a local ffigen run from the macOS header would drop ~5K lines of Windows-only declarations the upstream generator emits). * LanternCoreService / LanternFFIService / LanternPlatformService / LanternService: add setPeerProxyEnabled / isPeerProxyEnabled across all four service layers, mirroring the setBlockAdsEnabled pattern. FFI path on isFFISupported platforms (Windows + Linux), MethodChannel fallback on macOS / mobile. * RadianceSettingsState: new peerProxy bool field with copyWith and equality. * RadianceSettings notifier: new setPeerProxy method (pessimistic — call FFI, log on failure, update state on success — matching setBlockAds). _refresh now reads peerProxy alongside the others. * vpn_setting.dart: SwitchButton tile gated to PlatformUtils.isDesktop with i18n strings share_my_connection / share_my_connection_subtitle in en.po. Other locales will pick up via the standard translation flow. Lifecycle end-to-end: Dart toggle → RadianceSettings.setPeerProxy(bool) → LanternService.setPeerProxyEnabled → FFI: setPeerProxyEnabled(int) -> *char → Core.SetPeerShareEnabled(bool) → ipc.Client.PatchSettings({PeerShareEnabledKey: ...}) → radiance LocalBackend.PatchSettings dispatch → peer.Client.Start / Stop → UPnP MapPort + register + sing-box samizdat inbound + heartbeat flutter analyze: clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- assets/locales/en.po | 6 ++++ lib/core/models/radiance_settings_state.dart | 9 +++-- .../provider/radiance_settings_providers.dart | 14 ++++++++ lib/features/setting/vpn_setting.dart | 33 +++++++++++++++++++ lib/lantern/lantern_core_service.dart | 4 +++ lib/lantern/lantern_ffi_service.dart | 28 ++++++++++++++++ lib/lantern/lantern_generated_bindings.dart | 20 +++++++++++ lib/lantern/lantern_platform_service.dart | 24 ++++++++++++++ lib/lantern/lantern_service.dart | 16 +++++++++ 9 files changed, 152 insertions(+), 2 deletions(-) diff --git a/assets/locales/en.po b/assets/locales/en.po index f705fc01c6..303e466d36 100644 --- a/assets/locales/en.po +++ b/assets/locales/en.po @@ -551,6 +551,12 @@ msgstr "Block Ads" msgid "only_active" msgstr "Only active when VPN is connected" +msgid "share_my_connection" +msgstr "Share My Connection" + +msgid "share_my_connection_subtitle" +msgstr "Let other Lantern users route through your connection to bypass censorship." + msgid "vpn_connected" msgstr "Lantern is now connected." diff --git a/lib/core/models/radiance_settings_state.dart b/lib/core/models/radiance_settings_state.dart index 70323e0a13..28026ff95a 100644 --- a/lib/core/models/radiance_settings_state.dart +++ b/lib/core/models/radiance_settings_state.dart @@ -12,12 +12,14 @@ class RadianceSettingsState { final RoutingMode routingMode; final bool splitTunneling; final bool telemetry; + final bool peerProxy; const RadianceSettingsState({ this.blockAds = false, this.routingMode = RoutingMode.full, this.splitTunneling = false, this.telemetry = false, + this.peerProxy = false, }); RadianceSettingsState copyWith({ @@ -25,12 +27,14 @@ class RadianceSettingsState { RoutingMode? routingMode, bool? splitTunneling, bool? telemetry, + bool? peerProxy, }) { return RadianceSettingsState( blockAds: blockAds ?? this.blockAds, routingMode: routingMode ?? this.routingMode, splitTunneling: splitTunneling ?? this.splitTunneling, telemetry: telemetry ?? this.telemetry, + peerProxy: peerProxy ?? this.peerProxy, ); } @@ -41,9 +45,10 @@ class RadianceSettingsState { blockAds == other.blockAds && routingMode == other.routingMode && splitTunneling == other.splitTunneling && - telemetry == other.telemetry; + telemetry == other.telemetry && + peerProxy == other.peerProxy; @override int get hashCode => - Object.hash(blockAds, routingMode, splitTunneling, telemetry); + Object.hash(blockAds, routingMode, splitTunneling, telemetry, peerProxy); } diff --git a/lib/features/home/provider/radiance_settings_providers.dart b/lib/features/home/provider/radiance_settings_providers.dart index ae9cee0f6d..89ececbb66 100644 --- a/lib/features/home/provider/radiance_settings_providers.dart +++ b/lib/features/home/provider/radiance_settings_providers.dart @@ -29,16 +29,19 @@ class RadianceSettings extends _$RadianceSettings { final routingF = svc.isSmartRoutingEnabled(); final telemetryF = svc.isTelemetryEnabled(); final splitF = PlatformUtils.isIOS ? null : svc.isSplitTunnelingEnabled(); + final peerProxyF = svc.isPeerProxyEnabled(); final results = await Future.wait([ blockAdsF, routingF, telemetryF, ?splitF, + peerProxyF, ]); if (!ref.mounted) return; const defaults = RadianceSettingsState(); + final peerIdx = splitF == null ? 3 : 4; state = RadianceSettingsState( blockAds: results[0].fold((_) => defaults.blockAds, (v) => v), routingMode: results[1].fold( @@ -49,6 +52,7 @@ class RadianceSettings extends _$RadianceSettings { splitTunneling: splitF == null ? defaults.splitTunneling : results[3].fold((_) => defaults.splitTunneling, (v) => v), + peerProxy: results[peerIdx].fold((_) => defaults.peerProxy, (v) => v), ); } @@ -97,6 +101,16 @@ class RadianceSettings extends _$RadianceSettings { (_) => state = state.copyWith(telemetry: consent), ); } + + Future setPeerProxy(bool value) async { + final svc = ref.read(lanternServiceProvider); + final result = await svc.setPeerProxyEnabled(value); + if (!ref.mounted) return; + result.fold( + (err) => appLogger.error('setPeerProxyEnabled failed: ${err.error}'), + (_) => state = state.copyWith(peerProxy: value), + ); + } } /// Fetches whether user logged in via OAuth from radiance. diff --git a/lib/features/setting/vpn_setting.dart b/lib/features/setting/vpn_setting.dart index 8c108473a9..ec4d3920a5 100644 --- a/lib/features/setting/vpn_setting.dart +++ b/lib/features/setting/vpn_setting.dart @@ -35,6 +35,9 @@ class VPNSetting extends HookConsumerWidget { final telemetryConsent = ref.watch( radianceSettingsProvider.select((s) => s.telemetry), ); + final peerProxy = ref.watch( + radianceSettingsProvider.select((s) => s.peerProxy), + ); return ListView( padding: const EdgeInsets.all(0), @@ -117,6 +120,36 @@ class VPNSetting extends HookConsumerWidget { }, ), ), + if (PlatformUtils.isDesktop) ...{ + SizedBox(height: 16), + AppCard( + padding: EdgeInsets.zero, + child: AppTile( + label: 'share_my_connection'.i18n, + subtitle: Text( + 'share_my_connection_subtitle'.i18n, + style: textTheme.labelMedium!.copyWith( + color: context.textTertiary, + letterSpacing: 0.0, + ), + ), + icon: AppImagePaths.share, + trailing: SwitchButton( + value: peerProxy, + onChanged: (bool? value) { + ref + .read(radianceSettingsProvider.notifier) + .setPeerProxy(value ?? false); + }, + ), + onPressed: () { + ref + .read(radianceSettingsProvider.notifier) + .setPeerProxy(!peerProxy); + }, + ), + ), + }, SizedBox(height: 16), AppCard( padding: EdgeInsets.zero, diff --git a/lib/lantern/lantern_core_service.dart b/lib/lantern/lantern_core_service.dart index 7dde6b538b..124ad9b1d4 100644 --- a/lib/lantern/lantern_core_service.dart +++ b/lib/lantern/lantern_core_service.dart @@ -79,6 +79,10 @@ abstract class LanternCoreService { Future> isBlockAdsEnabled(); + Future> setPeerProxyEnabled(bool enabled); + + Future> isPeerProxyEnabled(); + Future> isSmartRoutingEnabled(); Future> isTelemetryEnabled(); diff --git a/lib/lantern/lantern_ffi_service.dart b/lib/lantern/lantern_ffi_service.dart index dec4dec5ca..6c206cf5ef 100644 --- a/lib/lantern/lantern_ffi_service.dart +++ b/lib/lantern/lantern_ffi_service.dart @@ -1550,6 +1550,34 @@ class LanternFFIService implements LanternCoreService { } } + @override + Future> setPeerProxyEnabled(bool enabled) async { + try { + final result = await runInBackground(() async { + return _ffiService + .setPeerProxyEnabled(enabled ? 1 : 0) + .cast() + .toDartString(); + }); + checkAPIError(result); + return right(unit); + } catch (e, st) { + appLogger.error('setPeerProxyEnabled error: $e', e, st); + return Left(e.toFailure()); + } + } + + @override + Future> isPeerProxyEnabled() async { + try { + final res = _ffiService.isPeerProxyEnabled(); + return right(res != 0); + } catch (e, st) { + appLogger.error('isPeerProxyEnabled error: $e', e, st); + return Left(e.toFailure()); + } + } + @override Future> isSmartRoutingEnabled() async { try { diff --git a/lib/lantern/lantern_generated_bindings.dart b/lib/lantern/lantern_generated_bindings.dart index 14cc3944cb..315ad4a52c 100644 --- a/lib/lantern/lantern_generated_bindings.dart +++ b/lib/lantern/lantern_generated_bindings.dart @@ -6275,6 +6275,26 @@ class LanternBindings { late final _isBlockAdsEnabled = _isBlockAdsEnabledPtr .asFunction(); + ffi.Pointer setPeerProxyEnabled(int enabled) { + return _setPeerProxyEnabled(enabled); + } + + late final _setPeerProxyEnabledPtr = + _lookup Function(ffi.Int)>>( + 'setPeerProxyEnabled', + ); + late final _setPeerProxyEnabled = _setPeerProxyEnabledPtr + .asFunction Function(int)>(); + + int isPeerProxyEnabled() { + return _isPeerProxyEnabled(); + } + + late final _isPeerProxyEnabledPtr = + _lookup>('isPeerProxyEnabled'); + late final _isPeerProxyEnabled = _isPeerProxyEnabledPtr + .asFunction(); + ffi.Pointer setSmartRoutingEnabled(int enabled) { return _setSmartRoutingEnabled(enabled); } diff --git a/lib/lantern/lantern_platform_service.dart b/lib/lantern/lantern_platform_service.dart index 734c827544..9774c1b7f4 100644 --- a/lib/lantern/lantern_platform_service.dart +++ b/lib/lantern/lantern_platform_service.dart @@ -290,6 +290,30 @@ class LanternPlatformService implements LanternCoreService { } } + @override + Future> setPeerProxyEnabled(bool enabled) async { + try { + await _methodChannel.invokeMethod('setPeerProxyEnabled', { + 'enabled': enabled, + }); + return right(unit); + } catch (e, st) { + appLogger.error('setPeerProxyEnabled failed', e, st); + return Left(e.toFailure()); + } + } + + @override + Future> isPeerProxyEnabled() async { + try { + final res = await _methodChannel.invokeMethod('isPeerProxyEnabled'); + return right(res ?? false); + } catch (e, st) { + appLogger.error('isPeerProxyEnabled failed', e, st); + return Left(e.toFailure()); + } + } + @override Future> isSmartRoutingEnabled() async { try { diff --git a/lib/lantern/lantern_service.dart b/lib/lantern/lantern_service.dart index 2f3ae8fa48..9936cd4164 100644 --- a/lib/lantern/lantern_service.dart +++ b/lib/lantern/lantern_service.dart @@ -794,6 +794,22 @@ class LanternService implements LanternCoreService { return _platformService.setBlockAdsEnabled(enabled); } + @override + Future> isPeerProxyEnabled() { + if (PlatformUtils.isFFISupported) { + return _ffiService.isPeerProxyEnabled(); + } + return _platformService.isPeerProxyEnabled(); + } + + @override + Future> setPeerProxyEnabled(bool enabled) { + if (PlatformUtils.isFFISupported) { + return _ffiService.setPeerProxyEnabled(enabled); + } + return _platformService.setPeerProxyEnabled(enabled); + } + @override Future> isSmartRoutingEnabled() { if (PlatformUtils.isFFISupported) { From 2c44a3f78088d28989a346d5674133849a376d74 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Tue, 5 May 2026 06:14:10 -0600 Subject: [PATCH 03/37] review: gate peer-proxy toggle to FFI-supported platforms Three review comments converged on the same root cause: the toggle was gated to PlatformUtils.isDesktop and the platform-service shims invoked MethodChannel methods that have no native handlers anywhere (Android/iOS/macOS), so on any non-FFI platform the toggle would render but the call would fail with MissingPluginException. * vpn_setting.dart: gate to PlatformUtils.isFFISupported (Windows + Linux), where the FFI path actually drives the toggle. * radiance_settings_providers.dart: skip the isPeerProxyEnabled probe in _refresh on non-FFI platforms so we don't log a failure on every settings init. * lantern_platform_service.dart: replace the MethodChannel passthroughs with explicit "not supported on this platform" stubs. They exist only for LanternCoreService interface conformance; the UI gate prevents them from ever being called. macOS / iOS / Android support requires a native handler (Swift / Kotlin) calling into the Go core; that's a follow-up. flutter analyze: clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../provider/radiance_settings_providers.dart | 13 +++++++--- lib/features/setting/vpn_setting.dart | 2 +- lib/lantern/lantern_platform_service.dart | 25 +++++++------------ 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/lib/features/home/provider/radiance_settings_providers.dart b/lib/features/home/provider/radiance_settings_providers.dart index 89ececbb66..b867592c68 100644 --- a/lib/features/home/provider/radiance_settings_providers.dart +++ b/lib/features/home/provider/radiance_settings_providers.dart @@ -29,19 +29,22 @@ class RadianceSettings extends _$RadianceSettings { final routingF = svc.isSmartRoutingEnabled(); final telemetryF = svc.isTelemetryEnabled(); final splitF = PlatformUtils.isIOS ? null : svc.isSplitTunnelingEnabled(); - final peerProxyF = svc.isPeerProxyEnabled(); + // Peer-proxy probe runs only on platforms with native handlers + // (FFI-supported = Windows + Linux). On other platforms the call would + // fail with MissingPluginException on every settings init. + final peerF = PlatformUtils.isFFISupported ? svc.isPeerProxyEnabled() : null; final results = await Future.wait([ blockAdsF, routingF, telemetryF, ?splitF, - peerProxyF, + ?peerF, ]); if (!ref.mounted) return; const defaults = RadianceSettingsState(); - final peerIdx = splitF == null ? 3 : 4; + final peerIdx = 3 + (splitF == null ? 0 : 1); state = RadianceSettingsState( blockAds: results[0].fold((_) => defaults.blockAds, (v) => v), routingMode: results[1].fold( @@ -52,7 +55,9 @@ class RadianceSettings extends _$RadianceSettings { splitTunneling: splitF == null ? defaults.splitTunneling : results[3].fold((_) => defaults.splitTunneling, (v) => v), - peerProxy: results[peerIdx].fold((_) => defaults.peerProxy, (v) => v), + peerProxy: peerF == null + ? defaults.peerProxy + : results[peerIdx].fold((_) => defaults.peerProxy, (v) => v), ); } diff --git a/lib/features/setting/vpn_setting.dart b/lib/features/setting/vpn_setting.dart index ec4d3920a5..a50279c94a 100644 --- a/lib/features/setting/vpn_setting.dart +++ b/lib/features/setting/vpn_setting.dart @@ -120,7 +120,7 @@ class VPNSetting extends HookConsumerWidget { }, ), ), - if (PlatformUtils.isDesktop) ...{ + if (PlatformUtils.isFFISupported) ...{ SizedBox(height: 16), AppCard( padding: EdgeInsets.zero, diff --git a/lib/lantern/lantern_platform_service.dart b/lib/lantern/lantern_platform_service.dart index 9774c1b7f4..038e6e8b39 100644 --- a/lib/lantern/lantern_platform_service.dart +++ b/lib/lantern/lantern_platform_service.dart @@ -290,28 +290,21 @@ class LanternPlatformService implements LanternCoreService { } } + // Peer-proxy has no native MethodChannel handler outside the FFI path. + // The toggle is gated to PlatformUtils.isFFISupported in the UI, so these + // implementations exist for interface conformance only and return a clear + // error rather than a MissingPluginException if ever invoked. @override Future> setPeerProxyEnabled(bool enabled) async { - try { - await _methodChannel.invokeMethod('setPeerProxyEnabled', { - 'enabled': enabled, - }); - return right(unit); - } catch (e, st) { - appLogger.error('setPeerProxyEnabled failed', e, st); - return Left(e.toFailure()); - } + return Left(Failure( + error: 'peer-proxy not supported on this platform', + localizedErrorMessage: 'peer-proxy not supported on this platform', + )); } @override Future> isPeerProxyEnabled() async { - try { - final res = await _methodChannel.invokeMethod('isPeerProxyEnabled'); - return right(res ?? false); - } catch (e, st) { - appLogger.error('isPeerProxyEnabled failed', e, st); - return Left(e.toFailure()); - } + return right(false); } @override From 05473d6473539f4edfed3bb97f7265f7126f3134 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Tue, 5 May 2026 10:47:18 -0600 Subject: [PATCH 04/37] peer-proxy: add macOS native handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit macOS routes through MethodChannel → Swift → MobileSetPeerShareEnabled (gomobile-bind) rather than the FFI path that Windows + Linux use. The previous review fix gated the toggle to PlatformUtils.isFFISupported to avoid a MissingPluginException on macOS, but per Phase 1 plan macOS should be supported. * macos/Runner/Handlers/MethodHandler.swift: new setPeerProxyEnabled case + setPeerProxyEnabled function calling MobileSetPeerShareEnabled, plus an isPeerProxyEnabled case calling MobileIsPeerShareEnabled. Mirrors the existing setBlockAdsEnabled handler exactly. (The MobileSet/IsPeerShareEnabled gomobile bindings come from the SetPeerShareEnabled / IsPeerShareEnabled methods added to lantern-core/mobile/mobile.go in PR 8729; the Liblantern xcframework needs a rebuild via `make macos-framework` to pick them up.) * lantern_platform_service.dart: restore the MethodChannel passthrough for setPeerProxyEnabled / isPeerProxyEnabled. The "not supported on this platform" stubs from the prior review fix are no longer appropriate now that there's a native handler. * vpn_setting.dart: widen the toggle gate from isFFISupported (Windows + Linux) to isDesktop (Windows + Linux + macOS). * radiance_settings_providers.dart: same widening for the isPeerProxyEnabled probe in _refresh. Verified locally: `make macos-framework` rebuilds successfully and exports MobileSetPeerShareEnabled / MobileIsPeerShareEnabled. flutter analyze clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../provider/radiance_settings_providers.dart | 7 +++--- lib/features/setting/vpn_setting.dart | 2 +- lib/lantern/lantern_platform_service.dart | 25 ++++++++++++------- macos/Runner/Handlers/MethodHandler.swift | 24 ++++++++++++++++++ 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/lib/features/home/provider/radiance_settings_providers.dart b/lib/features/home/provider/radiance_settings_providers.dart index b867592c68..be8900aede 100644 --- a/lib/features/home/provider/radiance_settings_providers.dart +++ b/lib/features/home/provider/radiance_settings_providers.dart @@ -30,9 +30,10 @@ class RadianceSettings extends _$RadianceSettings { final telemetryF = svc.isTelemetryEnabled(); final splitF = PlatformUtils.isIOS ? null : svc.isSplitTunnelingEnabled(); // Peer-proxy probe runs only on platforms with native handlers - // (FFI-supported = Windows + Linux). On other platforms the call would - // fail with MissingPluginException on every settings init. - final peerF = PlatformUtils.isFFISupported ? svc.isPeerProxyEnabled() : null; + // (Windows + Linux via FFI, macOS via MethodChannel — i.e. all desktop). + // On iOS / Android the call would fail with MissingPluginException on + // every settings init. + final peerF = PlatformUtils.isDesktop ? svc.isPeerProxyEnabled() : null; final results = await Future.wait([ blockAdsF, diff --git a/lib/features/setting/vpn_setting.dart b/lib/features/setting/vpn_setting.dart index a50279c94a..ec4d3920a5 100644 --- a/lib/features/setting/vpn_setting.dart +++ b/lib/features/setting/vpn_setting.dart @@ -120,7 +120,7 @@ class VPNSetting extends HookConsumerWidget { }, ), ), - if (PlatformUtils.isFFISupported) ...{ + if (PlatformUtils.isDesktop) ...{ SizedBox(height: 16), AppCard( padding: EdgeInsets.zero, diff --git a/lib/lantern/lantern_platform_service.dart b/lib/lantern/lantern_platform_service.dart index 038e6e8b39..9774c1b7f4 100644 --- a/lib/lantern/lantern_platform_service.dart +++ b/lib/lantern/lantern_platform_service.dart @@ -290,21 +290,28 @@ class LanternPlatformService implements LanternCoreService { } } - // Peer-proxy has no native MethodChannel handler outside the FFI path. - // The toggle is gated to PlatformUtils.isFFISupported in the UI, so these - // implementations exist for interface conformance only and return a clear - // error rather than a MissingPluginException if ever invoked. @override Future> setPeerProxyEnabled(bool enabled) async { - return Left(Failure( - error: 'peer-proxy not supported on this platform', - localizedErrorMessage: 'peer-proxy not supported on this platform', - )); + try { + await _methodChannel.invokeMethod('setPeerProxyEnabled', { + 'enabled': enabled, + }); + return right(unit); + } catch (e, st) { + appLogger.error('setPeerProxyEnabled failed', e, st); + return Left(e.toFailure()); + } } @override Future> isPeerProxyEnabled() async { - return right(false); + try { + final res = await _methodChannel.invokeMethod('isPeerProxyEnabled'); + return right(res ?? false); + } catch (e, st) { + appLogger.error('isPeerProxyEnabled failed', e, st); + return Left(e.toFailure()); + } } @override diff --git a/macos/Runner/Handlers/MethodHandler.swift b/macos/Runner/Handlers/MethodHandler.swift index d41e93e30d..6eaa33d4b2 100644 --- a/macos/Runner/Handlers/MethodHandler.swift +++ b/macos/Runner/Handlers/MethodHandler.swift @@ -245,6 +245,16 @@ class MethodHandler { let enabled = data?["enabled"] as? Bool ?? false self.setBlockAdsEnabled(result: result, enabled: enabled) + case "isPeerProxyEnabled": + Task { + await MainActor.run { result(MobileIsPeerShareEnabled()) } + } + + case "setPeerProxyEnabled": + let data = call.arguments as? [String: Any] + let enabled = data?["enabled"] as? Bool ?? false + self.setPeerProxyEnabled(result: result, enabled: enabled) + case "updateTelemetryEvents": guard let consent: Bool = self.decodeValue(from: call.arguments, result: result) else { return @@ -1152,6 +1162,20 @@ class MethodHandler { } } + func setPeerProxyEnabled(result: @escaping FlutterResult, enabled: Bool) { + Task { + var error: NSError? + MobileSetPeerShareEnabled(enabled, &error) + if let error { + await self.handleFlutterError(error, result: result, code: "SET_PEER_PROXY_ERROR") + return + } + await MainActor.run { + result("ok") + } + } + } + func updateTelemetryEvents(consent: Bool, result: @escaping FlutterResult) { Task { var error: NSError? From 55610697f1948ebc079af0312a152e01a8780eeb Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Thu, 7 May 2026 11:44:42 -0600 Subject: [PATCH 05/37] Prototype: unified Share My Connection screen with globe + SmC disclosure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UX prototype combining the Unbounded globe work (from Jigar's #8493 + Adam's #8492) with the Share My Connection FFI plumbing already on this branch. One unified screen, one toggle, one globe — auto-picks SmC when UPnP works and the user accepts the one-time disclosure, otherwise falls back to Unbounded. Backend wiring is mocked for the prototype: - UPnP probe is a 1.5s delay returning a coin-flip (so the demo exercises both the SmC and Unbounded paths across runs) - Connection events come from a 3s timer cycling through canned residential IPs in IR/CN/RU/TR/VN/PK/EG/MM, so the globe arcs animate while the screen is visible Real wiring (radiance peer module event emit, broflake OnConnectionChange plumb-through, persisted SmC acknowledgment, real UPnP probe via FFI) follows once we land the security review CRITICALs (C1/C2/C3). Reuses Jigar's flutter_earth_globe approach verbatim — uv-map textures, GeoLookupService, _GlobeView pattern with addPointConnection arcs. Co-Authored-By: Claude Opus 4.7 (1M context) --- assets/unbounded/uv-map-dark.png | Bin 0 -> 104139 bytes assets/unbounded/uv-map.png | Bin 0 -> 69958 bytes .../models/unbounded_connection_event.dart | 28 + lib/core/services/geo_lookup_service.dart | 173 ++++++ lib/features/setting/vpn_setting.dart | 23 +- .../share_my_connection.dart | 536 ++++++++++++++++++ pubspec.lock | 10 +- pubspec.yaml | 3 + 8 files changed, 761 insertions(+), 12 deletions(-) create mode 100644 assets/unbounded/uv-map-dark.png create mode 100644 assets/unbounded/uv-map.png create mode 100644 lib/core/models/unbounded_connection_event.dart create mode 100644 lib/core/services/geo_lookup_service.dart create mode 100644 lib/features/share_my_connection/share_my_connection.dart diff --git a/assets/unbounded/uv-map-dark.png b/assets/unbounded/uv-map-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..afe9a07568135b06ee2dd2ed13e13f590bef0052 GIT binary patch literal 104139 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 literal 0 HcmV?d00001 diff --git a/assets/unbounded/uv-map.png b/assets/unbounded/uv-map.png new file mode 100644 index 0000000000000000000000000000000000000000..98953b96df72b5536bdc44bdb0c6ecba9d8dc83d GIT binary patch literal 69958 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 ae4XNc-t*N?EnpEq*2iprulUXTU;hCokchJY literal 0 HcmV?d00001 diff --git a/lib/core/models/unbounded_connection_event.dart b/lib/core/models/unbounded_connection_event.dart new file mode 100644 index 0000000000..eb643f255a --- /dev/null +++ b/lib/core/models/unbounded_connection_event.dart @@ -0,0 +1,28 @@ +/// Represents a consumer connection change from the broflake widget proxy. +class UnboundedConnectionEvent { + final int state; // 1 = connected, -1 = disconnected + final int workerIdx; + final String addr; // IP address + + UnboundedConnectionEvent({ + required this.state, + required this.workerIdx, + required this.addr, + }); + + factory UnboundedConnectionEvent.fromJson(Map json) { + return UnboundedConnectionEvent( + state: json['state'] as int, + workerIdx: json['workerIdx'] as int, + addr: json['addr'] as String? ?? '', + ); + } +} + +/// Tracks live and cumulative connection counts for Unbounded. +class UnboundedStats { + final int activeCount; + final int totalCount; + + const UnboundedStats({this.activeCount = 0, this.totalCount = 0}); +} diff --git a/lib/core/services/geo_lookup_service.dart b/lib/core/services/geo_lookup_service.dart new file mode 100644 index 0000000000..50150fe375 --- /dev/null +++ b/lib/core/services/geo_lookup_service.dart @@ -0,0 +1,173 @@ +import 'dart:convert'; + +import 'package:flutter_earth_globe/globe_coordinates.dart'; +import 'package:http/http.dart' as http; + +class GeoLookupService { + static const _geoUrl = 'https://geo.getiantem.org'; + + // ISO country code → approximate centre coordinates + static const _countries = { + 'AF': (lat: 33.0, lng: 65.0), + 'AL': (lat: 41.0, lng: 20.0), + 'DZ': (lat: 28.0, lng: 3.0), + 'AD': (lat: 42.5, lng: 1.6), + 'AO': (lat: -12.5, lng: 18.5), + 'AR': (lat: -34.0, lng: -64.0), + 'AM': (lat: 40.0, lng: 45.0), + 'AU': (lat: -27.0, lng: 133.0), + 'AT': (lat: 47.33, lng: 13.33), + 'AZ': (lat: 40.5, lng: 47.5), + 'BD': (lat: 24.0, lng: 90.0), + 'BY': (lat: 53.0, lng: 28.0), + 'BE': (lat: 50.83, lng: 4.0), + 'BJ': (lat: 9.5, lng: 2.25), + 'BO': (lat: -17.0, lng: -65.0), + 'BA': (lat: 44.0, lng: 18.0), + 'BR': (lat: -10.0, lng: -55.0), + 'BG': (lat: 43.0, lng: 25.0), + 'KH': (lat: 13.0, lng: 105.0), + 'CM': (lat: 6.0, lng: 12.0), + 'CA': (lat: 60.0, lng: -95.0), + 'CL': (lat: -30.0, lng: -71.0), + 'CN': (lat: 35.0, lng: 105.0), + 'CO': (lat: 4.0, lng: -72.0), + 'CD': (lat: 0.0, lng: 25.0), + 'CR': (lat: 10.0, lng: -84.0), + 'HR': (lat: 45.17, lng: 15.5), + 'CU': (lat: 21.5, lng: -80.0), + 'CZ': (lat: 49.75, lng: 15.5), + 'DK': (lat: 56.0, lng: 10.0), + 'DO': (lat: 19.0, lng: -70.67), + 'EC': (lat: -2.0, lng: -77.5), + 'EG': (lat: 27.0, lng: 30.0), + 'SV': (lat: 13.83, lng: -88.92), + 'EE': (lat: 59.0, lng: 26.0), + 'ET': (lat: 8.0, lng: 38.0), + 'FI': (lat: 64.0, lng: 26.0), + 'FR': (lat: 46.0, lng: 2.0), + 'GE': (lat: 42.0, lng: 43.5), + 'DE': (lat: 51.0, lng: 9.0), + 'GH': (lat: 8.0, lng: -2.0), + 'GR': (lat: 39.0, lng: 22.0), + 'GT': (lat: 15.5, lng: -90.25), + 'HN': (lat: 15.0, lng: -86.5), + 'HK': (lat: 22.25, lng: 114.17), + 'HU': (lat: 47.0, lng: 20.0), + 'IS': (lat: 65.0, lng: -18.0), + 'IN': (lat: 20.0, lng: 77.0), + 'ID': (lat: -5.0, lng: 120.0), + 'IR': (lat: 32.0, lng: 53.0), + 'IQ': (lat: 33.0, lng: 44.0), + 'IE': (lat: 53.0, lng: -8.0), + 'IL': (lat: 31.5, lng: 34.75), + 'IT': (lat: 42.83, lng: 12.83), + 'CI': (lat: 8.0, lng: -5.0), + 'JP': (lat: 36.0, lng: 138.0), + 'JO': (lat: 31.0, lng: 36.0), + 'KZ': (lat: 48.0, lng: 68.0), + 'KE': (lat: 1.0, lng: 38.0), + 'KR': (lat: 37.0, lng: 127.5), + 'KW': (lat: 29.34, lng: 47.66), + 'KG': (lat: 41.0, lng: 75.0), + 'LA': (lat: 18.0, lng: 105.0), + 'LV': (lat: 57.0, lng: 25.0), + 'LB': (lat: 33.83, lng: 35.83), + 'LT': (lat: 56.0, lng: 24.0), + 'MG': (lat: -20.0, lng: 47.0), + 'MY': (lat: 2.5, lng: 112.5), + 'ML': (lat: 17.0, lng: -4.0), + 'MX': (lat: 23.0, lng: -102.0), + 'MD': (lat: 47.0, lng: 29.0), + 'MN': (lat: 46.0, lng: 105.0), + 'MA': (lat: 32.0, lng: -5.0), + 'MZ': (lat: -18.25, lng: 35.0), + 'MM': (lat: 22.0, lng: 98.0), + 'NP': (lat: 28.0, lng: 84.0), + 'NL': (lat: 52.5, lng: 5.75), + 'NZ': (lat: -41.0, lng: 174.0), + 'NI': (lat: 13.0, lng: -85.0), + 'NG': (lat: 10.0, lng: 8.0), + 'NO': (lat: 62.0, lng: 10.0), + 'OM': (lat: 21.0, lng: 57.0), + 'PK': (lat: 30.0, lng: 70.0), + 'PA': (lat: 9.0, lng: -80.0), + 'PY': (lat: -23.0, lng: -58.0), + 'PE': (lat: -10.0, lng: -76.0), + 'PH': (lat: 13.0, lng: 122.0), + 'PL': (lat: 52.0, lng: 20.0), + 'PT': (lat: 39.5, lng: -8.0), + 'QA': (lat: 25.5, lng: 51.25), + 'RO': (lat: 46.0, lng: 25.0), + 'RU': (lat: 60.0, lng: 100.0), + 'SA': (lat: 25.0, lng: 45.0), + 'SN': (lat: 14.0, lng: -14.0), + 'RS': (lat: 44.0, lng: 21.0), + 'SG': (lat: 1.37, lng: 103.8), + 'SK': (lat: 48.67, lng: 19.5), + 'SI': (lat: 46.0, lng: 15.0), + 'ZA': (lat: -29.0, lng: 24.0), + 'ES': (lat: 40.0, lng: -4.0), + 'LK': (lat: 7.0, lng: 81.0), + 'SE': (lat: 62.0, lng: 15.0), + 'CH': (lat: 47.0, lng: 8.0), + 'SY': (lat: 35.0, lng: 38.0), + 'TW': (lat: 23.5, lng: 121.0), + 'TJ': (lat: 39.0, lng: 71.0), + 'TZ': (lat: -6.0, lng: 35.0), + 'TH': (lat: 15.0, lng: 100.0), + 'TN': (lat: 34.0, lng: 9.0), + 'TR': (lat: 39.0, lng: 35.0), + 'TM': (lat: 40.0, lng: 60.0), + 'UA': (lat: 49.0, lng: 32.0), + 'AE': (lat: 24.0, lng: 54.0), + 'GB': (lat: 54.0, lng: -2.0), + 'US': (lat: 38.0, lng: -97.0), + 'UY': (lat: -33.0, lng: -56.0), + 'UZ': (lat: 41.0, lng: 64.0), + 'VE': (lat: 8.0, lng: -66.0), + 'VN': (lat: 16.0, lng: 106.0), + 'YE': (lat: 15.0, lng: 48.0), + 'ZM': (lat: -15.0, lng: 30.0), + 'ZW': (lat: -20.0, lng: 30.0), + }; + + static GlobeCoordinates _isoToCoords(String iso) { + final c = _countries[iso] ?? _countries['US']!; + return GlobeCoordinates(c.lat, c.lng); + } + + /// Looks up the current device's location (no IP argument). + static Future selfLookup() async { + try { + final response = await http + .get(Uri.parse('$_geoUrl/')) + .timeout(const Duration(seconds: 5)); + if (response.statusCode == 200) { + final data = jsonDecode(response.body) as Map; + final iso = + (data['Country'] as Map?)?['IsoCode'] as String? ?? + 'US'; + return _isoToCoords(iso); + } + } catch (_) {} + return _isoToCoords('US'); + } + + /// Looks up the country for a peer [ip] address. + static Future peerLookup(String ip) async { + try { + final response = await http + .get(Uri.parse('$_geoUrl/$ip')) + .timeout(const Duration(seconds: 5)); + if (response.statusCode == 200) { + final data = jsonDecode(response.body) as Map; + final iso = + (data['Country'] as Map?)?['IsoCode'] as String? ?? + 'IR'; + return _isoToCoords(iso); + } + } catch (_) {} + return _isoToCoords('IR'); + } +} diff --git a/lib/features/setting/vpn_setting.dart b/lib/features/setting/vpn_setting.dart index ec4d3920a5..cfcac3afd7 100644 --- a/lib/features/setting/vpn_setting.dart +++ b/lib/features/setting/vpn_setting.dart @@ -6,6 +6,7 @@ import 'package:lantern/core/common/common.dart'; import 'package:lantern/core/widgets/split_tunneling_tile.dart'; import 'package:lantern/core/widgets/switch_button.dart'; import 'package:lantern/features/home/provider/radiance_settings_providers.dart'; +import 'package:lantern/features/share_my_connection/share_my_connection.dart'; @RoutePage(name: 'VPNSetting') class VPNSetting extends HookConsumerWidget { @@ -127,25 +128,25 @@ class VPNSetting extends HookConsumerWidget { child: AppTile( label: 'share_my_connection'.i18n, subtitle: Text( - 'share_my_connection_subtitle'.i18n, + peerProxy + ? 'On — tap to view' + : 'share_my_connection_subtitle'.i18n, style: textTheme.labelMedium!.copyWith( color: context.textTertiary, letterSpacing: 0.0, ), ), icon: AppImagePaths.share, - trailing: SwitchButton( - value: peerProxy, - onChanged: (bool? value) { - ref - .read(radianceSettingsProvider.notifier) - .setPeerProxy(value ?? false); - }, + trailing: AppImage( + path: AppImagePaths.arrowForward, + height: 20, ), onPressed: () { - ref - .read(radianceSettingsProvider.notifier) - .setPeerProxy(!peerProxy); + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => const ShareMyConnectionScreen(), + ), + ); }, ), ), diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart new file mode 100644 index 0000000000..14c385dcdd --- /dev/null +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -0,0 +1,536 @@ +// Share My Connection — UX prototype. +// One unified screen for both protocols (Unbounded / Share-My-Connection): +// - Toggle ON triggers a (mocked) UPnP probe. +// - If UPnP works AND the user accepts the SmC disclosure, run SmC mode. +// - Otherwise fall back to Unbounded mode. +// - Globe animates connection arcs from a stream of UnboundedConnectionEvent. +// +// All wire-up to radiance / FFI is stubbed for this prototype: the UPnP probe +// returns success after a short delay, and connection events are fired by a +// timer cycling through a small set of canned residential peer IPs so the +// globe actually animates while the screen is visible. + +import 'dart:async'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_earth_globe/flutter_earth_globe.dart'; +import 'package:flutter_earth_globe/flutter_earth_globe_controller.dart'; +import 'package:flutter_earth_globe/globe_coordinates.dart'; +import 'package:flutter_earth_globe/point.dart'; +import 'package:flutter_earth_globe/point_connection.dart'; +import 'package:flutter_earth_globe/point_connection_style.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:lantern/core/common/common.dart'; +import 'package:lantern/core/models/unbounded_connection_event.dart'; +import 'package:lantern/core/services/geo_lookup_service.dart'; + +// ─── State ─────────────────────────────────────────────────────────────────── + +/// Which underlying protocol the user is contributing through. +/// +/// off — toggle is off / probe in flight +/// unbounded — broflake / WebRTC widget proxy (works on any network) +/// smc — samizdat-over-UPnP "Share My Connection" (higher capability, +/// higher risk; gated on a one-time disclosure) +enum ShareMode { off, unbounded, smc } + +class ShareState { + final bool active; + final bool probing; + final ShareMode mode; + final int activeCount; + final int totalCount; + + const ShareState({ + this.active = false, + this.probing = false, + this.mode = ShareMode.off, + this.activeCount = 0, + this.totalCount = 0, + }); + + ShareState copyWith({ + bool? active, + bool? probing, + ShareMode? mode, + int? activeCount, + int? totalCount, + }) => + ShareState( + active: active ?? this.active, + probing: probing ?? this.probing, + mode: mode ?? this.mode, + activeCount: activeCount ?? this.activeCount, + totalCount: totalCount ?? this.totalCount, + ); +} + +// ─── Notifier (mock-backed) ────────────────────────────────────────────────── + +class ShareNotifier extends Notifier { + // Persisted in real impl; in-process for the prototype so the disclosure + // re-fires on app restart and is easy to demo. + bool _smcAck = false; + + Timer? _mockTimer; + int _workerSeq = 0; + final List _activeWorkers = []; + + final _eventController = + StreamController.broadcast(); + Stream get connectionEvents => + _eventController.stream; + + @override + ShareState build() { + ref.onDispose(() { + _stopMockEvents(); + _eventController.close(); + }); + return const ShareState(); + } + + /// Toggle entry point. Caller passes its BuildContext so we can show the + /// disclosure modal inline. + Future toggle(BuildContext context) async { + if (state.active || state.probing) { + _stop(); + return; + } + + state = state.copyWith(probing: true); + + // MOCK: real impl will FFI into radiance/portforward to probe UPnP. + // Coin-flip the result so the demo exercises both the SmC and Unbounded + // paths across runs; flip to `true` for the SmC path while iterating on + // the disclosure copy. + await Future.delayed(const Duration(milliseconds: 1500)); + final upnpAvailable = Random().nextBool(); + if (!upnpAvailable) { + _start(ShareMode.unbounded); + return; + } + + if (_smcAck) { + _start(ShareMode.smc); + return; + } + + if (!context.mounted) { + state = state.copyWith(probing: false); + return; + } + + final accepted = await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const SmcDisclosureDialog(), + ); + + if (accepted == null) { + // User dismissed without choosing — leave off. + state = state.copyWith(probing: false); + return; + } + if (accepted) { + _smcAck = true; + _start(ShareMode.smc); + } else { + _start(ShareMode.unbounded); + } + } + + void _start(ShareMode mode) { + state = ShareState( + active: true, + probing: false, + mode: mode, + activeCount: 0, + totalCount: 0, + ); + _startMockEvents(); + } + + void _stop() { + _stopMockEvents(); + state = const ShareState(); + } + + // ── Mock connection event source ─────────────────────────────────────────── + // A small canned set of (country, IP) pairs heavy on Lantern's priority + // censored regions, so the globe shows arcs landing where users actually + // benefit from the network. Real impl will subscribe to a radiance event. + + static const _peerIPs = [ + '5.190.10.5', // IR + '120.196.10.5', // CN + '95.165.10.5', // RU + '85.159.10.5', // TR + '113.161.10.5', // VN + '111.68.10.5', // PK + '156.197.10.5', // EG + '103.81.10.5', // MM + '37.156.10.5', // IR (second) + '202.108.10.5', // CN (second) + ]; + + void _startMockEvents() { + final rand = Random(); + _mockTimer = Timer.periodic(const Duration(seconds: 3), (_) { + // Bias toward connecting until we have ~4 active, then 50/50 churn. + final shouldConnect = + _activeWorkers.length < 4 || rand.nextDouble() < 0.5; + + if (shouldConnect) { + final addr = _peerIPs[rand.nextInt(_peerIPs.length)]; + final widx = _workerSeq++; + _activeWorkers.add(widx); + _eventController.add(UnboundedConnectionEvent( + state: 1, + workerIdx: widx, + addr: addr, + )); + state = state.copyWith( + activeCount: state.activeCount + 1, + totalCount: state.totalCount + 1, + ); + } else if (_activeWorkers.isNotEmpty) { + final widx = _activeWorkers.removeAt(0); + _eventController.add(UnboundedConnectionEvent( + state: -1, + workerIdx: widx, + addr: '', + )); + state = state.copyWith( + activeCount: max(0, state.activeCount - 1), + ); + } + }); + } + + void _stopMockEvents() { + _mockTimer?.cancel(); + _mockTimer = null; + _activeWorkers.clear(); + _workerSeq = 0; + } +} + +final shareProvider = + NotifierProvider(ShareNotifier.new); + +// ─── Screen ────────────────────────────────────────────────────────────────── + +class ShareMyConnectionScreen extends HookConsumerWidget { + const ShareMyConnectionScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(shareProvider); + final notifier = ref.read(shareProvider.notifier); + final textTheme = Theme.of(context).textTheme; + + return BaseScreen( + title: 'Share My Connection', + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + children: [ + const SizedBox(height: 12), + Text( + 'Help others bypass censorship by sharing a small portion of ' + 'your home internet connection. While sharing is on, traffic ' + 'from users in censored regions will egress through your IP.', + style: textTheme.bodyMedium, + ), + const SizedBox(height: 16), + Expanded( + flex: 3, + child: _GlobeView(), + ), + const SizedBox(height: 8), + _StatusCard(state: state, onToggle: () => notifier.toggle(context)), + const SizedBox(height: 16), + ], + ), + ), + ); + } +} + +// ─── Status card ───────────────────────────────────────────────────────────── + +class _StatusCard extends StatelessWidget { + final ShareState state; + final VoidCallback onToggle; + + const _StatusCard({required this.state, required this.onToggle}); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + final modeLabel = switch (state.mode) { + ShareMode.off => state.probing ? 'Probing your network…' : 'Off', + ShareMode.unbounded => + 'Active — sharing via Unbounded (WebRTC)', + ShareMode.smc => + 'Active — sharing via Share My Connection (residential proxy)', + }; + + return Container( + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.black12), + ), + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Status', + style: textTheme.labelLarge, + ), + const SizedBox(height: 4), + Text( + modeLabel, + style: textTheme.bodyMedium?.copyWith( + color: state.active + ? AppColors.blue4 + : Theme.of(context).hintColor, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + Switch( + value: state.active || state.probing, + onChanged: state.probing ? null : (_) => onToggle(), + ), + ], + ), + if (state.active) ...[ + const SizedBox(height: 12), + const Divider(height: 1), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _Stat(label: 'Active now', value: '${state.activeCount}'), + _Stat(label: 'Total today', value: '${state.totalCount}'), + ], + ), + ], + ], + ), + ); + } +} + +class _Stat extends StatelessWidget { + final String label; + final String value; + const _Stat({required this.label, required this.value}); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + return Column( + children: [ + Text(value, style: textTheme.headlineSmall), + Text(label, style: textTheme.labelSmall), + ], + ); + } +} + +// ─── Globe ─────────────────────────────────────────────────────────────────── + +class _GlobeView extends ConsumerStatefulWidget { + @override + ConsumerState<_GlobeView> createState() => _GlobeViewState(); +} + +class _GlobeViewState extends ConsumerState<_GlobeView> { + static final _arcColor = AppColors.blue4.withValues(alpha: 0.75); + static final _originPointColor = AppColors.blue4.withValues(alpha: 0.15); + static final _peerPointColor = AppColors.yellow3.withValues(alpha: 0.15); + static const _atmosphereDark = AppColors.blue4; + static const _atmosphereLight = AppColors.blue6; + + final FlutterEarthGlobeController _globeController = + FlutterEarthGlobeController( + isRotating: true, + rotationSpeed: 0.04, + zoom: 0, + isZoomEnabled: false, + showAtmosphere: true, + atmosphereColor: _atmosphereDark, + atmosphereOpacity: 0.2, + atmosphereBlur: 20, + ); + + StreamSubscription? _eventSub; + GlobeCoordinates? _originCoords; + + @override + void initState() { + super.initState(); + _globeController.onLoaded = () { + if (!mounted) return; + _applyTheme(); + }; + _initOrigin(); + _eventSub = ref + .read(shareProvider.notifier) + .connectionEvents + .listen(_handleEvent); + } + + @override + void dispose() { + _eventSub?.cancel(); + _globeController.dispose(); + super.dispose(); + } + + void _applyTheme() { + final isDark = Theme.of(context).brightness == Brightness.dark; + _globeController.loadSurface(AssetImage( + isDark + ? 'assets/unbounded/uv-map-dark.png' + : 'assets/unbounded/uv-map.png', + )); + _globeController.atmosphereColor = + isDark ? _atmosphereDark : _atmosphereLight; + } + + Future _initOrigin() async { + final coords = await GeoLookupService.selfLookup(); + if (!mounted) return; + _originCoords = coords; + _globeController.addPoint(Point( + id: 'origin', + coordinates: coords, + style: PointStyle(color: _originPointColor, size: 8), + )); + } + + Future _handleEvent(UnboundedConnectionEvent event) async { + if (event.state == 1 && event.addr.isNotEmpty) { + await _addPeer(event.workerIdx, event.addr); + } else if (event.state == -1) { + _removePeer(event.workerIdx); + } + } + + Future _addPeer(int workerIdx, String addr) async { + final coords = await GeoLookupService.peerLookup(addr); + if (!mounted) return; + _globeController.addPointConnection(PointConnection( + id: 'conn_$workerIdx', + start: _originCoords ?? const GlobeCoordinates(0, 0), + end: coords, + curveScale: .6, + style: PointConnectionStyle( + color: _arcColor, + lineWidth: 3, + type: PointConnectionType.solid, + dashAnimateTime: 1000, + dashSize: 13, + spacing: 15, + dotSize: 10, + animateOnAdd: true, + ), + )); + _globeController.addPoint(Point( + id: 'peer_$workerIdx', + coordinates: coords, + style: PointStyle(color: _peerPointColor, size: 6), + )); + } + + void _removePeer(int workerIdx) { + _globeController.removePointConnection('conn_$workerIdx'); + _globeController.removePoint('peer_$workerIdx'); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final radius = + min(constraints.maxWidth, constraints.maxHeight) / 2 * 0.7; + return FlutterEarthGlobe( + controller: _globeController, + radius: radius, + alignment: Alignment.center, + onZoomChanged: (_) {}, + ); + }, + ); + } +} + +// ─── Disclosure dialog ─────────────────────────────────────────────────────── + +class SmcDisclosureDialog extends StatelessWidget { + const SmcDisclosureDialog({super.key}); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + return AlertDialog( + title: const Text('Use full Share My Connection?'), + content: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Your network supports the higher-bandwidth, more ' + 'block-resistant mode. In this mode, your home internet ' + 'connection routes traffic for users in censored countries.', + style: textTheme.bodyMedium, + ), + const SizedBox(height: 12), + Text( + 'Lantern blocks abuse destinations, rotates credentials, ' + 'and operates as a "mere conduit" under DMCA § 512(a) — ' + 'but your IP address will appear in the destination\'s ' + 'logs while you\'re sharing.', + style: textTheme.bodyMedium, + ), + const SizedBox(height: 12), + Text( + 'You can still help by selecting "Basic mode" instead, ' + 'which uses ephemeral WebRTC connections that are not ' + 'tied to your IP in the same way.', + style: textTheme.bodyMedium?.copyWith( + color: Theme.of(context).hintColor, + ), + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: const Text('Basic mode (Unbounded)'), + ), + FilledButton( + onPressed: () => Navigator.of(context).pop(true), + child: const Text('Full mode (SmC)'), + ), + ], + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index aa05b04761..204a170f99 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -547,6 +547,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_earth_globe: + dependency: "direct main" + description: + name: flutter_earth_globe + sha256: a5461c43f4dbf1c1af4e8fa46293c83e97b8af7908969ccd6c324282e60d9a31 + url: "https://pub.dev" + source: hosted + version: "2.2.1" flutter_hooks: dependency: "direct main" description: @@ -832,7 +840,7 @@ packages: source: hosted version: "4.3.0" http: - dependency: transitive + dependency: "direct main" description: name: http sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" diff --git a/pubspec.yaml b/pubspec.yaml index b29849c89c..919146cfe8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -46,6 +46,8 @@ dependencies: #Routing auto_route: ^11.1.0 #UI Utils + flutter_earth_globe: ^2.2.0 + http: ^1.2.2 animated_toggle_switch: ^0.8.7 animated_text_kit: ^4.3.0 flutter_screenutil: ^5.9.3 @@ -163,6 +165,7 @@ flutter: platforms: - windows - assets/locales/ + - assets/unbounded/ - app.env ffigen: From ba77c8d770a439f0795db08f1cfd7df567c4d6ee Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Thu, 7 May 2026 12:13:19 -0600 Subject: [PATCH 06/37] =?UTF-8?q?prototype:=20globe=20wasn't=20visible=20?= =?UTF-8?q?=E2=80=94=20restore=20MediaQuery=20override=20+=20ClipRect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit flutter_earth_globe positions the sphere relative to MediaQuery.size (full screen) by default, so embedding it in a non-fullscreen layout slot puts the sphere off-screen. The original unbounded.dart wrapped it in MediaQuery + Positioned.fill + ClipRect to keep the sphere centred inside the parent widget's bounds — I'd dropped those when porting. Restored. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../share_my_connection.dart | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index 14c385dcdd..a68e157347 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -467,13 +467,30 @@ class _GlobeViewState extends ConsumerState<_GlobeView> { Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { + // FlutterEarthGlobe positions the sphere relative to MediaQuery.size + // (i.e. the full screen). Without overriding it the globe ends up + // off-screen when it lives in a non-fullscreen layout slot. The + // MediaQuery override + Positioned.fill keeps the sphere centred + // within this widget's box; ClipRect keeps arcs from painting + // outside the box when they curve high. + final widgetSize = Size(constraints.maxWidth, constraints.maxHeight); final radius = min(constraints.maxWidth, constraints.maxHeight) / 2 * 0.7; - return FlutterEarthGlobe( - controller: _globeController, - radius: radius, - alignment: Alignment.center, - onZoomChanged: (_) {}, + return ClipRect( + child: MediaQuery( + data: MediaQueryData(size: widgetSize), + child: Stack( + children: [ + Positioned.fill( + child: FlutterEarthGlobe( + controller: _globeController, + radius: radius, + alignment: const Alignment(0.0, 0.1), + ), + ), + ], + ), + ), ); }, ); From e3bfff8934890517242c361b9426c0a6fe26e65e Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Thu, 7 May 2026 12:23:46 -0600 Subject: [PATCH 07/37] prototype: use SwitchButton to match the rest of the app's toggles Co-Authored-By: Claude Opus 4.7 (1M context) --- .../share_my_connection/share_my_connection.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index a68e157347..ef69a6624e 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -24,6 +24,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:lantern/core/common/common.dart'; import 'package:lantern/core/models/unbounded_connection_event.dart'; import 'package:lantern/core/services/geo_lookup_service.dart'; +import 'package:lantern/core/widgets/switch_button.dart'; // ─── State ─────────────────────────────────────────────────────────────────── @@ -311,9 +312,16 @@ class _StatusCard extends StatelessWidget { ], ), ), - Switch( + // Match the rest of the app's toggles (vpn_setting.dart etc.). + // SwitchButton has no built-in disabled state, so during the + // probe we render the switch but absorb the tap so the user + // doesn't double-fire toggle(). + SwitchButton( value: state.active || state.probing, - onChanged: state.probing ? null : (_) => onToggle(), + onChanged: (value) { + if (state.probing) return; + onToggle(); + }, ), ], ), From 32402ef131deca5c60545b3c62872a1152811bdd Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Thu, 7 May 2026 12:24:01 -0600 Subject: [PATCH 08/37] =?UTF-8?q?prototype:=20nudge=20the=20globe=20up=20?= =?UTF-8?q?=E2=80=94=20alignment(0,=200.1)=20=E2=86=92=20(0,=20-0.1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/features/share_my_connection/share_my_connection.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index ef69a6624e..8728988516 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -493,7 +493,7 @@ class _GlobeViewState extends ConsumerState<_GlobeView> { child: FlutterEarthGlobe( controller: _globeController, radius: radius, - alignment: const Alignment(0.0, 0.1), + alignment: const Alignment(0.0, -0.1), ), ), ], From 0ff71085f9c13bce8c8ff01575a73d41f478ec65 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Thu, 7 May 2026 12:42:05 -0600 Subject: [PATCH 09/37] prototype: replace mock event timer with poll of radiance peer stats endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Dart side now reads live connection state from the radiance peer client's localhost stats endpoint (127.0.0.1:17099/peer/connections) every 3s and diffs against the last snapshot to fire +1 / -1 events for the globe arcs. Globe origin is unchanged; arc destinations are real connected client IPs from Iran / China / Russia / etc. as the bandit assigns them. If the endpoint isn't up yet (peer.Client.Start in flight, or no real radiance peer process attached), the poll silently retries; the globe stays empty until the first successful snapshot. The IP→country geo lookup still runs through GeoLookupService.peerLookup (geo.getiantem.org), so each arc lands on the connecting client's country centroid. Co-Authored-By: Claude Opus 4.7 (1M context) --- go.mod | 6 +- .../share_my_connection.dart | 113 +++++++++++------- 2 files changed, 74 insertions(+), 45 deletions(-) diff --git a/go.mod b/go.mod index db2aeb882f..fccbf4d499 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,11 @@ module github.com/getlantern/lantern go 1.26.2 -// replace github.com/getlantern/radiance => ../radiance +// Local while peer connection-stats endpoint is in flight; remove once +// radiance tags a release that includes peer/connstats.go. +replace github.com/getlantern/radiance => ../radiance + +replace github.com/getlantern/lantern-box => ../lantern-box // replace github.com/getlantern/lantern-server-provisioner => ../lantern-server-provisioner diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index 8728988516..04513f43c0 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -11,9 +11,11 @@ // globe actually animates while the screen is visible. import 'dart:async'; +import 'dart:convert'; import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; import 'package:flutter_earth_globe/flutter_earth_globe.dart'; import 'package:flutter_earth_globe/flutter_earth_globe_controller.dart'; import 'package:flutter_earth_globe/globe_coordinates.dart'; @@ -158,54 +160,75 @@ class ShareNotifier extends Notifier { state = const ShareState(); } - // ── Mock connection event source ─────────────────────────────────────────── - // A small canned set of (country, IP) pairs heavy on Lantern's priority - // censored regions, so the globe shows arcs landing where users actually - // benefit from the network. Real impl will subscribe to a radiance event. - - static const _peerIPs = [ - '5.190.10.5', // IR - '120.196.10.5', // CN - '95.165.10.5', // RU - '85.159.10.5', // TR - '113.161.10.5', // VN - '111.68.10.5', // PK - '156.197.10.5', // EG - '103.81.10.5', // MM - '37.156.10.5', // IR (second) - '202.108.10.5', // CN (second) - ]; + // ── Live connection event source ─────────────────────────────────────────── + // Polls the radiance peer client's localhost stats endpoint (see + // radiance/peer/connstats.go) every 3s, diffs against the last + // snapshot, and emits +1/-1 events to drive the globe arcs. + // + // The radiance side strips the port from "ip:port" before serving, so + // each entry in `sources` is a bare IP. Worker index is assigned + // monotonically per source; we keep a source→workerIdx map so the + // disconnect side fires the matching event for the same arc. + // + // Stats endpoint defaults to 127.0.0.1:17099; override with + // RADIANCE_PEER_STATS_ADDR in the radiance process if it conflicts. + + static const _statsURL = 'http://127.0.0.1:17099/peer/connections'; + + final Map _sourceToWorker = {}; + Set _lastSeen = const {}; void _startMockEvents() { - final rand = Random(); - _mockTimer = Timer.periodic(const Duration(seconds: 3), (_) { - // Bias toward connecting until we have ~4 active, then 50/50 churn. - final shouldConnect = - _activeWorkers.length < 4 || rand.nextDouble() < 0.5; - - if (shouldConnect) { - final addr = _peerIPs[rand.nextInt(_peerIPs.length)]; - final widx = _workerSeq++; - _activeWorkers.add(widx); - _eventController.add(UnboundedConnectionEvent( - state: 1, - workerIdx: widx, - addr: addr, - )); - state = state.copyWith( - activeCount: state.activeCount + 1, - totalCount: state.totalCount + 1, - ); - } else if (_activeWorkers.isNotEmpty) { - final widx = _activeWorkers.removeAt(0); - _eventController.add(UnboundedConnectionEvent( - state: -1, - workerIdx: widx, - addr: '', - )); + _lastSeen = const {}; + _sourceToWorker.clear(); + _mockTimer = Timer.periodic(const Duration(seconds: 3), (_) async { + try { + final resp = await http + .get(Uri.parse(_statsURL)) + .timeout(const Duration(seconds: 2)); + if (resp.statusCode != 200) return; + final body = jsonDecode(resp.body) as Map; + final sourcesRaw = (body['sources'] as List?) ?? const []; + // Strip ":port" — the globe only cares about the IP. + final sources = { + for (final s in sourcesRaw) + (s as String).split(':').first, + }..removeWhere((s) => s.isEmpty); + + // Disconnects: in last but not in current. + for (final gone in _lastSeen.difference(sources)) { + final widx = _sourceToWorker.remove(gone); + if (widx != null) { + _eventController.add(UnboundedConnectionEvent( + state: -1, + workerIdx: widx, + addr: '', + )); + } + } + // Connects: in current but not in last. + for (final fresh in sources.difference(_lastSeen)) { + final widx = _workerSeq++; + _sourceToWorker[fresh] = widx; + _eventController.add(UnboundedConnectionEvent( + state: 1, + workerIdx: widx, + addr: fresh, + )); + } + _lastSeen = sources; + + // Refresh totals from the snapshot (active_count is authoritative; + // totalCount accumulates across the session for the status card). + final activeCount = + (body['active_count'] as num?)?.toInt() ?? sources.length; state = state.copyWith( - activeCount: max(0, state.activeCount - 1), + activeCount: activeCount, + totalCount: max(state.totalCount, _workerSeq), ); + } catch (_) { + // Endpoint may not be up yet (peer.Client.Start in flight) or the + // user never had a real radiance peer attached. Silently retry. } }); } @@ -214,6 +237,8 @@ class ShareNotifier extends Notifier { _mockTimer?.cancel(); _mockTimer = null; _activeWorkers.clear(); + _sourceToWorker.clear(); + _lastSeen = const {}; _workerSeq = 0; } } From aa56a5e8e69eb828d22e8c1d76e1e23a1e81ad73 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Thu, 7 May 2026 13:19:00 -0600 Subject: [PATCH 10/37] Stream peer-connection events from radiance to Flutter via the existing FlutterEvent bridge; wire SmC toggle to the real radiance peer module. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The localhost stats HTTP endpoint approach was reverted in radiance (detectability + extra attack surface). This swaps it for the existing Dart api_dl FlutterEvent channel — same bridge already carrying config / server-location / data-cap events, no new ports, no new process boundaries. lantern-core/core.go: - New EventTypePeerConnection event type, message JSON {state: +1|-1, source: "ip:port"}. - listenPeerConnectionEvents goroutine subscribes to radiance events.Subscribe[peer.ConnectionEvent] and forwards via notifyFlutter, which lights up the same appEventPort that AppEventNotifier already listens on. lib/features/share_my_connection/share_my_connection.dart: - Replaced the HTTP poll loop with a subscription to lanternServiceProvider.watchAppEvents(), filtered for type=='peer-connection'. Same UnboundedConnectionEvent shape goes into the existing globe stream — globe widget unchanged. - Wired the toggle to actually flip the real radiance peer module on for SmC mode via radianceSettingsProvider.setPeerProxy(true); the OFF path calls setPeerProxy(false) when the active mode was SmC (no-op otherwise so Unbounded mode doesn't accidentally tear down a peer that was never started). - Unbounded mode remains UI-only on this branch; broflake plumbing follows when radiance#336 lands. Co-Authored-By: Claude Opus 4.7 (1M context) --- lantern-core/core.go | 28 +++ .../share_my_connection.dart | 188 ++++++++++-------- 2 files changed, 128 insertions(+), 88 deletions(-) diff --git a/lantern-core/core.go b/lantern-core/core.go index 4c4669e9f5..83f6cc5d83 100644 --- a/lantern-core/core.go +++ b/lantern-core/core.go @@ -18,8 +18,10 @@ import ( "github.com/getlantern/radiance/common" "github.com/getlantern/radiance/common/env" "github.com/getlantern/radiance/common/settings" + "github.com/getlantern/radiance/events" "github.com/getlantern/radiance/ipc" "github.com/getlantern/radiance/issue" + "github.com/getlantern/radiance/peer" "github.com/getlantern/radiance/servers" "github.com/getlantern/radiance/vpn" @@ -36,6 +38,11 @@ const ( EventTypeServerLocation EventType = "server-location" EventTypeConfig EventType = "config" EventTypeCountryCode EventType = "country-code" + // EventTypePeerConnection signals a samizdat peer accept/close on the + // local Share My Connection inbound. Message is JSON + // {"state": +1|-1, "source": "ip:port"}; consumers extract the IP for + // geo-lookup or rate-limit attribution. + EventTypePeerConnection EventType = "peer-connection" DefaultLogLevel = "trace" ) @@ -246,6 +253,7 @@ func (lc *LanternCore) initialize(opts *utils.Opts, eventEmitter utils.FlutterEv go lc.listenAutoSelectedEvents() go lc.listenConfigEvents() go lc.listenDataCapEvents() + go lc.listenPeerConnectionEvents() go lc.fetchUserDataIfNeeded() slog.Debug("LanternCore initialized successfully") @@ -360,6 +368,26 @@ func (lc *LanternCore) listenDataCapEvents() { } } +// listenPeerConnectionEvents forwards samizdat accept/close events from the +// radiance peer client to the Flutter side via the existing FlutterEvent +// emitter, so the Share My Connection globe can render arcs as remote +// clients connect to the local peer's inbound. Subscription is process- +// lifetime; events.Subscribe is decoupled from peer.Client lifecycle and +// silently delivers nothing while no peer is active. +func (lc *LanternCore) listenPeerConnectionEvents() { + events.Subscribe(func(evt peer.ConnectionEvent) { + jsonBytes, err := json.Marshal(map[string]any{ + "state": evt.State, + "source": evt.Source, + }) + if err != nil { + slog.Error("marshal peer connection event", "error", err) + return + } + lc.notifyFlutter(EventTypePeerConnection, string(jsonBytes)) + }) +} + ///////////////// // VPN // ///////////////// diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index 04513f43c0..f84116c675 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -1,21 +1,23 @@ -// Share My Connection — UX prototype. -// One unified screen for both protocols (Unbounded / Share-My-Connection): +// Share My Connection — unified screen for both Unbounded and the +// samizdat-over-UPnP "Share My Connection" modes: // - Toggle ON triggers a (mocked) UPnP probe. -// - If UPnP works AND the user accepts the SmC disclosure, run SmC mode. -// - Otherwise fall back to Unbounded mode. -// - Globe animates connection arcs from a stream of UnboundedConnectionEvent. +// - If UPnP works AND the user accepts the SmC disclosure, run SmC mode +// (calls into radiance via the existing radianceSettingsProvider +// setPeerProxy path). +// - Otherwise fall back to Unbounded mode (UI-only for now; broflake +// wire-up follows once radiance#336 lands). +// - Globe animates connection arcs from peer-connection FlutterEvents +// streamed up from radiance. // -// All wire-up to radiance / FFI is stubbed for this prototype: the UPnP probe -// returns success after a short delay, and connection events are fired by a -// timer cycling through a small set of canned residential peer IPs so the -// globe actually animates while the screen is visible. +// UPnP probe is still mocked (a coin-flip) until the FFI binding lands; +// SmC mode is real — flipping the toggle starts the radiance peer +// module on this branch. import 'dart:async'; import 'dart:convert'; import 'dart:math'; import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; import 'package:flutter_earth_globe/flutter_earth_globe.dart'; import 'package:flutter_earth_globe/flutter_earth_globe_controller.dart'; import 'package:flutter_earth_globe/globe_coordinates.dart'; @@ -27,6 +29,8 @@ import 'package:lantern/core/common/common.dart'; import 'package:lantern/core/models/unbounded_connection_event.dart'; import 'package:lantern/core/services/geo_lookup_service.dart'; import 'package:lantern/core/widgets/switch_button.dart'; +import 'package:lantern/features/home/provider/radiance_settings_providers.dart'; +import 'package:lantern/lantern/lantern_service_notifier.dart'; // ─── State ─────────────────────────────────────────────────────────────────── @@ -76,9 +80,9 @@ class ShareNotifier extends Notifier { // re-fires on app restart and is easy to demo. bool _smcAck = false; - Timer? _mockTimer; + StreamSubscription? _appEventSub; int _workerSeq = 0; - final List _activeWorkers = []; + final Map _sourceToWorker = {}; final _eventController = StreamController.broadcast(); @@ -88,17 +92,18 @@ class ShareNotifier extends Notifier { @override ShareState build() { ref.onDispose(() { - _stopMockEvents(); + _stopEventSubscription(); _eventController.close(); }); return const ShareState(); } /// Toggle entry point. Caller passes its BuildContext so we can show the - /// disclosure modal inline. - Future toggle(BuildContext context) async { + /// disclosure modal inline, and a WidgetRef so we can drive the radiance + /// peer-share toggle. + Future toggle(BuildContext context, WidgetRef widgetRef) async { if (state.active || state.probing) { - _stop(); + await _stop(widgetRef); return; } @@ -111,12 +116,12 @@ class ShareNotifier extends Notifier { await Future.delayed(const Duration(milliseconds: 1500)); final upnpAvailable = Random().nextBool(); if (!upnpAvailable) { - _start(ShareMode.unbounded); + await _start(widgetRef, ShareMode.unbounded); return; } if (_smcAck) { - _start(ShareMode.smc); + await _start(widgetRef, ShareMode.smc); return; } @@ -138,13 +143,13 @@ class ShareNotifier extends Notifier { } if (accepted) { _smcAck = true; - _start(ShareMode.smc); + await _start(widgetRef, ShareMode.smc); } else { - _start(ShareMode.unbounded); + await _start(widgetRef, ShareMode.unbounded); } } - void _start(ShareMode mode) { + Future _start(WidgetRef widgetRef, ShareMode mode) async { state = ShareState( active: true, probing: false, @@ -152,93 +157,100 @@ class ShareNotifier extends Notifier { activeCount: 0, totalCount: 0, ); - _startMockEvents(); + _startEventSubscription(widgetRef); + if (mode == ShareMode.smc) { + // Flip the radiance peer-proxy setting; LocalBackend.PatchSettings + // routes that into peer.Client.Start, which spins up the UPnP map, + // registers with lantern-cloud, runs the samizdat inbound, and (via + // the lantern-box peerconn listener radiance/peer/peer.go now sets) + // emits ConnectionEvents that ride the radiance event bus → core.go + // listenPeerConnectionEvents → FlutterEvent → our Dart subscription. + await widgetRef + .read(radianceSettingsProvider.notifier) + .setPeerProxy(true); + } + // Unbounded mode is UI-only on this branch; broflake plumbing follows + // when radiance#336 lands. } - void _stop() { - _stopMockEvents(); + Future _stop(WidgetRef widgetRef) async { + _stopEventSubscription(); + final wasSmc = state.mode == ShareMode.smc; state = const ShareState(); + if (wasSmc) { + await widgetRef + .read(radianceSettingsProvider.notifier) + .setPeerProxy(false); + } } // ── Live connection event source ─────────────────────────────────────────── - // Polls the radiance peer client's localhost stats endpoint (see - // radiance/peer/connstats.go) every 3s, diffs against the last - // snapshot, and emits +1/-1 events to drive the globe arcs. + // Subscribes to the existing FFI app-event stream (the same one + // AppEventNotifier uses for config / server-location / data-cap events) + // and filters for type=='peer-connection'. Each event's message is + // {state: +1|-1, source: "ip:port"} originally emitted from the + // lantern-box samizdat inbound via the peerconn listener registry, then + // rebroadcast by lantern-core/core.go listenPeerConnectionEvents. // - // The radiance side strips the port from "ip:port" before serving, so - // each entry in `sources` is a bare IP. Worker index is assigned - // monotonically per source; we keep a source→workerIdx map so the - // disconnect side fires the matching event for the same arc. - // - // Stats endpoint defaults to 127.0.0.1:17099; override with - // RADIANCE_PEER_STATS_ADDR in the radiance process if it conflicts. - - static const _statsURL = 'http://127.0.0.1:17099/peer/connections'; - - final Map _sourceToWorker = {}; - Set _lastSeen = const {}; + // No local sockets, no fixed ports — the bridge rides on Dart api_dl, + // which is the same channel server-location updates and data-cap events + // already use. - void _startMockEvents() { - _lastSeen = const {}; + void _startEventSubscription(WidgetRef widgetRef) { _sourceToWorker.clear(); - _mockTimer = Timer.periodic(const Duration(seconds: 3), (_) async { + _appEventSub = widgetRef + .read(lanternServiceProvider) + .watchAppEvents() + .listen((event) { + if (event.eventType != 'peer-connection') return; try { - final resp = await http - .get(Uri.parse(_statsURL)) - .timeout(const Duration(seconds: 2)); - if (resp.statusCode != 200) return; - final body = jsonDecode(resp.body) as Map; - final sourcesRaw = (body['sources'] as List?) ?? const []; - // Strip ":port" — the globe only cares about the IP. - final sources = { - for (final s in sourcesRaw) - (s as String).split(':').first, - }..removeWhere((s) => s.isEmpty); - - // Disconnects: in last but not in current. - for (final gone in _lastSeen.difference(sources)) { - final widx = _sourceToWorker.remove(gone); - if (widx != null) { - _eventController.add(UnboundedConnectionEvent( - state: -1, - workerIdx: widx, - addr: '', - )); - } - } - // Connects: in current but not in last. - for (final fresh in sources.difference(_lastSeen)) { + final payload = jsonDecode(event.message) as Map; + final eventState = (payload['state'] as num?)?.toInt() ?? 0; + final source = (payload['source'] as String?) ?? ''; + // Globe only cares about the IP — strip ":port". + final ip = source.split(':').first; + if (ip.isEmpty) return; + + if (eventState == 1) { + // Each (source IP) gets a stable worker idx so the matching + // disconnect can find the arc to remove. Repeated +1 from the + // same source (re-connect after disconnect) gets a new idx. + if (_sourceToWorker.containsKey(ip)) return; final widx = _workerSeq++; - _sourceToWorker[fresh] = widx; + _sourceToWorker[ip] = widx; _eventController.add(UnboundedConnectionEvent( state: 1, workerIdx: widx, - addr: fresh, + addr: ip, + )); + state = state.copyWith( + activeCount: state.activeCount + 1, + totalCount: state.totalCount + 1, + ); + } else if (eventState == -1) { + final widx = _sourceToWorker.remove(ip); + if (widx == null) return; + _eventController.add(UnboundedConnectionEvent( + state: -1, + workerIdx: widx, + addr: '', )); + state = state.copyWith( + activeCount: max(0, state.activeCount - 1), + ); } - _lastSeen = sources; - - // Refresh totals from the snapshot (active_count is authoritative; - // totalCount accumulates across the session for the status card). - final activeCount = - (body['active_count'] as num?)?.toInt() ?? sources.length; - state = state.copyWith( - activeCount: activeCount, - totalCount: max(state.totalCount, _workerSeq), - ); - } catch (_) { - // Endpoint may not be up yet (peer.Client.Start in flight) or the - // user never had a real radiance peer attached. Silently retry. + } catch (e) { + // Malformed event — log via dev print to avoid bringing in the + // appLogger here. Real impl can switch to slog. + debugPrint('share-my-connection: bad peer-connection event: $e'); } }); } - void _stopMockEvents() { - _mockTimer?.cancel(); - _mockTimer = null; - _activeWorkers.clear(); + void _stopEventSubscription() { + _appEventSub?.cancel(); + _appEventSub = null; _sourceToWorker.clear(); - _lastSeen = const {}; _workerSeq = 0; } } @@ -276,7 +288,7 @@ class ShareMyConnectionScreen extends HookConsumerWidget { child: _GlobeView(), ), const SizedBox(height: 8), - _StatusCard(state: state, onToggle: () => notifier.toggle(context)), + _StatusCard(state: state, onToggle: () => notifier.toggle(context, ref)), const SizedBox(height: 16), ], ), From 2f036408cfc4431c1c76314740bdb6ae6c82f200 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Thu, 7 May 2026 14:29:07 -0600 Subject: [PATCH 11/37] Advanced section in Share My Connection: manual port forward setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For users on networks where UPnP doesn't work (most consumer routers ship with UPnP off by default, ISP gateways without IGD, double-NAT networks), this adds a UI-driven way to configure a router-side port forward without needing to set RADIANCE_PEER_EXTERNAL_PORT in the environment. Backend (Go side): - Core gains SetPeerManualPort(int) and GetPeerManualPort() — PatchSettings(PeerManualPortKey: ) and a typed read with koanf's float64-after-JSON-roundtrip behavior handled. - Two new //export FFI functions: setPeerManualPort(C.int) and getPeerManualPort() returning C.int. Frontend (Dart side): - lantern_generated_bindings.dart: hand-rolled bindings for the new exports (skipping ffigen for the prototype). - LanternCoreService interface, LanternFFIService impl, LanternService router, LanternPlatformService stub all gain setPeerManualPort / getPeerManualPort. Platform stub returns "not implemented" since the iOS/Android MethodChannel handlers aren't plumbed yet — degrades gracefully on those platforms. - New _AdvancedCard widget on the Share My Connection screen with an ExpansionTile (collapsed by default), containing _ManualPortField: loads the persisted port via getPeerManualPort, validates 1-65535, saves via setPeerManualPort, surfaces a SnackBar on success/failure. When set, displays a hint that toggling the share off-and-on is needed for the change to take effect (peer.Client.Start reads the setting once at start, doesn't watch it). Note on Unbounded: the disclosure dialog still references "Basic mode (Unbounded)" but Unbounded is not actually wired up on this branch — selecting it just sets local Dart state with no backend running. Real broflake/Unbounded integration follows when radiance#336 lands. Co-Authored-By: Claude Opus 4.7 (1M context) --- lantern-core/core.go | 26 +++ lantern-core/ffi/ffi.go | 27 +++ .../share_my_connection.dart | 185 ++++++++++++++++++ lib/lantern/lantern_core_service.dart | 8 + lib/lantern/lantern_ffi_service.dart | 28 +++ lib/lantern/lantern_generated_bindings.dart | 20 ++ lib/lantern/lantern_platform_service.dart | 19 ++ lib/lantern/lantern_service.dart | 16 ++ 8 files changed, 329 insertions(+) diff --git a/lantern-core/core.go b/lantern-core/core.go index 83f6cc5d83..b7577c8061 100644 --- a/lantern-core/core.go +++ b/lantern-core/core.go @@ -163,6 +163,11 @@ type SmartRouting interface { type PeerShare interface { SetPeerShareEnabled(bool) error IsPeerShareEnabled() bool + // SetPeerManualPort persists the user's manually-configured router + // port forward (Advanced setting in the Share My Connection UI). + // 0 clears the override, restoring UPnP-discovered port behavior. + SetPeerManualPort(port int) error + GetPeerManualPort() int } type VPN interface { @@ -498,6 +503,27 @@ func (lc *LanternCore) IsPeerShareEnabled() bool { return b } +func (lc *LanternCore) SetPeerManualPort(port int) error { + if port < 0 || port > 65535 { + return fmt.Errorf("port %d out of range (0-65535)", port) + } + _, err := lc.client.PatchSettings(lc.ctx, settings.Settings{settings.PeerManualPortKey: port}) + return err +} + +func (lc *LanternCore) GetPeerManualPort() int { + // koanf typically stores numeric settings as float64 after JSON + // round-trip; handle both float64 and int paths so loads from disk + // and freshly-set values both work. + switch v := lc.settings()[settings.PeerManualPortKey].(type) { + case int: + return v + case float64: + return int(v) + } + return 0 +} + func (lc *LanternCore) IsTelemetryEnabled() bool { b, _ := lc.settings()[settings.TelemetryKey].(bool) return b diff --git a/lantern-core/ffi/ffi.go b/lantern-core/ffi/ffi.go index ca67526fdb..2f8ffe69d3 100644 --- a/lantern-core/ffi/ffi.go +++ b/lantern-core/ffi/ffi.go @@ -1375,6 +1375,33 @@ func isPeerProxyEnabled() C.int { return 0 } +// setPeerManualPort persists the manually-configured router port-forward +// for the Share My Connection peer-share feature. 0 clears the override, +// reverting to UPnP discovery on the next peer.Client.Start. +// +//export setPeerManualPort +func setPeerManualPort(port C.int) *C.char { + return runOnGoStack(func() *C.char { + c, errStr := requireCore() + if errStr != nil { + return errStr + } + if err := c.SetPeerManualPort(int(port)); err != nil { + return SendError(err) + } + return C.CString("ok") + }) +} + +//export getPeerManualPort +func getPeerManualPort() C.int { + c, _ := requireCore() + if c == nil { + return 0 + } + return C.int(c.GetPeerManualPort()) +} + //export getSplitTunnelState func getSplitTunnelState() *C.char { return runOnGoStack(func() *C.char { diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index f84116c675..fac3f01663 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -18,6 +18,7 @@ import 'dart:convert'; import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_earth_globe/flutter_earth_globe.dart'; import 'package:flutter_earth_globe/flutter_earth_globe_controller.dart'; import 'package:flutter_earth_globe/globe_coordinates.dart'; @@ -289,6 +290,8 @@ class ShareMyConnectionScreen extends HookConsumerWidget { ), const SizedBox(height: 8), _StatusCard(state: state, onToggle: () => notifier.toggle(context, ref)), + const SizedBox(height: 12), + const _AdvancedCard(), const SizedBox(height: 16), ], ), @@ -542,6 +545,188 @@ class _GlobeViewState extends ConsumerState<_GlobeView> { } } +// ─── Advanced section ──────────────────────────────────────────────────────── + +/// _AdvancedCard exposes power-user knobs that don't belong in the +/// always-visible status card. Today: manual port forward (for users on +/// networks where UPnP doesn't work, who've configured a router-side +/// port forward by hand). +/// +/// Persisted via the existing FFI setPeerManualPort path; takes effect +/// on the next peer.Client.Start (i.e. next time the toggle is flipped +/// on after editing the field). +class _AdvancedCard extends HookConsumerWidget { + const _AdvancedCard(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final textTheme = Theme.of(context).textTheme; + return Container( + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.black12), + ), + child: Theme( + // Strip the divider lines ExpansionTile draws by default — the + // container border already gives the section its own outline. + data: Theme.of(context).copyWith(dividerColor: Colors.transparent), + child: ExpansionTile( + tilePadding: const EdgeInsets.symmetric(horizontal: 16), + childrenPadding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + title: Text('Advanced', style: textTheme.labelLarge), + subtitle: Text( + 'For users whose router doesn\'t support UPnP', + style: textTheme.labelSmall, + ), + children: const [_ManualPortField()], + ), + ), + ); + } +} + +class _ManualPortField extends HookConsumerWidget { + const _ManualPortField(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final textTheme = Theme.of(context).textTheme; + final controller = useTextEditingController(); + final loaded = useState(false); + final saving = useState(false); + final lastSaved = useState(null); + + // Load the persisted port once. We deliberately don't watch a + // provider here — the value rarely changes and a one-shot read + // matches the rest of the radianceSettingsProvider's eager-load + // pattern. + useEffect(() { + Future.microtask(() async { + final result = + await ref.read(lanternServiceProvider).getPeerManualPort(); + result.fold((_) => null, (port) { + if (port > 0) controller.text = port.toString(); + lastSaved.value = port; + }); + loaded.value = true; + }); + return null; + }, const []); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Manual port forward', + style: textTheme.labelLarge, + ), + const SizedBox(height: 4), + Text( + 'If your router doesn\'t support UPnP, configure a port forward ' + 'on your router and enter the port number here. Lantern will use ' + 'it as the external port instead of probing UPnP. Leave blank to ' + 'use UPnP (default).', + style: textTheme.bodySmall, + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: TextField( + controller: controller, + keyboardType: const TextInputType.numberWithOptions( + decimal: false, + signed: false, + ), + decoration: InputDecoration( + labelText: 'Port', + hintText: 'e.g. 5698', + border: const OutlineInputBorder(), + isDense: true, + enabled: loaded.value && !saving.value, + ), + ), + ), + const SizedBox(width: 12), + FilledButton( + onPressed: (loaded.value && !saving.value) + ? () => _save(ref, context, controller, saving, lastSaved) + : null, + child: saving.value + ? const SizedBox( + height: 16, width: 16, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('Save'), + ), + ], + ), + if (lastSaved.value != null && lastSaved.value! > 0) ...[ + const SizedBox(height: 8), + Text( + 'Currently set to port ${lastSaved.value}. Toggle Share My ' + 'Connection off and back on for the change to take effect.', + style: textTheme.bodySmall?.copyWith( + color: Theme.of(context).hintColor, + ), + ), + ], + ], + ); + } + + Future _save( + WidgetRef ref, + BuildContext context, + TextEditingController controller, + ValueNotifier saving, + ValueNotifier lastSaved, + ) async { + saving.value = true; + try { + final raw = controller.text.trim(); + int port = 0; + if (raw.isNotEmpty) { + port = int.tryParse(raw) ?? -1; + if (port < 1 || port > 65535) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Port must be between 1 and 65535')), + ); + } + return; + } + } + final result = + await ref.read(lanternServiceProvider).setPeerManualPort(port); + result.fold( + (err) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(err.localizedErrorMessage)), + ); + } + }, + (_) { + lastSaved.value = port; + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(port == 0 + ? 'Manual port cleared — using UPnP' + : 'Manual port set to $port'), + ), + ); + } + }, + ); + } finally { + saving.value = false; + } + } +} + // ─── Disclosure dialog ─────────────────────────────────────────────────────── class SmcDisclosureDialog extends StatelessWidget { diff --git a/lib/lantern/lantern_core_service.dart b/lib/lantern/lantern_core_service.dart index 124ad9b1d4..7d70f16e76 100644 --- a/lib/lantern/lantern_core_service.dart +++ b/lib/lantern/lantern_core_service.dart @@ -83,6 +83,14 @@ abstract class LanternCoreService { Future> isPeerProxyEnabled(); + /// Persists the manually-configured router port forward used as the + /// peer-share external port when set. Pass 0 to clear the override + /// and revert to UPnP-discovered port behavior. + Future> setPeerManualPort(int port); + + /// Returns the persisted manual port (0 if unset). + Future> getPeerManualPort(); + Future> isSmartRoutingEnabled(); Future> isTelemetryEnabled(); diff --git a/lib/lantern/lantern_ffi_service.dart b/lib/lantern/lantern_ffi_service.dart index 6c206cf5ef..0c67700472 100644 --- a/lib/lantern/lantern_ffi_service.dart +++ b/lib/lantern/lantern_ffi_service.dart @@ -1578,6 +1578,34 @@ class LanternFFIService implements LanternCoreService { } } + @override + Future> setPeerManualPort(int port) async { + try { + final result = await runInBackground(() async { + return _ffiService + .setPeerManualPort(port) + .cast() + .toDartString(); + }); + checkAPIError(result); + return right(unit); + } catch (e, st) { + appLogger.error('setPeerManualPort error: $e', e, st); + return Left(e.toFailure()); + } + } + + @override + Future> getPeerManualPort() async { + try { + final res = _ffiService.getPeerManualPort(); + return right(res); + } catch (e, st) { + appLogger.error('getPeerManualPort error: $e', e, st); + return Left(e.toFailure()); + } + } + @override Future> isSmartRoutingEnabled() async { try { diff --git a/lib/lantern/lantern_generated_bindings.dart b/lib/lantern/lantern_generated_bindings.dart index 315ad4a52c..3b2b39f6b7 100644 --- a/lib/lantern/lantern_generated_bindings.dart +++ b/lib/lantern/lantern_generated_bindings.dart @@ -6295,6 +6295,26 @@ class LanternBindings { late final _isPeerProxyEnabled = _isPeerProxyEnabledPtr .asFunction(); + ffi.Pointer setPeerManualPort(int port) { + return _setPeerManualPort(port); + } + + late final _setPeerManualPortPtr = + _lookup Function(ffi.Int)>>( + 'setPeerManualPort', + ); + late final _setPeerManualPort = _setPeerManualPortPtr + .asFunction Function(int)>(); + + int getPeerManualPort() { + return _getPeerManualPort(); + } + + late final _getPeerManualPortPtr = + _lookup>('getPeerManualPort'); + late final _getPeerManualPort = _getPeerManualPortPtr + .asFunction(); + ffi.Pointer setSmartRoutingEnabled(int enabled) { return _setSmartRoutingEnabled(enabled); } diff --git a/lib/lantern/lantern_platform_service.dart b/lib/lantern/lantern_platform_service.dart index 9774c1b7f4..05e4fdb2e5 100644 --- a/lib/lantern/lantern_platform_service.dart +++ b/lib/lantern/lantern_platform_service.dart @@ -314,6 +314,25 @@ class LanternPlatformService implements LanternCoreService { } } + // Manual port forward setting — only the FFI path is wired today; + // the iOS / Android MethodChannel handlers don't yet implement these + // methods. Stub returns "unsupported" rather than throwing so the + // Advanced UI degrades gracefully on platforms that don't (yet) plumb + // the setting through their tunnel-extension IPC. + @override + Future> setPeerManualPort(int port) async { + return Left(Failure( + error: 'setPeerManualPort: not implemented on this platform', + localizedErrorMessage: + 'Manual port forwarding is not yet available on this platform.', + )); + } + + @override + Future> getPeerManualPort() async { + return right(0); + } + @override Future> isSmartRoutingEnabled() async { try { diff --git a/lib/lantern/lantern_service.dart b/lib/lantern/lantern_service.dart index 9936cd4164..76ac21c8e4 100644 --- a/lib/lantern/lantern_service.dart +++ b/lib/lantern/lantern_service.dart @@ -810,6 +810,22 @@ class LanternService implements LanternCoreService { return _platformService.setPeerProxyEnabled(enabled); } + @override + Future> setPeerManualPort(int port) { + if (PlatformUtils.isFFISupported) { + return _ffiService.setPeerManualPort(port); + } + return _platformService.setPeerManualPort(port); + } + + @override + Future> getPeerManualPort() { + if (PlatformUtils.isFFISupported) { + return _ffiService.getPeerManualPort(); + } + return _platformService.getPeerManualPort(); + } + @override Future> isSmartRoutingEnabled() { if (PlatformUtils.isFFISupported) { From 8f5ced9dd7875c065505ec0fcd34a304838723df Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Thu, 7 May 2026 18:49:08 -0600 Subject: [PATCH 12/37] Unbounded fully wired through to the SmC UI's "Basic mode" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit End-to-end Unbounded integration on top of the radiance side: - Core gains SetUnboundedEnabled(bool) / IsUnboundedEnabled() — PatchSettings(UnboundedKey: ...) into the radiance settings store, picked up by radiance/unbounded's config-event subscription. - listenPeerConnectionEvents now subscribes to BOTH peer.ConnectionEvent (samizdat over UPnP / manual port — SmC mode) and unbounded.ConnectionEvent (broflake WebRTC — Unbounded mode), each forwarded as the same EventTypePeerConnection FlutterEvent. The globe sees a single unified stream and renders arcs identically regardless of which donor protocol produced the connection. - Two new //export FFI functions: setUnboundedEnabled, isUnboundedEnabled, with hand-rolled Dart bindings (skipping ffigen for the prototype). - LanternCoreService interface + FFI / Service / Platform impls all gain setUnboundedEnabled / isUnboundedEnabled. Platform stub returns "not implemented" for non-FFI platforms (iOS / Android) since their MethodChannel handlers aren't plumbed yet. - share_my_connection.dart's _start / _stop now actually call setUnboundedEnabled when the user picks Unbounded mode — so flipping the toggle and choosing "Basic mode (Unbounded)" in the disclosure dialog now starts the real broflake widget proxy, not just sets local Dart state. The broflake widget only actually runs when all three conditions hold: local opt-in (this toggle), server Features[UNBOUNDED] flag, and server-supplied UnboundedConfig. If the server hasn't rolled out the feature yet, the toggle persists the opt-in but the proxy stays inactive until the next /config response opts the user in. Co-Authored-By: Claude Opus 4.7 (1M context) --- lantern-core/core.go | 50 ++++++++++++++-- lantern-core/ffi/ffi.go | 30 ++++++++++ .../share_my_connection.dart | 59 +++++++++++++------ lib/lantern/lantern_core_service.dart | 7 +++ lib/lantern/lantern_ffi_service.dart | 28 +++++++++ lib/lantern/lantern_generated_bindings.dart | 20 +++++++ lib/lantern/lantern_platform_service.dart | 14 +++++ lib/lantern/lantern_service.dart | 16 +++++ 8 files changed, 201 insertions(+), 23 deletions(-) diff --git a/lantern-core/core.go b/lantern-core/core.go index b7577c8061..17a2fdcb66 100644 --- a/lantern-core/core.go +++ b/lantern-core/core.go @@ -23,6 +23,7 @@ import ( "github.com/getlantern/radiance/issue" "github.com/getlantern/radiance/peer" "github.com/getlantern/radiance/servers" + "github.com/getlantern/radiance/unbounded" "github.com/getlantern/radiance/vpn" "github.com/getlantern/lantern/lantern-core/apps" @@ -168,6 +169,13 @@ type PeerShare interface { // 0 clears the override, restoring UPnP-discovered port behavior. SetPeerManualPort(port int) error GetPeerManualPort() int + // SetUnboundedEnabled is the local opt-in for the broflake / + // Unbounded widget proxy ("Basic mode" in the SmC UI). The + // proxy actually runs only when this is true AND the server-side + // Features[unbounded] flag is on AND the server provides + // UnboundedConfig — see radiance/unbounded.shouldRunUnbounded. + SetUnboundedEnabled(bool) error + IsUnboundedEnabled() bool } type VPN interface { @@ -373,12 +381,20 @@ func (lc *LanternCore) listenDataCapEvents() { } } -// listenPeerConnectionEvents forwards samizdat accept/close events from the -// radiance peer client to the Flutter side via the existing FlutterEvent -// emitter, so the Share My Connection globe can render arcs as remote -// clients connect to the local peer's inbound. Subscription is process- -// lifetime; events.Subscribe is decoupled from peer.Client lifecycle and -// silently delivers nothing while no peer is active. +// listenPeerConnectionEvents forwards inbound accept/close events from +// either of the two donor protocols (samizdat-over-UPnP "Share My +// Connection" and broflake "Unbounded") to the Flutter side via the +// existing FlutterEvent emitter, so the same globe widget can render +// arcs without caring which protocol produced them. Subscription is +// process-lifetime; events.Subscribe silently delivers nothing while +// no peer / widget is active. +// +// The wire format unifies both protocols on a single event type +// (EventTypePeerConnection) with a {state, source} payload. Unbounded +// has a workerIdx in addition to source IP — surfaced as part of the +// JSON in case the Dart side eventually wants to disambiguate same-IP +// reconnects (broflake's WebRTC sessions are short and same-IP churn +// is more common than for SmC's long-lived TCP). func (lc *LanternCore) listenPeerConnectionEvents() { events.Subscribe(func(evt peer.ConnectionEvent) { jsonBytes, err := json.Marshal(map[string]any{ @@ -391,6 +407,18 @@ func (lc *LanternCore) listenPeerConnectionEvents() { } lc.notifyFlutter(EventTypePeerConnection, string(jsonBytes)) }) + events.Subscribe(func(evt unbounded.ConnectionEvent) { + jsonBytes, err := json.Marshal(map[string]any{ + "state": evt.State, + "source": evt.Addr, + "workerIdx": evt.WorkerIdx, + }) + if err != nil { + slog.Error("marshal unbounded connection event", "error", err) + return + } + lc.notifyFlutter(EventTypePeerConnection, string(jsonBytes)) + }) } ///////////////// @@ -524,6 +552,16 @@ func (lc *LanternCore) GetPeerManualPort() int { return 0 } +func (lc *LanternCore) SetUnboundedEnabled(enabled bool) error { + _, err := lc.client.PatchSettings(lc.ctx, settings.Settings{settings.UnboundedKey: enabled}) + return err +} + +func (lc *LanternCore) IsUnboundedEnabled() bool { + b, _ := lc.settings()[settings.UnboundedKey].(bool) + return b +} + func (lc *LanternCore) IsTelemetryEnabled() bool { b, _ := lc.settings()[settings.TelemetryKey].(bool) return b diff --git a/lantern-core/ffi/ffi.go b/lantern-core/ffi/ffi.go index 2f8ffe69d3..8bf32d90d9 100644 --- a/lantern-core/ffi/ffi.go +++ b/lantern-core/ffi/ffi.go @@ -1402,6 +1402,36 @@ func getPeerManualPort() C.int { return C.int(c.GetPeerManualPort()) } +// setUnboundedEnabled is the local opt-in for the broflake / Unbounded +// widget proxy ("Basic mode" in the SmC UI). The widget actually runs +// only when this is true AND the server-side Features[unbounded] flag +// is on AND the server provides UnboundedConfig — flipping this to +// true on a network where the server hasn't enabled the feature is a +// no-op until the next /config response opts the user in. +// +//export setUnboundedEnabled +func setUnboundedEnabled(enabled C.int) *C.char { + return runOnGoStack(func() *C.char { + c, errStr := requireCore() + if errStr != nil { + return errStr + } + if err := c.SetUnboundedEnabled(enabled != 0); err != nil { + return SendError(err) + } + return C.CString("ok") + }) +} + +//export isUnboundedEnabled +func isUnboundedEnabled() C.int { + c, _ := requireCore() + if c != nil && c.IsUnboundedEnabled() { + return 1 + } + return 0 +} + //export getSplitTunnelState func getSplitTunnelState() *C.char { return runOnGoStack(func() *C.char { diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index fac3f01663..8631a8a536 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -159,29 +159,54 @@ class ShareNotifier extends Notifier { totalCount: 0, ); _startEventSubscription(widgetRef); - if (mode == ShareMode.smc) { - // Flip the radiance peer-proxy setting; LocalBackend.PatchSettings - // routes that into peer.Client.Start, which spins up the UPnP map, - // registers with lantern-cloud, runs the samizdat inbound, and (via - // the lantern-box peerconn listener radiance/peer/peer.go now sets) - // emits ConnectionEvents that ride the radiance event bus → core.go - // listenPeerConnectionEvents → FlutterEvent → our Dart subscription. - await widgetRef - .read(radianceSettingsProvider.notifier) - .setPeerProxy(true); + switch (mode) { + case ShareMode.smc: + // Flip the radiance peer-proxy setting; LocalBackend.PatchSettings + // routes that into peer.Client.Start, which spins up the UPnP map + // (or honours PeerManualPortKey), registers with lantern-cloud, + // runs the samizdat inbound, and (via the lantern-box peerconn + // listener) emits ConnectionEvents that ride the radiance event + // bus → core.go listenPeerConnectionEvents → FlutterEvent → our + // Dart subscription. + await widgetRef + .read(radianceSettingsProvider.notifier) + .setPeerProxy(true); + break; + case ShareMode.unbounded: + // Unbounded is the broflake / WebRTC widget-proxy mode. Local + // opt-in only — actual run state also depends on the server's + // Features[unbounded] flag and supplied UnboundedConfig (see + // radiance/unbounded/unbounded.go shouldRunUnbounded). When + // running, broflake's OnConnectionChange callback emits + // unbounded.ConnectionEvent → forwarded by lantern-core as the + // same EventTypePeerConnection FlutterEvent the SmC path uses, + // so this Dart subscription consumes both protocols uniformly. + await widgetRef + .read(lanternServiceProvider) + .setUnboundedEnabled(true); + break; + case ShareMode.off: + break; } - // Unbounded mode is UI-only on this branch; broflake plumbing follows - // when radiance#336 lands. } Future _stop(WidgetRef widgetRef) async { _stopEventSubscription(); - final wasSmc = state.mode == ShareMode.smc; + final priorMode = state.mode; state = const ShareState(); - if (wasSmc) { - await widgetRef - .read(radianceSettingsProvider.notifier) - .setPeerProxy(false); + switch (priorMode) { + case ShareMode.smc: + await widgetRef + .read(radianceSettingsProvider.notifier) + .setPeerProxy(false); + break; + case ShareMode.unbounded: + await widgetRef + .read(lanternServiceProvider) + .setUnboundedEnabled(false); + break; + case ShareMode.off: + break; } } diff --git a/lib/lantern/lantern_core_service.dart b/lib/lantern/lantern_core_service.dart index 7d70f16e76..bc7c3abf99 100644 --- a/lib/lantern/lantern_core_service.dart +++ b/lib/lantern/lantern_core_service.dart @@ -91,6 +91,13 @@ abstract class LanternCoreService { /// Returns the persisted manual port (0 if unset). Future> getPeerManualPort(); + /// Local opt-in for the broflake / Unbounded widget proxy ("Basic + /// mode" in the Share My Connection UI). Actual run state also + /// depends on server feature-flag and config availability. + Future> setUnboundedEnabled(bool enabled); + + Future> isUnboundedEnabled(); + Future> isSmartRoutingEnabled(); Future> isTelemetryEnabled(); diff --git a/lib/lantern/lantern_ffi_service.dart b/lib/lantern/lantern_ffi_service.dart index 0c67700472..d59c892c47 100644 --- a/lib/lantern/lantern_ffi_service.dart +++ b/lib/lantern/lantern_ffi_service.dart @@ -1606,6 +1606,34 @@ class LanternFFIService implements LanternCoreService { } } + @override + Future> setUnboundedEnabled(bool enabled) async { + try { + final result = await runInBackground(() async { + return _ffiService + .setUnboundedEnabled(enabled ? 1 : 0) + .cast() + .toDartString(); + }); + checkAPIError(result); + return right(unit); + } catch (e, st) { + appLogger.error('setUnboundedEnabled error: $e', e, st); + return Left(e.toFailure()); + } + } + + @override + Future> isUnboundedEnabled() async { + try { + final res = _ffiService.isUnboundedEnabled(); + return right(res != 0); + } catch (e, st) { + appLogger.error('isUnboundedEnabled error: $e', e, st); + return Left(e.toFailure()); + } + } + @override Future> isSmartRoutingEnabled() async { try { diff --git a/lib/lantern/lantern_generated_bindings.dart b/lib/lantern/lantern_generated_bindings.dart index 3b2b39f6b7..89265acdcc 100644 --- a/lib/lantern/lantern_generated_bindings.dart +++ b/lib/lantern/lantern_generated_bindings.dart @@ -6315,6 +6315,26 @@ class LanternBindings { late final _getPeerManualPort = _getPeerManualPortPtr .asFunction(); + ffi.Pointer setUnboundedEnabled(int enabled) { + return _setUnboundedEnabled(enabled); + } + + late final _setUnboundedEnabledPtr = + _lookup Function(ffi.Int)>>( + 'setUnboundedEnabled', + ); + late final _setUnboundedEnabled = _setUnboundedEnabledPtr + .asFunction Function(int)>(); + + int isUnboundedEnabled() { + return _isUnboundedEnabled(); + } + + late final _isUnboundedEnabledPtr = + _lookup>('isUnboundedEnabled'); + late final _isUnboundedEnabled = _isUnboundedEnabledPtr + .asFunction(); + ffi.Pointer setSmartRoutingEnabled(int enabled) { return _setSmartRoutingEnabled(enabled); } diff --git a/lib/lantern/lantern_platform_service.dart b/lib/lantern/lantern_platform_service.dart index 05e4fdb2e5..c21180b2d7 100644 --- a/lib/lantern/lantern_platform_service.dart +++ b/lib/lantern/lantern_platform_service.dart @@ -333,6 +333,20 @@ class LanternPlatformService implements LanternCoreService { return right(0); } + @override + Future> setUnboundedEnabled(bool enabled) async { + return Left(Failure( + error: 'setUnboundedEnabled: not implemented on this platform', + localizedErrorMessage: + 'Unbounded is not yet available on this platform.', + )); + } + + @override + Future> isUnboundedEnabled() async { + return right(false); + } + @override Future> isSmartRoutingEnabled() async { try { diff --git a/lib/lantern/lantern_service.dart b/lib/lantern/lantern_service.dart index 76ac21c8e4..390109b54e 100644 --- a/lib/lantern/lantern_service.dart +++ b/lib/lantern/lantern_service.dart @@ -826,6 +826,22 @@ class LanternService implements LanternCoreService { return _platformService.getPeerManualPort(); } + @override + Future> setUnboundedEnabled(bool enabled) { + if (PlatformUtils.isFFISupported) { + return _ffiService.setUnboundedEnabled(enabled); + } + return _platformService.setUnboundedEnabled(enabled); + } + + @override + Future> isUnboundedEnabled() { + if (PlatformUtils.isFFISupported) { + return _ffiService.isUnboundedEnabled(); + } + return _platformService.isUnboundedEnabled(); + } + @override Future> isSmartRoutingEnabled() { if (PlatformUtils.isFFISupported) { From 566d789afeed62ccbfdaa9685ccd00d81b7e3df4 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Fri, 8 May 2026 09:28:22 -0600 Subject: [PATCH 13/37] macOS: wire setPeerManualPort + setUnboundedEnabled through MethodChannel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PlatformUtils.isFFISupported is Windows-or-Linux only — macOS routes through MethodChannel because the radiance backend runs inside the network extension, not the main app process. Without these handlers, the Advanced "Manual port forward" save and the Unbounded mode selection both hit the platform-service stub and surface "not yet available on this platform" SnackBars even though the underlying Core methods exist. Brings macOS to feature parity with Windows/Linux for the SmC stack: Already wired (existed): setPeerProxyEnabled / isPeerProxyEnabled → MobileSetPeerShareEnabled / MobileIsPeerShareEnabled Wired in this commit: setPeerManualPort / getPeerManualPort → MobileSetPeerManualPort / MobileGetPeerManualPort setUnboundedEnabled / isUnboundedEnabled → MobileSetUnboundedEnabled / MobileIsUnboundedEnabled After the next `make macos-release` (gomobile-bind regenerates Liblantern.xcframework with the four new symbols), the Share My Connection UI works end-to-end on macOS: - Toggle on, choose Full mode → peer.Client.Start, samizdat inbound - Choose Basic mode → unbounded.SetEnabled, broflake widget runs when the server's Features[unbounded] flag + config arrive - Advanced section save → port persisted, used as the manual forward override on next peer.Client.Start iOS / Android still don't have these handlers; SmC is also gated behind PlatformUtils.isDesktop in vpn_setting.dart so the tile isn't visible there. Mobile support is a separate UX pass — the "share my connection" mental model is different on cellular (sharing data plan, not residential bandwidth) and UPnP isn't applicable. Co-Authored-By: Claude Opus 4.7 (1M context) --- lantern-core/mobile/mobile.go | 51 ++++++++++++++++++++ lib/lantern/lantern_platform_service.dart | 58 ++++++++++++++++------- macos/Runner/Handlers/MethodHandler.swift | 48 +++++++++++++++++++ 3 files changed, 140 insertions(+), 17 deletions(-) diff --git a/lantern-core/mobile/mobile.go b/lantern-core/mobile/mobile.go index c04c2929e0..328f2a35f4 100644 --- a/lantern-core/mobile/mobile.go +++ b/lantern-core/mobile/mobile.go @@ -220,6 +220,57 @@ func IsPeerShareEnabled() bool { return ok } +// SetPeerManualPort persists the manually-configured router port forward +// the user has configured for the Share My Connection feature. Pass 0 +// to clear and revert to UPnP-discovered port behavior. Surfaced +// through the macOS / iOS / Android MethodChannel handler so platforms +// running radiance inside a network extension (where the main app +// process can't reach the FFI directly) can still drive the setting. +func SetPeerManualPort(port int) error { + slog.Info("peer-share: SetPeerManualPort", "port", port) + return withCore(func(c lanterncore.Core) error { + return c.SetPeerManualPort(port) + }) +} + +// GetPeerManualPort returns the currently-persisted manual port (0 if +// unset). Same MethodChannel rationale as SetPeerManualPort. +func GetPeerManualPort() int { + v, err := withCoreR(func(c lanterncore.Core) (int, error) { + return c.GetPeerManualPort(), nil + }) + if err != nil { + return 0 + } + return v +} + +// SetUnboundedEnabled is the local opt-in for the broflake / Unbounded +// widget proxy ("Basic mode" in the SmC UI). Surfaced through the +// MethodChannel so platforms running radiance inside a network +// extension (macOS, eventually iOS) can drive the setting from the +// main app process. +func SetUnboundedEnabled(enabled bool) error { + slog.Info("unbounded: SetUnboundedEnabled", "enabled", enabled) + return withCore(func(c lanterncore.Core) error { + return c.SetUnboundedEnabled(enabled) + }) +} + +// IsUnboundedEnabled returns the current local opt-in state for +// Unbounded. Note: actual run state also depends on the server +// Features[unbounded] flag and supplied UnboundedConfig — this just +// reports the persisted local toggle. +func IsUnboundedEnabled() bool { + ok, err := withCoreR(func(c lanterncore.Core) (bool, error) { + return c.IsUnboundedEnabled(), nil + }) + if err != nil { + return false + } + return ok +} + func IsSmartRoutingEnabled() bool { ok, err := withCoreR(func(c lanterncore.Core) (bool, error) { return c.IsSmartRoutingEnabled(), nil diff --git a/lib/lantern/lantern_platform_service.dart b/lib/lantern/lantern_platform_service.dart index c21180b2d7..730d6ead68 100644 --- a/lib/lantern/lantern_platform_service.dart +++ b/lib/lantern/lantern_platform_service.dart @@ -314,37 +314,61 @@ class LanternPlatformService implements LanternCoreService { } } - // Manual port forward setting — only the FFI path is wired today; - // the iOS / Android MethodChannel handlers don't yet implement these - // methods. Stub returns "unsupported" rather than throwing so the - // Advanced UI degrades gracefully on platforms that don't (yet) plumb - // the setting through their tunnel-extension IPC. + // Manual port forward setting — wired through MethodChannel to the + // platform-specific handler (Swift on macOS calls Mobile.SetPeerManualPort, + // similar pattern needed for iOS / Android when the user-facing toggle + // ships there). @override Future> setPeerManualPort(int port) async { - return Left(Failure( - error: 'setPeerManualPort: not implemented on this platform', - localizedErrorMessage: - 'Manual port forwarding is not yet available on this platform.', - )); + try { + await _methodChannel.invokeMethod('setPeerManualPort', { + 'port': port, + }); + return right(unit); + } catch (e, st) { + appLogger.error('setPeerManualPort failed', e, st); + return Left(e.toFailure()); + } } @override Future> getPeerManualPort() async { - return right(0); + try { + final res = await _methodChannel.invokeMethod('getPeerManualPort'); + return right(res ?? 0); + } catch (e, st) { + appLogger.error('getPeerManualPort failed', e, st); + return Left(e.toFailure()); + } } + // Unbounded toggle wired through MethodChannel to the platform-specific + // handler (Swift on macOS calls Mobile.SetUnboundedEnabled). Mobile + // platforms (iOS / Android) don't implement these handlers yet — they + // will throw MissingPluginException, which `e.toFailure()` translates + // into a localized error so the Advanced UI degrades cleanly. @override Future> setUnboundedEnabled(bool enabled) async { - return Left(Failure( - error: 'setUnboundedEnabled: not implemented on this platform', - localizedErrorMessage: - 'Unbounded is not yet available on this platform.', - )); + try { + await _methodChannel.invokeMethod('setUnboundedEnabled', { + 'enabled': enabled, + }); + return right(unit); + } catch (e, st) { + appLogger.error('setUnboundedEnabled failed', e, st); + return Left(e.toFailure()); + } } @override Future> isUnboundedEnabled() async { - return right(false); + try { + final res = await _methodChannel.invokeMethod('isUnboundedEnabled'); + return right(res ?? false); + } catch (e, st) { + appLogger.error('isUnboundedEnabled failed', e, st); + return Left(e.toFailure()); + } } @override diff --git a/macos/Runner/Handlers/MethodHandler.swift b/macos/Runner/Handlers/MethodHandler.swift index 6eaa33d4b2..7c3600d938 100644 --- a/macos/Runner/Handlers/MethodHandler.swift +++ b/macos/Runner/Handlers/MethodHandler.swift @@ -255,6 +255,26 @@ class MethodHandler { let enabled = data?["enabled"] as? Bool ?? false self.setPeerProxyEnabled(result: result, enabled: enabled) + case "setPeerManualPort": + let data = call.arguments as? [String: Any] + let port = data?["port"] as? Int ?? 0 + self.setPeerManualPort(result: result, port: port) + + case "getPeerManualPort": + Task { + await MainActor.run { result(Int(MobileGetPeerManualPort())) } + } + + case "setUnboundedEnabled": + let data = call.arguments as? [String: Any] + let enabled = data?["enabled"] as? Bool ?? false + self.setUnboundedEnabled(result: result, enabled: enabled) + + case "isUnboundedEnabled": + Task { + await MainActor.run { result(MobileIsUnboundedEnabled()) } + } + case "updateTelemetryEvents": guard let consent: Bool = self.decodeValue(from: call.arguments, result: result) else { return @@ -1176,6 +1196,34 @@ class MethodHandler { } } + func setPeerManualPort(result: @escaping FlutterResult, port: Int) { + Task { + var error: NSError? + MobileSetPeerManualPort(port, &error) + if let error { + await self.handleFlutterError(error, result: result, code: "SET_PEER_MANUAL_PORT_ERROR") + return + } + await MainActor.run { + result("ok") + } + } + } + + func setUnboundedEnabled(result: @escaping FlutterResult, enabled: Bool) { + Task { + var error: NSError? + MobileSetUnboundedEnabled(enabled, &error) + if let error { + await self.handleFlutterError(error, result: result, code: "SET_UNBOUNDED_ENABLED_ERROR") + return + } + await MainActor.run { + result("ok") + } + } + } + func updateTelemetryEvents(consent: Bool, result: @escaping FlutterResult) { Task { var error: NSError? From 4cd8d9c5bd8e178d753d905ead95c6f1e0af6ca0 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Fri, 8 May 2026 09:31:59 -0600 Subject: [PATCH 14/37] share-my-connection: toggle honors Advanced manual port before UPnP probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Dart-side toggle was running its mocked UPnP probe (a coin flip) without first checking whether the user had configured a manual port in Advanced settings. When the coin landed "no UPnP" the user got silently dropped into Unbounded mode despite having explicitly set up a port forward — defeating the whole point of the Advanced setting. Resolution order on enable is now: 1. settings.PeerManualPortKey is set (via Advanced UI): → straight to SmC mode, no UPnP probe, no disclosure dialog. Configuring a manual port forward is an explicit user-driven SmC opt-in; they wouldn't set it up if they weren't sure they wanted to share via the residential-IP path. 2. UPnP probe (mocked for now): → SmC if available + disclosure accepted, Unbounded if declined or unavailable. The radiance side already had the right precedence in peer.Client.Start's NewForwarder factory (settings > env var > UPnP); this just stops the Dart toggle from short-circuiting to Unbounded before the radiance side ever gets called. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../share_my_connection.dart | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index 8631a8a536..b62c98bfdf 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -102,6 +102,15 @@ class ShareNotifier extends Notifier { /// Toggle entry point. Caller passes its BuildContext so we can show the /// disclosure modal inline, and a WidgetRef so we can drive the radiance /// peer-share toggle. + /// + /// Resolution order on enable: + /// 1. If the user has set a manual port in Advanced settings, that + /// is an explicit opt-in — go straight to SmC mode. No UPnP + /// probe, no disclosure (user already crossed that line by + /// configuring the port forward on their router). + /// 2. Otherwise probe UPnP. If UPnP works AND the user accepts + /// the SmC disclosure, run SmC. Decline → Unbounded. + /// 3. UPnP unavailable → Unbounded fallback. Future toggle(BuildContext context, WidgetRef widgetRef) async { if (state.active || state.probing) { await _stop(widgetRef); @@ -110,10 +119,21 @@ class ShareNotifier extends Notifier { state = state.copyWith(probing: true); - // MOCK: real impl will FFI into radiance/portforward to probe UPnP. - // Coin-flip the result so the demo exercises both the SmC and Unbounded - // paths across runs; flip to `true` for the SmC path while iterating on - // the disclosure copy. + // Manual port forward bypasses both the UPnP probe and the SmC + // disclosure dialog. Configuring a port in Advanced is an explicit + // user-driven SmC opt-in — they wouldn't have set it up if they + // weren't sure they wanted to share via the residential-IP path. + final manualPortRes = + await widgetRef.read(lanternServiceProvider).getPeerManualPort(); + final manualPort = manualPortRes.fold((_) => 0, (p) => p); + if (manualPort > 0) { + await _start(widgetRef, ShareMode.smc); + return; + } + + // MOCK: real UPnP probe via FFI is not yet wired; coin-flip the + // result so the demo exercises both paths across runs without a + // manual port set. await Future.delayed(const Duration(milliseconds: 1500)); final upnpAvailable = Random().nextBool(); if (!upnpAvailable) { From 0d36530afe7c38fbb7f6c538ca449d34ca604ee5 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Fri, 8 May 2026 11:05:22 -0600 Subject: [PATCH 15/37] mobile: sanitize errors before returning to gomobile bridge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RunOffCgoStack normalizes any non-nil error to a plain errorString with a guaranteed non-empty, valid-UTF-8 Error() message before handing it back to the gomobile-exported caller. Without this, a SIGABRT crashes the Lantern process when any mobile-exported function returns an error whose string contains non-UTF-8 bytes. Reproduced when toggling Share My Connection on while the prod /v1/peer/register endpoint returned 404 with a body whose bytes weren't valid UTF-8 (likely a gzipped or otherwise binary error page from the upstream LB). The chain that triggers the crash: *Error{Message: <404 body bytes>} → Error.Error() = "ipc: status 500: ... body=" → withCore returns this through gomobile → -[Universeerror initWithRef:] auto-generated wrapper: self = [super initWithDomain:@"go" code:1 userInfo:@{NSLocalizedDescriptionKey: [self error]}]; → [self error] calls go_seq_to_objc_string() → [[NSString alloc] initWithBytesNoCopy:bytes length:N encoding:NSUTF8StringEncoding freeWhenDone:YES] → returns nil for non-UTF-8 input → @{...: nil} expands to +[NSDictionary dictionaryWithObjects:forKeys:count:] with objects[0] == nil → NSInvalidArgumentException → SIGABRT Crash signature on macOS: *** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0] ... -[Universeerror initWithRef:] + 192 MobileSetPeerShareEnabled + 160 Centralizing the sanitization in RunOffCgoStack covers every Mobile* function that funnels its body through withCore (essentially all of mobile.go), so we don't have to thread fixes through individual exports. Co-Authored-By: Claude Opus 4.7 (1M context) --- lantern-core/utils/gostack.go | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/lantern-core/utils/gostack.go b/lantern-core/utils/gostack.go index 99d6b5b660..75515a23e2 100644 --- a/lantern-core/utils/gostack.go +++ b/lantern-core/utils/gostack.go @@ -1,9 +1,12 @@ package utils import ( + "errors" "fmt" "log/slog" "runtime/debug" + "strings" + "unicode/utf8" ) // RunOffCgoStack executes fn on a new goroutine and returns its result. @@ -16,6 +19,17 @@ import ( // // If fn panics, the panic is recovered and a zero value + error are returned // instead of blocking the caller forever. +// +// Returned errors are normalized to a plain *errorString with a guaranteed +// non-empty, valid-UTF-8 message before crossing back into gomobile's +// objc bridge. The bridge wraps non-nil Go errors as a Universeerror whose +// initWithRef calls [NSString initWithBytesNoCopy: ... encoding:UTF8] on the +// raw error bytes; that returns nil for invalid UTF-8 (e.g. a gzipped 404 +// page, or a binary blob from an upstream LB), and the dictionary literal +// `@{NSLocalizedDescriptionKey: nil}` then aborts the app with +// "attempt to insert nil object from objects[0]". Sanitizing here means every +// gomobile-exported function that funnels through RunOffCgoStack is safe by +// construction, regardless of what shape of error its callee returns. func RunOffCgoStack[T any](fn func() (T, error)) (T, error) { type result struct { val T @@ -34,5 +48,19 @@ func RunOffCgoStack[T any](fn func() (T, error)) (T, error) { ch <- result{val: v, err: err} }() r := <-ch - return r.val, r.err + return r.val, sanitizeForGomobile(r.err) +} + +func sanitizeForGomobile(err error) error { + if err == nil { + return nil + } + msg := err.Error() + if !utf8.ValidString(msg) { + msg = strings.ToValidUTF8(msg, "?") + } + if msg == "" { + msg = "unknown error" + } + return errors.New(msg) } From e51e416848611d8af1337098b79e0b6a2aa7a2e6 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 11 May 2026 13:45:23 -0600 Subject: [PATCH 16/37] share-my-connection: surface radiance peer phase events to the UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The toggle today flips active/inactive with a multi-second gap between "on" and "Active — sharing" while radiance walks the Start lifecycle (port map → IP detect → register → libbox start → verify). To the user this looks hung. Adds granular status text driven by the new peer StatusEvent stream from radiance/peer (companion PR github.com/getlantern/radiance/pull/). lantern-core/core.go: + EventTypePeerStatus = "peer-status" + listenPeerStatusEvents() forwards peer.StatusEvent (whose .Status field already has JSON tags for phase, error, active, etc.) as a FlutterEvent so the Dart side gets per-stage notifications. share_my_connection.dart: + SharePhase enum mirrors radiance Phase strings; .fromWire() maps backward-compatibly so unknown future phases default to idle. + ShareState carries phase + errorMessage; _handlePeerStatus folds incoming events into state. + _StatusCard renders phase-specific labels (Opening port… → Registering… → Verifying… → Sharing) and the error message on the failure terminal state. Co-Authored-By: Claude Opus 4.7 (1M context) --- lantern-core/core.go | 32 ++++- .../share_my_connection.dart | 109 +++++++++++++++++- 2 files changed, 136 insertions(+), 5 deletions(-) diff --git a/lantern-core/core.go b/lantern-core/core.go index 17a2fdcb66..2b0688985b 100644 --- a/lantern-core/core.go +++ b/lantern-core/core.go @@ -44,7 +44,14 @@ const ( // {"state": +1|-1, "source": "ip:port"}; consumers extract the IP for // geo-lookup or rate-limit attribution. EventTypePeerConnection EventType = "peer-connection" - DefaultLogLevel = "trace" + // EventTypePeerStatus signals a peer.Client lifecycle phase change + // (mapping_port → registering → verifying → serving on the way up, + // stopping → idle on the way down, error on failure). Message is the + // JSON-marshalled peer.Status struct. The Dart side switches on + // .phase to render progress text and on .error to surface + // diagnostics on the failure path. + EventTypePeerStatus EventType = "peer-status" + DefaultLogLevel = "trace" ) // LanternCore wraps an IPC client and provides the interface expected by the FFI and mobile layers. @@ -267,6 +274,7 @@ func (lc *LanternCore) initialize(opts *utils.Opts, eventEmitter utils.FlutterEv go lc.listenConfigEvents() go lc.listenDataCapEvents() go lc.listenPeerConnectionEvents() + go lc.listenPeerStatusEvents() go lc.fetchUserDataIfNeeded() slog.Debug("LanternCore initialized successfully") @@ -421,6 +429,28 @@ func (lc *LanternCore) listenPeerConnectionEvents() { }) } +// listenPeerStatusEvents forwards peer.Client lifecycle phase changes to +// the Flutter side. radiance's peer module emits one StatusEvent per +// stage during Start (mapping_port → detecting_ip → registering → +// starting_proxy → verifying → serving) and during Stop (stopping → +// idle), plus an "error" terminal event with Status.Error populated on +// failure. The Dart side renders each phase as user-facing progress +// text instead of a single active/inactive flip. +// +// Message body is the JSON-marshalled peer.Status — the struct already +// carries phase, error, active, sharing_since, external_ip, +// external_port, route_id with stable JSON tags. +func (lc *LanternCore) listenPeerStatusEvents() { + events.Subscribe(func(evt peer.StatusEvent) { + jsonBytes, err := json.Marshal(evt.Status) + if err != nil { + slog.Error("marshal peer status event", "error", err) + return + } + lc.notifyFlutter(EventTypePeerStatus, string(jsonBytes)) + }) +} + ///////////////// // VPN // ///////////////// diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index b62c98bfdf..a642097011 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -43,12 +43,54 @@ import 'package:lantern/lantern/lantern_service_notifier.dart'; /// higher risk; gated on a one-time disclosure) enum ShareMode { off, unbounded, smc } +/// Lifecycle phase for SmC mode, sourced from radiance peer.Status.Phase +/// via the `peer-status` FlutterEvent. Stable strings — must stay in +/// sync with radiance/peer/peer.go's Phase constants. +/// +/// idle — nothing running +/// mappingPort — UPnP / manual port mapping in flight +/// detectingIp — public-IP detection +/// registering — POST /v1/peer/register against lantern-cloud +/// startingProxy — libbox samizdat inbound coming up +/// verifying — POST /v1/peer/verify, lantern-cloud is dialing back +/// serving — peer is live and assignable to censored clients +/// stopping — teardown in progress +/// error — Start failed; SharePhase.errorMessage holds the cause +enum SharePhase { + idle, + mappingPort, + detectingIp, + registering, + startingProxy, + verifying, + serving, + stopping, + error; + + static SharePhase fromWire(String? s) => switch (s) { + 'mapping_port' => SharePhase.mappingPort, + 'detecting_ip' => SharePhase.detectingIp, + 'registering' => SharePhase.registering, + 'starting_proxy' => SharePhase.startingProxy, + 'verifying' => SharePhase.verifying, + 'serving' => SharePhase.serving, + 'stopping' => SharePhase.stopping, + 'error' => SharePhase.error, + _ => SharePhase.idle, + }; +} + class ShareState { final bool active; final bool probing; final ShareMode mode; final int activeCount; final int totalCount; + // SmC-only: granular Start/Stop phase from radiance peer.Status. For + // Unbounded mode this stays SharePhase.idle (no equivalent staged + // lifecycle on the broflake side yet). + final SharePhase phase; + final String? errorMessage; const ShareState({ this.active = false, @@ -56,6 +98,8 @@ class ShareState { this.mode = ShareMode.off, this.activeCount = 0, this.totalCount = 0, + this.phase = SharePhase.idle, + this.errorMessage, }); ShareState copyWith({ @@ -64,6 +108,8 @@ class ShareState { ShareMode? mode, int? activeCount, int? totalCount, + SharePhase? phase, + String? errorMessage, }) => ShareState( active: active ?? this.active, @@ -71,6 +117,8 @@ class ShareState { mode: mode ?? this.mode, activeCount: activeCount ?? this.activeCount, totalCount: totalCount ?? this.totalCount, + phase: phase ?? this.phase, + errorMessage: errorMessage ?? this.errorMessage, ); } @@ -248,6 +296,10 @@ class ShareNotifier extends Notifier { .read(lanternServiceProvider) .watchAppEvents() .listen((event) { + if (event.eventType == 'peer-status') { + _handlePeerStatus(event.message); + return; + } if (event.eventType != 'peer-connection') return; try { final payload = jsonDecode(event.message) as Map; @@ -299,6 +351,26 @@ class ShareNotifier extends Notifier { _sourceToWorker.clear(); _workerSeq = 0; } + + // Parses a `peer-status` FlutterEvent and folds the new phase / error + // into ShareState. Payload is the JSON-marshalled radiance peer.Status + // (see lantern-core/core.go EventTypePeerStatus). Phase strings come + // from radiance/peer/peer.go's Phase constants; we map them through + // SharePhase.fromWire so an unknown future phase falls back to idle + // instead of crashing the consumer. + void _handlePeerStatus(String message) { + try { + final payload = jsonDecode(message) as Map; + final phase = SharePhase.fromWire(payload['phase'] as String?); + final errMsg = payload['error'] as String?; + state = state.copyWith( + phase: phase, + errorMessage: (errMsg == null || errMsg.isEmpty) ? null : errMsg, + ); + } catch (e) { + debugPrint('share-my-connection: bad peer-status event: $e'); + } + } } final shareProvider = @@ -356,11 +428,40 @@ class _StatusCard extends StatelessWidget { @override Widget build(BuildContext context) { final textTheme = Theme.of(context).textTheme; - final modeLabel = switch (state.mode) { - ShareMode.off => state.probing ? 'Probing your network…' : 'Off', - ShareMode.unbounded => + // Status text source-of-truth, in priority order: + // 1. Off and not probing → "Off" + // 2. Probing UPnP locally → "Probing your network…" + // 3. SmC mode → granular phase from radiance peer.Status. The + // backend emits one phase per stage during Start so the user + // sees real progress instead of "Active" for several seconds. + // 4. Unbounded mode → static "Active — Unbounded" (no equivalent + // staged lifecycle on the broflake side yet). + final modeLabel = switch ((state.mode, state.phase)) { + (ShareMode.off, _) => + state.probing ? 'Probing your network…' : 'Off', + (ShareMode.unbounded, _) => 'Active — sharing via Unbounded (WebRTC)', - ShareMode.smc => + (ShareMode.smc, SharePhase.mappingPort) => + 'Opening port on your router…', + (ShareMode.smc, SharePhase.detectingIp) => + 'Detecting your public IP…', + (ShareMode.smc, SharePhase.registering) => + 'Registering with Lantern…', + (ShareMode.smc, SharePhase.startingProxy) => + 'Starting local proxy…', + (ShareMode.smc, SharePhase.verifying) => + 'Verifying connectivity…', + (ShareMode.smc, SharePhase.serving) => + 'Sharing — ready to serve users in censored regions', + (ShareMode.smc, SharePhase.stopping) => 'Stopping…', + (ShareMode.smc, SharePhase.error) => + state.errorMessage != null + ? "Couldn't share: ${state.errorMessage}" + : "Couldn't share — try toggling again", + // SmC active but no phase yet (e.g. very first frame after toggle + // before the backend's first event arrives) — fall back to the + // legacy active label so the UI isn't blank. + (ShareMode.smc, SharePhase.idle) => 'Active — sharing via Share My Connection (residential proxy)', }; From a620404a981633754dd7364225765190b5d68c58 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 11 May 2026 14:30:05 -0600 Subject: [PATCH 17/37] core: instrument peer-connection subscriber to pair with radiance breadcrumb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If radiance's peer listener logs "forwarding" but this subscriber doesn't log "forwarding to Flutter", events.Emit is reaching no subscriber — the events bus is broken between Emit and Subscribe (process boundary in gomobile builds, etc.). If both log but Flutter sees nothing, the FlutterEvent bridge is the culprit. Spam-friendly: ~1 line per accept/close, bounded by peer inbound throughput. Co-Authored-By: Claude Opus 4.7 (1M context) --- lantern-core/core.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lantern-core/core.go b/lantern-core/core.go index 2b0688985b..ee41cb79ae 100644 --- a/lantern-core/core.go +++ b/lantern-core/core.go @@ -405,6 +405,16 @@ func (lc *LanternCore) listenDataCapEvents() { // is more common than for SmC's long-lived TCP). func (lc *LanternCore) listenPeerConnectionEvents() { events.Subscribe(func(evt peer.ConnectionEvent) { + // Diagnostic: every time this fires, we know events.Emit reached + // the subscriber. Pairs with the breadcrumb in radiance peer.go's + // peerconn listener — if the radiance side logs "forwarding" but + // we don't see this, the events bus is dropping between Emit and + // Subscribe (process boundary in gomobile builds, etc.). If both + // fire but Flutter sees nothing, the FlutterEvent bridge is the + // culprit. Spam-friendly: ~1 per accept/close, bounded by peer + // inbound throughput. + slog.Info("peer-connection subscriber: forwarding to Flutter", + "state", evt.State, "source", evt.Source) jsonBytes, err := json.Marshal(map[string]any{ "state": evt.State, "source": evt.Source, From beb020d942567f6c14ed899014bc6fe0056e4d04 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 11 May 2026 15:21:29 -0600 Subject: [PATCH 18/37] core: log listenPeerConnectionEvents goroutine entry One-shot diagnostic: if we see radiance peer listener firing but never this line, the goroutine that calls events.Subscribe was never started. Co-Authored-By: Claude Opus 4.7 (1M context) --- lantern-core/core.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lantern-core/core.go b/lantern-core/core.go index ee41cb79ae..ba297f2dd9 100644 --- a/lantern-core/core.go +++ b/lantern-core/core.go @@ -404,6 +404,12 @@ func (lc *LanternCore) listenDataCapEvents() { // reconnects (broflake's WebRTC sessions are short and same-IP churn // is more common than for SmC's long-lived TCP). func (lc *LanternCore) listenPeerConnectionEvents() { + // One-shot diagnostic: confirms this goroutine was actually started by + // the LanternCore init path. If we see "peer listener: forwarding..." + // from radiance but never see this line, listenPeerConnectionEvents + // was never called — init bailed out earlier or the goroutine was + // dropped. + slog.Info("peer-connection subscriber: registering events.Subscribe") events.Subscribe(func(evt peer.ConnectionEvent) { // Diagnostic: every time this fires, we know events.Emit reached // the subscriber. Pairs with the breadcrumb in radiance peer.go's From 71342dea37158805a3e816bc0dd22620a0578f17 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 11 May 2026 15:47:54 -0600 Subject: [PATCH 19/37] core: consume peer events over IPC SSE instead of in-process events.Subscribe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The events.Subscribe path was broken — radiance/peer emits in the lanternd process, but lantern-core's subscriber lives in Liblantern. Process boundary means two separate events package instances; subscribers=0 at every emit. Replace both listenPeerStatusEvents and listenPeerConnectionEvents (peer half) with the IPC client's PeerStatusEvents / PeerConnectionEvents SSE stream methods. The unbounded.ConnectionEvent half stays on events.Subscribe — broflake-as-library runs in the consumer process today and doesn't hit the cross-process gap. Co-Authored-By: Claude Opus 4.7 (1M context) --- lantern-core/core.go | 59 +++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/lantern-core/core.go b/lantern-core/core.go index ba297f2dd9..69dcff5575 100644 --- a/lantern-core/core.go +++ b/lantern-core/core.go @@ -404,33 +404,33 @@ func (lc *LanternCore) listenDataCapEvents() { // reconnects (broflake's WebRTC sessions are short and same-IP churn // is more common than for SmC's long-lived TCP). func (lc *LanternCore) listenPeerConnectionEvents() { - // One-shot diagnostic: confirms this goroutine was actually started by - // the LanternCore init path. If we see "peer listener: forwarding..." - // from radiance but never see this line, listenPeerConnectionEvents - // was never called — init bailed out earlier or the goroutine was - // dropped. - slog.Info("peer-connection subscriber: registering events.Subscribe") - events.Subscribe(func(evt peer.ConnectionEvent) { - // Diagnostic: every time this fires, we know events.Emit reached - // the subscriber. Pairs with the breadcrumb in radiance peer.go's - // peerconn listener — if the radiance side logs "forwarding" but - // we don't see this, the events bus is dropping between Emit and - // Subscribe (process boundary in gomobile builds, etc.). If both - // fire but Flutter sees nothing, the FlutterEvent bridge is the - // culprit. Spam-friendly: ~1 per accept/close, bounded by peer - // inbound throughput. - slog.Info("peer-connection subscriber: forwarding to Flutter", - "state", evt.State, "source", evt.Source) - jsonBytes, err := json.Marshal(map[string]any{ - "state": evt.State, - "source": evt.Source, + // peer.ConnectionEvent: subscribe via the IPC client's SSE stream. + // The events package's globals are process-scoped — events.Emit in + // lanternd (where radiance/peer runs) doesn't reach events.Subscribe + // in Liblantern. The /peer/connection/events SSE endpoint in + // radiance/ipc/server.go bridges the two processes. + go func() { + err := lc.client.PeerConnectionEvents(lc.ctx, func(evt peer.ConnectionEvent) { + jsonBytes, err := json.Marshal(map[string]any{ + "state": evt.State, + "source": evt.Source, + }) + if err != nil { + slog.Error("marshal peer connection event", "error", err) + return + } + lc.notifyFlutter(EventTypePeerConnection, string(jsonBytes)) }) - if err != nil { - slog.Error("marshal peer connection event", "error", err) - return + if err != nil && lc.ctx.Err() == nil { + slog.Error("peer-connection event stream exited unexpectedly", "error", err) } - lc.notifyFlutter(EventTypePeerConnection, string(jsonBytes)) - }) + }() + // unbounded.ConnectionEvent stays on in-process events.Subscribe for + // now. Unbounded runs in the same process as the consumer in mobile + // builds (broflake-as-library); the desktop path doesn't yet have a + // gomobile-bridged Unbounded peer, so the cross-process gap doesn't + // hit here today. Worth revisiting if Unbounded ever moves out of + // process. events.Subscribe(func(evt unbounded.ConnectionEvent) { jsonBytes, err := json.Marshal(map[string]any{ "state": evt.State, @@ -457,7 +457,11 @@ func (lc *LanternCore) listenPeerConnectionEvents() { // carries phase, error, active, sharing_since, external_ip, // external_port, route_id with stable JSON tags. func (lc *LanternCore) listenPeerStatusEvents() { - events.Subscribe(func(evt peer.StatusEvent) { + // Same cross-process bridging story as listenPeerConnectionEvents: the + // peer.StatusEvent emits live in lanternd, so subscribing in this + // process via events.Subscribe gets us nothing. /peer/status/events + // SSE in radiance/ipc/server.go is the canonical source. + err := lc.client.PeerStatusEvents(lc.ctx, func(evt peer.StatusEvent) { jsonBytes, err := json.Marshal(evt.Status) if err != nil { slog.Error("marshal peer status event", "error", err) @@ -465,6 +469,9 @@ func (lc *LanternCore) listenPeerStatusEvents() { } lc.notifyFlutter(EventTypePeerStatus, string(jsonBytes)) }) + if err != nil && lc.ctx.Err() == nil { + slog.Error("peer-status event stream exited unexpectedly", "error", err) + } } ///////////////// From 7dcf3d17f895a5204f4f3ef2da95ac1d35e8fd8a Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Tue, 12 May 2026 09:56:44 -0600 Subject: [PATCH 20/37] SmC: real per-peer geo, on-globe heart burst, arc reversal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Geo: peerLookup switched from geo.getiantem.org/ (returns 404 for arbitrary IPs — every peer collapsed to the IR-fallback center) to ipwho.is (HTTPS, no auth, city-level lat/lon + country name + flag emoji). PeerLookup now returns PeerGeo with a real, unique location per peer. - Event model: UnboundedConnectionEvent carries country name, flag emoji, coords, and an isReplay flag. - Notifier: ref-counts streams per TCP peer so the arc persists until the peer's last H2 stream closes (samizdat multiplexes many streams over one conn); resolves geo async then emits enriched events; replayCurrentPeers() seeds the globe with existing peers when the user navigates to SmC mid-stream; emits synthetic -1's on toggle-off so arcs don't orphan when peer.Client.Stop suppresses the box.Close cascade. - Globe: arcs linger 5s past last -1 so brief URL-test probes still register; coords jittered ±2° per workerIdx hash so multiple peers in the same city fan out instead of overlapping; arc direction reversed (censored user → uncensored peer) so the dash animation reads as traffic arriving at us. - Heart burst: on-globe animation anchored at peer coords via Point.labelBuilder (lib projects 3D→2D for us). Uses the actual assets from getlantern/unbounded — explosion.json Lottie + the inline FF5A79 heart SVG path via CustomPainter. 4.6s burst + 4.2s fading country label below. - StatusCard: small info_outline tooltip explaining that most events are short URL-test liveness probes (601 of ~700 CONNECTs in a measured session were to api.iantem.io — clients probing peer reachability before sending real traffic). Co-Authored-By: Claude Opus 4.7 (1M context) --- assets/unbounded/explosion.json | 1 + .../models/unbounded_connection_event.dart | 18 + lib/core/services/geo_lookup_service.dart | 70 ++- .../share_my_connection.dart | 413 ++++++++++++++++-- pubspec.lock | 24 + pubspec.yaml | 1 + 6 files changed, 477 insertions(+), 50 deletions(-) create mode 100644 assets/unbounded/explosion.json diff --git a/assets/unbounded/explosion.json b/assets/unbounded/explosion.json new file mode 100644 index 0000000000..1773e7f105 --- /dev/null +++ b/assets/unbounded/explosion.json @@ -0,0 +1 @@ +{"nm":"F","h":502,"w":420,"meta":{"g":"LottieFiles Figma v42"},"layers":[{"ty":4,"nm":"V","sr":1,"st":0,"op":138,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":114},{"s":[16,12.78],"t":137}]},"s":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":114},{"s":[100,100],"t":137}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[121,437.23],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[187.06,388.01],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[187.06,388.01],"t":114},{"s":[187.06,388.01],"t":137}]},"r":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[12.84],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[12.84],"t":114},{"s":[12.84],"t":137}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]}},"shapes":[{"ty":"sh","nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":114},{"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":137}]}},{"ty":"fl","nm":"","c":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":114},{"s":[1,0.353,0.475],"t":137}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[100],"t":137}]}}],"ind":1},{"ty":4,"nm":"V","sr":1,"st":0,"op":138,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0.03},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":114},{"s":[16,12.78],"t":137}]},"s":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0.03},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":114},{"s":[100,100],"t":137}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[286.35,305.11],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[286.35,305.11],"t":0.03},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[286.35,305.11],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[286.35,305.11],"t":114},{"s":[286.35,305.11],"t":137}]},"r":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[-15],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[-15],"t":0.03},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[-15],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[-15],"t":114},{"s":[-15],"t":137}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0.03},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]}},"shapes":[{"ty":"sh","nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0.03},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":114},{"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":137}]}},{"ty":"fl","nm":"","c":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0.03},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":114},{"s":[1,0.353,0.475],"t":137}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0.03},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[100],"t":137}]}}],"ind":2},{"ty":4,"nm":"V","sr":1,"st":0,"op":138,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":114},{"s":[16,12.78],"t":137}]},"s":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":114},{"s":[100,100],"t":137}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[121,437.23],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[57,292.33],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[57,292.33],"t":114},{"s":[57,292.33],"t":137}]},"r":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[13.56],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[13.56],"t":114},{"s":[13.56],"t":137}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]}},"shapes":[{"ty":"sh","nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":114},{"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":137}]}},{"ty":"fl","nm":"","c":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":114},{"s":[1,0.353,0.475],"t":137}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[100],"t":137}]}}],"ind":3},{"ty":4,"nm":"V","sr":1,"st":0,"op":138,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0.03},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":114},{"s":[16,12.78],"t":137}]},"s":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0.03},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":114},{"s":[100,100],"t":137}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[89,362.45],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[89,362.45],"t":0.03},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[89,362.45],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[89,362.45],"t":114},{"s":[89,362.45],"t":137}]},"r":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0.03},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0.03},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]}},"shapes":[{"ty":"sh","nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0.03},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":114},{"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":137}]}},{"ty":"fl","nm":"","c":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0.03},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":114},{"s":[1,0.353,0.475],"t":137}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0.03},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[100],"t":137}]}}],"ind":4},{"ty":4,"nm":"V","sr":1,"st":0,"op":138,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":114},{"s":[16,12.78],"t":137}]},"s":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":114},{"s":[100,100],"t":137}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[121,437.23],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[347,349.67],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[347,349.67],"t":114},{"s":[347,349.67],"t":137}]},"r":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]}},"shapes":[{"ty":"sh","nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":114},{"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":137}]}},{"ty":"fl","nm":"","c":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":114},{"s":[1,0.353,0.475],"t":137}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[100],"t":137}]}}],"ind":5},{"ty":4,"nm":"V","sr":1,"st":0,"op":138,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":114},{"s":[16,12.78],"t":137}]},"s":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":114},{"s":[100,100],"t":137}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[121,437.23],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[363,193.71],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[363,193.71],"t":114},{"s":[363,193.71],"t":137}]},"r":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[-10.18],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[-10.18],"t":114},{"s":[-10.18],"t":137}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]}},"shapes":[{"ty":"sh","nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":114},{"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":137}]}},{"ty":"fl","nm":"","c":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":114},{"s":[1,0.353,0.475],"t":137}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[100],"t":137}]}}],"ind":6},{"ty":4,"nm":"V","sr":1,"st":0,"op":138,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":114},{"s":[16,12.78],"t":137}]},"s":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":114},{"s":[100,100],"t":137}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[121,437.23],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[41,180.93],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[41,180.93],"t":114},{"s":[41,180.93],"t":137}]},"r":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[-7.58],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[-7.58],"t":114},{"s":[-7.58],"t":137}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]}},"shapes":[{"ty":"sh","nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":114},{"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":137}]}},{"ty":"fl","nm":"","c":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":114},{"s":[1,0.353,0.475],"t":137}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[100],"t":137}]}}],"ind":7},{"ty":4,"nm":"V","sr":1,"st":0,"op":138,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":114},{"s":[16,12.78],"t":137}]},"s":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":114},{"s":[100,100],"t":137}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[121,437.23],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[189.5,216.92],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[189.5,216.92],"t":114},{"s":[189.5,216.92],"t":137}]},"r":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]}},"shapes":[{"ty":"sh","nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":114},{"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":137}]}},{"ty":"fl","nm":"","c":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":114},{"s":[1,0.353,0.475],"t":137}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[100],"t":137}]}}],"ind":8},{"ty":4,"nm":"V","sr":1,"st":0,"op":138,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":114},{"s":[16,12.78],"t":137}]},"s":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":114},{"s":[100,100],"t":137}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[121,437.23],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[270.35,139.3],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[270.35,139.3],"t":114},{"s":[270.35,139.3],"t":137}]},"r":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[-8.33],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[-8.33],"t":114},{"s":[-8.33],"t":137}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]}},"shapes":[{"ty":"sh","nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":114},{"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":137}]}},{"ty":"fl","nm":"","c":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":114},{"s":[1,0.353,0.475],"t":137}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[100],"t":137}]}}],"ind":9},{"ty":4,"nm":"V","sr":1,"st":0,"op":138,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":114},{"s":[16,12.78],"t":137}]},"s":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":114},{"s":[100,100],"t":137}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[121,437.23],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[249,317.89],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[249,317.89],"t":114},{"s":[249,317.89],"t":137}]},"r":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[19.7],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[19.7],"t":114},{"s":[19.7],"t":137}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]}},"shapes":[{"ty":"sh","nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":114},{"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":137}]}},{"ty":"fl","nm":"","c":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":114},{"s":[1,0.353,0.475],"t":137}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[100],"t":137}]}}],"ind":10},{"ty":4,"nm":"V","sr":1,"st":0,"op":138,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":114},{"s":[16,12.78],"t":137}]},"s":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":114},{"s":[100,100],"t":137}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[121,437.23],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[89,96.56],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[89,96.56],"t":114},{"s":[89,96.56],"t":137}]},"r":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]}},"shapes":[{"ty":"sh","nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":114},{"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":137}]}},{"ty":"fl","nm":"","c":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":114},{"s":[1,0.353,0.475],"t":137}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[100],"t":137}]}}],"ind":11},{"ty":4,"nm":"V","sr":1,"st":0,"op":138,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":114},{"s":[16,12.78],"t":137}]},"s":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":114},{"s":[100,100],"t":137}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[121,437.23],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[272.03,46.69],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[272.03,46.69],"t":114},{"s":[272.03,46.69],"t":137}]},"r":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]}},"shapes":[{"ty":"sh","nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":114},{"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":137}]}},{"ty":"fl","nm":"","c":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":114},{"s":[1,0.353,0.475],"t":137}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[100],"t":137}]}}],"ind":12},{"ty":4,"nm":"V","sr":1,"st":0,"op":138,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":114},{"s":[16,12.78],"t":137}]},"s":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":114},{"s":[100,100],"t":137}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[121,437.23],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[347,71],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[347,71],"t":114},{"s":[347,71],"t":137}]},"r":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]}},"shapes":[{"ty":"sh","nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":114},{"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":137}]}},{"ty":"fl","nm":"","c":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":114},{"s":[1,0.353,0.475],"t":137}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[100],"t":137}]}}],"ind":13},{"ty":4,"nm":"V","sr":1,"st":0,"op":138,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":114},{"s":[16,12.78],"t":137}]},"s":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":114},{"s":[100,100],"t":137}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[121,437.23],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[152.62,62.88],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[152.62,62.88],"t":114},{"s":[152.62,62.88],"t":137}]},"r":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[20.16],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[20.16],"t":114},{"s":[20.16],"t":137}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]}},"shapes":[{"ty":"sh","nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":114},{"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":137}]}},{"ty":"fl","nm":"","c":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":114},{"s":[1,0.353,0.475],"t":137}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[100],"t":137}]}}],"ind":14},{"ty":4,"nm":"V","sr":1,"st":0,"op":138,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":114},{"s":[16,12.78],"t":137}]},"s":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":114},{"s":[100,100],"t":137}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[121,437.23],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[108.69,186.58],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[108.69,186.58],"t":114},{"s":[108.69,186.58],"t":137}]},"r":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[20.43],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[20.43],"t":114},{"s":[20.43],"t":137}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]}},"shapes":[{"ty":"sh","nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":114},{"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":137}]}},{"ty":"fl","nm":"","c":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":114},{"s":[1,0.353,0.475],"t":137}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[100],"t":137}]}}],"ind":15},{"ty":4,"nm":"V","sr":1,"st":0,"op":138,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":114},{"s":[16,12.78],"t":137}]},"s":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":114},{"s":[100,100],"t":137}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[121,437.23],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[397.01,263.98],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[397.01,263.98],"t":114},{"s":[397.01,263.98],"t":137}]},"r":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[-9.8],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[-9.8],"t":114},{"s":[-9.8],"t":137}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]}},"shapes":[{"ty":"sh","nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":114},{"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":137}]}},{"ty":"fl","nm":"","c":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":114},{"s":[1,0.353,0.475],"t":137}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[100],"t":137}]}}],"ind":16},{"ty":4,"nm":"V","sr":1,"st":0,"op":138,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[16,12.78],"t":114},{"s":[16,12.78],"t":137}]},"s":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100,100],"t":114},{"s":[100,100],"t":137}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[121,437.23],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[121,437.23],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[121,437.23],"t":114},{"s":[121,437.23],"t":137}]},"r":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[0],"t":114},{"s":[0],"t":137}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[0],"t":137}]}},"shapes":[{"ty":"sh","nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":114},{"s":[{"c":true,"i":[[0,0],[1.01,-4.53],[3.3,-8.84],[-1.28,-1.44],[3.68,9.7]],"o":[[-3.41,-9.06],[-1.39,-4.79],[-3.68,9.86],[1.28,-1.17],[0,0]],"v":[[31.5,5.42],[16,4.94],[0.5,5.42],[16,25.56],[31.5,5.42]]}],"t":137}]}},{"ty":"fl","nm":"","c":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[1,0.353,0.475],"t":114},{"s":[1,0.353,0.475],"t":137}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":0},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":90},{"o":{"x":0,"y":0},"i":{"x":0.58,"y":1},"s":[100],"t":114},{"s":[100],"t":137}]}}],"ind":17}],"v":"5.7.0","fr":30,"op":136.53,"ip":0,"assets":[]} \ No newline at end of file diff --git a/lib/core/models/unbounded_connection_event.dart b/lib/core/models/unbounded_connection_event.dart index eb643f255a..bcd040c515 100644 --- a/lib/core/models/unbounded_connection_event.dart +++ b/lib/core/models/unbounded_connection_event.dart @@ -1,13 +1,31 @@ +import 'package:flutter_earth_globe/globe_coordinates.dart'; + /// Represents a consumer connection change from the broflake widget proxy. class UnboundedConnectionEvent { final int state; // 1 = connected, -1 = disconnected final int workerIdx; final String addr; // IP address + // Geo fields are populated by ShareNotifier after peer lookup. Empty on + // legacy events and on -1 frames (where only workerIdx matters for the + // globe to remove the arc). + final String countryName; + final String countryCode; + final String flagEmoji; + final GlobeCoordinates? coordinates; + // True for synthetic events the notifier emits to seed a newly-mounted + // globe with peers that connected before the screen opened. Lets the UI + // suppress the "new connection from " burst for replays. + final bool isReplay; UnboundedConnectionEvent({ required this.state, required this.workerIdx, required this.addr, + this.countryName = '', + this.countryCode = '', + this.flagEmoji = '', + this.coordinates, + this.isReplay = false, }); factory UnboundedConnectionEvent.fromJson(Map json) { diff --git a/lib/core/services/geo_lookup_service.dart b/lib/core/services/geo_lookup_service.dart index 50150fe375..110aa38a8a 100644 --- a/lib/core/services/geo_lookup_service.dart +++ b/lib/core/services/geo_lookup_service.dart @@ -3,11 +3,38 @@ import 'dart:convert'; import 'package:flutter_earth_globe/globe_coordinates.dart'; import 'package:http/http.dart' as http; +/// Result of a peer geo lookup. Country/flag default to empty when the +/// lookup fails; coordinates default to a centre-of-the-globe sentinel. +class PeerGeo { + const PeerGeo({ + required this.coordinates, + required this.countryName, + required this.countryCode, + required this.flagEmoji, + }); + + final GlobeCoordinates coordinates; + final String countryName; + final String countryCode; + final String flagEmoji; + + static const unknown = PeerGeo( + coordinates: GlobeCoordinates(0, 0), + countryName: '', + countryCode: '', + flagEmoji: '', + ); +} + class GeoLookupService { - static const _geoUrl = 'https://geo.getiantem.org'; + static const _selfUrl = 'https://geo.getiantem.org'; + // ipwho.is: HTTPS, no auth, 10k req/month free. Returns country + lat/lon + // + flag emoji in one shot. + static const _peerUrl = 'https://ipwho.is'; - // ISO country code → approximate centre coordinates - static const _countries = { + // ISO country code → approximate centre coordinates. Used as a fallback + // when ipwho.is doesn't return city-level coords. + static const _countryCenters = { 'AF': (lat: 33.0, lng: 65.0), 'AL': (lat: 41.0, lng: 20.0), 'DZ': (lat: 28.0, lng: 3.0), @@ -133,7 +160,7 @@ class GeoLookupService { }; static GlobeCoordinates _isoToCoords(String iso) { - final c = _countries[iso] ?? _countries['US']!; + final c = _countryCenters[iso] ?? _countryCenters['US']!; return GlobeCoordinates(c.lat, c.lng); } @@ -141,7 +168,7 @@ class GeoLookupService { static Future selfLookup() async { try { final response = await http - .get(Uri.parse('$_geoUrl/')) + .get(Uri.parse('$_selfUrl/')) .timeout(const Duration(seconds: 5)); if (response.statusCode == 200) { final data = jsonDecode(response.body) as Map; @@ -154,20 +181,31 @@ class GeoLookupService { return _isoToCoords('US'); } - /// Looks up the country for a peer [ip] address. - static Future peerLookup(String ip) async { + /// Looks up country, flag, and coordinates for a peer [ip] address. + /// Returns [PeerGeo.unknown] on any failure so callers can suppress the + /// arc / banner rather than displaying a wrong country. + static Future peerLookup(String ip) async { try { final response = await http - .get(Uri.parse('$_geoUrl/$ip')) + .get(Uri.parse('$_peerUrl/$ip')) .timeout(const Duration(seconds: 5)); - if (response.statusCode == 200) { - final data = jsonDecode(response.body) as Map; - final iso = - (data['Country'] as Map?)?['IsoCode'] as String? ?? - 'IR'; - return _isoToCoords(iso); - } + if (response.statusCode != 200) return PeerGeo.unknown; + final data = jsonDecode(response.body) as Map; + if (data['success'] != true) return PeerGeo.unknown; + final iso = (data['country_code'] as String?) ?? ''; + final lat = (data['latitude'] as num?)?.toDouble(); + final lng = (data['longitude'] as num?)?.toDouble(); + final coords = (lat != null && lng != null) + ? GlobeCoordinates(lat, lng) + : _isoToCoords(iso); + final flagObj = data['flag'] as Map?; + return PeerGeo( + coordinates: coords, + countryName: (data['country'] as String?) ?? '', + countryCode: iso, + flagEmoji: (flagObj?['emoji'] as String?) ?? '', + ); } catch (_) {} - return _isoToCoords('IR'); + return PeerGeo.unknown; } } diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index a642097011..3a047afc57 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -26,6 +26,7 @@ import 'package:flutter_earth_globe/point.dart'; import 'package:flutter_earth_globe/point_connection.dart'; import 'package:flutter_earth_globe/point_connection_style.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:lottie/lottie.dart'; import 'package:lantern/core/common/common.dart'; import 'package:lantern/core/models/unbounded_connection_event.dart'; import 'package:lantern/core/services/geo_lookup_service.dart'; @@ -124,6 +125,15 @@ class ShareState { // ─── Notifier (mock-backed) ────────────────────────────────────────────────── +class _PeerArc { + _PeerArc(this.workerIdx) : streamCount = 1; + final int workerIdx; + int streamCount; + // Geo is resolved async after the first +1 lands. Until then the peer is + // tracked but no arc is emitted — avoids a flash of "unknown" arcs. + PeerGeo? geo; +} + class ShareNotifier extends Notifier { // Persisted in real impl; in-process for the prototype so the disclosure // re-fires on app restart and is easy to demo. @@ -131,7 +141,10 @@ class ShareNotifier extends Notifier { StreamSubscription? _appEventSub; int _workerSeq = 0; - final Map _sourceToWorker = {}; + // Per-peer arc + active-stream count. samizdat multiplexes many H2 streams + // over one TCP conn, all sharing the same RemoteAddr — ref-count so the arc + // persists until the peer's LAST stream closes, not its first. + final Map _peerArcs = {}; final _eventController = StreamController.broadcast(); @@ -291,7 +304,7 @@ class ShareNotifier extends Notifier { // already use. void _startEventSubscription(WidgetRef widgetRef) { - _sourceToWorker.clear(); + _peerArcs.clear(); _appEventSub = widgetRef .read(lanternServiceProvider) .watchAppEvents() @@ -310,29 +323,38 @@ class ShareNotifier extends Notifier { if (ip.isEmpty) return; if (eventState == 1) { - // Each (source IP) gets a stable worker idx so the matching - // disconnect can find the arc to remove. Repeated +1 from the - // same source (re-connect after disconnect) gets a new idx. - if (_sourceToWorker.containsKey(ip)) return; + final existing = _peerArcs[ip]; + if (existing != null) { + existing.streamCount++; + return; + } final widx = _workerSeq++; - _sourceToWorker[ip] = widx; - _eventController.add(UnboundedConnectionEvent( - state: 1, - workerIdx: widx, - addr: ip, - )); + final arc = _PeerArc(widx); + _peerArcs[ip] = arc; state = state.copyWith( activeCount: state.activeCount + 1, totalCount: state.totalCount + 1, ); + // Resolve country async. Emit the +1 only after lookup so the + // globe can render the arc at the right coords and the UI can + // surface the country name in the connection banner. + unawaited(_resolveAndEmit(ip, arc)); } else if (eventState == -1) { - final widx = _sourceToWorker.remove(ip); - if (widx == null) return; - _eventController.add(UnboundedConnectionEvent( - state: -1, - workerIdx: widx, - addr: '', - )); + final entry = _peerArcs[ip]; + if (entry == null) return; + entry.streamCount--; + if (entry.streamCount > 0) return; + _peerArcs.remove(ip); + // Only emit -1 if we already emitted a +1 for this peer (i.e. + // the geo lookup completed). Otherwise the globe never saw it + // and a -1 with no preceding +1 would just be noise. + if (entry.geo != null) { + _eventController.add(UnboundedConnectionEvent( + state: -1, + workerIdx: entry.workerIdx, + addr: '', + )); + } state = state.copyWith( activeCount: max(0, state.activeCount - 1), ); @@ -345,10 +367,75 @@ class ShareNotifier extends Notifier { }); } + Future _resolveAndEmit(String ip, _PeerArc arc) async { + PeerGeo geo; + try { + geo = await GeoLookupService.peerLookup(ip); + } catch (_) { + geo = PeerGeo.unknown; + } + // Peer may have disconnected before the lookup returned. The map + // entry's identity (workerIdx) is the cheapest check. + final current = _peerArcs[ip]; + if (current == null || current.workerIdx != arc.workerIdx) return; + // Skip arcs we couldn't geo-locate. The peer is still counted in + // activeCount, but we don't draw a wrong-country arc. + if (geo.countryCode.isEmpty) return; + arc.geo = geo; + _eventController.add(UnboundedConnectionEvent( + state: 1, + workerIdx: arc.workerIdx, + addr: ip, + countryName: geo.countryName, + countryCode: geo.countryCode, + flagEmoji: geo.flagEmoji, + coordinates: geo.coordinates, + )); + } + + /// Replays a synthetic +1 for every currently-active peer that has a + /// resolved geo. Callers (e.g. the globe widget when it mounts after + /// the user navigates into the screen) get a one-shot seed of the + /// current world state so they don't render an empty globe despite + /// active connections. Replayed events have isReplay=true so the UI + /// can suppress the "new connection" burst. + void replayCurrentPeers() { + for (final entry in _peerArcs.entries) { + final arc = entry.value; + final geo = arc.geo; + if (geo == null) continue; + _eventController.add(UnboundedConnectionEvent( + state: 1, + workerIdx: arc.workerIdx, + addr: entry.key, + countryName: geo.countryName, + countryCode: geo.countryCode, + flagEmoji: geo.flagEmoji, + coordinates: geo.coordinates, + isReplay: true, + )); + } + } + void _stopEventSubscription() { + // Synthesize -1 for every active peer BEFORE killing the source + // stream. peer.Client.Stop on the Go side suppresses the box.Close + // disconnect cascade (correct — avoids a flood of post-Stop noise), + // so without this loop the globe would never see -1's for peers + // that were live at toggle-time. Their arcs would orphan and rotate + // with the globe indefinitely. With this loop, the globe sees real + // -1's and runs them through the normal linger-then-remove path. + for (final arc in _peerArcs.values) { + if (arc.geo == null) continue; + _eventController.add(UnboundedConnectionEvent( + state: -1, + workerIdx: arc.workerIdx, + addr: '', + )); + } _appEventSub?.cancel(); _appEventSub = null; - _sourceToWorker.clear(); + _peerArcs.clear(); _workerSeq = 0; } @@ -515,11 +602,44 @@ class _StatusCard extends StatelessWidget { const SizedBox(height: 12), const Divider(height: 1), const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, + Stack( children: [ - _Stat(label: 'Active now', value: '${state.activeCount}'), - _Stat(label: 'Total today', value: '${state.totalCount}'), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _Stat(label: 'Active now', value: '${state.activeCount}'), + _Stat(label: 'Total today', value: '${state.totalCount}'), + ], + ), + Positioned( + top: 0, + right: 0, + child: Tooltip( + triggerMode: TooltipTriggerMode.tap, + waitDuration: const Duration(milliseconds: 200), + showDuration: const Duration(seconds: 8), + preferBelow: false, + margin: const EdgeInsets.symmetric(horizontal: 24), + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 10), + textStyle: const TextStyle(color: Colors.white, fontSize: 12), + decoration: BoxDecoration( + color: Colors.black87, + borderRadius: BorderRadius.circular(8), + ), + message: + 'Most connections are short liveness probes — Lantern ' + 'clients periodically check that this peer is reachable ' + 'before sending real traffic. A quick burst from many ' + 'locations is normal; an arc that lingers represents an ' + 'actual user session.', + child: Icon( + Icons.info_outline, + size: 16, + color: Theme.of(context).hintColor, + ), + ), + ), ], ), ], @@ -574,6 +694,15 @@ class _GlobeViewState extends ConsumerState<_GlobeView> { StreamSubscription? _eventSub; GlobeCoordinates? _originCoords; + // Pending arc removals: peer goes idle → we don't yank the arc + // immediately so brief URL-test probes (which dominate samizdat-peer + // traffic) still register visually. Timer is cancelled if the same + // workerIdx +1's again before it fires. + final Map _pendingRemovals = {}; + static const _arcLinger = Duration(seconds: 5); + // Matches the explosion.json timeline (4.55s at 30fps) so the Lottie + // plays to completion before the anchor point is removed. + static const _burstDuration = Duration(milliseconds: 4600); @override void initState() { @@ -583,15 +712,24 @@ class _GlobeViewState extends ConsumerState<_GlobeView> { _applyTheme(); }; _initOrigin(); + // Subscribe BEFORE the replay call so we don't miss any concurrent + // +1 events. The broadcast stream delivers synchronously when added, + // but the replay events come from inside the same notifier so order + // is preserved. _eventSub = ref .read(shareProvider.notifier) .connectionEvents .listen(_handleEvent); + ref.read(shareProvider.notifier).replayCurrentPeers(); } @override void dispose() { _eventSub?.cancel(); + for (final t in _pendingRemovals.values) { + t.cancel(); + } + _pendingRemovals.clear(); _globeController.dispose(); super.dispose(); } @@ -618,21 +756,44 @@ class _GlobeViewState extends ConsumerState<_GlobeView> { )); } - Future _handleEvent(UnboundedConnectionEvent event) async { - if (event.state == 1 && event.addr.isNotEmpty) { - await _addPeer(event.workerIdx, event.addr); + void _handleEvent(UnboundedConnectionEvent event) { + if (event.state == 1 && event.coordinates != null) { + // Cancel any lingering removal — same workerIdx is back. + _pendingRemovals.remove(event.workerIdx)?.cancel(); + _addPeer(event); + if (!event.isReplay) _announceArrival(event); } else if (event.state == -1) { - _removePeer(event.workerIdx); + // Linger the arc so brief connections still register visually. + _pendingRemovals[event.workerIdx]?.cancel(); + _pendingRemovals[event.workerIdx] = Timer(_arcLinger, () { + _pendingRemovals.remove(event.workerIdx); + if (!mounted) return; + _removePeer(event.workerIdx); + }); } } - Future _addPeer(int workerIdx, String addr) async { - final coords = await GeoLookupService.peerLookup(addr); + // Jitter coords by a workerIdx-derived offset so multiple peers from + // the same country don't draw arcs on top of each other. Hash-based so + // the same widx always lands in the same slot — no jitter drift on + // replay. + GlobeCoordinates _jittered(GlobeCoordinates base, int widx) { + final hash = widx * 2654435761; // Knuth multiplicative hash + final dLat = ((hash >> 4) & 0xff) / 255.0 * 4.0 - 2.0; // [-2, +2]° + final dLng = ((hash >> 12) & 0xff) / 255.0 * 4.0 - 2.0; + return GlobeCoordinates(base.latitude + dLat, base.longitude + dLng); + } + + void _addPeer(UnboundedConnectionEvent event) { if (!mounted) return; + final coords = _jittered(event.coordinates!, event.workerIdx); + // Arc direction is censored user → uncensored peer (us). The dash + // animation flows from start to end, so the visual "travel" reads + // as traffic arriving at our peer to escape censorship. _globeController.addPointConnection(PointConnection( - id: 'conn_$workerIdx', - start: _originCoords ?? const GlobeCoordinates(0, 0), - end: coords, + id: 'conn_${event.workerIdx}', + start: coords, + end: _originCoords ?? const GlobeCoordinates(0, 0), curveScale: .6, style: PointConnectionStyle( color: _arcColor, @@ -646,12 +807,39 @@ class _GlobeViewState extends ConsumerState<_GlobeView> { ), )); _globeController.addPoint(Point( - id: 'peer_$workerIdx', + id: 'peer_${event.workerIdx}', coordinates: coords, style: PointStyle(color: _peerPointColor, size: 6), )); } + void _announceArrival(UnboundedConnectionEvent event) { + if (!mounted) return; + final coords = _jittered(event.coordinates!, event.workerIdx); + final burstId = 'burst_${event.workerIdx}_${DateTime.now().microsecondsSinceEpoch}'; + // Anchor a zero-size point at the peer's location. flutter_earth_globe + // calls labelBuilder with the projected on-screen position, so the + // burst widget renders directly on top of the peer's spot on the map + // (rotating + projecting along with the globe). + _globeController.addPoint(Point( + id: burstId, + coordinates: coords, + style: const PointStyle(color: Colors.transparent, size: 0.1), + isLabelVisible: true, + labelBuilder: (ctx, _, isHovering, isVisible) { + if (!isVisible) return const SizedBox.shrink(); + return _HeartBurst( + countryName: event.countryName, + flagEmoji: event.flagEmoji, + ); + }, + )); + Future.delayed(_burstDuration, () { + if (!mounted) return; + _globeController.removePoint(burstId); + }); + } + void _removePeer(int workerIdx) { _globeController.removePointConnection('conn_$workerIdx'); _globeController.removePoint('peer_$workerIdx'); @@ -691,6 +879,163 @@ class _GlobeViewState extends ConsumerState<_GlobeView> { } } +// ─── Heart burst ───────────────────────────────────────────────────────────── + +/// Heart-burst anchored to a peer point on the globe — same visual as +/// unbounded.lantern.io. The pink heart is the inline SVG path from +/// `unbounded/ui/.../notification/explosion.tsx` (FF5A79 fill, 32×27 +/// viewBox); the burst is `unbounded/.../explosion.json` played once +/// via the `lottie` Flutter package. A small `flag country` label sits +/// just below and fades alongside. Self-disposes when the anchor point +/// is removed (~1.2s after creation by the caller). +class _HeartBurst extends StatefulWidget { + const _HeartBurst({this.countryName = '', this.flagEmoji = ''}); + + final String countryName; + final String flagEmoji; + + @override + State<_HeartBurst> createState() => _HeartBurstState(); +} + +class _HeartBurstState extends State<_HeartBurst> + with TickerProviderStateMixin { + // Drives only the label's fade in/out — the Lottie file owns its own + // animation timeline. + late final AnimationController _labelCtrl; + late final Animation _labelOpacity; + + // The lottie controller is driven by Lottie's own composition once + // it loads; we kick it off with goToAndPlay equivalent (forward). + AnimationController? _lottieCtrl; + + @override + void initState() { + super.initState(); + // Matches unbounded's notification auto-hide (3.5s visible + 0.7s + // fade-out) so the country label stays readable through the bulk + // of the explosion animation. + _labelCtrl = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 4200), + ); + _labelOpacity = TweenSequence([ + TweenSequenceItem(tween: Tween(begin: 0.0, end: 1.0), weight: 15), + TweenSequenceItem(tween: ConstantTween(1.0), weight: 55), + TweenSequenceItem(tween: Tween(begin: 1.0, end: 0.0), weight: 30), + ]).animate(_labelCtrl); + _labelCtrl.forward(); + } + + @override + void dispose() { + _labelCtrl.dispose(); + _lottieCtrl?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final label = widget.countryName.isEmpty + ? null + : '${widget.flagEmoji} ${widget.countryName}'.trim(); + return IgnorePointer( + child: SizedBox( + width: 120, + height: 120, + child: Stack( + alignment: Alignment.center, + clipBehavior: Clip.none, + children: [ + // Lottie explosion sits behind the heart, sized larger so + // particle spray extends past the heart bounds. Matches + // unbounded's LottieWrapper sizing (420px wide canvas + // around a 32×27 heart) scaled for our anchor. + SizedBox( + width: 120, + height: 120, + child: Lottie.asset( + 'assets/unbounded/explosion.json', + repeat: false, + fit: BoxFit.contain, + onLoaded: (composition) { + _lottieCtrl = AnimationController( + vsync: this, + duration: composition.duration, + )..forward(); + setState(() {}); + }, + controller: _lottieCtrl, + ), + ), + // Heart SVG path — same coords as unbounded's inline SVG + // (FF5A79, 32x27 viewBox). + const SizedBox( + width: 32, + height: 27, + child: CustomPaint(painter: _HeartPainter()), + ), + if (label != null) + Positioned( + top: 78, + child: AnimatedBuilder( + animation: _labelCtrl, + builder: (context, _) => Opacity( + opacity: _labelOpacity.value, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.6), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + label, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} + +/// Pink heart from `getlantern/unbounded` — exact SVG path coords +/// (viewBox 0 0 32 27, fill #FF5A79). +class _HeartPainter extends CustomPainter { + const _HeartPainter(); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint()..color = const Color(0xFFFF5A79); + final path = Path() + ..moveTo(31.5035, 5.87209) + ..cubicTo(28.0938, -3.18494, 17.0123, 0.864084, 16, 5.3926) + ..cubicTo(14.6148, 0.597701, 3.79965, -2.97183, 0.496497, 5.87209) + ..cubicTo(-3.17959, 15.7283, 14.7214, 24.5722, 16, 26.0107) + ..cubicTo(17.2786, 24.8386, 35.1796, 15.5684, 31.5035, 5.87209) + ..close(); + // Scale path from native 32x27 to the canvas size. + final scaled = path.transform(Matrix4.diagonal3Values( + size.width / 32.0, + size.height / 27.0, + 1.0, + ).storage); + canvas.drawPath(scaled, paint); + } + + @override + bool shouldRepaint(_HeartPainter oldDelegate) => false; +} + // ─── Advanced section ──────────────────────────────────────────────────────── /// _AdvancedCard exposes power-user knobs that don't belong in the diff --git a/pubspec.lock b/pubspec.lock index 204a170f99..0f69884877 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -105,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + archive: + dependency: transitive + description: + name: archive + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + url: "https://pub.dev" + source: hosted + version: "4.0.9" args: dependency: transitive description: @@ -1084,6 +1092,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + lottie: + dependency: "direct main" + description: + name: lottie + sha256: "8b6359a7422167014aa73ce763fa133fb832065dcc0ac4d1dec1f603a5cef7d0" + url: "https://pub.dev" + source: hosted + version: "3.3.3" matcher: dependency: transitive description: @@ -1292,6 +1308,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.2" + posix: + dependency: transitive + description: + name: posix + sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + url: "https://pub.dev" + source: hosted + version: "6.5.0" process: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 919146cfe8..8c402c033d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,6 +47,7 @@ dependencies: auto_route: ^11.1.0 #UI Utils flutter_earth_globe: ^2.2.0 + lottie: ^3.3.1 http: ^1.2.2 animated_toggle_switch: ^0.8.7 animated_text_kit: ^4.3.0 From a190859e3fdcdb136865c694f5937a20a79f8bd6 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Tue, 12 May 2026 11:43:10 -0600 Subject: [PATCH 21/37] SmC: lift heart-burst off the globe into a floating toast Anchoring the burst to projected globe coords (via Point.labelBuilder) forced the widget to repaint every rotation frame, which made the globe rotation jittery. The burst is now a separate floating pill overlaid at the bottom of the globe area: - _ArrivalToast subscribes to ShareNotifier.connectionEvents, ignores replays, surfaces the current arrival in a slide-up + fade-in card. ValueKey on workerIdx forces AnimatedSwitcher to swap the widget when overlapping arrivals land so the Lottie restarts cleanly. - _HeartBurst is now just heart + Lottie, no country label, no globe anchor. The label moved into _ArrivalCard alongside the burst. - Removed _announceArrival (Point/labelBuilder pattern) and the burst anchor lifecycle. Globe rotation is smooth again. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../share_my_connection.dart | 308 ++++++++++-------- 1 file changed, 176 insertions(+), 132 deletions(-) diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index 3a047afc57..a4f3320274 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -490,7 +490,22 @@ class ShareMyConnectionScreen extends HookConsumerWidget { const SizedBox(height: 16), Expanded( flex: 3, - child: _GlobeView(), + child: Stack( + children: [ + Positioned.fill(child: _GlobeView()), + // Floating "new connection from X" toast — overlays the + // bottom of the globe area rather than the peer's exact + // location on the sphere. Anchoring to projected coords + // forced the burst to repaint every globe rotation + // frame, which made the rotation jittery. + const Positioned( + left: 0, + right: 0, + bottom: 8, + child: Center(child: _ArrivalToast()), + ), + ], + ), ), const SizedBox(height: 8), _StatusCard(state: state, onToggle: () => notifier.toggle(context, ref)), @@ -700,9 +715,6 @@ class _GlobeViewState extends ConsumerState<_GlobeView> { // workerIdx +1's again before it fires. final Map _pendingRemovals = {}; static const _arcLinger = Duration(seconds: 5); - // Matches the explosion.json timeline (4.55s at 30fps) so the Lottie - // plays to completion before the anchor point is removed. - static const _burstDuration = Duration(milliseconds: 4600); @override void initState() { @@ -761,7 +773,6 @@ class _GlobeViewState extends ConsumerState<_GlobeView> { // Cancel any lingering removal — same workerIdx is back. _pendingRemovals.remove(event.workerIdx)?.cancel(); _addPeer(event); - if (!event.isReplay) _announceArrival(event); } else if (event.state == -1) { // Linger the arc so brief connections still register visually. _pendingRemovals[event.workerIdx]?.cancel(); @@ -813,33 +824,6 @@ class _GlobeViewState extends ConsumerState<_GlobeView> { )); } - void _announceArrival(UnboundedConnectionEvent event) { - if (!mounted) return; - final coords = _jittered(event.coordinates!, event.workerIdx); - final burstId = 'burst_${event.workerIdx}_${DateTime.now().microsecondsSinceEpoch}'; - // Anchor a zero-size point at the peer's location. flutter_earth_globe - // calls labelBuilder with the projected on-screen position, so the - // burst widget renders directly on top of the peer's spot on the map - // (rotating + projecting along with the globe). - _globeController.addPoint(Point( - id: burstId, - coordinates: coords, - style: const PointStyle(color: Colors.transparent, size: 0.1), - isLabelVisible: true, - labelBuilder: (ctx, _, isHovering, isVisible) { - if (!isVisible) return const SizedBox.shrink(); - return _HeartBurst( - countryName: event.countryName, - flagEmoji: event.flagEmoji, - ); - }, - )); - Future.delayed(_burstDuration, () { - if (!mounted) return; - _globeController.removePoint(burstId); - }); - } - void _removePeer(int workerIdx) { _globeController.removePointConnection('conn_$workerIdx'); _globeController.removePoint('peer_$workerIdx'); @@ -879,128 +863,123 @@ class _GlobeViewState extends ConsumerState<_GlobeView> { } } -// ─── Heart burst ───────────────────────────────────────────────────────────── - -/// Heart-burst anchored to a peer point on the globe — same visual as -/// unbounded.lantern.io. The pink heart is the inline SVG path from -/// `unbounded/ui/.../notification/explosion.tsx` (FF5A79 fill, 32×27 -/// viewBox); the burst is `unbounded/.../explosion.json` played once -/// via the `lottie` Flutter package. A small `flag country` label sits -/// just below and fades alongside. Self-disposes when the anchor point -/// is removed (~1.2s after creation by the caller). -class _HeartBurst extends StatefulWidget { - const _HeartBurst({this.countryName = '', this.flagEmoji = ''}); +// ─── Arrival toast ─────────────────────────────────────────────────────────── - final String countryName; - final String flagEmoji; +/// Floating notification overlay shown under the globe when a new peer +/// arrives. Mirrors the unbounded.lantern.io notification pattern: +/// heart-burst on the left, `New connection from ` text on +/// the right. Slides up + fades in, auto-hides after ~3.5s. Listens +/// directly to ShareNotifier.connectionEvents so we don't depend on +/// the globe widget for triggering. +class _ArrivalToast extends ConsumerStatefulWidget { + const _ArrivalToast(); @override - State<_HeartBurst> createState() => _HeartBurstState(); + ConsumerState<_ArrivalToast> createState() => _ArrivalToastState(); } -class _HeartBurstState extends State<_HeartBurst> - with TickerProviderStateMixin { - // Drives only the label's fade in/out — the Lottie file owns its own - // animation timeline. - late final AnimationController _labelCtrl; - late final Animation _labelOpacity; - - // The lottie controller is driven by Lottie's own composition once - // it loads; we kick it off with goToAndPlay equivalent (forward). - AnimationController? _lottieCtrl; +class _ArrivalToastState extends ConsumerState<_ArrivalToast> { + StreamSubscription? _sub; + Timer? _hideTimer; + UnboundedConnectionEvent? _current; @override void initState() { super.initState(); - // Matches unbounded's notification auto-hide (3.5s visible + 0.7s - // fade-out) so the country label stays readable through the bulk - // of the explosion animation. - _labelCtrl = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 4200), - ); - _labelOpacity = TweenSequence([ - TweenSequenceItem(tween: Tween(begin: 0.0, end: 1.0), weight: 15), - TweenSequenceItem(tween: ConstantTween(1.0), weight: 55), - TweenSequenceItem(tween: Tween(begin: 1.0, end: 0.0), weight: 30), - ]).animate(_labelCtrl); - _labelCtrl.forward(); + _sub = ref + .read(shareProvider.notifier) + .connectionEvents + .listen(_onEvent); + } + + void _onEvent(UnboundedConnectionEvent event) { + if (event.state != 1 || event.isReplay) return; + if (event.countryName.isEmpty) return; + if (!mounted) return; + _hideTimer?.cancel(); + setState(() => _current = event); + _hideTimer = Timer(const Duration(milliseconds: 3500), () { + if (!mounted) return; + setState(() => _current = null); + }); } @override void dispose() { - _labelCtrl.dispose(); - _lottieCtrl?.dispose(); + _sub?.cancel(); + _hideTimer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { - final label = widget.countryName.isEmpty - ? null - : '${widget.flagEmoji} ${widget.countryName}'.trim(); + final event = _current; + return AnimatedSwitcher( + duration: const Duration(milliseconds: 280), + transitionBuilder: (child, anim) => FadeTransition( + opacity: anim, + child: SlideTransition( + position: Tween( + begin: const Offset(0, 0.4), end: Offset.zero) + .animate(CurvedAnimation(parent: anim, curve: Curves.easeOut)), + child: child, + ), + ), + child: event == null + ? const SizedBox.shrink(key: ValueKey('arrival-hidden')) + : _ArrivalCard( + // ValueKey forces AnimatedSwitcher to swap children when a + // new arrival lands while the previous toast is still up, + // so the Lottie restarts cleanly. + key: ValueKey('arrival-${event.workerIdx}'), + countryName: event.countryName, + flagEmoji: event.flagEmoji, + ), + ); + } +} + +class _ArrivalCard extends StatelessWidget { + const _ArrivalCard({ + super.key, + required this.countryName, + required this.flagEmoji, + }); + + final String countryName; + final String flagEmoji; + + @override + Widget build(BuildContext context) { return IgnorePointer( - child: SizedBox( - width: 120, - height: 120, - child: Stack( - alignment: Alignment.center, - clipBehavior: Clip.none, + child: Container( + padding: const EdgeInsets.fromLTRB(10, 8, 16, 8), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface.withValues(alpha: 0.92), + borderRadius: BorderRadius.circular(100), + border: Border.all(color: Colors.black12), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.12), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, children: [ - // Lottie explosion sits behind the heart, sized larger so - // particle spray extends past the heart bounds. Matches - // unbounded's LottieWrapper sizing (420px wide canvas - // around a 32×27 heart) scaled for our anchor. - SizedBox( - width: 120, - height: 120, - child: Lottie.asset( - 'assets/unbounded/explosion.json', - repeat: false, - fit: BoxFit.contain, - onLoaded: (composition) { - _lottieCtrl = AnimationController( - vsync: this, - duration: composition.duration, - )..forward(); - setState(() {}); - }, - controller: _lottieCtrl, + const SizedBox(width: 40, height: 40, child: _HeartBurst()), + const SizedBox(width: 12), + Text( + flagEmoji.isEmpty + ? 'New connection from $countryName' + : '$flagEmoji New connection from $countryName', + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, ), ), - // Heart SVG path — same coords as unbounded's inline SVG - // (FF5A79, 32x27 viewBox). - const SizedBox( - width: 32, - height: 27, - child: CustomPaint(painter: _HeartPainter()), - ), - if (label != null) - Positioned( - top: 78, - child: AnimatedBuilder( - animation: _labelCtrl, - builder: (context, _) => Opacity( - opacity: _labelOpacity.value, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, vertical: 3), - decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.6), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - label, - style: const TextStyle( - color: Colors.white, - fontSize: 12, - fontWeight: FontWeight.w500, - ), - ), - ), - ), - ), - ), ], ), ), @@ -1008,6 +987,71 @@ class _HeartBurstState extends State<_HeartBurst> } } +// ─── Heart burst ───────────────────────────────────────────────────────────── + +/// Heart + Lottie explosion lifted from getlantern/unbounded. The pink +/// heart is the inline SVG path from `notification/explosion.tsx` +/// (FF5A79 fill, 32×27 viewBox); the burst is `explosion.json` played +/// once via the `lottie` Flutter package. Rendered inside _ArrivalCard +/// (under the globe), NOT anchored to globe coords — anchoring forced +/// a repaint per globe rotation frame and made rotation jittery. +class _HeartBurst extends StatefulWidget { + const _HeartBurst(); + + @override + State<_HeartBurst> createState() => _HeartBurstState(); +} + +class _HeartBurstState extends State<_HeartBurst> + with TickerProviderStateMixin { + AnimationController? _lottieCtrl; + + @override + void dispose() { + _lottieCtrl?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return IgnorePointer( + child: Stack( + alignment: Alignment.center, + clipBehavior: Clip.none, + children: [ + // Lottie explosion sized so particle spray extends slightly + // past the card bounds (Clip.none on parent lets it overflow). + // Mirrors unbounded's LottieWrapper sizing, scaled down for an + // inline card slot. + Positioned( + width: 110, + height: 110, + child: Lottie.asset( + 'assets/unbounded/explosion.json', + repeat: false, + fit: BoxFit.contain, + onLoaded: (composition) { + _lottieCtrl = AnimationController( + vsync: this, + duration: composition.duration, + )..forward(); + setState(() {}); + }, + controller: _lottieCtrl, + ), + ), + // Heart SVG path — exact coords from unbounded's inline SVG. + const SizedBox( + width: 22, + height: 19, + child: CustomPaint(painter: _HeartPainter()), + ), + ], + ), + ); + } +} + /// Pink heart from `getlantern/unbounded` — exact SVG path coords /// (viewBox 0 0 32 27, fill #FF5A79). class _HeartPainter extends CustomPainter { From 50d9bf7797654af25c757d62ede47c983c416460 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Fri, 29 May 2026 13:59:59 -0600 Subject: [PATCH 22/37] deps: bump radiance to #501 tip + lantern-box to #255 tip; drop local replaces After both stacks were rebased today, repin to fresh pseudo-versions: - github.com/getlantern/radiance @ 3684cef (radiance #501 tip; has peer/, settings.PeerShareEnabledKey, unbounded/) - github.com/getlantern/lantern-box @ 0b63c0f (lantern-box #255 tip; has tracker/peerconn + newer samizdat) Removed the dev-only `replace ../radiance` and `replace ../lantern-box` directives so this PR builds standalone for CI / reviewers. Once both feature stacks land on their respective mains, this commit can be amended away in favor of the released versions. Co-Authored-By: Claude Opus 4.7 --- go.mod | 10 ++-------- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index fccbf4d499..ab99956b5e 100644 --- a/go.mod +++ b/go.mod @@ -2,12 +2,6 @@ module github.com/getlantern/lantern go 1.26.2 -// Local while peer connection-stats endpoint is in flight; remove once -// radiance tags a release that includes peer/connstats.go. -replace github.com/getlantern/radiance => ../radiance - -replace github.com/getlantern/lantern-box => ../lantern-box - // replace github.com/getlantern/lantern-server-provisioner => ../lantern-server-provisioner // replace github.com/sagernet/sing-box => ../sing-box-minimal @@ -29,7 +23,7 @@ replace github.com/quic-go/qpack => github.com/quic-go/qpack v0.5.1 require ( github.com/alecthomas/assert/v2 v2.3.0 github.com/getlantern/lantern-server-provisioner v0.0.0-20251031121934-8ea031fccfa9 - github.com/getlantern/radiance v0.0.0-20260529193818-b9df7d613b1a + github.com/getlantern/radiance v0.0.0-20260529195536-3684cef6632c github.com/sagernet/sing-box v1.12.22 golang.org/x/mobile v0.0.0-20250711185624-d5bb5ecc55c0 golang.org/x/sys v0.41.0 @@ -176,7 +170,7 @@ require ( github.com/getlantern/domainfront v0.0.0-20260419161617-0bff0b2169f4 // indirect github.com/getlantern/keepcurrent v0.0.0-20260422161259-54a4d9a93694 // indirect github.com/getlantern/kindling v0.0.0-20260529141244-21f8b144afab // indirect - github.com/getlantern/lantern-box v0.0.86 // indirect + github.com/getlantern/lantern-box v0.0.87-0.20260529195337-0b63c0f42962 // indirect github.com/getlantern/lantern-water v0.0.0-20260520145825-958775d51395 // indirect github.com/getlantern/osversion v0.0.0-20240418205916-2e84a4a4e175 // indirect github.com/getlantern/pluriconfig v0.0.0-20251126214241-8cc8bc561535 // indirect diff --git a/go.sum b/go.sum index 33e0b0c3da..b7035ee886 100644 --- a/go.sum +++ b/go.sum @@ -247,8 +247,8 @@ github.com/getlantern/keepcurrent v0.0.0-20260422161259-54a4d9a93694 h1:iLWm6S/4 github.com/getlantern/keepcurrent v0.0.0-20260422161259-54a4d9a93694/go.mod h1:ag5g9aWUw2FJcX5RVRpJ9EBQBy5yJuy2WXDouIn/m4w= github.com/getlantern/kindling v0.0.0-20260529141244-21f8b144afab h1:PitYhTvo3oHRKYl4pVAoOIN8bhM+Bw+JBWncMglvHSg= github.com/getlantern/kindling v0.0.0-20260529141244-21f8b144afab/go.mod h1:TGTxpoNVwc8Be4qkBNtf5oj2psJaEIZEq47GOPS7zkA= -github.com/getlantern/lantern-box v0.0.86 h1:myJa+Crg/oMgqSFhX7DOox4XcVIx8VFiPnkel8x8YT4= -github.com/getlantern/lantern-box v0.0.86/go.mod h1:BVXPyEicSu7m4nQY1OHPkOZNj87M7sYrzmY9AgyiPkc= +github.com/getlantern/lantern-box v0.0.87-0.20260529195337-0b63c0f42962 h1:VSSC7BIn42+tQmhoYg7Wc+ilkXC4SdoJ0LQ6+4kvtC0= +github.com/getlantern/lantern-box v0.0.87-0.20260529195337-0b63c0f42962/go.mod h1:BVXPyEicSu7m4nQY1OHPkOZNj87M7sYrzmY9AgyiPkc= github.com/getlantern/lantern-server-provisioner v0.0.0-20251031121934-8ea031fccfa9 h1:6seyD2f9tz2am0YQd/Qn+q7LFiiQgnmxgwWFnVceGZw= github.com/getlantern/lantern-server-provisioner v0.0.0-20251031121934-8ea031fccfa9/go.mod h1:s0VKrlJf/z+M0U8IKHFL2hfuflocRw3SINmMacrTlMA= github.com/getlantern/lantern-water v0.0.0-20260520145825-958775d51395 h1:grfGavAUp2E9w9ZoJuM3FyWyQ0sCJ64V4ZMKtZKRqTc= @@ -261,8 +261,8 @@ github.com/getlantern/pluriconfig v0.0.0-20251126214241-8cc8bc561535 h1:rtDmW8YL github.com/getlantern/pluriconfig v0.0.0-20251126214241-8cc8bc561535/go.mod h1:WKJEdjMOD4IuTRYwjQHjT4bmqDl5J82RShMLxPAvi0Q= github.com/getlantern/publicip v0.0.0-20260328175246-2c460fe80c6b h1:gMYJzEhLrmIqQ+JnjiYNm+UyUDalK3WUmVyecFwmV5g= github.com/getlantern/publicip v0.0.0-20260328175246-2c460fe80c6b/go.mod h1:NpfXdK4ldEKkjQ4P1R+DBF4ua5VFOlxmgHROTnYrApg= -github.com/getlantern/radiance v0.0.0-20260529193818-b9df7d613b1a h1:BUnZxaaUJbDmILeIOj+/4qlUQrgD8hdV1+67fkLvdqY= -github.com/getlantern/radiance v0.0.0-20260529193818-b9df7d613b1a/go.mod h1:SOXeNVsbdP1v9yYRbuJLB9tV9DpPI77HN+iSecuC9kM= +github.com/getlantern/radiance v0.0.0-20260529195536-3684cef6632c h1:4IwmbsJ19H7uhgSCClT6+qpSiw3xkeoqwGjbkCa/NQg= +github.com/getlantern/radiance v0.0.0-20260529195536-3684cef6632c/go.mod h1:3ArruQc+mdm1NdTsLJkcpCftDDiZ9bWfmu6IDIPn+zM= github.com/getlantern/samizdat v0.0.3-0.20260529191731-5ea8ae61ddbf h1:KxiMF+oG0rTtuBi7GiIaHfccYOf69rLJ/VnO5myoYc4= github.com/getlantern/samizdat v0.0.3-0.20260529191731-5ea8ae61ddbf/go.mod h1:uEeykQSW2/6rTjfPlj3MTTo59poSHXfAHTGgzYDkbr0= github.com/getlantern/semconv v0.0.0-20260327040646-21845dda05cb h1:c5YM7b3a4r2J8Eh89KkI6M/iTFe6Bi+b8AJlfkKdFq4= From ac77f75b9ae92bc2e2117696417ddef8a2fcc612 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Sun, 31 May 2026 16:04:10 -0600 Subject: [PATCH 23/37] smc: address Copilot review (5 of 7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit share_my_connection.dart: - ShareState.copyWith now uses a sentinel default for errorMessage (Object? = _unsetErrorMessage) so callers can distinguish 'leave alone' from 'clear it'. The naive '?? this.errorMessage' pattern conflated the two and left stale error text wedged in state — the next phase transition into error would re-render the wrong message. - _smcAck (the SmC disclosure ack) now persists via LocalStorageService using a containsKey-based 'smc_disclosure_acked' key, so the disclosure modal doesn't re-fire on every app restart. - All new user-facing strings (~30) moved from hardcoded English into assets/locales/en.po and consumed via .i18n / .fill. Covers hero copy, status phase labels, status card stats, tooltip, arrival toast, Advanced section, manual port forward field, snackbar messages, and the disclosure dialog. Matches the established convention in vpn_setting.dart. lib/core/services/geo_lookup_service.dart: - Added a privacy note on peerLookup documenting the ipwho.is data flow: each call ships a peer's IP (typically a censored user's address) to a third-party geo-IP service. Documents the current rationale + the fix to do before any production-scale rollout (Lantern-controlled endpoint or local DB). The lookup itself stays; see PR reply for the design discussion. lib/features/home/provider/radiance_settings_providers.dart: - _refresh's fragile positional 'peerIdx' index into Future.wait results replaced with named-future await-per-variable. Adding another optional fetch later can't silently desync read indices. Performance unchanged: the futures are still started before any await, so they run concurrently; the awaits just collect them in order. lantern-core/core.go: - listenPeerConnectionEvents: unbounded.ConnectionEvent subscription was leaked (Subscribe but never Unsubscribe). Now captures the Subscription handle and unsubscribes on ctx.Done in a small companion goroutine. - Dropped the redundant inner 'go func()' that wrapped the SSE call. The caller already spawned the outer goroutine via 'go lc.listenPeerConnectionEvents()', so the inner go just exited the outer immediately and lost structured cancellation. The SSE call now blocks the outer goroutine directly. Co-Authored-By: Claude Opus 4.7 --- assets/locales/en.po | 114 ++++++++++++++++ lantern-core/core.go | 57 +++++--- lib/core/services/geo_lookup_service.dart | 13 ++ .../provider/radiance_settings_providers.dart | 31 ++--- .../share_my_connection.dart | 129 +++++++++--------- 5 files changed, 243 insertions(+), 101 deletions(-) diff --git a/assets/locales/en.po b/assets/locales/en.po index 303e466d36..2cbc270b7f 100644 --- a/assets/locales/en.po +++ b/assets/locales/en.po @@ -557,6 +557,120 @@ msgstr "Share My Connection" msgid "share_my_connection_subtitle" msgstr "Let other Lantern users route through your connection to bypass censorship." +# Share My Connection screen — body / hero copy +msgid "smc_intro" +msgstr "Help others bypass censorship by sharing a small portion of your home internet connection. While sharing is on, traffic from users in censored regions will egress through your IP." + +# Status card — phase labels +msgid "smc_status_label" +msgstr "Status" + +msgid "smc_status_off" +msgstr "Off" + +msgid "smc_status_probing" +msgstr "Probing your network…" + +msgid "smc_status_active_unbounded" +msgstr "Active — sharing via Unbounded (WebRTC)" + +msgid "smc_status_active_smc" +msgstr "Active — sharing via Share My Connection (residential proxy)" + +msgid "smc_status_mapping_port" +msgstr "Opening port on your router…" + +msgid "smc_status_detecting_ip" +msgstr "Detecting your public IP…" + +msgid "smc_status_registering" +msgstr "Registering with Lantern…" + +msgid "smc_status_starting_proxy" +msgstr "Starting local proxy…" + +msgid "smc_status_verifying" +msgstr "Verifying connectivity…" + +msgid "smc_status_serving" +msgstr "Sharing — ready to serve users in censored regions" + +msgid "smc_status_stopping" +msgstr "Stopping…" + +msgid "smc_status_error_with_message" +msgstr "Couldn't share: %s" + +msgid "smc_status_error_generic" +msgstr "Couldn't share — try toggling again" + +# Status card — stats + tooltip +msgid "smc_stat_active_now" +msgstr "Active now" + +msgid "smc_stat_total_today" +msgstr "Total today" + +msgid "smc_connections_tooltip" +msgstr "Most connections are short liveness probes — Lantern clients periodically check that this peer is reachable before sending real traffic. A quick burst from many locations is normal; an arc that lingers represents an actual user session." + +# Arrival toast — "New connection from {country}" +msgid "smc_arrival_toast" +msgstr "New connection from %s" + +# Advanced section / manual port forward +msgid "smc_advanced" +msgstr "Advanced" + +msgid "smc_advanced_subtitle" +msgstr "For users whose router doesn't support UPnP" + +msgid "smc_manual_port" +msgstr "Manual port forward" + +msgid "smc_manual_port_description" +msgstr "If your router doesn't support UPnP, configure a port forward on your router and enter the port number here. Lantern will use it as the external port instead of probing UPnP. Leave blank to use UPnP (default)." + +msgid "smc_manual_port_label" +msgstr "Port" + +msgid "smc_manual_port_hint" +msgstr "e.g. 5698" + +msgid "smc_manual_port_save" +msgstr "Save" + +msgid "smc_manual_port_currently_set" +msgstr "Currently set to port %d. Toggle Share My Connection off and back on for the change to take effect." + +msgid "smc_manual_port_out_of_range" +msgstr "Port must be between 1 and 65535" + +msgid "smc_manual_port_cleared" +msgstr "Manual port cleared — using UPnP" + +msgid "smc_manual_port_saved" +msgstr "Manual port set to %d" + +# SmC disclosure dialog (shown before opting into the residential-proxy mode) +msgid "smc_disclosure_title" +msgstr "Use full Share My Connection?" + +msgid "smc_disclosure_body_capability" +msgstr "Your network supports the higher-bandwidth, more block-resistant mode. In this mode, your home internet connection routes traffic for users in censored countries." + +msgid "smc_disclosure_body_safety" +msgstr "Lantern blocks abuse destinations, rotates credentials, and operates as a \"mere conduit\" under DMCA § 512(a) — but your IP address will appear in the destination's logs while you're sharing." + +msgid "smc_disclosure_body_alternative" +msgstr "You can still help by selecting \"Basic mode\" instead, which uses ephemeral WebRTC connections that are not tied to your IP in the same way." + +msgid "smc_disclosure_basic" +msgstr "Basic mode (Unbounded)" + +msgid "smc_disclosure_full" +msgstr "Full mode (SmC)" + msgid "vpn_connected" msgstr "Lantern is now connected." diff --git a/lantern-core/core.go b/lantern-core/core.go index 69dcff5575..7354436f85 100644 --- a/lantern-core/core.go +++ b/lantern-core/core.go @@ -404,34 +404,19 @@ func (lc *LanternCore) listenDataCapEvents() { // reconnects (broflake's WebRTC sessions are short and same-IP churn // is more common than for SmC's long-lived TCP). func (lc *LanternCore) listenPeerConnectionEvents() { - // peer.ConnectionEvent: subscribe via the IPC client's SSE stream. - // The events package's globals are process-scoped — events.Emit in - // lanternd (where radiance/peer runs) doesn't reach events.Subscribe - // in Liblantern. The /peer/connection/events SSE endpoint in - // radiance/ipc/server.go bridges the two processes. - go func() { - err := lc.client.PeerConnectionEvents(lc.ctx, func(evt peer.ConnectionEvent) { - jsonBytes, err := json.Marshal(map[string]any{ - "state": evt.State, - "source": evt.Source, - }) - if err != nil { - slog.Error("marshal peer connection event", "error", err) - return - } - lc.notifyFlutter(EventTypePeerConnection, string(jsonBytes)) - }) - if err != nil && lc.ctx.Err() == nil { - slog.Error("peer-connection event stream exited unexpectedly", "error", err) - } - }() // unbounded.ConnectionEvent stays on in-process events.Subscribe for // now. Unbounded runs in the same process as the consumer in mobile // builds (broflake-as-library); the desktop path doesn't yet have a // gomobile-bridged Unbounded peer, so the cross-process gap doesn't // hit here today. Worth revisiting if Unbounded ever moves out of // process. - events.Subscribe(func(evt unbounded.ConnectionEvent) { + // + // The subscription is tied to ctx via a separate goroutine that + // calls Unsubscribe on Done. Without this, a reinitialization of + // LanternCore over the process lifetime would leak handlers and + // events.Emit would fan out each connection event to N stale + // registrations. + unbSub := events.Subscribe(func(evt unbounded.ConnectionEvent) { jsonBytes, err := json.Marshal(map[string]any{ "state": evt.State, "source": evt.Addr, @@ -443,6 +428,34 @@ func (lc *LanternCore) listenPeerConnectionEvents() { } lc.notifyFlutter(EventTypePeerConnection, string(jsonBytes)) }) + go func() { + <-lc.ctx.Done() + unbSub.Unsubscribe() + }() + + // peer.ConnectionEvent: subscribe via the IPC client's SSE stream. + // The events package's globals are process-scoped — events.Emit in + // lanternd (where radiance/peer runs) doesn't reach events.Subscribe + // in Liblantern. The /peer/connection/events SSE endpoint bridges + // the two processes. PeerConnectionEvents blocks until ctx is done, + // so this goroutine (the caller spawned us with + // `go lc.listenPeerConnectionEvents()`) blocks on it directly — + // wrapping in another go would just exit the outer goroutine + // immediately. + err := lc.client.PeerConnectionEvents(lc.ctx, func(evt peer.ConnectionEvent) { + jsonBytes, err := json.Marshal(map[string]any{ + "state": evt.State, + "source": evt.Source, + }) + if err != nil { + slog.Error("marshal peer connection event", "error", err) + return + } + lc.notifyFlutter(EventTypePeerConnection, string(jsonBytes)) + }) + if err != nil && lc.ctx.Err() == nil { + slog.Error("peer-connection event stream exited unexpectedly", "error", err) + } } // listenPeerStatusEvents forwards peer.Client lifecycle phase changes to diff --git a/lib/core/services/geo_lookup_service.dart b/lib/core/services/geo_lookup_service.dart index 110aa38a8a..a064c72226 100644 --- a/lib/core/services/geo_lookup_service.dart +++ b/lib/core/services/geo_lookup_service.dart @@ -184,6 +184,19 @@ class GeoLookupService { /// Looks up country, flag, and coordinates for a peer [ip] address. /// Returns [PeerGeo.unknown] on any failure so callers can suppress the /// arc / banner rather than displaying a wrong country. + /// + /// Privacy note: each call ships [ip] — the address of a peer routing + /// through this user's Share My Connection inbound — to ipwho.is, a + /// third-party geo-IP service. For most SmC consumers these are + /// censored users' addresses, so this is an outbound data flow to a + /// non-Lantern endpoint. Current rationale for accepting it: the IP + /// is already public-by-virtue-of-being-a-TCP-source-addr the host + /// observes, ipwho.is doesn't tie lookups to the caller, and the + /// alternative — relaying through Lantern's own infrastructure or + /// shipping a MaxMind DB with the app — is meaningful additional + /// scope. Move this to a Lantern-controlled endpoint or a local DB + /// before any production-scale rollout where peer protection matters + /// beyond demo use. static Future peerLookup(String ip) async { try { final response = await http diff --git a/lib/features/home/provider/radiance_settings_providers.dart b/lib/features/home/provider/radiance_settings_providers.dart index be8900aede..5423893022 100644 --- a/lib/features/home/provider/radiance_settings_providers.dart +++ b/lib/features/home/provider/radiance_settings_providers.dart @@ -22,7 +22,9 @@ class RadianceSettings extends _$RadianceSettings { /// Fetches all settings in parallel and assigns a fresh state from the /// results. On a per-field fetch failure, falls back to the hardcoded - /// default for that field. + /// default for that field. Each future is awaited into a named variable + /// (positional-by-name, not positional-by-index) so adding another + /// optional fetch later can't silently desync the read indices. Future _refresh() async { final svc = ref.read(lanternServiceProvider); final blockAdsF = svc.isBlockAdsEnabled(); @@ -35,30 +37,27 @@ class RadianceSettings extends _$RadianceSettings { // every settings init. final peerF = PlatformUtils.isDesktop ? svc.isPeerProxyEnabled() : null; - final results = await Future.wait([ - blockAdsF, - routingF, - telemetryF, - ?splitF, - ?peerF, - ]); + final blockAds = await blockAdsF; + final routing = await routingF; + final telemetry = await telemetryF; + final split = splitF == null ? null : await splitF; + final peer = peerF == null ? null : await peerF; if (!ref.mounted) return; const defaults = RadianceSettingsState(); - final peerIdx = 3 + (splitF == null ? 0 : 1); state = RadianceSettingsState( - blockAds: results[0].fold((_) => defaults.blockAds, (v) => v), - routingMode: results[1].fold( + blockAds: blockAds.fold((_) => defaults.blockAds, (v) => v), + routingMode: routing.fold( (_) => defaults.routingMode, (smart) => smart ? RoutingMode.smart : RoutingMode.full, ), - telemetry: results[2].fold((_) => defaults.telemetry, (v) => v), - splitTunneling: splitF == null + telemetry: telemetry.fold((_) => defaults.telemetry, (v) => v), + splitTunneling: split == null ? defaults.splitTunneling - : results[3].fold((_) => defaults.splitTunneling, (v) => v), - peerProxy: peerF == null + : split.fold((_) => defaults.splitTunneling, (v) => v), + peerProxy: peer == null ? defaults.peerProxy - : results[peerIdx].fold((_) => defaults.peerProxy, (v) => v), + : peer.fold((_) => defaults.peerProxy, (v) => v), ); } diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index a4f3320274..4d61eda089 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -30,6 +30,8 @@ import 'package:lottie/lottie.dart'; import 'package:lantern/core/common/common.dart'; import 'package:lantern/core/models/unbounded_connection_event.dart'; import 'package:lantern/core/services/geo_lookup_service.dart'; +import 'package:lantern/core/services/injection_container.dart' show sl; +import 'package:lantern/core/services/local_storage_service.dart'; import 'package:lantern/core/widgets/switch_button.dart'; import 'package:lantern/features/home/provider/radiance_settings_providers.dart'; import 'package:lantern/lantern/lantern_service_notifier.dart'; @@ -110,7 +112,13 @@ class ShareState { int? activeCount, int? totalCount, SharePhase? phase, - String? errorMessage, + // Sentinel-defaulted so callers can distinguish "leave alone" (omit + // the argument) from "clear it" (pass null explicitly). The naive + // `String? errorMessage` + `?? this.errorMessage` pattern conflates + // the two and leaves stale error text wedged in state forever — the + // next time a phase transition lands in error, the wrong message + // would get re-rendered. + Object? errorMessage = _unsetErrorMessage, }) => ShareState( active: active ?? this.active, @@ -119,10 +127,21 @@ class ShareState { activeCount: activeCount ?? this.activeCount, totalCount: totalCount ?? this.totalCount, phase: phase ?? this.phase, - errorMessage: errorMessage ?? this.errorMessage, + errorMessage: identical(errorMessage, _unsetErrorMessage) + ? this.errorMessage + : errorMessage as String?, ); } +// Sentinel for ShareState.copyWith. Has to be a const value distinct +// from any String? a caller might supply (including null), hence the +// private class — `const Object()` instances can be canonicalized to +// the same identity as another bare Object literal. +class _UnsetErrorMessage { + const _UnsetErrorMessage(); +} +const _unsetErrorMessage = _UnsetErrorMessage(); + // ─── Notifier (mock-backed) ────────────────────────────────────────────────── class _PeerArc { @@ -135,9 +154,13 @@ class _PeerArc { } class ShareNotifier extends Notifier { - // Persisted in real impl; in-process for the prototype so the disclosure - // re-fires on app restart and is easy to demo. - bool _smcAck = false; + // Disclosure ack persists across launches via LocalStorageService + // (SharedPreferences). Key-presence is the signal — the value is + // arbitrary. Cleared by deleteAll() in the existing reset flow. + static const _smcAckKey = 'smc_disclosure_acked'; + LocalStorageService get _storage => sl(); + bool get _smcAck => _storage.containsKey(_smcAckKey); + Future _persistSmcAck() => _storage.setString(_smcAckKey, '1'); StreamSubscription? _appEventSub; int _workerSeq = 0; @@ -224,7 +247,7 @@ class ShareNotifier extends Notifier { return; } if (accepted) { - _smcAck = true; + await _persistSmcAck(); await _start(widgetRef, ShareMode.smc); } else { await _start(widgetRef, ShareMode.unbounded); @@ -475,16 +498,14 @@ class ShareMyConnectionScreen extends HookConsumerWidget { final textTheme = Theme.of(context).textTheme; return BaseScreen( - title: 'Share My Connection', + title: 'share_my_connection'.i18n, body: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( children: [ const SizedBox(height: 12), Text( - 'Help others bypass censorship by sharing a small portion of ' - 'your home internet connection. While sharing is on, traffic ' - 'from users in censored regions will egress through your IP.', + 'smc_intro'.i18n, style: textTheme.bodyMedium, ), const SizedBox(height: 16), @@ -540,31 +561,29 @@ class _StatusCard extends StatelessWidget { // staged lifecycle on the broflake side yet). final modeLabel = switch ((state.mode, state.phase)) { (ShareMode.off, _) => - state.probing ? 'Probing your network…' : 'Off', - (ShareMode.unbounded, _) => - 'Active — sharing via Unbounded (WebRTC)', + state.probing ? 'smc_status_probing'.i18n : 'smc_status_off'.i18n, + (ShareMode.unbounded, _) => 'smc_status_active_unbounded'.i18n, (ShareMode.smc, SharePhase.mappingPort) => - 'Opening port on your router…', + 'smc_status_mapping_port'.i18n, (ShareMode.smc, SharePhase.detectingIp) => - 'Detecting your public IP…', + 'smc_status_detecting_ip'.i18n, (ShareMode.smc, SharePhase.registering) => - 'Registering with Lantern…', + 'smc_status_registering'.i18n, (ShareMode.smc, SharePhase.startingProxy) => - 'Starting local proxy…', + 'smc_status_starting_proxy'.i18n, (ShareMode.smc, SharePhase.verifying) => - 'Verifying connectivity…', + 'smc_status_verifying'.i18n, (ShareMode.smc, SharePhase.serving) => - 'Sharing — ready to serve users in censored regions', - (ShareMode.smc, SharePhase.stopping) => 'Stopping…', + 'smc_status_serving'.i18n, + (ShareMode.smc, SharePhase.stopping) => 'smc_status_stopping'.i18n, (ShareMode.smc, SharePhase.error) => state.errorMessage != null - ? "Couldn't share: ${state.errorMessage}" - : "Couldn't share — try toggling again", + ? 'smc_status_error_with_message'.i18n.fill([state.errorMessage!]) + : 'smc_status_error_generic'.i18n, // SmC active but no phase yet (e.g. very first frame after toggle // before the backend's first event arrives) — fall back to the // legacy active label so the UI isn't blank. - (ShareMode.smc, SharePhase.idle) => - 'Active — sharing via Share My Connection (residential proxy)', + (ShareMode.smc, SharePhase.idle) => 'smc_status_active_smc'.i18n, }; return Container( @@ -584,7 +603,7 @@ class _StatusCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Status', + 'smc_status_label'.i18n, style: textTheme.labelLarge, ), const SizedBox(height: 4), @@ -622,8 +641,8 @@ class _StatusCard extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - _Stat(label: 'Active now', value: '${state.activeCount}'), - _Stat(label: 'Total today', value: '${state.totalCount}'), + _Stat(label: 'smc_stat_active_now'.i18n, value: '${state.activeCount}'), + _Stat(label: 'smc_stat_total_today'.i18n, value: '${state.totalCount}'), ], ), Positioned( @@ -642,12 +661,7 @@ class _StatusCard extends StatelessWidget { color: Colors.black87, borderRadius: BorderRadius.circular(8), ), - message: - 'Most connections are short liveness probes — Lantern ' - 'clients periodically check that this peer is reachable ' - 'before sending real traffic. A quick burst from many ' - 'locations is normal; an arc that lingers represents an ' - 'actual user session.', + message: 'smc_connections_tooltip'.i18n, child: Icon( Icons.info_outline, size: 16, @@ -973,8 +987,8 @@ class _ArrivalCard extends StatelessWidget { const SizedBox(width: 12), Text( flagEmoji.isEmpty - ? 'New connection from $countryName' - : '$flagEmoji New connection from $countryName', + ? 'smc_arrival_toast'.i18n.fill([countryName]) + : '$flagEmoji ${'smc_arrival_toast'.i18n.fill([countryName])}', style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w500, @@ -1109,9 +1123,9 @@ class _AdvancedCard extends HookConsumerWidget { child: ExpansionTile( tilePadding: const EdgeInsets.symmetric(horizontal: 16), childrenPadding: const EdgeInsets.fromLTRB(16, 0, 16, 16), - title: Text('Advanced', style: textTheme.labelLarge), + title: Text('smc_advanced'.i18n, style: textTheme.labelLarge), subtitle: Text( - 'For users whose router doesn\'t support UPnP', + 'smc_advanced_subtitle'.i18n, style: textTheme.labelSmall, ), children: const [_ManualPortField()], @@ -1153,15 +1167,12 @@ class _ManualPortField extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Manual port forward', + 'smc_manual_port'.i18n, style: textTheme.labelLarge, ), const SizedBox(height: 4), Text( - 'If your router doesn\'t support UPnP, configure a port forward ' - 'on your router and enter the port number here. Lantern will use ' - 'it as the external port instead of probing UPnP. Leave blank to ' - 'use UPnP (default).', + 'smc_manual_port_description'.i18n, style: textTheme.bodySmall, ), const SizedBox(height: 12), @@ -1175,8 +1186,8 @@ class _ManualPortField extends HookConsumerWidget { signed: false, ), decoration: InputDecoration( - labelText: 'Port', - hintText: 'e.g. 5698', + labelText: 'smc_manual_port_label'.i18n, + hintText: 'smc_manual_port_hint'.i18n, border: const OutlineInputBorder(), isDense: true, enabled: loaded.value && !saving.value, @@ -1193,15 +1204,14 @@ class _ManualPortField extends HookConsumerWidget { height: 16, width: 16, child: CircularProgressIndicator(strokeWidth: 2), ) - : const Text('Save'), + : Text('smc_manual_port_save'.i18n), ), ], ), if (lastSaved.value != null && lastSaved.value! > 0) ...[ const SizedBox(height: 8), Text( - 'Currently set to port ${lastSaved.value}. Toggle Share My ' - 'Connection off and back on for the change to take effect.', + 'smc_manual_port_currently_set'.i18n.fill([lastSaved.value!]), style: textTheme.bodySmall?.copyWith( color: Theme.of(context).hintColor, ), @@ -1227,7 +1237,7 @@ class _ManualPortField extends HookConsumerWidget { if (port < 1 || port > 65535) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Port must be between 1 and 65535')), + SnackBar(content: Text('smc_manual_port_out_of_range'.i18n)), ); } return; @@ -1249,8 +1259,8 @@ class _ManualPortField extends HookConsumerWidget { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(port == 0 - ? 'Manual port cleared — using UPnP' - : 'Manual port set to $port'), + ? 'smc_manual_port_cleared'.i18n + : 'smc_manual_port_saved'.i18n.fill([port])), ), ); } @@ -1271,31 +1281,24 @@ class SmcDisclosureDialog extends StatelessWidget { Widget build(BuildContext context) { final textTheme = Theme.of(context).textTheme; return AlertDialog( - title: const Text('Use full Share My Connection?'), + title: Text('smc_disclosure_title'.i18n), content: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( - 'Your network supports the higher-bandwidth, more ' - 'block-resistant mode. In this mode, your home internet ' - 'connection routes traffic for users in censored countries.', + 'smc_disclosure_body_capability'.i18n, style: textTheme.bodyMedium, ), const SizedBox(height: 12), Text( - 'Lantern blocks abuse destinations, rotates credentials, ' - 'and operates as a "mere conduit" under DMCA § 512(a) — ' - 'but your IP address will appear in the destination\'s ' - 'logs while you\'re sharing.', + 'smc_disclosure_body_safety'.i18n, style: textTheme.bodyMedium, ), const SizedBox(height: 12), Text( - 'You can still help by selecting "Basic mode" instead, ' - 'which uses ephemeral WebRTC connections that are not ' - 'tied to your IP in the same way.', + 'smc_disclosure_body_alternative'.i18n, style: textTheme.bodyMedium?.copyWith( color: Theme.of(context).hintColor, ), @@ -1306,11 +1309,11 @@ class SmcDisclosureDialog extends StatelessWidget { actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), - child: const Text('Basic mode (Unbounded)'), + child: Text('smc_disclosure_basic'.i18n), ), FilledButton( onPressed: () => Navigator.of(context).pop(true), - child: const Text('Full mode (SmC)'), + child: Text('smc_disclosure_full'.i18n), ), ], ); From 281d5b724f5c50427d8be5c66aa6d1643d0ec1df Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Sun, 31 May 2026 16:23:38 -0600 Subject: [PATCH 24/37] smc: wire real UPnP / IGD probe via FFI + MethodChannel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the Random().nextBool() mock that was gating Full SmC vs Unbounded mode on toggle. Half of opted-in users were getting routed to a mode that didn't match their network capabilities. End-to-end: - radiance/portforward.ProbeUPnP(ctx) (added in radiance commit 79400ef): wraps NewForwarder to do the M-SEARCH discovery, returns bool, no port actually mapped. - lantern-core/core.go: LanternCore.ProbeUPnP() bool wrapping portforward.ProbeUPnP with a 6s internal timeout. Added to the PeerShare interface so callers and stubs stay in sync. - lantern-core/ffi/ffi.go: //export probeUPnP returning C.int (0/1). Documents the 6s upper bound and the requirement that Dart callers invoke from a background isolate. - lantern-core/mobile/mobile.go: ProbeUPnP() bool for the iOS / Android MethodChannel handler — platforms whose Flutter side can't reach the FFI directly. - lantern_generated_bindings.dart: regenerated via make ffigen. All existing SmC stack exports (setPeerProxy / setPeerManualPort / setUnboundedEnabled / etc.) still present; probeUPnP added. - Dart service layer (core_service / service / ffi_service / platform_service): added probeUPnP() Future>. FFI implementation runs the synchronous C call inside runInBackground so the 6s wait doesn't pin the UI isolate. Platform implementation hops to the MethodChannel platform thread. - share_my_connection.dart: replaced the Random / 1.5s-sleep mock with svc.probeUPnP(). Any probe error degrades to 'UPnP unavailable' → Unbounded fallback, matching the user-visible contract from the mock. Dropped the now-unused dart:math import (showed up as max/min usage elsewhere, switched to a 'show' filter). Updated the file-level docstring to reflect the wired probe. - go.mod: bumped radiance to the commit with ProbeUPnP. - lantern-core/core.go: drive-by fix for ConnectionEvent shape drift (the radiance unbounded.ConnectionEvent JSON contract finalized to {state, source, timestamp}; the lantern side was still writing the old {addr, workerIdx} field names). dart analyze clean on touched files; CGO_ENABLED=1 go build ./lantern-core/... clean. Co-Authored-By: Claude Opus 4.7 --- go.mod | 2 +- go.sum | 4 +- lantern-core/core.go | 29 +- lantern-core/ffi/ffi.go | 24 + lantern-core/mobile/mobile.go | 14 + .../share_my_connection.dart | 27 +- lib/lantern/lantern_core_service.dart | 9 + lib/lantern/lantern_ffi_service.dart | 17 + lib/lantern/lantern_generated_bindings.dart | 10205 ++++++++-------- lib/lantern/lantern_platform_service.dart | 15 + lib/lantern/lantern_service.dart | 8 + 11 files changed, 4987 insertions(+), 5367 deletions(-) diff --git a/go.mod b/go.mod index ab99956b5e..570d554f7e 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ replace github.com/quic-go/qpack => github.com/quic-go/qpack v0.5.1 require ( github.com/alecthomas/assert/v2 v2.3.0 github.com/getlantern/lantern-server-provisioner v0.0.0-20251031121934-8ea031fccfa9 - github.com/getlantern/radiance v0.0.0-20260529195536-3684cef6632c + github.com/getlantern/radiance v0.0.0-20260531221356-11aa55a6ff16 github.com/sagernet/sing-box v1.12.22 golang.org/x/mobile v0.0.0-20250711185624-d5bb5ecc55c0 golang.org/x/sys v0.41.0 diff --git a/go.sum b/go.sum index b7035ee886..528a7c3e57 100644 --- a/go.sum +++ b/go.sum @@ -261,8 +261,8 @@ github.com/getlantern/pluriconfig v0.0.0-20251126214241-8cc8bc561535 h1:rtDmW8YL github.com/getlantern/pluriconfig v0.0.0-20251126214241-8cc8bc561535/go.mod h1:WKJEdjMOD4IuTRYwjQHjT4bmqDl5J82RShMLxPAvi0Q= github.com/getlantern/publicip v0.0.0-20260328175246-2c460fe80c6b h1:gMYJzEhLrmIqQ+JnjiYNm+UyUDalK3WUmVyecFwmV5g= github.com/getlantern/publicip v0.0.0-20260328175246-2c460fe80c6b/go.mod h1:NpfXdK4ldEKkjQ4P1R+DBF4ua5VFOlxmgHROTnYrApg= -github.com/getlantern/radiance v0.0.0-20260529195536-3684cef6632c h1:4IwmbsJ19H7uhgSCClT6+qpSiw3xkeoqwGjbkCa/NQg= -github.com/getlantern/radiance v0.0.0-20260529195536-3684cef6632c/go.mod h1:3ArruQc+mdm1NdTsLJkcpCftDDiZ9bWfmu6IDIPn+zM= +github.com/getlantern/radiance v0.0.0-20260531221356-11aa55a6ff16 h1:CpsYjT3sBimvg/GNYO5IKvRjWDc4BCeDjDQxUNsx8gA= +github.com/getlantern/radiance v0.0.0-20260531221356-11aa55a6ff16/go.mod h1:wemClXaug4hwPdsUEm8g1bCa8tkjk3UjDM+6PfWJwMI= github.com/getlantern/samizdat v0.0.3-0.20260529191731-5ea8ae61ddbf h1:KxiMF+oG0rTtuBi7GiIaHfccYOf69rLJ/VnO5myoYc4= github.com/getlantern/samizdat v0.0.3-0.20260529191731-5ea8ae61ddbf/go.mod h1:uEeykQSW2/6rTjfPlj3MTTo59poSHXfAHTGgzYDkbr0= github.com/getlantern/semconv v0.0.0-20260327040646-21845dda05cb h1:c5YM7b3a4r2J8Eh89KkI6M/iTFe6Bi+b8AJlfkKdFq4= diff --git a/lantern-core/core.go b/lantern-core/core.go index 7354436f85..b1f7191c9b 100644 --- a/lantern-core/core.go +++ b/lantern-core/core.go @@ -22,6 +22,7 @@ import ( "github.com/getlantern/radiance/ipc" "github.com/getlantern/radiance/issue" "github.com/getlantern/radiance/peer" + "github.com/getlantern/radiance/portforward" "github.com/getlantern/radiance/servers" "github.com/getlantern/radiance/unbounded" "github.com/getlantern/radiance/vpn" @@ -183,6 +184,12 @@ type PeerShare interface { // UnboundedConfig — see radiance/unbounded.shouldRunUnbounded. SetUnboundedEnabled(bool) error IsUnboundedEnabled() bool + // ProbeUPnP runs IGD discovery on the local network and reports + // whether a usable gateway is reachable. Called by the SmC toggle + // path to decide between SmC mode (residential-proxy, needs UPnP + // or a manual port forward) and Unbounded mode (WebRTC, works + // anywhere) when no manual port is configured. + ProbeUPnP() bool } type VPN interface { @@ -419,8 +426,8 @@ func (lc *LanternCore) listenPeerConnectionEvents() { unbSub := events.Subscribe(func(evt unbounded.ConnectionEvent) { jsonBytes, err := json.Marshal(map[string]any{ "state": evt.State, - "source": evt.Addr, - "workerIdx": evt.WorkerIdx, + "source": evt.Source, + "timestamp": evt.Timestamp, }) if err != nil { slog.Error("marshal unbounded connection event", "error", err) @@ -618,6 +625,24 @@ func (lc *LanternCore) GetPeerManualPort() int { return 0 } +// ProbeUPnP reports whether the local network has a UPnP / IGD gateway +// that could host a port mapping. The Share My Connection UI flow +// uses this to decide between SmC mode (requires a routable inbound) +// and Unbounded mode (works on any network). The result is a binary +// "available / not available" — distinguishing "no gateway" from +// "discovery timed out" doesn't change anything productive in the UI. +// +// Bounded by an internal 6-second timeout because UPnP M-SEARCH is +// multicast-and-wait: a too-aggressive deadline misses slower +// consumer gateways, while a too-loose one stalls the UI's mode +// decision. The Dart-side FFI wrapper runs this on a background +// isolate so the wait doesn't block the main thread. +func (lc *LanternCore) ProbeUPnP() bool { + ctx, cancel := context.WithTimeout(lc.ctx, 6*time.Second) + defer cancel() + return portforward.ProbeUPnP(ctx) +} + func (lc *LanternCore) SetUnboundedEnabled(enabled bool) error { _, err := lc.client.PatchSettings(lc.ctx, settings.Settings{settings.UnboundedKey: enabled}) return err diff --git a/lantern-core/ffi/ffi.go b/lantern-core/ffi/ffi.go index 8bf32d90d9..7c4652ad07 100644 --- a/lantern-core/ffi/ffi.go +++ b/lantern-core/ffi/ffi.go @@ -1402,6 +1402,30 @@ func getPeerManualPort() C.int { return C.int(c.GetPeerManualPort()) } +// probeUPnP runs the UPnP / IGD discovery scan against the local +// gateway and returns 1 when discovery succeeds, 0 when it doesn't +// (no gateway, scan timeout, ctx cancellation). The Share My +// Connection UI flow calls this to decide between SmC mode (needs +// a routable inbound) and Unbounded mode (works anywhere) when the +// user toggles sharing on without a manual port configured. +// +// Blocks for up to ~6 seconds (the internal LanternCore deadline) +// on the multicast M-SEARCH wait. Dart callers MUST invoke this +// from a background isolate via runInBackground so the wait +// doesn't pin the main thread. +// +//export probeUPnP +func probeUPnP() C.int { + c, _ := requireCore() + if c == nil { + return 0 + } + if c.ProbeUPnP() { + return 1 + } + return 0 +} + // setUnboundedEnabled is the local opt-in for the broflake / Unbounded // widget proxy ("Basic mode" in the SmC UI). The widget actually runs // only when this is true AND the server-side Features[unbounded] flag diff --git a/lantern-core/mobile/mobile.go b/lantern-core/mobile/mobile.go index 328f2a35f4..c01e469556 100644 --- a/lantern-core/mobile/mobile.go +++ b/lantern-core/mobile/mobile.go @@ -271,6 +271,20 @@ func IsUnboundedEnabled() bool { return ok } +// ProbeUPnP runs IGD discovery and reports whether a usable gateway +// is reachable. Surfaced through the MethodChannel so the SmC mode +// gate works on platforms whose Flutter side can't reach the FFI +// directly (mobile network-extension hosts). +func ProbeUPnP() bool { + ok, err := withCoreR(func(c lanterncore.Core) (bool, error) { + return c.ProbeUPnP(), nil + }) + if err != nil { + return false + } + return ok +} + func IsSmartRoutingEnabled() bool { ok, err := withCoreR(func(c lanterncore.Core) (bool, error) { return c.IsSmartRoutingEnabled(), nil diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index 4d61eda089..2d20a9fc9d 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -1,21 +1,17 @@ // Share My Connection — unified screen for both Unbounded and the // samizdat-over-UPnP "Share My Connection" modes: -// - Toggle ON triggers a (mocked) UPnP probe. +// - Toggle ON triggers a real UPnP / IGD probe via the lantern +// service (FFI on desktop, MethodChannel on mobile). // - If UPnP works AND the user accepts the SmC disclosure, run SmC mode // (calls into radiance via the existing radianceSettingsProvider // setPeerProxy path). -// - Otherwise fall back to Unbounded mode (UI-only for now; broflake -// wire-up follows once radiance#336 lands). +// - Otherwise fall back to Unbounded mode. // - Globe animates connection arcs from peer-connection FlutterEvents // streamed up from radiance. -// -// UPnP probe is still mocked (a coin-flip) until the FFI binding lands; -// SmC mode is real — flipping the toggle starts the radiance peer -// module on this branch. import 'dart:async'; import 'dart:convert'; -import 'dart:math'; +import 'dart:math' show max, min; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -215,11 +211,16 @@ class ShareNotifier extends Notifier { return; } - // MOCK: real UPnP probe via FFI is not yet wired; coin-flip the - // result so the demo exercises both paths across runs without a - // manual port set. - await Future.delayed(const Duration(milliseconds: 1500)); - final upnpAvailable = Random().nextBool(); + // Real UPnP probe via FFI / MethodChannel. probeUPnP runs IGD + // discovery on the local network and returns true when a usable + // gateway is reachable. Blocks up to ~6 seconds on the M-SEARCH + // multicast wait — long enough that the "Probing your network…" + // status from copyWith(probing: true) above is visible to the + // user. Any failure (no IGD, timeout, FFI / channel error) is + // treated as "UPnP unavailable" → fall back to Unbounded. + final probeRes = + await widgetRef.read(lanternServiceProvider).probeUPnP(); + final upnpAvailable = probeRes.fold((_) => false, (v) => v); if (!upnpAvailable) { await _start(widgetRef, ShareMode.unbounded); return; diff --git a/lib/lantern/lantern_core_service.dart b/lib/lantern/lantern_core_service.dart index bc7c3abf99..162a5cdde9 100644 --- a/lib/lantern/lantern_core_service.dart +++ b/lib/lantern/lantern_core_service.dart @@ -98,6 +98,15 @@ abstract class LanternCoreService { Future> isUnboundedEnabled(); + /// Runs UPnP / IGD discovery on the local network and reports + /// whether a usable gateway is reachable. Used by the Share My + /// Connection toggle path to decide between SmC mode (residential + /// proxy, needs UPnP or a manual port forward) and Unbounded mode + /// (WebRTC, works anywhere) when no manual port is configured. + /// Blocks for up to ~6 seconds on the multicast M-SEARCH wait; + /// the FFI implementation runs in a background isolate. + Future> probeUPnP(); + Future> isSmartRoutingEnabled(); Future> isTelemetryEnabled(); diff --git a/lib/lantern/lantern_ffi_service.dart b/lib/lantern/lantern_ffi_service.dart index d59c892c47..689af9f7da 100644 --- a/lib/lantern/lantern_ffi_service.dart +++ b/lib/lantern/lantern_ffi_service.dart @@ -1634,6 +1634,23 @@ class LanternFFIService implements LanternCoreService { } } + @override + Future> probeUPnP() async { + try { + // UPnP M-SEARCH waits for multicast replies (~5-6s upper bound + // in the LanternCore-side timeout). Off-main-thread mandatory; + // a direct call from the UI isolate stalls every frame for the + // duration of the wait. + final res = await runInBackground(() async { + return _ffiService.probeUPnP(); + }); + return right(res != 0); + } catch (e, st) { + appLogger.error('probeUPnP error: $e', e, st); + return Left(e.toFailure()); + } + } + @override Future> isSmartRoutingEnabled() async { try { diff --git a/lib/lantern/lantern_generated_bindings.dart b/lib/lantern/lantern_generated_bindings.dart index 89265acdcc..1054e22529 100644 --- a/lib/lantern/lantern_generated_bindings.dart +++ b/lib/lantern/lantern_generated_bindings.dart @@ -18,162 +18,6 @@ class LanternBindings { ffi.Pointer Function(String symbolName) lookup, ) : _lookup = lookup; - void __va_start(ffi.Pointer arg0) { - return ___va_start(arg0); - } - - late final ___va_startPtr = - _lookup)>>( - '__va_start', - ); - late final ___va_start = ___va_startPtr - .asFunction)>(); - - void __security_init_cookie() { - return ___security_init_cookie(); - } - - late final ___security_init_cookiePtr = - _lookup>( - '__security_init_cookie', - ); - late final ___security_init_cookie = ___security_init_cookiePtr - .asFunction(); - - void __security_check_cookie(int _StackCookie) { - return ___security_check_cookie(_StackCookie); - } - - late final ___security_check_cookiePtr = - _lookup>( - '__security_check_cookie', - ); - late final ___security_check_cookie = ___security_check_cookiePtr - .asFunction(); - - void __report_gsfailure(int _StackCookie) { - return ___report_gsfailure(_StackCookie); - } - - late final ___report_gsfailurePtr = - _lookup>( - '__report_gsfailure', - ); - late final ___report_gsfailure = ___report_gsfailurePtr - .asFunction(); - - late final ffi.Pointer ___security_cookie = _lookup( - '__security_cookie', - ); - - int get __security_cookie => ___security_cookie.value; - - set __security_cookie(int value) => ___security_cookie.value = value; - - void _invalid_parameter_noinfo() { - return __invalid_parameter_noinfo(); - } - - late final __invalid_parameter_noinfoPtr = - _lookup>( - '_invalid_parameter_noinfo', - ); - late final __invalid_parameter_noinfo = __invalid_parameter_noinfoPtr - .asFunction(); - - void _invalid_parameter_noinfo_noreturn() { - return __invalid_parameter_noinfo_noreturn(); - } - - late final __invalid_parameter_noinfo_noreturnPtr = - _lookup>( - '_invalid_parameter_noinfo_noreturn', - ); - late final __invalid_parameter_noinfo_noreturn = - __invalid_parameter_noinfo_noreturnPtr.asFunction(); - - void _invoke_watson( - ffi.Pointer _Expression, - ffi.Pointer _FunctionName, - ffi.Pointer _FileName, - int _LineNo, - int _Reserved, - ) { - return __invoke_watson( - _Expression, - _FunctionName, - _FileName, - _LineNo, - _Reserved, - ); - } - - late final __invoke_watsonPtr = - _lookup< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.UnsignedInt, - ffi.UintPtr, - ) - > - >('_invoke_watson'); - late final __invoke_watson = __invoke_watsonPtr - .asFunction< - void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - int, - int, - ) - >(); - - ffi.Pointer _errno() { - return __errno(); - } - - late final __errnoPtr = - _lookup Function()>>('_errno'); - late final __errno = __errnoPtr.asFunction Function()>(); - - int _set_errno(int _Value) { - return __set_errno(_Value); - } - - late final __set_errnoPtr = - _lookup>('_set_errno'); - late final __set_errno = __set_errnoPtr.asFunction(); - - int _get_errno(ffi.Pointer _Value) { - return __get_errno(_Value); - } - - late final __get_errnoPtr = - _lookup)>>( - '_get_errno', - ); - late final __get_errno = __get_errnoPtr - .asFunction)>(); - - int __threadid() { - return ___threadid(); - } - - late final ___threadidPtr = - _lookup>('__threadid'); - late final ___threadid = ___threadidPtr.asFunction(); - - int __threadhandle() { - return ___threadhandle(); - } - - late final ___threadhandlePtr = - _lookup>('__threadhandle'); - late final ___threadhandle = ___threadhandlePtr.asFunction(); - int _GoStringLen(_GoString_ s) { return __GoStringLen(s); } @@ -196,6782 +40,6197 @@ class LanternBindings { late final __GoStringPtr = __GoStringPtrPtr .asFunction Function(_GoString_)>(); - ffi.Pointer _calloc_base(int _Count, int _Size) { - return __calloc_base(_Count, _Size); + ffi.Pointer> signal( + int arg0, + ffi.Pointer> arg1, + ) { + return _signal(arg0, arg1); } - late final __calloc_basePtr = + late final _signalPtr = _lookup< - ffi.NativeFunction Function(ffi.Size, ffi.Size)> - >('_calloc_base'); - late final __calloc_base = __calloc_basePtr - .asFunction Function(int, int)>(); + ffi.NativeFunction< + ffi.Pointer> Function( + ffi.Int, + ffi.Pointer>, + ) + > + >('signal'); + late final _signal = _signalPtr + .asFunction< + ffi.Pointer> Function( + int, + ffi.Pointer>, + ) + >(); - ffi.Pointer calloc(int _Count, int _Size) { - return _calloc(_Count, _Size); + int getpriority(int arg0, int arg1) { + return _getpriority(arg0, arg1); } - late final _callocPtr = - _lookup< - ffi.NativeFunction Function(ffi.Size, ffi.Size)> - >('calloc'); - late final _calloc = _callocPtr - .asFunction Function(int, int)>(); + late final _getpriorityPtr = + _lookup>( + 'getpriority', + ); + late final _getpriority = _getpriorityPtr + .asFunction(); - int _callnewh(int _Size) { - return __callnewh(_Size); + int getiopolicy_np(int arg0, int arg1) { + return _getiopolicy_np(arg0, arg1); } - late final __callnewhPtr = - _lookup>('_callnewh'); - late final __callnewh = __callnewhPtr.asFunction(); + late final _getiopolicy_npPtr = + _lookup>( + 'getiopolicy_np', + ); + late final _getiopolicy_np = _getiopolicy_npPtr + .asFunction(); - ffi.Pointer _expand(ffi.Pointer _Block, int _Size) { - return __expand(_Block, _Size); + int getrlimit(int arg0, ffi.Pointer arg1) { + return _getrlimit(arg0, arg1); } - late final __expandPtr = + late final _getrlimitPtr = _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer, ffi.Size) - > - >('_expand'); - late final __expand = __expandPtr - .asFunction Function(ffi.Pointer, int)>(); - - void _free_base(ffi.Pointer _Block) { - return __free_base(_Block); - } - - late final __free_basePtr = - _lookup)>>( - '_free_base', - ); - late final __free_base = __free_basePtr - .asFunction)>(); + ffi.NativeFunction)> + >('getrlimit'); + late final _getrlimit = _getrlimitPtr + .asFunction)>(); - void free(ffi.Pointer _Block) { - return _free(_Block); + int getrusage(int arg0, ffi.Pointer arg1) { + return _getrusage(arg0, arg1); } - late final _freePtr = - _lookup)>>( - 'free', - ); - late final _free = _freePtr - .asFunction)>(); + late final _getrusagePtr = + _lookup< + ffi.NativeFunction)> + >('getrusage'); + late final _getrusage = _getrusagePtr + .asFunction)>(); - ffi.Pointer _malloc_base(int _Size) { - return __malloc_base(_Size); + int setpriority(int arg0, int arg1, int arg2) { + return _setpriority(arg0, arg1, arg2); } - late final __malloc_basePtr = - _lookup Function(ffi.Size)>>( - '_malloc_base', + late final _setpriorityPtr = + _lookup>( + 'setpriority', ); - late final __malloc_base = __malloc_basePtr - .asFunction Function(int)>(); + late final _setpriority = _setpriorityPtr + .asFunction(); - ffi.Pointer malloc(int _Size) { - return _malloc(_Size); + int setiopolicy_np(int arg0, int arg1, int arg2) { + return _setiopolicy_np(arg0, arg1, arg2); } - late final _mallocPtr = - _lookup Function(ffi.Size)>>( - 'malloc', + late final _setiopolicy_npPtr = + _lookup>( + 'setiopolicy_np', ); - late final _malloc = _mallocPtr - .asFunction Function(int)>(); + late final _setiopolicy_np = _setiopolicy_npPtr + .asFunction(); - int _msize_base(ffi.Pointer _Block) { - return __msize_base(_Block); + int setrlimit(int arg0, ffi.Pointer arg1) { + return _setrlimit(arg0, arg1); } - late final __msize_basePtr = - _lookup)>>( - '_msize_base', - ); - late final __msize_base = __msize_basePtr - .asFunction)>(); + late final _setrlimitPtr = + _lookup< + ffi.NativeFunction)> + >('setrlimit'); + late final _setrlimit = _setrlimitPtr + .asFunction)>(); - int _msize(ffi.Pointer _Block) { - return __msize(_Block); + int wait(ffi.Pointer arg0) { + return _wait(arg0); } - late final __msizePtr = - _lookup)>>( - '_msize', - ); - late final __msize = __msizePtr - .asFunction)>(); + late final _waitPtr = + _lookup)>>('wait'); + late final _wait = _waitPtr.asFunction)>(); - ffi.Pointer _realloc_base(ffi.Pointer _Block, int _Size) { - return __realloc_base(_Block, _Size); + int waitpid(int arg0, ffi.Pointer arg1, int arg2) { + return _waitpid(arg0, arg1, arg2); } - late final __realloc_basePtr = + late final _waitpidPtr = _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer, ffi.Size) - > - >('_realloc_base'); - late final __realloc_base = __realloc_basePtr - .asFunction Function(ffi.Pointer, int)>(); + ffi.NativeFunction, ffi.Int)> + >('waitpid'); + late final _waitpid = _waitpidPtr + .asFunction, int)>(); - ffi.Pointer realloc(ffi.Pointer _Block, int _Size) { - return _realloc(_Block, _Size); + int waitid( + idtype_t arg0, + Dart__uint32_t arg1, + ffi.Pointer arg2, + int arg3, + ) { + return _waitid(arg0.value, arg1, arg2, arg3); } - late final _reallocPtr = + late final _waitidPtr = _lookup< ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer, ffi.Size) + ffi.Int Function( + ffi.UnsignedInt, + id_t, + ffi.Pointer, + ffi.Int, + ) > - >('realloc'); - late final _realloc = _reallocPtr - .asFunction Function(ffi.Pointer, int)>(); + >('waitid'); + late final _waitid = _waitidPtr + .asFunction, int)>(); - ffi.Pointer _recalloc_base( - ffi.Pointer _Block, - int _Count, - int _Size, - ) { - return __recalloc_base(_Block, _Count, _Size); + int wait3(ffi.Pointer arg0, int arg1, ffi.Pointer arg2) { + return _wait3(arg0, arg1, arg2); } - late final __recalloc_basePtr = + late final _wait3Ptr = _lookup< ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Size, - ffi.Size, - ) + pid_t Function(ffi.Pointer, ffi.Int, ffi.Pointer) > - >('_recalloc_base'); - late final __recalloc_base = __recalloc_basePtr + >('wait3'); + late final _wait3 = _wait3Ptr .asFunction< - ffi.Pointer Function(ffi.Pointer, int, int) + int Function(ffi.Pointer, int, ffi.Pointer) >(); - ffi.Pointer _recalloc( - ffi.Pointer _Block, - int _Count, - int _Size, + int wait4( + int arg0, + ffi.Pointer arg1, + int arg2, + ffi.Pointer arg3, ) { - return __recalloc(_Block, _Count, _Size); + return _wait4(arg0, arg1, arg2, arg3); } - late final __recallocPtr = + late final _wait4Ptr = _lookup< ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Size, - ffi.Size, + pid_t Function( + pid_t, + ffi.Pointer, + ffi.Int, + ffi.Pointer, ) > - >('_recalloc'); - late final __recalloc = __recallocPtr + >('wait4'); + late final _wait4 = _wait4Ptr .asFunction< - ffi.Pointer Function(ffi.Pointer, int, int) + int Function(int, ffi.Pointer, int, ffi.Pointer) >(); - void _aligned_free(ffi.Pointer _Block) { - return __aligned_free(_Block); + ffi.Pointer alloca(int __size) { + return _alloca(__size); } - late final __aligned_freePtr = - _lookup)>>( - '_aligned_free', + late final _allocaPtr = + _lookup Function(ffi.Size)>>( + 'alloca', ); - late final __aligned_free = __aligned_freePtr - .asFunction)>(); + late final _alloca = _allocaPtr + .asFunction Function(int)>(); + + late final ffi.Pointer ___mb_cur_max = _lookup( + '__mb_cur_max', + ); + + int get __mb_cur_max => ___mb_cur_max.value; - ffi.Pointer _aligned_malloc(int _Size, int _Alignment) { - return __aligned_malloc(_Size, _Alignment); + set __mb_cur_max(int value) => ___mb_cur_max.value = value; + + ffi.Pointer malloc_type_malloc(int size, int type_id) { + return _malloc_type_malloc(size, type_id); } - late final __aligned_mallocPtr = + late final _malloc_type_mallocPtr = _lookup< - ffi.NativeFunction Function(ffi.Size, ffi.Size)> - >('_aligned_malloc'); - late final __aligned_malloc = __aligned_mallocPtr + ffi.NativeFunction< + ffi.Pointer Function(ffi.Size, malloc_type_id_t) + > + >('malloc_type_malloc'); + late final _malloc_type_malloc = _malloc_type_mallocPtr .asFunction Function(int, int)>(); - ffi.Pointer _aligned_offset_malloc( - int _Size, - int _Alignment, - int _Offset, - ) { - return __aligned_offset_malloc(_Size, _Alignment, _Offset); + ffi.Pointer malloc_type_calloc(int count, int size, int type_id) { + return _malloc_type_calloc(count, size, type_id); } - late final __aligned_offset_mallocPtr = + late final _malloc_type_callocPtr = _lookup< ffi.NativeFunction< - ffi.Pointer Function(ffi.Size, ffi.Size, ffi.Size) + ffi.Pointer Function(ffi.Size, ffi.Size, malloc_type_id_t) > - >('_aligned_offset_malloc'); - late final __aligned_offset_malloc = __aligned_offset_mallocPtr + >('malloc_type_calloc'); + late final _malloc_type_calloc = _malloc_type_callocPtr .asFunction Function(int, int, int)>(); - int _aligned_msize( - ffi.Pointer _Block, - int _Alignment, - int _Offset, - ) { - return __aligned_msize(_Block, _Alignment, _Offset); + void malloc_type_free(ffi.Pointer ptr, int type_id) { + return _malloc_type_free(ptr, type_id); } - late final __aligned_msizePtr = + late final _malloc_type_freePtr = _lookup< ffi.NativeFunction< - ffi.Size Function(ffi.Pointer, ffi.Size, ffi.Size) + ffi.Void Function(ffi.Pointer, malloc_type_id_t) > - >('_aligned_msize'); - late final __aligned_msize = __aligned_msizePtr - .asFunction, int, int)>(); - - ffi.Pointer _aligned_offset_realloc( - ffi.Pointer _Block, - int _Size, - int _Alignment, - int _Offset, + >('malloc_type_free'); + late final _malloc_type_free = _malloc_type_freePtr + .asFunction, int)>(); + + ffi.Pointer malloc_type_realloc( + ffi.Pointer ptr, + int size, + int type_id, ) { - return __aligned_offset_realloc(_Block, _Size, _Alignment, _Offset); + return _malloc_type_realloc(ptr, size, type_id); } - late final __aligned_offset_reallocPtr = + late final _malloc_type_reallocPtr = _lookup< ffi.NativeFunction< ffi.Pointer Function( ffi.Pointer, ffi.Size, - ffi.Size, - ffi.Size, + malloc_type_id_t, ) > - >('_aligned_offset_realloc'); - late final __aligned_offset_realloc = __aligned_offset_reallocPtr + >('malloc_type_realloc'); + late final _malloc_type_realloc = _malloc_type_reallocPtr .asFunction< - ffi.Pointer Function(ffi.Pointer, int, int, int) + ffi.Pointer Function(ffi.Pointer, int, int) >(); - ffi.Pointer _aligned_offset_recalloc( - ffi.Pointer _Block, - int _Count, - int _Size, - int _Alignment, - int _Offset, + ffi.Pointer malloc_type_valloc(int size, int type_id) { + return _malloc_type_valloc(size, type_id); + } + + late final _malloc_type_vallocPtr = + _lookup< + ffi.NativeFunction< + ffi.Pointer Function(ffi.Size, malloc_type_id_t) + > + >('malloc_type_valloc'); + late final _malloc_type_valloc = _malloc_type_vallocPtr + .asFunction Function(int, int)>(); + + ffi.Pointer malloc_type_aligned_alloc( + int alignment, + int size, + int type_id, ) { - return __aligned_offset_recalloc( - _Block, - _Count, - _Size, - _Alignment, - _Offset, - ); + return _malloc_type_aligned_alloc(alignment, size, type_id); } - late final __aligned_offset_recallocPtr = + late final _malloc_type_aligned_allocPtr = _lookup< ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Size, - ffi.Size, - ffi.Size, - ffi.Size, - ) + ffi.Pointer Function(ffi.Size, ffi.Size, malloc_type_id_t) > - >('_aligned_offset_recalloc'); - late final __aligned_offset_recalloc = __aligned_offset_recallocPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - int, - int, - int, - int, - ) - >(); + >('malloc_type_aligned_alloc'); + late final _malloc_type_aligned_alloc = _malloc_type_aligned_allocPtr + .asFunction Function(int, int, int)>(); - ffi.Pointer _aligned_realloc( - ffi.Pointer _Block, - int _Size, - int _Alignment, + int malloc_type_posix_memalign( + ffi.Pointer> memptr, + int alignment, + int size, + int type_id, ) { - return __aligned_realloc(_Block, _Size, _Alignment); + return _malloc_type_posix_memalign(memptr, alignment, size, type_id); } - late final __aligned_reallocPtr = + late final _malloc_type_posix_memalignPtr = _lookup< ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, + ffi.Int Function( + ffi.Pointer>, ffi.Size, ffi.Size, + malloc_type_id_t, ) > - >('_aligned_realloc'); - late final __aligned_realloc = __aligned_reallocPtr + >('malloc_type_posix_memalign'); + late final _malloc_type_posix_memalign = _malloc_type_posix_memalignPtr .asFunction< - ffi.Pointer Function(ffi.Pointer, int, int) + int Function(ffi.Pointer>, int, int, int) >(); - ffi.Pointer _aligned_recalloc( - ffi.Pointer _Block, - int _Count, - int _Size, - int _Alignment, + ffi.Pointer malloc_type_zone_malloc( + ffi.Pointer zone, + int size, + int type_id, ) { - return __aligned_recalloc(_Block, _Count, _Size, _Alignment); + return _malloc_type_zone_malloc(zone, size, type_id); } - late final __aligned_recallocPtr = + late final _malloc_type_zone_mallocPtr = _lookup< ffi.NativeFunction< ffi.Pointer Function( - ffi.Pointer, - ffi.Size, - ffi.Size, + ffi.Pointer, ffi.Size, + malloc_type_id_t, ) > - >('_aligned_recalloc'); - late final __aligned_recalloc = __aligned_recallocPtr + >('malloc_type_zone_malloc'); + late final _malloc_type_zone_malloc = _malloc_type_zone_mallocPtr .asFunction< - ffi.Pointer Function(ffi.Pointer, int, int, int) + ffi.Pointer Function(ffi.Pointer, int, int) >(); - ffi.Pointer bsearch_s( - ffi.Pointer _Key, - ffi.Pointer _Base, - int _NumOfElements, - int _SizeOfElements, - _CoreCrtSecureSearchSortCompareFunction _CompareFunction, - ffi.Pointer _Context, + ffi.Pointer malloc_type_zone_calloc( + ffi.Pointer zone, + int count, + int size, + int type_id, ) { - return _bsearch_s( - _Key, - _Base, - _NumOfElements, - _SizeOfElements, - _CompareFunction, - _Context, - ); + return _malloc_type_zone_calloc(zone, count, size, type_id); } - late final _bsearch_sPtr = + late final _malloc_type_zone_callocPtr = _lookup< ffi.NativeFunction< ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - rsize_t, - rsize_t, - _CoreCrtSecureSearchSortCompareFunction, - ffi.Pointer, + ffi.Pointer, + ffi.Size, + ffi.Size, + malloc_type_id_t, ) > - >('bsearch_s'); - late final _bsearch_s = _bsearch_sPtr + >('malloc_type_zone_calloc'); + late final _malloc_type_zone_calloc = _malloc_type_zone_callocPtr .asFunction< ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, + ffi.Pointer, + int, int, int, - _CoreCrtSecureSearchSortCompareFunction, - ffi.Pointer, ) >(); - void qsort_s( - ffi.Pointer _Base, - int _NumOfElements, - int _SizeOfElements, - _CoreCrtSecureSearchSortCompareFunction _CompareFunction, - ffi.Pointer _Context, + void malloc_type_zone_free( + ffi.Pointer zone, + ffi.Pointer ptr, + int type_id, ) { - return _qsort_s( - _Base, - _NumOfElements, - _SizeOfElements, - _CompareFunction, - _Context, - ); + return _malloc_type_zone_free(zone, ptr, type_id); } - late final _qsort_sPtr = + late final _malloc_type_zone_freePtr = _lookup< ffi.NativeFunction< ffi.Void Function( + ffi.Pointer, ffi.Pointer, - rsize_t, - rsize_t, - _CoreCrtSecureSearchSortCompareFunction, - ffi.Pointer, + malloc_type_id_t, ) > - >('qsort_s'); - late final _qsort_s = _qsort_sPtr + >('malloc_type_zone_free'); + late final _malloc_type_zone_free = _malloc_type_zone_freePtr .asFunction< - void Function( - ffi.Pointer, - int, - int, - _CoreCrtSecureSearchSortCompareFunction, - ffi.Pointer, - ) + void Function(ffi.Pointer, ffi.Pointer, int) >(); - ffi.Pointer bsearch( - ffi.Pointer _Key, - ffi.Pointer _Base, - int _NumOfElements, - int _SizeOfElements, - _CoreCrtNonSecureSearchSortCompareFunction _CompareFunction, + ffi.Pointer malloc_type_zone_realloc( + ffi.Pointer zone, + ffi.Pointer ptr, + int size, + int type_id, ) { - return _bsearch( - _Key, - _Base, - _NumOfElements, - _SizeOfElements, - _CompareFunction, - ); + return _malloc_type_zone_realloc(zone, ptr, size, type_id); } - late final _bsearchPtr = + late final _malloc_type_zone_reallocPtr = _lookup< ffi.NativeFunction< ffi.Pointer Function( - ffi.Pointer, + ffi.Pointer, ffi.Pointer, ffi.Size, - ffi.Size, - _CoreCrtNonSecureSearchSortCompareFunction, + malloc_type_id_t, ) > - >('bsearch'); - late final _bsearch = _bsearchPtr + >('malloc_type_zone_realloc'); + late final _malloc_type_zone_realloc = _malloc_type_zone_reallocPtr .asFunction< ffi.Pointer Function( - ffi.Pointer, + ffi.Pointer, ffi.Pointer, int, int, - _CoreCrtNonSecureSearchSortCompareFunction, ) >(); - void qsort( - ffi.Pointer _Base, - int _NumOfElements, - int _SizeOfElements, - _CoreCrtNonSecureSearchSortCompareFunction _CompareFunction, + ffi.Pointer malloc_type_zone_valloc( + ffi.Pointer zone, + int size, + int type_id, ) { - return _qsort(_Base, _NumOfElements, _SizeOfElements, _CompareFunction); + return _malloc_type_zone_valloc(zone, size, type_id); } - late final _qsortPtr = + late final _malloc_type_zone_vallocPtr = _lookup< ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, - ffi.Size, + ffi.Pointer Function( + ffi.Pointer, ffi.Size, - _CoreCrtNonSecureSearchSortCompareFunction, + malloc_type_id_t, ) > - >('qsort'); - late final _qsort = _qsortPtr + >('malloc_type_zone_valloc'); + late final _malloc_type_zone_valloc = _malloc_type_zone_vallocPtr .asFunction< - void Function( - ffi.Pointer, - int, - int, - _CoreCrtNonSecureSearchSortCompareFunction, - ) + ffi.Pointer Function(ffi.Pointer, int, int) >(); - ffi.Pointer _lfind_s( - ffi.Pointer _Key, - ffi.Pointer _Base, - ffi.Pointer _NumOfElements, - int _SizeOfElements, - _CoreCrtSecureSearchSortCompareFunction _CompareFunction, - ffi.Pointer _Context, + ffi.Pointer malloc_type_zone_memalign( + ffi.Pointer zone, + int alignment, + int size, + int type_id, ) { - return __lfind_s( - _Key, - _Base, - _NumOfElements, - _SizeOfElements, - _CompareFunction, - _Context, - ); + return _malloc_type_zone_memalign(zone, alignment, size, type_id); } - late final __lfind_sPtr = + late final _malloc_type_zone_memalignPtr = _lookup< ffi.NativeFunction< ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, + ffi.Pointer, ffi.Size, - _CoreCrtSecureSearchSortCompareFunction, - ffi.Pointer, + ffi.Size, + malloc_type_id_t, ) > - >('_lfind_s'); - late final __lfind_s = __lfind_sPtr + >('malloc_type_zone_memalign'); + late final _malloc_type_zone_memalign = _malloc_type_zone_memalignPtr .asFunction< ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, + ffi.Pointer, + int, + int, int, - _CoreCrtSecureSearchSortCompareFunction, - ffi.Pointer, ) >(); - ffi.Pointer _lfind( - ffi.Pointer _Key, - ffi.Pointer _Base, - ffi.Pointer _NumOfElements, - int _SizeOfElements, - _CoreCrtNonSecureSearchSortCompareFunction _CompareFunction, - ) { - return __lfind( - _Key, - _Base, - _NumOfElements, - _SizeOfElements, - _CompareFunction, - ); + ffi.Pointer malloc(int __size) { + return _malloc(__size); + } + + late final _mallocPtr = + _lookup Function(ffi.Size)>>( + 'malloc', + ); + late final _malloc = _mallocPtr + .asFunction Function(int)>(); + + ffi.Pointer calloc(int __count, int __size) { + return _calloc(__count, __size); } - late final __lfindPtr = + late final _callocPtr = _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.UnsignedInt, - _CoreCrtNonSecureSearchSortCompareFunction, - ) + ffi.NativeFunction Function(ffi.Size, ffi.Size)> + >('calloc'); + late final _calloc = _callocPtr + .asFunction Function(int, int)>(); + + void free(ffi.Pointer arg0) { + return _free(arg0); + } + + late final _freePtr = + _lookup)>>( + 'free', + ); + late final _free = _freePtr + .asFunction)>(); + + ffi.Pointer realloc(ffi.Pointer __ptr, int __size) { + return _realloc(__ptr, __size); + } + + late final _reallocPtr = + _lookup< + ffi.NativeFunction< + ffi.Pointer Function(ffi.Pointer, ffi.Size) > - >('_lfind'); - late final __lfind = __lfindPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - int, - _CoreCrtNonSecureSearchSortCompareFunction, - ) - >(); + >('realloc'); + late final _realloc = _reallocPtr + .asFunction Function(ffi.Pointer, int)>(); + + ffi.Pointer reallocf(ffi.Pointer __ptr, int __size) { + return _reallocf(__ptr, __size); + } + + late final _reallocfPtr = + _lookup< + ffi.NativeFunction< + ffi.Pointer Function(ffi.Pointer, ffi.Size) + > + >('reallocf'); + late final _reallocf = _reallocfPtr + .asFunction Function(ffi.Pointer, int)>(); + + ffi.Pointer valloc(int __size) { + return _valloc(__size); + } + + late final _vallocPtr = + _lookup Function(ffi.Size)>>( + 'valloc', + ); + late final _valloc = _vallocPtr + .asFunction Function(int)>(); + + ffi.Pointer aligned_alloc(int __alignment, int __size) { + return _aligned_alloc(__alignment, __size); + } + + late final _aligned_allocPtr = + _lookup< + ffi.NativeFunction Function(ffi.Size, ffi.Size)> + >('aligned_alloc'); + late final _aligned_alloc = _aligned_allocPtr + .asFunction Function(int, int)>(); - ffi.Pointer _lsearch_s( - ffi.Pointer _Key, - ffi.Pointer _Base, - ffi.Pointer _NumOfElements, - int _SizeOfElements, - _CoreCrtSecureSearchSortCompareFunction _CompareFunction, - ffi.Pointer _Context, + int posix_memalign( + ffi.Pointer> __memptr, + int __alignment, + int __size, ) { - return __lsearch_s( - _Key, - _Base, - _NumOfElements, - _SizeOfElements, - _CompareFunction, - _Context, - ); + return _posix_memalign(__memptr, __alignment, __size); } - late final __lsearch_sPtr = + late final _posix_memalignPtr = _lookup< ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, + ffi.Int Function( + ffi.Pointer>, + ffi.Size, ffi.Size, - _CoreCrtSecureSearchSortCompareFunction, - ffi.Pointer, ) > - >('_lsearch_s'); - late final __lsearch_s = __lsearch_sPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - int, - _CoreCrtSecureSearchSortCompareFunction, - ffi.Pointer, - ) - >(); + >('posix_memalign'); + late final _posix_memalign = _posix_memalignPtr + .asFunction>, int, int)>(); - ffi.Pointer _lsearch( - ffi.Pointer _Key, - ffi.Pointer _Base, - ffi.Pointer _NumOfElements, - int _SizeOfElements, - _CoreCrtNonSecureSearchSortCompareFunction _CompareFunction, - ) { - return __lsearch( - _Key, - _Base, - _NumOfElements, - _SizeOfElements, - _CompareFunction, - ); + void abort() { + return _abort(); + } + + late final _abortPtr = _lookup>( + 'abort', + ); + late final _abort = _abortPtr.asFunction(); + + int abs(int arg0) { + return _abs(arg0); + } + + late final _absPtr = _lookup>( + 'abs', + ); + late final _abs = _absPtr.asFunction(); + + int atexit(ffi.Pointer> arg0) { + return _atexit(arg0); } - late final __lsearchPtr = + late final _atexitPtr = _lookup< ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.UnsignedInt, - _CoreCrtNonSecureSearchSortCompareFunction, - ) + ffi.Int Function(ffi.Pointer>) > - >('_lsearch'); - late final __lsearch = __lsearchPtr + >('atexit'); + late final _atexit = _atexitPtr .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - int, - _CoreCrtNonSecureSearchSortCompareFunction, - ) + int Function(ffi.Pointer>) >(); - ffi.Pointer lfind( - ffi.Pointer _Key, - ffi.Pointer _Base, - ffi.Pointer _NumOfElements, - int _SizeOfElements, - _CoreCrtNonSecureSearchSortCompareFunction _CompareFunction, - ) { - return _lfind$1( - _Key, - _Base, - _NumOfElements, - _SizeOfElements, - _CompareFunction, - ); + int at_quick_exit(ffi.Pointer> arg0) { + return _at_quick_exit(arg0); } - late final _lfindPtr = + late final _at_quick_exitPtr = _lookup< ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.UnsignedInt, - _CoreCrtNonSecureSearchSortCompareFunction, - ) + ffi.Int Function(ffi.Pointer>) > - >('lfind'); - late final _lfind$1 = _lfindPtr + >('at_quick_exit'); + late final _at_quick_exit = _at_quick_exitPtr .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - int, - _CoreCrtNonSecureSearchSortCompareFunction, - ) + int Function(ffi.Pointer>) >(); - ffi.Pointer lsearch( - ffi.Pointer _Key, - ffi.Pointer _Base, - ffi.Pointer _NumOfElements, - int _SizeOfElements, - _CoreCrtNonSecureSearchSortCompareFunction _CompareFunction, + double atof(ffi.Pointer arg0) { + return _atof(arg0); + } + + late final _atofPtr = + _lookup)>>( + 'atof', + ); + late final _atof = _atofPtr + .asFunction)>(); + + int atoi(ffi.Pointer arg0) { + return _atoi(arg0); + } + + late final _atoiPtr = + _lookup)>>( + 'atoi', + ); + late final _atoi = _atoiPtr.asFunction)>(); + + int atol(ffi.Pointer arg0) { + return _atol(arg0); + } + + late final _atolPtr = + _lookup)>>( + 'atol', + ); + late final _atol = _atolPtr.asFunction)>(); + + int atoll(ffi.Pointer arg0) { + return _atoll(arg0); + } + + late final _atollPtr = + _lookup)>>( + 'atoll', + ); + late final _atoll = _atollPtr + .asFunction)>(); + + ffi.Pointer bsearch( + ffi.Pointer __key, + ffi.Pointer __base, + int __nel, + int __width, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function(ffi.Pointer, ffi.Pointer) + > + > + __compar, ) { - return _lsearch$1( - _Key, - _Base, - _NumOfElements, - _SizeOfElements, - _CompareFunction, - ); + return _bsearch(__key, __base, __nel, __width, __compar); } - late final _lsearchPtr = + late final _bsearchPtr = _lookup< ffi.NativeFunction< ffi.Pointer Function( ffi.Pointer, ffi.Pointer, - ffi.Pointer, - ffi.UnsignedInt, - _CoreCrtNonSecureSearchSortCompareFunction, + ffi.Size, + ffi.Size, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function(ffi.Pointer, ffi.Pointer) + > + >, ) > - >('lsearch'); - late final _lsearch$1 = _lsearchPtr + >('bsearch'); + late final _bsearch = _bsearchPtr .asFunction< ffi.Pointer Function( ffi.Pointer, ffi.Pointer, - ffi.Pointer, int, - _CoreCrtNonSecureSearchSortCompareFunction, + int, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function(ffi.Pointer, ffi.Pointer) + > + >, ) >(); - int _itow_s( - int _Value, - ffi.Pointer _Buffer, - int _BufferCount, - int _Radix, - ) { - return __itow_s(_Value, _Buffer, _BufferCount, _Radix); + div_t div(int arg0, int arg1) { + return _div(arg0, arg1); } - late final __itow_sPtr = - _lookup< - ffi.NativeFunction< - errno_t Function(ffi.Int, ffi.Pointer, ffi.Size, ffi.Int) - > - >('_itow_s'); - late final __itow_s = __itow_sPtr - .asFunction, int, int)>(); - - ffi.Pointer _itow( - int _Value, - ffi.Pointer _Buffer, - int _Radix, - ) { - return __itow(_Value, _Buffer, _Radix); + late final _divPtr = + _lookup>('div'); + late final _div = _divPtr.asFunction(); + + void exit(int arg0) { + return _exit(arg0); } - late final __itowPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Int, - ffi.Pointer, - ffi.Int, - ) - > - >('_itow'); - late final __itow = __itowPtr - .asFunction< - ffi.Pointer Function(int, ffi.Pointer, int) - >(); + late final _exitPtr = _lookup>( + 'exit', + ); + late final _exit = _exitPtr.asFunction(); - int _ltow_s( - int _Value, - ffi.Pointer _Buffer, - int _BufferCount, - int _Radix, - ) { - return __ltow_s(_Value, _Buffer, _BufferCount, _Radix); + ffi.Pointer getenv(ffi.Pointer arg0) { + return _getenv(arg0); } - late final __ltow_sPtr = + late final _getenvPtr = _lookup< ffi.NativeFunction< - errno_t Function(ffi.Long, ffi.Pointer, ffi.Size, ffi.Int) + ffi.Pointer Function(ffi.Pointer) > - >('_ltow_s'); - late final __ltow_s = __ltow_sPtr - .asFunction, int, int)>(); - - ffi.Pointer _ltow( - int _Value, - ffi.Pointer _Buffer, - int _Radix, - ) { - return __ltow(_Value, _Buffer, _Radix); + >('getenv'); + late final _getenv = _getenvPtr + .asFunction Function(ffi.Pointer)>(); + + int labs(int arg0) { + return _labs(arg0); + } + + late final _labsPtr = + _lookup>('labs'); + late final _labs = _labsPtr.asFunction(); + + ldiv_t ldiv(int arg0, int arg1) { + return _ldiv(arg0, arg1); + } + + late final _ldivPtr = + _lookup>('ldiv'); + late final _ldiv = _ldivPtr.asFunction(); + + int llabs(int arg0) { + return _llabs(arg0); + } + + late final _llabsPtr = + _lookup>('llabs'); + late final _llabs = _llabsPtr.asFunction(); + + lldiv_t lldiv(int arg0, int arg1) { + return _lldiv(arg0, arg1); + } + + late final _lldivPtr = + _lookup>( + 'lldiv', + ); + late final _lldiv = _lldivPtr.asFunction(); + + int mblen(ffi.Pointer __s, int __n) { + return _mblen(__s, __n); } - late final __ltowPtr = + late final _mblenPtr = _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Long, - ffi.Pointer, - ffi.Int, - ) - > - >('_ltow'); - late final __ltow = __ltowPtr - .asFunction< - ffi.Pointer Function(int, ffi.Pointer, int) - >(); + ffi.NativeFunction, ffi.Size)> + >('mblen'); + late final _mblen = _mblenPtr + .asFunction, int)>(); - int _ultow_s( - int _Value, - ffi.Pointer _Buffer, - int _BufferCount, - int _Radix, + int mbstowcs( + ffi.Pointer arg0, + ffi.Pointer arg1, + int __n, ) { - return __ultow_s(_Value, _Buffer, _BufferCount, _Radix); + return _mbstowcs(arg0, arg1, __n); } - late final __ultow_sPtr = + late final _mbstowcsPtr = _lookup< ffi.NativeFunction< - errno_t Function( - ffi.UnsignedLong, + ffi.Size Function( ffi.Pointer, + ffi.Pointer, ffi.Size, - ffi.Int, ) > - >('_ultow_s'); - late final __ultow_s = __ultow_sPtr - .asFunction, int, int)>(); - - ffi.Pointer _ultow( - int _Value, - ffi.Pointer _Buffer, - int _Radix, - ) { - return __ultow(_Value, _Buffer, _Radix); + >('mbstowcs'); + late final _mbstowcs = _mbstowcsPtr + .asFunction< + int Function(ffi.Pointer, ffi.Pointer, int) + >(); + + int mbtowc(ffi.Pointer arg0, ffi.Pointer arg1, int __n) { + return _mbtowc(arg0, arg1, __n); } - late final __ultowPtr = + late final _mbtowcPtr = _lookup< ffi.NativeFunction< - ffi.Pointer Function( - ffi.UnsignedLong, + ffi.Int Function( ffi.Pointer, - ffi.Int, + ffi.Pointer, + ffi.Size, ) > - >('_ultow'); - late final __ultow = __ultowPtr + >('mbtowc'); + late final _mbtowc = _mbtowcPtr .asFunction< - ffi.Pointer Function(int, ffi.Pointer, int) + int Function(ffi.Pointer, ffi.Pointer, int) >(); - double wcstod( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, + void qsort( + ffi.Pointer __base, + int __nel, + int __width, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function(ffi.Pointer, ffi.Pointer) + > + > + __compar, ) { - return _wcstod(_String, _EndPtr); + return _qsort(__base, __nel, __width, __compar); } - late final _wcstodPtr = + late final _qsortPtr = _lookup< ffi.NativeFunction< - ffi.Double Function( - ffi.Pointer, - ffi.Pointer>, + ffi.Void Function( + ffi.Pointer, + ffi.Size, + ffi.Size, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function(ffi.Pointer, ffi.Pointer) + > + >, ) > - >('wcstod'); - late final _wcstod = _wcstodPtr + >('qsort'); + late final _qsort = _qsortPtr .asFunction< - double Function( - ffi.Pointer, - ffi.Pointer>, + void Function( + ffi.Pointer, + int, + int, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function(ffi.Pointer, ffi.Pointer) + > + >, ) >(); - double _wcstod_l( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - _locale_t _Locale, + void quick_exit(int arg0) { + return _quick_exit(arg0); + } + + late final _quick_exitPtr = + _lookup>('quick_exit'); + late final _quick_exit = _quick_exitPtr.asFunction(); + + int rand() { + return _rand(); + } + + late final _randPtr = _lookup>('rand'); + late final _rand = _randPtr.asFunction(); + + void srand(int arg0) { + return _srand(arg0); + } + + late final _srandPtr = + _lookup>('srand'); + late final _srand = _srandPtr.asFunction(); + + double strtod( + ffi.Pointer arg0, + ffi.Pointer> arg1, ) { - return __wcstod_l(_String, _EndPtr, _Locale); + return _strtod(arg0, arg1); } - late final __wcstod_lPtr = + late final _strtodPtr = _lookup< ffi.NativeFunction< ffi.Double Function( - ffi.Pointer, - ffi.Pointer>, - _locale_t, + ffi.Pointer, + ffi.Pointer>, ) > - >('_wcstod_l'); - late final __wcstod_l = __wcstod_lPtr + >('strtod'); + late final _strtod = _strtodPtr .asFunction< double Function( - ffi.Pointer, - ffi.Pointer>, - _locale_t, + ffi.Pointer, + ffi.Pointer>, ) >(); - int wcstol( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, + double strtof( + ffi.Pointer arg0, + ffi.Pointer> arg1, ) { - return _wcstol(_String, _EndPtr, _Radix); + return _strtof(arg0, arg1); } - late final _wcstolPtr = + late final _strtofPtr = _lookup< ffi.NativeFunction< - ffi.Long Function( - ffi.Pointer, - ffi.Pointer>, - ffi.Int, + ffi.Float Function( + ffi.Pointer, + ffi.Pointer>, ) > - >('wcstol'); - late final _wcstol = _wcstolPtr + >('strtof'); + late final _strtof = _strtofPtr .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer>, - int, + double Function( + ffi.Pointer, + ffi.Pointer>, ) >(); - int _wcstol_l( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, - _locale_t _Locale, + int strtol( + ffi.Pointer __str, + ffi.Pointer> __endptr, + int __base, ) { - return __wcstol_l(_String, _EndPtr, _Radix, _Locale); + return _strtol(__str, __endptr, __base); } - late final __wcstol_lPtr = + late final _strtolPtr = _lookup< ffi.NativeFunction< ffi.Long Function( - ffi.Pointer, - ffi.Pointer>, + ffi.Pointer, + ffi.Pointer>, ffi.Int, - _locale_t, ) > - >('_wcstol_l'); - late final __wcstol_l = __wcstol_lPtr + >('strtol'); + late final _strtol = _strtolPtr .asFunction< int Function( - ffi.Pointer, - ffi.Pointer>, + ffi.Pointer, + ffi.Pointer>, int, - _locale_t, ) >(); - int wcstoll( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, + int strtoll( + ffi.Pointer __str, + ffi.Pointer> __endptr, + int __base, ) { - return _wcstoll(_String, _EndPtr, _Radix); + return _strtoll(__str, __endptr, __base); } - late final _wcstollPtr = + late final _strtollPtr = _lookup< ffi.NativeFunction< ffi.LongLong Function( - ffi.Pointer, - ffi.Pointer>, + ffi.Pointer, + ffi.Pointer>, ffi.Int, ) > - >('wcstoll'); - late final _wcstoll = _wcstollPtr + >('strtoll'); + late final _strtoll = _strtollPtr .asFunction< int Function( - ffi.Pointer, - ffi.Pointer>, + ffi.Pointer, + ffi.Pointer>, int, ) >(); - int _wcstoll_l( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, - _locale_t _Locale, + int strtoul( + ffi.Pointer __str, + ffi.Pointer> __endptr, + int __base, ) { - return __wcstoll_l(_String, _EndPtr, _Radix, _Locale); + return _strtoul(__str, __endptr, __base); } - late final __wcstoll_lPtr = + late final _strtoulPtr = _lookup< ffi.NativeFunction< - ffi.LongLong Function( - ffi.Pointer, - ffi.Pointer>, + ffi.UnsignedLong Function( + ffi.Pointer, + ffi.Pointer>, ffi.Int, - _locale_t, ) > - >('_wcstoll_l'); - late final __wcstoll_l = __wcstoll_lPtr + >('strtoul'); + late final _strtoul = _strtoulPtr .asFunction< int Function( - ffi.Pointer, - ffi.Pointer>, + ffi.Pointer, + ffi.Pointer>, int, - _locale_t, ) >(); - int wcstoul( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, + int strtoull( + ffi.Pointer __str, + ffi.Pointer> __endptr, + int __base, ) { - return _wcstoul(_String, _EndPtr, _Radix); + return _strtoull(__str, __endptr, __base); } - late final _wcstoulPtr = + late final _strtoullPtr = _lookup< ffi.NativeFunction< - ffi.UnsignedLong Function( - ffi.Pointer, - ffi.Pointer>, + ffi.UnsignedLongLong Function( + ffi.Pointer, + ffi.Pointer>, ffi.Int, ) > - >('wcstoul'); - late final _wcstoul = _wcstoulPtr + >('strtoull'); + late final _strtoull = _strtoullPtr .asFunction< int Function( - ffi.Pointer, - ffi.Pointer>, + ffi.Pointer, + ffi.Pointer>, int, ) >(); - int _wcstoul_l( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, - _locale_t _Locale, + int system(ffi.Pointer arg0) { + return _system(arg0); + } + + late final _systemPtr = + _lookup)>>( + 'system', + ); + late final _system = _systemPtr + .asFunction)>(); + + int wcstombs( + ffi.Pointer arg0, + ffi.Pointer arg1, + int __n, ) { - return __wcstoul_l(_String, _EndPtr, _Radix, _Locale); + return _wcstombs(arg0, arg1, __n); } - late final __wcstoul_lPtr = + late final _wcstombsPtr = _lookup< ffi.NativeFunction< - ffi.UnsignedLong Function( + ffi.Size Function( + ffi.Pointer, ffi.Pointer, - ffi.Pointer>, - ffi.Int, - _locale_t, + ffi.Size, ) > - >('_wcstoul_l'); - late final __wcstoul_l = __wcstoul_lPtr + >('wcstombs'); + late final _wcstombs = _wcstombsPtr .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer>, - int, - _locale_t, - ) + int Function(ffi.Pointer, ffi.Pointer, int) >(); - int wcstoull( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, + int wctomb(ffi.Pointer arg0, int arg1) { + return _wctomb(arg0, arg1); + } + + late final _wctombPtr = + _lookup< + ffi.NativeFunction, ffi.WChar)> + >('wctomb'); + late final _wctomb = _wctombPtr + .asFunction, int)>(); + + void _Exit(int arg0) { + return __Exit(arg0); + } + + late final __ExitPtr = + _lookup>('_Exit'); + late final __Exit = __ExitPtr.asFunction(); + + int a64l(ffi.Pointer arg0) { + return _a64l(arg0); + } + + late final _a64lPtr = + _lookup)>>( + 'a64l', + ); + late final _a64l = _a64lPtr.asFunction)>(); + + double drand48() { + return _drand48(); + } + + late final _drand48Ptr = _lookup>( + 'drand48', + ); + late final _drand48 = _drand48Ptr.asFunction(); + + ffi.Pointer ecvt( + double arg0, + int arg1, + ffi.Pointer arg2, + ffi.Pointer arg3, ) { - return _wcstoull(_String, _EndPtr, _Radix); + return _ecvt(arg0, arg1, arg2, arg3); } - late final _wcstoullPtr = + late final _ecvtPtr = _lookup< ffi.NativeFunction< - ffi.UnsignedLongLong Function( - ffi.Pointer, - ffi.Pointer>, + ffi.Pointer Function( + ffi.Double, ffi.Int, + ffi.Pointer, + ffi.Pointer, ) > - >('wcstoull'); - late final _wcstoull = _wcstoullPtr + >('ecvt'); + late final _ecvt = _ecvtPtr .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer>, + ffi.Pointer Function( + double, int, + ffi.Pointer, + ffi.Pointer, ) >(); - int _wcstoull_l( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, - _locale_t _Locale, + double erand48(ffi.Pointer arg0) { + return _erand48(arg0); + } + + late final _erand48Ptr = + _lookup< + ffi.NativeFunction)> + >('erand48'); + late final _erand48 = _erand48Ptr + .asFunction)>(); + + ffi.Pointer fcvt( + double arg0, + int arg1, + ffi.Pointer arg2, + ffi.Pointer arg3, ) { - return __wcstoull_l(_String, _EndPtr, _Radix, _Locale); + return _fcvt(arg0, arg1, arg2, arg3); } - late final __wcstoull_lPtr = + late final _fcvtPtr = _lookup< ffi.NativeFunction< - ffi.UnsignedLongLong Function( - ffi.Pointer, - ffi.Pointer>, + ffi.Pointer Function( + ffi.Double, ffi.Int, - _locale_t, + ffi.Pointer, + ffi.Pointer, ) > - >('_wcstoull_l'); - late final __wcstoull_l = __wcstoull_lPtr + >('fcvt'); + late final _fcvt = _fcvtPtr .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer>, + ffi.Pointer Function( + double, int, - _locale_t, + ffi.Pointer, + ffi.Pointer, ) >(); - double wcstof( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, + ffi.Pointer gcvt( + double arg0, + int arg1, + ffi.Pointer arg2, ) { - return _wcstof(_String, _EndPtr); + return _gcvt(arg0, arg1, arg2); } - late final _wcstofPtr = + late final _gcvtPtr = _lookup< ffi.NativeFunction< - ffi.Float Function( - ffi.Pointer, - ffi.Pointer>, + ffi.Pointer Function( + ffi.Double, + ffi.Int, + ffi.Pointer, ) > - >('wcstof'); - late final _wcstof = _wcstofPtr + >('gcvt'); + late final _gcvt = _gcvtPtr .asFunction< - double Function( - ffi.Pointer, - ffi.Pointer>, - ) + ffi.Pointer Function(double, int, ffi.Pointer) >(); - double _wcstof_l( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - _locale_t _Locale, + int getsubopt( + ffi.Pointer> arg0, + ffi.Pointer> arg1, + ffi.Pointer> arg2, ) { - return __wcstof_l(_String, _EndPtr, _Locale); + return _getsubopt(arg0, arg1, arg2); } - late final __wcstof_lPtr = + late final _getsuboptPtr = _lookup< ffi.NativeFunction< - ffi.Float Function( - ffi.Pointer, - ffi.Pointer>, - _locale_t, + ffi.Int Function( + ffi.Pointer>, + ffi.Pointer>, + ffi.Pointer>, ) > - >('_wcstof_l'); - late final __wcstof_l = __wcstof_lPtr + >('getsubopt'); + late final _getsubopt = _getsuboptPtr .asFunction< - double Function( - ffi.Pointer, - ffi.Pointer>, - _locale_t, + int Function( + ffi.Pointer>, + ffi.Pointer>, + ffi.Pointer>, ) >(); - double _wtof(ffi.Pointer _String) { - return __wtof(_String); + int grantpt(int arg0) { + return _grantpt(arg0); } - late final __wtofPtr = - _lookup)>>( - '_wtof', - ); - late final __wtof = __wtofPtr - .asFunction)>(); + late final _grantptPtr = + _lookup>('grantpt'); + late final _grantpt = _grantptPtr.asFunction(); - double _wtof_l(ffi.Pointer _String, _locale_t _Locale) { - return __wtof_l(_String, _Locale); + ffi.Pointer initstate( + int arg0, + ffi.Pointer arg1, + int __size, + ) { + return _initstate(arg0, arg1, __size); } - late final __wtof_lPtr = + late final _initstatePtr = _lookup< ffi.NativeFunction< - ffi.Double Function(ffi.Pointer, _locale_t) + ffi.Pointer Function( + ffi.UnsignedInt, + ffi.Pointer, + ffi.Size, + ) > - >('_wtof_l'); - late final __wtof_l = __wtof_lPtr - .asFunction, _locale_t)>(); - - int _wtoi(ffi.Pointer _String) { - return __wtoi(_String); - } - - late final __wtoiPtr = - _lookup)>>( - '_wtoi', - ); - late final __wtoi = __wtoiPtr - .asFunction)>(); + >('initstate'); + late final _initstate = _initstatePtr + .asFunction< + ffi.Pointer Function(int, ffi.Pointer, int) + >(); - int _wtoi_l(ffi.Pointer _String, _locale_t _Locale) { - return __wtoi_l(_String, _Locale); + int jrand48(ffi.Pointer arg0) { + return _jrand48(arg0); } - late final __wtoi_lPtr = + late final _jrand48Ptr = _lookup< - ffi.NativeFunction, _locale_t)> - >('_wtoi_l'); - late final __wtoi_l = __wtoi_lPtr - .asFunction, _locale_t)>(); + ffi.NativeFunction)> + >('jrand48'); + late final _jrand48 = _jrand48Ptr + .asFunction)>(); - int _wtol(ffi.Pointer _String) { - return __wtol(_String); + ffi.Pointer l64a(int arg0) { + return _l64a(arg0); } - late final __wtolPtr = - _lookup)>>( - '_wtol', + late final _l64aPtr = + _lookup Function(ffi.Long)>>( + 'l64a', ); - late final __wtol = __wtolPtr - .asFunction)>(); + late final _l64a = _l64aPtr.asFunction Function(int)>(); - int _wtol_l(ffi.Pointer _String, _locale_t _Locale) { - return __wtol_l(_String, _Locale); + void lcong48(ffi.Pointer arg0) { + return _lcong48(arg0); } - late final __wtol_lPtr = + late final _lcong48Ptr = _lookup< - ffi.NativeFunction, _locale_t)> - >('_wtol_l'); - late final __wtol_l = __wtol_lPtr - .asFunction, _locale_t)>(); + ffi.NativeFunction)> + >('lcong48'); + late final _lcong48 = _lcong48Ptr + .asFunction)>(); - int _wtoll(ffi.Pointer _String) { - return __wtoll(_String); + int lrand48() { + return _lrand48(); } - late final __wtollPtr = - _lookup< - ffi.NativeFunction)> - >('_wtoll'); - late final __wtoll = __wtollPtr - .asFunction)>(); + late final _lrand48Ptr = _lookup>( + 'lrand48', + ); + late final _lrand48 = _lrand48Ptr.asFunction(); - int _wtoll_l(ffi.Pointer _String, _locale_t _Locale) { - return __wtoll_l(_String, _Locale); + ffi.Pointer mktemp(ffi.Pointer arg0) { + return _mktemp(arg0); } - late final __wtoll_lPtr = + late final _mktempPtr = _lookup< ffi.NativeFunction< - ffi.LongLong Function(ffi.Pointer, _locale_t) + ffi.Pointer Function(ffi.Pointer) > - >('_wtoll_l'); - late final __wtoll_l = __wtoll_lPtr - .asFunction, _locale_t)>(); - - int _i64tow_s( - int _Value, - ffi.Pointer _Buffer, - int _BufferCount, - int _Radix, - ) { - return __i64tow_s(_Value, _Buffer, _BufferCount, _Radix); - } + >('mktemp'); + late final _mktemp = _mktempPtr + .asFunction Function(ffi.Pointer)>(); - late final __i64tow_sPtr = - _lookup< - ffi.NativeFunction< - errno_t Function( - ffi.LongLong, - ffi.Pointer, - ffi.Size, - ffi.Int, - ) - > - >('_i64tow_s'); - late final __i64tow_s = __i64tow_sPtr - .asFunction, int, int)>(); - - ffi.Pointer _i64tow( - int _Value, - ffi.Pointer _Buffer, - int _Radix, - ) { - return __i64tow(_Value, _Buffer, _Radix); + int mkstemp(ffi.Pointer arg0) { + return _mkstemp(arg0); } - late final __i64towPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.LongLong, - ffi.Pointer, - ffi.Int, - ) - > - >('_i64tow'); - late final __i64tow = __i64towPtr - .asFunction< - ffi.Pointer Function(int, ffi.Pointer, int) - >(); + late final _mkstempPtr = + _lookup)>>( + 'mkstemp', + ); + late final _mkstemp = _mkstempPtr + .asFunction)>(); - int _ui64tow_s( - int _Value, - ffi.Pointer _Buffer, - int _BufferCount, - int _Radix, - ) { - return __ui64tow_s(_Value, _Buffer, _BufferCount, _Radix); + int mrand48() { + return _mrand48(); } - late final __ui64tow_sPtr = - _lookup< - ffi.NativeFunction< - errno_t Function( - ffi.UnsignedLongLong, - ffi.Pointer, - ffi.Size, - ffi.Int, - ) - > - >('_ui64tow_s'); - late final __ui64tow_s = __ui64tow_sPtr - .asFunction, int, int)>(); - - ffi.Pointer _ui64tow( - int _Value, - ffi.Pointer _Buffer, - int _Radix, - ) { - return __ui64tow(_Value, _Buffer, _Radix); + late final _mrand48Ptr = _lookup>( + 'mrand48', + ); + late final _mrand48 = _mrand48Ptr.asFunction(); + + int nrand48(ffi.Pointer arg0) { + return _nrand48(arg0); } - late final __ui64towPtr = + late final _nrand48Ptr = _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.UnsignedLongLong, - ffi.Pointer, - ffi.Int, - ) - > - >('_ui64tow'); - late final __ui64tow = __ui64towPtr - .asFunction< - ffi.Pointer Function(int, ffi.Pointer, int) - >(); + ffi.NativeFunction)> + >('nrand48'); + late final _nrand48 = _nrand48Ptr + .asFunction)>(); - int _wtoi64(ffi.Pointer _String) { - return __wtoi64(_String); + int posix_openpt(int arg0) { + return _posix_openpt(arg0); } - late final __wtoi64Ptr = - _lookup< - ffi.NativeFunction)> - >('_wtoi64'); - late final __wtoi64 = __wtoi64Ptr - .asFunction)>(); + late final _posix_openptPtr = + _lookup>('posix_openpt'); + late final _posix_openpt = _posix_openptPtr.asFunction(); - int _wtoi64_l(ffi.Pointer _String, _locale_t _Locale) { - return __wtoi64_l(_String, _Locale); + ffi.Pointer ptsname(int arg0) { + return _ptsname(arg0); } - late final __wtoi64_lPtr = - _lookup< - ffi.NativeFunction< - ffi.LongLong Function(ffi.Pointer, _locale_t) - > - >('_wtoi64_l'); - late final __wtoi64_l = __wtoi64_lPtr - .asFunction, _locale_t)>(); - - int _wcstoi64( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, - ) { - return __wcstoi64(_String, _EndPtr, _Radix); + late final _ptsnamePtr = + _lookup Function(ffi.Int)>>( + 'ptsname', + ); + late final _ptsname = _ptsnamePtr + .asFunction Function(int)>(); + + int ptsname_r(int fildes, ffi.Pointer buffer, int buflen) { + return _ptsname_r(fildes, buffer, buflen); } - late final __wcstoi64Ptr = + late final _ptsname_rPtr = _lookup< ffi.NativeFunction< - ffi.LongLong Function( - ffi.Pointer, - ffi.Pointer>, - ffi.Int, - ) + ffi.Int Function(ffi.Int, ffi.Pointer, ffi.Size) > - >('_wcstoi64'); - late final __wcstoi64 = __wcstoi64Ptr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer>, - int, - ) - >(); + >('ptsname_r'); + late final _ptsname_r = _ptsname_rPtr + .asFunction, int)>(); + + int putenv(ffi.Pointer arg0) { + return _putenv(arg0); + } + + late final _putenvPtr = + _lookup)>>( + 'putenv', + ); + late final _putenv = _putenvPtr + .asFunction)>(); + + int random() { + return _random(); + } + + late final _randomPtr = _lookup>( + 'random', + ); + late final _random = _randomPtr.asFunction(); + + int rand_r(ffi.Pointer arg0) { + return _rand_r(arg0); + } + + late final _rand_rPtr = + _lookup< + ffi.NativeFunction)> + >('rand_r'); + late final _rand_r = _rand_rPtr + .asFunction)>(); - int _wcstoi64_l( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, - _locale_t _Locale, + ffi.Pointer realpath( + ffi.Pointer arg0, + ffi.Pointer arg1, ) { - return __wcstoi64_l(_String, _EndPtr, _Radix, _Locale); + return _realpath(arg0, arg1); } - late final __wcstoi64_lPtr = + late final _realpathPtr = _lookup< ffi.NativeFunction< - ffi.LongLong Function( - ffi.Pointer, - ffi.Pointer>, - ffi.Int, - _locale_t, + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, ) > - >('_wcstoi64_l'); - late final __wcstoi64_l = __wcstoi64_lPtr + >('realpath'); + late final _realpath = _realpathPtr .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer>, - int, - _locale_t, + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, ) >(); - int _wcstoui64( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, - ) { - return __wcstoui64(_String, _EndPtr, _Radix); + ffi.Pointer seed48(ffi.Pointer arg0) { + return _seed48(arg0); } - late final __wcstoui64Ptr = + late final _seed48Ptr = _lookup< ffi.NativeFunction< - ffi.UnsignedLongLong Function( - ffi.Pointer, - ffi.Pointer>, - ffi.Int, + ffi.Pointer Function( + ffi.Pointer, ) > - >('_wcstoui64'); - late final __wcstoui64 = __wcstoui64Ptr + >('seed48'); + late final _seed48 = _seed48Ptr .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer>, - int, - ) + ffi.Pointer Function(ffi.Pointer) >(); - int _wcstoui64_l( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, - _locale_t _Locale, + int setenv( + ffi.Pointer __name, + ffi.Pointer __value, + int __overwrite, ) { - return __wcstoui64_l(_String, _EndPtr, _Radix, _Locale); + return _setenv(__name, __value, __overwrite); } - late final __wcstoui64_lPtr = + late final _setenvPtr = _lookup< ffi.NativeFunction< - ffi.UnsignedLongLong Function( - ffi.Pointer, - ffi.Pointer>, + ffi.Int Function( + ffi.Pointer, + ffi.Pointer, ffi.Int, - _locale_t, ) > - >('_wcstoui64_l'); - late final __wcstoui64_l = __wcstoui64_lPtr + >('setenv'); + late final _setenv = _setenvPtr .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer>, - int, - _locale_t, - ) + int Function(ffi.Pointer, ffi.Pointer, int) >(); - ffi.Pointer _wfullpath( - ffi.Pointer _Buffer, - ffi.Pointer _Path, - int _BufferCount, - ) { - return __wfullpath(_Buffer, _Path, _BufferCount); + void setkey(ffi.Pointer arg0) { + return _setkey(arg0); + } + + late final _setkeyPtr = + _lookup)>>( + 'setkey', + ); + late final _setkey = _setkeyPtr + .asFunction)>(); + + ffi.Pointer setstate(ffi.Pointer arg0) { + return _setstate(arg0); } - late final __wfullpathPtr = + late final _setstatePtr = _lookup< ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Size, - ) + ffi.Pointer Function(ffi.Pointer) > - >('_wfullpath'); - late final __wfullpath = __wfullpathPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - int, - ) - >(); + >('setstate'); + late final _setstate = _setstatePtr + .asFunction Function(ffi.Pointer)>(); - int _wmakepath_s( - ffi.Pointer _Buffer, - int _BufferCount, - ffi.Pointer _Drive, - ffi.Pointer _Dir, - ffi.Pointer _Filename, - ffi.Pointer _Ext, - ) { - return __wmakepath_s(_Buffer, _BufferCount, _Drive, _Dir, _Filename, _Ext); + void srand48(int arg0) { + return _srand48(arg0); + } + + late final _srand48Ptr = + _lookup>('srand48'); + late final _srand48 = _srand48Ptr.asFunction(); + + void srandom(int arg0) { + return _srandom(arg0); + } + + late final _srandomPtr = + _lookup>( + 'srandom', + ); + late final _srandom = _srandomPtr.asFunction(); + + int unlockpt(int arg0) { + return _unlockpt(arg0); + } + + late final _unlockptPtr = + _lookup>('unlockpt'); + late final _unlockpt = _unlockptPtr.asFunction(); + + int unsetenv(ffi.Pointer arg0) { + return _unsetenv(arg0); + } + + late final _unsetenvPtr = + _lookup)>>( + 'unsetenv', + ); + late final _unsetenv = _unsetenvPtr + .asFunction)>(); + + int arc4random() { + return _arc4random(); } - late final __wmakepath_sPtr = + late final _arc4randomPtr = + _lookup>('arc4random'); + late final _arc4random = _arc4randomPtr.asFunction(); + + void arc4random_addrandom(ffi.Pointer arg0, int __datlen) { + return _arc4random_addrandom(arg0, __datlen); + } + + late final _arc4random_addrandomPtr = _lookup< ffi.NativeFunction< - errno_t Function( - ffi.Pointer, - ffi.Size, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) + ffi.Void Function(ffi.Pointer, ffi.Int) > - >('_wmakepath_s'); - late final __wmakepath_s = __wmakepath_sPtr - .asFunction< - int Function( - ffi.Pointer, - int, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - >(); + >('arc4random_addrandom'); + late final _arc4random_addrandom = _arc4random_addrandomPtr + .asFunction, int)>(); - void _wmakepath( - ffi.Pointer _Buffer, - ffi.Pointer _Drive, - ffi.Pointer _Dir, - ffi.Pointer _Filename, - ffi.Pointer _Ext, - ) { - return __wmakepath(_Buffer, _Drive, _Dir, _Filename, _Ext); + void arc4random_buf(ffi.Pointer __buf, int __nbytes) { + return _arc4random_buf(__buf, __nbytes); } - late final __wmakepathPtr = + late final _arc4random_bufPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - > - >('_wmakepath'); - late final __wmakepath = __wmakepathPtr - .asFunction< - void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - >(); + ffi.NativeFunction, ffi.Size)> + >('arc4random_buf'); + late final _arc4random_buf = _arc4random_bufPtr + .asFunction, int)>(); + + void arc4random_stir() { + return _arc4random_stir(); + } + + late final _arc4random_stirPtr = + _lookup>('arc4random_stir'); + late final _arc4random_stir = _arc4random_stirPtr + .asFunction(); - void _wperror(ffi.Pointer _ErrorMessage) { - return __wperror(_ErrorMessage); + int arc4random_uniform(int __upper_bound) { + return _arc4random_uniform(__upper_bound); } - late final __wperrorPtr = - _lookup)>>( - '_wperror', + late final _arc4random_uniformPtr = + _lookup>( + 'arc4random_uniform', ); - late final __wperror = __wperrorPtr - .asFunction)>(); - - void _wsplitpath( - ffi.Pointer _FullPath, - ffi.Pointer _Drive, - ffi.Pointer _Dir, - ffi.Pointer _Filename, - ffi.Pointer _Ext, + late final _arc4random_uniform = _arc4random_uniformPtr + .asFunction(); + + ffi.Pointer cgetcap( + ffi.Pointer arg0, + ffi.Pointer arg1, + int arg2, ) { - return __wsplitpath(_FullPath, _Drive, _Dir, _Filename, _Ext); + return _cgetcap(arg0, arg1, arg2); } - late final __wsplitpathPtr = + late final _cgetcapPtr = _lookup< ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Int, ) > - >('_wsplitpath'); - late final __wsplitpath = __wsplitpathPtr + >('cgetcap'); + late final _cgetcap = _cgetcapPtr .asFunction< - void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + int, ) >(); - int _wsplitpath_s( - ffi.Pointer _FullPath, - ffi.Pointer _Drive, - int _DriveCount, - ffi.Pointer _Dir, - int _DirCount, - ffi.Pointer _Filename, - int _FilenameCount, - ffi.Pointer _Ext, - int _ExtCount, + int cgetclose() { + return _cgetclose(); + } + + late final _cgetclosePtr = _lookup>( + 'cgetclose', + ); + late final _cgetclose = _cgetclosePtr.asFunction(); + + int cgetent( + ffi.Pointer> arg0, + ffi.Pointer> arg1, + ffi.Pointer arg2, ) { - return __wsplitpath_s( - _FullPath, - _Drive, - _DriveCount, - _Dir, - _DirCount, - _Filename, - _FilenameCount, - _Ext, - _ExtCount, - ); + return _cgetent(arg0, arg1, arg2); } - late final __wsplitpath_sPtr = + late final _cgetentPtr = _lookup< ffi.NativeFunction< - errno_t Function( - ffi.Pointer, - ffi.Pointer, - ffi.Size, - ffi.Pointer, - ffi.Size, - ffi.Pointer, - ffi.Size, - ffi.Pointer, - ffi.Size, + ffi.Int Function( + ffi.Pointer>, + ffi.Pointer>, + ffi.Pointer, ) > - >('_wsplitpath_s'); - late final __wsplitpath_s = __wsplitpath_sPtr + >('cgetent'); + late final _cgetent = _cgetentPtr .asFunction< int Function( - ffi.Pointer, - ffi.Pointer, - int, - ffi.Pointer, - int, - ffi.Pointer, - int, - ffi.Pointer, - int, + ffi.Pointer>, + ffi.Pointer>, + ffi.Pointer, ) >(); - int _wdupenv_s( - ffi.Pointer> _Buffer, - ffi.Pointer _BufferCount, - ffi.Pointer _VarName, + int cgetfirst( + ffi.Pointer> arg0, + ffi.Pointer> arg1, ) { - return __wdupenv_s(_Buffer, _BufferCount, _VarName); + return _cgetfirst(arg0, arg1); } - late final __wdupenv_sPtr = + late final _cgetfirstPtr = _lookup< ffi.NativeFunction< - errno_t Function( - ffi.Pointer>, - ffi.Pointer, - ffi.Pointer, + ffi.Int Function( + ffi.Pointer>, + ffi.Pointer>, ) > - >('_wdupenv_s'); - late final __wdupenv_s = __wdupenv_sPtr + >('cgetfirst'); + late final _cgetfirst = _cgetfirstPtr .asFunction< int Function( - ffi.Pointer>, - ffi.Pointer, - ffi.Pointer, + ffi.Pointer>, + ffi.Pointer>, ) >(); - ffi.Pointer _wgetenv(ffi.Pointer _VarName) { - return __wgetenv(_VarName); + int cgetmatch(ffi.Pointer arg0, ffi.Pointer arg1) { + return _cgetmatch(arg0, arg1); } - late final __wgetenvPtr = + late final _cgetmatchPtr = _lookup< ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) + ffi.Int Function(ffi.Pointer, ffi.Pointer) > - >('_wgetenv'); - late final __wgetenv = __wgetenvPtr - .asFunction Function(ffi.Pointer)>(); - - int _wgetenv_s( - ffi.Pointer _RequiredCount, - ffi.Pointer _Buffer, - int _BufferCount, - ffi.Pointer _VarName, + >('cgetmatch'); + late final _cgetmatch = _cgetmatchPtr + .asFunction, ffi.Pointer)>(); + + int cgetnext( + ffi.Pointer> arg0, + ffi.Pointer> arg1, ) { - return __wgetenv_s(_RequiredCount, _Buffer, _BufferCount, _VarName); + return _cgetnext(arg0, arg1); } - late final __wgetenv_sPtr = + late final _cgetnextPtr = _lookup< ffi.NativeFunction< - errno_t Function( - ffi.Pointer, - ffi.Pointer, - ffi.Size, - ffi.Pointer, + ffi.Int Function( + ffi.Pointer>, + ffi.Pointer>, ) > - >('_wgetenv_s'); - late final __wgetenv_s = __wgetenv_sPtr + >('cgetnext'); + late final _cgetnext = _cgetnextPtr .asFunction< int Function( - ffi.Pointer, - ffi.Pointer, - int, - ffi.Pointer, + ffi.Pointer>, + ffi.Pointer>, ) >(); - int _wputenv(ffi.Pointer _EnvString) { - return __wputenv(_EnvString); - } - - late final __wputenvPtr = - _lookup)>>( - '_wputenv', - ); - late final __wputenv = __wputenvPtr - .asFunction)>(); - - int _wputenv_s(ffi.Pointer _Name, ffi.Pointer _Value) { - return __wputenv_s(_Name, _Value); + int cgetnum( + ffi.Pointer arg0, + ffi.Pointer arg1, + ffi.Pointer arg2, + ) { + return _cgetnum(arg0, arg1, arg2); } - late final __wputenv_sPtr = + late final _cgetnumPtr = _lookup< ffi.NativeFunction< - errno_t Function(ffi.Pointer, ffi.Pointer) + ffi.Int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ) > - >('_wputenv_s'); - late final __wputenv_s = __wputenv_sPtr + >('cgetnum'); + late final _cgetnum = _cgetnumPtr .asFunction< - int Function(ffi.Pointer, ffi.Pointer) + int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ) >(); - int _wsearchenv_s( - ffi.Pointer _Filename, - ffi.Pointer _VarName, - ffi.Pointer _Buffer, - int _BufferCount, + int cgetset(ffi.Pointer arg0) { + return _cgetset(arg0); + } + + late final _cgetsetPtr = + _lookup)>>( + 'cgetset', + ); + late final _cgetset = _cgetsetPtr + .asFunction)>(); + + int cgetstr( + ffi.Pointer arg0, + ffi.Pointer arg1, + ffi.Pointer> arg2, ) { - return __wsearchenv_s(_Filename, _VarName, _Buffer, _BufferCount); + return _cgetstr(arg0, arg1, arg2); } - late final __wsearchenv_sPtr = + late final _cgetstrPtr = _lookup< ffi.NativeFunction< - errno_t Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Size, + ffi.Int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>, ) > - >('_wsearchenv_s'); - late final __wsearchenv_s = __wsearchenv_sPtr + >('cgetstr'); + late final _cgetstr = _cgetstrPtr .asFunction< int Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - int, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>, ) >(); - void _wsearchenv( - ffi.Pointer _Filename, - ffi.Pointer _VarName, - ffi.Pointer _ResultPath, + int cgetustr( + ffi.Pointer arg0, + ffi.Pointer arg1, + ffi.Pointer> arg2, ) { - return __wsearchenv(_Filename, _VarName, _ResultPath); + return _cgetustr(arg0, arg1, arg2); } - late final __wsearchenvPtr = + late final _cgetustrPtr = _lookup< ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, + ffi.Int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>, ) > - >('_wsearchenv'); - late final __wsearchenv = __wsearchenvPtr + >('cgetustr'); + late final _cgetustr = _cgetustrPtr .asFunction< - void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, + int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>, ) >(); - int _wsystem(ffi.Pointer _Command) { - return __wsystem(_Command); + int daemon(int arg0, int arg1) { + return _daemon(arg0, arg1); } - late final __wsystemPtr = - _lookup)>>( - '_wsystem', - ); - late final __wsystem = __wsystemPtr - .asFunction)>(); + late final _daemonPtr = + _lookup>('daemon'); + late final _daemon = _daemonPtr.asFunction(); + + ffi.Pointer devname(int arg0, int arg1) { + return _devname(arg0, arg1); + } + + late final _devnamePtr = + _lookup< + ffi.NativeFunction Function(dev_t, mode_t)> + >('devname'); + late final _devname = _devnamePtr + .asFunction Function(int, int)>(); - void _swab( - ffi.Pointer _Buf1, - ffi.Pointer _Buf2, - int _SizeInBytes, + ffi.Pointer devname_r( + int arg0, + int arg1, + ffi.Pointer buf, + int len, ) { - return __swab(_Buf1, _Buf2, _SizeInBytes); + return _devname_r(arg0, arg1, buf, len); } - late final __swabPtr = + late final _devname_rPtr = _lookup< ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, + ffi.Pointer Function( + dev_t, + mode_t, ffi.Pointer, ffi.Int, ) > - >('_swab'); - late final __swab = __swabPtr + >('devname_r'); + late final _devname_r = _devname_rPtr .asFunction< - void Function(ffi.Pointer, ffi.Pointer, int) + ffi.Pointer Function(int, int, ffi.Pointer, int) >(); - void exit(int _Code) { - return _exit$1(_Code); + ffi.Pointer getbsize( + ffi.Pointer arg0, + ffi.Pointer arg1, + ) { + return _getbsize(arg0, arg1); } - late final _exitPtr = _lookup>( - 'exit', - ); - late final _exit$1 = _exitPtr.asFunction(); + late final _getbsizePtr = + _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ) + > + >('getbsize'); + late final _getbsize = _getbsizePtr + .asFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ) + >(); - void _exit(int _Code) { - return __exit(_Code); + int getloadavg(ffi.Pointer arg0, int __nelem) { + return _getloadavg(arg0, __nelem); } - late final __exitPtr = - _lookup>('_exit'); - late final __exit = __exitPtr.asFunction(); + late final _getloadavgPtr = + _lookup< + ffi.NativeFunction, ffi.Int)> + >('getloadavg'); + late final _getloadavg = _getloadavgPtr + .asFunction, int)>(); - void _Exit(int _Code) { - return __Exit(_Code); + ffi.Pointer getprogname() { + return _getprogname(); } - late final __ExitPtr = - _lookup>('_Exit'); - late final __Exit = __ExitPtr.asFunction(); - - void quick_exit(int _Code) { - return _quick_exit(_Code); - } - - late final _quick_exitPtr = - _lookup>('quick_exit'); - late final _quick_exit = _quick_exitPtr.asFunction(); + late final _getprognamePtr = + _lookup Function()>>( + 'getprogname', + ); + late final _getprogname = _getprognamePtr + .asFunction Function()>(); - void abort() { - return _abort(); + void setprogname(ffi.Pointer arg0) { + return _setprogname(arg0); } - late final _abortPtr = _lookup>( - 'abort', - ); - late final _abort = _abortPtr.asFunction(); + late final _setprognamePtr = + _lookup)>>( + 'setprogname', + ); + late final _setprogname = _setprognamePtr + .asFunction)>(); - int _set_abort_behavior(int _Flags, int _Mask) { - return __set_abort_behavior(_Flags, _Mask); + int heapsort( + ffi.Pointer __base, + int __nel, + int __width, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function(ffi.Pointer, ffi.Pointer) + > + > + __compar, + ) { + return _heapsort(__base, __nel, __width, __compar); } - late final __set_abort_behaviorPtr = + late final _heapsortPtr = _lookup< ffi.NativeFunction< - ffi.UnsignedInt Function(ffi.UnsignedInt, ffi.UnsignedInt) + ffi.Int Function( + ffi.Pointer, + ffi.Size, + ffi.Size, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function(ffi.Pointer, ffi.Pointer) + > + >, + ) > - >('_set_abort_behavior'); - late final __set_abort_behavior = __set_abort_behaviorPtr - .asFunction(); + >('heapsort'); + late final _heapsort = _heapsortPtr + .asFunction< + int Function( + ffi.Pointer, + int, + int, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function(ffi.Pointer, ffi.Pointer) + > + >, + ) + >(); - int atexit(ffi.Pointer> arg0) { - return _atexit(arg0); + int mergesort( + ffi.Pointer __base, + int __nel, + int __width, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function(ffi.Pointer, ffi.Pointer) + > + > + __compar, + ) { + return _mergesort(__base, __nel, __width, __compar); } - late final _atexitPtr = + late final _mergesortPtr = _lookup< ffi.NativeFunction< - ffi.Int Function(ffi.Pointer>) + ffi.Int Function( + ffi.Pointer, + ffi.Size, + ffi.Size, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function(ffi.Pointer, ffi.Pointer) + > + >, + ) > - >('atexit'); - late final _atexit = _atexitPtr + >('mergesort'); + late final _mergesort = _mergesortPtr .asFunction< - int Function(ffi.Pointer>) + int Function( + ffi.Pointer, + int, + int, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function(ffi.Pointer, ffi.Pointer) + > + >, + ) >(); - _onexit_t _onexit(_onexit_t _Func) { - return __onexit(_Func); - } - - late final __onexitPtr = - _lookup>('_onexit'); - late final __onexit = __onexitPtr.asFunction<_onexit_t Function(_onexit_t)>(); - - int at_quick_exit(ffi.Pointer> arg0) { - return _at_quick_exit(arg0); + void psort( + ffi.Pointer __base, + int __nel, + int __width, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function(ffi.Pointer, ffi.Pointer) + > + > + __compar, + ) { + return _psort(__base, __nel, __width, __compar); } - late final _at_quick_exitPtr = + late final _psortPtr = _lookup< ffi.NativeFunction< - ffi.Int Function(ffi.Pointer>) + ffi.Void Function( + ffi.Pointer, + ffi.Size, + ffi.Size, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function(ffi.Pointer, ffi.Pointer) + > + >, + ) > - >('at_quick_exit'); - late final _at_quick_exit = _at_quick_exitPtr + >('psort'); + late final _psort = _psortPtr .asFunction< - int Function(ffi.Pointer>) + void Function( + ffi.Pointer, + int, + int, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function(ffi.Pointer, ffi.Pointer) + > + >, + ) >(); - _purecall_handler _set_purecall_handler(_purecall_handler _Handler) { - return __set_purecall_handler(_Handler); + void psort_r( + ffi.Pointer __base, + int __nel, + int __width, + ffi.Pointer arg3, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ) + > + > + __compar, + ) { + return _psort_r(__base, __nel, __width, arg3, __compar); } - late final __set_purecall_handlerPtr = + late final _psort_rPtr = _lookup< - ffi.NativeFunction<_purecall_handler Function(_purecall_handler)> - >('_set_purecall_handler'); - late final __set_purecall_handler = __set_purecall_handlerPtr - .asFunction<_purecall_handler Function(_purecall_handler)>(); - - _purecall_handler _get_purecall_handler() { - return __get_purecall_handler(); - } - - late final __get_purecall_handlerPtr = - _lookup>( - '_get_purecall_handler', - ); - late final __get_purecall_handler = __get_purecall_handlerPtr - .asFunction<_purecall_handler Function()>(); + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + ffi.Size, + ffi.Size, + ffi.Pointer, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ) + > + >, + ) + > + >('psort_r'); + late final _psort_r = _psort_rPtr + .asFunction< + void Function( + ffi.Pointer, + int, + int, + ffi.Pointer, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ) + > + >, + ) + >(); - _invalid_parameter_handler _set_invalid_parameter_handler( - _invalid_parameter_handler _Handler, + void qsort_r( + ffi.Pointer __base, + int __nel, + int __width, + ffi.Pointer arg3, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ) + > + > + __compar, ) { - return __set_invalid_parameter_handler(_Handler); + return _qsort_r(__base, __nel, __width, arg3, __compar); } - late final __set_invalid_parameter_handlerPtr = + late final _qsort_rPtr = _lookup< ffi.NativeFunction< - _invalid_parameter_handler Function(_invalid_parameter_handler) + ffi.Void Function( + ffi.Pointer, + ffi.Size, + ffi.Size, + ffi.Pointer, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ) + > + >, + ) > - >('_set_invalid_parameter_handler'); - late final __set_invalid_parameter_handler = - __set_invalid_parameter_handlerPtr - .asFunction< - _invalid_parameter_handler Function(_invalid_parameter_handler) - >(); - - _invalid_parameter_handler _get_invalid_parameter_handler() { - return __get_invalid_parameter_handler(); - } - - late final __get_invalid_parameter_handlerPtr = - _lookup>( - '_get_invalid_parameter_handler', - ); - late final __get_invalid_parameter_handler = - __get_invalid_parameter_handlerPtr - .asFunction<_invalid_parameter_handler Function()>(); + >('qsort_r'); + late final _qsort_r = _qsort_rPtr + .asFunction< + void Function( + ffi.Pointer, + int, + int, + ffi.Pointer, + ffi.Pointer< + ffi.NativeFunction< + ffi.Int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ) + > + >, + ) + >(); - _invalid_parameter_handler _set_thread_local_invalid_parameter_handler( - _invalid_parameter_handler _Handler, + int radixsort( + ffi.Pointer> __base, + int __nel, + ffi.Pointer __table, + int __endbyte, ) { - return __set_thread_local_invalid_parameter_handler(_Handler); + return _radixsort(__base, __nel, __table, __endbyte); } - late final __set_thread_local_invalid_parameter_handlerPtr = + late final _radixsortPtr = _lookup< ffi.NativeFunction< - _invalid_parameter_handler Function(_invalid_parameter_handler) + ffi.Int Function( + ffi.Pointer>, + ffi.Int, + ffi.Pointer, + ffi.UnsignedInt, + ) > - >('_set_thread_local_invalid_parameter_handler'); - late final __set_thread_local_invalid_parameter_handler = - __set_thread_local_invalid_parameter_handlerPtr - .asFunction< - _invalid_parameter_handler Function(_invalid_parameter_handler) - >(); - - _invalid_parameter_handler _get_thread_local_invalid_parameter_handler() { - return __get_thread_local_invalid_parameter_handler(); - } - - late final __get_thread_local_invalid_parameter_handlerPtr = - _lookup>( - '_get_thread_local_invalid_parameter_handler', - ); - late final __get_thread_local_invalid_parameter_handler = - __get_thread_local_invalid_parameter_handlerPtr - .asFunction<_invalid_parameter_handler Function()>(); - - int _set_error_mode(int _Mode) { - return __set_error_mode(_Mode); - } - - late final __set_error_modePtr = - _lookup>('_set_error_mode'); - late final __set_error_mode = __set_error_modePtr - .asFunction(); - - ffi.Pointer __doserrno() { - return ___doserrno(); - } - - late final ___doserrnoPtr = - _lookup Function()>>( - '__doserrno', - ); - late final ___doserrno = ___doserrnoPtr - .asFunction Function()>(); + >('radixsort'); + late final _radixsort = _radixsortPtr + .asFunction< + int Function( + ffi.Pointer>, + int, + ffi.Pointer, + int, + ) + >(); - int _set_doserrno(int _Value) { - return __set_doserrno(_Value); + int rpmatch(ffi.Pointer arg0) { + return _rpmatch(arg0); } - late final __set_doserrnoPtr = - _lookup>( - '_set_doserrno', + late final _rpmatchPtr = + _lookup)>>( + 'rpmatch', ); - late final __set_doserrno = __set_doserrnoPtr.asFunction(); - - int _get_doserrno(ffi.Pointer _Value) { - return __get_doserrno(_Value); - } - - late final __get_doserrnoPtr = - _lookup< - ffi.NativeFunction)> - >('_get_doserrno'); - late final __get_doserrno = __get_doserrnoPtr - .asFunction)>(); + late final _rpmatch = _rpmatchPtr + .asFunction)>(); - ffi.Pointer> __sys_errlist() { - return ___sys_errlist(); + int sradixsort( + ffi.Pointer> __base, + int __nel, + ffi.Pointer __table, + int __endbyte, + ) { + return _sradixsort(__base, __nel, __table, __endbyte); } - late final ___sys_errlistPtr = + late final _sradixsortPtr = _lookup< - ffi.NativeFunction> Function()> - >('__sys_errlist'); - late final ___sys_errlist = ___sys_errlistPtr - .asFunction> Function()>(); - - ffi.Pointer __sys_nerr() { - return ___sys_nerr(); - } - - late final ___sys_nerrPtr = - _lookup Function()>>( - '__sys_nerr', - ); - late final ___sys_nerr = ___sys_nerrPtr - .asFunction Function()>(); + ffi.NativeFunction< + ffi.Int Function( + ffi.Pointer>, + ffi.Int, + ffi.Pointer, + ffi.UnsignedInt, + ) + > + >('sradixsort'); + late final _sradixsort = _sradixsortPtr + .asFunction< + int Function( + ffi.Pointer>, + int, + ffi.Pointer, + int, + ) + >(); - void perror(ffi.Pointer _ErrMsg) { - return _perror(_ErrMsg); + void sranddev() { + return _sranddev(); } - late final _perrorPtr = - _lookup)>>( - 'perror', - ); - late final _perror = _perrorPtr - .asFunction)>(); + late final _sranddevPtr = _lookup>( + 'sranddev', + ); + late final _sranddev = _sranddevPtr.asFunction(); - ffi.Pointer> __p__pgmptr() { - return ___p__pgmptr(); + void srandomdev() { + return _srandomdev(); } - late final ___p__pgmptrPtr = - _lookup< - ffi.NativeFunction> Function()> - >('__p__pgmptr'); - late final ___p__pgmptr = ___p__pgmptrPtr - .asFunction> Function()>(); + late final _srandomdevPtr = _lookup>( + 'srandomdev', + ); + late final _srandomdev = _srandomdevPtr.asFunction(); - ffi.Pointer> __p__wpgmptr() { - return ___p__wpgmptr(); + int strtonum( + ffi.Pointer __numstr, + int __minval, + int __maxval, + ffi.Pointer> __errstrp, + ) { + return _strtonum(__numstr, __minval, __maxval, __errstrp); } - late final ___p__wpgmptrPtr = + late final _strtonumPtr = _lookup< - ffi.NativeFunction> Function()> - >('__p__wpgmptr'); - late final ___p__wpgmptr = ___p__wpgmptrPtr - .asFunction> Function()>(); - - ffi.Pointer __p__fmode() { - return ___p__fmode(); - } - - late final ___p__fmodePtr = - _lookup Function()>>( - '__p__fmode', - ); - late final ___p__fmode = ___p__fmodePtr - .asFunction Function()>(); + ffi.NativeFunction< + ffi.LongLong Function( + ffi.Pointer, + ffi.LongLong, + ffi.LongLong, + ffi.Pointer>, + ) + > + >('strtonum'); + late final _strtonum = _strtonumPtr + .asFunction< + int Function( + ffi.Pointer, + int, + int, + ffi.Pointer>, + ) + >(); - int _get_pgmptr(ffi.Pointer> _Value) { - return __get_pgmptr(_Value); + int strtoq( + ffi.Pointer __str, + ffi.Pointer> __endptr, + int __base, + ) { + return _strtoq(__str, __endptr, __base); } - late final __get_pgmptrPtr = + late final _strtoqPtr = _lookup< - ffi.NativeFunction>)> - >('_get_pgmptr'); - late final __get_pgmptr = __get_pgmptrPtr - .asFunction>)>(); + ffi.NativeFunction< + ffi.LongLong Function( + ffi.Pointer, + ffi.Pointer>, + ffi.Int, + ) + > + >('strtoq'); + late final _strtoq = _strtoqPtr + .asFunction< + int Function( + ffi.Pointer, + ffi.Pointer>, + int, + ) + >(); - int _get_wpgmptr(ffi.Pointer> _Value) { - return __get_wpgmptr(_Value); + int strtouq( + ffi.Pointer __str, + ffi.Pointer> __endptr, + int __base, + ) { + return _strtouq(__str, __endptr, __base); } - late final __get_wpgmptrPtr = + late final _strtouqPtr = _lookup< ffi.NativeFunction< - errno_t Function(ffi.Pointer>) + ffi.UnsignedLongLong Function( + ffi.Pointer, + ffi.Pointer>, + ffi.Int, + ) > - >('_get_wpgmptr'); - late final __get_wpgmptr = __get_wpgmptrPtr - .asFunction>)>(); - - int _set_fmode(int _Mode) { - return __set_fmode(_Mode); - } - - late final __set_fmodePtr = - _lookup>('_set_fmode'); - late final __set_fmode = __set_fmodePtr.asFunction(); - - int _get_fmode(ffi.Pointer _PMode) { - return __get_fmode(_PMode); - } - - late final __get_fmodePtr = - _lookup)>>( - '_get_fmode', - ); - late final __get_fmode = __get_fmodePtr - .asFunction)>(); - - int abs(int _Number) { - return _abs(_Number); - } - - late final _absPtr = _lookup>( - 'abs', - ); - late final _abs = _absPtr.asFunction(); - - int labs(int _Number) { - return _labs(_Number); - } + >('strtouq'); + late final _strtouq = _strtouqPtr + .asFunction< + int Function( + ffi.Pointer, + ffi.Pointer>, + int, + ) + >(); - late final _labsPtr = - _lookup>('labs'); - late final _labs = _labsPtr.asFunction(); + late final ffi.Pointer> _suboptarg = + _lookup>('suboptarg'); - int llabs(int _Number) { - return _llabs(_Number); - } + ffi.Pointer get suboptarg => _suboptarg.value; - late final _llabsPtr = - _lookup>('llabs'); - late final _llabs = _llabsPtr.asFunction(); + set suboptarg(ffi.Pointer value) => _suboptarg.value = value; - int _abs64(int _Number) { - return __abs64(_Number); + ffi.Pointer getAppDataDir() { + return _getAppDataDir(); } - late final __abs64Ptr = - _lookup>( - '_abs64', + late final _getAppDataDirPtr = + _lookup Function()>>( + 'getAppDataDir', ); - late final __abs64 = __abs64Ptr.asFunction(); + late final _getAppDataDir = _getAppDataDirPtr + .asFunction Function()>(); - int _byteswap_ushort(int _Number) { - return __byteswap_ushort(_Number); + ffi.Pointer setup( + ffi.Pointer _logDir, + ffi.Pointer _dataDir, + ffi.Pointer _locale, + ffi.Pointer _env, + int logP, + int appsP, + int statusP, + int privateServerP, + int appEventP, + int consent, + ffi.Pointer api, + ) { + return _setup( + _logDir, + _dataDir, + _locale, + _env, + logP, + appsP, + statusP, + privateServerP, + appEventP, + consent, + api, + ); } - late final __byteswap_ushortPtr = + late final _setupPtr = _lookup< - ffi.NativeFunction - >('_byteswap_ushort'); - late final __byteswap_ushort = __byteswap_ushortPtr - .asFunction(); + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Int64, + ffi.Int64, + ffi.Int64, + ffi.Int64, + ffi.Int64, + ffi.Int, + ffi.Pointer, + ) + > + >('setup'); + late final _setup = _setupPtr + .asFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + int, + int, + int, + int, + int, + int, + ffi.Pointer, + ) + >(); - int _byteswap_ulong(int _Number) { - return __byteswap_ulong(_Number); + ffi.Pointer updateTelemetryConsent(int consent) { + return _updateTelemetryConsent(consent); } - late final __byteswap_ulongPtr = - _lookup>( - '_byteswap_ulong', + late final _updateTelemetryConsentPtr = + _lookup Function(ffi.Int)>>( + 'updateTelemetryConsent', ); - late final __byteswap_ulong = __byteswap_ulongPtr - .asFunction(); - - int _byteswap_uint64(int _Number) { - return __byteswap_uint64(_Number); - } - - late final __byteswap_uint64Ptr = - _lookup< - ffi.NativeFunction - >('_byteswap_uint64'); - late final __byteswap_uint64 = __byteswap_uint64Ptr - .asFunction(); + late final _updateTelemetryConsent = _updateTelemetryConsentPtr + .asFunction Function(int)>(); - div_t div(int _Numerator, int _Denominator) { - return _div(_Numerator, _Denominator); + int isTelemetryEnabled() { + return _isTelemetryEnabled(); } - late final _divPtr = - _lookup>('div'); - late final _div = _divPtr.asFunction(); + late final _isTelemetryEnabledPtr = + _lookup>('isTelemetryEnabled'); + late final _isTelemetryEnabled = _isTelemetryEnabledPtr + .asFunction(); - ldiv_t ldiv(int _Numerator, int _Denominator) { - return _ldiv(_Numerator, _Denominator); + int isOAuthLogin() { + return _isOAuthLogin(); } - late final _ldivPtr = - _lookup>('ldiv'); - late final _ldiv = _ldivPtr.asFunction(); + late final _isOAuthLoginPtr = _lookup>( + 'isOAuthLogin', + ); + late final _isOAuthLogin = _isOAuthLoginPtr.asFunction(); - lldiv_t lldiv(int _Numerator, int _Denominator) { - return _lldiv(_Numerator, _Denominator); + ffi.Pointer getOAuthProvider() { + return _getOAuthProvider(); } - late final _lldivPtr = - _lookup>( - 'lldiv', + late final _getOAuthProviderPtr = + _lookup Function()>>( + 'getOAuthProvider', ); - late final _lldiv = _lldivPtr.asFunction(); - - int _rotl(int _Value, int _Shift) { - return __rotl(_Value, _Shift); - } - - late final __rotlPtr = - _lookup< - ffi.NativeFunction - >('_rotl'); - late final __rotl = __rotlPtr.asFunction(); + late final _getOAuthProvider = _getOAuthProviderPtr + .asFunction Function()>(); - int _lrotl(int _Value, int _Shift) { - return __lrotl(_Value, _Shift); + ffi.Pointer availableFeatures() { + return _availableFeatures(); } - late final __lrotlPtr = - _lookup< - ffi.NativeFunction - >('_lrotl'); - late final __lrotl = __lrotlPtr.asFunction(); + late final _availableFeaturesPtr = + _lookup Function()>>( + 'availableFeatures', + ); + late final _availableFeatures = _availableFeaturesPtr + .asFunction Function()>(); - int _rotl64(int _Value, int _Shift) { - return __rotl64(_Value, _Shift); + ffi.Pointer updateLocale(ffi.Pointer _locale) { + return _updateLocale(_locale); } - late final __rotl64Ptr = + late final _updateLocalePtr = _lookup< ffi.NativeFunction< - ffi.UnsignedLongLong Function(ffi.UnsignedLongLong, ffi.Int) + ffi.Pointer Function(ffi.Pointer) > - >('_rotl64'); - late final __rotl64 = __rotl64Ptr.asFunction(); - - int _rotr(int _Value, int _Shift) { - return __rotr(_Value, _Shift); - } - - late final __rotrPtr = - _lookup< - ffi.NativeFunction - >('_rotr'); - late final __rotr = __rotrPtr.asFunction(); + >('updateLocale'); + late final _updateLocale = _updateLocalePtr + .asFunction Function(ffi.Pointer)>(); - int _lrotr(int _Value, int _Shift) { - return __lrotr(_Value, _Shift); + ffi.Pointer addSplitTunnelItem( + ffi.Pointer filterTypeC, + ffi.Pointer itemC, + ) { + return _addSplitTunnelItem(filterTypeC, itemC); } - late final __lrotrPtr = + late final _addSplitTunnelItemPtr = _lookup< - ffi.NativeFunction - >('_lrotr'); - late final __lrotr = __lrotrPtr.asFunction(); + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ) + > + >('addSplitTunnelItem'); + late final _addSplitTunnelItem = _addSplitTunnelItemPtr + .asFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ) + >(); - int _rotr64(int _Value, int _Shift) { - return __rotr64(_Value, _Shift); + ffi.Pointer removeSplitTunnelItem( + ffi.Pointer filterTypeC, + ffi.Pointer itemC, + ) { + return _removeSplitTunnelItem(filterTypeC, itemC); } - late final __rotr64Ptr = + late final _removeSplitTunnelItemPtr = _lookup< ffi.NativeFunction< - ffi.UnsignedLongLong Function(ffi.UnsignedLongLong, ffi.Int) + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ) > - >('_rotr64'); - late final __rotr64 = __rotr64Ptr.asFunction(); - - void srand(int _Seed) { - return _srand(_Seed); - } - - late final _srandPtr = - _lookup>('srand'); - late final _srand = _srandPtr.asFunction(); - - int rand() { - return _rand(); - } - - late final _randPtr = _lookup>('rand'); - late final _rand = _randPtr.asFunction(); - - double atof(ffi.Pointer _String) { - return _atof(_String); - } - - late final _atofPtr = - _lookup)>>( - 'atof', - ); - late final _atof = _atofPtr - .asFunction)>(); - - int atoi(ffi.Pointer _String) { - return _atoi(_String); - } - - late final _atoiPtr = - _lookup)>>( - 'atoi', - ); - late final _atoi = _atoiPtr.asFunction)>(); - - int atol(ffi.Pointer _String) { - return _atol(_String); - } - - late final _atolPtr = - _lookup)>>( - 'atol', - ); - late final _atol = _atolPtr.asFunction)>(); + >('removeSplitTunnelItem'); + late final _removeSplitTunnelItem = _removeSplitTunnelItemPtr + .asFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ) + >(); - int atoll(ffi.Pointer _String) { - return _atoll(_String); + ffi.Pointer setSplitTunnelingEnabled(int enabled) { + return _setSplitTunnelingEnabled(enabled); } - late final _atollPtr = - _lookup)>>( - 'atoll', + late final _setSplitTunnelingEnabledPtr = + _lookup Function(ffi.Int)>>( + 'setSplitTunnelingEnabled', ); - late final _atoll = _atollPtr - .asFunction)>(); + late final _setSplitTunnelingEnabled = _setSplitTunnelingEnabledPtr + .asFunction Function(int)>(); - int _atoi64(ffi.Pointer _String) { - return __atoi64(_String); + int isSplitTunnelingEnabled() { + return _isSplitTunnelingEnabled(); } - late final __atoi64Ptr = - _lookup)>>( - '_atoi64', + late final _isSplitTunnelingEnabledPtr = + _lookup>( + 'isSplitTunnelingEnabled', ); - late final __atoi64 = __atoi64Ptr - .asFunction)>(); + late final _isSplitTunnelingEnabled = _isSplitTunnelingEnabledPtr + .asFunction(); - double _atof_l(ffi.Pointer _String, _locale_t _Locale) { - return __atof_l(_String, _Locale); + ffi.Pointer loadInstalledApps(ffi.Pointer dataDir) { + return _loadInstalledApps(dataDir); } - late final __atof_lPtr = + late final _loadInstalledAppsPtr = _lookup< ffi.NativeFunction< - ffi.Double Function(ffi.Pointer, _locale_t) + ffi.Pointer Function(ffi.Pointer) > - >('_atof_l'); - late final __atof_l = __atof_lPtr - .asFunction, _locale_t)>(); - - int _atoi_l(ffi.Pointer _String, _locale_t _Locale) { - return __atoi_l(_String, _Locale); - } - - late final __atoi_lPtr = - _lookup< - ffi.NativeFunction, _locale_t)> - >('_atoi_l'); - late final __atoi_l = __atoi_lPtr - .asFunction, _locale_t)>(); - - int _atol_l(ffi.Pointer _String, _locale_t _Locale) { - return __atol_l(_String, _Locale); - } - - late final __atol_lPtr = - _lookup< - ffi.NativeFunction, _locale_t)> - >('_atol_l'); - late final __atol_l = __atol_lPtr - .asFunction, _locale_t)>(); + >('loadInstalledApps'); + late final _loadInstalledApps = _loadInstalledAppsPtr + .asFunction Function(ffi.Pointer)>(); - int _atoll_l(ffi.Pointer _String, _locale_t _Locale) { - return __atoll_l(_String, _Locale); + ffi.Pointer loadInstalledAppIcon( + ffi.Pointer appPathC, + ffi.Pointer iconPathC, + ) { + return _loadInstalledAppIcon(appPathC, iconPathC); } - late final __atoll_lPtr = + late final _loadInstalledAppIconPtr = _lookup< ffi.NativeFunction< - ffi.LongLong Function(ffi.Pointer, _locale_t) + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ) > - >('_atoll_l'); - late final __atoll_l = __atoll_lPtr - .asFunction, _locale_t)>(); + >('loadInstalledAppIcon'); + late final _loadInstalledAppIcon = _loadInstalledAppIconPtr + .asFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ) + >(); - int _atoi64_l(ffi.Pointer _String, _locale_t _Locale) { - return __atoi64_l(_String, _Locale); + ffi.Pointer getDataCapInfo() { + return _getDataCapInfo(); } - late final __atoi64_lPtr = - _lookup< - ffi.NativeFunction< - ffi.LongLong Function(ffi.Pointer, _locale_t) - > - >('_atoi64_l'); - late final __atoi64_l = __atoi64_lPtr - .asFunction, _locale_t)>(); + late final _getDataCapInfoPtr = + _lookup Function()>>( + 'getDataCapInfo', + ); + late final _getDataCapInfo = _getDataCapInfoPtr + .asFunction Function()>(); - int _atoflt(ffi.Pointer<_CRT_FLOAT> _Result, ffi.Pointer _String) { - return __atoflt(_Result, _String); + ffi.Pointer reportIssue( + ffi.Pointer emailC, + ffi.Pointer typeC, + ffi.Pointer descC, + ffi.Pointer deviceC, + ffi.Pointer modelC, + ffi.Pointer logPathC, + ffi.Pointer attachmentsJSONC, + ) { + return _reportIssue( + emailC, + typeC, + descC, + deviceC, + modelC, + logPathC, + attachmentsJSONC, + ); } - late final __atofltPtr = + late final _reportIssuePtr = _lookup< ffi.NativeFunction< - ffi.Int Function(ffi.Pointer<_CRT_FLOAT>, ffi.Pointer) + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ) > - >('_atoflt'); - late final __atoflt = __atofltPtr + >('reportIssue'); + late final _reportIssue = _reportIssuePtr .asFunction< - int Function(ffi.Pointer<_CRT_FLOAT>, ffi.Pointer) + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ) >(); - int _atodbl(ffi.Pointer<_CRT_DOUBLE> _Result, ffi.Pointer _String) { - return __atodbl(_Result, _String); + ffi.Pointer getSelectedServerJSON() { + return _getSelectedServerJSON(); } - late final __atodblPtr = - _lookup< - ffi.NativeFunction< - ffi.Int Function(ffi.Pointer<_CRT_DOUBLE>, ffi.Pointer) - > - >('_atodbl'); - late final __atodbl = __atodblPtr - .asFunction< - int Function(ffi.Pointer<_CRT_DOUBLE>, ffi.Pointer) - >(); + late final _getSelectedServerJSONPtr = + _lookup Function()>>( + 'getSelectedServerJSON', + ); + late final _getSelectedServerJSON = _getSelectedServerJSONPtr + .asFunction Function()>(); - int _atoldbl(ffi.Pointer<_LDOUBLE> _Result, ffi.Pointer _String) { - return __atoldbl(_Result, _String); + ffi.Pointer getAutoLocation() { + return _getAutoLocation(); } - late final __atoldblPtr = - _lookup< - ffi.NativeFunction< - ffi.Int Function(ffi.Pointer<_LDOUBLE>, ffi.Pointer) - > - >('_atoldbl'); - late final __atoldbl = __atoldblPtr - .asFunction, ffi.Pointer)>(); - - int _atoflt_l( - ffi.Pointer<_CRT_FLOAT> _Result, - ffi.Pointer _String, - _locale_t _Locale, - ) { - return __atoflt_l(_Result, _String, _Locale); + late final _getAutoLocationPtr = + _lookup Function()>>( + 'getAutoLocation', + ); + late final _getAutoLocation = _getAutoLocationPtr + .asFunction Function()>(); + + ffi.Pointer isTagAvailable(ffi.Pointer _tag) { + return _isTagAvailable(_tag); } - late final __atoflt_lPtr = + late final _isTagAvailablePtr = _lookup< ffi.NativeFunction< - ffi.Int Function( - ffi.Pointer<_CRT_FLOAT>, - ffi.Pointer, - _locale_t, - ) + ffi.Pointer Function(ffi.Pointer) > - >('_atoflt_l'); - late final __atoflt_l = __atoflt_lPtr - .asFunction< - int Function(ffi.Pointer<_CRT_FLOAT>, ffi.Pointer, _locale_t) - >(); + >('isTagAvailable'); + late final _isTagAvailable = _isTagAvailablePtr + .asFunction Function(ffi.Pointer)>(); - int _atodbl_l( - ffi.Pointer<_CRT_DOUBLE> _Result, - ffi.Pointer _String, - _locale_t _Locale, - ) { - return __atodbl_l(_Result, _String, _Locale); + ffi.Pointer getAvailableServers() { + return _getAvailableServers(); } - late final __atodbl_lPtr = - _lookup< - ffi.NativeFunction< - ffi.Int Function( - ffi.Pointer<_CRT_DOUBLE>, - ffi.Pointer, - _locale_t, - ) - > - >('_atodbl_l'); - late final __atodbl_l = __atodbl_lPtr - .asFunction< - int Function(ffi.Pointer<_CRT_DOUBLE>, ffi.Pointer, _locale_t) - >(); + late final _getAvailableServersPtr = + _lookup Function()>>( + 'getAvailableServers', + ); + late final _getAvailableServers = _getAvailableServersPtr + .asFunction Function()>(); - int _atoldbl_l( - ffi.Pointer<_LDOUBLE> _Result, - ffi.Pointer _String, - _locale_t _Locale, - ) { - return __atoldbl_l(_Result, _String, _Locale); + ffi.Pointer startVPN() { + return _startVPN(); } - late final __atoldbl_lPtr = - _lookup< - ffi.NativeFunction< - ffi.Int Function( - ffi.Pointer<_LDOUBLE>, - ffi.Pointer, - _locale_t, - ) - > - >('_atoldbl_l'); - late final __atoldbl_l = __atoldbl_lPtr - .asFunction< - int Function(ffi.Pointer<_LDOUBLE>, ffi.Pointer, _locale_t) - >(); + late final _startVPNPtr = + _lookup Function()>>('startVPN'); + late final _startVPN = _startVPNPtr + .asFunction Function()>(); - double strtof( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - ) { - return _strtof(_String, _EndPtr); + ffi.Pointer stopVPN() { + return _stopVPN(); } - late final _strtofPtr = - _lookup< - ffi.NativeFunction< - ffi.Float Function( - ffi.Pointer, - ffi.Pointer>, - ) - > - >('strtof'); - late final _strtof = _strtofPtr - .asFunction< - double Function( - ffi.Pointer, - ffi.Pointer>, - ) - >(); + late final _stopVPNPtr = + _lookup Function()>>('stopVPN'); + late final _stopVPN = _stopVPNPtr + .asFunction Function()>(); - double _strtof_l( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - _locale_t _Locale, - ) { - return __strtof_l(_String, _EndPtr, _Locale); + ffi.Pointer connectToServer(ffi.Pointer _tag) { + return _connectToServer(_tag); } - late final __strtof_lPtr = + late final _connectToServerPtr = _lookup< ffi.NativeFunction< - ffi.Float Function( - ffi.Pointer, - ffi.Pointer>, - _locale_t, - ) + ffi.Pointer Function(ffi.Pointer) > - >('_strtof_l'); - late final __strtof_l = __strtof_lPtr - .asFunction< - double Function( - ffi.Pointer, - ffi.Pointer>, - _locale_t, - ) - >(); + >('connectToServer'); + late final _connectToServer = _connectToServerPtr + .asFunction Function(ffi.Pointer)>(); - double strtod( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, + int isVPNConnected() { + return _isVPNConnected(); + } + + late final _isVPNConnectedPtr = + _lookup>('isVPNConnected'); + late final _isVPNConnected = _isVPNConnectedPtr.asFunction(); + + ffi.Pointer getUserData() { + return _getUserData(); + } + + late final _getUserDataPtr = + _lookup Function()>>( + 'getUserData', + ); + late final _getUserData = _getUserDataPtr + .asFunction Function()>(); + + ffi.Pointer fetchUserData() { + return _fetchUserData(); + } + + late final _fetchUserDataPtr = + _lookup Function()>>( + 'fetchUserData', + ); + late final _fetchUserData = _fetchUserDataPtr + .asFunction Function()>(); + + ffi.Pointer stripeSubscriptionPaymentRedirect( + ffi.Pointer subType, + ffi.Pointer _planId, + ffi.Pointer _email, + ffi.Pointer _idempotencyKey, ) { - return _strtod(_String, _EndPtr); + return _stripeSubscriptionPaymentRedirect( + subType, + _planId, + _email, + _idempotencyKey, + ); } - late final _strtodPtr = + late final _stripeSubscriptionPaymentRedirectPtr = _lookup< ffi.NativeFunction< - ffi.Double Function( + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, ffi.Pointer, - ffi.Pointer>, ) > - >('strtod'); - late final _strtod = _strtodPtr - .asFunction< - double Function( - ffi.Pointer, - ffi.Pointer>, - ) - >(); + >('stripeSubscriptionPaymentRedirect'); + late final _stripeSubscriptionPaymentRedirect = + _stripeSubscriptionPaymentRedirectPtr + .asFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ) + >(); - double _strtod_l( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - _locale_t _Locale, + ffi.Pointer paymentRedirect( + ffi.Pointer _plan, + ffi.Pointer _provider, + ffi.Pointer _email, + ffi.Pointer _idempotencyKey, ) { - return __strtod_l(_String, _EndPtr, _Locale); + return _paymentRedirect(_plan, _provider, _email, _idempotencyKey); } - late final __strtod_lPtr = + late final _paymentRedirectPtr = _lookup< ffi.NativeFunction< - ffi.Double Function( + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, ffi.Pointer, - ffi.Pointer>, - _locale_t, ) > - >('_strtod_l'); - late final __strtod_l = __strtod_lPtr + >('paymentRedirect'); + late final _paymentRedirect = _paymentRedirectPtr .asFunction< - double Function( + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, ffi.Pointer, - ffi.Pointer>, - _locale_t, ) >(); - int strtol( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, - ) { - return _strtol(_String, _EndPtr, _Radix); + ffi.Pointer stripeBillingPortalUrl() { + return _stripeBillingPortalUrl(); } - late final _strtolPtr = - _lookup< - ffi.NativeFunction< - ffi.Long Function( - ffi.Pointer, - ffi.Pointer>, - ffi.Int, - ) - > - >('strtol'); - late final _strtol = _strtolPtr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer>, - int, - ) - >(); + late final _stripeBillingPortalUrlPtr = + _lookup Function()>>( + 'stripeBillingPortalUrl', + ); + late final _stripeBillingPortalUrl = _stripeBillingPortalUrlPtr + .asFunction Function()>(); - int _strtol_l( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, - _locale_t _Locale, - ) { - return __strtol_l(_String, _EndPtr, _Radix, _Locale); + ffi.Pointer plans() { + return _plans(); + } + + late final _plansPtr = + _lookup Function()>>('plans'); + late final _plans = _plansPtr.asFunction Function()>(); + + ffi.Pointer oauthLoginUrl(ffi.Pointer _provider) { + return _oauthLoginUrl(_provider); } - late final __strtol_lPtr = + late final _oauthLoginUrlPtr = _lookup< ffi.NativeFunction< - ffi.Long Function( - ffi.Pointer, - ffi.Pointer>, - ffi.Int, - _locale_t, - ) + ffi.Pointer Function(ffi.Pointer) > - >('_strtol_l'); - late final __strtol_l = __strtol_lPtr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer>, - int, - _locale_t, - ) - >(); + >('oauthLoginUrl'); + late final _oauthLoginUrl = _oauthLoginUrlPtr + .asFunction Function(ffi.Pointer)>(); - int strtoll( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, - ) { - return _strtoll(_String, _EndPtr, _Radix); + ffi.Pointer oAuthLoginCallback(ffi.Pointer _oAuthToken) { + return _oAuthLoginCallback(_oAuthToken); } - late final _strtollPtr = + late final _oAuthLoginCallbackPtr = _lookup< ffi.NativeFunction< - ffi.LongLong Function( - ffi.Pointer, - ffi.Pointer>, - ffi.Int, - ) + ffi.Pointer Function(ffi.Pointer) > - >('strtoll'); - late final _strtoll = _strtollPtr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer>, - int, - ) - >(); + >('oAuthLoginCallback'); + late final _oAuthLoginCallback = _oAuthLoginCallbackPtr + .asFunction Function(ffi.Pointer)>(); - int _strtoll_l( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, - _locale_t _Locale, + ffi.Pointer login( + ffi.Pointer _email, + ffi.Pointer _password, ) { - return __strtoll_l(_String, _EndPtr, _Radix, _Locale); + return _login(_email, _password); } - late final __strtoll_lPtr = + late final _loginPtr = _lookup< ffi.NativeFunction< - ffi.LongLong Function( + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, - ffi.Pointer>, - ffi.Int, - _locale_t, ) > - >('_strtoll_l'); - late final __strtoll_l = __strtoll_lPtr + >('login'); + late final _login = _loginPtr .asFunction< - int Function( + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, - ffi.Pointer>, - int, - _locale_t, ) >(); - int strtoul( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, + ffi.Pointer signup( + ffi.Pointer _email, + ffi.Pointer _password, ) { - return _strtoul(_String, _EndPtr, _Radix); + return _signup(_email, _password); } - late final _strtoulPtr = + late final _signupPtr = _lookup< ffi.NativeFunction< - ffi.UnsignedLong Function( + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, - ffi.Pointer>, - ffi.Int, ) > - >('strtoul'); - late final _strtoul = _strtoulPtr + >('signup'); + late final _signup = _signupPtr .asFunction< - int Function( + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, - ffi.Pointer>, - int, ) >(); - int _strtoul_l( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, - _locale_t _Locale, - ) { - return __strtoul_l(_String, _EndPtr, _Radix, _Locale); + ffi.Pointer logout(ffi.Pointer _email) { + return _logout(_email); } - late final __strtoul_lPtr = + late final _logoutPtr = _lookup< ffi.NativeFunction< - ffi.UnsignedLong Function( - ffi.Pointer, - ffi.Pointer>, - ffi.Int, - _locale_t, - ) + ffi.Pointer Function(ffi.Pointer) > - >('_strtoul_l'); - late final __strtoul_l = __strtoul_lPtr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer>, - int, - _locale_t, - ) - >(); + >('logout'); + late final _logout = _logoutPtr + .asFunction Function(ffi.Pointer)>(); - int strtoull( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, - ) { - return _strtoull(_String, _EndPtr, _Radix); + ffi.Pointer startRecoveryByEmail(ffi.Pointer _email) { + return _startRecoveryByEmail(_email); } - late final _strtoullPtr = + late final _startRecoveryByEmailPtr = _lookup< ffi.NativeFunction< - ffi.UnsignedLongLong Function( - ffi.Pointer, - ffi.Pointer>, - ffi.Int, - ) + ffi.Pointer Function(ffi.Pointer) > - >('strtoull'); - late final _strtoull = _strtoullPtr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer>, - int, - ) - >(); + >('startRecoveryByEmail'); + late final _startRecoveryByEmail = _startRecoveryByEmailPtr + .asFunction Function(ffi.Pointer)>(); - int _strtoull_l( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, - _locale_t _Locale, + ffi.Pointer validateEmailRecoveryCode( + ffi.Pointer _email, + ffi.Pointer _code, ) { - return __strtoull_l(_String, _EndPtr, _Radix, _Locale); + return _validateEmailRecoveryCode(_email, _code); } - late final __strtoull_lPtr = + late final _validateEmailRecoveryCodePtr = _lookup< ffi.NativeFunction< - ffi.UnsignedLongLong Function( + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, - ffi.Pointer>, - ffi.Int, - _locale_t, ) > - >('_strtoull_l'); - late final __strtoull_l = __strtoull_lPtr + >('validateEmailRecoveryCode'); + late final _validateEmailRecoveryCode = _validateEmailRecoveryCodePtr .asFunction< - int Function( + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, - ffi.Pointer>, - int, - _locale_t, ) >(); - int _strtoi64( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, + ffi.Pointer completeRecoveryByEmail( + ffi.Pointer _email, + ffi.Pointer _newPassword, + ffi.Pointer _code, ) { - return __strtoi64(_String, _EndPtr, _Radix); + return _completeRecoveryByEmail(_email, _newPassword, _code); } - late final __strtoi64Ptr = + late final _completeRecoveryByEmailPtr = _lookup< ffi.NativeFunction< - ffi.LongLong Function( + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, ffi.Pointer, - ffi.Pointer>, - ffi.Int, ) > - >('_strtoi64'); - late final __strtoi64 = __strtoi64Ptr + >('completeRecoveryByEmail'); + late final _completeRecoveryByEmail = _completeRecoveryByEmailPtr .asFunction< - int Function( + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, ffi.Pointer, - ffi.Pointer>, - int, ) >(); - int _strtoi64_l( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, - _locale_t _Locale, - ) { - return __strtoi64_l(_String, _EndPtr, _Radix, _Locale); + ffi.Pointer removeDevice(ffi.Pointer deviceId) { + return _removeDevice(deviceId); } - late final __strtoi64_lPtr = + late final _removeDevicePtr = _lookup< ffi.NativeFunction< - ffi.LongLong Function( - ffi.Pointer, - ffi.Pointer>, - ffi.Int, - _locale_t, - ) + ffi.Pointer Function(ffi.Pointer) > - >('_strtoi64_l'); - late final __strtoi64_l = __strtoi64_lPtr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer>, - int, - _locale_t, - ) - >(); + >('removeDevice'); + late final _removeDevice = _removeDevicePtr + .asFunction Function(ffi.Pointer)>(); - int _strtoui64( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, + ffi.Pointer referralAttachment( + ffi.Pointer _referralCode, ) { - return __strtoui64(_String, _EndPtr, _Radix); + return _referralAttachment(_referralCode); } - late final __strtoui64Ptr = + late final _referralAttachmentPtr = _lookup< ffi.NativeFunction< - ffi.UnsignedLongLong Function( - ffi.Pointer, - ffi.Pointer>, - ffi.Int, - ) + ffi.Pointer Function(ffi.Pointer) > - >('_strtoui64'); - late final __strtoui64 = __strtoui64Ptr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer>, - int, - ) - >(); + >('referralAttachment'); + late final _referralAttachment = _referralAttachmentPtr + .asFunction Function(ffi.Pointer)>(); - int _strtoui64_l( - ffi.Pointer _String, - ffi.Pointer> _EndPtr, - int _Radix, - _locale_t _Locale, + ffi.Pointer startChangeEmail( + ffi.Pointer _newEmail, + ffi.Pointer _password, ) { - return __strtoui64_l(_String, _EndPtr, _Radix, _Locale); + return _startChangeEmail(_newEmail, _password); } - late final __strtoui64_lPtr = + late final _startChangeEmailPtr = _lookup< ffi.NativeFunction< - ffi.UnsignedLongLong Function( + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, - ffi.Pointer>, - ffi.Int, - _locale_t, ) > - >('_strtoui64_l'); - late final __strtoui64_l = __strtoui64_lPtr + >('startChangeEmail'); + late final _startChangeEmail = _startChangeEmailPtr .asFunction< - int Function( + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, - ffi.Pointer>, - int, - _locale_t, ) >(); - int _itoa_s( - int _Value, - ffi.Pointer _Buffer, - int _BufferCount, - int _Radix, + ffi.Pointer completeChangeEmail( + ffi.Pointer _newEmail, + ffi.Pointer _password, + ffi.Pointer _code, ) { - return __itoa_s(_Value, _Buffer, _BufferCount, _Radix); + return _completeChangeEmail(_newEmail, _password, _code); } - late final __itoa_sPtr = - _lookup< - ffi.NativeFunction< - errno_t Function(ffi.Int, ffi.Pointer, ffi.Size, ffi.Int) - > - >('_itoa_s'); - late final __itoa_s = __itoa_sPtr - .asFunction, int, int)>(); - - ffi.Pointer _itoa( - int _Value, - ffi.Pointer _Buffer, - int _Radix, - ) { - return __itoa(_Value, _Buffer, _Radix); - } - - late final __itoaPtr = + late final _completeChangeEmailPtr = _lookup< ffi.NativeFunction< ffi.Pointer Function( - ffi.Int, ffi.Pointer, - ffi.Int, + ffi.Pointer, + ffi.Pointer, ) > - >('_itoa'); - late final __itoa = __itoaPtr + >('completeChangeEmail'); + late final _completeChangeEmail = _completeChangeEmailPtr .asFunction< - ffi.Pointer Function(int, ffi.Pointer, int) + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ) >(); - int _ltoa_s( - int _Value, - ffi.Pointer _Buffer, - int _BufferCount, - int _Radix, - ) { - return __ltoa_s(_Value, _Buffer, _BufferCount, _Radix); - } - - late final __ltoa_sPtr = - _lookup< - ffi.NativeFunction< - errno_t Function(ffi.Long, ffi.Pointer, ffi.Size, ffi.Int) - > - >('_ltoa_s'); - late final __ltoa_s = __ltoa_sPtr - .asFunction, int, int)>(); - - ffi.Pointer _ltoa( - int _Value, - ffi.Pointer _Buffer, - int _Radix, + ffi.Pointer deleteAccount( + ffi.Pointer _email, + ffi.Pointer _password, ) { - return __ltoa(_Value, _Buffer, _Radix); + return _deleteAccount(_email, _password); } - late final __ltoaPtr = + late final _deleteAccountPtr = _lookup< ffi.NativeFunction< ffi.Pointer Function( - ffi.Long, ffi.Pointer, - ffi.Int, + ffi.Pointer, ) > - >('_ltoa'); - late final __ltoa = __ltoaPtr + >('deleteAccount'); + late final _deleteAccount = _deleteAccountPtr .asFunction< - ffi.Pointer Function(int, ffi.Pointer, int) + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ) >(); - int _ultoa_s( - int _Value, - ffi.Pointer _Buffer, - int _BufferCount, - int _Radix, - ) { - return __ultoa_s(_Value, _Buffer, _BufferCount, _Radix); - } - - late final __ultoa_sPtr = - _lookup< - ffi.NativeFunction< - errno_t Function( - ffi.UnsignedLong, - ffi.Pointer, - ffi.Size, - ffi.Int, - ) - > - >('_ultoa_s'); - late final __ultoa_s = __ultoa_sPtr - .asFunction, int, int)>(); - - ffi.Pointer _ultoa( - int _Value, - ffi.Pointer _Buffer, - int _Radix, + ffi.Pointer activationCode( + ffi.Pointer _email, + ffi.Pointer _resellerCode, ) { - return __ultoa(_Value, _Buffer, _Radix); + return _activationCode(_email, _resellerCode); } - late final __ultoaPtr = + late final _activationCodePtr = _lookup< ffi.NativeFunction< ffi.Pointer Function( - ffi.UnsignedLong, ffi.Pointer, - ffi.Int, + ffi.Pointer, ) > - >('_ultoa'); - late final __ultoa = __ultoaPtr + >('activationCode'); + late final _activationCode = _activationCodePtr .asFunction< - ffi.Pointer Function(int, ffi.Pointer, int) + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ) >(); - int _i64toa_s( - int _Value, - ffi.Pointer _Buffer, - int _BufferCount, - int _Radix, - ) { - return __i64toa_s(_Value, _Buffer, _BufferCount, _Radix); + void freeCString(ffi.Pointer cstr) { + return _freeCString(cstr); } - late final __i64toa_sPtr = + late final _freeCStringPtr = + _lookup)>>( + 'freeCString', + ); + late final _freeCString = _freeCStringPtr + .asFunction)>(); + + ffi.Pointer patchSettings(ffi.Pointer patchJSON) { + return _patchSettings(patchJSON); + } + + late final _patchSettingsPtr = _lookup< ffi.NativeFunction< - errno_t Function( - ffi.LongLong, - ffi.Pointer, - ffi.Size, - ffi.Int, - ) + ffi.Pointer Function(ffi.Pointer) > - >('_i64toa_s'); - late final __i64toa_s = __i64toa_sPtr - .asFunction, int, int)>(); - - ffi.Pointer _i64toa( - int _Value, - ffi.Pointer _Buffer, - int _Radix, - ) { - return __i64toa(_Value, _Buffer, _Radix); + >('patchSettings'); + late final _patchSettings = _patchSettingsPtr + .asFunction Function(ffi.Pointer)>(); + + ffi.Pointer getSettings() { + return _getSettings(); + } + + late final _getSettingsPtr = + _lookup Function()>>( + 'getSettings', + ); + late final _getSettings = _getSettingsPtr + .asFunction Function()>(); + + ffi.Pointer patchEnvVars(ffi.Pointer patchJSON) { + return _patchEnvVars(patchJSON); } - late final __i64toaPtr = + late final _patchEnvVarsPtr = _lookup< ffi.NativeFunction< - ffi.Pointer Function( - ffi.LongLong, - ffi.Pointer, - ffi.Int, - ) + ffi.Pointer Function(ffi.Pointer) > - >('_i64toa'); - late final __i64toa = __i64toaPtr - .asFunction< - ffi.Pointer Function(int, ffi.Pointer, int) - >(); + >('patchEnvVars'); + late final _patchEnvVars = _patchEnvVarsPtr + .asFunction Function(ffi.Pointer)>(); - int _ui64toa_s( - int _Value, - ffi.Pointer _Buffer, - int _BufferCount, - int _Radix, - ) { - return __ui64toa_s(_Value, _Buffer, _BufferCount, _Radix); + ffi.Pointer getEnvVars() { + return _getEnvVars(); + } + + late final _getEnvVarsPtr = + _lookup Function()>>( + 'getEnvVars', + ); + late final _getEnvVars = _getEnvVarsPtr + .asFunction Function()>(); + + ffi.Pointer runURLTests() { + return _runURLTests(); + } + + late final _runURLTestsPtr = + _lookup Function()>>( + 'runURLTests', + ); + late final _runURLTests = _runURLTestsPtr + .asFunction Function()>(); + + ffi.Pointer updateConfig() { + return _updateConfig(); + } + + late final _updateConfigPtr = + _lookup Function()>>( + 'updateConfig', + ); + late final _updateConfig = _updateConfigPtr + .asFunction Function()>(); + + ffi.Pointer digitalOceanPrivateServer() { + return _digitalOceanPrivateServer(); + } + + late final _digitalOceanPrivateServerPtr = + _lookup Function()>>( + 'digitalOceanPrivateServer', + ); + late final _digitalOceanPrivateServer = _digitalOceanPrivateServerPtr + .asFunction Function()>(); + + ffi.Pointer googleCloudPrivateServer() { + return _googleCloudPrivateServer(); } - late final __ui64toa_sPtr = + late final _googleCloudPrivateServerPtr = + _lookup Function()>>( + 'googleCloudPrivateServer', + ); + late final _googleCloudPrivateServer = _googleCloudPrivateServerPtr + .asFunction Function()>(); + + ffi.Pointer selectAccount(ffi.Pointer _account) { + return _selectAccount(_account); + } + + late final _selectAccountPtr = _lookup< ffi.NativeFunction< - errno_t Function( - ffi.UnsignedLongLong, - ffi.Pointer, - ffi.Size, - ffi.Int, - ) + ffi.Pointer Function(ffi.Pointer) > - >('_ui64toa_s'); - late final __ui64toa_s = __ui64toa_sPtr - .asFunction, int, int)>(); - - ffi.Pointer _ui64toa( - int _Value, - ffi.Pointer _Buffer, - int _Radix, - ) { - return __ui64toa(_Value, _Buffer, _Radix); + >('selectAccount'); + late final _selectAccount = _selectAccountPtr + .asFunction Function(ffi.Pointer)>(); + + ffi.Pointer selectProject(ffi.Pointer _project) { + return _selectProject(_project); } - late final __ui64toaPtr = + late final _selectProjectPtr = _lookup< ffi.NativeFunction< - ffi.Pointer Function( - ffi.UnsignedLongLong, - ffi.Pointer, - ffi.Int, - ) + ffi.Pointer Function(ffi.Pointer) > - >('_ui64toa'); - late final __ui64toa = __ui64toaPtr - .asFunction< - ffi.Pointer Function(int, ffi.Pointer, int) - >(); + >('selectProject'); + late final _selectProject = _selectProjectPtr + .asFunction Function(ffi.Pointer)>(); + + ffi.Pointer validateSession() { + return _validateSession(); + } - int _ecvt_s( - ffi.Pointer _Buffer, - int _BufferCount, - double _Value, - int _DigitCount, - ffi.Pointer _PtDec, - ffi.Pointer _PtSign, + late final _validateSessionPtr = + _lookup Function()>>( + 'validateSession', + ); + late final _validateSession = _validateSessionPtr + .asFunction Function()>(); + + ffi.Pointer startDepolyment( + ffi.Pointer _selectedLocation, + ffi.Pointer _serverName, ) { - return __ecvt_s( - _Buffer, - _BufferCount, - _Value, - _DigitCount, - _PtDec, - _PtSign, - ); + return _startDepolyment(_selectedLocation, _serverName); } - late final __ecvt_sPtr = + late final _startDepolymentPtr = _lookup< ffi.NativeFunction< - errno_t Function( + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, - ffi.Size, - ffi.Double, - ffi.Int, - ffi.Pointer, - ffi.Pointer, ) > - >('_ecvt_s'); - late final __ecvt_s = __ecvt_sPtr + >('startDepolyment'); + late final _startDepolyment = _startDepolymentPtr .asFunction< - int Function( + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, - int, - double, - int, - ffi.Pointer, - ffi.Pointer, ) >(); - ffi.Pointer _ecvt( - double _Value, - int _DigitCount, - ffi.Pointer _PtDec, - ffi.Pointer _PtSign, + ffi.Pointer cancelDepolyment() { + return _cancelDepolyment(); + } + + late final _cancelDepolymentPtr = + _lookup Function()>>( + 'cancelDepolyment', + ); + late final _cancelDepolyment = _cancelDepolymentPtr + .asFunction Function()>(); + + ffi.Pointer addServerManagerInstance( + ffi.Pointer _ip, + ffi.Pointer _port, + ffi.Pointer _accessToken, + ffi.Pointer _tag, ) { - return __ecvt(_Value, _DigitCount, _PtDec, _PtSign); + return _addServerManagerInstance(_ip, _port, _accessToken, _tag); } - late final __ecvtPtr = + late final _addServerManagerInstancePtr = _lookup< ffi.NativeFunction< ffi.Pointer Function( - ffi.Double, - ffi.Int, - ffi.Pointer, - ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, ) > - >('_ecvt'); - late final __ecvt = __ecvtPtr + >('addServerManagerInstance'); + late final _addServerManagerInstance = _addServerManagerInstancePtr .asFunction< ffi.Pointer Function( - double, - int, - ffi.Pointer, - ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, ) >(); - int _fcvt_s( - ffi.Pointer _Buffer, - int _BufferCount, - double _Value, - int _FractionalDigitCount, - ffi.Pointer _PtDec, - ffi.Pointer _PtSign, + ffi.Pointer inviteToServerManagerInstance( + ffi.Pointer _ip, + ffi.Pointer _port, + ffi.Pointer _accessToken, + ffi.Pointer _inviteName, ) { - return __fcvt_s( - _Buffer, - _BufferCount, - _Value, - _FractionalDigitCount, - _PtDec, - _PtSign, + return _inviteToServerManagerInstance( + _ip, + _port, + _accessToken, + _inviteName, ); } - late final __fcvt_sPtr = + late final _inviteToServerManagerInstancePtr = _lookup< ffi.NativeFunction< - errno_t Function( + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, ffi.Pointer, - ffi.Size, - ffi.Double, - ffi.Int, - ffi.Pointer, - ffi.Pointer, ) > - >('_fcvt_s'); - late final __fcvt_s = __fcvt_sPtr + >('inviteToServerManagerInstance'); + late final _inviteToServerManagerInstance = _inviteToServerManagerInstancePtr .asFunction< - int Function( + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, ffi.Pointer, - int, - double, - int, - ffi.Pointer, - ffi.Pointer, ) >(); - ffi.Pointer _fcvt( - double _Value, - int _FractionalDigitCount, - ffi.Pointer _PtDec, - ffi.Pointer _PtSign, + ffi.Pointer revokeServerManagerInvite( + ffi.Pointer _ip, + ffi.Pointer _port, + ffi.Pointer _accessToken, + ffi.Pointer _inviteName, ) { - return __fcvt(_Value, _FractionalDigitCount, _PtDec, _PtSign); + return _revokeServerManagerInvite(_ip, _port, _accessToken, _inviteName); } - late final __fcvtPtr = + late final _revokeServerManagerInvitePtr = _lookup< ffi.NativeFunction< ffi.Pointer Function( - ffi.Double, - ffi.Int, - ffi.Pointer, - ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, ) > - >('_fcvt'); - late final __fcvt = __fcvtPtr + >('revokeServerManagerInvite'); + late final _revokeServerManagerInvite = _revokeServerManagerInvitePtr .asFunction< ffi.Pointer Function( - double, - int, - ffi.Pointer, - ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, ) >(); - int _gcvt_s( - ffi.Pointer _Buffer, - int _BufferCount, - double _Value, - int _DigitCount, + ffi.Pointer addServerBasedOnURLs( + ffi.Pointer _urls, + int _skipCertVerification, ) { - return __gcvt_s(_Buffer, _BufferCount, _Value, _DigitCount); + return _addServerBasedOnURLs(_urls, _skipCertVerification); } - late final __gcvt_sPtr = + late final _addServerBasedOnURLsPtr = _lookup< ffi.NativeFunction< - errno_t Function(ffi.Pointer, ffi.Size, ffi.Double, ffi.Int) + ffi.Pointer Function(ffi.Pointer, ffi.Int) > - >('_gcvt_s'); - late final __gcvt_s = __gcvt_sPtr - .asFunction, int, double, int)>(); - - ffi.Pointer _gcvt( - double _Value, - int _DigitCount, - ffi.Pointer _Buffer, - ) { - return __gcvt(_Value, _DigitCount, _Buffer); + >('addServerBasedOnURLs'); + late final _addServerBasedOnURLs = _addServerBasedOnURLsPtr + .asFunction Function(ffi.Pointer, int)>(); + + ffi.Pointer setBlockAdsEnabled(int enabled) { + return _setBlockAdsEnabled(enabled); } - late final __gcvtPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Double, - ffi.Int, - ffi.Pointer, - ) - > - >('_gcvt'); - late final __gcvt = __gcvtPtr - .asFunction< - ffi.Pointer Function(double, int, ffi.Pointer) - >(); + late final _setBlockAdsEnabledPtr = + _lookup Function(ffi.Int)>>( + 'setBlockAdsEnabled', + ); + late final _setBlockAdsEnabled = _setBlockAdsEnabledPtr + .asFunction Function(int)>(); - int ___mb_cur_max_func() { - return ____mb_cur_max_func(); + int isBlockAdsEnabled() { + return _isBlockAdsEnabled(); } - late final ____mb_cur_max_funcPtr = - _lookup>('___mb_cur_max_func'); - late final ____mb_cur_max_func = ____mb_cur_max_funcPtr + late final _isBlockAdsEnabledPtr = + _lookup>('isBlockAdsEnabled'); + late final _isBlockAdsEnabled = _isBlockAdsEnabledPtr .asFunction(); - int ___mb_cur_max_l_func(_locale_t _Locale) { - return ____mb_cur_max_l_func(_Locale); + ffi.Pointer setSmartRoutingEnabled(int enabled) { + return _setSmartRoutingEnabled(enabled); } - late final ____mb_cur_max_l_funcPtr = - _lookup>( - '___mb_cur_max_l_func', + late final _setSmartRoutingEnabledPtr = + _lookup Function(ffi.Int)>>( + 'setSmartRoutingEnabled', ); - late final ____mb_cur_max_l_func = ____mb_cur_max_l_funcPtr - .asFunction(); + late final _setSmartRoutingEnabled = _setSmartRoutingEnabledPtr + .asFunction Function(int)>(); - int mblen(ffi.Pointer _Ch, int _MaxCount) { - return _mblen(_Ch, _MaxCount); + int isSmartRoutingEnabled() { + return _isSmartRoutingEnabled(); } - late final _mblenPtr = - _lookup< - ffi.NativeFunction, ffi.Size)> - >('mblen'); - late final _mblen = _mblenPtr - .asFunction, int)>(); + late final _isSmartRoutingEnabledPtr = + _lookup>('isSmartRoutingEnabled'); + late final _isSmartRoutingEnabled = _isSmartRoutingEnabledPtr + .asFunction(); - int _mblen_l(ffi.Pointer _Ch, int _MaxCount, _locale_t _Locale) { - return __mblen_l(_Ch, _MaxCount, _Locale); + ffi.Pointer setPeerProxyEnabled(int enabled) { + return _setPeerProxyEnabled(enabled); } - late final __mblen_lPtr = - _lookup< - ffi.NativeFunction< - ffi.Int Function(ffi.Pointer, ffi.Size, _locale_t) - > - >('_mblen_l'); - late final __mblen_l = __mblen_lPtr - .asFunction, int, _locale_t)>(); + late final _setPeerProxyEnabledPtr = + _lookup Function(ffi.Int)>>( + 'setPeerProxyEnabled', + ); + late final _setPeerProxyEnabled = _setPeerProxyEnabledPtr + .asFunction Function(int)>(); + + int isPeerProxyEnabled() { + return _isPeerProxyEnabled(); + } - int _mbstrlen(ffi.Pointer _String) { - return __mbstrlen(_String); + late final _isPeerProxyEnabledPtr = + _lookup>('isPeerProxyEnabled'); + late final _isPeerProxyEnabled = _isPeerProxyEnabledPtr + .asFunction(); + + ffi.Pointer setPeerManualPort(int port) { + return _setPeerManualPort(port); } - late final __mbstrlenPtr = - _lookup)>>( - '_mbstrlen', + late final _setPeerManualPortPtr = + _lookup Function(ffi.Int)>>( + 'setPeerManualPort', ); - late final __mbstrlen = __mbstrlenPtr - .asFunction)>(); + late final _setPeerManualPort = _setPeerManualPortPtr + .asFunction Function(int)>(); - int _mbstrlen_l(ffi.Pointer _String, _locale_t _Locale) { - return __mbstrlen_l(_String, _Locale); + int getPeerManualPort() { + return _getPeerManualPort(); } - late final __mbstrlen_lPtr = - _lookup< - ffi.NativeFunction, _locale_t)> - >('_mbstrlen_l'); - late final __mbstrlen_l = __mbstrlen_lPtr - .asFunction, _locale_t)>(); + late final _getPeerManualPortPtr = + _lookup>('getPeerManualPort'); + late final _getPeerManualPort = _getPeerManualPortPtr + .asFunction(); - int _mbstrnlen(ffi.Pointer _String, int _MaxCount) { - return __mbstrnlen(_String, _MaxCount); + int probeUPnP() { + return _probeUPnP(); } - late final __mbstrnlenPtr = - _lookup< - ffi.NativeFunction, ffi.Size)> - >('_mbstrnlen'); - late final __mbstrnlen = __mbstrnlenPtr - .asFunction, int)>(); + late final _probeUPnPPtr = _lookup>( + 'probeUPnP', + ); + late final _probeUPnP = _probeUPnPPtr.asFunction(); - int _mbstrnlen_l( - ffi.Pointer _String, - int _MaxCount, - _locale_t _Locale, - ) { - return __mbstrnlen_l(_String, _MaxCount, _Locale); + ffi.Pointer setUnboundedEnabled(int enabled) { + return _setUnboundedEnabled(enabled); } - late final __mbstrnlen_lPtr = - _lookup< - ffi.NativeFunction< - ffi.Size Function(ffi.Pointer, ffi.Size, _locale_t) - > - >('_mbstrnlen_l'); - late final __mbstrnlen_l = __mbstrnlen_lPtr - .asFunction, int, _locale_t)>(); - - int mbtowc( - ffi.Pointer _DstCh, - ffi.Pointer _SrcCh, - int _SrcSizeInBytes, - ) { - return _mbtowc(_DstCh, _SrcCh, _SrcSizeInBytes); + late final _setUnboundedEnabledPtr = + _lookup Function(ffi.Int)>>( + 'setUnboundedEnabled', + ); + late final _setUnboundedEnabled = _setUnboundedEnabledPtr + .asFunction Function(int)>(); + + int isUnboundedEnabled() { + return _isUnboundedEnabled(); } - late final _mbtowcPtr = + late final _isUnboundedEnabledPtr = + _lookup>('isUnboundedEnabled'); + late final _isUnboundedEnabled = _isUnboundedEnabledPtr + .asFunction(); + + ffi.Pointer getSplitTunnelState() { + return _getSplitTunnelState(); + } + + late final _getSplitTunnelStatePtr = + _lookup Function()>>( + 'getSplitTunnelState', + ); + late final _getSplitTunnelState = _getSplitTunnelStatePtr + .asFunction Function()>(); + + ffi.Pointer getSplitTunnelItems(ffi.Pointer filterTypeC) { + return _getSplitTunnelItems(filterTypeC); + } + + late final _getSplitTunnelItemsPtr = _lookup< ffi.NativeFunction< - ffi.Int Function( - ffi.Pointer, - ffi.Pointer, - ffi.Size, - ) + ffi.Pointer Function(ffi.Pointer) > - >('mbtowc'); - late final _mbtowc = _mbtowcPtr - .asFunction< - int Function(ffi.Pointer, ffi.Pointer, int) - >(); + >('getSplitTunnelItems'); + late final _getSplitTunnelItems = _getSplitTunnelItemsPtr + .asFunction Function(ffi.Pointer)>(); - int _mbtowc_l( - ffi.Pointer _DstCh, - ffi.Pointer _SrcCh, - int _SrcSizeInBytes, - _locale_t _Locale, - ) { - return __mbtowc_l(_DstCh, _SrcCh, _SrcSizeInBytes, _Locale); + ffi.Pointer deletePrivateServerByName(ffi.Pointer _name) { + return _deletePrivateServerByName(_name); } - late final __mbtowc_lPtr = + late final _deletePrivateServerByNamePtr = _lookup< ffi.NativeFunction< - ffi.Int Function( - ffi.Pointer, - ffi.Pointer, - ffi.Size, - _locale_t, - ) + ffi.Pointer Function(ffi.Pointer) > - >('_mbtowc_l'); - late final __mbtowc_l = __mbtowc_lPtr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer, - int, - _locale_t, - ) - >(); + >('deletePrivateServerByName'); + late final _deletePrivateServerByName = _deletePrivateServerByNamePtr + .asFunction Function(ffi.Pointer)>(); - int mbstowcs_s( - ffi.Pointer _PtNumOfCharConverted, - ffi.Pointer _DstBuf, - int _SizeInWords, - ffi.Pointer _SrcBuf, - int _MaxCount, + ffi.Pointer updatePrivateServerName( + ffi.Pointer _oldName, + ffi.Pointer _newName, ) { - return _mbstowcs_s( - _PtNumOfCharConverted, - _DstBuf, - _SizeInWords, - _SrcBuf, - _MaxCount, - ); + return _updatePrivateServerName(_oldName, _newName); } - late final _mbstowcs_sPtr = + late final _updatePrivateServerNamePtr = _lookup< ffi.NativeFunction< - errno_t Function( - ffi.Pointer, - ffi.Pointer, - ffi.Size, + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, - ffi.Size, ) > - >('mbstowcs_s'); - late final _mbstowcs_s = _mbstowcs_sPtr + >('updatePrivateServerName'); + late final _updatePrivateServerName = _updatePrivateServerNamePtr .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer, - int, + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, - int, ) >(); - int mbstowcs( - ffi.Pointer _Dest, - ffi.Pointer _Source, - int _MaxCount, - ) { - return _mbstowcs(_Dest, _Source, _MaxCount); + ffi.Pointer getEnabledApps() { + return _getEnabledApps(); } - late final _mbstowcsPtr = - _lookup< - ffi.NativeFunction< - ffi.Size Function( - ffi.Pointer, - ffi.Pointer, - ffi.Size, - ) - > - >('mbstowcs'); - late final _mbstowcs = _mbstowcsPtr - .asFunction< - int Function(ffi.Pointer, ffi.Pointer, int) - >(); + late final _getEnabledAppsPtr = + _lookup Function()>>( + 'getEnabledApps', + ); + late final _getEnabledApps = _getEnabledAppsPtr + .asFunction Function()>(); +} + +typedef __int8_t = ffi.SignedChar; +typedef Dart__int8_t = int; +typedef __uint8_t = ffi.UnsignedChar; +typedef Dart__uint8_t = int; +typedef __int16_t = ffi.Short; +typedef Dart__int16_t = int; +typedef __uint16_t = ffi.UnsignedShort; +typedef Dart__uint16_t = int; +typedef __int32_t = ffi.Int; +typedef Dart__int32_t = int; +typedef __uint32_t = ffi.UnsignedInt; +typedef Dart__uint32_t = int; +typedef __int64_t = ffi.LongLong; +typedef Dart__int64_t = int; +typedef __uint64_t = ffi.UnsignedLongLong; +typedef Dart__uint64_t = int; +typedef __darwin_intptr_t = ffi.Long; +typedef Dart__darwin_intptr_t = int; +typedef __darwin_natural_t = ffi.UnsignedInt; +typedef Dart__darwin_natural_t = int; +typedef __darwin_ct_rune_t = ffi.Int; +typedef Dart__darwin_ct_rune_t = int; + +final class __mbstate_t extends ffi.Union { + @ffi.Array.multi([128]) + external ffi.Array __mbstate8; + + @ffi.LongLong() + external int _mbstateL; +} + +typedef __darwin_mbstate_t = __mbstate_t; +typedef __darwin_ptrdiff_t = ffi.Long; +typedef Dart__darwin_ptrdiff_t = int; +typedef __darwin_size_t = ffi.UnsignedLong; +typedef Dart__darwin_size_t = int; +typedef __builtin_va_list = ffi.Pointer; +typedef __darwin_va_list = __builtin_va_list; +typedef __darwin_wchar_t = ffi.Int; +typedef Dart__darwin_wchar_t = int; +typedef __darwin_rune_t = __darwin_wchar_t; +typedef __darwin_wint_t = ffi.Int; +typedef Dart__darwin_wint_t = int; +typedef __darwin_clock_t = ffi.UnsignedLong; +typedef Dart__darwin_clock_t = int; +typedef __darwin_socklen_t = __uint32_t; +typedef __darwin_ssize_t = ffi.Long; +typedef Dart__darwin_ssize_t = int; +typedef __darwin_time_t = ffi.Long; +typedef Dart__darwin_time_t = int; +typedef __darwin_blkcnt_t = __int64_t; +typedef __darwin_blksize_t = __int32_t; +typedef __darwin_dev_t = __int32_t; +typedef __darwin_fsblkcnt_t = ffi.UnsignedInt; +typedef Dart__darwin_fsblkcnt_t = int; +typedef __darwin_fsfilcnt_t = ffi.UnsignedInt; +typedef Dart__darwin_fsfilcnt_t = int; +typedef __darwin_gid_t = __uint32_t; +typedef __darwin_id_t = __uint32_t; +typedef __darwin_ino64_t = __uint64_t; +typedef __darwin_ino_t = __darwin_ino64_t; +typedef __darwin_mach_port_name_t = __darwin_natural_t; +typedef __darwin_mach_port_t = __darwin_mach_port_name_t; +typedef __darwin_mode_t = __uint16_t; +typedef __darwin_off_t = __int64_t; +typedef __darwin_pid_t = __int32_t; +typedef __darwin_sigset_t = __uint32_t; +typedef __darwin_suseconds_t = __int32_t; +typedef __darwin_uid_t = __uint32_t; +typedef __darwin_useconds_t = __uint32_t; + +final class __darwin_pthread_handler_rec extends ffi.Struct { + external ffi.Pointer< + ffi.NativeFunction)> + > + __routine; + + external ffi.Pointer __arg; + + external ffi.Pointer<__darwin_pthread_handler_rec> __next; +} + +final class _opaque_pthread_attr_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([56]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_cond_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([40]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_condattr_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([8]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_mutex_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([56]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_mutexattr_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([8]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_once_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([8]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_rwlock_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([192]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_rwlockattr_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + @ffi.Array.multi([16]) + external ffi.Array __opaque; +} + +final class _opaque_pthread_t extends ffi.Struct { + @ffi.Long() + external int __sig; + + external ffi.Pointer<__darwin_pthread_handler_rec> __cleanup_stack; + + @ffi.Array.multi([8176]) + external ffi.Array __opaque; +} + +typedef __darwin_pthread_attr_t = _opaque_pthread_attr_t; +typedef __darwin_pthread_cond_t = _opaque_pthread_cond_t; +typedef __darwin_pthread_condattr_t = _opaque_pthread_condattr_t; +typedef __darwin_pthread_key_t = ffi.UnsignedLong; +typedef Dart__darwin_pthread_key_t = int; +typedef __darwin_pthread_mutex_t = _opaque_pthread_mutex_t; +typedef __darwin_pthread_mutexattr_t = _opaque_pthread_mutexattr_t; +typedef __darwin_pthread_once_t = _opaque_pthread_once_t; +typedef __darwin_pthread_rwlock_t = _opaque_pthread_rwlock_t; +typedef __darwin_pthread_rwlockattr_t = _opaque_pthread_rwlockattr_t; +typedef __darwin_pthread_t = ffi.Pointer<_opaque_pthread_t>; +typedef __darwin_nl_item = ffi.Int; +typedef Dart__darwin_nl_item = int; +typedef __darwin_wctrans_t = ffi.Int; +typedef Dart__darwin_wctrans_t = int; +typedef __darwin_wctype_t = __uint32_t; +typedef u_int8_t = ffi.UnsignedChar; +typedef Dartu_int8_t = int; +typedef u_int16_t = ffi.UnsignedShort; +typedef Dartu_int16_t = int; +typedef u_int32_t = ffi.UnsignedInt; +typedef Dartu_int32_t = int; +typedef u_int64_t = ffi.UnsignedLongLong; +typedef Dartu_int64_t = int; +typedef register_t = ffi.Int64; +typedef Dartregister_t = int; +typedef user_addr_t = u_int64_t; +typedef user_size_t = u_int64_t; +typedef user_ssize_t = ffi.Int64; +typedef Dartuser_ssize_t = int; +typedef user_long_t = ffi.Int64; +typedef Dartuser_long_t = int; +typedef user_ulong_t = u_int64_t; +typedef user_time_t = ffi.Int64; +typedef Dartuser_time_t = int; +typedef user_off_t = ffi.Int64; +typedef Dartuser_off_t = int; +typedef syscall_arg_t = u_int64_t; +typedef ptrdiff_t = __darwin_ptrdiff_t; +typedef rsize_t = __darwin_size_t; +typedef wint_t = __darwin_wint_t; + +final class _GoString_ extends ffi.Struct { + external ffi.Pointer p; + + @ptrdiff_t() + external int n; +} + +enum idtype_t { + P_ALL(0), + P_PID(1), + P_PGID(2); + + final int value; + const idtype_t(this.value); + + static idtype_t fromValue(int value) => switch (value) { + 0 => P_ALL, + 1 => P_PID, + 2 => P_PGID, + _ => throw ArgumentError('Unknown value for idtype_t: $value'), + }; +} + +typedef pid_t = __darwin_pid_t; +typedef id_t = __darwin_id_t; +typedef sig_atomic_t = ffi.Int; +typedef Dartsig_atomic_t = int; + +final class __darwin_arm_exception_state extends ffi.Struct { + @__uint32_t() + external int __exception; + + @__uint32_t() + external int __fsr; + + @__uint32_t() + external int __far; +} + +final class __darwin_arm_exception_state64 extends ffi.Struct { + @__uint64_t() + external int __far; + + @__uint32_t() + external int __esr; + + @__uint32_t() + external int __exception; +} + +final class __darwin_arm_exception_state64_v2 extends ffi.Struct { + @__uint64_t() + external int __far; + + @__uint64_t() + external int __esr; +} + +final class __darwin_arm_thread_state extends ffi.Struct { + @ffi.Array.multi([13]) + external ffi.Array<__uint32_t> __r; + + @__uint32_t() + external int __sp; + + @__uint32_t() + external int __lr; + + @__uint32_t() + external int __pc; + + @__uint32_t() + external int __cpsr; +} + +final class __darwin_arm_thread_state64 extends ffi.Struct { + @ffi.Array.multi([29]) + external ffi.Array<__uint64_t> __x; + + @__uint64_t() + external int __fp; + + @__uint64_t() + external int __lr; + + @__uint64_t() + external int __sp; + + @__uint64_t() + external int __pc; + + @__uint32_t() + external int __cpsr; + + @__uint32_t() + external int __pad; +} + +final class __darwin_arm_vfp_state extends ffi.Struct { + @ffi.Array.multi([64]) + external ffi.Array<__uint32_t> __r; + + @__uint32_t() + external int __fpscr; +} + +final class __darwin_arm_neon_state64 extends ffi.Opaque {} + +final class __darwin_arm_neon_state extends ffi.Opaque {} + +final class __arm_pagein_state extends ffi.Struct { + @ffi.Int() + external int __pagein_error; +} + +final class __darwin_arm_sme_state extends ffi.Struct { + @__uint64_t() + external int __svcr; + + @__uint64_t() + external int __tpidr2_el0; + + @__uint16_t() + external int __svl_b; +} + +final class __darwin_arm_sve_z_state extends ffi.Struct { + @ffi.Array.multi([16, 256]) + external ffi.Array> __z; +} + +final class __darwin_arm_sve_p_state extends ffi.Struct { + @ffi.Array.multi([16, 32]) + external ffi.Array> __p; +} + +final class __darwin_arm_sme_za_state extends ffi.Struct { + @ffi.Array.multi([4096]) + external ffi.Array __za; +} + +final class __darwin_arm_sme2_state extends ffi.Struct { + @ffi.Array.multi([64]) + external ffi.Array __zt0; +} + +final class __arm_legacy_debug_state extends ffi.Struct { + @ffi.Array.multi([16]) + external ffi.Array<__uint32_t> __bvr; + + @ffi.Array.multi([16]) + external ffi.Array<__uint32_t> __bcr; + + @ffi.Array.multi([16]) + external ffi.Array<__uint32_t> __wvr; + + @ffi.Array.multi([16]) + external ffi.Array<__uint32_t> __wcr; +} + +final class __darwin_arm_debug_state32 extends ffi.Struct { + @ffi.Array.multi([16]) + external ffi.Array<__uint32_t> __bvr; + + @ffi.Array.multi([16]) + external ffi.Array<__uint32_t> __bcr; + + @ffi.Array.multi([16]) + external ffi.Array<__uint32_t> __wvr; + + @ffi.Array.multi([16]) + external ffi.Array<__uint32_t> __wcr; + + @__uint64_t() + external int __mdscr_el1; +} + +final class __darwin_arm_debug_state64 extends ffi.Struct { + @ffi.Array.multi([16]) + external ffi.Array<__uint64_t> __bvr; + + @ffi.Array.multi([16]) + external ffi.Array<__uint64_t> __bcr; + + @ffi.Array.multi([16]) + external ffi.Array<__uint64_t> __wvr; + + @ffi.Array.multi([16]) + external ffi.Array<__uint64_t> __wcr; + + @__uint64_t() + external int __mdscr_el1; +} + +final class __darwin_arm_cpmu_state64 extends ffi.Struct { + @ffi.Array.multi([16]) + external ffi.Array<__uint64_t> __ctrs; +} + +final class __darwin_mcontext32 extends ffi.Struct { + external __darwin_arm_exception_state __es; + + external __darwin_arm_thread_state __ss; + + external __darwin_arm_vfp_state __fs; +} + +final class __darwin_mcontext64 extends ffi.Opaque {} + +typedef mcontext_t = ffi.Pointer<__darwin_mcontext64>; +typedef pthread_attr_t = __darwin_pthread_attr_t; + +final class __darwin_sigaltstack extends ffi.Struct { + external ffi.Pointer ss_sp; + + @__darwin_size_t() + external int ss_size; + + @ffi.Int() + external int ss_flags; +} + +typedef stack_t = __darwin_sigaltstack; + +final class __darwin_ucontext extends ffi.Struct { + @ffi.Int() + external int uc_onstack; + + @__darwin_sigset_t() + external int uc_sigmask; + + external __darwin_sigaltstack uc_stack; + + external ffi.Pointer<__darwin_ucontext> uc_link; + + @__darwin_size_t() + external int uc_mcsize; + + external ffi.Pointer<__darwin_mcontext64> uc_mcontext; +} + +typedef ucontext_t = __darwin_ucontext; +typedef sigset_t = __darwin_sigset_t; +typedef uid_t = __darwin_uid_t; + +final class sigval extends ffi.Union { + @ffi.Int() + external int sival_int; + + external ffi.Pointer sival_ptr; +} + +final class sigevent extends ffi.Struct { + @ffi.Int() + external int sigev_notify; + + @ffi.Int() + external int sigev_signo; + + external sigval sigev_value; + + external ffi.Pointer> + sigev_notify_function; + + external ffi.Pointer sigev_notify_attributes; +} + +final class __siginfo extends ffi.Struct { + @ffi.Int() + external int si_signo; + + @ffi.Int() + external int si_errno; + + @ffi.Int() + external int si_code; + + @pid_t() + external int si_pid; + + @uid_t() + external int si_uid; + + @ffi.Int() + external int si_status; + + external ffi.Pointer si_addr; + + external sigval si_value; + + @ffi.Long() + external int si_band; + + @ffi.Array.multi([7]) + external ffi.Array __pad; +} + +typedef siginfo_t = __siginfo; + +final class __sigaction_u extends ffi.Union { + external ffi.Pointer> + __sa_handler; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Int, ffi.Pointer<__siginfo>, ffi.Pointer) + > + > + __sa_sigaction; +} + +final class __sigaction extends ffi.Struct { + external __sigaction_u __sigaction_u$1; + + external ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + ffi.Int, + ffi.Int, + ffi.Pointer, + ffi.Pointer, + ) + > + > + sa_tramp; + + @sigset_t() + external int sa_mask; + + @ffi.Int() + external int sa_flags; +} + +final class sigaction extends ffi.Struct { + external __sigaction_u __sigaction_u$1; + + @sigset_t() + external int sa_mask; + + @ffi.Int() + external int sa_flags; +} + +typedef sig_tFunction = ffi.Void Function(ffi.Int); +typedef Dartsig_tFunction = void Function(int); +typedef sig_t = ffi.Pointer>; + +final class sigvec extends ffi.Struct { + external ffi.Pointer> + sv_handler; + + @ffi.Int() + external int sv_mask; + + @ffi.Int() + external int sv_flags; +} + +final class sigstack extends ffi.Struct { + external ffi.Pointer ss_sp; + + @ffi.Int() + external int ss_onstack; +} + +typedef int_least8_t = ffi.Int8; +typedef Dartint_least8_t = int; +typedef int_least16_t = ffi.Int16; +typedef Dartint_least16_t = int; +typedef int_least32_t = ffi.Int32; +typedef Dartint_least32_t = int; +typedef int_least64_t = ffi.Int64; +typedef Dartint_least64_t = int; +typedef uint_least8_t = ffi.Uint8; +typedef Dartuint_least8_t = int; +typedef uint_least16_t = ffi.Uint16; +typedef Dartuint_least16_t = int; +typedef uint_least32_t = ffi.Uint32; +typedef Dartuint_least32_t = int; +typedef uint_least64_t = ffi.Uint64; +typedef Dartuint_least64_t = int; +typedef int_fast8_t = ffi.Int8; +typedef Dartint_fast8_t = int; +typedef int_fast16_t = ffi.Int16; +typedef Dartint_fast16_t = int; +typedef int_fast32_t = ffi.Int32; +typedef Dartint_fast32_t = int; +typedef int_fast64_t = ffi.Int64; +typedef Dartint_fast64_t = int; +typedef uint_fast8_t = ffi.Uint8; +typedef Dartuint_fast8_t = int; +typedef uint_fast16_t = ffi.Uint16; +typedef Dartuint_fast16_t = int; +typedef uint_fast32_t = ffi.Uint32; +typedef Dartuint_fast32_t = int; +typedef uint_fast64_t = ffi.Uint64; +typedef Dartuint_fast64_t = int; +typedef intmax_t = ffi.Long; +typedef Dartintmax_t = int; +typedef uintmax_t = ffi.UnsignedLong; +typedef Dartuintmax_t = int; + +final class timeval extends ffi.Struct { + @__darwin_time_t() + external int tv_sec; + + @__darwin_suseconds_t() + external int tv_usec; +} + +typedef rlim_t = __uint64_t; + +final class rusage extends ffi.Struct { + external timeval ru_utime; + + external timeval ru_stime; + + @ffi.Long() + external int ru_maxrss; + + @ffi.Long() + external int ru_ixrss; + + @ffi.Long() + external int ru_idrss; + + @ffi.Long() + external int ru_isrss; + + @ffi.Long() + external int ru_minflt; + + @ffi.Long() + external int ru_majflt; + + @ffi.Long() + external int ru_nswap; + + @ffi.Long() + external int ru_inblock; + + @ffi.Long() + external int ru_oublock; + + @ffi.Long() + external int ru_msgsnd; + + @ffi.Long() + external int ru_msgrcv; + + @ffi.Long() + external int ru_nsignals; + + @ffi.Long() + external int ru_nvcsw; + + @ffi.Long() + external int ru_nivcsw; +} + +typedef rusage_info_t = ffi.Pointer; + +final class rusage_info_v0 extends ffi.Struct { + @ffi.Array.multi([16]) + external ffi.Array ri_uuid; + + @ffi.Uint64() + external int ri_user_time; + + @ffi.Uint64() + external int ri_system_time; + + @ffi.Uint64() + external int ri_pkg_idle_wkups; + + @ffi.Uint64() + external int ri_interrupt_wkups; + + @ffi.Uint64() + external int ri_pageins; + + @ffi.Uint64() + external int ri_wired_size; + + @ffi.Uint64() + external int ri_resident_size; + + @ffi.Uint64() + external int ri_phys_footprint; + + @ffi.Uint64() + external int ri_proc_start_abstime; + + @ffi.Uint64() + external int ri_proc_exit_abstime; +} + +final class rusage_info_v1 extends ffi.Struct { + @ffi.Array.multi([16]) + external ffi.Array ri_uuid; + + @ffi.Uint64() + external int ri_user_time; + + @ffi.Uint64() + external int ri_system_time; + + @ffi.Uint64() + external int ri_pkg_idle_wkups; + + @ffi.Uint64() + external int ri_interrupt_wkups; + + @ffi.Uint64() + external int ri_pageins; + + @ffi.Uint64() + external int ri_wired_size; + + @ffi.Uint64() + external int ri_resident_size; + + @ffi.Uint64() + external int ri_phys_footprint; + + @ffi.Uint64() + external int ri_proc_start_abstime; + + @ffi.Uint64() + external int ri_proc_exit_abstime; + + @ffi.Uint64() + external int ri_child_user_time; + + @ffi.Uint64() + external int ri_child_system_time; + + @ffi.Uint64() + external int ri_child_pkg_idle_wkups; + + @ffi.Uint64() + external int ri_child_interrupt_wkups; + + @ffi.Uint64() + external int ri_child_pageins; + + @ffi.Uint64() + external int ri_child_elapsed_abstime; +} + +final class rusage_info_v2 extends ffi.Struct { + @ffi.Array.multi([16]) + external ffi.Array ri_uuid; + + @ffi.Uint64() + external int ri_user_time; + + @ffi.Uint64() + external int ri_system_time; + + @ffi.Uint64() + external int ri_pkg_idle_wkups; + + @ffi.Uint64() + external int ri_interrupt_wkups; + + @ffi.Uint64() + external int ri_pageins; + + @ffi.Uint64() + external int ri_wired_size; + + @ffi.Uint64() + external int ri_resident_size; + + @ffi.Uint64() + external int ri_phys_footprint; + + @ffi.Uint64() + external int ri_proc_start_abstime; + + @ffi.Uint64() + external int ri_proc_exit_abstime; + + @ffi.Uint64() + external int ri_child_user_time; + + @ffi.Uint64() + external int ri_child_system_time; + + @ffi.Uint64() + external int ri_child_pkg_idle_wkups; + + @ffi.Uint64() + external int ri_child_interrupt_wkups; + + @ffi.Uint64() + external int ri_child_pageins; + + @ffi.Uint64() + external int ri_child_elapsed_abstime; + + @ffi.Uint64() + external int ri_diskio_bytesread; + + @ffi.Uint64() + external int ri_diskio_byteswritten; +} + +final class rusage_info_v3 extends ffi.Struct { + @ffi.Array.multi([16]) + external ffi.Array ri_uuid; + + @ffi.Uint64() + external int ri_user_time; + + @ffi.Uint64() + external int ri_system_time; + + @ffi.Uint64() + external int ri_pkg_idle_wkups; + + @ffi.Uint64() + external int ri_interrupt_wkups; + + @ffi.Uint64() + external int ri_pageins; + + @ffi.Uint64() + external int ri_wired_size; + + @ffi.Uint64() + external int ri_resident_size; + + @ffi.Uint64() + external int ri_phys_footprint; + + @ffi.Uint64() + external int ri_proc_start_abstime; + + @ffi.Uint64() + external int ri_proc_exit_abstime; + + @ffi.Uint64() + external int ri_child_user_time; + + @ffi.Uint64() + external int ri_child_system_time; + + @ffi.Uint64() + external int ri_child_pkg_idle_wkups; + + @ffi.Uint64() + external int ri_child_interrupt_wkups; + + @ffi.Uint64() + external int ri_child_pageins; + + @ffi.Uint64() + external int ri_child_elapsed_abstime; + + @ffi.Uint64() + external int ri_diskio_bytesread; + + @ffi.Uint64() + external int ri_diskio_byteswritten; + + @ffi.Uint64() + external int ri_cpu_time_qos_default; + + @ffi.Uint64() + external int ri_cpu_time_qos_maintenance; + + @ffi.Uint64() + external int ri_cpu_time_qos_background; + + @ffi.Uint64() + external int ri_cpu_time_qos_utility; + + @ffi.Uint64() + external int ri_cpu_time_qos_legacy; + + @ffi.Uint64() + external int ri_cpu_time_qos_user_initiated; + + @ffi.Uint64() + external int ri_cpu_time_qos_user_interactive; + + @ffi.Uint64() + external int ri_billed_system_time; + + @ffi.Uint64() + external int ri_serviced_system_time; +} + +final class rusage_info_v4 extends ffi.Struct { + @ffi.Array.multi([16]) + external ffi.Array ri_uuid; + + @ffi.Uint64() + external int ri_user_time; + + @ffi.Uint64() + external int ri_system_time; + + @ffi.Uint64() + external int ri_pkg_idle_wkups; + + @ffi.Uint64() + external int ri_interrupt_wkups; + + @ffi.Uint64() + external int ri_pageins; + + @ffi.Uint64() + external int ri_wired_size; + + @ffi.Uint64() + external int ri_resident_size; + + @ffi.Uint64() + external int ri_phys_footprint; + + @ffi.Uint64() + external int ri_proc_start_abstime; + + @ffi.Uint64() + external int ri_proc_exit_abstime; + + @ffi.Uint64() + external int ri_child_user_time; + + @ffi.Uint64() + external int ri_child_system_time; + + @ffi.Uint64() + external int ri_child_pkg_idle_wkups; + + @ffi.Uint64() + external int ri_child_interrupt_wkups; + + @ffi.Uint64() + external int ri_child_pageins; + + @ffi.Uint64() + external int ri_child_elapsed_abstime; + + @ffi.Uint64() + external int ri_diskio_bytesread; + + @ffi.Uint64() + external int ri_diskio_byteswritten; + + @ffi.Uint64() + external int ri_cpu_time_qos_default; + + @ffi.Uint64() + external int ri_cpu_time_qos_maintenance; + + @ffi.Uint64() + external int ri_cpu_time_qos_background; + + @ffi.Uint64() + external int ri_cpu_time_qos_utility; + + @ffi.Uint64() + external int ri_cpu_time_qos_legacy; + + @ffi.Uint64() + external int ri_cpu_time_qos_user_initiated; + + @ffi.Uint64() + external int ri_cpu_time_qos_user_interactive; + + @ffi.Uint64() + external int ri_billed_system_time; + + @ffi.Uint64() + external int ri_serviced_system_time; + + @ffi.Uint64() + external int ri_logical_writes; + + @ffi.Uint64() + external int ri_lifetime_max_phys_footprint; + + @ffi.Uint64() + external int ri_instructions; + + @ffi.Uint64() + external int ri_cycles; + + @ffi.Uint64() + external int ri_billed_energy; + + @ffi.Uint64() + external int ri_serviced_energy; + + @ffi.Uint64() + external int ri_interval_max_phys_footprint; + + @ffi.Uint64() + external int ri_runnable_time; +} + +final class rusage_info_v5 extends ffi.Struct { + @ffi.Array.multi([16]) + external ffi.Array ri_uuid; + + @ffi.Uint64() + external int ri_user_time; + + @ffi.Uint64() + external int ri_system_time; + + @ffi.Uint64() + external int ri_pkg_idle_wkups; + + @ffi.Uint64() + external int ri_interrupt_wkups; + + @ffi.Uint64() + external int ri_pageins; + + @ffi.Uint64() + external int ri_wired_size; + + @ffi.Uint64() + external int ri_resident_size; + + @ffi.Uint64() + external int ri_phys_footprint; + + @ffi.Uint64() + external int ri_proc_start_abstime; + + @ffi.Uint64() + external int ri_proc_exit_abstime; + + @ffi.Uint64() + external int ri_child_user_time; + + @ffi.Uint64() + external int ri_child_system_time; + + @ffi.Uint64() + external int ri_child_pkg_idle_wkups; + + @ffi.Uint64() + external int ri_child_interrupt_wkups; + + @ffi.Uint64() + external int ri_child_pageins; + + @ffi.Uint64() + external int ri_child_elapsed_abstime; + + @ffi.Uint64() + external int ri_diskio_bytesread; + + @ffi.Uint64() + external int ri_diskio_byteswritten; + + @ffi.Uint64() + external int ri_cpu_time_qos_default; + + @ffi.Uint64() + external int ri_cpu_time_qos_maintenance; + + @ffi.Uint64() + external int ri_cpu_time_qos_background; + + @ffi.Uint64() + external int ri_cpu_time_qos_utility; + + @ffi.Uint64() + external int ri_cpu_time_qos_legacy; + + @ffi.Uint64() + external int ri_cpu_time_qos_user_initiated; + + @ffi.Uint64() + external int ri_cpu_time_qos_user_interactive; + + @ffi.Uint64() + external int ri_billed_system_time; + + @ffi.Uint64() + external int ri_serviced_system_time; + + @ffi.Uint64() + external int ri_logical_writes; + + @ffi.Uint64() + external int ri_lifetime_max_phys_footprint; + + @ffi.Uint64() + external int ri_instructions; + + @ffi.Uint64() + external int ri_cycles; + + @ffi.Uint64() + external int ri_billed_energy; + + @ffi.Uint64() + external int ri_serviced_energy; + + @ffi.Uint64() + external int ri_interval_max_phys_footprint; + + @ffi.Uint64() + external int ri_runnable_time; + + @ffi.Uint64() + external int ri_flags; +} + +final class rusage_info_v6 extends ffi.Struct { + @ffi.Array.multi([16]) + external ffi.Array ri_uuid; + + @ffi.Uint64() + external int ri_user_time; + + @ffi.Uint64() + external int ri_system_time; + + @ffi.Uint64() + external int ri_pkg_idle_wkups; + + @ffi.Uint64() + external int ri_interrupt_wkups; + + @ffi.Uint64() + external int ri_pageins; + + @ffi.Uint64() + external int ri_wired_size; + + @ffi.Uint64() + external int ri_resident_size; + + @ffi.Uint64() + external int ri_phys_footprint; + + @ffi.Uint64() + external int ri_proc_start_abstime; + + @ffi.Uint64() + external int ri_proc_exit_abstime; + + @ffi.Uint64() + external int ri_child_user_time; + + @ffi.Uint64() + external int ri_child_system_time; + + @ffi.Uint64() + external int ri_child_pkg_idle_wkups; + + @ffi.Uint64() + external int ri_child_interrupt_wkups; + + @ffi.Uint64() + external int ri_child_pageins; + + @ffi.Uint64() + external int ri_child_elapsed_abstime; + + @ffi.Uint64() + external int ri_diskio_bytesread; + + @ffi.Uint64() + external int ri_diskio_byteswritten; + + @ffi.Uint64() + external int ri_cpu_time_qos_default; + + @ffi.Uint64() + external int ri_cpu_time_qos_maintenance; + + @ffi.Uint64() + external int ri_cpu_time_qos_background; + + @ffi.Uint64() + external int ri_cpu_time_qos_utility; + + @ffi.Uint64() + external int ri_cpu_time_qos_legacy; + + @ffi.Uint64() + external int ri_cpu_time_qos_user_initiated; + + @ffi.Uint64() + external int ri_cpu_time_qos_user_interactive; + + @ffi.Uint64() + external int ri_billed_system_time; + + @ffi.Uint64() + external int ri_serviced_system_time; + + @ffi.Uint64() + external int ri_logical_writes; + + @ffi.Uint64() + external int ri_lifetime_max_phys_footprint; + + @ffi.Uint64() + external int ri_instructions; + + @ffi.Uint64() + external int ri_cycles; + + @ffi.Uint64() + external int ri_billed_energy; + + @ffi.Uint64() + external int ri_serviced_energy; + + @ffi.Uint64() + external int ri_interval_max_phys_footprint; + + @ffi.Uint64() + external int ri_runnable_time; + + @ffi.Uint64() + external int ri_flags; + + @ffi.Uint64() + external int ri_user_ptime; + + @ffi.Uint64() + external int ri_system_ptime; + + @ffi.Uint64() + external int ri_pinstructions; + + @ffi.Uint64() + external int ri_pcycles; + + @ffi.Uint64() + external int ri_energy_nj; + + @ffi.Uint64() + external int ri_penergy_nj; + + @ffi.Uint64() + external int ri_secure_time_in_system; + + @ffi.Uint64() + external int ri_secure_ptime_in_system; + + @ffi.Uint64() + external int ri_neural_footprint; + + @ffi.Uint64() + external int ri_lifetime_max_neural_footprint; + + @ffi.Uint64() + external int ri_interval_max_neural_footprint; + + @ffi.Array.multi([9]) + external ffi.Array ri_reserved; +} + +typedef rusage_info_current = rusage_info_v6; + +final class rlimit extends ffi.Struct { + @rlim_t() + external int rlim_cur; + + @rlim_t() + external int rlim_max; +} + +final class proc_rlimit_control_wakeupmon extends ffi.Struct { + @ffi.Uint32() + external int wm_flags; + + @ffi.Int32() + external int wm_rate; +} + +final class wait$1 extends ffi.Opaque {} + +typedef ct_rune_t = __darwin_ct_rune_t; +typedef rune_t = __darwin_rune_t; + +final class div_t extends ffi.Struct { + @ffi.Int() + external int quot; + + @ffi.Int() + external int rem; +} + +final class ldiv_t extends ffi.Struct { + @ffi.Long() + external int quot; + + @ffi.Long() + external int rem; +} - int _mbstowcs_s_l( - ffi.Pointer _PtNumOfCharConverted, - ffi.Pointer _DstBuf, - int _SizeInWords, - ffi.Pointer _SrcBuf, - int _MaxCount, - _locale_t _Locale, - ) { - return __mbstowcs_s_l( - _PtNumOfCharConverted, - _DstBuf, - _SizeInWords, - _SrcBuf, - _MaxCount, - _Locale, - ); - } +final class lldiv_t extends ffi.Struct { + @ffi.LongLong() + external int quot; - late final __mbstowcs_s_lPtr = - _lookup< - ffi.NativeFunction< - errno_t Function( - ffi.Pointer, - ffi.Pointer, - ffi.Size, - ffi.Pointer, - ffi.Size, - _locale_t, - ) - > - >('_mbstowcs_s_l'); - late final __mbstowcs_s_l = __mbstowcs_s_lPtr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer, - int, - ffi.Pointer, - int, - _locale_t, - ) - >(); + @ffi.LongLong() + external int rem; +} - int _mbstowcs_l( - ffi.Pointer _Dest, - ffi.Pointer _Source, - int _MaxCount, - _locale_t _Locale, - ) { - return __mbstowcs_l(_Dest, _Source, _MaxCount, _Locale); - } +typedef malloc_type_id_t = ffi.UnsignedLongLong; +typedef Dartmalloc_type_id_t = int; - late final __mbstowcs_lPtr = - _lookup< - ffi.NativeFunction< - ffi.Size Function( - ffi.Pointer, - ffi.Pointer, - ffi.Size, - _locale_t, - ) - > - >('_mbstowcs_l'); - late final __mbstowcs_l = __mbstowcs_lPtr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer, - int, - _locale_t, - ) - >(); +final class _malloc_zone_t extends ffi.Opaque {} - int wctomb(ffi.Pointer _MbCh, int _WCh) { - return _wctomb(_MbCh, _WCh); - } +typedef malloc_zone_t = _malloc_zone_t; +typedef dev_t = __darwin_dev_t; +typedef mode_t = __darwin_mode_t; +typedef GoInt8 = ffi.SignedChar; +typedef DartGoInt8 = int; +typedef GoUint8 = ffi.UnsignedChar; +typedef DartGoUint8 = int; +typedef GoInt16 = ffi.Short; +typedef DartGoInt16 = int; +typedef GoUint16 = ffi.UnsignedShort; +typedef DartGoUint16 = int; +typedef GoInt32 = ffi.Int; +typedef DartGoInt32 = int; +typedef GoUint32 = ffi.UnsignedInt; +typedef DartGoUint32 = int; +typedef GoInt64 = ffi.LongLong; +typedef DartGoInt64 = int; +typedef GoUint64 = ffi.UnsignedLongLong; +typedef DartGoUint64 = int; +typedef GoInt = GoInt64; +typedef GoUint = GoUint64; +typedef GoUintptr = ffi.Size; +typedef DartGoUintptr = int; +typedef GoFloat32 = ffi.Float; +typedef DartGoFloat32 = double; +typedef GoFloat64 = ffi.Double; +typedef DartGoFloat64 = double; +typedef GoString = _GoString_; +typedef GoMap = ffi.Pointer; +typedef GoChan = ffi.Pointer; - late final _wctombPtr = - _lookup< - ffi.NativeFunction, ffi.WChar)> - >('wctomb'); - late final _wctomb = _wctombPtr - .asFunction, int)>(); +final class GoInterface extends ffi.Struct { + external ffi.Pointer t; - int _wctomb_l(ffi.Pointer _MbCh, int _WCh, _locale_t _Locale) { - return __wctomb_l(_MbCh, _WCh, _Locale); - } + external ffi.Pointer v; +} - late final __wctomb_lPtr = - _lookup< - ffi.NativeFunction< - ffi.Int Function(ffi.Pointer, ffi.WChar, _locale_t) - > - >('_wctomb_l'); - late final __wctomb_l = __wctomb_lPtr - .asFunction, int, _locale_t)>(); - - int wctomb_s( - ffi.Pointer _SizeConverted, - ffi.Pointer _MbCh, - int _SizeInBytes, - int _WCh, - ) { - return _wctomb_s(_SizeConverted, _MbCh, _SizeInBytes, _WCh); - } +final class GoSlice extends ffi.Struct { + external ffi.Pointer data; - late final _wctomb_sPtr = - _lookup< - ffi.NativeFunction< - errno_t Function( - ffi.Pointer, - ffi.Pointer, - rsize_t, - ffi.WChar, - ) - > - >('wctomb_s'); - late final _wctomb_s = _wctomb_sPtr - .asFunction< - int Function(ffi.Pointer, ffi.Pointer, int, int) - >(); + @GoInt() + external int len; - int _wctomb_s_l( - ffi.Pointer _SizeConverted, - ffi.Pointer _MbCh, - int _SizeInBytes, - int _WCh, - _locale_t _Locale, - ) { - return __wctomb_s_l(_SizeConverted, _MbCh, _SizeInBytes, _WCh, _Locale); - } + @GoInt() + external int cap; +} - late final __wctomb_s_lPtr = - _lookup< - ffi.NativeFunction< - errno_t Function( - ffi.Pointer, - ffi.Pointer, - ffi.Size, - ffi.WChar, - _locale_t, - ) - > - >('_wctomb_s_l'); - late final __wctomb_s_l = __wctomb_s_lPtr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer, - int, - int, - _locale_t, - ) - >(); +const int __has_safe_buffers = 1; - int wcstombs_s( - ffi.Pointer _PtNumOfCharConverted, - ffi.Pointer _Dst, - int _DstSizeInBytes, - ffi.Pointer _Src, - int _MaxCountInBytes, - ) { - return _wcstombs_s( - _PtNumOfCharConverted, - _Dst, - _DstSizeInBytes, - _Src, - _MaxCountInBytes, - ); - } +const int __DARWIN_ONLY_64_BIT_INO_T = 1; - late final _wcstombs_sPtr = - _lookup< - ffi.NativeFunction< - errno_t Function( - ffi.Pointer, - ffi.Pointer, - ffi.Size, - ffi.Pointer, - ffi.Size, - ) - > - >('wcstombs_s'); - late final _wcstombs_s = _wcstombs_sPtr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer, - int, - ffi.Pointer, - int, - ) - >(); +const int __DARWIN_ONLY_UNIX_CONFORMANCE = 1; - int wcstombs( - ffi.Pointer _Dest, - ffi.Pointer _Source, - int _MaxCount, - ) { - return _wcstombs(_Dest, _Source, _MaxCount); - } +const int __DARWIN_ONLY_VERS_1050 = 1; - late final _wcstombsPtr = - _lookup< - ffi.NativeFunction< - ffi.Size Function( - ffi.Pointer, - ffi.Pointer, - ffi.Size, - ) - > - >('wcstombs'); - late final _wcstombs = _wcstombsPtr - .asFunction< - int Function(ffi.Pointer, ffi.Pointer, int) - >(); +const int __DARWIN_UNIX03 = 1; + +const int __DARWIN_64_BIT_INO_T = 1; + +const int __DARWIN_VERS_1050 = 1; + +const int __DARWIN_NON_CANCELABLE = 0; + +const String __DARWIN_SUF_EXTSN = '\$DARWIN_EXTSN'; + +const int __DARWIN_C_ANSI = 4096; + +const int __DARWIN_C_FULL = 900000; + +const int __DARWIN_C_LEVEL = 900000; + +const int __STDC_WANT_LIB_EXT1__ = 1; + +const int __DARWIN_NO_LONG_LONG = 0; + +const int _DARWIN_FEATURE_64_BIT_INODE = 1; + +const int _DARWIN_FEATURE_ONLY_64_BIT_INODE = 1; + +const int _DARWIN_FEATURE_ONLY_VERS_1050 = 1; + +const int _DARWIN_FEATURE_ONLY_UNIX_CONFORMANCE = 1; + +const int _DARWIN_FEATURE_UNIX_CONFORMANCE = 3; + +const int __has_ptrcheck = 0; + +const int __has_bounds_safety_attributes = 0; + +const int __DARWIN_NULL = 0; + +const int __PTHREAD_SIZE__ = 8176; + +const int __PTHREAD_ATTR_SIZE__ = 56; + +const int __PTHREAD_MUTEXATTR_SIZE__ = 8; + +const int __PTHREAD_MUTEX_SIZE__ = 56; + +const int __PTHREAD_CONDATTR_SIZE__ = 8; + +const int __PTHREAD_COND_SIZE__ = 40; + +const int __PTHREAD_ONCE_SIZE__ = 8; + +const int __PTHREAD_RWLOCK_SIZE__ = 192; + +const int __PTHREAD_RWLOCKATTR_SIZE__ = 16; + +const int __DARWIN_WCHAR_MAX = 2147483647; + +const int __DARWIN_WCHAR_MIN = -2147483648; + +const int __DARWIN_WEOF = -1; + +const int _FORTIFY_SOURCE = 2; + +const int NULL = 0; + +const int USER_ADDR_NULL = 0; + +const int __API_TO_BE_DEPRECATED = 100000; + +const int __API_TO_BE_DEPRECATED_MACOS = 100000; + +const int __API_TO_BE_DEPRECATED_MACOSAPPLICATIONEXTENSION = 100000; + +const int __API_TO_BE_DEPRECATED_IOS = 100000; + +const int __API_TO_BE_DEPRECATED_IOSAPPLICATIONEXTENSION = 100000; + +const int __API_TO_BE_DEPRECATED_MACCATALYST = 100000; + +const int __API_TO_BE_DEPRECATED_MACCATALYSTAPPLICATIONEXTENSION = 100000; + +const int __API_TO_BE_DEPRECATED_WATCHOS = 100000; + +const int __API_TO_BE_DEPRECATED_WATCHOSAPPLICATIONEXTENSION = 100000; + +const int __API_TO_BE_DEPRECATED_TVOS = 100000; + +const int __API_TO_BE_DEPRECATED_TVOSAPPLICATIONEXTENSION = 100000; + +const int __API_TO_BE_DEPRECATED_DRIVERKIT = 100000; + +const int __API_TO_BE_DEPRECATED_VISIONOS = 100000; + +const int __API_TO_BE_DEPRECATED_VISIONOSAPPLICATIONEXTENSION = 100000; + +const int __API_TO_BE_DEPRECATED_KERNELKIT = 100000; + +const int __MAC_10_0 = 1000; + +const int __MAC_10_1 = 1010; + +const int __MAC_10_2 = 1020; + +const int __MAC_10_3 = 1030; + +const int __MAC_10_4 = 1040; + +const int __MAC_10_5 = 1050; + +const int __MAC_10_6 = 1060; + +const int __MAC_10_7 = 1070; + +const int __MAC_10_8 = 1080; + +const int __MAC_10_9 = 1090; + +const int __MAC_10_10 = 101000; + +const int __MAC_10_10_2 = 101002; + +const int __MAC_10_10_3 = 101003; + +const int __MAC_10_11 = 101100; + +const int __MAC_10_11_2 = 101102; + +const int __MAC_10_11_3 = 101103; + +const int __MAC_10_11_4 = 101104; + +const int __MAC_10_12 = 101200; + +const int __MAC_10_12_1 = 101201; + +const int __MAC_10_12_2 = 101202; + +const int __MAC_10_12_4 = 101204; + +const int __MAC_10_13 = 101300; + +const int __MAC_10_13_1 = 101301; + +const int __MAC_10_13_2 = 101302; + +const int __MAC_10_13_4 = 101304; + +const int __MAC_10_14 = 101400; + +const int __MAC_10_14_1 = 101401; + +const int __MAC_10_14_4 = 101404; + +const int __MAC_10_14_5 = 101405; + +const int __MAC_10_14_6 = 101406; + +const int __MAC_10_15 = 101500; + +const int __MAC_10_15_1 = 101501; + +const int __MAC_10_15_4 = 101504; + +const int __MAC_10_16 = 101600; + +const int __MAC_11_0 = 110000; + +const int __MAC_11_1 = 110100; + +const int __MAC_11_3 = 110300; + +const int __MAC_11_4 = 110400; + +const int __MAC_11_5 = 110500; + +const int __MAC_11_6 = 110600; + +const int __MAC_12_0 = 120000; + +const int __MAC_12_1 = 120100; + +const int __MAC_12_2 = 120200; + +const int __MAC_12_3 = 120300; + +const int __MAC_12_4 = 120400; + +const int __MAC_12_5 = 120500; + +const int __MAC_12_6 = 120600; + +const int __MAC_12_7 = 120700; + +const int __MAC_13_0 = 130000; + +const int __MAC_13_1 = 130100; + +const int __MAC_13_2 = 130200; + +const int __MAC_13_3 = 130300; + +const int __MAC_13_4 = 130400; + +const int __MAC_13_5 = 130500; + +const int __MAC_13_6 = 130600; + +const int __MAC_13_7 = 130700; + +const int __MAC_14_0 = 140000; - int _wcstombs_s_l( - ffi.Pointer _PtNumOfCharConverted, - ffi.Pointer _Dst, - int _DstSizeInBytes, - ffi.Pointer _Src, - int _MaxCountInBytes, - _locale_t _Locale, - ) { - return __wcstombs_s_l( - _PtNumOfCharConverted, - _Dst, - _DstSizeInBytes, - _Src, - _MaxCountInBytes, - _Locale, - ); - } +const int __MAC_14_1 = 140100; - late final __wcstombs_s_lPtr = - _lookup< - ffi.NativeFunction< - errno_t Function( - ffi.Pointer, - ffi.Pointer, - ffi.Size, - ffi.Pointer, - ffi.Size, - _locale_t, - ) - > - >('_wcstombs_s_l'); - late final __wcstombs_s_l = __wcstombs_s_lPtr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer, - int, - ffi.Pointer, - int, - _locale_t, - ) - >(); +const int __MAC_14_2 = 140200; - int _wcstombs_l( - ffi.Pointer _Dest, - ffi.Pointer _Source, - int _MaxCount, - _locale_t _Locale, - ) { - return __wcstombs_l(_Dest, _Source, _MaxCount, _Locale); - } +const int __MAC_14_3 = 140300; - late final __wcstombs_lPtr = - _lookup< - ffi.NativeFunction< - ffi.Size Function( - ffi.Pointer, - ffi.Pointer, - ffi.Size, - _locale_t, - ) - > - >('_wcstombs_l'); - late final __wcstombs_l = __wcstombs_lPtr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer, - int, - _locale_t, - ) - >(); +const int __MAC_14_4 = 140400; - ffi.Pointer _fullpath( - ffi.Pointer _Buffer, - ffi.Pointer _Path, - int _BufferCount, - ) { - return __fullpath(_Buffer, _Path, _BufferCount); - } +const int __MAC_14_5 = 140500; - late final __fullpathPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Size, - ) - > - >('_fullpath'); - late final __fullpath = __fullpathPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - int, - ) - >(); +const int __MAC_14_6 = 140600; - int _makepath_s( - ffi.Pointer _Buffer, - int _BufferCount, - ffi.Pointer _Drive, - ffi.Pointer _Dir, - ffi.Pointer _Filename, - ffi.Pointer _Ext, - ) { - return __makepath_s(_Buffer, _BufferCount, _Drive, _Dir, _Filename, _Ext); - } +const int __MAC_14_7 = 140700; - late final __makepath_sPtr = - _lookup< - ffi.NativeFunction< - errno_t Function( - ffi.Pointer, - ffi.Size, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - > - >('_makepath_s'); - late final __makepath_s = __makepath_sPtr - .asFunction< - int Function( - ffi.Pointer, - int, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __MAC_15_0 = 150000; - void _makepath( - ffi.Pointer _Buffer, - ffi.Pointer _Drive, - ffi.Pointer _Dir, - ffi.Pointer _Filename, - ffi.Pointer _Ext, - ) { - return __makepath(_Buffer, _Drive, _Dir, _Filename, _Ext); - } +const int __MAC_15_1 = 150100; - late final __makepathPtr = - _lookup< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - > - >('_makepath'); - late final __makepath = __makepathPtr - .asFunction< - void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __MAC_15_2 = 150200; - void _splitpath( - ffi.Pointer _FullPath, - ffi.Pointer _Drive, - ffi.Pointer _Dir, - ffi.Pointer _Filename, - ffi.Pointer _Ext, - ) { - return __splitpath(_FullPath, _Drive, _Dir, _Filename, _Ext); - } +const int __MAC_15_3 = 150300; - late final __splitpathPtr = - _lookup< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - > - >('_splitpath'); - late final __splitpath = __splitpathPtr - .asFunction< - void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __MAC_15_4 = 150400; - int _splitpath_s( - ffi.Pointer _FullPath, - ffi.Pointer _Drive, - int _DriveCount, - ffi.Pointer _Dir, - int _DirCount, - ffi.Pointer _Filename, - int _FilenameCount, - ffi.Pointer _Ext, - int _ExtCount, - ) { - return __splitpath_s( - _FullPath, - _Drive, - _DriveCount, - _Dir, - _DirCount, - _Filename, - _FilenameCount, - _Ext, - _ExtCount, - ); - } +const int __MAC_15_5 = 150500; - late final __splitpath_sPtr = - _lookup< - ffi.NativeFunction< - errno_t Function( - ffi.Pointer, - ffi.Pointer, - ffi.Size, - ffi.Pointer, - ffi.Size, - ffi.Pointer, - ffi.Size, - ffi.Pointer, - ffi.Size, - ) - > - >('_splitpath_s'); - late final __splitpath_s = __splitpath_sPtr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer, - int, - ffi.Pointer, - int, - ffi.Pointer, - int, - ffi.Pointer, - int, - ) - >(); +const int __MAC_15_6 = 150600; - int getenv_s( - ffi.Pointer _RequiredCount, - ffi.Pointer _Buffer, - int _BufferCount, - ffi.Pointer _VarName, - ) { - return _getenv_s(_RequiredCount, _Buffer, _BufferCount, _VarName); - } +const int __MAC_16_0 = 160000; - late final _getenv_sPtr = - _lookup< - ffi.NativeFunction< - errno_t Function( - ffi.Pointer, - ffi.Pointer, - rsize_t, - ffi.Pointer, - ) - > - >('getenv_s'); - late final _getenv_s = _getenv_sPtr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer, - int, - ffi.Pointer, - ) - >(); +const int __MAC_26_0 = 260000; - ffi.Pointer __p___argc() { - return ___p___argc(); - } +const int __MAC_26_1 = 260100; - late final ___p___argcPtr = - _lookup Function()>>( - '__p___argc', - ); - late final ___p___argc = ___p___argcPtr - .asFunction Function()>(); +const int __MAC_26_2 = 260200; - ffi.Pointer>> __p___argv() { - return ___p___argv(); - } +const int __MAC_26_3 = 260300; - late final ___p___argvPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer>> Function() - > - >('__p___argv'); - late final ___p___argv = ___p___argvPtr - .asFunction>> Function()>(); +const int __MAC_26_4 = 260400; - ffi.Pointer>> __p___wargv() { - return ___p___wargv(); - } +const int __IPHONE_2_0 = 20000; - late final ___p___wargvPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer>> Function() - > - >('__p___wargv'); - late final ___p___wargv = ___p___wargvPtr - .asFunction< - ffi.Pointer>> Function() - >(); +const int __IPHONE_2_1 = 20100; - ffi.Pointer>> __p__environ() { - return ___p__environ(); - } +const int __IPHONE_2_2 = 20200; - late final ___p__environPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer>> Function() - > - >('__p__environ'); - late final ___p__environ = ___p__environPtr - .asFunction>> Function()>(); +const int __IPHONE_3_0 = 30000; - ffi.Pointer>> __p__wenviron() { - return ___p__wenviron(); - } +const int __IPHONE_3_1 = 30100; - late final ___p__wenvironPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer>> Function() - > - >('__p__wenviron'); - late final ___p__wenviron = ___p__wenvironPtr - .asFunction< - ffi.Pointer>> Function() - >(); +const int __IPHONE_3_2 = 30200; - ffi.Pointer getenv(ffi.Pointer _VarName) { - return _getenv(_VarName); - } +const int __IPHONE_4_0 = 40000; - late final _getenvPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) - > - >('getenv'); - late final _getenv = _getenvPtr - .asFunction Function(ffi.Pointer)>(); +const int __IPHONE_4_1 = 40100; - int _dupenv_s( - ffi.Pointer> _Buffer, - ffi.Pointer _BufferCount, - ffi.Pointer _VarName, - ) { - return __dupenv_s(_Buffer, _BufferCount, _VarName); - } +const int __IPHONE_4_2 = 40200; - late final __dupenv_sPtr = - _lookup< - ffi.NativeFunction< - errno_t Function( - ffi.Pointer>, - ffi.Pointer, - ffi.Pointer, - ) - > - >('_dupenv_s'); - late final __dupenv_s = __dupenv_sPtr - .asFunction< - int Function( - ffi.Pointer>, - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __IPHONE_4_3 = 40300; - int system(ffi.Pointer _Command) { - return _system(_Command); - } +const int __IPHONE_5_0 = 50000; - late final _systemPtr = - _lookup)>>( - 'system', - ); - late final _system = _systemPtr - .asFunction)>(); +const int __IPHONE_5_1 = 50100; - int _putenv(ffi.Pointer _EnvString) { - return __putenv(_EnvString); - } +const int __IPHONE_6_0 = 60000; - late final __putenvPtr = - _lookup)>>( - '_putenv', - ); - late final __putenv = __putenvPtr - .asFunction)>(); +const int __IPHONE_6_1 = 60100; - int _putenv_s(ffi.Pointer _Name, ffi.Pointer _Value) { - return __putenv_s(_Name, _Value); - } +const int __IPHONE_7_0 = 70000; - late final __putenv_sPtr = - _lookup< - ffi.NativeFunction< - errno_t Function(ffi.Pointer, ffi.Pointer) - > - >('_putenv_s'); - late final __putenv_s = __putenv_sPtr - .asFunction, ffi.Pointer)>(); +const int __IPHONE_7_1 = 70100; - int _searchenv_s( - ffi.Pointer _Filename, - ffi.Pointer _VarName, - ffi.Pointer _Buffer, - int _BufferCount, - ) { - return __searchenv_s(_Filename, _VarName, _Buffer, _BufferCount); - } +const int __IPHONE_8_0 = 80000; - late final __searchenv_sPtr = - _lookup< - ffi.NativeFunction< - errno_t Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Size, - ) - > - >('_searchenv_s'); - late final __searchenv_s = __searchenv_sPtr - .asFunction< - int Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - int, - ) - >(); +const int __IPHONE_8_1 = 80100; - void _searchenv( - ffi.Pointer _Filename, - ffi.Pointer _VarName, - ffi.Pointer _Buffer, - ) { - return __searchenv(_Filename, _VarName, _Buffer); - } +const int __IPHONE_8_2 = 80200; - late final __searchenvPtr = - _lookup< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - > - >('_searchenv'); - late final __searchenv = __searchenvPtr - .asFunction< - void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __IPHONE_8_3 = 80300; + +const int __IPHONE_8_4 = 80400; - void _seterrormode(int _Mode) { - return __seterrormode(_Mode); - } +const int __IPHONE_9_0 = 90000; - late final __seterrormodePtr = - _lookup>('_seterrormode'); - late final __seterrormode = __seterrormodePtr - .asFunction(); +const int __IPHONE_9_1 = 90100; - void _beep(int _Frequency, int _Duration) { - return __beep(_Frequency, _Duration); - } +const int __IPHONE_9_2 = 90200; - late final __beepPtr = - _lookup< - ffi.NativeFunction - >('_beep'); - late final __beep = __beepPtr.asFunction(); +const int __IPHONE_9_3 = 90300; - void _sleep(int _Duration) { - return __sleep(_Duration); - } +const int __IPHONE_10_0 = 100000; - late final __sleepPtr = - _lookup>( - '_sleep', - ); - late final __sleep = __sleepPtr.asFunction(); +const int __IPHONE_10_1 = 100100; - ffi.Pointer ecvt( - double _Value, - int _DigitCount, - ffi.Pointer _PtDec, - ffi.Pointer _PtSign, - ) { - return _ecvt$1(_Value, _DigitCount, _PtDec, _PtSign); - } +const int __IPHONE_10_2 = 100200; - late final _ecvtPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Double, - ffi.Int, - ffi.Pointer, - ffi.Pointer, - ) - > - >('ecvt'); - late final _ecvt$1 = _ecvtPtr - .asFunction< - ffi.Pointer Function( - double, - int, - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __IPHONE_10_3 = 100300; - ffi.Pointer fcvt( - double _Value, - int _FractionalDigitCount, - ffi.Pointer _PtDec, - ffi.Pointer _PtSign, - ) { - return _fcvt$1(_Value, _FractionalDigitCount, _PtDec, _PtSign); - } +const int __IPHONE_11_0 = 110000; - late final _fcvtPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Double, - ffi.Int, - ffi.Pointer, - ffi.Pointer, - ) - > - >('fcvt'); - late final _fcvt$1 = _fcvtPtr - .asFunction< - ffi.Pointer Function( - double, - int, - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __IPHONE_11_1 = 110100; - ffi.Pointer gcvt( - double _Value, - int _DigitCount, - ffi.Pointer _DstBuf, - ) { - return _gcvt$1(_Value, _DigitCount, _DstBuf); - } +const int __IPHONE_11_2 = 110200; - late final _gcvtPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Double, - ffi.Int, - ffi.Pointer, - ) - > - >('gcvt'); - late final _gcvt$1 = _gcvtPtr - .asFunction< - ffi.Pointer Function(double, int, ffi.Pointer) - >(); +const int __IPHONE_11_3 = 110300; - ffi.Pointer itoa( - int _Value, - ffi.Pointer _Buffer, - int _Radix, - ) { - return _itoa$1(_Value, _Buffer, _Radix); - } +const int __IPHONE_11_4 = 110400; - late final _itoaPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Int, - ffi.Pointer, - ffi.Int, - ) - > - >('itoa'); - late final _itoa$1 = _itoaPtr - .asFunction< - ffi.Pointer Function(int, ffi.Pointer, int) - >(); +const int __IPHONE_12_0 = 120000; - ffi.Pointer ltoa( - int _Value, - ffi.Pointer _Buffer, - int _Radix, - ) { - return _ltoa$1(_Value, _Buffer, _Radix); - } +const int __IPHONE_12_1 = 120100; - late final _ltoaPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Long, - ffi.Pointer, - ffi.Int, - ) - > - >('ltoa'); - late final _ltoa$1 = _ltoaPtr - .asFunction< - ffi.Pointer Function(int, ffi.Pointer, int) - >(); +const int __IPHONE_12_2 = 120200; - void swab( - ffi.Pointer _Buf1, - ffi.Pointer _Buf2, - int _SizeInBytes, - ) { - return _swab$1(_Buf1, _Buf2, _SizeInBytes); - } +const int __IPHONE_12_3 = 120300; - late final _swabPtr = - _lookup< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Int, - ) - > - >('swab'); - late final _swab$1 = _swabPtr - .asFunction< - void Function(ffi.Pointer, ffi.Pointer, int) - >(); +const int __IPHONE_12_4 = 120400; - ffi.Pointer ultoa( - int _Value, - ffi.Pointer _Buffer, - int _Radix, - ) { - return _ultoa$1(_Value, _Buffer, _Radix); - } +const int __IPHONE_13_0 = 130000; - late final _ultoaPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.UnsignedLong, - ffi.Pointer, - ffi.Int, - ) - > - >('ultoa'); - late final _ultoa$1 = _ultoaPtr - .asFunction< - ffi.Pointer Function(int, ffi.Pointer, int) - >(); +const int __IPHONE_13_1 = 130100; - int putenv(ffi.Pointer _EnvString) { - return _putenv$1(_EnvString); - } +const int __IPHONE_13_2 = 130200; - late final _putenvPtr = - _lookup)>>( - 'putenv', - ); - late final _putenv$1 = _putenvPtr - .asFunction)>(); +const int __IPHONE_13_3 = 130300; - _onexit_t onexit(_onexit_t _Func) { - return _onexit$1(_Func); - } +const int __IPHONE_13_4 = 130400; - late final _onexitPtr = - _lookup>('onexit'); - late final _onexit$1 = _onexitPtr.asFunction<_onexit_t Function(_onexit_t)>(); +const int __IPHONE_13_5 = 130500; - double cabs(_Dcomplex _Z) { - return _cabs(_Z); - } +const int __IPHONE_13_6 = 130600; - late final _cabsPtr = - _lookup>('cabs'); - late final _cabs = _cabsPtr.asFunction(); +const int __IPHONE_13_7 = 130700; - _Dcomplex cacos(_Dcomplex _Z) { - return _cacos(_Z); - } +const int __IPHONE_14_0 = 140000; - late final _cacosPtr = - _lookup>('cacos'); - late final _cacos = _cacosPtr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_14_1 = 140100; - _Dcomplex cacosh(_Dcomplex _Z) { - return _cacosh(_Z); - } +const int __IPHONE_14_2 = 140200; - late final _cacoshPtr = - _lookup>('cacosh'); - late final _cacosh = _cacoshPtr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_14_3 = 140300; - double carg(_Dcomplex _Z) { - return _carg(_Z); - } +const int __IPHONE_14_5 = 140500; - late final _cargPtr = - _lookup>('carg'); - late final _carg = _cargPtr.asFunction(); +const int __IPHONE_14_6 = 140600; - _Dcomplex casin(_Dcomplex _Z) { - return _casin(_Z); - } +const int __IPHONE_14_7 = 140700; - late final _casinPtr = - _lookup>('casin'); - late final _casin = _casinPtr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_14_8 = 140800; - _Dcomplex casinh(_Dcomplex _Z) { - return _casinh(_Z); - } +const int __IPHONE_15_0 = 150000; - late final _casinhPtr = - _lookup>('casinh'); - late final _casinh = _casinhPtr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_15_1 = 150100; - _Dcomplex catan(_Dcomplex _Z) { - return _catan(_Z); - } +const int __IPHONE_15_2 = 150200; - late final _catanPtr = - _lookup>('catan'); - late final _catan = _catanPtr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_15_3 = 150300; - _Dcomplex catanh(_Dcomplex _Z) { - return _catanh(_Z); - } +const int __IPHONE_15_4 = 150400; - late final _catanhPtr = - _lookup>('catanh'); - late final _catanh = _catanhPtr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_15_5 = 150500; - _Dcomplex ccos(_Dcomplex _Z) { - return _ccos(_Z); - } +const int __IPHONE_15_6 = 150600; - late final _ccosPtr = - _lookup>('ccos'); - late final _ccos = _ccosPtr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_15_7 = 150700; - _Dcomplex ccosh(_Dcomplex _Z) { - return _ccosh(_Z); - } +const int __IPHONE_15_8 = 150800; - late final _ccoshPtr = - _lookup>('ccosh'); - late final _ccosh = _ccoshPtr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_16_0 = 160000; - _Dcomplex cexp(_Dcomplex _Z) { - return _cexp(_Z); - } +const int __IPHONE_16_1 = 160100; - late final _cexpPtr = - _lookup>('cexp'); - late final _cexp = _cexpPtr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_16_2 = 160200; - double cimag(_Dcomplex _Z) { - return _cimag(_Z); - } +const int __IPHONE_16_3 = 160300; - late final _cimagPtr = - _lookup>('cimag'); - late final _cimag = _cimagPtr.asFunction(); +const int __IPHONE_16_4 = 160400; - _Dcomplex clog(_Dcomplex _Z) { - return _clog(_Z); - } +const int __IPHONE_16_5 = 160500; - late final _clogPtr = - _lookup>('clog'); - late final _clog = _clogPtr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_16_6 = 160600; - _Dcomplex clog10(_Dcomplex _Z) { - return _clog10(_Z); - } +const int __IPHONE_16_7 = 160700; - late final _clog10Ptr = - _lookup>('clog10'); - late final _clog10 = _clog10Ptr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_17_0 = 170000; - _Dcomplex conj(_Dcomplex _Z) { - return _conj(_Z); - } +const int __IPHONE_17_1 = 170100; - late final _conjPtr = - _lookup>('conj'); - late final _conj = _conjPtr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_17_2 = 170200; - _Dcomplex cpow(_Dcomplex _X, _Dcomplex _Y) { - return _cpow(_X, _Y); - } +const int __IPHONE_17_3 = 170300; - late final _cpowPtr = - _lookup>( - 'cpow', - ); - late final _cpow = _cpowPtr - .asFunction<_Dcomplex Function(_Dcomplex, _Dcomplex)>(); +const int __IPHONE_17_4 = 170400; - _Dcomplex cproj(_Dcomplex _Z) { - return _cproj(_Z); - } +const int __IPHONE_17_5 = 170500; - late final _cprojPtr = - _lookup>('cproj'); - late final _cproj = _cprojPtr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_17_6 = 170600; - double creal(_Dcomplex _Z) { - return _creal(_Z); - } +const int __IPHONE_17_7 = 170700; - late final _crealPtr = - _lookup>('creal'); - late final _creal = _crealPtr.asFunction(); +const int __IPHONE_18_0 = 180000; - _Dcomplex csin(_Dcomplex _Z) { - return _csin(_Z); - } +const int __IPHONE_18_1 = 180100; - late final _csinPtr = - _lookup>('csin'); - late final _csin = _csinPtr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_18_2 = 180200; - _Dcomplex csinh(_Dcomplex _Z) { - return _csinh(_Z); - } +const int __IPHONE_18_3 = 180300; - late final _csinhPtr = - _lookup>('csinh'); - late final _csinh = _csinhPtr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_18_4 = 180400; - _Dcomplex csqrt(_Dcomplex _Z) { - return _csqrt(_Z); - } +const int __IPHONE_18_5 = 180500; - late final _csqrtPtr = - _lookup>('csqrt'); - late final _csqrt = _csqrtPtr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_18_6 = 180600; - _Dcomplex ctan(_Dcomplex _Z) { - return _ctan(_Z); - } +const int __IPHONE_19_0 = 190000; - late final _ctanPtr = - _lookup>('ctan'); - late final _ctan = _ctanPtr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_26_0 = 260000; - _Dcomplex ctanh(_Dcomplex _Z) { - return _ctanh(_Z); - } +const int __IPHONE_26_1 = 260100; - late final _ctanhPtr = - _lookup>('ctanh'); - late final _ctanh = _ctanhPtr.asFunction<_Dcomplex Function(_Dcomplex)>(); +const int __IPHONE_26_2 = 260200; - double norm(_Dcomplex _Z) { - return _norm(_Z); - } +const int __IPHONE_26_3 = 260300; - late final _normPtr = - _lookup>('norm'); - late final _norm = _normPtr.asFunction(); +const int __IPHONE_26_4 = 260400; - double cabsf(_Fcomplex _Z) { - return _cabsf(_Z); - } +const int __WATCHOS_1_0 = 10000; - late final _cabsfPtr = - _lookup>('cabsf'); - late final _cabsf = _cabsfPtr.asFunction(); +const int __WATCHOS_2_0 = 20000; - _Fcomplex cacosf(_Fcomplex _Z) { - return _cacosf(_Z); - } +const int __WATCHOS_2_1 = 20100; - late final _cacosfPtr = - _lookup>('cacosf'); - late final _cacosf = _cacosfPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_2_2 = 20200; - _Fcomplex cacoshf(_Fcomplex _Z) { - return _cacoshf(_Z); - } +const int __WATCHOS_3_0 = 30000; - late final _cacoshfPtr = - _lookup>('cacoshf'); - late final _cacoshf = _cacoshfPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_3_1 = 30100; - double cargf(_Fcomplex _Z) { - return _cargf(_Z); - } +const int __WATCHOS_3_1_1 = 30101; - late final _cargfPtr = - _lookup>('cargf'); - late final _cargf = _cargfPtr.asFunction(); +const int __WATCHOS_3_2 = 30200; - _Fcomplex casinf(_Fcomplex _Z) { - return _casinf(_Z); - } +const int __WATCHOS_4_0 = 40000; - late final _casinfPtr = - _lookup>('casinf'); - late final _casinf = _casinfPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_4_1 = 40100; - _Fcomplex casinhf(_Fcomplex _Z) { - return _casinhf(_Z); - } +const int __WATCHOS_4_2 = 40200; - late final _casinhfPtr = - _lookup>('casinhf'); - late final _casinhf = _casinhfPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_4_3 = 40300; - _Fcomplex catanf(_Fcomplex _Z) { - return _catanf(_Z); - } +const int __WATCHOS_5_0 = 50000; - late final _catanfPtr = - _lookup>('catanf'); - late final _catanf = _catanfPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_5_1 = 50100; - _Fcomplex catanhf(_Fcomplex _Z) { - return _catanhf(_Z); - } +const int __WATCHOS_5_2 = 50200; - late final _catanhfPtr = - _lookup>('catanhf'); - late final _catanhf = _catanhfPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_5_3 = 50300; - _Fcomplex ccosf(_Fcomplex _Z) { - return _ccosf(_Z); - } +const int __WATCHOS_6_0 = 60000; - late final _ccosfPtr = - _lookup>('ccosf'); - late final _ccosf = _ccosfPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_6_1 = 60100; - _Fcomplex ccoshf(_Fcomplex _Z) { - return _ccoshf(_Z); - } +const int __WATCHOS_6_2 = 60200; - late final _ccoshfPtr = - _lookup>('ccoshf'); - late final _ccoshf = _ccoshfPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_7_0 = 70000; - _Fcomplex cexpf(_Fcomplex _Z) { - return _cexpf(_Z); - } +const int __WATCHOS_7_1 = 70100; - late final _cexpfPtr = - _lookup>('cexpf'); - late final _cexpf = _cexpfPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_7_2 = 70200; - double cimagf(_Fcomplex _Z) { - return _cimagf(_Z); - } +const int __WATCHOS_7_3 = 70300; - late final _cimagfPtr = - _lookup>('cimagf'); - late final _cimagf = _cimagfPtr.asFunction(); +const int __WATCHOS_7_4 = 70400; - _Fcomplex clogf(_Fcomplex _Z) { - return _clogf(_Z); - } +const int __WATCHOS_7_5 = 70500; - late final _clogfPtr = - _lookup>('clogf'); - late final _clogf = _clogfPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_7_6 = 70600; - _Fcomplex clog10f(_Fcomplex _Z) { - return _clog10f(_Z); - } +const int __WATCHOS_8_0 = 80000; - late final _clog10fPtr = - _lookup>('clog10f'); - late final _clog10f = _clog10fPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_8_1 = 80100; - _Fcomplex conjf(_Fcomplex _Z) { - return _conjf(_Z); - } +const int __WATCHOS_8_3 = 80300; - late final _conjfPtr = - _lookup>('conjf'); - late final _conjf = _conjfPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_8_4 = 80400; - _Fcomplex cpowf(_Fcomplex _X, _Fcomplex _Y) { - return _cpowf(_X, _Y); - } +const int __WATCHOS_8_5 = 80500; - late final _cpowfPtr = - _lookup>( - 'cpowf', - ); - late final _cpowf = _cpowfPtr - .asFunction<_Fcomplex Function(_Fcomplex, _Fcomplex)>(); +const int __WATCHOS_8_6 = 80600; - _Fcomplex cprojf(_Fcomplex _Z) { - return _cprojf(_Z); - } +const int __WATCHOS_8_7 = 80700; - late final _cprojfPtr = - _lookup>('cprojf'); - late final _cprojf = _cprojfPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_8_8 = 80800; - double crealf(_Fcomplex _Z) { - return _crealf(_Z); - } +const int __WATCHOS_9_0 = 90000; - late final _crealfPtr = - _lookup>('crealf'); - late final _crealf = _crealfPtr.asFunction(); +const int __WATCHOS_9_1 = 90100; - _Fcomplex csinf(_Fcomplex _Z) { - return _csinf(_Z); - } +const int __WATCHOS_9_2 = 90200; - late final _csinfPtr = - _lookup>('csinf'); - late final _csinf = _csinfPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_9_3 = 90300; - _Fcomplex csinhf(_Fcomplex _Z) { - return _csinhf(_Z); - } +const int __WATCHOS_9_4 = 90400; - late final _csinhfPtr = - _lookup>('csinhf'); - late final _csinhf = _csinhfPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_9_5 = 90500; - _Fcomplex csqrtf(_Fcomplex _Z) { - return _csqrtf(_Z); - } +const int __WATCHOS_9_6 = 90600; - late final _csqrtfPtr = - _lookup>('csqrtf'); - late final _csqrtf = _csqrtfPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_10_0 = 100000; - _Fcomplex ctanf(_Fcomplex _Z) { - return _ctanf(_Z); - } +const int __WATCHOS_10_1 = 100100; - late final _ctanfPtr = - _lookup>('ctanf'); - late final _ctanf = _ctanfPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_10_2 = 100200; - _Fcomplex ctanhf(_Fcomplex _Z) { - return _ctanhf(_Z); - } +const int __WATCHOS_10_3 = 100300; - late final _ctanhfPtr = - _lookup>('ctanhf'); - late final _ctanhf = _ctanhfPtr.asFunction<_Fcomplex Function(_Fcomplex)>(); +const int __WATCHOS_10_4 = 100400; - double normf(_Fcomplex _Z) { - return _normf(_Z); - } +const int __WATCHOS_10_5 = 100500; - late final _normfPtr = - _lookup>('normf'); - late final _normf = _normfPtr.asFunction(); +const int __WATCHOS_10_6 = 100600; - _Dcomplex _Cbuild(double _Re, double _Im) { - return __Cbuild(_Re, _Im); - } +const int __WATCHOS_10_7 = 100700; - late final __CbuildPtr = - _lookup>( - '_Cbuild', - ); - late final __Cbuild = __CbuildPtr - .asFunction<_Dcomplex Function(double, double)>(); +const int __WATCHOS_11_0 = 110000; - _Dcomplex _Cmulcc(_Dcomplex _X, _Dcomplex _Y) { - return __Cmulcc(_X, _Y); - } +const int __WATCHOS_11_1 = 110100; - late final __CmulccPtr = - _lookup>( - '_Cmulcc', - ); - late final __Cmulcc = __CmulccPtr - .asFunction<_Dcomplex Function(_Dcomplex, _Dcomplex)>(); +const int __WATCHOS_11_2 = 110200; - _Dcomplex _Cmulcr(_Dcomplex _X, double _Y) { - return __Cmulcr(_X, _Y); - } +const int __WATCHOS_11_3 = 110300; - late final __CmulcrPtr = - _lookup>( - '_Cmulcr', - ); - late final __Cmulcr = __CmulcrPtr - .asFunction<_Dcomplex Function(_Dcomplex, double)>(); +const int __WATCHOS_11_4 = 110400; - _Fcomplex _FCbuild(double _Re, double _Im) { - return __FCbuild(_Re, _Im); - } +const int __WATCHOS_11_5 = 110500; - late final __FCbuildPtr = - _lookup>( - '_FCbuild', - ); - late final __FCbuild = __FCbuildPtr - .asFunction<_Fcomplex Function(double, double)>(); +const int __WATCHOS_11_6 = 110600; - _Fcomplex _FCmulcc(_Fcomplex _X, _Fcomplex _Y) { - return __FCmulcc(_X, _Y); - } +const int __WATCHOS_12_0 = 120000; - late final __FCmulccPtr = - _lookup>( - '_FCmulcc', - ); - late final __FCmulcc = __FCmulccPtr - .asFunction<_Fcomplex Function(_Fcomplex, _Fcomplex)>(); +const int __WATCHOS_26_0 = 260000; - _Fcomplex _FCmulcr(_Fcomplex _X, double _Y) { - return __FCmulcr(_X, _Y); - } +const int __WATCHOS_26_1 = 260100; - late final __FCmulcrPtr = - _lookup>( - '_FCmulcr', - ); - late final __FCmulcr = __FCmulcrPtr - .asFunction<_Fcomplex Function(_Fcomplex, double)>(); +const int __WATCHOS_26_2 = 260200; - ffi.Pointer getAppDataDir() { - return _getAppDataDir(); - } +const int __WATCHOS_26_3 = 260300; - late final _getAppDataDirPtr = - _lookup Function()>>( - 'getAppDataDir', - ); - late final _getAppDataDir = _getAppDataDirPtr - .asFunction Function()>(); +const int __WATCHOS_26_4 = 260400; - ffi.Pointer setup( - ffi.Pointer _logDir, - ffi.Pointer _dataDir, - ffi.Pointer _locale, - ffi.Pointer _env, - int logP, - int appsP, - int statusP, - int privateServerP, - int appEventP, - int consent, - ffi.Pointer api, - ) { - return _setup( - _logDir, - _dataDir, - _locale, - _env, - logP, - appsP, - statusP, - privateServerP, - appEventP, - consent, - api, - ); - } +const int __TVOS_9_0 = 90000; - late final _setupPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Int64, - ffi.Int64, - ffi.Int64, - ffi.Int64, - ffi.Int64, - ffi.Int, - ffi.Pointer, - ) - > - >('setup'); - late final _setup = _setupPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - int, - int, - int, - int, - int, - int, - ffi.Pointer, - ) - >(); +const int __TVOS_9_1 = 90100; + +const int __TVOS_9_2 = 90200; + +const int __TVOS_10_0 = 100000; + +const int __TVOS_10_0_1 = 100001; - ffi.Pointer updateTelemetryConsent(int consent) { - return _updateTelemetryConsent(consent); - } +const int __TVOS_10_1 = 100100; - late final _updateTelemetryConsentPtr = - _lookup Function(ffi.Int)>>( - 'updateTelemetryConsent', - ); - late final _updateTelemetryConsent = _updateTelemetryConsentPtr - .asFunction Function(int)>(); +const int __TVOS_10_2 = 100200; - int isTelemetryEnabled() { - return _isTelemetryEnabled(); - } +const int __TVOS_11_0 = 110000; - late final _isTelemetryEnabledPtr = - _lookup>('isTelemetryEnabled'); - late final _isTelemetryEnabled = _isTelemetryEnabledPtr - .asFunction(); +const int __TVOS_11_1 = 110100; - int isOAuthLogin() { - return _isOAuthLogin(); - } +const int __TVOS_11_2 = 110200; - late final _isOAuthLoginPtr = _lookup>( - 'isOAuthLogin', - ); - late final _isOAuthLogin = _isOAuthLoginPtr.asFunction(); +const int __TVOS_11_3 = 110300; - ffi.Pointer getOAuthProvider() { - return _getOAuthProvider(); - } +const int __TVOS_11_4 = 110400; - late final _getOAuthProviderPtr = - _lookup Function()>>( - 'getOAuthProvider', - ); - late final _getOAuthProvider = _getOAuthProviderPtr - .asFunction Function()>(); +const int __TVOS_12_0 = 120000; - ffi.Pointer availableFeatures() { - return _availableFeatures(); - } +const int __TVOS_12_1 = 120100; - late final _availableFeaturesPtr = - _lookup Function()>>( - 'availableFeatures', - ); - late final _availableFeatures = _availableFeaturesPtr - .asFunction Function()>(); +const int __TVOS_12_2 = 120200; - ffi.Pointer updateLocale(ffi.Pointer _locale) { - return _updateLocale(_locale); - } +const int __TVOS_12_3 = 120300; - late final _updateLocalePtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) - > - >('updateLocale'); - late final _updateLocale = _updateLocalePtr - .asFunction Function(ffi.Pointer)>(); +const int __TVOS_12_4 = 120400; - ffi.Pointer addSplitTunnelItem( - ffi.Pointer filterTypeC, - ffi.Pointer itemC, - ) { - return _addSplitTunnelItem(filterTypeC, itemC); - } +const int __TVOS_13_0 = 130000; - late final _addSplitTunnelItemPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - > - >('addSplitTunnelItem'); - late final _addSplitTunnelItem = _addSplitTunnelItemPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __TVOS_13_2 = 130200; - ffi.Pointer removeSplitTunnelItem( - ffi.Pointer filterTypeC, - ffi.Pointer itemC, - ) { - return _removeSplitTunnelItem(filterTypeC, itemC); - } +const int __TVOS_13_3 = 130300; - late final _removeSplitTunnelItemPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - > - >('removeSplitTunnelItem'); - late final _removeSplitTunnelItem = _removeSplitTunnelItemPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __TVOS_13_4 = 130400; - ffi.Pointer setSplitTunnelingEnabled(int enabled) { - return _setSplitTunnelingEnabled(enabled); - } +const int __TVOS_14_0 = 140000; - late final _setSplitTunnelingEnabledPtr = - _lookup Function(ffi.Int)>>( - 'setSplitTunnelingEnabled', - ); - late final _setSplitTunnelingEnabled = _setSplitTunnelingEnabledPtr - .asFunction Function(int)>(); +const int __TVOS_14_1 = 140100; - int isSplitTunnelingEnabled() { - return _isSplitTunnelingEnabled(); - } +const int __TVOS_14_2 = 140200; - late final _isSplitTunnelingEnabledPtr = - _lookup>( - 'isSplitTunnelingEnabled', - ); - late final _isSplitTunnelingEnabled = _isSplitTunnelingEnabledPtr - .asFunction(); +const int __TVOS_14_3 = 140300; - ffi.Pointer loadInstalledApps(ffi.Pointer dataDir) { - return _loadInstalledApps(dataDir); - } +const int __TVOS_14_5 = 140500; - late final _loadInstalledAppsPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) - > - >('loadInstalledApps'); - late final _loadInstalledApps = _loadInstalledAppsPtr - .asFunction Function(ffi.Pointer)>(); +const int __TVOS_14_6 = 140600; - ffi.Pointer loadInstalledAppIcon( - ffi.Pointer appPath, - ffi.Pointer iconPath, - ) { - return _loadInstalledAppIcon(appPath, iconPath); - } +const int __TVOS_14_7 = 140700; - late final _loadInstalledAppIconPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - > - >('loadInstalledAppIcon'); - late final _loadInstalledAppIcon = _loadInstalledAppIconPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __TVOS_15_0 = 150000; - ffi.Pointer getDataCapInfo() { - return _getDataCapInfo(); - } +const int __TVOS_15_1 = 150100; - late final _getDataCapInfoPtr = - _lookup Function()>>( - 'getDataCapInfo', - ); - late final _getDataCapInfo = _getDataCapInfoPtr - .asFunction Function()>(); +const int __TVOS_15_2 = 150200; - ffi.Pointer reportIssue( - ffi.Pointer emailC, - ffi.Pointer typeC, - ffi.Pointer descC, - ffi.Pointer deviceC, - ffi.Pointer modelC, - ffi.Pointer logPathC, - ffi.Pointer attachmentsJSONC, - ) { - return _reportIssue( - emailC, - typeC, - descC, - deviceC, - modelC, - logPathC, - attachmentsJSONC, - ); - } +const int __TVOS_15_3 = 150300; - late final _reportIssuePtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - > - >('reportIssue'); - late final _reportIssue = _reportIssuePtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __TVOS_15_4 = 150400; - ffi.Pointer getSelectedServerJSON() { - return _getSelectedServerJSON(); - } +const int __TVOS_15_5 = 150500; - late final _getSelectedServerJSONPtr = - _lookup Function()>>( - 'getSelectedServerJSON', - ); - late final _getSelectedServerJSON = _getSelectedServerJSONPtr - .asFunction Function()>(); +const int __TVOS_15_6 = 150600; - ffi.Pointer getAutoLocation() { - return _getAutoLocation(); - } +const int __TVOS_16_0 = 160000; - late final _getAutoLocationPtr = - _lookup Function()>>( - 'getAutoLocation', - ); - late final _getAutoLocation = _getAutoLocationPtr - .asFunction Function()>(); +const int __TVOS_16_1 = 160100; - ffi.Pointer isTagAvailable(ffi.Pointer _tag) { - return _isTagAvailable(_tag); - } +const int __TVOS_16_2 = 160200; - late final _isTagAvailablePtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) - > - >('isTagAvailable'); - late final _isTagAvailable = _isTagAvailablePtr - .asFunction Function(ffi.Pointer)>(); +const int __TVOS_16_3 = 160300; - ffi.Pointer getAvailableServers() { - return _getAvailableServers(); - } +const int __TVOS_16_4 = 160400; - late final _getAvailableServersPtr = - _lookup Function()>>( - 'getAvailableServers', - ); - late final _getAvailableServers = _getAvailableServersPtr - .asFunction Function()>(); +const int __TVOS_16_5 = 160500; - ffi.Pointer startVPN() { - return _startVPN(); - } +const int __TVOS_16_6 = 160600; - late final _startVPNPtr = - _lookup Function()>>('startVPN'); - late final _startVPN = _startVPNPtr - .asFunction Function()>(); +const int __TVOS_17_0 = 170000; + +const int __TVOS_17_1 = 170100; + +const int __TVOS_17_2 = 170200; + +const int __TVOS_17_3 = 170300; + +const int __TVOS_17_4 = 170400; + +const int __TVOS_17_5 = 170500; + +const int __TVOS_17_6 = 170600; + +const int __TVOS_18_0 = 180000; + +const int __TVOS_18_1 = 180100; + +const int __TVOS_18_2 = 180200; + +const int __TVOS_18_3 = 180300; + +const int __TVOS_18_4 = 180400; + +const int __TVOS_18_5 = 180500; + +const int __TVOS_18_6 = 180600; - ffi.Pointer stopVPN() { - return _stopVPN(); - } +const int __TVOS_19_0 = 190000; - late final _stopVPNPtr = - _lookup Function()>>('stopVPN'); - late final _stopVPN = _stopVPNPtr - .asFunction Function()>(); +const int __TVOS_26_0 = 260000; - ffi.Pointer connectToServer(ffi.Pointer _tag) { - return _connectToServer(_tag); - } +const int __TVOS_26_1 = 260100; - late final _connectToServerPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) - > - >('connectToServer'); - late final _connectToServer = _connectToServerPtr - .asFunction Function(ffi.Pointer)>(); +const int __TVOS_26_2 = 260200; - int isVPNConnected() { - return _isVPNConnected(); - } +const int __TVOS_26_3 = 260300; - late final _isVPNConnectedPtr = - _lookup>('isVPNConnected'); - late final _isVPNConnected = _isVPNConnectedPtr.asFunction(); +const int __TVOS_26_4 = 260400; - ffi.Pointer getUserData() { - return _getUserData(); - } +const int __BRIDGEOS_2_0 = 20000; - late final _getUserDataPtr = - _lookup Function()>>( - 'getUserData', - ); - late final _getUserData = _getUserDataPtr - .asFunction Function()>(); +const int __BRIDGEOS_3_0 = 30000; - ffi.Pointer fetchUserData() { - return _fetchUserData(); - } +const int __BRIDGEOS_3_1 = 30100; - late final _fetchUserDataPtr = - _lookup Function()>>( - 'fetchUserData', - ); - late final _fetchUserData = _fetchUserDataPtr - .asFunction Function()>(); +const int __BRIDGEOS_3_4 = 30400; - ffi.Pointer stripeSubscriptionPaymentRedirect( - ffi.Pointer subType, - ffi.Pointer _planId, - ffi.Pointer _email, - ffi.Pointer _idempotencyKey, - ) { - return _stripeSubscriptionPaymentRedirect( - subType, - _planId, - _email, - _idempotencyKey, - ); - } +const int __BRIDGEOS_4_0 = 40000; - late final _stripeSubscriptionPaymentRedirectPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - > - >('stripeSubscriptionPaymentRedirect'); - late final _stripeSubscriptionPaymentRedirect = - _stripeSubscriptionPaymentRedirectPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __BRIDGEOS_4_1 = 40100; - ffi.Pointer paymentRedirect( - ffi.Pointer _plan, - ffi.Pointer _provider, - ffi.Pointer _email, - ffi.Pointer _idempotencyKey, - ) { - return _paymentRedirect(_plan, _provider, _email, _idempotencyKey); - } +const int __BRIDGEOS_5_0 = 50000; - late final _paymentRedirectPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - > - >('paymentRedirect'); - late final _paymentRedirect = _paymentRedirectPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __BRIDGEOS_5_1 = 50100; - ffi.Pointer stripeBillingPortalUrl() { - return _stripeBillingPortalUrl(); - } +const int __BRIDGEOS_5_3 = 50300; - late final _stripeBillingPortalUrlPtr = - _lookup Function()>>( - 'stripeBillingPortalUrl', - ); - late final _stripeBillingPortalUrl = _stripeBillingPortalUrlPtr - .asFunction Function()>(); +const int __BRIDGEOS_6_0 = 60000; - ffi.Pointer plans() { - return _plans(); - } +const int __BRIDGEOS_6_2 = 60200; - late final _plansPtr = - _lookup Function()>>('plans'); - late final _plans = _plansPtr.asFunction Function()>(); +const int __BRIDGEOS_6_4 = 60400; - ffi.Pointer oauthLoginUrl(ffi.Pointer _provider) { - return _oauthLoginUrl(_provider); - } +const int __BRIDGEOS_6_5 = 60500; - late final _oauthLoginUrlPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) - > - >('oauthLoginUrl'); - late final _oauthLoginUrl = _oauthLoginUrlPtr - .asFunction Function(ffi.Pointer)>(); +const int __BRIDGEOS_6_6 = 60600; - ffi.Pointer oAuthLoginCallback(ffi.Pointer _oAuthToken) { - return _oAuthLoginCallback(_oAuthToken); - } +const int __BRIDGEOS_7_0 = 70000; - late final _oAuthLoginCallbackPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) - > - >('oAuthLoginCallback'); - late final _oAuthLoginCallback = _oAuthLoginCallbackPtr - .asFunction Function(ffi.Pointer)>(); +const int __BRIDGEOS_7_1 = 70100; - ffi.Pointer login( - ffi.Pointer _email, - ffi.Pointer _password, - ) { - return _login(_email, _password); - } +const int __BRIDGEOS_7_2 = 70200; - late final _loginPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - > - >('login'); - late final _login = _loginPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __BRIDGEOS_7_3 = 70300; - ffi.Pointer signup( - ffi.Pointer _email, - ffi.Pointer _password, - ) { - return _signup(_email, _password); - } +const int __BRIDGEOS_7_4 = 70400; - late final _signupPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - > - >('signup'); - late final _signup = _signupPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __BRIDGEOS_7_6 = 70600; - ffi.Pointer logout(ffi.Pointer _email) { - return _logout(_email); - } +const int __BRIDGEOS_8_0 = 80000; - late final _logoutPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) - > - >('logout'); - late final _logout = _logoutPtr - .asFunction Function(ffi.Pointer)>(); +const int __BRIDGEOS_8_1 = 80100; - ffi.Pointer startRecoveryByEmail(ffi.Pointer _email) { - return _startRecoveryByEmail(_email); - } +const int __BRIDGEOS_8_2 = 80200; - late final _startRecoveryByEmailPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) - > - >('startRecoveryByEmail'); - late final _startRecoveryByEmail = _startRecoveryByEmailPtr - .asFunction Function(ffi.Pointer)>(); +const int __BRIDGEOS_8_3 = 80300; - ffi.Pointer validateEmailRecoveryCode( - ffi.Pointer _email, - ffi.Pointer _code, - ) { - return _validateEmailRecoveryCode(_email, _code); - } +const int __BRIDGEOS_8_4 = 80400; - late final _validateEmailRecoveryCodePtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - > - >('validateEmailRecoveryCode'); - late final _validateEmailRecoveryCode = _validateEmailRecoveryCodePtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __BRIDGEOS_8_5 = 80500; - ffi.Pointer completeRecoveryByEmail( - ffi.Pointer _email, - ffi.Pointer _newPassword, - ffi.Pointer _code, - ) { - return _completeRecoveryByEmail(_email, _newPassword, _code); - } +const int __BRIDGEOS_8_6 = 80600; - late final _completeRecoveryByEmailPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - > - >('completeRecoveryByEmail'); - late final _completeRecoveryByEmail = _completeRecoveryByEmailPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __BRIDGEOS_9_0 = 90000; + +const int __BRIDGEOS_9_1 = 90100; + +const int __BRIDGEOS_9_2 = 90200; - ffi.Pointer removeDevice(ffi.Pointer deviceId) { - return _removeDevice(deviceId); - } +const int __BRIDGEOS_9_3 = 90300; - late final _removeDevicePtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) - > - >('removeDevice'); - late final _removeDevice = _removeDevicePtr - .asFunction Function(ffi.Pointer)>(); +const int __BRIDGEOS_9_4 = 90400; - ffi.Pointer referralAttachment( - ffi.Pointer _referralCode, - ) { - return _referralAttachment(_referralCode); - } +const int __BRIDGEOS_9_5 = 90500; - late final _referralAttachmentPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) - > - >('referralAttachment'); - late final _referralAttachment = _referralAttachmentPtr - .asFunction Function(ffi.Pointer)>(); +const int __BRIDGEOS_9_6 = 90600; - ffi.Pointer startChangeEmail( - ffi.Pointer _newEmail, - ffi.Pointer _password, - ) { - return _startChangeEmail(_newEmail, _password); - } +const int __BRIDGEOS_10_0 = 100000; - late final _startChangeEmailPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - > - >('startChangeEmail'); - late final _startChangeEmail = _startChangeEmailPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __BRIDGEOS_10_1 = 100100; - ffi.Pointer completeChangeEmail( - ffi.Pointer _newEmail, - ffi.Pointer _password, - ffi.Pointer _code, - ) { - return _completeChangeEmail(_newEmail, _password, _code); - } +const int __BRIDGEOS_10_2 = 100200; - late final _completeChangeEmailPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - > - >('completeChangeEmail'); - late final _completeChangeEmail = _completeChangeEmailPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __BRIDGEOS_10_3 = 100300; - ffi.Pointer deleteAccount( - ffi.Pointer _email, - ffi.Pointer _password, - ) { - return _deleteAccount(_email, _password); - } +const int __BRIDGEOS_10_4 = 100400; - late final _deleteAccountPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - > - >('deleteAccount'); - late final _deleteAccount = _deleteAccountPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __DRIVERKIT_19_0 = 190000; - ffi.Pointer activationCode( - ffi.Pointer _email, - ffi.Pointer _resellerCode, - ) { - return _activationCode(_email, _resellerCode); - } +const int __DRIVERKIT_20_0 = 200000; - late final _activationCodePtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - > - >('activationCode'); - late final _activationCode = _activationCodePtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __DRIVERKIT_21_0 = 210000; - void freeCString(ffi.Pointer cstr) { - return _freeCString(cstr); - } +const int __DRIVERKIT_22_0 = 220000; - late final _freeCStringPtr = - _lookup)>>( - 'freeCString', - ); - late final _freeCString = _freeCStringPtr - .asFunction)>(); +const int __DRIVERKIT_22_4 = 220400; - ffi.Pointer digitalOceanPrivateServer() { - return _digitalOceanPrivateServer(); - } +const int __DRIVERKIT_22_5 = 220500; - late final _digitalOceanPrivateServerPtr = - _lookup Function()>>( - 'digitalOceanPrivateServer', - ); - late final _digitalOceanPrivateServer = _digitalOceanPrivateServerPtr - .asFunction Function()>(); +const int __DRIVERKIT_22_6 = 220600; - ffi.Pointer googleCloudPrivateServer() { - return _googleCloudPrivateServer(); - } +const int __DRIVERKIT_23_0 = 230000; - late final _googleCloudPrivateServerPtr = - _lookup Function()>>( - 'googleCloudPrivateServer', - ); - late final _googleCloudPrivateServer = _googleCloudPrivateServerPtr - .asFunction Function()>(); +const int __DRIVERKIT_23_1 = 230100; - ffi.Pointer selectAccount(ffi.Pointer _account) { - return _selectAccount(_account); - } +const int __DRIVERKIT_23_2 = 230200; - late final _selectAccountPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) - > - >('selectAccount'); - late final _selectAccount = _selectAccountPtr - .asFunction Function(ffi.Pointer)>(); +const int __DRIVERKIT_23_3 = 230300; - ffi.Pointer selectProject(ffi.Pointer _project) { - return _selectProject(_project); - } +const int __DRIVERKIT_23_4 = 230400; - late final _selectProjectPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) - > - >('selectProject'); - late final _selectProject = _selectProjectPtr - .asFunction Function(ffi.Pointer)>(); +const int __DRIVERKIT_23_5 = 230500; - ffi.Pointer validateSession() { - return _validateSession(); - } +const int __DRIVERKIT_23_6 = 230600; - late final _validateSessionPtr = - _lookup Function()>>( - 'validateSession', - ); - late final _validateSession = _validateSessionPtr - .asFunction Function()>(); +const int __DRIVERKIT_24_0 = 240000; - ffi.Pointer startDepolyment( - ffi.Pointer _selectedLocation, - ffi.Pointer _serverName, - ) { - return _startDepolyment(_selectedLocation, _serverName); - } +const int __DRIVERKIT_24_1 = 240100; - late final _startDepolymentPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - > - >('startDepolyment'); - late final _startDepolyment = _startDepolymentPtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __DRIVERKIT_24_2 = 240200; - ffi.Pointer cancelDepolyment() { - return _cancelDepolyment(); - } +const int __DRIVERKIT_24_3 = 240300; - late final _cancelDepolymentPtr = - _lookup Function()>>( - 'cancelDepolyment', - ); - late final _cancelDepolyment = _cancelDepolymentPtr - .asFunction Function()>(); +const int __DRIVERKIT_24_4 = 240400; - ffi.Pointer addServerManagerInstance( - ffi.Pointer _ip, - ffi.Pointer _port, - ffi.Pointer _accessToken, - ffi.Pointer _tag, - ) { - return _addServerManagerInstance(_ip, _port, _accessToken, _tag); - } +const int __DRIVERKIT_24_5 = 240500; - late final _addServerManagerInstancePtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - > - >('addServerManagerInstance'); - late final _addServerManagerInstance = _addServerManagerInstancePtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __DRIVERKIT_24_6 = 240600; - ffi.Pointer inviteToServerManagerInstance( - ffi.Pointer _ip, - ffi.Pointer _port, - ffi.Pointer _accessToken, - ffi.Pointer _inviteName, - ) { - return _inviteToServerManagerInstance( - _ip, - _port, - _accessToken, - _inviteName, - ); - } +const int __DRIVERKIT_25_0 = 250000; + +const int __DRIVERKIT_25_1 = 250100; + +const int __DRIVERKIT_25_2 = 250200; + +const int __DRIVERKIT_25_3 = 250300; + +const int __DRIVERKIT_25_4 = 250400; + +const int __VISIONOS_1_0 = 10000; + +const int __VISIONOS_1_1 = 10100; + +const int __VISIONOS_1_2 = 10200; + +const int __VISIONOS_1_3 = 10300; + +const int __VISIONOS_2_0 = 20000; + +const int __VISIONOS_2_1 = 20100; + +const int __VISIONOS_2_2 = 20200; - late final _inviteToServerManagerInstancePtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - > - >('inviteToServerManagerInstance'); - late final _inviteToServerManagerInstance = _inviteToServerManagerInstancePtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __VISIONOS_2_3 = 20300; - ffi.Pointer revokeServerManagerInvite( - ffi.Pointer _ip, - ffi.Pointer _port, - ffi.Pointer _accessToken, - ffi.Pointer _inviteName, - ) { - return _revokeServerManagerInvite(_ip, _port, _accessToken, _inviteName); - } +const int __VISIONOS_2_4 = 20400; - late final _revokeServerManagerInvitePtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - > - >('revokeServerManagerInvite'); - late final _revokeServerManagerInvite = _revokeServerManagerInvitePtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int __VISIONOS_2_5 = 20500; - ffi.Pointer addServerBasedOnURLs( - ffi.Pointer _urls, - int _skipCertVerification, - ) { - return _addServerBasedOnURLs(_urls, _skipCertVerification); - } +const int __VISIONOS_2_6 = 20600; - late final _addServerBasedOnURLsPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer, ffi.Int) - > - >('addServerBasedOnURLs'); - late final _addServerBasedOnURLs = _addServerBasedOnURLsPtr - .asFunction Function(ffi.Pointer, int)>(); +const int __VISIONOS_3_0 = 30000; - ffi.Pointer setBlockAdsEnabled(int enabled) { - return _setBlockAdsEnabled(enabled); - } +const int __VISIONOS_26_0 = 260000; - late final _setBlockAdsEnabledPtr = - _lookup Function(ffi.Int)>>( - 'setBlockAdsEnabled', - ); - late final _setBlockAdsEnabled = _setBlockAdsEnabledPtr - .asFunction Function(int)>(); +const int __VISIONOS_26_1 = 260100; - int isBlockAdsEnabled() { - return _isBlockAdsEnabled(); - } +const int __VISIONOS_26_2 = 260200; - late final _isBlockAdsEnabledPtr = - _lookup>('isBlockAdsEnabled'); - late final _isBlockAdsEnabled = _isBlockAdsEnabledPtr - .asFunction(); +const int __VISIONOS_26_3 = 260300; - ffi.Pointer setPeerProxyEnabled(int enabled) { - return _setPeerProxyEnabled(enabled); - } +const int __VISIONOS_26_4 = 260400; - late final _setPeerProxyEnabledPtr = - _lookup Function(ffi.Int)>>( - 'setPeerProxyEnabled', - ); - late final _setPeerProxyEnabled = _setPeerProxyEnabledPtr - .asFunction Function(int)>(); +const int MAC_OS_X_VERSION_10_0 = 1000; - int isPeerProxyEnabled() { - return _isPeerProxyEnabled(); - } +const int MAC_OS_X_VERSION_10_1 = 1010; - late final _isPeerProxyEnabledPtr = - _lookup>('isPeerProxyEnabled'); - late final _isPeerProxyEnabled = _isPeerProxyEnabledPtr - .asFunction(); +const int MAC_OS_X_VERSION_10_2 = 1020; - ffi.Pointer setPeerManualPort(int port) { - return _setPeerManualPort(port); - } +const int MAC_OS_X_VERSION_10_3 = 1030; - late final _setPeerManualPortPtr = - _lookup Function(ffi.Int)>>( - 'setPeerManualPort', - ); - late final _setPeerManualPort = _setPeerManualPortPtr - .asFunction Function(int)>(); +const int MAC_OS_X_VERSION_10_4 = 1040; - int getPeerManualPort() { - return _getPeerManualPort(); - } +const int MAC_OS_X_VERSION_10_5 = 1050; - late final _getPeerManualPortPtr = - _lookup>('getPeerManualPort'); - late final _getPeerManualPort = _getPeerManualPortPtr - .asFunction(); +const int MAC_OS_X_VERSION_10_6 = 1060; - ffi.Pointer setUnboundedEnabled(int enabled) { - return _setUnboundedEnabled(enabled); - } +const int MAC_OS_X_VERSION_10_7 = 1070; - late final _setUnboundedEnabledPtr = - _lookup Function(ffi.Int)>>( - 'setUnboundedEnabled', - ); - late final _setUnboundedEnabled = _setUnboundedEnabledPtr - .asFunction Function(int)>(); +const int MAC_OS_X_VERSION_10_8 = 1080; - int isUnboundedEnabled() { - return _isUnboundedEnabled(); - } +const int MAC_OS_X_VERSION_10_9 = 1090; - late final _isUnboundedEnabledPtr = - _lookup>('isUnboundedEnabled'); - late final _isUnboundedEnabled = _isUnboundedEnabledPtr - .asFunction(); +const int MAC_OS_X_VERSION_10_10 = 101000; - ffi.Pointer setSmartRoutingEnabled(int enabled) { - return _setSmartRoutingEnabled(enabled); - } +const int MAC_OS_X_VERSION_10_10_2 = 101002; - late final _setSmartRoutingEnabledPtr = - _lookup Function(ffi.Int)>>( - 'setSmartRoutingEnabled', - ); - late final _setSmartRoutingEnabled = _setSmartRoutingEnabledPtr - .asFunction Function(int)>(); +const int MAC_OS_X_VERSION_10_10_3 = 101003; - int isSmartRoutingEnabled() { - return _isSmartRoutingEnabled(); - } +const int MAC_OS_X_VERSION_10_11 = 101100; - late final _isSmartRoutingEnabledPtr = - _lookup>('isSmartRoutingEnabled'); - late final _isSmartRoutingEnabled = _isSmartRoutingEnabledPtr - .asFunction(); +const int MAC_OS_X_VERSION_10_11_2 = 101102; - ffi.Pointer getSplitTunnelState() { - return _getSplitTunnelState(); - } +const int MAC_OS_X_VERSION_10_11_3 = 101103; - late final _getSplitTunnelStatePtr = - _lookup Function()>>( - 'getSplitTunnelState', - ); - late final _getSplitTunnelState = _getSplitTunnelStatePtr - .asFunction Function()>(); +const int MAC_OS_X_VERSION_10_11_4 = 101104; - ffi.Pointer getSplitTunnelItems(ffi.Pointer filterTypeC) { - return _getSplitTunnelItems(filterTypeC); - } +const int MAC_OS_X_VERSION_10_12 = 101200; - late final _getSplitTunnelItemsPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) - > - >('getSplitTunnelItems'); - late final _getSplitTunnelItems = _getSplitTunnelItemsPtr - .asFunction Function(ffi.Pointer)>(); +const int MAC_OS_X_VERSION_10_12_1 = 101201; - ffi.Pointer deletePrivateServerByName(ffi.Pointer _name) { - return _deletePrivateServerByName(_name); - } +const int MAC_OS_X_VERSION_10_12_2 = 101202; - late final _deletePrivateServerByNamePtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) - > - >('deletePrivateServerByName'); - late final _deletePrivateServerByName = _deletePrivateServerByNamePtr - .asFunction Function(ffi.Pointer)>(); +const int MAC_OS_X_VERSION_10_12_4 = 101204; - ffi.Pointer updatePrivateServerName( - ffi.Pointer _oldName, - ffi.Pointer _newName, - ) { - return _updatePrivateServerName(_oldName, _newName); - } +const int MAC_OS_X_VERSION_10_13 = 101300; - late final _updatePrivateServerNamePtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - > - >('updatePrivateServerName'); - late final _updatePrivateServerName = _updatePrivateServerNamePtr - .asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer, - ) - >(); +const int MAC_OS_X_VERSION_10_13_1 = 101301; - ffi.Pointer getEnabledApps() { - return _getEnabledApps(); - } +const int MAC_OS_X_VERSION_10_13_2 = 101302; - late final _getEnabledAppsPtr = - _lookup Function()>>( - 'getEnabledApps', - ); - late final _getEnabledApps = _getEnabledAppsPtr - .asFunction Function()>(); +const int MAC_OS_X_VERSION_10_13_4 = 101304; - ffi.Pointer patchSettings(ffi.Pointer patchJSON) { - return _patchSettings(patchJSON); - } +const int MAC_OS_X_VERSION_10_14 = 101400; - late final _patchSettingsPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) - > - >('patchSettings'); - late final _patchSettings = _patchSettingsPtr - .asFunction Function(ffi.Pointer)>(); +const int MAC_OS_X_VERSION_10_14_1 = 101401; - ffi.Pointer getSettings() { - return _getSettings(); - } +const int MAC_OS_X_VERSION_10_14_4 = 101404; - late final _getSettingsPtr = - _lookup Function()>>( - 'getSettings', - ); - late final _getSettings = _getSettingsPtr - .asFunction Function()>(); +const int MAC_OS_X_VERSION_10_14_5 = 101405; - ffi.Pointer patchEnvVars(ffi.Pointer patchJSON) { - return _patchEnvVars(patchJSON); - } +const int MAC_OS_X_VERSION_10_14_6 = 101406; - late final _patchEnvVarsPtr = - _lookup< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Pointer) - > - >('patchEnvVars'); - late final _patchEnvVars = _patchEnvVarsPtr - .asFunction Function(ffi.Pointer)>(); +const int MAC_OS_X_VERSION_10_15 = 101500; - ffi.Pointer getEnvVars() { - return _getEnvVars(); - } +const int MAC_OS_X_VERSION_10_15_1 = 101501; - late final _getEnvVarsPtr = - _lookup Function()>>( - 'getEnvVars', - ); - late final _getEnvVars = _getEnvVarsPtr - .asFunction Function()>(); +const int MAC_OS_X_VERSION_10_15_4 = 101504; - ffi.Pointer runURLTests() { - return _runURLTests(); - } +const int MAC_OS_X_VERSION_10_16 = 101600; - late final _runURLTestsPtr = - _lookup Function()>>( - 'runURLTests', - ); - late final _runURLTests = _runURLTestsPtr - .asFunction Function()>(); +const int MAC_OS_VERSION_11_0 = 110000; + +const int MAC_OS_VERSION_11_1 = 110100; + +const int MAC_OS_VERSION_11_3 = 110300; + +const int MAC_OS_VERSION_11_4 = 110400; - ffi.Pointer updateConfig() { - return _updateConfig(); - } +const int MAC_OS_VERSION_11_5 = 110500; - late final _updateConfigPtr = - _lookup Function()>>( - 'updateConfig', - ); - late final _updateConfig = _updateConfigPtr - .asFunction Function()>(); -} +const int MAC_OS_VERSION_11_6 = 110600; -typedef va_list = ffi.Pointer; -typedef ptrdiff_t = ffi.LongLong; -typedef Dartptrdiff_t = int; -typedef errno_t = ffi.Int; -typedef Darterrno_t = int; -typedef wint_t = ffi.UnsignedShort; -typedef Dartwint_t = int; -typedef wctype_t = ffi.UnsignedShort; -typedef Dartwctype_t = int; -typedef __time32_t = ffi.Long; -typedef Dart__time32_t = int; -typedef __time64_t = ffi.LongLong; -typedef Dart__time64_t = int; - -final class __crt_locale_data_public extends ffi.Struct { - external ffi.Pointer _locale_pctype; +const int MAC_OS_VERSION_12_0 = 120000; - @ffi.Int() - external int _locale_mb_cur_max; +const int MAC_OS_VERSION_12_1 = 120100; - @ffi.UnsignedInt() - external int _locale_lc_codepage; -} +const int MAC_OS_VERSION_12_2 = 120200; -final class __crt_locale_data extends ffi.Opaque {} +const int MAC_OS_VERSION_12_3 = 120300; -final class __crt_multibyte_data extends ffi.Opaque {} +const int MAC_OS_VERSION_12_4 = 120400; -final class __crt_locale_pointers extends ffi.Struct { - external ffi.Pointer<__crt_locale_data> locinfo; +const int MAC_OS_VERSION_12_5 = 120500; - external ffi.Pointer<__crt_multibyte_data> mbcinfo; -} +const int MAC_OS_VERSION_12_6 = 120600; -typedef _locale_t = ffi.Pointer<__crt_locale_pointers>; +const int MAC_OS_VERSION_12_7 = 120700; -final class _Mbstatet extends ffi.Struct { - @ffi.UnsignedLong() - external int _Wchar; +const int MAC_OS_VERSION_13_0 = 130000; - @ffi.UnsignedShort() - external int _Byte; +const int MAC_OS_VERSION_13_1 = 130100; - @ffi.UnsignedShort() - external int _State; -} +const int MAC_OS_VERSION_13_2 = 130200; -typedef mbstate_t = _Mbstatet; -typedef time_t = __time64_t; -typedef rsize_t = ffi.Size; -typedef Dartrsize_t = int; +const int MAC_OS_VERSION_13_3 = 130300; -final class _GoString_ extends ffi.Struct { - external ffi.Pointer p; +const int MAC_OS_VERSION_13_4 = 130400; - @ptrdiff_t() - external int n; -} +const int MAC_OS_VERSION_13_5 = 130500; -typedef _CoreCrtSecureSearchSortCompareFunctionFunction = - ffi.Int Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ); -typedef Dart_CoreCrtSecureSearchSortCompareFunctionFunction = - int Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ); -typedef _CoreCrtSecureSearchSortCompareFunction = - ffi.Pointer< - ffi.NativeFunction<_CoreCrtSecureSearchSortCompareFunctionFunction> - >; -typedef _CoreCrtNonSecureSearchSortCompareFunctionFunction = - ffi.Int Function(ffi.Pointer, ffi.Pointer); -typedef Dart_CoreCrtNonSecureSearchSortCompareFunctionFunction = - int Function(ffi.Pointer, ffi.Pointer); -typedef _CoreCrtNonSecureSearchSortCompareFunction = - ffi.Pointer< - ffi.NativeFunction<_CoreCrtNonSecureSearchSortCompareFunctionFunction> - >; -typedef _onexit_tFunction = ffi.Int Function(); -typedef Dart_onexit_tFunction = int Function(); -typedef _onexit_t = ffi.Pointer>; -typedef _purecall_handlerFunction = ffi.Void Function(); -typedef Dart_purecall_handlerFunction = void Function(); -typedef _purecall_handler = - ffi.Pointer>; -typedef _invalid_parameter_handlerFunction = - ffi.Void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.UnsignedInt, - ffi.UintPtr, - ); -typedef Dart_invalid_parameter_handlerFunction = - void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - int, - int, - ); -typedef _invalid_parameter_handler = - ffi.Pointer>; +const int MAC_OS_VERSION_13_6 = 130600; -final class _div_t extends ffi.Struct { - @ffi.Int() - external int quot; +const int MAC_OS_VERSION_13_7 = 130700; - @ffi.Int() - external int rem; -} +const int MAC_OS_VERSION_14_0 = 140000; -typedef div_t = _div_t; +const int MAC_OS_VERSION_14_1 = 140100; -final class _ldiv_t extends ffi.Struct { - @ffi.Long() - external int quot; +const int MAC_OS_VERSION_14_2 = 140200; - @ffi.Long() - external int rem; -} +const int MAC_OS_VERSION_14_3 = 140300; -typedef ldiv_t = _ldiv_t; +const int MAC_OS_VERSION_14_4 = 140400; -final class _lldiv_t extends ffi.Struct { - @ffi.LongLong() - external int quot; +const int MAC_OS_VERSION_14_5 = 140500; - @ffi.LongLong() - external int rem; -} +const int MAC_OS_VERSION_14_6 = 140600; -typedef lldiv_t = _lldiv_t; +const int MAC_OS_VERSION_14_7 = 140700; -final class _LDOUBLE extends ffi.Struct { - @ffi.Array.multi([10]) - external ffi.Array ld; -} +const int MAC_OS_VERSION_15_0 = 150000; -final class _CRT_DOUBLE extends ffi.Struct { - @ffi.Double() - external double x; -} +const int MAC_OS_VERSION_15_1 = 150100; -final class _CRT_FLOAT extends ffi.Struct { - @ffi.Float() - external double f; -} +const int MAC_OS_VERSION_15_2 = 150200; -final class _LONGDOUBLE extends ffi.Opaque {} +const int MAC_OS_VERSION_15_3 = 150300; -final class _LDBL12 extends ffi.Struct { - @ffi.Array.multi([12]) - external ffi.Array ld12; -} +const int MAC_OS_VERSION_15_4 = 150400; -typedef int_least8_t = ffi.SignedChar; -typedef Dartint_least8_t = int; -typedef int_least16_t = ffi.Short; -typedef Dartint_least16_t = int; -typedef int_least32_t = ffi.Int; -typedef Dartint_least32_t = int; -typedef int_least64_t = ffi.LongLong; -typedef Dartint_least64_t = int; -typedef uint_least8_t = ffi.UnsignedChar; -typedef Dartuint_least8_t = int; -typedef uint_least16_t = ffi.UnsignedShort; -typedef Dartuint_least16_t = int; -typedef uint_least32_t = ffi.UnsignedInt; -typedef Dartuint_least32_t = int; -typedef uint_least64_t = ffi.UnsignedLongLong; -typedef Dartuint_least64_t = int; -typedef int_fast8_t = ffi.SignedChar; -typedef Dartint_fast8_t = int; -typedef int_fast16_t = ffi.Int; -typedef Dartint_fast16_t = int; -typedef int_fast32_t = ffi.Int; -typedef Dartint_fast32_t = int; -typedef int_fast64_t = ffi.LongLong; -typedef Dartint_fast64_t = int; -typedef uint_fast8_t = ffi.UnsignedChar; -typedef Dartuint_fast8_t = int; -typedef uint_fast16_t = ffi.UnsignedInt; -typedef Dartuint_fast16_t = int; -typedef uint_fast32_t = ffi.UnsignedInt; -typedef Dartuint_fast32_t = int; -typedef uint_fast64_t = ffi.UnsignedLongLong; -typedef Dartuint_fast64_t = int; -typedef intmax_t = ffi.LongLong; -typedef Dartintmax_t = int; -typedef uintmax_t = ffi.UnsignedLongLong; -typedef Dartuintmax_t = int; -typedef GoInt8 = ffi.SignedChar; -typedef DartGoInt8 = int; -typedef GoUint8 = ffi.UnsignedChar; -typedef DartGoUint8 = int; -typedef GoInt16 = ffi.Short; -typedef DartGoInt16 = int; -typedef GoUint16 = ffi.UnsignedShort; -typedef DartGoUint16 = int; -typedef GoInt32 = ffi.Int; -typedef DartGoInt32 = int; -typedef GoUint32 = ffi.UnsignedInt; -typedef DartGoUint32 = int; -typedef GoInt64 = ffi.LongLong; -typedef DartGoInt64 = int; -typedef GoUint64 = ffi.UnsignedLongLong; -typedef DartGoUint64 = int; -typedef GoInt = GoInt64; -typedef GoUint = GoUint64; -typedef GoUintptr = ffi.Size; -typedef DartGoUintptr = int; -typedef GoFloat32 = ffi.Float; -typedef DartGoFloat32 = double; -typedef GoFloat64 = ffi.Double; -typedef DartGoFloat64 = double; +const int MAC_OS_VERSION_15_5 = 150500; -final class _C_double_complex extends ffi.Struct { - @ffi.Array.multi([2]) - external ffi.Array _Val; -} +const int MAC_OS_VERSION_15_6 = 150600; -final class _C_float_complex extends ffi.Struct { - @ffi.Array.multi([2]) - external ffi.Array _Val; -} +const int MAC_OS_VERSION_16_0 = 160000; -final class _C_ldouble_complex extends ffi.Opaque {} +const int MAC_OS_VERSION_26_0 = 260000; -typedef _Dcomplex = _C_double_complex; -typedef _Fcomplex = _C_float_complex; -typedef _Lcomplex = _C_ldouble_complex; -typedef GoComplex64 = _Fcomplex; -typedef GoComplex128 = _Dcomplex; -typedef GoString = _GoString_; -typedef GoMap = ffi.Pointer; -typedef GoChan = ffi.Pointer; +const int MAC_OS_VERSION_26_1 = 260100; -final class GoInterface extends ffi.Struct { - external ffi.Pointer t; +const int MAC_OS_VERSION_26_2 = 260200; - external ffi.Pointer v; -} +const int MAC_OS_VERSION_26_3 = 260300; -final class GoSlice extends ffi.Struct { - external ffi.Pointer data; +const int MAC_OS_VERSION_26_4 = 260400; - @GoInt() - external int len; +const int __AVAILABILITY_VERSIONS_VERSION_HASH = 93585900; - @GoInt() - external int cap; -} +const String __AVAILABILITY_VERSIONS_VERSION_STRING = 'Local'; -const int _VCRT_COMPILER_PREPROCESSOR = 1; +const String __AVAILABILITY_FILE = 'AvailabilityVersions.h'; -const int _SAL_VERSION = 20; +const int __MAC_OS_X_VERSION_MIN_REQUIRED = 260000; -const int __SAL_H_VERSION = 180000000; +const int __MAC_OS_X_VERSION_MAX_ALLOWED = 260400; -const int _USE_DECLSPECS_FOR_SAL = 0; +const int __ENABLE_LEGACY_MAC_AVAILABILITY = 1; -const int _USE_ATTRIBUTES_FOR_SAL = 0; +const int __DARWIN_NSIG = 32; -const int _CRT_PACKING = 8; +const int NSIG = 32; -const int _VCRUNTIME_DISABLED_WARNINGS = 4514; +const int _ARM_SIGNAL_ = 1; -const int _HAS_EXCEPTIONS = 1; +const int SIGHUP = 1; -const int _WCHAR_T_DEFINED = 1; +const int SIGINT = 2; -const int NULL = 0; +const int SIGQUIT = 3; -const int _HAS_CXX17 = 0; +const int SIGILL = 4; -const int _HAS_CXX20 = 0; +const int SIGTRAP = 5; -const int _HAS_CXX23 = 0; +const int SIGABRT = 6; -const int _HAS_NODISCARD = 1; +const int SIGIOT = 6; -const int _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE = 1; +const int SIGEMT = 7; -const int _CRT_BUILD_DESKTOP_APP = 1; +const int SIGFPE = 8; -const int _UCRT_DISABLED_WARNINGS = 4324; +const int SIGKILL = 9; -const int _ARGMAX = 100; +const int SIGBUS = 10; -const int _TRUNCATE = -1; +const int SIGSEGV = 11; -const int _CRT_INT_MAX = 2147483647; +const int SIGSYS = 12; -const int _CRT_SIZE_MAX = -1; +const int SIGPIPE = 13; -const String __FILEW__ = 'C'; +const int SIGALRM = 14; -const int _CRT_FUNCTIONS_REQUIRED = 1; +const int SIGTERM = 15; -const int _CRT_HAS_CXX17 = 0; +const int SIGURG = 16; -const int _CRT_HAS_C11 = 0; +const int SIGSTOP = 17; -const int _CRT_INTERNAL_NONSTDC_NAMES = 1; +const int SIGTSTP = 18; -const int __STDC_SECURE_LIB__ = 200411; +const int SIGCONT = 19; -const int __GOT_SECURE_LIB__ = 200411; +const int SIGCHLD = 20; -const int __STDC_WANT_SECURE_LIB__ = 1; +const int SIGTTIN = 21; -const int _SECURECRT_FILL_BUFFER_PATTERN = 254; +const int SIGTTOU = 22; -const int _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES = 0; +const int SIGIO = 23; -const int _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT = 0; +const int SIGXCPU = 24; -const int _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES = 1; +const int SIGXFSZ = 25; -const int _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_MEMORY = 0; +const int SIGVTALRM = 26; -const int _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES_MEMORY = 0; +const int SIGPROF = 27; -const int _MAX_ITOSTR_BASE16_COUNT = 9; +const int SIGWINCH = 28; -const int _MAX_ITOSTR_BASE10_COUNT = 12; +const int SIGINFO = 29; -const int _MAX_ITOSTR_BASE8_COUNT = 12; +const int SIGUSR1 = 30; -const int _MAX_ITOSTR_BASE2_COUNT = 33; +const int SIGUSR2 = 31; -const int _MAX_LTOSTR_BASE16_COUNT = 9; +const int __DARWIN_OPAQUE_ARM_THREAD_STATE64 = 0; -const int _MAX_LTOSTR_BASE10_COUNT = 12; +const int SIGEV_NONE = 0; -const int _MAX_LTOSTR_BASE8_COUNT = 12; +const int SIGEV_SIGNAL = 1; -const int _MAX_LTOSTR_BASE2_COUNT = 33; +const int SIGEV_THREAD = 3; -const int _MAX_ULTOSTR_BASE16_COUNT = 9; +const int SIGEV_KEVENT = 4; -const int _MAX_ULTOSTR_BASE10_COUNT = 11; +const int ILL_NOOP = 0; -const int _MAX_ULTOSTR_BASE8_COUNT = 12; +const int ILL_ILLOPC = 1; -const int _MAX_ULTOSTR_BASE2_COUNT = 33; +const int ILL_ILLTRP = 2; -const int _MAX_I64TOSTR_BASE16_COUNT = 17; +const int ILL_PRVOPC = 3; -const int _MAX_I64TOSTR_BASE10_COUNT = 21; +const int ILL_ILLOPN = 4; -const int _MAX_I64TOSTR_BASE8_COUNT = 23; +const int ILL_ILLADR = 5; -const int _MAX_I64TOSTR_BASE2_COUNT = 65; +const int ILL_PRVREG = 6; -const int _MAX_U64TOSTR_BASE16_COUNT = 17; +const int ILL_COPROC = 7; -const int _MAX_U64TOSTR_BASE10_COUNT = 21; +const int ILL_BADSTK = 8; -const int _MAX_U64TOSTR_BASE8_COUNT = 23; +const int FPE_NOOP = 0; -const int _MAX_U64TOSTR_BASE2_COUNT = 65; +const int FPE_FLTDIV = 1; -const int CHAR_BIT = 8; +const int FPE_FLTOVF = 2; -const int SCHAR_MIN = -128; +const int FPE_FLTUND = 3; -const int SCHAR_MAX = 127; +const int FPE_FLTRES = 4; -const int UCHAR_MAX = 255; +const int FPE_FLTINV = 5; -const int CHAR_MIN = -128; +const int FPE_FLTSUB = 6; -const int CHAR_MAX = 127; +const int FPE_INTDIV = 7; -const int MB_LEN_MAX = 5; +const int FPE_INTOVF = 8; -const int SHRT_MIN = -32768; +const int SEGV_NOOP = 0; -const int SHRT_MAX = 32767; +const int SEGV_MAPERR = 1; -const int USHRT_MAX = 65535; +const int SEGV_ACCERR = 2; -const int INT_MIN = -2147483648; +const int BUS_NOOP = 0; -const int INT_MAX = 2147483647; +const int BUS_ADRALN = 1; -const int UINT_MAX = 4294967295; +const int BUS_ADRERR = 2; -const int LONG_MIN = -2147483648; +const int BUS_OBJERR = 3; -const int LONG_MAX = 2147483647; +const int TRAP_BRKPT = 1; -const int ULONG_MAX = 4294967295; +const int TRAP_TRACE = 2; -const int LLONG_MAX = 9223372036854775807; +const int CLD_NOOP = 0; -const int LLONG_MIN = -9223372036854775808; +const int CLD_EXITED = 1; -const int ULLONG_MAX = -1; +const int CLD_KILLED = 2; -const int _I8_MIN = -128; +const int CLD_DUMPED = 3; -const int _I8_MAX = 127; +const int CLD_TRAPPED = 4; -const int _UI8_MAX = 255; +const int CLD_STOPPED = 5; -const int _I16_MIN = -32768; +const int CLD_CONTINUED = 6; -const int _I16_MAX = 32767; +const int POLL_IN = 1; -const int _UI16_MAX = 65535; +const int POLL_OUT = 2; -const int _I32_MIN = -2147483648; +const int POLL_MSG = 3; -const int _I32_MAX = 2147483647; +const int POLL_ERR = 4; -const int _UI32_MAX = 4294967295; +const int POLL_PRI = 5; -const int _I64_MIN = -9223372036854775808; +const int POLL_HUP = 6; -const int _I64_MAX = 9223372036854775807; +const int SA_ONSTACK = 1; -const int _UI64_MAX = -1; +const int SA_RESTART = 2; -const int SIZE_MAX = -1; +const int SA_RESETHAND = 4; -const int RSIZE_MAX = 9223372036854775807; +const int SA_NOCLDSTOP = 8; -const int EXIT_SUCCESS = 0; +const int SA_NODEFER = 16; -const int EXIT_FAILURE = 1; +const int SA_NOCLDWAIT = 32; -const int _WRITE_ABORT_MSG = 1; +const int SA_SIGINFO = 64; -const int _CALL_REPORTFAULT = 2; +const int SA_USERTRAMP = 256; -const int _OUT_TO_DEFAULT = 0; +const int SA_64REGSET = 512; -const int _OUT_TO_STDERR = 1; +const int SA_USERSPACE_MASK = 127; -const int _OUT_TO_MSGBOX = 2; +const int SIG_BLOCK = 1; -const int _REPORT_ERRMODE = 3; +const int SIG_UNBLOCK = 2; -const int RAND_MAX = 32767; +const int SIG_SETMASK = 3; -const int _CVTBUFSIZE = 349; +const int SI_USER = 65537; -const int _MAX_PATH = 260; +const int SI_QUEUE = 65538; -const int _MAX_DRIVE = 3; +const int SI_TIMER = 65539; -const int _MAX_DIR = 256; +const int SI_ASYNCIO = 65540; -const int _MAX_FNAME = 256; +const int SI_MESGQ = 65541; -const int _MAX_EXT = 256; +const int SS_ONSTACK = 1; -const int _MAX_ENV = 32767; +const int SS_DISABLE = 4; -const int INT8_MIN = -128; +const int MINSIGSTKSZ = 32768; -const int INT16_MIN = -32768; +const int SIGSTKSZ = 131072; -const int INT32_MIN = -2147483648; +const int SV_ONSTACK = 1; -const int INT64_MIN = -9223372036854775808; +const int SV_INTERRUPT = 2; + +const int SV_RESETHAND = 4; + +const int SV_NODEFER = 16; + +const int SV_NOCLDSTOP = 8; + +const int SV_SIGINFO = 64; + +const int __WORDSIZE = 64; const int INT8_MAX = 127; @@ -6981,6 +6240,14 @@ const int INT32_MAX = 2147483647; const int INT64_MAX = 9223372036854775807; +const int INT8_MIN = -128; + +const int INT16_MIN = -32768; + +const int INT32_MIN = -2147483648; + +const int INT64_MIN = -9223372036854775808; + const int UINT8_MAX = 255; const int UINT16_MAX = 65535; @@ -7015,7 +6282,7 @@ const int UINT_LEAST64_MAX = -1; const int INT_FAST8_MIN = -128; -const int INT_FAST16_MIN = -2147483648; +const int INT_FAST16_MIN = -32768; const int INT_FAST32_MIN = -2147483648; @@ -7023,7 +6290,7 @@ const int INT_FAST64_MIN = -9223372036854775808; const int INT_FAST8_MAX = 127; -const int INT_FAST16_MAX = 2147483647; +const int INT_FAST16_MAX = 32767; const int INT_FAST32_MAX = 2147483647; @@ -7031,36 +6298,276 @@ const int INT_FAST64_MAX = 9223372036854775807; const int UINT_FAST8_MAX = 255; -const int UINT_FAST16_MAX = 4294967295; +const int UINT_FAST16_MAX = 65535; const int UINT_FAST32_MAX = 4294967295; const int UINT_FAST64_MAX = -1; -const int INTPTR_MIN = -9223372036854775808; - const int INTPTR_MAX = 9223372036854775807; -const int UINTPTR_MAX = -1; +const int INTPTR_MIN = -9223372036854775808; -const int INTMAX_MIN = -9223372036854775808; +const int UINTPTR_MAX = -1; const int INTMAX_MAX = 9223372036854775807; const int UINTMAX_MAX = -1; +const int INTMAX_MIN = -9223372036854775808; + const int PTRDIFF_MIN = -9223372036854775808; const int PTRDIFF_MAX = 9223372036854775807; +const int SIZE_MAX = -1; + +const int RSIZE_MAX = 9223372036854775807; + +const int WCHAR_MAX = 2147483647; + +const int WCHAR_MIN = -2147483648; + +const int WINT_MIN = -2147483648; + +const int WINT_MAX = 2147483647; + const int SIG_ATOMIC_MIN = -2147483648; const int SIG_ATOMIC_MAX = 2147483647; -const int WCHAR_MIN = 0; +const int PRIO_PROCESS = 0; + +const int PRIO_PGRP = 1; + +const int PRIO_USER = 2; + +const int PRIO_DARWIN_THREAD = 3; + +const int PRIO_DARWIN_PROCESS = 4; + +const int PRIO_MIN = -20; + +const int PRIO_MAX = 20; + +const int PRIO_DARWIN_BG = 4096; + +const int PRIO_DARWIN_NONUI = 4097; + +const int RUSAGE_SELF = 0; + +const int RUSAGE_CHILDREN = -1; + +const int RUSAGE_INFO_V0 = 0; + +const int RUSAGE_INFO_V1 = 1; + +const int RUSAGE_INFO_V2 = 2; + +const int RUSAGE_INFO_V3 = 3; + +const int RUSAGE_INFO_V4 = 4; + +const int RUSAGE_INFO_V5 = 5; + +const int RUSAGE_INFO_V6 = 6; + +const int RUSAGE_INFO_CURRENT = 6; + +const int RU_PROC_RUNS_RESLIDE = 1; + +const int RLIM_INFINITY = 9223372036854775807; + +const int RLIM_SAVED_MAX = 9223372036854775807; + +const int RLIM_SAVED_CUR = 9223372036854775807; + +const int RLIMIT_CPU = 0; + +const int RLIMIT_FSIZE = 1; + +const int RLIMIT_DATA = 2; + +const int RLIMIT_STACK = 3; + +const int RLIMIT_CORE = 4; + +const int RLIMIT_AS = 5; + +const int RLIMIT_RSS = 5; + +const int RLIMIT_MEMLOCK = 6; + +const int RLIMIT_NPROC = 7; + +const int RLIMIT_NOFILE = 8; + +const int RLIM_NLIMITS = 9; + +const int _RLIMIT_POSIX_FLAG = 4096; + +const int RLIMIT_WAKEUPS_MONITOR = 1; + +const int RLIMIT_CPU_USAGE_MONITOR = 2; + +const int RLIMIT_THREAD_CPULIMITS = 3; + +const int RLIMIT_FOOTPRINT_INTERVAL = 4; + +const int WAKEMON_ENABLE = 1; + +const int WAKEMON_DISABLE = 2; + +const int WAKEMON_GET_PARAMS = 4; + +const int WAKEMON_SET_DEFAULTS = 8; + +const int WAKEMON_MAKE_FATAL = 16; + +const int CPUMON_MAKE_FATAL = 4096; + +const int FOOTPRINT_INTERVAL_RESET = 1; + +const int IOPOL_TYPE_DISK = 0; + +const int IOPOL_TYPE_VFS_ATIME_UPDATES = 2; + +const int IOPOL_TYPE_VFS_MATERIALIZE_DATALESS_FILES = 3; + +const int IOPOL_TYPE_VFS_STATFS_NO_DATA_VOLUME = 4; + +const int IOPOL_TYPE_VFS_TRIGGER_RESOLVE = 5; + +const int IOPOL_TYPE_VFS_IGNORE_CONTENT_PROTECTION = 6; + +const int IOPOL_TYPE_VFS_IGNORE_PERMISSIONS = 7; -const int WCHAR_MAX = 65535; +const int IOPOL_TYPE_VFS_SKIP_MTIME_UPDATE = 8; + +const int IOPOL_TYPE_VFS_ALLOW_LOW_SPACE_WRITES = 9; + +const int IOPOL_TYPE_VFS_DISALLOW_RW_FOR_O_EVTONLY = 10; + +const int IOPOL_TYPE_VFS_ENTITLED_RESERVE_ACCESS = 14; + +const int IOPOL_SCOPE_PROCESS = 0; + +const int IOPOL_SCOPE_THREAD = 1; + +const int IOPOL_SCOPE_DARWIN_BG = 2; + +const int IOPOL_DEFAULT = 0; + +const int IOPOL_IMPORTANT = 1; + +const int IOPOL_PASSIVE = 2; + +const int IOPOL_THROTTLE = 3; + +const int IOPOL_UTILITY = 4; + +const int IOPOL_STANDARD = 5; + +const int IOPOL_APPLICATION = 5; + +const int IOPOL_NORMAL = 1; + +const int IOPOL_ATIME_UPDATES_DEFAULT = 0; + +const int IOPOL_ATIME_UPDATES_OFF = 1; + +const int IOPOL_MATERIALIZE_DATALESS_FILES_DEFAULT = 0; + +const int IOPOL_MATERIALIZE_DATALESS_FILES_OFF = 1; + +const int IOPOL_MATERIALIZE_DATALESS_FILES_ON = 2; + +const int IOPOL_MATERIALIZE_DATALESS_FILES_ORIG = 4; + +const int IOPOL_MATERIALIZE_DATALESS_FILES_BASIC_MASK = 3; + +const int IOPOL_VFS_STATFS_NO_DATA_VOLUME_DEFAULT = 0; + +const int IOPOL_VFS_STATFS_FORCE_NO_DATA_VOLUME = 1; + +const int IOPOL_VFS_TRIGGER_RESOLVE_DEFAULT = 0; + +const int IOPOL_VFS_TRIGGER_RESOLVE_OFF = 1; + +const int IOPOL_VFS_CONTENT_PROTECTION_DEFAULT = 0; + +const int IOPOL_VFS_CONTENT_PROTECTION_IGNORE = 1; + +const int IOPOL_VFS_IGNORE_PERMISSIONS_OFF = 0; + +const int IOPOL_VFS_IGNORE_PERMISSIONS_ON = 1; + +const int IOPOL_VFS_SKIP_MTIME_UPDATE_OFF = 0; + +const int IOPOL_VFS_SKIP_MTIME_UPDATE_ON = 1; + +const int IOPOL_VFS_SKIP_MTIME_UPDATE_IGNORE = 2; + +const int IOPOL_VFS_ALLOW_LOW_SPACE_WRITES_OFF = 0; + +const int IOPOL_VFS_ALLOW_LOW_SPACE_WRITES_ON = 1; + +const int IOPOL_VFS_DISALLOW_RW_FOR_O_EVTONLY_DEFAULT = 0; + +const int IOPOL_VFS_DISALLOW_RW_FOR_O_EVTONLY_ON = 1; + +const int IOPOL_VFS_NOCACHE_WRITE_FS_BLKSIZE_DEFAULT = 0; + +const int IOPOL_VFS_NOCACHE_WRITE_FS_BLKSIZE_ON = 1; + +const int IOPOL_VFS_ENTITLED_RESERVE_ACCESS_OFF = 0; + +const int IOPOL_VFS_ENTITLED_RESERVE_ACCESS_ON = 1; + +const int WNOHANG = 1; + +const int WUNTRACED = 2; + +const int WCOREFLAG = 128; + +const int _WSTOPPED = 127; + +const int WEXITED = 4; + +const int WSTOPPED = 8; + +const int WCONTINUED = 16; + +const int WNOWAIT = 32; + +const int WAIT_ANY = -1; + +const int WAIT_MYPGRP = 0; + +const int _QUAD_HIGHWORD = 1; + +const int _QUAD_LOWWORD = 0; + +const int __DARWIN_LITTLE_ENDIAN = 1234; + +const int __DARWIN_BIG_ENDIAN = 4321; + +const int __DARWIN_PDP_ENDIAN = 3412; + +const int LITTLE_ENDIAN = 1234; + +const int BIG_ENDIAN = 4321; + +const int PDP_ENDIAN = 3412; + +const int __DARWIN_BYTE_ORDER = 1234; + +const int BYTE_ORDER = 1234; + +const int EXIT_FAILURE = 1; + +const int EXIT_SUCCESS = 0; -const int WINT_MIN = 0; +const int RAND_MAX = 2147483647; -const int WINT_MAX = 65535; +const int _MALLOC_TYPE_MALLOC_BACKDEPLOY_PUBLIC = 1; diff --git a/lib/lantern/lantern_platform_service.dart b/lib/lantern/lantern_platform_service.dart index 730d6ead68..ed94a15583 100644 --- a/lib/lantern/lantern_platform_service.dart +++ b/lib/lantern/lantern_platform_service.dart @@ -371,6 +371,21 @@ class LanternPlatformService implements LanternCoreService { } } + @override + Future> probeUPnP() async { + try { + // The Swift / Kotlin side calls Mobile.ProbeUPnP, which blocks + // up to ~6s on the M-SEARCH multicast scan. The MethodChannel + // call hops to a platform thread, so the Dart UI isolate isn't + // pinned during the wait. + final res = await _methodChannel.invokeMethod('probeUPnP'); + return right(res ?? false); + } catch (e, st) { + appLogger.error('probeUPnP failed', e, st); + return Left(e.toFailure()); + } + } + @override Future> isSmartRoutingEnabled() async { try { diff --git a/lib/lantern/lantern_service.dart b/lib/lantern/lantern_service.dart index 390109b54e..48c4eea31b 100644 --- a/lib/lantern/lantern_service.dart +++ b/lib/lantern/lantern_service.dart @@ -842,6 +842,14 @@ class LanternService implements LanternCoreService { return _platformService.isUnboundedEnabled(); } + @override + Future> probeUPnP() { + if (PlatformUtils.isFFISupported) { + return _ffiService.probeUPnP(); + } + return _platformService.probeUPnP(); + } + @override Future> isSmartRoutingEnabled() { if (PlatformUtils.isFFISupported) { From 5258c7db568d26a91e0043f2a4d133798b13f3ec Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Sun, 31 May 2026 16:29:04 -0600 Subject: [PATCH 25/37] =?UTF-8?q?smc:=20i18n=20the=20'On=20=E2=80=94=20tap?= =?UTF-8?q?=20to=20view'=20subtitle=20in=20VPN=20settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added share_my_connection_on_tap_to_view key to assets/locales/en.po and consumed it via .i18n. Matches the existing share_my_connection_subtitle pattern in the same conditional. Co-Authored-By: Claude Opus 4.7 --- assets/locales/en.po | 3 +++ lib/features/setting/vpn_setting.dart | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/assets/locales/en.po b/assets/locales/en.po index 2cbc270b7f..3a0aaf2c4e 100644 --- a/assets/locales/en.po +++ b/assets/locales/en.po @@ -557,6 +557,9 @@ msgstr "Share My Connection" msgid "share_my_connection_subtitle" msgstr "Let other Lantern users route through your connection to bypass censorship." +msgid "share_my_connection_on_tap_to_view" +msgstr "On — tap to view" + # Share My Connection screen — body / hero copy msgid "smc_intro" msgstr "Help others bypass censorship by sharing a small portion of your home internet connection. While sharing is on, traffic from users in censored regions will egress through your IP." diff --git a/lib/features/setting/vpn_setting.dart b/lib/features/setting/vpn_setting.dart index cfcac3afd7..12eb6e6b17 100644 --- a/lib/features/setting/vpn_setting.dart +++ b/lib/features/setting/vpn_setting.dart @@ -129,7 +129,7 @@ class VPNSetting extends HookConsumerWidget { label: 'share_my_connection'.i18n, subtitle: Text( peerProxy - ? 'On — tap to view' + ? 'share_my_connection_on_tap_to_view'.i18n : 'share_my_connection_subtitle'.i18n, style: textTheme.labelMedium!.copyWith( color: context.textTertiary, From 967c20171d10f3d04fdbd144ec9abfd6cf0effa1 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Sun, 31 May 2026 17:08:21 -0600 Subject: [PATCH 26/37] macos: wire probeUPnP MethodChannel handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round-2's UPnP probe FFI wiring added the Dart-side platform service that calls _methodChannel.invokeMethod('probeUPnP'), but forgot to add the corresponding case in MethodHandler.swift. Without this, the MethodChannel call throws MissingPluginException on macOS, the platform service returns Left(...), and ShareNotifier.toggle interprets that as 'UPnP unavailable' → falls straight through to Unbounded mode. macOS users would never reach the Full SmC path unless they set a manual port — directly contradicting the macOS test plan in the PR description. Mirrors the existing pattern for the peer-share / unbounded methods. Uses Task.detached so the up-to-6s M-SEARCH wait runs off the MainActor; delivers the bool back via MainActor.run for the Flutter result callback. Co-Authored-By: Claude Opus 4.7 --- macos/Runner/Handlers/MethodHandler.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/macos/Runner/Handlers/MethodHandler.swift b/macos/Runner/Handlers/MethodHandler.swift index 7c3600d938..84f5f4a9d2 100644 --- a/macos/Runner/Handlers/MethodHandler.swift +++ b/macos/Runner/Handlers/MethodHandler.swift @@ -275,6 +275,16 @@ class MethodHandler { await MainActor.run { result(MobileIsUnboundedEnabled()) } } + case "probeUPnP": + // UPnP M-SEARCH multicast wait — up to ~6s in MobileProbeUPnP. + // Hop off the main actor (the Task default executor) so the + // wait doesn't stall the UI, then deliver the bool back on + // MainActor for the Flutter result callback. + Task.detached { + let available = MobileProbeUPnP() + await MainActor.run { result(available) } + } + case "updateTelemetryEvents": guard let consent: Bool = self.decodeValue(from: call.arguments, result: result) else { return From 015cc1fb55d3e90f1a238228ec7bd0008a72c3b8 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Sun, 31 May 2026 17:40:46 -0600 Subject: [PATCH 27/37] mobile: wire SmC + Unbounded MethodChannel handlers for Android & iOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit macOS already had all seven SmC stack methods wired in its Runner's MethodHandler.swift, but Android (handler/MethodHandler.kt) and iOS (ios/Runner/Handlers/MethodHandler.swift) had zero coverage. With the unbounded tab gated on FeatureFlag.unbounded (server-side region check) rather than a platform check, mobile users in unbounded-enabled regions would tap the toggle and hit MissingPluginException for setUnboundedEnabled / probeUPnP / setPeerShareEnabled / setPeerManualPort — silent failures, no actual effect. The whole stack is exposed on mobile rather than desktop-only because manual port forwarding works on any home WiFi where the user owns the router (UPnP discovery works there too). The probe correctly returns false on cellular networks, falling back to Unbounded. Android handler/MethodHandler.kt: - 7 new enum entries (setPeerProxyEnabled, isPeerProxyEnabled, setPeerManualPort, getPeerManualPort, setUnboundedEnabled, isUnboundedEnabled, probeUPnP). - 7 new dispatch cases delegating to Mobile.setPeerShareEnabled, Mobile.isPeerShareEnabled, Mobile.setPeerManualPort, Mobile.getPeerManualPort, Mobile.setUnboundedEnabled, Mobile.isUnboundedEnabled, Mobile.probeUPnP. Wraps go's int return as toInt() (the gomobile binding maps Go int → Java long), and the input port as toLong(). scope.handleValue runs on Dispatchers.IO so probeUPnP's up-to-6s M-SEARCH wait doesn't pin the main thread. ios/Runner/Handlers/MethodHandler.swift: - 7 new case branches mirroring the existing macOS handler: - Direct one-liner cases for the simple bool getters (isPeerProxyEnabled, isUnboundedEnabled). - Helper-function dispatches for the setters (setPeerProxyEnabled, setPeerManualPort, setUnboundedEnabled) so the gomobile error-out path stays consistent with handleFlutterError. - probeUPnP uses Task.detached so the multicast wait runs off the main actor. - 3 new helper functions (setPeerProxyEnabled, setPeerManualPort, setUnboundedEnabled) following the existing setBlockAdsEnabled pattern. lib/features/home/provider/radiance_settings_providers.dart: - Dropped the PlatformUtils.isDesktop gate on the isPeerProxyEnabled fetch. The handler now exists on every platform, so the fetch succeeds across the board. Updated the comment to reflect the Windows+Linux+macOS+Android+iOS coverage and the rationale (manual port forwarding works on home WiFi). Co-Authored-By: Claude Opus 4.7 --- .../lantern/handler/MethodHandler.kt | 65 ++++++++++++++ ios/Runner/Handlers/MethodHandler.swift | 89 +++++++++++++++++++ .../provider/radiance_settings_providers.dart | 16 ++-- 3 files changed, 161 insertions(+), 9 deletions(-) diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/handler/MethodHandler.kt b/android/app/src/main/kotlin/org/getlantern/lantern/handler/MethodHandler.kt index a01238345d..5316a0caf3 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/handler/MethodHandler.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/handler/MethodHandler.kt @@ -130,6 +130,15 @@ enum class Methods(val method: String) { SetRoutingMode("setRoutingMode"), IsSmartRoutingEnabled("isSmartRoutingEnabled"), + // Share My Connection (samizdat) + Unbounded (broflake) peer-share + SetPeerProxyEnabled("setPeerProxyEnabled"), + IsPeerProxyEnabled("isPeerProxyEnabled"), + SetPeerManualPort("setPeerManualPort"), + GetPeerManualPort("getPeerManualPort"), + SetUnboundedEnabled("setUnboundedEnabled"), + IsUnboundedEnabled("isUnboundedEnabled"), + ProbeUPnP("probeUPnP"), + // Telemetry IsTelemetryEnabled("isTelemetryEnabled"), @@ -1221,6 +1230,62 @@ class MethodHandler : FlutterPlugin, } } + // Share My Connection (samizdat) + Unbounded (broflake) + // peer-share. Phones on mobile data won't have UPnP, but + // home WiFi does — manual port forwarding works there too, + // so the whole stack is exposed on Android rather than + // desktop-only. + Methods.SetPeerProxyEnabled.method -> { + scope.handleResult(result, "set_peer_proxy_enabled") { + val enabled = call.argument("enabled") + ?: error("Missing enabled") + Mobile.setPeerShareEnabled(enabled) + } + } + + Methods.IsPeerProxyEnabled.method -> { + scope.handleValue(result, "is_peer_proxy_enabled") { + Mobile.isPeerShareEnabled() + } + } + + Methods.SetPeerManualPort.method -> { + scope.handleResult(result, "set_peer_manual_port") { + val port = call.argument("port") ?: 0 + Mobile.setPeerManualPort(port.toLong()) + } + } + + Methods.GetPeerManualPort.method -> { + scope.handleValue(result, "get_peer_manual_port") { + Mobile.getPeerManualPort().toInt() + } + } + + Methods.SetUnboundedEnabled.method -> { + scope.handleResult(result, "set_unbounded_enabled") { + val enabled = call.argument("enabled") + ?: error("Missing enabled") + Mobile.setUnboundedEnabled(enabled) + } + } + + Methods.IsUnboundedEnabled.method -> { + scope.handleValue(result, "is_unbounded_enabled") { + Mobile.isUnboundedEnabled() + } + } + + Methods.ProbeUPnP.method -> { + // UPnP M-SEARCH waits up to ~6s on multicast replies; + // handleValue is already coroutine-backed (Dispatchers.IO + // via scope), so the wait runs off the main thread and + // doesn't block the Flutter UI isolate. + scope.handleValue(result, "probe_upnp") { + Mobile.probeUPnP() + } + } + Methods.IsTelemetryEnabled.method -> { scope.handleValue(result, "is_telemetry_enabled") { Mobile.isTelemetryEnabled() diff --git a/ios/Runner/Handlers/MethodHandler.swift b/ios/Runner/Handlers/MethodHandler.swift index 48c41b95af..0d89558692 100644 --- a/ios/Runner/Handlers/MethodHandler.swift +++ b/ios/Runner/Handlers/MethodHandler.swift @@ -263,6 +263,51 @@ class MethodHandler { await MainActor.run { result(MobileIsSmartRoutingEnabled()) } } + // Share My Connection (samizdat) + Unbounded (broflake) peer-share. + // Phones on cellular data won't have UPnP, but home WiFi does — and + // manual port forwarding works on any network where the user owns + // the router. The whole stack is exposed on iOS rather than + // desktop-only. + case "setPeerProxyEnabled": + let data = call.arguments as? [String: Any] + let enabled = data?["enabled"] as? Bool ?? false + self.setPeerProxyEnabled(result: result, enabled: enabled) + + case "isPeerProxyEnabled": + Task { + await MainActor.run { result(MobileIsPeerShareEnabled()) } + } + + case "setPeerManualPort": + let data = call.arguments as? [String: Any] + let port = data?["port"] as? Int ?? 0 + self.setPeerManualPort(result: result, port: port) + + case "getPeerManualPort": + Task { + await MainActor.run { result(Int(MobileGetPeerManualPort())) } + } + + case "setUnboundedEnabled": + let data = call.arguments as? [String: Any] + let enabled = data?["enabled"] as? Bool ?? false + self.setUnboundedEnabled(result: result, enabled: enabled) + + case "isUnboundedEnabled": + Task { + await MainActor.run { result(MobileIsUnboundedEnabled()) } + } + + case "probeUPnP": + // UPnP M-SEARCH multicast wait — up to ~6s in MobileProbeUPnP. + // Hop off the main actor so the wait doesn't stall the UI, then + // deliver the bool back on MainActor for the Flutter result + // callback. + Task.detached { + let available = MobileProbeUPnP() + await MainActor.run { result(available) } + } + case "isTelemetryEnabled": Task { await MainActor.run { result(MobileIsTelemetryEnabled()) } @@ -1127,6 +1172,50 @@ class MethodHandler { } } + // Share My Connection (samizdat) toggle. Mirrors the macOS handler's + // pattern: blocking Mobile call goes onto a detached Task; result + // and error delivery hop to MainActor. + func setPeerProxyEnabled(result: @escaping FlutterResult, enabled: Bool) { + Task { + var error: NSError? + MobileSetPeerShareEnabled(enabled, &error) + if let error { + await self.handleFlutterError(error, result: result, code: "SET_PEER_PROXY_ENABLED_ERROR") + return + } + await MainActor.run { result("ok") } + } + } + + // Manual port forward setting for the SmC residential-proxy path. + // Pass 0 to clear and revert to UPnP-discovered port behavior. + func setPeerManualPort(result: @escaping FlutterResult, port: Int) { + Task { + var error: NSError? + MobileSetPeerManualPort(port, &error) + if let error { + await self.handleFlutterError(error, result: result, code: "SET_PEER_MANUAL_PORT_ERROR") + return + } + await MainActor.run { result("ok") } + } + } + + // Local opt-in for the broflake / Unbounded widget proxy ("Basic mode" + // in the SmC UI). Actual run state also depends on the server's + // unbounded feature flag and supplied UnboundedConfig. + func setUnboundedEnabled(result: @escaping FlutterResult, enabled: Bool) { + Task { + var error: NSError? + MobileSetUnboundedEnabled(enabled, &error) + if let error { + await self.handleFlutterError(error, result: result, code: "SET_UNBOUNDED_ENABLED_ERROR") + return + } + await MainActor.run { result("ok") } + } + } + func updateTelemetryEvents(consent: Bool, result: @escaping FlutterResult) { Task { var error: NSError? diff --git a/lib/features/home/provider/radiance_settings_providers.dart b/lib/features/home/provider/radiance_settings_providers.dart index 5423893022..536a806499 100644 --- a/lib/features/home/provider/radiance_settings_providers.dart +++ b/lib/features/home/provider/radiance_settings_providers.dart @@ -31,17 +31,17 @@ class RadianceSettings extends _$RadianceSettings { final routingF = svc.isSmartRoutingEnabled(); final telemetryF = svc.isTelemetryEnabled(); final splitF = PlatformUtils.isIOS ? null : svc.isSplitTunnelingEnabled(); - // Peer-proxy probe runs only on platforms with native handlers - // (Windows + Linux via FFI, macOS via MethodChannel — i.e. all desktop). - // On iOS / Android the call would fail with MissingPluginException on - // every settings init. - final peerF = PlatformUtils.isDesktop ? svc.isPeerProxyEnabled() : null; + // Peer-proxy probe runs on every platform with a handler wired up: + // Windows + Linux via FFI, macOS + Android + iOS via MethodChannel. + // Manual port forwarding works on any home WiFi where the user owns + // the router, so mobile is included rather than desktop-only. + final peerF = svc.isPeerProxyEnabled(); final blockAds = await blockAdsF; final routing = await routingF; final telemetry = await telemetryF; final split = splitF == null ? null : await splitF; - final peer = peerF == null ? null : await peerF; + final peer = await peerF; if (!ref.mounted) return; const defaults = RadianceSettingsState(); @@ -55,9 +55,7 @@ class RadianceSettings extends _$RadianceSettings { splitTunneling: split == null ? defaults.splitTunneling : split.fold((_) => defaults.splitTunneling, (v) => v), - peerProxy: peer == null - ? defaults.peerProxy - : peer.fold((_) => defaults.peerProxy, (v) => v), + peerProxy: peer.fold((_) => defaults.peerProxy, (v) => v), ); } From 063160883c649b4b822e655435e977bc76830910 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 1 Jun 2026 02:13:40 -0600 Subject: [PATCH 28/37] smc: address Copilot review on #8819 round-N MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six findings: 1-3. lantern_ffi_service.dart: the SmC stack's three setter wrappers (setPeerProxyEnabled, setPeerManualPort, setUnboundedEnabled) each invoked the FFI `*C.char`-returning function, converted the pointer to a Dart string, and dropped the pointer. The Go side allocates each return via C.CString — every call leaked a small heap allocation. Wrapped each in the resultPtr-finally-freeCString pattern matching the existing startVPN / stripeBillingPortalUrl call sites. 4. share_my_connection.dart: the peer-event handler used `source.split(':').first` to strip the port off the source address. Mis-parses IPv6: '[2001:db8::1]:443'.split(':').first → '[2001' '2001:db8::1'.split(':').first → '2001' Extracted a small _extractIP helper handling the three emit forms — bracketed-IPv6 host:port, bare-IPv6, IPv4 host:port — via Uri.tryParse with a synthesized scheme (which gets the bracket-stripping right) and a bare-IP fallback for cases with no port. 5-6. lantern_platform_service.dart: the docstrings on the setPeerManualPort and setUnboundedEnabled MethodChannel wrappers still said the handlers exist only on macOS and 'iOS / Android don't implement these handlers yet' — that's stale after the previous commit added them. Rewrote both to describe the current state (macOS / iOS Swift + Android Kotlin all delegate to Mobile.* via the gomobile binding). dart analyze clean on the touched files. Co-Authored-By: Claude Opus 4.7 --- .../share_my_connection.dart | 35 +++++++++++++++++- lib/lantern/lantern_ffi_service.dart | 37 +++++++++++++------ lib/lantern/lantern_platform_service.dart | 11 ++---- 3 files changed, 62 insertions(+), 21 deletions(-) diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index 2d20a9fc9d..a343b507d2 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -140,6 +140,28 @@ const _unsetErrorMessage = _UnsetErrorMessage(); // ─── Notifier (mock-backed) ────────────────────────────────────────────────── +/// Extracts the IP from a peer source string. Handles all three forms +/// the Go side might emit: `203.0.113.5:443`, `[2001:db8::1]:443`, +/// and bare `2001:db8::1` / `203.0.113.5`. Returns an empty string if +/// nothing IP-shaped is recoverable. Synthesizing a scheme lets the +/// URI parser do the host extraction; the bare-IP fallback covers +/// emit paths where no port is appended. +String _extractIP(String source) { + if (source.isEmpty) return ''; + // Bracketed IPv6 ("[...]:port") or any host:port → Uri.tryParse with + // a synthesized scheme handles both correctly (returns the bracket- + // stripped host for IPv6 and the bare host for IPv4). + if (source.contains('[') || source.lastIndexOf(':') != source.indexOf(':')) { + final uri = Uri.tryParse('p://$source'); + final host = uri?.host ?? ''; + if (host.isNotEmpty) return host; + } + // Single ':' or no ':' at all → IPv4 host[:port] OR bare IP. + final colon = source.lastIndexOf(':'); + if (colon < 0) return source; + return source.substring(0, colon); +} + class _PeerArc { _PeerArc(this.workerIdx) : streamCount = 1; final int workerIdx; @@ -342,8 +364,17 @@ class ShareNotifier extends Notifier { final payload = jsonDecode(event.message) as Map; final eventState = (payload['state'] as num?)?.toInt() ?? 0; final source = (payload['source'] as String?) ?? ''; - // Globe only cares about the IP — strip ":port". - final ip = source.split(':').first; + // Globe only cares about the IP — strip ":port". Handle three + // forms the Go side might emit: + // IPv4 host:port → "203.0.113.5:443" + // IPv6 bracketed host:port → "[2001:db8::1]:443" + // bare IP (no port) → "2001:db8::1" or "203.0.113.5" + // A naive split(':').first works for the first form but + // mangles IPv6 ("[2001" or "2001"). Use Uri.tryParse against + // the synthesized "scheme://source" form to do host extraction + // properly, with a bare-IP fallback for cases where source has + // no port appended. + final ip = _extractIP(source); if (ip.isEmpty) return; if (eventState == 1) { diff --git a/lib/lantern/lantern_ffi_service.dart b/lib/lantern/lantern_ffi_service.dart index 689af9f7da..f2d38f9cbe 100644 --- a/lib/lantern/lantern_ffi_service.dart +++ b/lib/lantern/lantern_ffi_service.dart @@ -1554,10 +1554,15 @@ class LanternFFIService implements LanternCoreService { Future> setPeerProxyEnabled(bool enabled) async { try { final result = await runInBackground(() async { - return _ffiService - .setPeerProxyEnabled(enabled ? 1 : 0) - .cast() - .toDartString(); + // The Go side returns C.CString-allocated memory; free via + // freeCString after copying into a Dart string, otherwise + // every toggle leaks a small heap allocation. + final resultPtr = _ffiService.setPeerProxyEnabled(enabled ? 1 : 0); + try { + return resultPtr.cast().toDartString(); + } finally { + _ffiService.freeCString(resultPtr); + } }); checkAPIError(result); return right(unit); @@ -1582,10 +1587,14 @@ class LanternFFIService implements LanternCoreService { Future> setPeerManualPort(int port) async { try { final result = await runInBackground(() async { - return _ffiService - .setPeerManualPort(port) - .cast() - .toDartString(); + // Go returns C.CString-allocated memory; free via freeCString + // after copying into a Dart string. + final resultPtr = _ffiService.setPeerManualPort(port); + try { + return resultPtr.cast().toDartString(); + } finally { + _ffiService.freeCString(resultPtr); + } }); checkAPIError(result); return right(unit); @@ -1610,10 +1619,14 @@ class LanternFFIService implements LanternCoreService { Future> setUnboundedEnabled(bool enabled) async { try { final result = await runInBackground(() async { - return _ffiService - .setUnboundedEnabled(enabled ? 1 : 0) - .cast() - .toDartString(); + // Go returns C.CString-allocated memory; free via freeCString + // after copying into a Dart string. + final resultPtr = _ffiService.setUnboundedEnabled(enabled ? 1 : 0); + try { + return resultPtr.cast().toDartString(); + } finally { + _ffiService.freeCString(resultPtr); + } }); checkAPIError(result); return right(unit); diff --git a/lib/lantern/lantern_platform_service.dart b/lib/lantern/lantern_platform_service.dart index ed94a15583..302360f3d6 100644 --- a/lib/lantern/lantern_platform_service.dart +++ b/lib/lantern/lantern_platform_service.dart @@ -315,9 +315,8 @@ class LanternPlatformService implements LanternCoreService { } // Manual port forward setting — wired through MethodChannel to the - // platform-specific handler (Swift on macOS calls Mobile.SetPeerManualPort, - // similar pattern needed for iOS / Android when the user-facing toggle - // ships there). + // platform-specific handler. macOS / iOS Swift and Android Kotlin + // each delegate to Mobile.SetPeerManualPort via the gomobile binding. @override Future> setPeerManualPort(int port) async { try { @@ -343,10 +342,8 @@ class LanternPlatformService implements LanternCoreService { } // Unbounded toggle wired through MethodChannel to the platform-specific - // handler (Swift on macOS calls Mobile.SetUnboundedEnabled). Mobile - // platforms (iOS / Android) don't implement these handlers yet — they - // will throw MissingPluginException, which `e.toFailure()` translates - // into a localized error so the Advanced UI degrades cleanly. + // handler. macOS / iOS Swift and Android Kotlin each delegate to + // Mobile.SetUnboundedEnabled via the gomobile binding. @override Future> setUnboundedEnabled(bool enabled) async { try { From 4bb5b524f49b2e6e90844d2f6fe4795796732d4f Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 1 Jun 2026 02:26:50 -0600 Subject: [PATCH 29/37] smc: tighten _extractIP for bare IPv6 + check Unbounded enable Either MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two Copilot findings: 1. _extractIP mis-parsed bare IPv6 (e.g. `2001:db8::1`). The previous condition routed multi-colon strings into Uri.tryParse, which can't parse an un-bracketed IPv6 host and returned empty, then fell through to substring(0, lastColon) which truncated the address to '2001:db8:'. Reworked the parser around the four shapes the Go side emits: - bracketed IPv6 host:port → Uri parse (strips brackets) - bare IPv6 (multi-colon, no brackets) → return as-is - IPv4 host:port (single colon) → substring up to colon - bare IPv4 (no colon) → return as-is 2. _start's ShareMode.unbounded branch threw away the Either returned by setUnboundedEnabled — a failure (core not initialized, MethodChannel failure, etc.) left the UI stuck at 'Active' while nothing actually started. Now folds the result, logs on Left, and reverts state to SharePhase.error with the error message so the user sees an actionable failure. The ShareMode.smc branch doesn't need an equivalent check because peer.Client emits phase=error StatusEvent on real failures, which _handlePeerStatus already routes through _fallbackToUnbounded. dart analyze clean on the touched file. Co-Authored-By: Claude Opus 4.7 --- .../share_my_connection.dart | 63 ++++++++++++++----- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index a343b507d2..806ca9b854 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -140,26 +140,33 @@ const _unsetErrorMessage = _UnsetErrorMessage(); // ─── Notifier (mock-backed) ────────────────────────────────────────────────── -/// Extracts the IP from a peer source string. Handles all three forms -/// the Go side might emit: `203.0.113.5:443`, `[2001:db8::1]:443`, -/// and bare `2001:db8::1` / `203.0.113.5`. Returns an empty string if -/// nothing IP-shaped is recoverable. Synthesizing a scheme lets the -/// URI parser do the host extraction; the bare-IP fallback covers -/// emit paths where no port is appended. +/// Extracts the IP from a peer source string. Handles the four shapes +/// the Go side might emit: +/// - bracketed IPv6 host:port `[2001:db8::1]:443` → `2001:db8::1` +/// - bare IPv6 (no port) `2001:db8::1` → `2001:db8::1` +/// - IPv4 host:port `203.0.113.5:443` → `203.0.113.5` +/// - bare IPv4 (no port) `203.0.113.5` → `203.0.113.5` +/// Returns an empty string if input is empty. String _extractIP(String source) { if (source.isEmpty) return ''; - // Bracketed IPv6 ("[...]:port") or any host:port → Uri.tryParse with - // a synthesized scheme handles both correctly (returns the bracket- - // stripped host for IPv6 and the bare host for IPv4). - if (source.contains('[') || source.lastIndexOf(':') != source.indexOf(':')) { + // Bracketed IPv6 host:port — Uri parser strips the brackets and + // returns the inner host. This is the only shape where the + // synthesized-scheme URI parse is unambiguous. + if (source.startsWith('[')) { final uri = Uri.tryParse('p://$source'); final host = uri?.host ?? ''; if (host.isNotEmpty) return host; + return ''; // malformed bracket } - // Single ':' or no ':' at all → IPv4 host[:port] OR bare IP. - final colon = source.lastIndexOf(':'); - if (colon < 0) return source; - return source.substring(0, colon); + final first = source.indexOf(':'); + if (first < 0) return source; // bare IPv4 (no port) + // Multiple colons + no brackets = bare IPv6. Bare IPv6 is + // emitted by some broflake paths that don't bracket-format + // addresses; we accept it as-is rather than truncating at the + // last ':' which would mangle the address. + if (first != source.lastIndexOf(':')) return source; + // Exactly one ':' — IPv4 host:port. + return source.substring(0, first); } class _PeerArc { @@ -302,15 +309,37 @@ class ShareNotifier extends Notifier { case ShareMode.unbounded: // Unbounded is the broflake / WebRTC widget-proxy mode. Local // opt-in only — actual run state also depends on the server's - // Features[unbounded] flag and supplied UnboundedConfig (see - // radiance/unbounded/unbounded.go shouldRunUnbounded). When + // Features[unbounded] flag and supplied UnboundedConfig. When // running, broflake's OnConnectionChange callback emits // unbounded.ConnectionEvent → forwarded by lantern-core as the // same EventTypePeerConnection FlutterEvent the SmC path uses, // so this Dart subscription consumes both protocols uniformly. - await widgetRef + // + // Unbounded has no equivalent of the peer.Client phase=error + // StatusEvent that the SmC path leans on for failure recovery, + // so check the Either return here and revert to off if the + // setting flip failed (core not initialized, MethodChannel + // failure, etc.) — otherwise the UI sticks at "Active" while + // nothing actually started. + final res = await widgetRef .read(lanternServiceProvider) .setUnboundedEnabled(true); + res.fold( + (err) { + appLogger.error('setUnboundedEnabled failed: ${err.error}'); + _stopEventSubscription(); + state = ShareState( + active: false, + probing: false, + mode: ShareMode.off, + activeCount: 0, + totalCount: 0, + phase: SharePhase.error, + errorMessage: err.error, + ); + }, + (_) => null, + ); break; case ShareMode.off: break; From f7adec5949b3879a63c325e3e0d8b09875c1cccd Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 1 Jun 2026 02:49:36 -0600 Subject: [PATCH 30/37] smc: render off-with-error + check SmC enable Either + cache peer geo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four Copilot findings: 1. _StatusCard's switch matched (ShareMode.off, _) before (ShareMode.off, SharePhase.error), so the round-N 'revert to off+error on Unbounded failure' state rendered as plain 'Off' instead of an actionable error. Added a specific (off, error) arm before the catch-all that renders the same smc_status_error_with_message / generic strings the SmC error path uses. 2. The ShareMode.smc enable path threw away setPeerProxy's result, so failures BEFORE peer.Client.Start (IPC error, MissingPluginException, core not initialized) didn't surface anywhere — the screen stuck at 'active: true' with an event subscription running while sharing never started. Changed radianceSettingsProvider.notifier.setPeerProxy to return Future> instead of Future. The SmC branch in _start now folds the result and, on Left, tears down the event subscription and reverts to mode=off / phase=error (matching the Unbounded branch's pattern). The stop-path caller doesn't read the return value, which is fine — Dart allows the discard, and toggle-off is fire-and- forget anyway. 3-4. GeoLookupService.peerLookup ran a fresh HTTP request to ipwho.is for every probe connection. The tooltip explicitly notes most connections are short liveness probes from the same handful of client IPs — without caching this would chew through the 10k/month free quota in minutes and leak more data to the third party than necessary. Added a process-lifetime per-IP cache (Map) that also caches the PeerGeo.unknown sentinel from failed lookups (so a previously-failed lookup doesn't retry on every subsequent probe). No TTL — IP→country bindings don't change on human timescales, and the TTL bookkeeping adds complexity without changing the privacy or quota math. Added a resetCacheForTest() helper for unit tests. dart analyze clean on the touched files. Co-Authored-By: Claude Opus 4.7 --- lib/core/services/geo_lookup_service.dart | 57 +++++++++++++------ .../provider/radiance_settings_providers.dart | 22 +++++-- .../share_my_connection.dart | 34 ++++++++++- 3 files changed, 90 insertions(+), 23 deletions(-) diff --git a/lib/core/services/geo_lookup_service.dart b/lib/core/services/geo_lookup_service.dart index a064c72226..fdc09ff948 100644 --- a/lib/core/services/geo_lookup_service.dart +++ b/lib/core/services/geo_lookup_service.dart @@ -32,6 +32,20 @@ class GeoLookupService { // + flag emoji in one shot. static const _peerUrl = 'https://ipwho.is'; + // Per-IP cache for peerLookup. Most connections are short-lived + // liveness probes from the same handful of client IPs, so without a + // cache an active SmC peer would issue hundreds of ipwho.is requests + // per minute — chewing through the 10k/month free quota and leaking + // more data to the third party than necessary. Cached entries + // (including the PeerGeo.unknown sentinel from a failed lookup) live + // for the rest of the process — IP→country bindings don't change on + // human timescales, and the alternative TTL bookkeeping adds + // complexity without changing the privacy or quota math. + static final Map _peerCache = {}; + + /// Test-only: drop the cache. Production code does not call this. + static void resetCacheForTest() => _peerCache.clear(); + // ISO country code → approximate centre coordinates. Used as a fallback // when ipwho.is doesn't return city-level coords. static const _countryCenters = { @@ -198,27 +212,36 @@ class GeoLookupService { /// before any production-scale rollout where peer protection matters /// beyond demo use. static Future peerLookup(String ip) async { + // Cache hit: short-circuit before any network I/O. Also caches + // PeerGeo.unknown so a previously-failed lookup doesn't retry on + // every subsequent probe from the same IP. + final cached = _peerCache[ip]; + if (cached != null) return cached; + PeerGeo result = PeerGeo.unknown; try { final response = await http .get(Uri.parse('$_peerUrl/$ip')) .timeout(const Duration(seconds: 5)); - if (response.statusCode != 200) return PeerGeo.unknown; - final data = jsonDecode(response.body) as Map; - if (data['success'] != true) return PeerGeo.unknown; - final iso = (data['country_code'] as String?) ?? ''; - final lat = (data['latitude'] as num?)?.toDouble(); - final lng = (data['longitude'] as num?)?.toDouble(); - final coords = (lat != null && lng != null) - ? GlobeCoordinates(lat, lng) - : _isoToCoords(iso); - final flagObj = data['flag'] as Map?; - return PeerGeo( - coordinates: coords, - countryName: (data['country'] as String?) ?? '', - countryCode: iso, - flagEmoji: (flagObj?['emoji'] as String?) ?? '', - ); + if (response.statusCode == 200) { + final data = jsonDecode(response.body) as Map; + if (data['success'] == true) { + final iso = (data['country_code'] as String?) ?? ''; + final lat = (data['latitude'] as num?)?.toDouble(); + final lng = (data['longitude'] as num?)?.toDouble(); + final coords = (lat != null && lng != null) + ? GlobeCoordinates(lat, lng) + : _isoToCoords(iso); + final flagObj = data['flag'] as Map?; + result = PeerGeo( + coordinates: coords, + countryName: (data['country'] as String?) ?? '', + countryCode: iso, + flagEmoji: (flagObj?['emoji'] as String?) ?? '', + ); + } + } } catch (_) {} - return PeerGeo.unknown; + _peerCache[ip] = result; + return result; } } diff --git a/lib/features/home/provider/radiance_settings_providers.dart b/lib/features/home/provider/radiance_settings_providers.dart index 536a806499..899fd1c128 100644 --- a/lib/features/home/provider/radiance_settings_providers.dart +++ b/lib/features/home/provider/radiance_settings_providers.dart @@ -105,13 +105,25 @@ class RadianceSettings extends _$RadianceSettings { ); } - Future setPeerProxy(bool value) async { + /// Enable/disable the peer-proxy (Share My Connection) radiance + /// setting. Returns the underlying Either so the caller can react to + /// failure — share_my_connection.dart's _start depends on it to + /// revert UI state if the setting flip fails before peer.Client + /// emits its own phase=error StatusEvent. Internal logging still + /// happens on failure for fire-and-forget call sites. + Future> setPeerProxy(bool value) async { final svc = ref.read(lanternServiceProvider); final result = await svc.setPeerProxyEnabled(value); - if (!ref.mounted) return; - result.fold( - (err) => appLogger.error('setPeerProxyEnabled failed: ${err.error}'), - (_) => state = state.copyWith(peerProxy: value), + if (!ref.mounted) return result; + return result.fold( + (err) { + appLogger.error('setPeerProxyEnabled failed: ${err.error}'); + return left(err); + }, + (_) { + state = state.copyWith(peerProxy: value); + return right(unit); + }, ); } } diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index 806ca9b854..5cf114d58b 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -302,9 +302,32 @@ class ShareNotifier extends Notifier { // listener) emits ConnectionEvents that ride the radiance event // bus → core.go listenPeerConnectionEvents → FlutterEvent → our // Dart subscription. - await widgetRef + // + // Failures AFTER peer.Client.Start surface via a phase=error + // StatusEvent that _handlePeerStatus routes through + // _fallbackToUnbounded. Failures BEFORE Start (IPC error, + // MissingPluginException, core not initialized) don't go + // through that path, so check the setPeerProxy Either here + // and revert UI state to mode=off, phase=error. + final smcRes = await widgetRef .read(radianceSettingsProvider.notifier) .setPeerProxy(true); + smcRes.fold( + (err) { + appLogger.error('SmC setPeerProxy failed: ${err.error}'); + _stopEventSubscription(); + state = ShareState( + active: false, + probing: false, + mode: ShareMode.off, + activeCount: 0, + totalCount: 0, + phase: SharePhase.error, + errorMessage: err.error, + ); + }, + (_) => null, + ); break; case ShareMode.unbounded: // Unbounded is the broflake / WebRTC widget-proxy mode. Local @@ -621,6 +644,15 @@ class _StatusCard extends StatelessWidget { // 4. Unbounded mode → static "Active — Unbounded" (no equivalent // staged lifecycle on the broflake side yet). final modeLabel = switch ((state.mode, state.phase)) { + // Off-with-error is a legitimate terminal state: the enable + // path failed (e.g. setUnboundedEnabled / setPeerProxyEnabled + // returned Left) and reverted mode to off. Render the error + // before the catch-all "Off" so the user sees an actionable + // message instead of a misleading off state. + (ShareMode.off, SharePhase.error) => + state.errorMessage != null + ? 'smc_status_error_with_message'.i18n.fill([state.errorMessage!]) + : 'smc_status_error_generic'.i18n, (ShareMode.off, _) => state.probing ? 'smc_status_probing'.i18n : 'smc_status_off'.i18n, (ShareMode.unbounded, _) => 'smc_status_active_unbounded'.i18n, From 0bcd72906fe7d7987d40495da6ae39aff816323f Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 1 Jun 2026 05:09:11 -0600 Subject: [PATCH 31/37] smc: list-spread + dispose guards + terminal-phase reset + docs Seven Copilot findings (with 5 duplicate threads, so 7 unique): 1. vpn_setting.dart used '...{' (Set literal spread) inside a List children: literal in three spots. Switched all three to '...[', and closed with '],' to match. Set literals would dedup widgets silently and the type mismatch is easy to miss; list spread is the conventional shape. 2. _ManualPortField's useEffect did a fire-and-forget Future.microtask that wrote to the TextEditingController and ValueNotifiers after disposal if the user navigated away quickly. Added a 'disposed' flag flipped from the useEffect cleanup, checked after the await. 3. _HeartBurst's Lottie.asset onLoaded callback could fire after the State was disposed (rapid arrival burst replaces the ArrivalCard before composition load). The setState + AnimationController(vsync: this) inside the callback would then throw. Added 'if (!mounted) return;' guard and disposed any prior controller so a stale ticker subscription from an earlier onLoaded doesn't leak. 4. _handlePeerStatus only updated phase/errorMessage, leaving state.active/mode stuck on SmC when the backend reported a terminal phase. The toggle could show ON while radiance was idle (clean stop) or error (start failed). Added a terminal- phase branch: both idle and error tear down the event subscription; error preserves the message so the new (off, error) StatusCard arm renders it. 5. lantern-core/core.go's listenPeerConnectionEvents doc comment said the Unbounded payload included workerIdx, but the actual marshal block emits {state, source, timestamp}. Updated the comment to match the actual payload + describe the source format on both protocols. dart analyze clean on the touched files; Go build clean on lantern-core/... Co-Authored-By: Claude Opus 4.7 --- lantern-core/core.go | 11 ++-- lib/features/setting/vpn_setting.dart | 16 +++--- .../share_my_connection.dart | 57 ++++++++++++++++--- 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/lantern-core/core.go b/lantern-core/core.go index b1f7191c9b..534299ab40 100644 --- a/lantern-core/core.go +++ b/lantern-core/core.go @@ -405,11 +405,12 @@ func (lc *LanternCore) listenDataCapEvents() { // no peer / widget is active. // // The wire format unifies both protocols on a single event type -// (EventTypePeerConnection) with a {state, source} payload. Unbounded -// has a workerIdx in addition to source IP — surfaced as part of the -// JSON in case the Dart side eventually wants to disambiguate same-IP -// reconnects (broflake's WebRTC sessions are short and same-IP churn -// is more common than for SmC's long-lived TCP). +// (EventTypePeerConnection) with a {state, source, timestamp} +// payload — peer.ConnectionEvent and unbounded.ConnectionEvent both +// expose the same shape on the radiance side, so consumers don't +// need to disambiguate which protocol produced an event. Source is +// "host:port" (IPv4) or "[host]:port" (IPv6) for peer-share, and +// the broflake-reported consumer IP for Unbounded (no port). func (lc *LanternCore) listenPeerConnectionEvents() { // unbounded.ConnectionEvent stays on in-process events.Subscribe for // now. Unbounded runs in the same process as the consumer in mobile diff --git a/lib/features/setting/vpn_setting.dart b/lib/features/setting/vpn_setting.dart index 12eb6e6b17..b4b26c3b89 100644 --- a/lib/features/setting/vpn_setting.dart +++ b/lib/features/setting/vpn_setting.dart @@ -60,19 +60,19 @@ class VPNSetting extends HookConsumerWidget { appRouter.push(const ServerSelection()); }, ), - if (!PlatformUtils.isIOS) ...{ + if (!PlatformUtils.isIOS) ...[ DividerSpace(), SplitTunnelingTile( label: 'routing_mode'.i18n, icon: AppImagePaths.route, actionText: routingMode.label(), onPressed: () => appRouter.push(const SmartRouting()), - ) - }, + ), + ], DividerSpace(), if (PlatformUtils.isAndroid || PlatformUtils.isMacOS || - PlatformUtils.isWindows) ...{ + PlatformUtils.isWindows) ...[ SplitTunnelingTile( label: 'split_tunneling'.i18n, icon: AppImagePaths.callSpilt, @@ -80,8 +80,8 @@ class VPNSetting extends HookConsumerWidget { splitTunnelingEnabled ? 'enabled'.i18n : 'disabled'.i18n, onPressed: () => appRouter.push(const SplitTunneling()), ), - DividerSpace() - }, + DividerSpace(), + ], ], ), ), @@ -121,7 +121,7 @@ class VPNSetting extends HookConsumerWidget { }, ), ), - if (PlatformUtils.isDesktop) ...{ + if (PlatformUtils.isDesktop) ...[ SizedBox(height: 16), AppCard( padding: EdgeInsets.zero, @@ -150,7 +150,7 @@ class VPNSetting extends HookConsumerWidget { }, ), ), - }, + ], SizedBox(height: 16), AppCard( padding: EdgeInsets.zero, diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index 5cf114d58b..649e1de01e 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -304,11 +304,12 @@ class ShareNotifier extends Notifier { // Dart subscription. // // Failures AFTER peer.Client.Start surface via a phase=error - // StatusEvent that _handlePeerStatus routes through - // _fallbackToUnbounded. Failures BEFORE Start (IPC error, - // MissingPluginException, core not initialized) don't go - // through that path, so check the setPeerProxy Either here - // and revert UI state to mode=off, phase=error. + // StatusEvent that _handlePeerStatus turns into a terminal- + // state reset (mode=off, phase=error). Failures BEFORE + // Start (IPC error, MissingPluginException, core not + // initialized) don't go through that path, so check the + // setPeerProxy Either here and revert UI state to + // mode=off, phase=error directly. final smcRes = await widgetRef .read(radianceSettingsProvider.notifier) .setPeerProxy(true); @@ -557,9 +558,32 @@ class ShareNotifier extends Notifier { final payload = jsonDecode(message) as Map; final phase = SharePhase.fromWire(payload['phase'] as String?); final errMsg = payload['error'] as String?; + final hasErr = errMsg != null && errMsg.isNotEmpty; + + // Terminal-phase reset: when radiance reports the backend is + // idle (clean stop) or error (start failed / runtime collapse), + // the SmC active/mode bits also need to flip — otherwise the + // toggle stays ON while the backend is off. Both terminal + // phases tear down the event subscription and return to the + // off-state; the error phase additionally preserves the + // backend's error message so the StatusCard's (off, error) + // arm renders it. + if ((phase == SharePhase.idle || phase == SharePhase.error) && + state.mode == ShareMode.smc) { + _stopEventSubscription(); + if (phase == SharePhase.error) { + state = ShareState( + phase: SharePhase.error, + errorMessage: hasErr ? errMsg : null, + ); + } else { + state = const ShareState(); + } + return; + } state = state.copyWith( phase: phase, - errorMessage: (errMsg == null || errMsg.isEmpty) ? null : errMsg, + errorMessage: hasErr ? errMsg : null, ); } catch (e) { debugPrint('share-my-connection: bad peer-status event: $e'); @@ -1138,6 +1162,16 @@ class _HeartBurstState extends State<_HeartBurst> repeat: false, fit: BoxFit.contain, onLoaded: (composition) { + // Dispose guard: Lottie's onLoaded can fire after the + // burst is replaced (rapid peer arrivals replace the + // ArrivalCard before composition load completes). + // Without this, AnimationController(vsync: this) + + // setState would throw on a disposed State. + if (!mounted) return; + // Also dispose any prior controller — a stale + // AnimationController from an earlier onLoaded would + // leak its ticker subscription otherwise. + _lottieCtrl?.dispose(); _lottieCtrl = AnimationController( vsync: this, duration: composition.duration, @@ -1243,17 +1277,26 @@ class _ManualPortField extends HookConsumerWidget { // provider here — the value rarely changes and a one-shot read // matches the rest of the radianceSettingsProvider's eager-load // pattern. + // + // Dispose guard: the user can navigate away while the + // getPeerManualPort future is still in flight (especially on the + // FFI path where it can take several ms). Without the guard, the + // continuation would write to a disposed TextEditingController + // and useState ValueNotifiers and throw. `disposed` flips on the + // useEffect cleanup so post-dispose writes short-circuit. useEffect(() { + var disposed = false; Future.microtask(() async { final result = await ref.read(lanternServiceProvider).getPeerManualPort(); + if (disposed) return; result.fold((_) => null, (port) { if (port > 0) controller.text = port.toString(); lastSaved.value = port; }); loaded.value = true; }); - return null; + return () => disposed = true; }, const []); return Column( From 24abae2729a6b216034647dc0b9af0371c684bf8 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 1 Jun 2026 05:29:32 -0600 Subject: [PATCH 32/37] smc: MediaQuery copyWith + symmetric wire format + drop stale fromJson MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three Copilot findings: 1. _GlobeView wrapped its body in MediaQuery(data: MediaQueryData(size: widgetSize), ...) — constructing MediaQueryData from scratch drops inherited fields (devicePixelRatio, textScaleFactor, padding, viewInsets, etc.). On high-DPI displays the pixel ratio fell to 1.0, breaking globe rendering crispness; accessibility scaling for any descendants would also break. Switched to MediaQuery.of(context).copyWith(size: widgetSize) which keeps the inherited fields and only overrides what we need. 2. lantern-core/core.go's doc comment said the peer-connection wire payload was always {state, source, timestamp}, but the peer.ConnectionEvent marshal block emitted only {state, source}. Added timestamp to the peer marshal (both peer.ConnectionEvent and unbounded.ConnectionEvent carry Timestamp on the radiance side, so the consumer-facing shape is now symmetric) and updated the comment to spell out the source format difference between protocols and call out Unix-millis for timestamp. 3. UnboundedConnectionEvent.fromJson was stale: it expected {workerIdx, addr} keys, but the actual wire format is {state, source, timestamp}. The factory is never called from anywhere in lib/ — wire-format parsing happens inline in share_my_connection.dart, and the class is only constructed directly by the notifier as an internal Dart-side event model. Dropped the dead factory and clarified the class docstring to distinguish 'internal Dart-side model' from 'wire format', including a note that workerIdx is a Dart-side identity counter (_workerSeq), not the broflake worker index. dart analyze clean; Go build clean. Co-Authored-By: Claude Opus 4.7 --- lantern-core/core.go | 14 +++++++------ .../models/unbounded_connection_event.dart | 20 ++++++++++--------- .../share_my_connection.dart | 7 ++++++- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/lantern-core/core.go b/lantern-core/core.go index 534299ab40..a4d189558f 100644 --- a/lantern-core/core.go +++ b/lantern-core/core.go @@ -407,10 +407,11 @@ func (lc *LanternCore) listenDataCapEvents() { // The wire format unifies both protocols on a single event type // (EventTypePeerConnection) with a {state, source, timestamp} // payload — peer.ConnectionEvent and unbounded.ConnectionEvent both -// expose the same shape on the radiance side, so consumers don't -// need to disambiguate which protocol produced an event. Source is -// "host:port" (IPv4) or "[host]:port" (IPv6) for peer-share, and -// the broflake-reported consumer IP for Unbounded (no port). +// carry the same three fields on the radiance side, so the Dart +// consumer can deserialize each frame uniformly without caring +// which protocol produced it. Source is "host:port" / +// "[host]:port" for peer-share, and the broflake-reported consumer +// IP (no port) for Unbounded. Timestamp is Unix milliseconds. func (lc *LanternCore) listenPeerConnectionEvents() { // unbounded.ConnectionEvent stays on in-process events.Subscribe for // now. Unbounded runs in the same process as the consumer in mobile @@ -452,8 +453,9 @@ func (lc *LanternCore) listenPeerConnectionEvents() { // immediately. err := lc.client.PeerConnectionEvents(lc.ctx, func(evt peer.ConnectionEvent) { jsonBytes, err := json.Marshal(map[string]any{ - "state": evt.State, - "source": evt.Source, + "state": evt.State, + "source": evt.Source, + "timestamp": evt.Timestamp, }) if err != nil { slog.Error("marshal peer connection event", "error", err) diff --git a/lib/core/models/unbounded_connection_event.dart b/lib/core/models/unbounded_connection_event.dart index bcd040c515..467a53e303 100644 --- a/lib/core/models/unbounded_connection_event.dart +++ b/lib/core/models/unbounded_connection_event.dart @@ -1,6 +1,16 @@ import 'package:flutter_earth_globe/globe_coordinates.dart'; -/// Represents a consumer connection change from the broflake widget proxy. +/// Internal Dart-side connection-change model the ShareNotifier emits +/// to the globe via _eventController. NOT the wire format — +/// FlutterEvent messages from lantern-core (forwarded from radiance) +/// are parsed inline in share_my_connection.dart's event subscription +/// as `{state, source, timestamp}` and synthesized into this model +/// after geo-resolution. +/// +/// workerIdx here is the Dart-side identity counter (_workerSeq++ in +/// the notifier), not the broflake worker index — it's a stable +/// handle for matching accept/close pairs and cancelling pending arc +/// removals when a peer reconnects from the same IP. class UnboundedConnectionEvent { final int state; // 1 = connected, -1 = disconnected final int workerIdx; @@ -27,14 +37,6 @@ class UnboundedConnectionEvent { this.coordinates, this.isReplay = false, }); - - factory UnboundedConnectionEvent.fromJson(Map json) { - return UnboundedConnectionEvent( - state: json['state'] as int, - workerIdx: json['workerIdx'] as int, - addr: json['addr'] as String? ?? '', - ); - } } /// Tracks live and cumulative connection counts for Unbounded. diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index 649e1de01e..d8ff1620d2 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -974,8 +974,13 @@ class _GlobeViewState extends ConsumerState<_GlobeView> { final radius = min(constraints.maxWidth, constraints.maxHeight) / 2 * 0.7; return ClipRect( + // copyWith preserves the inherited devicePixelRatio, + // textScaleFactor, padding/insets etc. — constructing + // MediaQueryData from scratch with just `size:` would drop + // those, breaking high-DPI rendering (pixel ratio falls to + // 1.0) and accessibility scaling for the globe subtree. child: MediaQuery( - data: MediaQueryData(size: widgetSize), + data: MediaQuery.of(context).copyWith(size: widgetSize), child: Stack( children: [ Positioned.fill( From 690494bfd9160cbc63afb0beff5aac3e24803a60 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 1 Jun 2026 05:47:25 -0600 Subject: [PATCH 33/37] smc: gate arc draws on origin coords + accurate event type doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four Copilot findings: 1. replayCurrentPeers fired before _initOrigin completed — replayed arcs drew to GlobeCoordinates(0,0) and never got corrected. Moved the replay call into _initOrigin's continuation so it runs only AFTER origin coords are known. 2. _addPeer fell back to GlobeCoordinates(0,0) when _originCoords hadn't loaded yet, so real-time +1 events arriving during the origin-lookup window could still draw to (0,0). Added a null guard: if origin isn't resolved, skip the draw — the peer is still tracked in the notifier's _peerArcs map (source of truth), and replayCurrentPeers in _initOrigin's continuation picks it up. Reordered initState: subscribe FIRST so real-time events accumulate in _peerArcs while origin is loading, then call _initOrigin which finishes by calling replayCurrentPeers. With both changes there's no window where a peer is drawn without correct origin coords. 3. debugPrint comment claimed it 'avoids bringing in the appLogger' and that 'real impl can switch to slog' — but the file already uses appLogger, and slog isn't a Dart logger. Rewrote to describe the actual rationale: avoid escalating a single malformed wire event to a user-visible error toast in debug builds; keep the listener subscribed so subsequent well-formed events still arrive. 4. lantern-core's EventTypePeerConnection doc described it as samizdat-only with a {state, source} payload. Both donor protocols now emit on this event type with the unified {state, source, timestamp} payload (peer-share's marshal was bumped in the previous round to include timestamp). Updated the doc accordingly with source-format details for both protocols. dart analyze clean; Go build clean. Co-Authored-By: Claude Opus 4.7 --- lantern-core/core.go | 13 ++++--- .../share_my_connection.dart | 35 ++++++++++++++----- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/lantern-core/core.go b/lantern-core/core.go index a4d189558f..66bad95d99 100644 --- a/lantern-core/core.go +++ b/lantern-core/core.go @@ -40,10 +40,15 @@ const ( EventTypeServerLocation EventType = "server-location" EventTypeConfig EventType = "config" EventTypeCountryCode EventType = "country-code" - // EventTypePeerConnection signals a samizdat peer accept/close on the - // local Share My Connection inbound. Message is JSON - // {"state": +1|-1, "source": "ip:port"}; consumers extract the IP for - // geo-lookup or rate-limit attribution. + // EventTypePeerConnection signals a peer accept/close on the local + // peer-share inbound. Both donor protocols emit this event type: + // samizdat-over-UPnP "Share My Connection" and broflake + // "Unbounded". Message is JSON + // {"state": +1|-1, "source": "...", "timestamp": }. + // Source is "host:port" / "[host]:port" for SmC, bare consumer IP + // for Unbounded. Consumers extract the IP for geo-lookup or + // rate-limit attribution; timestamp is useful for ordering when + // the event-bus dispatch is async. EventTypePeerConnection EventType = "peer-connection" // EventTypePeerStatus signals a peer.Client lifecycle phase change // (mapping_port → registering → verifying → serving on the way up, diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index d8ff1620d2..0f4e582368 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -468,8 +468,12 @@ class ShareNotifier extends Notifier { ); } } catch (e) { - // Malformed event — log via dev print to avoid bringing in the - // appLogger here. Real impl can switch to slog. + // Malformed event — log without bringing down the listener. + // debugPrint is intentional: appLogger.error would surface as + // a user-visible error toast in some debug builds, and a + // single bad event from the wire shouldn't escalate that + // far. The listener stays subscribed so subsequent + // well-formed events still arrive. debugPrint('share-my-connection: bad peer-connection event: $e'); } }); @@ -854,16 +858,18 @@ class _GlobeViewState extends ConsumerState<_GlobeView> { if (!mounted) return; _applyTheme(); }; - _initOrigin(); - // Subscribe BEFORE the replay call so we don't miss any concurrent - // +1 events. The broadcast stream delivers synchronously when added, - // but the replay events come from inside the same notifier so order - // is preserved. + // Subscribe FIRST so we don't miss any real-time +1 events while + // _initOrigin is in flight. _addPeer guards on _originCoords — + // events that arrive before origin lookup completes are still + // tracked by the notifier's _peerArcs map (the source of truth); + // _initOrigin's continuation calls replayCurrentPeers to draw + // them with the now-known origin coords. Without this ordering, + // arcs drew to GlobeCoordinates(0,0) and never got corrected. _eventSub = ref .read(shareProvider.notifier) .connectionEvents .listen(_handleEvent); - ref.read(shareProvider.notifier).replayCurrentPeers(); + _initOrigin(); } @override @@ -897,6 +903,11 @@ class _GlobeViewState extends ConsumerState<_GlobeView> { coordinates: coords, style: PointStyle(color: _originPointColor, size: 8), )); + // Origin coords are now known — draw any peers that connected + // before (or during) the origin lookup. _addPeer's null-guard + // skipped them earlier; replayCurrentPeers re-fires +1 events + // for everything in _peerArcs. + ref.read(shareProvider.notifier).replayCurrentPeers(); } void _handleEvent(UnboundedConnectionEvent event) { @@ -928,6 +939,12 @@ class _GlobeViewState extends ConsumerState<_GlobeView> { void _addPeer(UnboundedConnectionEvent event) { if (!mounted) return; + // Origin not yet resolved — skip the draw rather than render an + // arc to GlobeCoordinates(0, 0). The peer is still tracked in + // the notifier's _peerArcs map, and _initOrigin's continuation + // calls replayCurrentPeers once origin is known so anything + // skipped here gets drawn with the correct destination. + if (_originCoords == null) return; final coords = _jittered(event.coordinates!, event.workerIdx); // Arc direction is censored user → uncensored peer (us). The dash // animation flows from start to end, so the visual "travel" reads @@ -935,7 +952,7 @@ class _GlobeViewState extends ConsumerState<_GlobeView> { _globeController.addPointConnection(PointConnection( id: 'conn_${event.workerIdx}', start: coords, - end: _originCoords ?? const GlobeCoordinates(0, 0), + end: _originCoords!, curveScale: .6, style: PointConnectionStyle( color: _arcColor, From 7d03f48a17c96681adb1c7dedc2d58b794647b33 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 1 Jun 2026 06:02:06 -0600 Subject: [PATCH 34/37] smc: defer subscription cleanup + strict arg validation across handlers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six Copilot findings: 1. lantern-core/core.go's listenPeerConnectionEvents subscribed to unbounded.ConnectionEvent and started a goroutine waiting on ctx.Done to unsubscribe. If client.PeerConnectionEvents returned an error while ctx was still live, the function returned but the ctx-watcher goroutine + subscription leaked for the rest of the process. Replaced with 'defer unbSub.Unsubscribe()' so cleanup runs on both exit paths (normal ctx cancel + unexpected stream exit). 2. iOS handler comment said the SmC setter helpers use a 'detached Task' but the implementation uses 'Task {}'. Updated the comment to describe the actual choice — plain Task is fine for the millisecond-range PatchSettings calls because inheriting the current actor's executor is cheap; the probeUPnP case is the one exception that uses Task.detached because its M-SEARCH wait is multi-second. 3-5. macOS / iOS handlers had unsafe defaults on the SmC setters: - setPeerProxyEnabled: defaulted enabled=false on missing arg (would silently disable sharing on caller bugs) - setPeerManualPort: defaulted port=0 on missing arg (would silently clear the user's manual port override, since 0 has the real semantic of 'no manual port') - setUnboundedEnabled: same Bool-defaults-to-false issue (Copilot didn't flag this one but it has the identical problem) All four now route through requireArg, which surfaces a FlutterError on missing/invalid argument shape instead of defaulting silently. 6. Android setPeerManualPort defaulted port=0 the same way. Switched the elvis '?: 0' to '?: error("Missing port")' to match the SetPeerProxyEnabled pattern on Android. Go build clean. The Swift / Kotlin changes are mechanical and follow patterns already established in the same files (requireArg / error()-on-missing). Co-Authored-By: Claude Opus 4.7 --- .../lantern/handler/MethodHandler.kt | 6 ++++- ios/Runner/Handlers/MethodHandler.swift | 25 +++++++++++++------ lantern-core/core.go | 16 ++++++------ macos/Runner/Handlers/MethodHandler.swift | 17 ++++++++----- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/handler/MethodHandler.kt b/android/app/src/main/kotlin/org/getlantern/lantern/handler/MethodHandler.kt index 5316a0caf3..e70974fdfd 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/handler/MethodHandler.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/handler/MethodHandler.kt @@ -1251,7 +1251,11 @@ class MethodHandler : FlutterPlugin, Methods.SetPeerManualPort.method -> { scope.handleResult(result, "set_peer_manual_port") { - val port = call.argument("port") ?: 0 + // error() surfaces the failure rather than silently + // defaulting to 0 — which would clear the user's + // manual port override on caller bugs. Matches the + // SetPeerProxyEnabled pattern above. + val port = call.argument("port") ?: error("Missing port") Mobile.setPeerManualPort(port.toLong()) } } diff --git a/ios/Runner/Handlers/MethodHandler.swift b/ios/Runner/Handlers/MethodHandler.swift index 0d89558692..0126eca141 100644 --- a/ios/Runner/Handlers/MethodHandler.swift +++ b/ios/Runner/Handlers/MethodHandler.swift @@ -269,8 +269,10 @@ class MethodHandler { // the router. The whole stack is exposed on iOS rather than // desktop-only. case "setPeerProxyEnabled": - let data = call.arguments as? [String: Any] - let enabled = data?["enabled"] as? Bool ?? false + // requireArg surfaces a FlutterError on missing/invalid + // argument shape instead of silently defaulting to false + // (which would disable sharing on caller bugs). + guard let enabled: Bool = requireArg(call: call, name: "enabled", result: result) else { return } self.setPeerProxyEnabled(result: result, enabled: enabled) case "isPeerProxyEnabled": @@ -279,8 +281,12 @@ class MethodHandler { } case "setPeerManualPort": - let data = call.arguments as? [String: Any] - let port = data?["port"] as? Int ?? 0 + // requireArg surfaces a FlutterError on missing/invalid + // argument shape instead of silently defaulting to 0 + // (which has the real semantic of clearing the manual port + // override — caller bugs would silently wipe the user's + // setting). + guard let port: Int = requireArg(call: call, name: "port", result: result) else { return } self.setPeerManualPort(result: result, port: port) case "getPeerManualPort": @@ -289,8 +295,7 @@ class MethodHandler { } case "setUnboundedEnabled": - let data = call.arguments as? [String: Any] - let enabled = data?["enabled"] as? Bool ?? false + guard let enabled: Bool = requireArg(call: call, name: "enabled", result: result) else { return } self.setUnboundedEnabled(result: result, enabled: enabled) case "isUnboundedEnabled": @@ -1173,8 +1178,12 @@ class MethodHandler { } // Share My Connection (samizdat) toggle. Mirrors the macOS handler's - // pattern: blocking Mobile call goes onto a detached Task; result - // and error delivery hop to MainActor. + // pattern: blocking Mobile call goes onto a Task; result and error + // delivery hop to MainActor. Plain Task (not Task.detached) is fine + // for these PatchSettings calls — they finish in milliseconds, so + // inheriting the current actor's executor is cheap. probeUPnP is + // the one exception that uses Task.detached because its M-SEARCH + // wait is multi-second. func setPeerProxyEnabled(result: @escaping FlutterResult, enabled: Bool) { Task { var error: NSError? diff --git a/lantern-core/core.go b/lantern-core/core.go index 66bad95d99..66baa5322b 100644 --- a/lantern-core/core.go +++ b/lantern-core/core.go @@ -425,11 +425,12 @@ func (lc *LanternCore) listenPeerConnectionEvents() { // hit here today. Worth revisiting if Unbounded ever moves out of // process. // - // The subscription is tied to ctx via a separate goroutine that - // calls Unsubscribe on Done. Without this, a reinitialization of - // LanternCore over the process lifetime would leak handlers and - // events.Emit would fan out each connection event to N stale - // registrations. + // defer Unsubscribe so cleanup runs on EITHER path that exits + // this function: ctx cancel (normal) OR PeerConnectionEvents + // returning an error (unexpected stream exit). The earlier + // ctx-watching goroutine missed the error path and leaked both + // the goroutine and the subscription whenever the SSE stream + // died while ctx was still live. unbSub := events.Subscribe(func(evt unbounded.ConnectionEvent) { jsonBytes, err := json.Marshal(map[string]any{ "state": evt.State, @@ -442,10 +443,7 @@ func (lc *LanternCore) listenPeerConnectionEvents() { } lc.notifyFlutter(EventTypePeerConnection, string(jsonBytes)) }) - go func() { - <-lc.ctx.Done() - unbSub.Unsubscribe() - }() + defer unbSub.Unsubscribe() // peer.ConnectionEvent: subscribe via the IPC client's SSE stream. // The events package's globals are process-scoped — events.Emit in diff --git a/macos/Runner/Handlers/MethodHandler.swift b/macos/Runner/Handlers/MethodHandler.swift index 84f5f4a9d2..c42a2bb306 100644 --- a/macos/Runner/Handlers/MethodHandler.swift +++ b/macos/Runner/Handlers/MethodHandler.swift @@ -251,13 +251,19 @@ class MethodHandler { } case "setPeerProxyEnabled": - let data = call.arguments as? [String: Any] - let enabled = data?["enabled"] as? Bool ?? false + // requireArg surfaces a FlutterError on missing/invalid + // argument shape rather than silently defaulting to false + // (which would disable sharing on caller bugs). + guard let enabled: Bool = requireArg(call: call, name: "enabled", result: result) else { return } self.setPeerProxyEnabled(result: result, enabled: enabled) case "setPeerManualPort": - let data = call.arguments as? [String: Any] - let port = data?["port"] as? Int ?? 0 + // requireArg surfaces a FlutterError on missing/invalid + // argument shape rather than silently defaulting to 0 + // (which has the real semantic of clearing the manual port + // override — caller bugs would silently wipe the user's + // setting). + guard let port: Int = requireArg(call: call, name: "port", result: result) else { return } self.setPeerManualPort(result: result, port: port) case "getPeerManualPort": @@ -266,8 +272,7 @@ class MethodHandler { } case "setUnboundedEnabled": - let data = call.arguments as? [String: Any] - let enabled = data?["enabled"] as? Bool ?? false + guard let enabled: Bool = requireArg(call: call, name: "enabled", result: result) else { return } self.setUnboundedEnabled(result: result, enabled: enabled) case "isUnboundedEnabled": From 95c1b5af9e8e268496a524987bc689b24a69e557 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 1 Jun 2026 13:58:38 -0600 Subject: [PATCH 35/37] smc: _resolveAndEmit isClosed guard + honest 'session' stat label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two Copilot fixes (+ a third pushback in the reply): 1. _resolveAndEmit awaits peerLookup. If the notifier is disposed during the await, _eventController.close() has already run; the subsequent _eventController.add would throw 'Bad state: Cannot add event after closing'. Added an isClosed check after the await before the identity check. 2. smc_stat_total_today msgstr read 'Total today' but the ShareState.totalCount is session-scoped (reset on every toggle-on, no day bucket, no persistence). Renamed to 'Total this session' so the label matches the implemented semantics. #8820's rebase later replaces this key with smc_stat_total_helped + 'Total people helped to date' alongside persistence via unboundedTotalHelped — until then the more honest 'session' wording matches reality. Co-Authored-By: Claude Opus 4.7 --- assets/locales/en.po | 2 +- lib/features/share_my_connection/share_my_connection.dart | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/assets/locales/en.po b/assets/locales/en.po index 3a0aaf2c4e..792c034b96 100644 --- a/assets/locales/en.po +++ b/assets/locales/en.po @@ -612,7 +612,7 @@ msgid "smc_stat_active_now" msgstr "Active now" msgid "smc_stat_total_today" -msgstr "Total today" +msgstr "Total this session" msgid "smc_connections_tooltip" msgstr "Most connections are short liveness probes — Lantern clients periodically check that this peer is reachable before sending real traffic. A quick burst from many locations is normal; an arc that lingers represents an actual user session." diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index 0f4e582368..98be47a055 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -486,6 +486,11 @@ class ShareNotifier extends Notifier { } catch (_) { geo = PeerGeo.unknown; } + // The notifier could have been disposed during the await (provider + // teardown closes _eventController). Guard before touching it so + // a late lookup completion doesn't throw "Bad state: Cannot add + // event after closing" on the disposed sink. + if (_eventController.isClosed) return; // Peer may have disconnected before the lookup returned. The map // entry's identity (workerIdx) is the cheapest check. final current = _peerArcs[ip]; From 2b553c7a50677417b9fd16d57bee7a9bd7bc5db9 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 1 Jun 2026 15:08:36 -0600 Subject: [PATCH 36/37] smc: keep-alive ShareNotifier + reflect Unbounded in VPN tile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two new Copilot fixes (+ a third escalation acknowledged in reply): 1. shareProvider was the default (non-annotated) NotifierProvider, which is autoDispose in Riverpod 3.x — so navigating away from the screen disposed the notifier, re-entry reset state to mode=off / active=false even when SmC or Unbounded was still running, and the next toggle tried to re-enable an already- enabled setting. Added `ref.keepAlive()` at the top of build() so the notifier sticks for the process lifetime. onDispose stays registered for the explicit teardown paths (provider container reset, hot reload) so the event subscription + stream controller still get cleaned up. 2. VPN settings tile rendered "Off" when the user had picked "Basic mode (Unbounded)" in the disclosure dialog because the subtitle was driven solely by peerProxy. Added unboundedEnabled to RadianceSettingsState (with a copyWith field + equality / hashCode update), wired the fetch + setter through radianceSettingsProvider, and the tile now reads OR of both to decide whether to show share_my_connection_on_tap_to_view. dart analyze clean on the touched files. Co-Authored-By: Claude Opus 4.7 --- lib/core/models/radiance_settings_state.dart | 23 ++++++++++++++++--- .../provider/radiance_settings_providers.dart | 23 +++++++++++++++++++ lib/features/setting/vpn_setting.dart | 11 ++++++++- .../share_my_connection.dart | 13 +++++++++++ 4 files changed, 66 insertions(+), 4 deletions(-) diff --git a/lib/core/models/radiance_settings_state.dart b/lib/core/models/radiance_settings_state.dart index 28026ff95a..e9ca26363c 100644 --- a/lib/core/models/radiance_settings_state.dart +++ b/lib/core/models/radiance_settings_state.dart @@ -13,6 +13,13 @@ class RadianceSettingsState { final bool splitTunneling; final bool telemetry; final bool peerProxy; + // Local opt-in for the broflake / Unbounded widget proxy. Separate + // from peerProxy because the two are independent toggles — the SmC + // disclosure dialog flips just one of them based on the user's + // choice ("Basic mode" → unboundedEnabled, "Full mode" → peerProxy). + // The VPN settings tile uses BOTH to decide whether to show the + // "On — tap to view" subtitle. + final bool unboundedEnabled; const RadianceSettingsState({ this.blockAds = false, @@ -20,6 +27,7 @@ class RadianceSettingsState { this.splitTunneling = false, this.telemetry = false, this.peerProxy = false, + this.unboundedEnabled = false, }); RadianceSettingsState copyWith({ @@ -28,6 +36,7 @@ class RadianceSettingsState { bool? splitTunneling, bool? telemetry, bool? peerProxy, + bool? unboundedEnabled, }) { return RadianceSettingsState( blockAds: blockAds ?? this.blockAds, @@ -35,6 +44,7 @@ class RadianceSettingsState { splitTunneling: splitTunneling ?? this.splitTunneling, telemetry: telemetry ?? this.telemetry, peerProxy: peerProxy ?? this.peerProxy, + unboundedEnabled: unboundedEnabled ?? this.unboundedEnabled, ); } @@ -46,9 +56,16 @@ class RadianceSettingsState { routingMode == other.routingMode && splitTunneling == other.splitTunneling && telemetry == other.telemetry && - peerProxy == other.peerProxy; + peerProxy == other.peerProxy && + unboundedEnabled == other.unboundedEnabled; @override - int get hashCode => - Object.hash(blockAds, routingMode, splitTunneling, telemetry, peerProxy); + int get hashCode => Object.hash( + blockAds, + routingMode, + splitTunneling, + telemetry, + peerProxy, + unboundedEnabled, + ); } diff --git a/lib/features/home/provider/radiance_settings_providers.dart b/lib/features/home/provider/radiance_settings_providers.dart index 899fd1c128..2a68d2ba27 100644 --- a/lib/features/home/provider/radiance_settings_providers.dart +++ b/lib/features/home/provider/radiance_settings_providers.dart @@ -36,12 +36,14 @@ class RadianceSettings extends _$RadianceSettings { // Manual port forwarding works on any home WiFi where the user owns // the router, so mobile is included rather than desktop-only. final peerF = svc.isPeerProxyEnabled(); + final unboundedF = svc.isUnboundedEnabled(); final blockAds = await blockAdsF; final routing = await routingF; final telemetry = await telemetryF; final split = splitF == null ? null : await splitF; final peer = await peerF; + final unbounded = await unboundedF; if (!ref.mounted) return; const defaults = RadianceSettingsState(); @@ -56,6 +58,8 @@ class RadianceSettings extends _$RadianceSettings { ? defaults.splitTunneling : split.fold((_) => defaults.splitTunneling, (v) => v), peerProxy: peer.fold((_) => defaults.peerProxy, (v) => v), + unboundedEnabled: + unbounded.fold((_) => defaults.unboundedEnabled, (v) => v), ); } @@ -126,6 +130,25 @@ class RadianceSettings extends _$RadianceSettings { }, ); } + + /// Mirror of setPeerProxy for the Unbounded toggle. Returns the + /// Either so callers can react to failure (the Unbounded enable + /// path in share_my_connection.dart uses this for UI rollback). + Future> setUnboundedEnabled(bool value) async { + final svc = ref.read(lanternServiceProvider); + final result = await svc.setUnboundedEnabled(value); + if (!ref.mounted) return result; + return result.fold( + (err) { + appLogger.error('setUnboundedEnabled failed: ${err.error}'); + return left(err); + }, + (_) { + state = state.copyWith(unboundedEnabled: value); + return right(unit); + }, + ); + } } /// Fetches whether user logged in via OAuth from radiance. diff --git a/lib/features/setting/vpn_setting.dart b/lib/features/setting/vpn_setting.dart index b4b26c3b89..0071fe5c53 100644 --- a/lib/features/setting/vpn_setting.dart +++ b/lib/features/setting/vpn_setting.dart @@ -39,6 +39,15 @@ class VPNSetting extends HookConsumerWidget { final peerProxy = ref.watch( radianceSettingsProvider.select((s) => s.peerProxy), ); + final unboundedEnabled = ref.watch( + radianceSettingsProvider.select((s) => s.unboundedEnabled), + ); + // The tile reads "On" when EITHER donor protocol is active — + // the disclosure dialog flips peerProxy for "Full mode" and + // unboundedEnabled for "Basic mode", and the user shouldn't + // see a stale "Off" subtitle just because they picked the + // lower-friction Unbounded path. + final shareActive = peerProxy || unboundedEnabled; return ListView( padding: const EdgeInsets.all(0), @@ -128,7 +137,7 @@ class VPNSetting extends HookConsumerWidget { child: AppTile( label: 'share_my_connection'.i18n, subtitle: Text( - peerProxy + shareActive ? 'share_my_connection_on_tap_to_view'.i18n : 'share_my_connection_subtitle'.i18n, style: textTheme.labelMedium!.copyWith( diff --git a/lib/features/share_my_connection/share_my_connection.dart b/lib/features/share_my_connection/share_my_connection.dart index 98be47a055..ed9d7f2cb0 100644 --- a/lib/features/share_my_connection/share_my_connection.dart +++ b/lib/features/share_my_connection/share_my_connection.dart @@ -201,6 +201,19 @@ class ShareNotifier extends Notifier { @override ShareState build() { + // Keep the notifier alive for the process lifetime. Without this, + // navigating away from the screen disposes the notifier; re-entry + // calls build() again and resets state to mode=off / active=false + // even when SmC or Unbounded is still actually running on the + // backend. The next toggle would then try to re-enable an + // already-enabled setting, and the user loses visibility into + // the active counter and the granular peer-status phase. + // + // ref.onDispose stays registered for explicit Stop/Disable paths + // (provider container reset, hot reload, etc.) so the event + // subscription and stream controller still get cleaned up when + // it does actually happen. + ref.keepAlive(); ref.onDispose(() { _stopEventSubscription(); _eventController.close(); From 34e64f1f7446563497bdd0c5ea30792d765f7fa6 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 1 Jun 2026 15:31:34 -0600 Subject: [PATCH 37/37] smc: revise defer Unsubscribe comment to match actual lifecycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per Copilot — the previous comment claimed parity with the SSE-stream-failure path, but in practice PeerConnectionEvents blocks until ctx cancellation and there's no retry loop wrapping listenPeerConnectionEvents, so the defer effectively runs at process shutdown. Rewrote to spell that out while still calling out why defer is the right shape (future-proofing against early returns / retry wrappers). No code change. Co-Authored-By: Claude Opus 4.7 --- lantern-core/core.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lantern-core/core.go b/lantern-core/core.go index 66baa5322b..c0ec2bcc5a 100644 --- a/lantern-core/core.go +++ b/lantern-core/core.go @@ -425,12 +425,15 @@ func (lc *LanternCore) listenPeerConnectionEvents() { // hit here today. Worth revisiting if Unbounded ever moves out of // process. // - // defer Unsubscribe so cleanup runs on EITHER path that exits - // this function: ctx cancel (normal) OR PeerConnectionEvents - // returning an error (unexpected stream exit). The earlier - // ctx-watching goroutine missed the error path and leaked both - // the goroutine and the subscription whenever the SSE stream - // died while ctx was still live. + // defer Unsubscribe so cleanup runs whenever this function + // returns. In practice that's process shutdown — the function + // is started once from initialize(), the PeerConnectionEvents + // call below blocks until ctx cancellation, and there's no + // retry loop wrapping it. But if PeerConnectionEvents ever did + // exit early on stream error (or a future caller adds retries), + // defer keeps the subscription lifetime bound to this function's + // scope rather than leaking until the next reinitialization — + // which the previous ctx-watching goroutine implicitly relied on. unbSub := events.Subscribe(func(evt unbounded.ConnectionEvent) { jsonBytes, err := json.Marshal(map[string]any{ "state": evt.State,