diff --git a/README.md b/README.md
index 6a6a401..8c74a50 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,9 @@


-**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 @@


-**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 {