diff --git a/README.md b/README.md index 6a6a401..8c74a50 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ ![Crypto](https://img.shields.io/badge/Crypto-Noise__NNpsk0-blueviolet?style=for-the-badge) ![Transport](https://img.shields.io/badge/Transport-UDP%20ARQ-informational?style=for-the-badge) -**OSTP** (Ospab Stealth Transport Protocol) is a high-performance, censorship-resistant zero-signature transport protocol. It implements a custom, reliable ARQ transport over UDP, as well as a UoT (UDP-over-TCP) mode. Every byte on the wire — including packet headers — is cryptographically indistinguishable from random noise, making it highly resistant to Deep Packet Inspection (DPI), active probing, and statistical traffic analysis. +> A fast, custom encrypted transport protocol written in Rust. + +**OSTP** (Ospab Stealth Transport Protocol) is a high-performance transport protocol. It implements a custom ARQ transport over UDP, as well as a UoT (UDP-over-TCP) mode. Every byte on the wire — including packet headers — is cryptographically indistinguishable from random noise, making it highly resistant to Deep Packet Inspection (DPI). --- diff --git a/README.ru.md b/README.ru.md index 8d9912a..0445759 100644 --- a/README.ru.md +++ b/README.ru.md @@ -8,7 +8,9 @@ ![Crypto](https://img.shields.io/badge/Crypto-Noise__NNpsk0-blueviolet?style=for-the-badge) ![Transport](https://img.shields.io/badge/Transport-UDP%20ARQ-informational?style=for-the-badge) -**OSTP** (Ospab Stealth Transport Protocol) — высокопроизводительный, устойчивый к цензуре zero-signature транспортный протокол. Реализует собственный надёжный ARQ-транспорт поверх UDP, а также режим UoT (UDP-over-TCP). Каждый байт, включая заголовки пакетов, криптографически неотличим от случайного шума. Полностью устойчив к Deep Packet Inspection (DPI), активному зондированию и статистическому анализу трафика. +> Быстрый кастомный зашифрованный транспортный протокол на Rust. + +**OSTP** (Ospab Stealth Transport Protocol) — кастомный транспортный протокол. Реализует собственный ARQ-транспорт поверх UDP, а также режим UoT (UDP-over-TCP). Каждый байт, включая заголовки пакетов, криптографически неотличим от случайного шума, что делает его устойчивым к системам глубокого анализа трафика (DPI). --- diff --git a/icons/circle.png b/icons/circle.png new file mode 100644 index 0000000..c4ba45b Binary files /dev/null and b/icons/circle.png differ diff --git a/icons/sqare.png b/icons/sqare.png new file mode 100644 index 0000000..9b17971 Binary files /dev/null and b/icons/sqare.png differ diff --git a/icons/sqare.svg b/icons/sqare.svg new file mode 100644 index 0000000..24d1384 --- /dev/null +++ b/icons/sqare.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/netstack-smoltcp/src/tcp.rs b/netstack-smoltcp/src/tcp.rs index fa2c66f..91358d2 100644 --- a/netstack-smoltcp/src/tcp.rs +++ b/netstack-smoltcp/src/tcp.rs @@ -34,9 +34,9 @@ use crate::{ Runner, }; -// NOTE: Default buffer could contain 20 AEAD packets -const DEFAULT_TCP_SEND_BUFFER_SIZE: u32 = 0x3FFF * 20; -const DEFAULT_TCP_RECV_BUFFER_SIZE: u32 = 0x3FFF * 20; +// Reduced buffer sizes to 16KB to prevent excessive memory overhead (was 0x3FFF * 20 = 327KB per buffer) +const DEFAULT_TCP_SEND_BUFFER_SIZE: u32 = 16384; +const DEFAULT_TCP_RECV_BUFFER_SIZE: u32 = 16384; #[derive(Debug, Clone, Copy, Eq, PartialEq)] enum TcpSocketState { @@ -542,7 +542,7 @@ impl AsyncWrite for TcpStream { fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut control = self.control.lock(); - if matches!(control.send_state, TcpSocketState::Closed) { + if matches!(control.send_state, TcpSocketState::Closed | TcpSocketState::Closing) { return Ok(()).into(); } diff --git a/ostp-client/src/tunnel/native_handler.rs b/ostp-client/src/tunnel/native_handler.rs index d137786..bb9ce10 100644 --- a/ostp-client/src/tunnel/native_handler.rs +++ b/ostp-client/src/tunnel/native_handler.rs @@ -255,17 +255,17 @@ pub async fn run_native_tunnel( if !should_bypass { if let Some(proc_name) = crate::tunnel::process_lookup::get_process_name_from_port(local.port()) { if debug { - tracing::info!("TUN TCP lookup: port {} -> process {}", local.port(), proc_name); + tracing::debug!("TUN TCP lookup: port {} -> process {}", local.port(), proc_name); } if matcher.match_process(&proc_name) { if debug { - tracing::info!("TUN TCP BYPASS (Process match): {} → {remote}", proc_name); + tracing::debug!("TUN TCP BYPASS (Process match): {} → {remote}", proc_name); } should_bypass = true; } } else { if debug { - tracing::info!("TUN TCP lookup: port {} -> no process found", local.port()); + tracing::debug!("TUN TCP lookup: port {} -> no process found", local.port()); } } } diff --git a/ostp-client/src/tunnel/proxy.rs b/ostp-client/src/tunnel/proxy.rs index ba7c206..4db81b5 100644 --- a/ostp-client/src/tunnel/proxy.rs +++ b/ostp-client/src/tunnel/proxy.rs @@ -665,9 +665,7 @@ async fn handle_proxy_client( ).await; } - if true { - tracing::info!("proxy CONNECT stream_id={stream_id} target={target}"); - } + tracing::debug!("proxy CONNECT stream_id={stream_id} target={target}"); let target_host = if let Some((host, _)) = split_host_port(&target) { host } else { target.clone() }; let target_port = match split_host_port(&target) { Some((_, p)) => p, None => 0 }; if matcher.should_bypass_target(&target_host, target_port, connect_timeout).await { diff --git a/ostp-client/src/tunnel/udp_nat.rs b/ostp-client/src/tunnel/udp_nat.rs index 5f94301..93ab9c2 100644 --- a/ostp-client/src/tunnel/udp_nat.rs +++ b/ostp-client/src/tunnel/udp_nat.rs @@ -50,17 +50,17 @@ pub async fn run_udp_nat( if !should_bypass { if let Some(proc_name) = crate::tunnel::process_lookup::get_process_name_from_port_udp(src.port()) { if debug { - tracing::info!("TUN UDP lookup: port {} -> process {}", src.port(), proc_name); + tracing::debug!("TUN UDP lookup: port {} -> process {}", src.port(), proc_name); } if matcher_guard.match_process(&proc_name) { should_bypass = true; if debug { - tracing::info!("TUN UDP BYPASS (Process match): {} ({} → {})", proc_name, src, dst); + tracing::debug!("TUN UDP BYPASS (Process match): {} ({} → {})", proc_name, src, dst); } } } else { if debug { - tracing::info!("TUN UDP lookup: port {} -> no process found", src.port()); + tracing::debug!("TUN UDP lookup: port {} -> no process found", src.port()); } } } diff --git a/ostp-flutter/android/app/build.gradle.kts b/ostp-flutter/android/app/build.gradle.kts index a61acab..268ee60 100644 --- a/ostp-flutter/android/app/build.gradle.kts +++ b/ostp-flutter/android/app/build.gradle.kts @@ -28,6 +28,10 @@ android { targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName + + ndk { + abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86_64") + } } buildTypes { diff --git a/ostp-flutter/android/app/src/main/AndroidManifest.xml b/ostp-flutter/android/app/src/main/AndroidManifest.xml index 7fb4afc..54f4f4f 100644 --- a/ostp-flutter/android/app/src/main/AndroidManifest.xml +++ b/ostp-flutter/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + @@ -10,6 +11,7 @@ android:label="ostp_client" android:name="${applicationName}" android:icon="@mipmap/launcher_icon" + android:roundIcon="@mipmap/launcher_icon_round" android:extractNativeLibs="true"> diff --git a/ostp-flutter/android/app/src/main/kotlin/com/ospab/ostp_client/MainActivity.kt b/ostp-flutter/android/app/src/main/kotlin/com/ospab/ostp_client/MainActivity.kt index ab1ba66..1f9dd8d 100644 --- a/ostp-flutter/android/app/src/main/kotlin/com/ospab/ostp_client/MainActivity.kt +++ b/ostp-flutter/android/app/src/main/kotlin/com/ospab/ostp_client/MainActivity.kt @@ -133,6 +133,6 @@ class MainActivity : FlutterActivity() { if (pendingConfigJson != null) { intent.putExtra("configJson", pendingConfigJson) } - startService(intent) + androidx.core.content.ContextCompat.startForegroundService(this, intent) } } diff --git a/ostp-flutter/android/app/src/main/kotlin/com/ospab/ostp_client/OstpVpnService.kt b/ostp-flutter/android/app/src/main/kotlin/com/ospab/ostp_client/OstpVpnService.kt index 098d243..3803894 100644 --- a/ostp-flutter/android/app/src/main/kotlin/com/ospab/ostp_client/OstpVpnService.kt +++ b/ostp-flutter/android/app/src/main/kotlin/com/ospab/ostp_client/OstpVpnService.kt @@ -16,6 +16,7 @@ import java.io.IOException import androidx.annotation.Keep import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.app.ServiceCompat @Keep class OstpVpnService : VpnService() { @@ -56,7 +57,7 @@ class OstpVpnService : VpnService() { if (action == "START") { val configJson = intent.getStringExtra("configJson") ?: return START_NOT_STICKY // Launch foreground immediately so Android doesn't kill us - startForeground(NOTIF_ID, buildNotification(connecting = true)) + ServiceCompat.startForeground(this, NOTIF_ID, buildNotification(connecting = true), ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE) startVpn(configJson) } else if (action == "STOP") { stopVpn() @@ -271,6 +272,9 @@ class OstpVpnService : VpnService() { } catch (e: Throwable) { Log.e("OstpVpnService", "Error starting VPN", e) + android.os.Handler(android.os.Looper.getMainLooper()).post { + android.widget.Toast.makeText(applicationContext, "VPN Error: ${e.message}", android.widget.Toast.LENGTH_LONG).show() + } stopVpn() } diff --git a/ostp-flutter/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/ostp-flutter/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..3ba4c1f Binary files /dev/null and b/ostp-flutter/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png differ diff --git a/ostp-flutter/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/ostp-flutter/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..6d6c333 Binary files /dev/null and b/ostp-flutter/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png differ diff --git a/ostp-flutter/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/ostp-flutter/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..bf6f93a Binary files /dev/null and b/ostp-flutter/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ diff --git a/ostp-flutter/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/ostp-flutter/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..7d30bac Binary files /dev/null and b/ostp-flutter/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ diff --git a/ostp-flutter/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/ostp-flutter/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..c21e2d3 Binary files /dev/null and b/ostp-flutter/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ diff --git a/ostp-flutter/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml b/ostp-flutter/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml new file mode 100644 index 0000000..5f349f7 --- /dev/null +++ b/ostp-flutter/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/ostp-flutter/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/ostp-flutter/android/app/src/main/res/mipmap-hdpi/launcher_icon.png index d35010e..e7db254 100644 Binary files a/ostp-flutter/android/app/src/main/res/mipmap-hdpi/launcher_icon.png and b/ostp-flutter/android/app/src/main/res/mipmap-hdpi/launcher_icon.png differ diff --git a/ostp-flutter/android/app/src/main/res/mipmap-hdpi/launcher_icon_round.png b/ostp-flutter/android/app/src/main/res/mipmap-hdpi/launcher_icon_round.png new file mode 100644 index 0000000..d35010e Binary files /dev/null and b/ostp-flutter/android/app/src/main/res/mipmap-hdpi/launcher_icon_round.png differ diff --git a/ostp-flutter/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/ostp-flutter/android/app/src/main/res/mipmap-mdpi/launcher_icon.png index efd0319..07d142f 100644 Binary files a/ostp-flutter/android/app/src/main/res/mipmap-mdpi/launcher_icon.png and b/ostp-flutter/android/app/src/main/res/mipmap-mdpi/launcher_icon.png differ diff --git a/ostp-flutter/android/app/src/main/res/mipmap-mdpi/launcher_icon_round.png b/ostp-flutter/android/app/src/main/res/mipmap-mdpi/launcher_icon_round.png new file mode 100644 index 0000000..efd0319 Binary files /dev/null and b/ostp-flutter/android/app/src/main/res/mipmap-mdpi/launcher_icon_round.png differ diff --git a/ostp-flutter/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/ostp-flutter/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png index a05c278..903d88c 100644 Binary files a/ostp-flutter/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png and b/ostp-flutter/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png differ diff --git a/ostp-flutter/android/app/src/main/res/mipmap-xhdpi/launcher_icon_round.png b/ostp-flutter/android/app/src/main/res/mipmap-xhdpi/launcher_icon_round.png new file mode 100644 index 0000000..a05c278 Binary files /dev/null and b/ostp-flutter/android/app/src/main/res/mipmap-xhdpi/launcher_icon_round.png differ diff --git a/ostp-flutter/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/ostp-flutter/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png index 6bcf903..6f29b7e 100644 Binary files a/ostp-flutter/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png and b/ostp-flutter/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png differ diff --git a/ostp-flutter/android/app/src/main/res/mipmap-xxhdpi/launcher_icon_round.png b/ostp-flutter/android/app/src/main/res/mipmap-xxhdpi/launcher_icon_round.png new file mode 100644 index 0000000..6bcf903 Binary files /dev/null and b/ostp-flutter/android/app/src/main/res/mipmap-xxhdpi/launcher_icon_round.png differ diff --git a/ostp-flutter/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/ostp-flutter/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png index d06daed..6cefa52 100644 Binary files a/ostp-flutter/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png and b/ostp-flutter/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png differ diff --git a/ostp-flutter/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon_round.png b/ostp-flutter/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon_round.png new file mode 100644 index 0000000..d06daed Binary files /dev/null and b/ostp-flutter/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon_round.png differ diff --git a/ostp-flutter/android/app/src/main/res/values/colors.xml b/ostp-flutter/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..fd9fca5 --- /dev/null +++ b/ostp-flutter/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #08080F + \ No newline at end of file diff --git a/ostp-flutter/android/app/src/main/res/values/ic_launcher_background.xml b/ostp-flutter/android/app/src/main/res/values/ic_launcher_background.xml deleted file mode 100644 index ea9c223..0000000 --- a/ostp-flutter/android/app/src/main/res/values/ic_launcher_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #fff - \ No newline at end of file diff --git a/ostp-flutter/android/gradle.properties b/ostp-flutter/android/gradle.properties index fbee1d8..77ebc19 100644 --- a/ostp-flutter/android/gradle.properties +++ b/ostp-flutter/android/gradle.properties @@ -1,2 +1,2 @@ -org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=1G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true diff --git a/ostp-flutter/android_icon_backup.png b/ostp-flutter/android_icon_backup.png new file mode 100644 index 0000000..c4ba45b Binary files /dev/null and b/ostp-flutter/android_icon_backup.png differ diff --git a/ostp-flutter/lib/ui/settings_screen.dart b/ostp-flutter/lib/ui/settings_screen.dart index 5b5c296..ee1f3a2 100644 --- a/ostp-flutter/lib/ui/settings_screen.dart +++ b/ostp-flutter/lib/ui/settings_screen.dart @@ -9,6 +9,10 @@ import 'package:mobile_scanner/mobile_scanner.dart'; import 'app_routing_screen.dart'; import 'logs_screen.dart'; import 'qr_scanner_screen.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:http/http.dart' as http; +import 'package:url_launcher/url_launcher.dart'; +import 'package:package_info_plus/package_info_plus.dart'; class SettingsScreen extends StatefulWidget { final SharedPreferences prefs; @@ -39,7 +43,7 @@ class _SettingsScreenState extends State { String _tunStack = 'ostp'; // 'system' | 'ostp' bool _muxEnabled = false; late TextEditingController _muxSessionsCtrl; - + bool _isCheckingUpdates = false; @override void initState() { @@ -174,6 +178,11 @@ class _SettingsScreenState extends State { onPressed: () => Navigator.pop(context), ), actions: [ + IconButton( + icon: const Icon(Icons.share_rounded), + tooltip: 'Share Config', + onPressed: _showShareModal, + ), IconButton( icon: const Icon(Icons.qr_code_scanner_rounded), onPressed: () async { @@ -487,10 +496,190 @@ class _SettingsScreenState extends State { ), ), + const SizedBox(height: 16), + + InkWell( + onTap: _isCheckingUpdates ? null : _checkForUpdates, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.02), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.white.withOpacity(0.05)), + ), + child: Row( + children: [ + Icon(Icons.system_update_rounded, color: Colors.white70, size: 24), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Check for Updates', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: Colors.white), + ), + SizedBox(height: 4), + Text( + _isCheckingUpdates ? 'Checking...' : 'Check latest release on GitHub', + style: TextStyle(fontSize: 13, color: Colors.white54), + ), + ], + ), + ), + if (_isCheckingUpdates) + const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white54), + ) + else + const Icon(Icons.arrow_forward_ios_rounded, color: Colors.white54, size: 16), + ], + ), + ), + ), + const SizedBox(height: 40), ], ), ); } + + String _generateShareUrl() { + final host = _serverCtrl.text.trim(); + final key = Uri.encodeComponent(_keyCtrl.text.trim()); + if (host.isEmpty || key.isEmpty) return ''; + + final queryParams = []; + if (_stealthSniCtrl.text.trim().isNotEmpty) { + queryParams.add('sni=${Uri.encodeComponent(_stealthSniCtrl.text.trim())}'); + } + if (_pbkCtrl.text.trim().isNotEmpty) { + queryParams.add('pbk=${Uri.encodeComponent(_pbkCtrl.text.trim())}'); + } + if (_sidCtrl.text.trim().isNotEmpty) { + queryParams.add('sid=${Uri.encodeComponent(_sidCtrl.text.trim())}'); + } + if (_wss) { + queryParams.add('wss=true'); + } + if (_transportMode != 'udp') { + queryParams.add('type=$_transportMode'); + } + + final queryString = queryParams.isEmpty ? '' : '?${queryParams.join('&')}'; + return 'ostp://$key@$host$queryString'; + } + + void _showShareModal() { + final url = _generateShareUrl(); + if (url.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Server Address and Access Key are required to share.'))); + return; + } + + showDialog( + context: context, + builder: (context) { + return AlertDialog( + backgroundColor: Theme.of(context).colorScheme.surface, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + title: const Text('Share Config', textAlign: TextAlign.center), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + ), + child: QrImageView( + data: url, + version: QrVersions.auto, + size: 200.0, + ), + ), + const SizedBox(height: 20), + ElevatedButton.icon( + onPressed: () { + Clipboard.setData(ClipboardData(text: url)); + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Copied to clipboard'))); + Navigator.pop(context); + }, + icon: const Icon(Icons.copy_rounded, color: Colors.white), + label: const Text('Copy Link', style: TextStyle(color: Colors.white)), + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + ) + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Close'), + ) + ], + ); + } + ); + } + + Future _checkForUpdates() async { + if (_isCheckingUpdates) return; + setState(() { _isCheckingUpdates = true; }); + try { + final packageInfo = await PackageInfo.fromPlatform(); + final currentVersion = packageInfo.version; + + final response = await http.get(Uri.parse('https://api.github.com/repos/ospab/ostp/releases/latest')); + if (response.statusCode == 200) { + final data = json.decode(response.body); + final latestVersion = (data['tag_name'] as String).replaceAll('v', ''); + + final hasUpdate = latestVersion != currentVersion; + + if (!mounted) return; + showDialog( + context: context, + builder: (context) { + return AlertDialog( + backgroundColor: Theme.of(context).colorScheme.surface, + title: Text(hasUpdate ? 'Update Available!' : 'Up to Date'), + content: Text(hasUpdate + ? 'A new version ($latestVersion) is available on GitHub. You are currently running version $currentVersion.' + : 'You are running the latest version ($currentVersion).'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Close'), + ), + if (hasUpdate) + TextButton( + onPressed: () { + Navigator.pop(context); + final url = Uri.parse(data['html_url'] ?? 'https://github.com/ospab/ostp/releases/latest'); + launchUrl(url, mode: LaunchMode.externalApplication); + }, + child: const Text('Download'), + ) + ], + ); + } + ); + } else { + throw Exception('HTTP ${response.statusCode}'); + } + } catch (e) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error checking updates: $e'))); + } finally { + if (mounted) setState(() { _isCheckingUpdates = false; }); + } + } } diff --git a/ostp-flutter/pubspec.lock b/ostp-flutter/pubspec.lock index c1f396a..aee1865 100644 --- a/ostp-flutter/pubspec.lock +++ b/ostp-flutter/pubspec.lock @@ -97,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + ffi_leak_tracker: + dependency: transitive + description: + name: ffi_leak_tracker + sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97" + url: "https://pub.dev" + source: hosted + version: "0.1.2" file: dependency: transitive description: @@ -136,6 +144,22 @@ packages: description: flutter source: sdk version: "0.0.0" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" image: dependency: transitive description: @@ -224,6 +248,22 @@ packages: url: "https://pub.dev" source: hosted version: "5.2.3" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: "4bf625947f6c7713ee242296a682e23e44823c09cf9d79e4f1238923c92db852" + url: "https://pub.dev" + source: hosted + version: "10.1.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: db762cb2f4f25ee60fb6359773861b0f199e00b90d237bd85a76a1e806b46ef4 + url: "https://pub.dev" + source: hosted + version: "4.1.0" path: dependency: transitive description: @@ -288,6 +328,22 @@ packages: url: "https://pub.dev" source: hosted version: "6.5.0" + qr: + dependency: transitive + description: + name: qr + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + qr_flutter: + dependency: "direct main" + description: + name: qr_flutter + sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" + url: "https://pub.dev" + source: hosted + version: "4.1.0" screen_retriever: dependency: transitive description: @@ -453,6 +509,78 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c" + url: "https://pub.dev" + source: hosted + version: "6.3.30" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" vector_math: dependency: transitive description: @@ -477,6 +605,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: ba6f4bba816c8d7e3c1580e170f3786d216951cc6b94babc3b814c08d2cb2738 + url: "https://pub.dev" + source: hosted + version: "6.3.0" window_manager: dependency: "direct main" description: @@ -511,4 +647,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.11.4 <4.0.0" - flutter: ">=3.35.0" + flutter: ">=3.38.1" diff --git a/ostp-flutter/pubspec.yaml b/ostp-flutter/pubspec.yaml index 0bca5b5..ff4ad24 100644 --- a/ostp-flutter/pubspec.yaml +++ b/ostp-flutter/pubspec.yaml @@ -1,5 +1,5 @@ name: ostp_client -description: "A new Flutter project." +description: "OSTP VPN Client by Ospab Foundation." # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.2.96+11 +version: 0.2.97+12 environment: sdk: ^3.11.4 @@ -38,6 +38,10 @@ dependencies: mobile_scanner: ^5.0.0 window_manager: ^0.5.1 tray_manager: ^0.5.2 + qr_flutter: ^4.1.0 + http: ^1.6.0 + url_launcher: ^6.3.2 + package_info_plus: ^10.1.0 dev_dependencies: flutter_test: @@ -54,7 +58,7 @@ dev_dependencies: flutter_launcher_icons: android: "launcher_icon" ios: false - image_path: "android_icon.png" + image_path: "../icons/sqare.png" # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/ostp-flutter/resize_icon.ps1 b/ostp-flutter/resize_icon.ps1 new file mode 100644 index 0000000..a6cf83b --- /dev/null +++ b/ostp-flutter/resize_icon.ps1 @@ -0,0 +1,44 @@ +Add-Type -AssemblyName System.Drawing +$imgPath = "D:\ospab-projects\ostp\ostp-flutter\android_icon.png" +$backupPath = "D:\ospab-projects\ostp\ostp-flutter\android_icon_backup.png" + +# Copy as backup +Copy-Item -Path $imgPath -Destination $backupPath -Force + +# Load image +$srcImg = [System.Drawing.Image]::FromFile($imgPath) +$w = $srcImg.Width +$h = $srcImg.Height + +# Create new transparent bitmap of the SAME size +$bmp = New-Object System.Drawing.Bitmap($w, $h) +$bmp.MakeTransparent() +$graphics = [System.Drawing.Graphics]::FromImage($bmp) + +# Set high quality resizing +$graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic +$graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality +$graphics.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality + +# Clear with transparent +$graphics.Clear([System.Drawing.Color]::Transparent) + +# We want to scale to 50% and center +$newW = [math]::Round($w / 2) +$newH = [math]::Round($h / 2) +$x = [math]::Round(($w - $newW) / 2) +$y = [math]::Round(($h - $newH) / 2) + +# Draw image scaled down +$rect = New-Object System.Drawing.Rectangle($x, $y, $newW, $newH) +$graphics.DrawImage($srcImg, $rect) + +# Dispose graphics and src before saving over +$graphics.Dispose() +$srcImg.Dispose() + +# Save over original +$bmp.Save($imgPath, [System.Drawing.Imaging.ImageFormat]::Png) +$bmp.Dispose() + +Write-Output "Successfully resized image." diff --git a/ostp-gui/src-tauri/Cargo.lock b/ostp-gui/src-tauri/Cargo.lock index 93b26aa..eb91ebb 100644 --- a/ostp-gui/src-tauri/Cargo.lock +++ b/ostp-gui/src-tauri/Cargo.lock @@ -2665,7 +2665,7 @@ dependencies = [ [[package]] name = "ostp-client" -version = "0.2.95" +version = "0.2.97" dependencies = [ "anyhow", "base64 0.22.1", @@ -2699,7 +2699,7 @@ dependencies = [ [[package]] name = "ostp-core" -version = "0.2.95" +version = "0.2.97" dependencies = [ "anyhow", "bytes", @@ -2734,7 +2734,7 @@ dependencies = [ [[package]] name = "ostp-tun" -version = "0.2.95" +version = "0.2.97" dependencies = [ "anyhow", "libc", diff --git a/ostp-gui/src-tauri/src/lib.rs b/ostp-gui/src-tauri/src/lib.rs index 062fb5a..217a21a 100644 --- a/ostp-gui/src-tauri/src/lib.rs +++ b/ostp-gui/src-tauri/src/lib.rs @@ -720,7 +720,13 @@ fn launch_as_admin(exe: &std::path::PathBuf, token: &str, port: u16) -> anyhow:: let exe_wstr: Vec = exe.as_os_str().encode_wide().chain(Some(0)).collect(); let verb_wstr: Vec = OsStr::new("runas").encode_wide().chain(Some(0)).collect(); - let params_str = format!("--port {} --token {}", port, token); + + // Write token to temp file for security instead of passing via cmdline + let temp_dir = std::env::temp_dir(); + let token_file = temp_dir.join(format!("ostp_auth_{}.tmp", rand::random::())); + std::fs::write(&token_file, token)?; + + let params_str = format!("--port {} --token-file \"{}\"", port, token_file.display()); let params_wstr: Vec = OsStr::new(¶ms_str).encode_wide().chain(Some(0)).collect(); #[link(name = "shell32")] extern "system" { fn ShellExecuteW(h: *mut std::ffi::c_void, op: *const u16, f: *const u16, p: *const u16, d: *const u16, s: i32) -> isize; } diff --git a/ostp-gui/src-tauri/tauri.conf.json b/ostp-gui/src-tauri/tauri.conf.json index 86bebb2..a1878b1 100644 --- a/ostp-gui/src-tauri/tauri.conf.json +++ b/ostp-gui/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "ostp-gui", - "version": "0.2.96", + "version": "0.2.97", "identifier": "com.ospab.ostp", "build": { "frontendDist": "../src" @@ -23,6 +23,7 @@ "bundle": { "active": true, "targets": "all", + "publisher": "Ospab Foundation", "icon": [ "icons/32x32.png", "icons/128x128.png", diff --git a/ostp-tun-helper/src/main.rs b/ostp-tun-helper/src/main.rs index a13e1ee..0090948 100644 --- a/ostp-tun-helper/src/main.rs +++ b/ostp-tun-helper/src/main.rs @@ -61,23 +61,27 @@ async fn main() -> Result<()> { } } - let mut expected_token = String::new(); + let mut expected_token = std::env::var("OSTP_TUN_TOKEN").unwrap_or_default(); let mut port = 53211u16; let args: Vec = std::env::args().collect(); for i in 1..args.len() { if args[i] == "--port" && i + 1 < args.len() { port = args[i + 1].parse().unwrap_or(53211); } - if args[i] == "--token" && i + 1 < args.len() { - expected_token = args[i + 1].clone(); + if args[i] == "--token-file" && i + 1 < args.len() { + let path = &args[i + 1]; + if let Ok(content) = std::fs::read_to_string(path) { + expected_token = content.trim().to_string(); + let _ = std::fs::remove_file(path); // securely delete after reading + } } } log_to_file("Helper started (TCP mode)"); if expected_token.is_empty() { - log_to_file("FATAL: OSTP_TUN_TOKEN environment variable is required for security. Unauthorized access denied."); - return Err(anyhow::anyhow!("OSTP_TUN_TOKEN environment variable is required")); + log_to_file("FATAL: Auth token is required for security (--token-file or OSTP_TUN_TOKEN)."); + return Err(anyhow::anyhow!("Auth token is required")); } if let Err(e) = run_server(expected_token, port).await {