Compare commits

...

16 Commits

Author SHA1 Message Date
Evgeny Poberezkin
7e9db28201 disable transport test 2021-10-30 22:27:42 +01:00
Evgeny Poberezkin
6d5b023b67 transport works 2021-10-30 22:25:31 +01:00
Evgeny Poberezkin
422c8bf88d use PSS signature verification 2021-10-30 21:37:28 +01:00
Evgeny Poberezkin
0988eaef08 update dependency git ref 2021-10-30 20:05:31 +01:00
Evgeny Poberezkin
1b6f4683d9 send SMP command and received stream of messages 2021-10-28 19:55:40 +01:00
Evgeny Poberezkin
41681aaa6b SMP handshake - validate key hash and protocol version 2021-10-26 21:38:15 +01:00
Evgeny Poberezkin
e8fe5632f4 SMP transport handshake works 2021-10-21 20:16:11 +01:00
Evgeny Poberezkin
61e452356b transport test 2021-10-18 07:28:49 +01:00
Evgeny Poberezkin
78565914db SMP client transport handshake (WIP) 2021-10-16 22:56:41 +01:00
Evgeny Poberezkin
d79c9d7ef5 cryptographic primitives (#118)
* AES-GSM encryption with padding

* RSA-OAEP encryption and key generation

* SPKI encoding/decoding RSA public keys

* rename functions

* encode/decode RSA keys using asn1lib library

* remove poitycastle namespace

* remove unnecessary typecheck

* fix: ci

Co-authored-by: alex <alex@tekartik.com>
2021-10-16 14:02:06 +01:00
Evgeny Poberezkin
fd247a4e6b style: prefer single quotes (#117) 2021-10-09 13:12:12 +01:00
Evgeny Poberezkin
19ef1f65db abstract Transport class, SocketTransport class (#115)
* abstract Transport class, SocketTransport class

* build: import simplexmq

* feat: simple io transport unit test for read and write

* more efficient buffer extension

Co-authored-by: alex <alex@tekartik.com>
2021-10-09 12:42:41 +01:00
Alexandre Roux
09ace76b82 build: setup dart simple ci using github actions (#114) 2021-10-04 20:27:37 +01:00
Evgeny Poberezkin
e198424da8 move flutter app to packages (#112)
* remove flutter app from root

* add flutter app to packages

* ci: haskell build only on haskell changes

* update app identifiers
2021-10-03 19:59:08 +01:00
Evgeny Poberezkin
a39cd2990f SMP protocol commands encoding/decoding (#111)
* SMP protocol commands encoding/decoding

* change "var" to type

* Parser `word` method now returns null if the word is empty

* refactor Parser `word` method

* move Parser `end` getter

* add linter rules, move linter options to root

* remove omit_local_variable_types linter rule

* ci: only build haskell on changes
2021-10-03 19:10:11 +01:00
Evgeny Poberezkin
fec99d526a Dart / flutter app template (#109) 2021-10-02 11:06:10 +01:00
109 changed files with 3634 additions and 3 deletions

View File

@@ -7,7 +7,13 @@ on:
- v5
tags:
- "v*"
paths:
- haskell/**
- .github/workflows/build_haskell.yml
pull_request:
paths:
- haskell/**
- .github/workflows/build_haskell.yml
jobs:
prepare-release:

32
.github/workflows/ci_dart.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Run CI
on:
push:
workflow_dispatch:
schedule:
- cron: '0 0 * * 0' # every sunday at midnight
jobs:
test:
name: Test on ${{ matrix.os }} / ${{ matrix.flutter }}
runs-on: ${{ matrix.os }}
defaults:
run:
working-directory: packages/repo_support
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
flutter: [stable, beta, dev]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: '12.x'
- uses: subosito/flutter-action@v1
with:
channel: ${{ matrix.flutter }}
- run: flutter --version
- run: flutter upgrade
- run: dart --version
- run: dart pub get
- run: dart run tool/run_ci.dart

51
.gitignore vendored
View File

@@ -43,9 +43,54 @@ cabal.project.local~
*.cabal
stack.yaml.lock
# Idris
*.ibc
# chat database
*.db
*.db.bak
# Misc
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ
*.iml
*.ipr
*.iws
.idea/
# VS Code
.vscode/
# Flutter/Dart/Pub
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Default behavior, only keep it for apps
pubspec.lock
# Web
lib/generated_plugin_registrant.dart
# Symbolication
app.*.symbols
# Obfuscation
app.*.map.json
# Android Studio build artifacts
/android/app/debug
/android/app/profile
/android/app/release

View File

@@ -0,0 +1,44 @@
include: package:lints/recommended.yaml
linter:
rules:
prefer_single_quotes: true
constant_identifier_names: false
always_declare_return_types: true
avoid_dynamic_calls: true
avoid_empty_else: true
avoid_relative_lib_imports: true
avoid_shadowing_type_parameters: true
avoid_slow_async_io: true
avoid_types_as_parameter_names: true
await_only_futures: true
camel_case_extensions: true
camel_case_types: true
cancel_subscriptions: true
curly_braces_in_flow_control_structures: true
directives_ordering: true
empty_catches: true
hash_and_equals: true
iterable_contains_unrelated_type: true
list_remove_unrelated_type: true
no_adjacent_strings_in_list: true
no_duplicate_case_values: true
package_api_docs: true
package_prefixed_library_names: true
prefer_generic_function_type_aliases: true
prefer_is_empty: true
prefer_is_not_empty: true
prefer_iterable_whereType: true
prefer_typing_uninitialized_variables: true
sort_child_properties_last: true
test_types_in_equals: true
throw_in_finally: true
unawaited_futures: true
unnecessary_import: true
unnecessary_null_aware_assignments: true
unnecessary_statements: true
unnecessary_type_check: true
unrelated_type_equality_checks: true
unsafe_html: true
use_full_hex_values_for_flutter_colors: true
valid_regexps: true

23
packages/repo_support/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# See https://www.dartlang.org/guides/libraries/private-files
# Files and directories created by pub
.dart_tool/
.packages
.pub/
build/
# If you're building an application, you may want to check-in your pubspec.lock
pubspec.lock
# Directory created by dartdoc
# If you don't generate documentation locally you can remove this line.
doc/api/
# Intellij
*.idea/
*.iml
# VS code
.vscode
# Local
.local/

View File

@@ -0,0 +1,8 @@
# repo_support
App flutter utils repo support
```
dart run tool/run_ci.dart
```

View File

@@ -0,0 +1,11 @@
name: simplex_repo_support
description: SimpleX build tools
version: 0.2.0
publish_to: none
environment:
sdk: '>=2.12.0 <3.0.0'
dev_dependencies:
dev_test:

View File

@@ -0,0 +1,14 @@
// ignore_for_file: prefer_double_quotes
import 'package:dev_test/package.dart';
import 'package:path/path.dart';
Future main() async {
for (var dir in [
'simplex_app',
'simplexmq',
'repo_support',
]) {
await packageRunCi(join('..', dir));
}
}

2
packages/simplex_app/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
# Keep for apps
!pubspec.lock

View File

@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 4cc385b4b84ac2f816d939a49ea1f328c4e0b48e
channel: stable
project_type: app

View File

@@ -0,0 +1,16 @@
# simplex_app
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@@ -0,0 +1,45 @@
# https://dart.dev/guides/language/analysis-options
include: package:flutter_lints/flutter.yaml
linter:
# https://dart-lang.github.io/linter/lints/index.html.
rules:
prefer_double_quotes: true
always_declare_return_types: true
avoid_dynamic_calls: true
avoid_empty_else: true
avoid_relative_lib_imports: true
avoid_shadowing_type_parameters: true
avoid_slow_async_io: true
avoid_types_as_parameter_names: true
await_only_futures: true
camel_case_extensions: true
camel_case_types: true
cancel_subscriptions: true
curly_braces_in_flow_control_structures: true
directives_ordering: true
empty_catches: true
hash_and_equals: true
iterable_contains_unrelated_type: true
list_remove_unrelated_type: true
no_adjacent_strings_in_list: true
no_duplicate_case_values: true
package_api_docs: true
package_prefixed_library_names: true
prefer_generic_function_type_aliases: true
prefer_is_empty: true
prefer_is_not_empty: true
prefer_iterable_whereType: true
prefer_typing_uninitialized_variables: true
sort_child_properties_last: true
test_types_in_equals: true
throw_in_finally: true
unawaited_futures: true
unnecessary_import: true
unnecessary_null_aware_assignments: true
unnecessary_statements: true
unnecessary_type_check: true
unrelated_type_equality_checks: true
unsafe_html: true
use_full_hex_values_for_flutter_colors: true
valid_regexps: true

13
packages/simplex_app/android/.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks

View File

@@ -0,0 +1,68 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 30
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "chat.simplex.app"
minSdkVersion 16
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chat.simplex.app">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,41 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chat.simplex.app">
<application
android:label="simplex_chat"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

View File

@@ -0,0 +1,6 @@
package chat.simplex.app
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chat.simplex.app">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,29 @@
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip

View File

@@ -0,0 +1,11 @@
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"

View File

@@ -0,0 +1 @@
{"packages/cupertino_icons/assets/CupertinoIcons.ttf":["packages/cupertino_icons/assets/CupertinoIcons.ttf"]}

View File

@@ -0,0 +1 @@
[{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]},{"family":"packages/cupertino_icons/CupertinoIcons","fonts":[{"asset":"packages/cupertino_icons/assets/CupertinoIcons.ttf"}]}]

Binary file not shown.

33
packages/simplex_app/ios/.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>9.0</string>
</dict>
</plist>

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@@ -0,0 +1,471 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,13 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>simplex_chat</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View File

@@ -0,0 +1,115 @@
import "package:flutter/material.dart";
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Demo",
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: "Flutter Demo Home Page"),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
"You have pushed the button this many times:",
),
Text(
"$_counter",
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: "Increment",
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}

View File

@@ -0,0 +1,167 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
lints:
dependency: transitive
description:
name: lints
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.10"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.2"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
sdks:
dart: ">=2.14.0 <3.0.0"

View File

@@ -0,0 +1,89 @@
name: simplex_app
description: A new Flutter project.
# 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
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1
environment:
sdk: ">=2.14.0 <3.0.0"
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^1.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages

View File

@@ -0,0 +1,30 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import "package:flutter/material.dart";
import "package:flutter_test/flutter_test.dart";
import "package:simplex_app/main.dart";
void main() {
testWidgets("Counter increments smoke test", (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text("0"), findsOneWidget);
expect(find.text("1"), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text("0"), findsNothing);
expect(find.text("1"), findsOneWidget);
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,101 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="simplex_chat">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<title>simplex_chat</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
var serviceWorkerVersion = null;
var scriptLoaded = false;
function loadMainDartJs() {
if (scriptLoaded) {
return;
}
scriptLoaded = true;
var scriptTag = document.createElement('script');
scriptTag.src = 'main.dart.js';
scriptTag.type = 'application/javascript';
document.body.append(scriptTag);
}
if ('serviceWorker' in navigator) {
// Service workers are supported. Use them.
window.addEventListener('load', function () {
// Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl)
.then((reg) => {
function waitForActivation(serviceWorker) {
serviceWorker.addEventListener('statechange', () => {
if (serviceWorker.state == 'activated') {
console.log('Installed new service worker.');
loadMainDartJs();
}
});
}
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
waitForActivation(reg.installing || reg.waiting);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
console.log('New service worker available.');
reg.update();
waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log('Loading app from service worker.');
loadMainDartJs();
}
});
// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
if (!scriptLoaded) {
console.warn(
'Failed to load app from service worker. Falling back to plain <script> tag.',
);
loadMainDartJs();
}
}, 4000);
});
} else {
// Service workers not supported. Just drop the <script> tag.
loadMainDartJs();
}
</script>
</body>
</html>

View File

@@ -0,0 +1,35 @@
{
"name": "simplex_chat",
"short_name": "simplex_chat",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}

View File

@@ -0,0 +1,3 @@
## 1.0.0
- Initial version.

View File

@@ -0,0 +1,39 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
For general information about developing packages, see the Dart guide for
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/developing-packages).
-->
TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.
## Features
TODO: List what your package can do. Maybe include images, gifs, or videos.
## Getting started
TODO: List prerequisites and provide or point to information on how to
start using the package.
## Usage
TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.
```dart
const like = 'sample';
```
## Additional information
TODO: Tell users more about the package: where to find more information, how to
contribute to the package, how to file issues, what response they can expect
from the package authors, and more.

View File

@@ -0,0 +1 @@
void main() {}

View File

@@ -0,0 +1,7 @@
/// Support for doing something awesome.
///
/// More dartdocs go here.
library simplexmq;
export 'src/protocol.dart';
export 'src/transport.dart' show Transport, SMPTransportClient;

View File

@@ -0,0 +1,154 @@
import 'dart:typed_data';
Uint8List encodeAscii(String s) => Uint8List.fromList(s.codeUnits);
String decodeAscii(Uint8List b) => String.fromCharCodes(b);
Uint8List concat(Uint8List b1, Uint8List b2) {
final a = Uint8List(b1.length + b2.length);
a.setAll(0, b1);
a.setAll(b1.length, b2);
return a;
}
T fold<T, E>(List<E> xs, T Function(T, E) combine, T initial) {
T res = initial;
for (final x in xs) {
res = combine(res, x);
}
return res;
}
Uint8List concatN(List<Uint8List> bs) {
final aLen = fold(bs, (int size, Uint8List b) => size + b.length, 0);
final a = Uint8List(aLen);
fold(bs, (int offset, Uint8List b) {
a.setAll(offset, b);
return offset + b.length;
}, 0);
return a;
}
final charSpace = ' '.codeUnitAt(0);
final charEqual = '='.codeUnitAt(0);
final empty = Uint8List(0);
Uint8List unwords(Uint8List b1, Uint8List b2) {
final a = Uint8List(b1.length + b2.length + 1);
a.setAll(0, b1);
a[b1.length] = charSpace;
a.setAll(b1.length + 1, b2);
return a;
}
Uint8List unwordsN(List<Uint8List> bs) {
int i = bs.length;
int size = bs.length - 1;
while (i > 0) {
size += bs[--i].length;
}
final a = Uint8List(size);
int offset = 0;
for (i = 0; i < bs.length - 1; i++) {
final b = bs[i];
a.setAll(offset, b);
offset += b.length;
a[offset++] = charSpace;
}
a.setAll(offset, bs[i]);
return a;
}
final _base64chars = Uint8List.fromList(
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
.codeUnits);
List<int?> __base64lookup() {
final a = List<int?>.filled(256, null);
for (int i = 0; i < _base64chars.length; i++) {
a[_base64chars[i]] = i;
}
return a;
}
final _base64lookup = __base64lookup();
Uint8List encode64(Uint8List a) {
final len = a.length;
final b64len = (len / 3).ceil() * 4;
final b64 = Uint8List(b64len);
int j = 0;
for (int i = 0; i < len; i += 3) {
final e1 = i + 1 < len ? a[i + 1] : 0;
final e2 = i + 2 < len ? a[i + 2] : 0;
b64[j++] = _base64chars[a[i] >> 2];
b64[j++] = _base64chars[((a[i] & 3) << 4) | (e1 >> 4)];
b64[j++] = _base64chars[((e1 & 15) << 2) | (e2 >> 6)];
b64[j++] = _base64chars[e2 & 63];
}
if (len % 3 != 0) b64[b64len - 1] = charEqual;
if (len % 3 == 1) b64[b64len - 2] = charEqual;
return b64;
}
Uint8List? decode64(Uint8List b64) {
int len = b64.length;
if (len % 4 != 0) return null;
int bLen = (len * 3) >> 2;
if (b64[len - 1] == charEqual) {
len--;
bLen--;
if (b64[len - 1] == charEqual) {
len--;
bLen--;
}
}
final bytes = Uint8List(bLen);
int i = 0;
int pos = 0;
while (i < len) {
final enc1 = _base64lookup[b64[i++]];
final enc2 = i < len ? _base64lookup[b64[i++]] : 0;
final enc3 = i < len ? _base64lookup[b64[i++]] : 0;
final enc4 = i < len ? _base64lookup[b64[i++]] : 0;
if (enc1 == null || enc2 == null || enc3 == null || enc4 == null) {
return null;
}
bytes[pos++] = (enc1 << 2) | (enc2 >> 4);
int p = pos++;
if (p < bLen) bytes[p] = ((enc2 & 15) << 4) | (enc3 >> 2);
p = pos++;
if (p < bLen) bytes[p] = ((enc3 & 3) << 6) | (enc4 & 63);
}
return bytes;
}
Uint8List encodeInt32(int n) {
final data = Uint8List(4);
ByteData.sublistView(data).setInt32(0, n);
return data;
}
Uint8List encodeInt16(int n) {
final data = Uint8List(2);
ByteData.sublistView(data).setInt16(0, n);
return data;
}
extension EqualUint8List on Uint8List {
bool equal(Uint8List b) {
if (length != b.length) return false;
for (int i = 0; i < length; i++) {
if (this[i] != b[i]) return false;
}
return true;
}
}

View File

@@ -0,0 +1,101 @@
import 'dart:math' show Random;
import 'dart:typed_data';
import 'package:pointycastle/api.dart';
import 'package:pointycastle/asymmetric/api.dart';
import 'package:pointycastle/asymmetric/oaep.dart';
import 'package:pointycastle/asymmetric/rsa.dart';
import 'package:pointycastle/block/aes_fast.dart';
import 'package:pointycastle/block/modes/gcm.dart';
import 'package:pointycastle/digests/sha256.dart';
import 'package:pointycastle/key_generators/api.dart';
import 'package:pointycastle/key_generators/rsa_key_generator.dart';
import 'package:pointycastle/random/fortuna_random.dart';
import 'package:pointycastle/signers/pss_signer.dart';
import 'buffer.dart';
class AESKey {
final Uint8List _key;
AESKey._make(this._key);
static AESKey random([bool secure = false]) =>
AESKey._make((secure ? secureRandomBytes : pseudoRandomBytes)(32));
static AESKey decode(Uint8List rawKey) => AESKey._make(rawKey);
Uint8List get bytes => _key;
}
Uint8List randomIV() => pseudoRandomBytes(16);
Uint8List secureRandomBytes(int len) => _randomBytes(len, Random.secure());
final sessionSeed = Random.secure();
Uint8List pseudoRandomBytes(int len) => _randomBytes(len, sessionSeed);
Uint8List _randomBytes(int len, Random seedSource) {
final bytes = Uint8List(len);
for (int i = 0; i < len; i++) {
bytes[i] = seedSource.nextInt(256);
}
return bytes;
}
final paddingByte = '#'.codeUnitAt(0);
Uint8List encryptAES(AESKey key, Uint8List iv, int padTo, Uint8List data) {
if (data.length >= padTo) throw ArgumentError('large message');
final padded = Uint8List(padTo);
padded.setAll(0, data);
padded.fillRange(data.length, padTo, paddingByte);
return _makeGCMCipher(key, iv, true).process(padded);
}
Uint8List decryptAES(AESKey key, Uint8List iv, Uint8List encryptedAndTag) =>
_makeGCMCipher(key, iv, false).process(encryptedAndTag);
GCMBlockCipher _makeGCMCipher(AESKey key, Uint8List iv, bool encrypt) =>
GCMBlockCipher(AESFastEngine())
..init(encrypt, AEADParameters(KeyParameter(key._key), 128, iv, empty));
FortunaRandom _secureFortunaRandom() =>
FortunaRandom()..seed(KeyParameter(secureRandomBytes(32)));
AsymmetricKeyPair<RSAPublicKey, RSAPrivateKey> generateRSAkeyPair(
[int bitLength = 2048]) {
final keyGen = RSAKeyGenerator()
..init(ParametersWithRandom(
RSAKeyGeneratorParameters(BigInt.parse('65537'), bitLength, 64),
_secureFortunaRandom()));
final pair = keyGen.generateKeyPair();
return AsymmetricKeyPair<RSAPublicKey, RSAPrivateKey>(
pair.publicKey as RSAPublicKey, pair.privateKey as RSAPrivateKey);
}
Uint8List encryptOAEP(RSAPublicKey key, Uint8List data) =>
_oaep(true, PublicKeyParameter<RSAPublicKey>(key)).process(data);
Uint8List decryptOAEP(RSAPrivateKey key, Uint8List data) =>
_oaep(false, PrivateKeyParameter<RSAPrivateKey>(key)).process(data);
OAEPEncoding _oaep(bool encrypt, AsymmetricKeyParameter keyParam) =>
OAEPEncoding.withSHA256(RSAEngine())..init(encrypt, keyParam);
Uint8List signPSS(RSAPrivateKey privateKey, Uint8List data) =>
_pss(true, PrivateKeyParameter<RSAPrivateKey>(privateKey))
.generateSignature(data)
.bytes;
bool verifyPSS(RSAPublicKey publicKey, Uint8List data, Uint8List sig) {
try {
return _pss(false, PublicKeyParameter<RSAPublicKey>(publicKey))
.verifySignature(data, PSSSignature(sig));
} on ArgumentError {
return false;
}
}
PSSSigner _pss(bool sign, AsymmetricKeyParameter keyParam) => PSSSigner(
RSAEngine(), SHA256Digest(), SHA256Digest())
..init(sign,
ParametersWithSaltConfiguration(keyParam, _secureFortunaRandom(), 32));

View File

@@ -0,0 +1,160 @@
import 'dart:typed_data';
import 'buffer.dart';
typedef BinaryTags<T> = Map<T, Uint8List>;
int cc(String c) => c.codeUnitAt(0);
final char0 = cc('0');
final char9 = cc('9');
final charLowerA = cc('a');
final charLowerZ = cc('z');
final charUpperA = cc('A');
final charUpperZ = cc('Z');
final charPlus = cc('+');
final charSlash = cc('/');
final charDot = cc('.');
class Parser {
final Uint8List _s;
final List<int> _positions = [];
int _pos = 0;
bool _fail = false;
Parser(this._s);
bool get fail => _fail;
bool get end => _pos >= _s.length;
// only calls `parse` if the parser did not previously fail
T? _run<T>(T? Function() parse) {
if (_fail || _pos >= _s.length) {
_fail = true;
return null;
}
final res = parse();
if (res == null) _fail = true;
return res;
}
T? tryParse<T>(T? Function(Parser p) parse) {
if (_fail || _pos >= _s.length) {
_fail = true;
return null;
}
_positions.add(_pos);
final res = parse(this);
final prevPos = _positions.removeLast();
if (res == null) {
_pos = prevPos;
_fail = false;
}
}
// takes a required number of bytes
Uint8List? take(int len) => _run(() {
final end = _pos + len;
if (end > _s.length) return null;
final res = _s.sublist(_pos, end);
_pos = end;
return res;
});
// takes chars (> 0) while condition is true; function isAlphaNum or isDigit can be used
Uint8List? takeWhile1(bool Function(int) f) => _run(() {
final pos = _pos;
while (f(_s[_pos])) {
_pos++;
}
return _pos > pos ? _s.sublist(pos, _pos) : null;
});
// takes the non-empty word until the first space or until the end of the string
Uint8List? word() => _run(() {
int pos = _s.indexOf(charSpace, _pos);
if (pos == -1) pos = _s.length;
if (pos <= _pos) return null;
final res = _s.sublist(_pos, pos);
_pos = pos;
return res;
});
bool? str(Uint8List s) => _run(() {
for (int i = 0, j = _pos; i < s.length; i++, j++) {
if (s[i] != _s[j]) return null;
}
_pos += s.length;
return true;
});
// takes the passed char
bool? char(int c) => _run(() {
if (_s[_pos] == c) {
_pos++;
return true;
}
});
// takes space
bool? space() => _run(() {
if (_s[_pos] == charSpace) {
_pos++;
return true;
}
});
int? decimal() => _run(() {
final s = takeWhile1(isDigit);
if (s == null) return null;
int n = 0;
for (int i = 0; i < s.length; i++) {
n *= 10;
n += s[i] - char0;
}
return n;
});
DateTime? datetime() => _run(() {
final s = word();
if (s != null) return DateTime.tryParse(decodeAscii(s));
});
// takes base-64 encoded string and returns decoded binary
Uint8List? base64() => _run(() {
bool tryCharEqual() {
final ok = _pos < _s.length && _s[_pos] == charEqual;
if (ok) _pos++;
return ok;
}
final pos = _pos;
int c;
do {
c = _s[_pos];
} while ((isAlphaNum(c) || c == charPlus || c == charSlash) &&
++_pos < _s.length);
if (tryCharEqual()) tryCharEqual();
return _pos > pos ? decode64(_s.sublist(pos, _pos)) : null;
});
// takes one of the binary tags and returns its key
T? someStr<T>(BinaryTags<T> ts) => _run(() {
outer:
for (final t in ts.entries) {
final s = t.value;
for (int i = 0, j = _pos; i < s.length; i++, j++) {
if (s[i] != _s[j]) continue outer;
}
_pos += s.length;
return t.key;
}
return null;
});
}
bool isDigit(int c) => c >= char0 && c <= char9;
bool isAlphaNum(int c) =>
(c >= char0 && c <= char9) ||
(c >= charLowerA && c <= charLowerZ) ||
(c >= charUpperA && c <= charUpperZ);

View File

@@ -0,0 +1,268 @@
import 'dart:typed_data';
import 'buffer.dart';
import 'parser.dart';
abstract class SMPCommand {
Uint8List serialize();
}
abstract class ClientCommand extends SMPCommand {}
abstract class BrokerCommand extends SMPCommand {}
final rsaPrefix = encodeAscii('rsa:');
Uint8List serializePubKey(Uint8List rcvPubKey) =>
concat(rsaPrefix, encode64(rcvPubKey));
final Uint8List cNEW = encodeAscii('NEW');
final Uint8List cSUB = encodeAscii('SUB');
final Uint8List cKEY = encodeAscii('KEY');
final Uint8List cACK = encodeAscii('ACK');
final Uint8List cOFF = encodeAscii('OFF');
final Uint8List cDEL = encodeAscii('DEL');
final Uint8List cSEND = encodeAscii('SEND');
final Uint8List cPING = encodeAscii('PING');
final Uint8List cIDS = encodeAscii('IDS');
final Uint8List cMSG = encodeAscii('MSG');
final Uint8List cEND = encodeAscii('END');
final Uint8List cOK = encodeAscii('OK');
final Uint8List cERR = encodeAscii('ERR');
final Uint8List cPONG = encodeAscii('PONG');
enum SMPCmdTag {
NEW,
SUB,
KEY,
ACK,
OFF,
DEL,
SEND,
PING,
IDS,
MSG,
END,
OK,
ERR,
PONG,
}
final BinaryTags<SMPCmdTag> smpCmdTags = {
SMPCmdTag.NEW: cNEW,
SMPCmdTag.SUB: cSUB,
SMPCmdTag.KEY: cKEY,
SMPCmdTag.ACK: cACK,
SMPCmdTag.OFF: cOFF,
SMPCmdTag.DEL: cDEL,
SMPCmdTag.SEND: cSEND,
SMPCmdTag.PING: cPING,
SMPCmdTag.IDS: cIDS,
SMPCmdTag.MSG: cMSG,
SMPCmdTag.END: cEND,
SMPCmdTag.OK: cOK,
SMPCmdTag.ERR: cERR,
SMPCmdTag.PONG: cPONG,
};
class NEW extends ClientCommand {
final Uint8List rcvPubKey;
NEW(this.rcvPubKey);
@override
Uint8List serialize() => unwords(cNEW, serializePubKey(rcvPubKey));
}
class SUB extends ClientCommand {
@override
Uint8List serialize() => cSUB;
}
class KEY extends ClientCommand {
final Uint8List sndPubKey;
KEY(this.sndPubKey);
@override
Uint8List serialize() => unwords(cKEY, serializePubKey(sndPubKey));
}
class ACK extends ClientCommand {
@override
Uint8List serialize() => cACK;
}
class OFF extends ClientCommand {
@override
Uint8List serialize() => cOFF;
}
class DEL extends ClientCommand {
@override
Uint8List serialize() => cDEL;
}
List<Uint8List> serializeMsg(Uint8List msg) =>
[encodeAscii(msg.length.toString()), msg, empty];
class SEND extends ClientCommand {
final Uint8List msgBody;
SEND(this.msgBody);
@override
Uint8List serialize() => unwordsN([cSEND, ...serializeMsg(msgBody)]);
}
class PING extends ClientCommand {
@override
Uint8List serialize() => cPING;
}
class IDS extends BrokerCommand {
final Uint8List rcvId;
final Uint8List sndId;
IDS(this.rcvId, this.sndId) : super();
@override
Uint8List serialize() => unwordsN([cIDS, encode64(rcvId), encode64(sndId)]);
}
class MSG extends BrokerCommand {
final Uint8List msgId;
final DateTime ts;
final Uint8List msgBody;
MSG(this.msgId, this.ts, this.msgBody);
@override
Uint8List serialize() => unwordsN([
cMSG,
encode64(msgId),
encodeAscii(ts.toIso8601String()),
...serializeMsg(msgBody)
]);
}
class END extends BrokerCommand {
@override
Uint8List serialize() => cEND;
}
class OK extends BrokerCommand {
@override
Uint8List serialize() => cOK;
}
enum ErrorType { BLOCK, CMD, AUTH, QUOTA, NO_MSG, INTERNAL }
final BinaryTags<ErrorType> errorTags = {
ErrorType.BLOCK: encodeAscii('BLOCK'),
ErrorType.CMD: encodeAscii('CMD'),
ErrorType.AUTH: encodeAscii('AUTH'),
ErrorType.QUOTA: encodeAscii('QUOTA'),
ErrorType.NO_MSG: encodeAscii('NO_MSG'),
ErrorType.INTERNAL: encodeAscii('INTERNAL'),
};
enum CmdErrorType { PROHIBITED, KEY_SIZE, SYNTAX, NO_AUTH, HAS_AUTH, NO_QUEUE }
final BinaryTags<CmdErrorType> cmdErrorTags = {
CmdErrorType.PROHIBITED: encodeAscii('PROHIBITED'),
CmdErrorType.KEY_SIZE: encodeAscii('KEY_SIZE'),
CmdErrorType.SYNTAX: encodeAscii('SYNTAX'),
CmdErrorType.NO_AUTH: encodeAscii('NO_AUTH'),
CmdErrorType.HAS_AUTH: encodeAscii('HAS_AUTH'),
CmdErrorType.NO_QUEUE: encodeAscii('NO_QUEUE'),
};
class ERR extends BrokerCommand {
final ErrorType err;
final CmdErrorType? cmdErr;
ERR(this.err)
: cmdErr = err == ErrorType.CMD
? throw ArgumentError('CMD error should be created with ERR.CMD')
: null;
ERR.cmd(CmdErrorType this.cmdErr) : err = ErrorType.CMD;
@override
Uint8List serialize() {
final _err = errorTags[err]!;
return cmdErr == null
? unwords(cERR, _err)
: unwordsN([cERR, _err, cmdErrorTags[cmdErr!]!]);
}
}
class PONG extends BrokerCommand {
@override
Uint8List serialize() => cPONG;
}
final Map<SMPCmdTag, SMPCommand? Function(Parser p)> smpCmdParsers = {
SMPCmdTag.NEW: (p) {
p.space();
final key = pubKeyP(p);
if (key != null) return NEW(key);
},
SMPCmdTag.SUB: (_) => SUB(),
SMPCmdTag.KEY: (p) {
p.space();
final key = pubKeyP(p);
if (key != null) return KEY(key);
},
SMPCmdTag.ACK: (_) => ACK(),
SMPCmdTag.OFF: (_) => OFF(),
SMPCmdTag.DEL: (_) => DEL(),
SMPCmdTag.SEND: (p) {
p.space();
final msg = messageP(p);
if (msg != null) return SEND(msg);
},
SMPCmdTag.PING: (_) => PING(),
SMPCmdTag.IDS: (p) {
p.space();
final rId = p.base64();
p.space();
final sId = p.base64();
if (rId != null && sId != null) return IDS(rId, sId);
},
SMPCmdTag.MSG: (p) {
p.space();
final msgId = p.base64();
p.space();
final ts = p.datetime();
p.space();
final msg = messageP(p);
if (msgId != null && ts != null && msg != null) return MSG(msgId, ts, msg);
},
SMPCmdTag.END: (_) => END(),
SMPCmdTag.OK: (_) => OK(),
SMPCmdTag.ERR: (p) {
p.space();
final err = p.someStr(errorTags);
if (err == ErrorType.CMD) {
p.space();
final cmdErr = p.someStr(cmdErrorTags);
if (cmdErr != null) return ERR.cmd(cmdErr);
} else if (err != null) {
return ERR(err);
}
},
SMPCmdTag.PONG: (_) => PONG(),
};
SMPCommand? smpCommandP(Parser p) {
final cmd = p.someStr(smpCmdTags);
return p.fail ? null : smpCmdParsers[cmd]!(p);
}
SMPCommand? parseSMPCommand(Uint8List s) {
final p = Parser(s);
final cmd = smpCommandP(p);
if (cmd != null && p.end) return cmd;
}
Uint8List? pubKeyP(Parser p) {
p.str(rsaPrefix);
return p.base64();
}
Uint8List? messageP(Parser p) {
final len = p.decimal();
p.space();
Uint8List? msg;
if (len != null) msg = p.take(len);
p.space();
return p.fail ? null : msg;
}

View File

@@ -0,0 +1,109 @@
import 'dart:typed_data';
import 'package:asn1lib/asn1lib.dart';
import 'package:pointycastle/asymmetric/api.dart'
show RSAPublicKey, RSAPrivateKey;
const _rsaOid = '1.2.840.113549.1.1.1';
final _asnRsaOid = ASN1ObjectIdentifier.fromComponentString(_rsaOid);
final _asn1Null = ASN1Null();
ASN1Sequence _asn1Sequence(List<ASN1Object> elements) {
final seq = ASN1Sequence()..elements = elements;
return seq;
}
void _assertRsaAlgorithm(ASN1Sequence seq) {
if (seq.elements.isEmpty ||
seq.elements[0] is! ASN1ObjectIdentifier ||
(seq.elements[0] as ASN1ObjectIdentifier).identifier != _rsaOid) {
throw Exception('Invalid key algorithm identifier');
}
}
/// Decodes binary PKCS1 to [RSAPublicKey]
RSAPublicKey decodeRsaPubKeyPKCS1(Uint8List bytes) {
final els = ASN1Sequence.fromBytes(bytes).elements;
if (els.length != 2 || els[0] is! ASN1Integer || els[1] is! ASN1Integer) {
throw Exception('Invalid PKCS1 encoding');
}
return RSAPublicKey(
(els[0] as ASN1Integer).valueAsBigInteger!,
(els[1] as ASN1Integer).valueAsBigInteger!,
);
}
/// Decodes binary SPKI to [RSAPublicKey]
RSAPublicKey decodeRsaPubKey(Uint8List bytes) {
final els = ASN1Sequence.fromBytes(bytes).elements;
if (els.length != 2 || els[1] is! ASN1BitString || els[0] is! ASN1Sequence) {
throw Exception('Invalid SPKI structure');
}
_assertRsaAlgorithm(els[0] as ASN1Sequence);
return decodeRsaPubKeyPKCS1(els[1].valueBytes().sublist(1));
}
/// Encodes [key] as binary PKCS1
Uint8List encodeRsaPubKeyPKCS1(RSAPublicKey key) =>
_asn1Sequence([ASN1Integer(key.modulus!), ASN1Integer(key.publicExponent!)])
.encodedBytes;
/// Encodes [key] as binary SPKI
Uint8List encodeRsaPubKey(RSAPublicKey key) => _asn1Sequence([
_asn1Sequence([_asnRsaOid, _asn1Null]),
ASN1BitString(encodeRsaPubKeyPKCS1(key))
]).encodedBytes;
/// Decodes binary PKCS1 to [RSAPrivateKey]
RSAPrivateKey decodeRsaPrivKeyPKCS1(Uint8List bytes) {
final els = ASN1Sequence.fromBytes(bytes).elements;
if (els.length != 9 || els.any((el) => el is! ASN1Integer)) {
throw Exception('Invalid PKCS1 encoding');
}
return RSAPrivateKey(
(els[1] as ASN1Integer).valueAsBigInteger!,
(els[3] as ASN1Integer).valueAsBigInteger!,
(els[4] as ASN1Integer).valueAsBigInteger!,
(els[5] as ASN1Integer).valueAsBigInteger!);
}
/// Decodes binary PKCS8 to [RSAPrivateKey]
RSAPrivateKey decodeRsaPrivKey(Uint8List bytes) {
final els = ASN1Sequence.fromBytes(bytes).elements;
if (els.length != 3 ||
els[1] is! ASN1Sequence ||
els[2] is! ASN1OctetString) {
throw Exception('Invalid PKCS8 structure');
}
_assertRsaAlgorithm(els[1] as ASN1Sequence);
return decodeRsaPrivKeyPKCS1(els[2].valueBytes());
}
final _asnZero = ASN1Integer(BigInt.from(0));
/// Encodes [key] as PKCS1 binary
Uint8List encodeRsaPrivKeyPKCS1(RSAPrivateKey key) {
final d = key.privateExponent!;
final p = key.p!;
final q = key.q!;
final dModP = d % (p - BigInt.from(1));
final dModQ = d % (q - BigInt.from(1));
final coefficient = q.modInverse(p);
return _asn1Sequence([
_asnZero,
ASN1Integer(key.modulus!),
ASN1Integer(key.publicExponent!),
ASN1Integer(d),
ASN1Integer(p),
ASN1Integer(q),
ASN1Integer(dModP),
ASN1Integer(dModQ),
ASN1Integer(coefficient),
]).encodedBytes;
}
/// Encodes [key] as PKCS8 binary
Uint8List encodeRsaPrivKey(RSAPrivateKey key) => _asn1Sequence([
_asnZero,
_asn1Sequence([_asnRsaOid, _asn1Null]),
ASN1OctetString(encodeRsaPrivKeyPKCS1(key))
]).encodedBytes;

View File

@@ -0,0 +1,310 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:pointycastle/asymmetric/api.dart';
import 'package:pointycastle/digests/sha256.dart';
import 'buffer.dart';
import 'crypto.dart';
import 'parser.dart';
import 'protocol.dart';
import 'rsa_keys.dart';
abstract class Transport {
Future<Uint8List> read(int n);
Future<void> write(Uint8List data);
Future<void> close();
}
class SMPServer {
final String host;
final int? port;
final Uint8List? keyHash;
SMPServer(this.host, [this.port, this.keyHash]);
}
class SessionKey {
final AESKey aesKey;
final Uint8List baseIV;
int _counter = 0;
SessionKey._(this.aesKey, this.baseIV);
static SessionKey create() => SessionKey._(AESKey.random(), randomIV());
Uint8List serialize() => concat(aesKey.bytes, baseIV);
}
class ServerHeader {
final int blockSize;
final int keySize;
ServerHeader(this.blockSize, this.keySize);
}
class ServerHandshake {
final RSAPublicKey publicKey;
final int blockSize;
ServerHandshake(this.publicKey, this.blockSize);
}
typedef SMPVersion = List<int>;
SMPVersion _currentSMPVersion = const [0, 4, 1, 0];
const int _serverHeaderSize = 8;
const int _binaryRsaTransport = 0;
const int _transportBlockSize = 4096;
const int _maxTransportBlockSize = 65536;
class _Request {
final Uint8List queueId;
final Completer<BrokerResponse> completer = Completer();
_Request(this.queueId);
}
class Either<L, R> {
final L? left;
final R? right;
Either.left(L this.left) : right = null;
Either.right(R this.right) : left = null;
}
enum ClientErrorType { SMPServerError, SMPResponseError, SMPUnexpectedResponse }
class BrokerResponse {
final BrokerCommand? command;
final ClientErrorType? errorType;
final ERR? error;
BrokerResponse(BrokerCommand this.command)
: errorType = null,
error = null;
BrokerResponse.error(ClientErrorType this.errorType, this.error)
: command = null;
}
class ClientTransmission {
final String corrId;
final Uint8List queueId;
final ClientCommand command;
ClientTransmission(this.corrId, this.queueId, this.command);
Uint8List serialize() =>
unwordsN([encodeAscii(corrId), encode64(queueId), command.serialize()]);
}
class BrokerTransmission {
final String corrId;
final Uint8List queueId;
final BrokerCommand? command;
final ERR? error;
BrokerTransmission(this.corrId, this.queueId, BrokerCommand this.command)
: error = null;
BrokerTransmission.error(this.corrId, this.queueId, ERR this.error)
: command = null;
}
final badBlock = BrokerTransmission.error('', empty, ERR(ErrorType.BLOCK));
class SMPTransportClient {
final Transport _conn;
final _sndKey = SessionKey.create();
final _rcvKey = SessionKey.create();
final int blockSize;
int _corrId = 0;
bool _messageStreamCreated = false;
final Map<String, _Request> _sentCommands = {};
SMPTransportClient._(this._conn, this.blockSize);
static Future<SMPTransportClient> connect(Transport conn,
{Uint8List? keyHash, int? blockSize}) {
return _clientHandshake(conn, keyHash, blockSize);
}
Future<void> close() {
return _conn.close();
}
Future<BrokerResponse> sendSMPCommand(
RSAPrivateKey? key, Uint8List queueId, ClientCommand cmd) async {
final corrId = (++_corrId).toString();
final t = ClientTransmission(corrId, queueId, cmd).serialize();
final sig = key == null ? empty : encode64(signPSS(key, t));
final data = unwordsN([sig, t, empty]);
final r = _sentCommands[corrId] = _Request(queueId);
await _writeEncrypted(data);
return r.completer.future;
}
Stream<BrokerTransmission> messageStream() {
if (_messageStreamCreated) {
throw Exception('message stream already created');
}
_messageStreamCreated = true;
return _messageStream();
}
Stream<BrokerTransmission> _messageStream() async* {
try {
while (true) {
final block = await _readEncrypted();
final t = _parseBrokerTransmission(block);
if (t.corrId == '') {
yield t;
} else {
final r = _sentCommands.remove(t.corrId);
if (r == null) {
yield t;
} else {
final cmd = t.command;
r.completer.complete(r.queueId.equal(t.queueId)
? cmd == null
? BrokerResponse.error(
ClientErrorType.SMPResponseError, t.error)
: cmd is ERR
? BrokerResponse.error(
ClientErrorType.SMPServerError, cmd)
: BrokerResponse(cmd)
: BrokerResponse.error(
ClientErrorType.SMPUnexpectedResponse, null));
}
}
}
} catch (e) {
return;
}
}
static BrokerTransmission _parseBrokerTransmission(Uint8List s) {
final p = Parser(s);
p.space();
final cId = p.word();
p.space();
final queueId = p.tryParse((p) => p.base64()) ?? empty;
p.space();
if (p.fail || cId == null) return badBlock;
final corrId = decodeAscii(cId);
final command = smpCommandP(p);
if (command == null) {
return BrokerTransmission.error(
corrId, queueId, ERR.cmd(CmdErrorType.SYNTAX));
}
if (command is! BrokerCommand) {
return BrokerTransmission.error(
corrId, queueId, ERR.cmd(CmdErrorType.PROHIBITED));
}
final qErr = _tQueueError(queueId, command);
if (qErr != null) {
return BrokerTransmission.error(corrId, queueId, ERR.cmd(qErr));
}
return BrokerTransmission(corrId, queueId, command);
}
static CmdErrorType? _tQueueError(Uint8List queueId, BrokerCommand cmd) {
if (cmd is IDS || cmd is PONG) {
if (queueId.isNotEmpty) return CmdErrorType.HAS_AUTH;
} else if (cmd is! ERR && queueId.isEmpty) {
return CmdErrorType.NO_QUEUE;
}
}
static Future<SMPTransportClient> _clientHandshake(
Transport conn, Uint8List? keyHash, int? blkSize) async {
final srv = await _getHeaderAndPublicKey_1_2(conn, keyHash);
final t = SMPTransportClient._(conn, blkSize ?? srv.blockSize);
await t._sendEncryptedKeys_4(srv.publicKey);
_checkVersion(await t._getWelcome_6());
return t;
}
static Future<ServerHandshake> _getHeaderAndPublicKey_1_2(
Transport conn, Uint8List? keyHash) async {
final srvHeader = parseServerHeader(await conn.read(_serverHeaderSize));
final blkSize = srvHeader.blockSize;
if (blkSize < _transportBlockSize || blkSize > _maxTransportBlockSize) {
throw Exception('smp handshake header error: bad block size $blkSize');
}
final rawKey = await conn.read(srvHeader.keySize);
if (keyHash != null) validateKeyHash_2(rawKey, keyHash);
final serverKey = decodeRsaPubKey(rawKey);
return ServerHandshake(serverKey, blkSize);
}
static void validateKeyHash_2(Uint8List rawKey, Uint8List keyHash) {
if (keyHash.equal(SHA256Digest().process(rawKey))) return;
throw Exception('smp handshake error: bad key hash');
}
static ServerHeader parseServerHeader(Uint8List a) {
if (a.length != 8) {
throw Exception('smp handshake error: bad header size ${a.length}');
}
final v = ByteData.sublistView(a);
final blockSize = v.getUint32(0);
final transportMode = v.getUint16(4);
if (transportMode != _binaryRsaTransport) {
throw Exception('smp handshake error: bad transport mode $transportMode');
}
final keySize = v.getUint16(6);
return ServerHeader(blockSize, keySize);
}
Future<void> _sendEncryptedKeys_4(RSAPublicKey serverKey) async {
final header = encryptOAEP(serverKey, _clientHeader());
await _conn.write(header);
}
Uint8List _clientHeader() => concatN([
encodeInt32(blockSize),
encodeInt16(_binaryRsaTransport),
_sndKey.serialize(),
_rcvKey.serialize()
]);
Future<SMPVersion> _getWelcome_6() async =>
_parseSMPVersion(await _readEncrypted());
static SMPVersion _parseSMPVersion(Uint8List block) {
final p = Parser(block);
final SMPVersion version = [0, 0, 0, 0];
void setVer(int i, int? v) {
if (v == null || p.fail) {
throw Exception('smp handshake error: bad version format');
}
version[i] = v;
}
for (var i = 0; i < 3; i++) {
final v = p.decimal();
p.char(charDot);
setVer(i, v);
}
final v = p.decimal();
p.space();
setVer(3, v);
return version;
}
static void _checkVersion(SMPVersion srvVersion) {
final s0 = srvVersion[0];
final c0 = _currentSMPVersion[0];
if (s0 > c0 || (s0 == c0 && srvVersion[1] > _currentSMPVersion[1])) {
throw Exception('smp handshake error: incompatible server version');
}
}
Future<Uint8List> _readEncrypted() async {
final block = await _conn.read(blockSize);
final iv = _nextIV(_rcvKey);
return decryptAES(_rcvKey.aesKey, iv, block);
}
Future<void> _writeEncrypted(Uint8List data) {
final iv = _nextIV(_sndKey);
final block = encryptAES(_sndKey.aesKey, iv, blockSize - 16, data);
return _conn.write(block);
}
static Uint8List _nextIV(SessionKey sk) {
final c = encodeInt32(sk._counter++);
final start = sk.baseIV.sublist(0, 4);
final rest = sk.baseIV.sublist(4);
for (int i = 0; i < 4; i++) {
start[i] ^= c[i];
}
return concat(start, rest);
}
}

View File

@@ -0,0 +1,20 @@
name: simplexmq
description: A starting point for Dart libraries or applications.
version: 0.0.1
publish_to: none
environment:
sdk: '>=2.14.0 <3.0.0'
dependencies:
asn1lib: ^1.0.2
pointycastle:
git:
url: https://github.com/simplex-chat/pc-dart.git
ref: nullsafety
# pointycastle: ^3.3.4
dev_dependencies:
lints: ^1.0.0
test: ^1.16.0

View File

@@ -0,0 +1,66 @@
import 'dart:typed_data';
import 'package:simplexmq/src/buffer.dart';
import 'package:test/test.dart';
final hello123 = Uint8List.fromList([104, 101, 108, 108, 111, 49, 50, 51]);
class Base64Test {
final String description;
final String binary;
final String base64;
Base64Test(this.binary, this.base64) : description = binary;
Base64Test.withDescription(this.description, this.binary, this.base64);
}
void main() {
group('ascii encoding/decoding', () {
test('encodeAscii', () {
expect(encodeAscii('hello123'), hello123);
});
test('decodeAscii', () {
expect(decodeAscii(hello123), 'hello123');
});
});
group('base-64 encoding/decoding', () {
String allBinaryChars() {
final a = Uint8List(256);
for (var i = 0; i < 256; i++) {
a[i] = i;
}
return decodeAscii(a);
}
final base64tests = [
Base64Test('\x12\x34\x56\x78', 'EjRWeA=='),
Base64Test('hello123', 'aGVsbG8xMjM='),
Base64Test('Hello world', 'SGVsbG8gd29ybGQ='),
Base64Test('Hello worlds!', 'SGVsbG8gd29ybGRzIQ=='),
Base64Test('May', 'TWF5'),
Base64Test('Ma', 'TWE='),
Base64Test('M', 'TQ=='),
Base64Test.withDescription(
'all binary chars',
allBinaryChars(),
'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==',
),
];
test('encode64', () {
for (final t in base64tests) {
expect(encode64(encodeAscii(t.binary)), encodeAscii(t.base64));
}
});
test('decode64', () {
for (final t in base64tests) {
expect(decode64(encodeAscii(t.base64)), encodeAscii(t.binary));
}
expect(decode64(encodeAscii('TWE')), null);
expect(decode64(encodeAscii('TWE==')), null);
expect(decode64(encodeAscii('TW!=')), null);
});
});
}

View File

@@ -0,0 +1,29 @@
import 'package:simplexmq/src/buffer.dart';
import 'package:simplexmq/src/crypto.dart';
import 'package:test/test.dart';
void main() {
group('AES-GCM encryption with padding', () {
test('encrypt and decrypt', () {
final key = AESKey.random();
final iv = pseudoRandomBytes(16);
final data = encodeAscii('hello');
final cipherText = encryptAES(key, iv, 32, data);
expect(cipherText.length, 32 + 16);
final decrypted = decryptAES(key, iv, cipherText);
expect(decodeAscii(decrypted),
'hello' + List.filled(32 - 'hello'.length, '#').join());
});
});
group('RSA-OAEP encryption', () {
test('encrypt and decrypt', () {
final keyPair = generateRSAkeyPair();
final data = encodeAscii('hello there');
final cipherText = encryptOAEP(keyPair.publicKey, data);
expect(cipherText.length, 2048 ~/ 8);
final decrypted = decryptOAEP(keyPair.privateKey, cipherText);
expect(decodeAscii(decrypted), 'hello there');
});
});
}

View File

@@ -0,0 +1,105 @@
import 'dart:typed_data';
import 'package:simplexmq/src/buffer.dart';
import 'package:simplexmq/src/protocol.dart';
import 'package:test/test.dart';
void main() {
group('Parsing & serializing SMP commands', () {
group('valid commands', () {
Null Function() parseSerialize(SMPCommand cmd) => () {
final s = cmd.serialize();
expect(parseSMPCommand(s)?.serialize(), s);
expect(parseSMPCommand(concat(s, Uint8List.fromList([charSpace]))),
null);
};
test('NEW', parseSerialize(NEW(encodeAscii('rsa:1234'))));
test('SUB', parseSerialize(SUB()));
test('KEY', parseSerialize(KEY(encodeAscii('rsa:1234'))));
test('ACK', parseSerialize(ACK()));
test('OFF', parseSerialize(OFF()));
test('DEL', parseSerialize(DEL()));
test('SEND', parseSerialize(SEND(encodeAscii('hello'))));
test('PING', parseSerialize(PING()));
test('IDS', parseSerialize(IDS(encodeAscii('abc'), encodeAscii('def'))));
test(
'MSG',
parseSerialize(MSG(encodeAscii('fgh'), DateTime.now().toUtc(),
encodeAscii('hello'))));
test('END', parseSerialize(END()));
test('OK', parseSerialize(OK()));
test('ERR', parseSerialize(ERR(ErrorType.AUTH)));
test('ERR CMD', parseSerialize(ERR.cmd(CmdErrorType.SYNTAX)));
test('PONG', parseSerialize(PONG()));
});
group('invalid commands', () {
void Function() parseFailure(String s) =>
() => expect(parseSMPCommand(encodeAscii(s)), null);
void Function() parseSuccess(String s) =>
() => expect(parseSMPCommand(encodeAscii(s)) is SMPCommand, true);
group('NEW', () {
test('ok', parseSuccess('NEW rsa:abcd'));
test('no key', parseFailure('NEW'));
test('invalid base64', parseFailure('NEW rsa:abc'));
test('double space', parseFailure('NEW rsa:abcd'));
});
group('KEY', () {
test('ok', parseSuccess('KEY rsa:abcd'));
test('no key', parseFailure('KEY'));
test('invalid base64', parseFailure('KEY rsa:abc'));
test('double space', parseFailure('KEY rsa:abcd'));
});
group('SEND', () {
test('ok', parseSuccess('SEND 5 hello '));
test('no size', parseFailure('SEND hello '));
test('incorrect size', parseFailure('SEND 6 hello '));
test('no trailing space', parseFailure('SEND 5 hello'));
test('double space 1', parseFailure('SEND 5 hello '));
test('double space 2', parseFailure('SEND 5 hello '));
});
group('IDS', () {
test('ok', parseSuccess('IDS abcd efgh'));
test('no IDs', parseFailure('IDS'));
test('only one ID', parseFailure('IDS abcd'));
test('invalid base64 1', parseFailure('IDS abc efgh'));
test('invalid base64 2', parseFailure('IDS abcd efg'));
test('double space 1', parseFailure('IDS abcd efgh'));
test('double space 2', parseFailure('IDS abcd efgh'));
});
group('MSG', () {
final String ts = '2021-10-03T10:50:59.895Z';
test('ok', parseSuccess('MSG abcd $ts 5 hello '));
test('invalid base64', parseFailure('MSG abc $ts 5 hello '));
test('invalid timestamp 1',
parseFailure('MSG abc 2021-10-03T10:50:59.895 5 hello '));
test('invalid timestamp 2',
parseFailure('MSG abc 2021-14-03T10:50:59.895Z 5 hello '));
test('no size', parseFailure('MSG abcd $ts hello '));
test('incorrect size', parseFailure('MSG abcd $ts 6 hello '));
test('no trailing space', parseFailure('MSG abcd $ts 5 hello'));
test('double space 1', parseFailure('MSG abcd $ts 5 hello '));
test('double space 2', parseFailure('MSG abcd $ts 5 hello '));
test('double space 3', parseFailure('MSG abcd $ts 5 hello '));
test('double space 4', parseFailure('MSG abcd $ts 5 hello '));
});
group('ERR', () {
test('ok 1', parseSuccess('ERR AUTH'));
test('ok 2', parseSuccess('ERR CMD SYNTAX'));
test('unknown error', parseFailure('ERR HELLO'));
test('unknown CMD error', parseFailure('ERR CMD HELLO'));
test('bad sub-error', parseFailure('ERR AUTH SYNTAX'));
test('double space 1', parseFailure('ERR AUTH'));
test('double space 2', parseFailure('ERR CMD SYNTAX'));
test('double space 3', parseFailure('ERR CMD SYNTAX'));
});
});
});
}

Some files were not shown because too many files have changed in this diff Show More