Compare commits
16 Commits
dev
...
message-se
Author | SHA1 | Date | |
---|---|---|---|
4ecf7c546b | |||
afa0c5b5a4 | |||
|
00e7d850b0 | ||
|
1a020bbbd8 | ||
|
918265744e | ||
|
96427d78d9 | ||
|
612cccc381 | ||
|
f89c5440fc | ||
|
8cb206a640 | ||
|
9e04185da1 | ||
|
fd9c469fc1 | ||
|
64089f2b3e | ||
|
91a739a0cd | ||
|
62d48dc084 | ||
|
cef7a27e88 | ||
|
664dd4bb38 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,7 +9,6 @@
|
|||||||
.history
|
.history
|
||||||
.svn/
|
.svn/
|
||||||
migrate_working_dir/
|
migrate_working_dir/
|
||||||
protocol_prototype/venv
|
|
||||||
|
|
||||||
# IntelliJ related
|
# IntelliJ related
|
||||||
*.iml
|
*.iml
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.icing.dialer
|
||||||
|
|
||||||
|
import java.security.KeyStore
|
||||||
|
|
||||||
|
object KeyDeleterHelper {
|
||||||
|
|
||||||
|
private const val ANDROID_KEYSTORE = "AndroidKeyStore"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the key pair associated with the given alias from the Android Keystore.
|
||||||
|
*
|
||||||
|
* @param alias The alias of the key pair to delete.
|
||||||
|
* @throws Exception if deletion fails.
|
||||||
|
*/
|
||||||
|
fun deleteKeyPair(alias: String) {
|
||||||
|
try {
|
||||||
|
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
|
||||||
|
|
||||||
|
if (!keyStore.containsAlias(alias)) {
|
||||||
|
throw Exception("No key found with alias \"$alias\" to delete.")
|
||||||
|
}
|
||||||
|
|
||||||
|
keyStore.deleteEntry(alias)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw Exception("Failed to delete key pair: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package com.icing.dialer
|
||||||
|
|
||||||
|
import android.security.keystore.KeyGenParameterSpec
|
||||||
|
import android.security.keystore.KeyProperties
|
||||||
|
import java.security.KeyPairGenerator
|
||||||
|
import java.security.KeyStore
|
||||||
|
|
||||||
|
object KeyGeneratorHelper {
|
||||||
|
|
||||||
|
private const val ANDROID_KEYSTORE = "AndroidKeyStore"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an ECDSA P-256 key pair and stores it in the Android Keystore.
|
||||||
|
*
|
||||||
|
* @param alias Unique identifier for the key pair.
|
||||||
|
* @throws Exception if key generation fails.
|
||||||
|
*/
|
||||||
|
fun generateECKeyPair(alias: String) {
|
||||||
|
try {
|
||||||
|
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
|
||||||
|
|
||||||
|
// Check if the key already exists
|
||||||
|
if (keyStore.containsAlias(alias)) {
|
||||||
|
throw Exception("Key with alias \"$alias\" already exists.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val keyPairGenerator = KeyPairGenerator.getInstance(
|
||||||
|
KeyProperties.KEY_ALGORITHM_EC,
|
||||||
|
ANDROID_KEYSTORE
|
||||||
|
)
|
||||||
|
|
||||||
|
val parameterSpec = KeyGenParameterSpec.Builder(
|
||||||
|
alias,
|
||||||
|
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
|
||||||
|
)
|
||||||
|
.setAlgorithmParameterSpec(java.security.spec.ECGenParameterSpec("secp256r1"))
|
||||||
|
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA512)
|
||||||
|
.setUserAuthenticationRequired(false) // Set to true if you require user authentication
|
||||||
|
.build()
|
||||||
|
|
||||||
|
keyPairGenerator.initialize(parameterSpec)
|
||||||
|
keyPairGenerator.generateKeyPair()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw Exception("Failed to generate EC key pair: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package com.icing.dialer
|
package com.icing.dialer
|
||||||
|
|
||||||
import android.os.Build
|
import java.security.PrivateKey
|
||||||
import android.security.keystore.KeyGenParameterSpec
|
import android.security.keystore.KeyGenParameterSpec
|
||||||
import android.security.keystore.KeyProperties
|
import android.security.keystore.KeyProperties
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
@ -8,21 +8,15 @@ import io.flutter.plugin.common.MethodCall
|
|||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
import java.security.PrivateKey
|
|
||||||
import java.security.Signature
|
import java.security.Signature
|
||||||
import java.security.spec.ECGenParameterSpec
|
|
||||||
|
|
||||||
class KeystoreHelper(private val call: MethodCall, private val result: MethodChannel.Result) {
|
class KeystoreHelper(private val call: MethodCall, private val result: MethodChannel.Result) {
|
||||||
|
|
||||||
private val ANDROID_KEYSTORE = "AndroidKeyStore"
|
private val ANDROID_KEYSTORE = "AndroidKeyStore"
|
||||||
|
|
||||||
fun handleMethodCall() {
|
fun handleMethodCall() {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
|
||||||
result.error("UNSUPPORTED_API", "ED25519 requires Android 11 (API 30) or higher", null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"generateKeyPair" -> generateEDKeyPair()
|
"generateKeyPair" -> generateECKeyPair()
|
||||||
"signData" -> signData()
|
"signData" -> signData()
|
||||||
"getPublicKey" -> getPublicKey()
|
"getPublicKey" -> getPublicKey()
|
||||||
"deleteKeyPair" -> deleteKeyPair()
|
"deleteKeyPair" -> deleteKeyPair()
|
||||||
@ -31,7 +25,7 @@ class KeystoreHelper(private val call: MethodCall, private val result: MethodCha
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateEDKeyPair() {
|
private fun generateECKeyPair() {
|
||||||
val alias = call.argument<String>("alias")
|
val alias = call.argument<String>("alias")
|
||||||
if (alias == null) {
|
if (alias == null) {
|
||||||
result.error("INVALID_ARGUMENT", "Alias is required", null)
|
result.error("INVALID_ARGUMENT", "Alias is required", null)
|
||||||
@ -50,14 +44,16 @@ class KeystoreHelper(private val call: MethodCall, private val result: MethodCha
|
|||||||
KeyProperties.KEY_ALGORITHM_EC,
|
KeyProperties.KEY_ALGORITHM_EC,
|
||||||
ANDROID_KEYSTORE
|
ANDROID_KEYSTORE
|
||||||
)
|
)
|
||||||
|
|
||||||
val parameterSpec = KeyGenParameterSpec.Builder(
|
val parameterSpec = KeyGenParameterSpec.Builder(
|
||||||
alias,
|
alias,
|
||||||
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
|
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
|
||||||
)
|
)
|
||||||
.setAlgorithmParameterSpec(ECGenParameterSpec("ed25519"))
|
.setAlgorithmParameterSpec(java.security.spec.ECGenParameterSpec("secp256r1"))
|
||||||
.setDigests(KeyProperties.DIGEST_SHA256)
|
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA512)
|
||||||
.setUserAuthenticationRequired(false)
|
.setUserAuthenticationRequired(false)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
keyPairGenerator.initialize(parameterSpec)
|
keyPairGenerator.initialize(parameterSpec)
|
||||||
keyPairGenerator.generateKeyPair()
|
keyPairGenerator.generateKeyPair()
|
||||||
|
|
||||||
@ -77,14 +73,17 @@ class KeystoreHelper(private val call: MethodCall, private val result: MethodCha
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
|
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
|
||||||
|
|
||||||
val privateKey = keyStore.getKey(alias, null) as? PrivateKey ?: run {
|
val privateKey = keyStore.getKey(alias, null) as? PrivateKey ?: run {
|
||||||
result.error("KEY_NOT_FOUND", "Private key not found for alias \"$alias\".", null)
|
result.error("KEY_NOT_FOUND", "Private key not found for alias \"$alias\".", null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val signature = Signature.getInstance("Ed25519")
|
|
||||||
|
val signature = Signature.getInstance("SHA256withECDSA")
|
||||||
signature.initSign(privateKey)
|
signature.initSign(privateKey)
|
||||||
signature.update(data.toByteArray())
|
signature.update(data.toByteArray())
|
||||||
val signedBytes = signature.sign()
|
val signedBytes = signature.sign()
|
||||||
|
|
||||||
val signatureBase64 = Base64.encodeToString(signedBytes, Base64.DEFAULT)
|
val signatureBase64 = Base64.encodeToString(signedBytes, Base64.DEFAULT)
|
||||||
result.success(signatureBase64)
|
result.success(signatureBase64)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.icing.dialer
|
||||||
|
|
||||||
|
import java.security.KeyStore
|
||||||
|
import java.security.PublicKey
|
||||||
|
import android.util.Base64
|
||||||
|
|
||||||
|
object PublicKeyHelper {
|
||||||
|
|
||||||
|
private const val ANDROID_KEYSTORE = "AndroidKeyStore"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the public key associated with the given alias.
|
||||||
|
*
|
||||||
|
* @param alias The alias of the key pair.
|
||||||
|
* @return The public key as a Base64-encoded string.
|
||||||
|
* @throws Exception if retrieval fails.
|
||||||
|
*/
|
||||||
|
fun getPublicKey(alias: String): String {
|
||||||
|
try {
|
||||||
|
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
|
||||||
|
|
||||||
|
val certificate = keyStore.getCertificate(alias) ?: throw Exception("Certificate not found for alias \"$alias\".")
|
||||||
|
val publicKey: PublicKey = certificate.publicKey
|
||||||
|
|
||||||
|
return Base64.encodeToString(publicKey.encoded, Base64.DEFAULT)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw Exception("Failed to retrieve public key: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package com.icing.dialer
|
||||||
|
|
||||||
|
import android.security.keystore.KeyProperties
|
||||||
|
import java.security.KeyStore
|
||||||
|
import java.security.Signature
|
||||||
|
import android.util.Base64
|
||||||
|
import java.security.PrivateKey
|
||||||
|
|
||||||
|
object SignerHelper {
|
||||||
|
|
||||||
|
private const val ANDROID_KEYSTORE = "AndroidKeyStore"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs the provided data using the private key associated with the given alias.
|
||||||
|
*
|
||||||
|
* @param alias The alias of the key pair.
|
||||||
|
* @param data The data to sign.
|
||||||
|
* @return The signature as a Base64-encoded string.
|
||||||
|
* @throws Exception if signing fails.
|
||||||
|
*/
|
||||||
|
fun signData(alias: String, data: ByteArray): String {
|
||||||
|
try {
|
||||||
|
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
|
||||||
|
|
||||||
|
val privateKey = keyStore.getKey(alias, null) as? PrivateKey?: throw Exception("Private key not found for alias \"$alias\".")
|
||||||
|
|
||||||
|
val signature = Signature.getInstance("SHA256withECDSA")
|
||||||
|
signature.initSign(privateKey)
|
||||||
|
signature.update(data)
|
||||||
|
val signedBytes = signature.sign()
|
||||||
|
|
||||||
|
return Base64.encodeToString(signedBytes, Base64.DEFAULT)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw Exception("Failed to sign data: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -95,6 +95,7 @@ class MainActivity : FlutterActivity() {
|
|||||||
pendingIncomingCall = null
|
pendingIncomingCall = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
checkAndRequestDefaultDialer()
|
||||||
result.success(true)
|
result.success(true)
|
||||||
}
|
}
|
||||||
"makeGsmCall" -> {
|
"makeGsmCall" -> {
|
||||||
@ -190,33 +191,6 @@ class MainActivity : FlutterActivity() {
|
|||||||
result.error("SPEAKER_FAILED", "No active call or failed to set speaker", null)
|
result.error("SPEAKER_FAILED", "No active call or failed to set speaker", null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"isDefaultDialer" -> {
|
|
||||||
val isDefault = isDefaultDialer()
|
|
||||||
Log.d(TAG, "isDefaultDialer called, returning: $isDefault")
|
|
||||||
result.success(isDefault)
|
|
||||||
}
|
|
||||||
"requestDefaultDialer" -> {
|
|
||||||
checkAndRequestDefaultDialer()
|
|
||||||
result.success(true)
|
|
||||||
}
|
|
||||||
"sendDtmfTone" -> {
|
|
||||||
val digit = call.argument<String>("digit")
|
|
||||||
if (digit != null) {
|
|
||||||
val success = MyInCallService.sendDtmfTone(digit)
|
|
||||||
result.success(success)
|
|
||||||
} else {
|
|
||||||
result.error("INVALID_ARGUMENT", "Digit is null", null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"isDefaultDialer" -> {
|
|
||||||
val isDefault = isDefaultDialer()
|
|
||||||
Log.d(TAG, "isDefaultDialer called, returning: $isDefault")
|
|
||||||
result.success(isDefault)
|
|
||||||
}
|
|
||||||
"requestDefaultDialer" -> {
|
|
||||||
checkAndRequestDefaultDialer()
|
|
||||||
result.success(true)
|
|
||||||
}
|
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -242,13 +216,6 @@ class MainActivity : FlutterActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isDefaultDialer(): Boolean {
|
|
||||||
val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
|
|
||||||
val currentDefault = telecomManager.defaultDialerPackage
|
|
||||||
Log.d(TAG, "Checking default dialer: current=$currentDefault, myPackage=$packageName")
|
|
||||||
return currentDefault == packageName
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkAndRequestDefaultDialer() {
|
private fun checkAndRequestDefaultDialer() {
|
||||||
val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
|
val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
|
||||||
val currentDefault = telecomManager.defaultDialerPackage
|
val currentDefault = telecomManager.defaultDialerPackage
|
||||||
|
@ -52,22 +52,6 @@ class MyInCallService : InCallService() {
|
|||||||
}
|
}
|
||||||
} ?: false
|
} ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendDtmfTone(digit: String): Boolean {
|
|
||||||
return instance?.let { service ->
|
|
||||||
try {
|
|
||||||
currentCall?.let { call ->
|
|
||||||
call.playDtmfTone(digit[0])
|
|
||||||
call.stopDtmfTone()
|
|
||||||
Log.d(TAG, "Sent DTMF tone: $digit")
|
|
||||||
true
|
|
||||||
} ?: false
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Failed to send DTMF tone: $e")
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} ?: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val callCallback = object : Call.Callback() {
|
private val callCallback = object : Call.Callback() {
|
||||||
@ -127,7 +111,7 @@ class MyInCallService : InCallService() {
|
|||||||
}
|
}
|
||||||
call.registerCallback(callCallback)
|
call.registerCallback(callCallback)
|
||||||
if (callAudioState != null) {
|
if (callAudioState != null) {
|
||||||
val audioState = callAudioState
|
val audioState = callAudioState
|
||||||
channel?.invokeMethod("audioStateChanged", mapOf(
|
channel?.invokeMethod("audioStateChanged", mapOf(
|
||||||
"route" to audioState.route,
|
"route" to audioState.route,
|
||||||
"muted" to audioState.isMuted,
|
"muted" to audioState.isMuted,
|
||||||
|
@ -4,7 +4,7 @@ import '../../presentation/features/call/incoming_call_page.dart';
|
|||||||
import '../../presentation/features/home/home_page.dart';
|
import '../../presentation/features/home/home_page.dart';
|
||||||
import '../../presentation/features/settings/settings.dart'; // Updated import
|
import '../../presentation/features/settings/settings.dart'; // Updated import
|
||||||
import '../../presentation/features/contacts/contact_page.dart';
|
import '../../presentation/features/contacts/contact_page.dart';
|
||||||
import '../../presentation/features/composition/composition.dart';
|
import '../../presentation/features/dialer/composition_page.dart';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
class AppRouter {
|
class AppRouter {
|
||||||
|
37
dialer/lib/core/utils/color_utils.dart
Normal file
37
dialer/lib/core/utils/color_utils.dart
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Generates a color based on a string input (typically a name)
|
||||||
|
Color generateColorFromName(String name) {
|
||||||
|
if (name.isEmpty) return Colors.grey;
|
||||||
|
|
||||||
|
// Use the hashCode of the name to generate a consistent color
|
||||||
|
int hash = name.hashCode;
|
||||||
|
|
||||||
|
// Use the hash to generate RGB values
|
||||||
|
final r = (hash & 0xFF0000) >> 16;
|
||||||
|
final g = (hash & 0x00FF00) >> 8;
|
||||||
|
final b = hash & 0x0000FF;
|
||||||
|
|
||||||
|
// Create a color with these RGB values
|
||||||
|
return Color.fromARGB(255, r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Darkens a color by a percentage (0.0 to 1.0)
|
||||||
|
Color darken(Color color, [double amount = 0.3]) {
|
||||||
|
assert(amount >= 0 && amount <= 1);
|
||||||
|
|
||||||
|
final hsl = HSLColor.fromColor(color);
|
||||||
|
final darkened = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0));
|
||||||
|
|
||||||
|
return darkened.toColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lightens a color by a percentage (0.0 to 1.0)
|
||||||
|
Color lighten(Color color, [double amount = 0.3]) {
|
||||||
|
assert(amount >= 0 && amount <= 1);
|
||||||
|
|
||||||
|
final hsl = HSLColor.fromColor(color);
|
||||||
|
final lightened = hsl.withLightness((hsl.lightness + amount).clamp(0.0, 1.0));
|
||||||
|
|
||||||
|
return lightened.toColor();
|
||||||
|
}
|
@ -21,12 +21,12 @@ class BlockService {
|
|||||||
try {
|
try {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
final blockedNumbers = prefs.getStringList(_blockedNumbersKey) ?? [];
|
final blockedNumbers = prefs.getStringList(_blockedNumbersKey) ?? [];
|
||||||
|
|
||||||
// Don't add if already blocked
|
// Don't add if already blocked
|
||||||
if (blockedNumbers.contains(phoneNumber)) {
|
if (blockedNumbers.contains(phoneNumber)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockedNumbers.add(phoneNumber);
|
blockedNumbers.add(phoneNumber);
|
||||||
return await prefs.setStringList(_blockedNumbersKey, blockedNumbers);
|
return await prefs.setStringList(_blockedNumbersKey, blockedNumbers);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -40,11 +40,11 @@ class BlockService {
|
|||||||
try {
|
try {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
final blockedNumbers = prefs.getStringList(_blockedNumbersKey) ?? [];
|
final blockedNumbers = prefs.getStringList(_blockedNumbersKey) ?? [];
|
||||||
|
|
||||||
if (!blockedNumbers.contains(phoneNumber)) {
|
if (!blockedNumbers.contains(phoneNumber)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockedNumbers.remove(phoneNumber);
|
blockedNumbers.remove(phoneNumber);
|
||||||
return await prefs.setStringList(_blockedNumbersKey, blockedNumbers);
|
return await prefs.setStringList(_blockedNumbersKey, blockedNumbers);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -10,7 +10,7 @@ class AsymmetricCryptoService {
|
|||||||
final String _aliasPrefix = 'icing_';
|
final String _aliasPrefix = 'icing_';
|
||||||
final Uuid _uuid = Uuid();
|
final Uuid _uuid = Uuid();
|
||||||
|
|
||||||
/// Generates an ED25519 key pair with a unique alias and stores its metadata.
|
/// Generates an ECDSA P-256 key pair with a unique alias and stores its metadata.
|
||||||
Future<String> generateKeyPair({String? label}) async {
|
Future<String> generateKeyPair({String? label}) async {
|
||||||
try {
|
try {
|
||||||
// Generate a unique identifier for the key
|
// Generate a unique identifier for the key
|
||||||
|
18
dialer/lib/domain/services/message_service.dart
Normal file
18
dialer/lib/domain/services/message_service.dart
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import 'dart:io' show Platform;
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
/// A service to handle sending SMS messages using the url_launcher package.
|
||||||
|
class MessageService {
|
||||||
|
/// Launches the SMS dialer for the given [phoneNumber].
|
||||||
|
Future<void> sendSms(String phoneNumber) async {
|
||||||
|
// Sanitize number (keep only digits and plus sign)
|
||||||
|
final sanitized = phoneNumber.replaceAll(RegExp(r'[^0-9+]'), '');
|
||||||
|
|
||||||
|
Uri uri;
|
||||||
|
|
||||||
|
uri = Uri(scheme: 'sms', path: sanitized);
|
||||||
|
|
||||||
|
await launchUrl(uri);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
// lib/services/obfuscate_service.dart
|
// lib/services/obfuscate_service.dart
|
||||||
import 'package:dialer/presentation/common/widgets/color_darkener.dart';
|
import 'package:dialer/widgets/color_darkener.dart';
|
||||||
|
|
||||||
import '../../core/config/app_config.dart';
|
import '../../core/config/app_config.dart';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import 'package:dialer/presentation/features/home/home_page.dart';
|
|
||||||
import 'package:dialer/presentation/features/home/default_dialer_prompt.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'core/config/app_config.dart';
|
import 'core/config/app_config.dart';
|
||||||
|
import 'core/navigation/app_router.dart';
|
||||||
import 'domain/services/call_service.dart';
|
import 'domain/services/call_service.dart';
|
||||||
import 'domain/services/cryptography/asymmetric_crypto_service.dart';
|
import 'domain/services/cryptography/asymmetric_crypto_service.dart';
|
||||||
|
import 'presentation/common/theme/app_theme.dart';
|
||||||
import 'presentation/features/contacts/contact_state.dart';
|
import 'presentation/features/contacts/contact_state.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
@ -44,60 +44,42 @@ void main() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _requestPermissions() async {
|
Future<void> _requestPermissions() async {
|
||||||
Map<Permission, PermissionStatus> statuses = await [
|
try {
|
||||||
Permission.phone,
|
Map<Permission, PermissionStatus> statuses = await [
|
||||||
Permission.contacts,
|
Permission.phone,
|
||||||
Permission.microphone,
|
Permission.contacts,
|
||||||
].request();
|
Permission.microphone,
|
||||||
if (statuses.values.every((status) => status.isGranted)) {
|
].request();
|
||||||
print("All required permissions granted");
|
|
||||||
const channel = MethodChannel('call_service');
|
if (statuses.values.every((status) => status.isGranted)) {
|
||||||
await channel.invokeMethod('permissionsGranted');
|
debugPrint("All required permissions granted");
|
||||||
} else {
|
const channel = MethodChannel('call_service');
|
||||||
print("Permissions denied: ${statuses.entries.where((e) => !e.value.isGranted).map((e) => e.key).join(', ')}");
|
await channel.invokeMethod('permissionsGranted');
|
||||||
|
} else {
|
||||||
|
debugPrint("Permissions denied: ${statuses.entries.where((e) => !e.value.isGranted).map((e) => e.key).join(', ')}");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error requesting permissions: $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DialerApp extends StatelessWidget {
|
class DialerApp extends StatelessWidget {
|
||||||
const DialerApp({super.key});
|
const DialerApp({super.key});
|
||||||
|
|
||||||
Future<bool> _isDefaultDialer() async {
|
|
||||||
const channel = MethodChannel('call_service');
|
|
||||||
try {
|
|
||||||
final isDefault = await channel.invokeMethod<bool>('isDefaultDialer');
|
|
||||||
return isDefault ?? false;
|
|
||||||
} catch (e) {
|
|
||||||
print('Error checking default dialer: $e');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ContactState(
|
return ContactState(
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
title: 'Dialer App',
|
title: 'Dialer App',
|
||||||
navigatorKey: CallService.navigatorKey,
|
navigatorKey: CallService.navigatorKey,
|
||||||
theme: ThemeData(
|
theme: AppTheme.darkTheme,
|
||||||
brightness: Brightness.dark,
|
onGenerateRoute: AppRouter.generateRoute,
|
||||||
),
|
|
||||||
initialRoute: '/',
|
initialRoute: '/',
|
||||||
routes: {
|
// Add a builder to wrap all routes with SafeArea
|
||||||
'/': (context) => FutureBuilder<bool>(
|
builder: (context, child) {
|
||||||
future: _isDefaultDialer(),
|
return SafeArea(
|
||||||
builder: (context, snapshot) {
|
child: child ?? const SizedBox.shrink(),
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
);
|
||||||
return Scaffold(
|
|
||||||
body: Center(child: CircularProgressIndicator()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (snapshot.hasError || !snapshot.hasData || snapshot.data == false) {
|
|
||||||
return DefaultDialerPromptScreen();
|
|
||||||
}
|
|
||||||
return SafeArea(child: MyHomePage());
|
|
||||||
},
|
|
||||||
),
|
|
||||||
'/home': (context) => SafeArea(child: MyHomePage()),
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -8,7 +8,7 @@ class AppTheme {
|
|||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
),
|
),
|
||||||
tabBarTheme: const TabBarThemeData(
|
tabBarTheme: const TabBarTheme(
|
||||||
labelColor: Colors.white,
|
labelColor: Colors.white,
|
||||||
unselectedLabelColor: Color.fromARGB(255, 158, 158, 158),
|
unselectedLabelColor: Color.fromARGB(255, 158, 158, 158),
|
||||||
indicatorSize: TabBarIndicatorSize.label,
|
indicatorSize: TabBarIndicatorSize.label,
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
|
||||||
import 'package:dialer/domain/services/call_service.dart';
|
|
||||||
import 'package:dialer/domain/services/obfuscate_service.dart';
|
|
||||||
import 'package:dialer/presentation/common/widgets/username_color_generator.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import '../../../domain/services/call_service.dart';
|
||||||
|
import '../../../domain/services/obfuscate_service.dart';
|
||||||
|
import '../../../core/utils/color_utils.dart';
|
||||||
|
// import '../../../presentation/common/widgets/obfuscated_avatar.dart';
|
||||||
|
|
||||||
class CallPage extends StatefulWidget {
|
class CallPage extends StatefulWidget {
|
||||||
final String displayName;
|
final String displayName;
|
||||||
@ -26,174 +26,27 @@ class _CallPageState extends State<CallPage> {
|
|||||||
final ObfuscateService _obfuscateService = ObfuscateService();
|
final ObfuscateService _obfuscateService = ObfuscateService();
|
||||||
final CallService _callService = CallService();
|
final CallService _callService = CallService();
|
||||||
bool isMuted = false;
|
bool isMuted = false;
|
||||||
bool isSpeaker = false;
|
bool isSpeakerOn = false;
|
||||||
bool isKeypadVisible = false;
|
bool isKeypadVisible = false;
|
||||||
bool icingProtocolOk = true;
|
bool icingProtocolOk = true;
|
||||||
String _typedDigits = "";
|
String _typedDigits = "";
|
||||||
Timer? _callTimer;
|
|
||||||
int _callSeconds = 0;
|
|
||||||
String _callStatus = "Calling...";
|
|
||||||
StreamSubscription<String>? _callStateSubscription;
|
|
||||||
StreamSubscription<Map<String, dynamic>>? _audioStateSubscription;
|
|
||||||
|
|
||||||
bool get isNumberUnknown => widget.displayName == widget.phoneNumber;
|
void _addDigit(String digit) {
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_checkInitialCallState();
|
|
||||||
_listenToCallState();
|
|
||||||
_listenToAudioState();
|
|
||||||
_setInitialAudioState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_callTimer?.cancel();
|
|
||||||
_callStateSubscription?.cancel();
|
|
||||||
_audioStateSubscription?.cancel();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _setInitialAudioState() {
|
|
||||||
final initialAudioState = _callService.currentAudioState;
|
|
||||||
if (initialAudioState != null) {
|
|
||||||
setState(() {
|
|
||||||
isMuted = initialAudioState['muted'] ?? false;
|
|
||||||
isSpeaker = initialAudioState['speaker'] ?? false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _checkInitialCallState() async {
|
|
||||||
try {
|
|
||||||
final state = await _callService.getCallState();
|
|
||||||
print('CallPage: Initial call state: $state');
|
|
||||||
if (mounted && state == "active") {
|
|
||||||
setState(() {
|
|
||||||
_callStatus = "00:00";
|
|
||||||
_startCallTimer();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('CallPage: Error checking initial state: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _listenToCallState() {
|
|
||||||
_callStateSubscription = _callService.callStateStream.listen((state) {
|
|
||||||
print('CallPage: Call state changed to $state');
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
if (state == "active") {
|
|
||||||
_callStatus = "00:00";
|
|
||||||
_startCallTimer();
|
|
||||||
} else if (state == "disconnected" || state == "disconnecting") {
|
|
||||||
_callTimer?.cancel();
|
|
||||||
_callStatus = "Call Ended";
|
|
||||||
} else {
|
|
||||||
_callStatus = "Calling...";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _listenToAudioState() {
|
|
||||||
_audioStateSubscription = _callService.audioStateStream.listen((state) {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
isMuted = state['muted'] ?? isMuted;
|
|
||||||
isSpeaker = state['speaker'] ?? isSpeaker;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _startCallTimer() {
|
|
||||||
_callTimer?.cancel();
|
|
||||||
_callTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_callSeconds++;
|
|
||||||
final minutes = (_callSeconds ~/ 60).toString().padLeft(2, '0');
|
|
||||||
final seconds = (_callSeconds % 60).toString().padLeft(2, '0');
|
|
||||||
_callStatus = '$minutes:$seconds';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _addDigit(String digit) async {
|
|
||||||
print('CallPage: Tapped digit: $digit');
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_typedDigits += digit;
|
_typedDigits += digit;
|
||||||
});
|
});
|
||||||
// Send DTMF tone
|
|
||||||
const channel = MethodChannel('call_service');
|
|
||||||
try {
|
|
||||||
final success =
|
|
||||||
await channel.invokeMethod<bool>('sendDtmfTone', {'digit': digit});
|
|
||||||
if (success != true) {
|
|
||||||
print('CallPage: Failed to send DTMF tone for $digit');
|
|
||||||
if (mounted) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Failed to send DTMF tone')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('CallPage: Error sending DTMF tone: $e');
|
|
||||||
if (mounted) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Error sending DTMF tone: $e')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _toggleMute() async {
|
void _toggleMute() {
|
||||||
try {
|
setState(() {
|
||||||
print('CallPage: Toggling mute, current state: $isMuted');
|
isMuted = !isMuted;
|
||||||
final result = await _callService.muteCall(context, mute: !isMuted);
|
});
|
||||||
print('CallPage: Mute call result: $result');
|
|
||||||
if (mounted && result['status'] != 'success') {
|
|
||||||
print('CallPage: Failed to toggle mute: ${result['message']}');
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text('Failed to toggle mute: ${result['message']}')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('CallPage: Error toggling mute: $e');
|
|
||||||
if (mounted) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Error toggling mute: $e')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _toggleSpeaker() async {
|
void _toggleSpeaker() {
|
||||||
try {
|
setState(() {
|
||||||
print('CallPage: Toggling speaker, current state: $isSpeaker');
|
isSpeakerOn = !isSpeakerOn;
|
||||||
final result =
|
});
|
||||||
await _callService.speakerCall(context, speaker: !isSpeaker);
|
|
||||||
print('CallPage: Speaker call result: $result');
|
|
||||||
if (result['status'] != 'success') {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text('Failed to toggle speaker: ${result['message']}')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('CallPage: Error toggling speaker: $e');
|
|
||||||
if (mounted) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Error toggling speaker: $e')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _toggleKeypad() {
|
void _toggleKeypad() {
|
||||||
@ -202,363 +55,197 @@ class _CallPageState extends State<CallPage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _toggleIcingProtocol() {
|
|
||||||
setState(() {
|
|
||||||
icingProtocolOk = !icingProtocolOk;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _hangUp() async {
|
void _hangUp() async {
|
||||||
try {
|
try {
|
||||||
print('CallPage: Initiating hangUp');
|
await _callService.hangUpCall(context);
|
||||||
final result = await _callService.hangUpCall(context);
|
|
||||||
print('CallPage: Hang up result: $result');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('CallPage: Error hanging up: $e');
|
debugPrint("Error hanging up: $e");
|
||||||
if (mounted) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Error hanging up: $e')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _addContact() async {
|
|
||||||
if (await FlutterContacts.requestPermission()) {
|
|
||||||
final newContact = Contact()..phones = [Phone(widget.phoneNumber)];
|
|
||||||
final updatedContact =
|
|
||||||
await FlutterContacts.openExternalInsert(newContact);
|
|
||||||
if (updatedContact != null) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Contact added successfully!')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Permission denied for contacts')),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final double avatarRadius = isKeypadVisible ? 45.0 : 45.0;
|
final double avatarRadius = 45.0;
|
||||||
final double nameFontSize = isKeypadVisible ? 24.0 : 24.0;
|
|
||||||
final double statusFontSize = isKeypadVisible ? 16.0 : 16.0;
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.black,
|
||||||
print(
|
body: SafeArea(
|
||||||
'CallPage: Building UI, _callStatus: $_callStatus, route: ${ModalRoute.of(context)?.settings.name ?? "unknown"}');
|
child: Column(
|
||||||
return PopScope(
|
children: [
|
||||||
canPop: _callStatus == "Call Ended",
|
Container(
|
||||||
onPopInvoked: (didPop) {
|
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||||
if (!didPop) {
|
child: Column(
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
mainAxisSize: MainAxisSize.min,
|
||||||
SnackBar(content: Text('Cannot leave during an active call')),
|
children: [
|
||||||
);
|
ObfuscatedAvatar(
|
||||||
}
|
imageBytes: widget.thumbnail,
|
||||||
},
|
radius: avatarRadius,
|
||||||
child: Scaffold(
|
backgroundColor: generateColorFromName(widget.displayName),
|
||||||
body: Container(
|
fallbackInitial: widget.displayName,
|
||||||
color: Colors.black,
|
|
||||||
child: SafeArea(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 35),
|
|
||||||
ObfuscatedAvatar(
|
|
||||||
imageBytes: widget.thumbnail,
|
|
||||||
radius: avatarRadius,
|
|
||||||
backgroundColor:
|
|
||||||
generateColorFromName(widget.displayName),
|
|
||||||
fallbackInitial: widget.displayName,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
icingProtocolOk ? Icons.lock : Icons.lock_open,
|
|
||||||
color: icingProtocolOk ? Colors.green : Colors.red,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Text(
|
|
||||||
'Icing protocol: ${icingProtocolOk ? "ok" : "ko"}',
|
|
||||||
style: TextStyle(
|
|
||||||
color:
|
|
||||||
icingProtocolOk ? Colors.green : Colors.red,
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
_obfuscateService.obfuscateData(widget.displayName),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: nameFontSize,
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
widget.phoneNumber,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: statusFontSize,
|
|
||||||
color: Colors.white70,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
_callStatus,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: statusFontSize,
|
|
||||||
color: Colors.white70,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
Expanded(
|
Text(
|
||||||
child: Column(
|
_obfuscateService.obfuscateData(widget.displayName),
|
||||||
children: [
|
style: const TextStyle(
|
||||||
if (isKeypadVisible) ...[
|
fontSize: 24,
|
||||||
const Spacer(flex: 2),
|
color: Colors.white,
|
||||||
Padding(
|
fontWeight: FontWeight.bold,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
_typedDigits,
|
|
||||||
maxLines: 1,
|
|
||||||
textAlign: TextAlign.right,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
onPressed: _toggleKeypad,
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.close,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: MediaQuery.of(context).size.height * 0.4,
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 20),
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: GridView.count(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
crossAxisCount: 3,
|
|
||||||
childAspectRatio: 1.5,
|
|
||||||
mainAxisSpacing: 8,
|
|
||||||
crossAxisSpacing: 8,
|
|
||||||
children: List.generate(12, (index) {
|
|
||||||
String label;
|
|
||||||
if (index < 9) {
|
|
||||||
label = '${index + 1}';
|
|
||||||
} else if (index == 9) {
|
|
||||||
label = '*';
|
|
||||||
} else if (index == 10) {
|
|
||||||
label = '0';
|
|
||||||
} else {
|
|
||||||
label = '#';
|
|
||||||
}
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () => _addDigit(label),
|
|
||||||
child: Container(
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
color: Colors.transparent,
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
label,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 32,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(flex: 1),
|
|
||||||
] else ...[
|
|
||||||
const Spacer(),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: _toggleMute,
|
|
||||||
icon: Icon(
|
|
||||||
isMuted ? Icons.mic_off : Icons.mic,
|
|
||||||
color: isMuted
|
|
||||||
? Colors.amber
|
|
||||||
: Colors.white,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
isMuted ? 'Unmute' : 'Mute',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: _toggleKeypad,
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.dialpad,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
'Keypad',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: _toggleSpeaker,
|
|
||||||
icon: Icon(
|
|
||||||
isSpeaker
|
|
||||||
? Icons.volume_up
|
|
||||||
: Icons.volume_off,
|
|
||||||
color: isSpeaker
|
|
||||||
? Colors.amber
|
|
||||||
: Colors.white,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
'Speaker',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
if (isNumberUnknown)
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: _addContact,
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.person_add,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
'Add Contact',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {},
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.sim_card,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
'Change SIM',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(flex: 3),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 16.0),
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: _hangUp,
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.red,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.call_end,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Text(
|
||||||
|
widget.phoneNumber,
|
||||||
|
style: const TextStyle(fontSize: 16, color: Colors.white70),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Calling...',
|
||||||
|
style: const TextStyle(fontSize: 16, color: Colors.white70),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Expanded(
|
||||||
|
child: isKeypadVisible
|
||||||
|
? _buildKeypad()
|
||||||
|
: _buildCallControls(),
|
||||||
|
),
|
||||||
|
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 24.0),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: _hangUp,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.red,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.call_end,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildKeypad() {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||||
|
child: Text(
|
||||||
|
_typedDigits,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Expanded(
|
||||||
|
child: GridView.count(
|
||||||
|
crossAxisCount: 3,
|
||||||
|
childAspectRatio: 1.5,
|
||||||
|
children: [
|
||||||
|
_buildDialButton('1'),
|
||||||
|
_buildDialButton('2'),
|
||||||
|
_buildDialButton('3'),
|
||||||
|
_buildDialButton('4'),
|
||||||
|
_buildDialButton('5'),
|
||||||
|
_buildDialButton('6'),
|
||||||
|
_buildDialButton('7'),
|
||||||
|
_buildDialButton('8'),
|
||||||
|
_buildDialButton('9'),
|
||||||
|
_buildDialButton('*'),
|
||||||
|
_buildDialButton('0'),
|
||||||
|
_buildDialButton('#'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: _toggleKeypad,
|
||||||
|
child: const Text('Hide Keypad'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCallControls() {
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
_buildControlButton(
|
||||||
|
icon: isMuted ? Icons.mic_off : Icons.mic,
|
||||||
|
label: isMuted ? 'Unmute' : 'Mute',
|
||||||
|
onPressed: _toggleMute,
|
||||||
|
),
|
||||||
|
_buildControlButton(
|
||||||
|
icon: Icons.dialpad,
|
||||||
|
label: 'Keypad',
|
||||||
|
onPressed: _toggleKeypad,
|
||||||
|
),
|
||||||
|
_buildControlButton(
|
||||||
|
icon: isSpeakerOn ? Icons.volume_up : Icons.volume_off,
|
||||||
|
label: 'Speaker',
|
||||||
|
onPressed: _toggleSpeaker,
|
||||||
|
iconColor: isSpeakerOn ? Colors.amber : Colors.white,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDialButton(String digit) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => _addDigit(digit),
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Colors.grey[800],
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
digit,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Widget _buildControlButton({
|
||||||
|
required IconData icon,
|
||||||
|
required String label,
|
||||||
|
required VoidCallback onPressed,
|
||||||
|
Color iconColor = Colors.white,
|
||||||
|
}) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
iconSize: 32,
|
||||||
|
icon: Icon(icon, color: iconColor),
|
||||||
|
onPressed: onPressed,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import '../../../domain/services/call_service.dart';
|
import '../../../domain/services/call_service.dart';
|
||||||
import '../../../domain/services/obfuscate_service.dart';
|
import '../../../domain/services/obfuscate_service.dart';
|
||||||
import 'package:dialer/presentation/common/widgets/username_color_generator.dart';
|
import '../../../core/utils/color_utils.dart';
|
||||||
import 'call_page.dart';
|
import 'call_page.dart';
|
||||||
|
|
||||||
class IncomingCallPage extends StatefulWidget {
|
class IncomingCallPage extends StatefulWidget {
|
||||||
|
@ -4,6 +4,7 @@ import 'package:url_launcher/url_launcher.dart';
|
|||||||
import '../../../domain/services/contact_service.dart';
|
import '../../../domain/services/contact_service.dart';
|
||||||
import '../../../domain/services/obfuscate_service.dart';
|
import '../../../domain/services/obfuscate_service.dart';
|
||||||
import '../../../domain/services/call_service.dart';
|
import '../../../domain/services/call_service.dart';
|
||||||
|
import '../contacts/widgets/add_contact_button.dart';
|
||||||
|
|
||||||
class CompositionPage extends StatefulWidget {
|
class CompositionPage extends StatefulWidget {
|
||||||
const CompositionPage({super.key});
|
const CompositionPage({super.key});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||||
import '../../../../domain/services/obfuscate_service.dart';
|
import '../../../../domain/services/obfuscate_service.dart';
|
||||||
import 'package:dialer/presentation/common/widgets/username_color_generator.dart';
|
import '../../../../core/utils/color_utils.dart';
|
||||||
import '../contact_state.dart';
|
import '../contact_state.dart';
|
||||||
import 'add_contact_button.dart';
|
import 'add_contact_button.dart';
|
||||||
import 'contact_modal.dart';
|
import 'contact_modal.dart';
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import '../../../common/widgets/username_color_generator.dart';
|
import '../../../../widgets/username_color_generator.dart';
|
||||||
import '../../../common/widgets/color_darkener.dart';
|
import '../../../../widgets/color_darkener.dart';
|
||||||
import '../../../../domain/services/obfuscate_service.dart';
|
import '../../../../domain/services/obfuscate_service.dart';
|
||||||
import '../../../../domain/services/block_service.dart';
|
import '../../../../domain/services/block_service.dart';
|
||||||
import '../../../../domain/services/contact_service.dart';
|
import '../../../../domain/services/contact_service.dart';
|
||||||
import '../../../../domain/services/call_service.dart';
|
import '../../../../domain/services/call_service.dart';
|
||||||
|
import '../../../../domain/services/message_service.dart';
|
||||||
|
|
||||||
class ContactModal extends StatefulWidget {
|
class ContactModal extends StatefulWidget {
|
||||||
final Contact contact;
|
final Contact contact;
|
||||||
@ -32,6 +33,7 @@ class _ContactModalState extends State<ContactModal> {
|
|||||||
final ObfuscateService _obfuscateService = ObfuscateService();
|
final ObfuscateService _obfuscateService = ObfuscateService();
|
||||||
final CallService _callService = CallService();
|
final CallService _callService = CallService();
|
||||||
final ContactService _contactService = ContactService();
|
final ContactService _contactService = ContactService();
|
||||||
|
final MessageService _messageService = MessageService();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -86,15 +88,6 @@ class _ContactModalState extends State<ContactModal> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _launchSms(String phoneNumber) async {
|
|
||||||
final uri = Uri(scheme: 'sms', path: phoneNumber);
|
|
||||||
if (await canLaunchUrl(uri)) {
|
|
||||||
await launchUrl(uri);
|
|
||||||
} else {
|
|
||||||
debugPrint('Could not launch SMS to $phoneNumber');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _launchEmail(String email) async {
|
void _launchEmail(String email) async {
|
||||||
final uri = Uri(scheme: 'mailto', path: email);
|
final uri = Uri(scheme: 'mailto', path: email);
|
||||||
if (await canLaunchUrl(uri)) {
|
if (await canLaunchUrl(uri)) {
|
||||||
@ -237,7 +230,8 @@ class _ContactModalState extends State<ContactModal> {
|
|||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
widget.contact.thumbnail != null && widget.contact.thumbnail!.isNotEmpty
|
widget.contact.thumbnail != null &&
|
||||||
|
widget.contact.thumbnail!.isNotEmpty
|
||||||
? ClipOval(
|
? ClipOval(
|
||||||
child: Image.memory(
|
child: Image.memory(
|
||||||
widget.contact.thumbnail!,
|
widget.contact.thumbnail!,
|
||||||
@ -251,7 +245,8 @@ class _ContactModalState extends State<ContactModal> {
|
|||||||
radius: 50,
|
radius: 50,
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.contact.displayName.isNotEmpty
|
widget.contact.displayName.isNotEmpty
|
||||||
? widget.contact.displayName[0].toUpperCase()
|
? widget.contact.displayName[0]
|
||||||
|
.toUpperCase()
|
||||||
: '?',
|
: '?',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: darken(avatarColor),
|
color: darken(avatarColor),
|
||||||
@ -278,7 +273,8 @@ class _ContactModalState extends State<ContactModal> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.phone, color: Colors.green),
|
leading:
|
||||||
|
const Icon(Icons.phone, color: Colors.green),
|
||||||
title: Text(
|
title: Text(
|
||||||
_obfuscateService.obfuscateData(phoneNumber),
|
_obfuscateService.obfuscateData(phoneNumber),
|
||||||
style: const TextStyle(color: Colors.white),
|
style: const TextStyle(color: Colors.white),
|
||||||
@ -291,19 +287,21 @@ class _ContactModalState extends State<ContactModal> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.message, color: Colors.blue),
|
leading:
|
||||||
|
const Icon(Icons.message, color: Colors.blue),
|
||||||
title: Text(
|
title: Text(
|
||||||
_obfuscateService.obfuscateData(phoneNumber),
|
_obfuscateService.obfuscateData(phoneNumber),
|
||||||
style: const TextStyle(color: Colors.white),
|
style: const TextStyle(color: Colors.white),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (widget.contact.phones.isNotEmpty) {
|
if (widget.contact.phones.isNotEmpty) {
|
||||||
_launchSms(phoneNumber);
|
_messageService.sendSms(phoneNumber);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.email, color: Colors.orange),
|
leading:
|
||||||
|
const Icon(Icons.email, color: Colors.orange),
|
||||||
title: Text(
|
title: Text(
|
||||||
email,
|
email,
|
||||||
style: const TextStyle(color: Colors.white),
|
style: const TextStyle(color: Colors.white),
|
||||||
@ -317,7 +315,8 @@ class _ContactModalState extends State<ContactModal> {
|
|||||||
const Divider(color: Colors.grey),
|
const Divider(color: Colors.grey),
|
||||||
// Favorite, Edit, and Block/Unblock Buttons
|
// Favorite, Edit, and Block/Unblock Buttons
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Favorite button
|
// Favorite button
|
||||||
@ -333,8 +332,9 @@ class _ContactModalState extends State<ContactModal> {
|
|||||||
icon: Icon(widget.isFavorite
|
icon: Icon(widget.isFavorite
|
||||||
? Icons.star
|
? Icons.star
|
||||||
: Icons.star_border),
|
: Icons.star_border),
|
||||||
label: Text(
|
label: Text(widget.isFavorite
|
||||||
widget.isFavorite ? 'Unfavorite' : 'Favorite'),
|
? 'Unfavorite'
|
||||||
|
: 'Favorite'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
@ -353,9 +353,11 @@ class _ContactModalState extends State<ContactModal> {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: _toggleBlockState,
|
onPressed: _toggleBlockState,
|
||||||
icon: Icon(
|
icon: Icon(isBlocked
|
||||||
isBlocked ? Icons.block : Icons.block_flipped),
|
? Icons.block
|
||||||
label: Text(isBlocked ? 'Unblock' : 'Block'),
|
: Icons.block_flipped),
|
||||||
|
label:
|
||||||
|
Text(isBlocked ? 'Unblock' : 'Block'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
311
dialer/lib/presentation/features/dialer/composition_page.dart
Normal file
311
dialer/lib/presentation/features/dialer/composition_page.dart
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import '../../../domain/services/contact_service.dart';
|
||||||
|
import '../../../domain/services/obfuscate_service.dart';
|
||||||
|
import '../../../domain/services/call_service.dart';
|
||||||
|
import '../contacts/widgets/add_contact_button.dart';
|
||||||
|
|
||||||
|
class CompositionPage extends StatefulWidget {
|
||||||
|
const CompositionPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CompositionPageState createState() => _CompositionPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CompositionPageState extends State<CompositionPage> {
|
||||||
|
String dialedNumber = "";
|
||||||
|
List<Contact> _allContacts = [];
|
||||||
|
List<Contact> _filteredContacts = [];
|
||||||
|
final ContactService _contactService = ContactService();
|
||||||
|
final ObfuscateService _obfuscateService = ObfuscateService();
|
||||||
|
final CallService _callService = CallService();
|
||||||
|
|
||||||
|
// Cache for normalized phone numbers to avoid repeated processing
|
||||||
|
final Map<String, String> _normalizedPhoneCache = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchContacts();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchContacts() async {
|
||||||
|
_allContacts = await _contactService.fetchContacts();
|
||||||
|
_filteredContacts = _allContacts;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getNormalizedPhone(String phone) {
|
||||||
|
return _normalizedPhoneCache.putIfAbsent(
|
||||||
|
phone,
|
||||||
|
() => phone.replaceAll(RegExp(r'\D'), '')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _filterContacts() {
|
||||||
|
if (dialedNumber.isEmpty) {
|
||||||
|
setState(() {
|
||||||
|
_filteredContacts = _allContacts;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String normalizedDialed = dialedNumber.replaceAll(RegExp(r'\D'), '');
|
||||||
|
final String lowerDialed = dialedNumber.toLowerCase();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_filteredContacts = _allContacts.where((contact) {
|
||||||
|
// Check phone numbers
|
||||||
|
final phoneMatch = contact.phones.any((phone) =>
|
||||||
|
_getNormalizedPhone(phone.number).contains(normalizedDialed));
|
||||||
|
|
||||||
|
// Only check name if phone doesn't match (optimization)
|
||||||
|
if (phoneMatch) return true;
|
||||||
|
|
||||||
|
// Check display name
|
||||||
|
return contact.displayName.toLowerCase().contains(lowerDialed);
|
||||||
|
}).toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onNumberPress(String number) {
|
||||||
|
setState(() {
|
||||||
|
dialedNumber += number;
|
||||||
|
_filterContacts();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onDeletePress() {
|
||||||
|
setState(() {
|
||||||
|
if (dialedNumber.isNotEmpty) {
|
||||||
|
dialedNumber = dialedNumber.substring(0, dialedNumber.length - 1);
|
||||||
|
_filterContacts();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onClearPress() {
|
||||||
|
setState(() {
|
||||||
|
dialedNumber = "";
|
||||||
|
_filteredContacts = _allContacts;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _makeCall(String phoneNumber) async {
|
||||||
|
try {
|
||||||
|
await _callService.makeGsmCall(context, phoneNumber: phoneNumber);
|
||||||
|
setState(() {
|
||||||
|
dialedNumber = phoneNumber;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Error making call: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _launchSms(String phoneNumber) async {
|
||||||
|
final uri = Uri(scheme: 'sms', path: phoneNumber);
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri);
|
||||||
|
} else {
|
||||||
|
debugPrint('Could not send SMS to $phoneNumber');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
// Top half: Display contacts matching dialed number
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 42.0, left: 16.0, right: 16.0, bottom: 16.0),
|
||||||
|
color: Colors.black,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ListView(
|
||||||
|
children: _filteredContacts.isNotEmpty
|
||||||
|
? _filteredContacts.map((contact) {
|
||||||
|
final phoneNumber = contact.phones.isNotEmpty
|
||||||
|
? contact.phones.first.number
|
||||||
|
: 'No phone number';
|
||||||
|
return ListTile(
|
||||||
|
title: Text(
|
||||||
|
_obfuscateService.obfuscateData(contact.displayName),
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
_obfuscateService.obfuscateData(phoneNumber),
|
||||||
|
style: const TextStyle(color: Colors.grey),
|
||||||
|
),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.phone,
|
||||||
|
color: Colors.green[300],
|
||||||
|
size: 20),
|
||||||
|
onPressed: () {
|
||||||
|
_makeCall(phoneNumber);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.message,
|
||||||
|
color: Colors.blue[300],
|
||||||
|
size: 20),
|
||||||
|
onPressed: () {
|
||||||
|
_launchSms(phoneNumber);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
// Handle contact selection if needed
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}).toList()
|
||||||
|
: [],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Bottom half: Dialpad and Dialed number display with erase button
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Display dialed number with erase button
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
dialedNumber,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 24, color: Colors.white),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: _onClearPress,
|
||||||
|
icon: const Icon(Icons.backspace,
|
||||||
|
color: Colors.white),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
|
// Dialpad
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
_buildDialButton('1'),
|
||||||
|
_buildDialButton('2'),
|
||||||
|
_buildDialButton('3'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
_buildDialButton('4'),
|
||||||
|
_buildDialButton('5'),
|
||||||
|
_buildDialButton('6'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
_buildDialButton('7'),
|
||||||
|
_buildDialButton('8'),
|
||||||
|
_buildDialButton('9'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
_buildDialButton('*'),
|
||||||
|
_buildDialButton('0'),
|
||||||
|
_buildDialButton('#'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// Add Contact Button
|
||||||
|
Positioned(
|
||||||
|
bottom: 20.0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Center(
|
||||||
|
child: AddContactButton(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Top Row with Back Arrow
|
||||||
|
Positioned(
|
||||||
|
top: 40.0,
|
||||||
|
left: 16.0,
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDialButton(String number) {
|
||||||
|
return ElevatedButton(
|
||||||
|
onPressed: () => _onNumberPress(number),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
shape: const CircleBorder(),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
number,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -6,8 +6,8 @@ import 'package:intl/intl.dart';
|
|||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import '../../../domain/services/obfuscate_service.dart';
|
import '../../../domain/services/obfuscate_service.dart';
|
||||||
import '../../common/widgets/color_darkener.dart';
|
import '../../../widgets/color_darkener.dart';
|
||||||
import '../../common/widgets/username_color_generator.dart';
|
import '../../../widgets/username_color_generator.dart';
|
||||||
import '../../../domain/services/block_service.dart';
|
import '../../../domain/services/block_service.dart';
|
||||||
import '../../../domain/services/call_service.dart';
|
import '../../../domain/services/call_service.dart';
|
||||||
import '../contacts/contact_state.dart';
|
import '../contacts/contact_state.dart';
|
||||||
|
@ -1,102 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
class DefaultDialerPromptScreen extends StatelessWidget {
|
|
||||||
const DefaultDialerPromptScreen({super.key});
|
|
||||||
|
|
||||||
Future<void> _requestDefaultDialer(BuildContext context) async {
|
|
||||||
const channel = MethodChannel('call_service');
|
|
||||||
try {
|
|
||||||
await channel.invokeMethod('requestDefaultDialer');
|
|
||||||
// Navigate to home page after requesting default dialer
|
|
||||||
Navigator.of(context).pushReplacementNamed('/home');
|
|
||||||
} catch (e) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Error requesting default dialer: $e')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _exploreApp(BuildContext context) {
|
|
||||||
// Navigate to home page without requesting default dialer
|
|
||||||
Navigator.of(context).pushReplacementNamed('/home');
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
body: SafeArea(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Set as Default Dialer',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
'To handle calls effectively, Icing needs to be your default dialer app. This allows Icing to manage incoming and outgoing calls seamlessly.\n\nWithout the permission, Icing will not be able to encrypt calls.',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: Colors.white70,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
flex: 1,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 8.0),
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: () => _exploreApp(context),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Colors.grey[800],
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 16, vertical: 12),
|
|
||||||
),
|
|
||||||
child: Text('Explore App first'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 1,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 8.0),
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: () => _requestDefaultDialer(context),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Colors.blue,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 16, vertical: 12),
|
|
||||||
),
|
|
||||||
child: Text('Set as Default Dialer'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,89 +3,89 @@ import '../../../domain/services/obfuscate_service.dart';
|
|||||||
import '../contacts/contact_page.dart';
|
import '../contacts/contact_page.dart';
|
||||||
import '../favorites/favorites_page.dart';
|
import '../favorites/favorites_page.dart';
|
||||||
import '../history/history_page.dart';
|
import '../history/history_page.dart';
|
||||||
import '../composition/composition.dart';
|
import '../dialer/composition_page.dart';
|
||||||
import '../settings/settings.dart';
|
import '../settings/settings.dart';
|
||||||
import '../voicemail/voicemail_page.dart';
|
import '../voicemail/voicemail_page.dart';
|
||||||
|
import '../contacts/contact_state.dart';
|
||||||
import '../contacts/widgets/contact_modal.dart';
|
import '../contacts/widgets/contact_modal.dart';
|
||||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||||
import 'package:dialer/domain/services/contact_service.dart';
|
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
|
class MyHomePage extends StatefulWidget {
|
||||||
|
const MyHomePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_MyHomePageState createState() => _MyHomePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MyHomePageState extends State<MyHomePage>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
List<Contact> _allContacts = [];
|
List<Contact> _allContacts = [];
|
||||||
List<Contact> _contactSuggestions = [];
|
List<Contact> _contactSuggestions = [];
|
||||||
final ContactService _contactService = ContactService();
|
|
||||||
final ObfuscateService _obfuscateService = ObfuscateService();
|
final ObfuscateService _obfuscateService = ObfuscateService();
|
||||||
final TextEditingController _searchController = TextEditingController();
|
final SearchController _searchController = SearchController();
|
||||||
late SearchController _searchBarController;
|
bool _isInitialized = false;
|
||||||
String _rawSearchInput = '';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_tabController = TabController(length: 4, vsync: this, initialIndex: 2);
|
_tabController = TabController(length: 4, vsync: this, initialIndex: 1);
|
||||||
_tabController.addListener(_handleTabIndex);
|
_tabController.addListener(_handleTabIndex);
|
||||||
_searchBarController = SearchController();
|
}
|
||||||
_searchBarController.addListener(() {
|
|
||||||
if (_searchController.text != _searchBarController.text) {
|
@override
|
||||||
_rawSearchInput = _searchBarController.text;
|
void didChangeDependencies() {
|
||||||
_searchController.text = _rawSearchInput;
|
super.didChangeDependencies();
|
||||||
_onSearchChanged(_searchBarController.text);
|
if (!_isInitialized) {
|
||||||
}
|
// Use a post-frame callback to avoid setState during build
|
||||||
});
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_fetchContacts();
|
_fetchContacts();
|
||||||
|
});
|
||||||
|
_isInitialized = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _fetchContacts() async {
|
void _fetchContacts() async {
|
||||||
_allContacts = await _contactService.fetchContacts();
|
final contactState = ContactState.of(context);
|
||||||
_contactSuggestions = List.from(_allContacts);
|
// Wait for initial load to finish if it hasn't already
|
||||||
if (mounted) setState(() {});
|
if (contactState.loading) {
|
||||||
}
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
}
|
||||||
void _clearSearch() {
|
// Then explicitly fetch contacts (which will call setState safely)
|
||||||
_searchController.clear();
|
await contactState.fetchContacts();
|
||||||
_searchBarController.clear();
|
if (mounted) {
|
||||||
_rawSearchInput = '';
|
setState(() {
|
||||||
_onSearchChanged('');
|
_allContacts = contactState.contacts;
|
||||||
|
_contactSuggestions = _allContacts;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSearchChanged(String query) {
|
void _onSearchChanged(String query) {
|
||||||
|
if (query.isEmpty) {
|
||||||
|
setState(() {
|
||||||
|
_contactSuggestions = List.from(_allContacts); // Reset suggestions
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert query to lowercase once for efficiency
|
||||||
|
final lowerQuery = query.toLowerCase();
|
||||||
|
|
||||||
|
// Use where with efficient filter
|
||||||
|
final filtered = _allContacts.where((contact) {
|
||||||
|
final name = contact.displayName.toLowerCase();
|
||||||
|
return name.contains(lowerQuery);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
if (query.isEmpty) {
|
_contactSuggestions = filtered;
|
||||||
_contactSuggestions = List.from(_allContacts);
|
|
||||||
} else {
|
|
||||||
final normalizedQuery = _normalizeString(query.toLowerCase());
|
|
||||||
_contactSuggestions = _allContacts.where((contact) {
|
|
||||||
final normalizedName = _normalizeString(contact.displayName.toLowerCase());
|
|
||||||
return normalizedName.contains(normalizedQuery);
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
String _normalizeString(String input) {
|
|
||||||
const accentMap = {
|
|
||||||
'àáâãäå': 'a',
|
|
||||||
'èéêë': 'e',
|
|
||||||
'ìíîï': 'i',
|
|
||||||
'òóôõö': 'o',
|
|
||||||
'ùúûü': 'u',
|
|
||||||
'ç': 'c',
|
|
||||||
'ñ': 'n',
|
|
||||||
};
|
|
||||||
String normalized = input;
|
|
||||||
accentMap.forEach((accents, base) {
|
|
||||||
for (var accent in accents.split('')) {
|
|
||||||
normalized = normalized.replaceAll(accent, base);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_searchController.dispose();
|
_searchController.dispose();
|
||||||
_searchBarController.dispose();
|
|
||||||
_tabController.removeListener(_handleTabIndex);
|
_tabController.removeListener(_handleTabIndex);
|
||||||
_tabController.dispose();
|
_tabController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@ -98,27 +98,28 @@ class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateM
|
|||||||
void _toggleFavorite(Contact contact) async {
|
void _toggleFavorite(Contact contact) async {
|
||||||
try {
|
try {
|
||||||
if (await FlutterContacts.requestPermission()) {
|
if (await FlutterContacts.requestPermission()) {
|
||||||
Contact? fullContact = await FlutterContacts.getContact(
|
Contact? fullContact = await FlutterContacts.getContact(contact.id,
|
||||||
contact.id,
|
withProperties: true,
|
||||||
withProperties: true,
|
withAccounts: true,
|
||||||
withAccounts: true,
|
withPhoto: true,
|
||||||
withPhoto: true,
|
withThumbnail: true);
|
||||||
withThumbnail: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (fullContact != null) {
|
if (fullContact != null) {
|
||||||
fullContact.isStarred = !fullContact.isStarred;
|
fullContact.isStarred = !fullContact.isStarred;
|
||||||
await FlutterContacts.updateContact(fullContact);
|
await FlutterContacts.updateContact(fullContact);
|
||||||
_fetchContacts();
|
// Check if widget is still mounted before updating state
|
||||||
|
if (mounted) {
|
||||||
|
_fetchContacts();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
print("Could not fetch contact details");
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Error updating favorite status: $e");
|
debugPrint("Error updating favorite status: $e");
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
if (mounted) {
|
||||||
SnackBar(content: Text('Failed to update contact favorite status')),
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
);
|
SnackBar(content: Text('Failed to update contact favorite status')),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +129,7 @@ class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateM
|
|||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
|
// Persistent Search Bar
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
top: 24.0,
|
top: 24.0,
|
||||||
@ -138,113 +140,108 @@ class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateM
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: SearchAnchor(
|
||||||
decoration: BoxDecoration(
|
builder: (BuildContext context, SearchController controller) {
|
||||||
color: const Color.fromARGB(255, 30, 30, 30),
|
return GestureDetector(
|
||||||
borderRadius: BorderRadius.circular(12.0),
|
onTap: () {
|
||||||
border: Border.all(color: Colors.grey.shade800, width: 1),
|
controller.openView(); // Open the search view
|
||||||
),
|
},
|
||||||
child: SearchAnchor(
|
child: Container(
|
||||||
searchController: _searchBarController,
|
decoration: BoxDecoration(
|
||||||
builder: (BuildContext context, SearchController controller) {
|
color: const Color.fromARGB(255, 30, 30, 30),
|
||||||
return GestureDetector(
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
onTap: () {
|
border: Border.all(color: Colors.grey.shade800, width: 1),
|
||||||
controller.openView();
|
),
|
||||||
},
|
padding: const EdgeInsets.symmetric(
|
||||||
child: Container(
|
vertical: 12.0, horizontal: 16.0),
|
||||||
decoration: BoxDecoration(
|
child: Row(
|
||||||
color: const Color.fromARGB(255, 30, 30, 30),
|
children: [
|
||||||
borderRadius: BorderRadius.circular(12.0),
|
const Icon(Icons.search,
|
||||||
border: Border.all(color: Colors.grey.shade800, width: 1),
|
color: Colors.grey, size: 24.0),
|
||||||
),
|
const SizedBox(width: 8.0),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
|
Text(
|
||||||
child: Row(
|
controller.text.isEmpty
|
||||||
children: [
|
? 'Search contacts'
|
||||||
const Icon(Icons.search, color: Colors.grey, size: 24.0),
|
: controller.text,
|
||||||
const SizedBox(width: 8.0),
|
style: const TextStyle(
|
||||||
Expanded(
|
color: Colors.grey, fontSize: 16.0),
|
||||||
child: Text(
|
),
|
||||||
_rawSearchInput.isEmpty
|
const Spacer(),
|
||||||
? 'Search contacts'
|
if (controller.text.isNotEmpty)
|
||||||
: _rawSearchInput,
|
GestureDetector(
|
||||||
style: const TextStyle(color: Colors.grey, fontSize: 16.0),
|
onTap: () {
|
||||||
overflow: TextOverflow.ellipsis,
|
controller.clear();
|
||||||
|
_onSearchChanged('');
|
||||||
|
},
|
||||||
|
child: const Icon(
|
||||||
|
Icons.clear,
|
||||||
|
color: Colors.grey,
|
||||||
|
size: 24.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_rawSearchInput.isNotEmpty)
|
],
|
||||||
GestureDetector(
|
|
||||||
onTap: _clearSearch,
|
|
||||||
child: const Icon(
|
|
||||||
Icons.clear,
|
|
||||||
color: Colors.grey,
|
|
||||||
size: 24.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
viewOnChanged: (query) {
|
},
|
||||||
|
viewOnChanged: _onSearchChanged, // Update immediately
|
||||||
|
suggestionsBuilder:
|
||||||
|
(BuildContext context, SearchController controller) {
|
||||||
|
return _contactSuggestions.map((contact) {
|
||||||
|
return ListTile(
|
||||||
|
key: ValueKey(contact.id),
|
||||||
|
title: Text(_obfuscateService.obfuscateData(contact.displayName),
|
||||||
|
style: const TextStyle(color: Colors.white)),
|
||||||
|
onTap: () {
|
||||||
|
// Clear the search text input
|
||||||
|
controller.text = '';
|
||||||
|
|
||||||
if (_searchBarController.text != query) {
|
// Close the search view
|
||||||
_rawSearchInput = query;
|
controller.closeView(contact.displayName);
|
||||||
_searchBarController.text = query;
|
|
||||||
_searchController.text = query;
|
// Show the ContactModal when a contact is tapped
|
||||||
}
|
showModalBottomSheet(
|
||||||
_onSearchChanged(query);
|
context: context,
|
||||||
},
|
isScrollControlled: true,
|
||||||
suggestionsBuilder: (BuildContext context, SearchController controller) {
|
backgroundColor: Colors.transparent,
|
||||||
return _contactSuggestions.map((contact) {
|
builder: (context) {
|
||||||
return ListTile(
|
return ContactModal(
|
||||||
key: ValueKey(contact.id),
|
contact: contact,
|
||||||
title: Text(
|
onEdit: () async {
|
||||||
_obfuscateService.obfuscateData(contact.displayName),
|
if (await FlutterContacts.requestPermission()) {
|
||||||
style: const TextStyle(color: Colors.white),
|
final updatedContact =
|
||||||
),
|
await FlutterContacts.openExternalEdit(contact.id);
|
||||||
onTap: () {
|
if (updatedContact != null) {
|
||||||
controller.closeView(contact.displayName);
|
_fetchContacts();
|
||||||
showModalBottomSheet(
|
Navigator.of(context).pop();
|
||||||
context: context,
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
isScrollControlled: true,
|
SnackBar(
|
||||||
backgroundColor: Colors.transparent,
|
content: Text(
|
||||||
builder: (context) {
|
'${contact.displayName} updated successfully!'),
|
||||||
return ContactModal(
|
),
|
||||||
contact: contact,
|
);
|
||||||
onEdit: () async {
|
} else {
|
||||||
if (await FlutterContacts.requestPermission()) {
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
final updatedContact = await FlutterContacts
|
SnackBar(
|
||||||
.openExternalEdit(contact.id);
|
content: Text(
|
||||||
if (updatedContact != null) {
|
'Edit canceled or failed.'),
|
||||||
_fetchContacts();
|
),
|
||||||
Navigator.of(context).pop();
|
);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(
|
|
||||||
'${contact.displayName} updated successfully!'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text('Edit canceled or failed.'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
onToggleFavorite: () => _toggleFavorite(contact),
|
},
|
||||||
isFavorite: contact.isStarred,
|
onToggleFavorite: () => _toggleFavorite(contact),
|
||||||
);
|
isFavorite: contact.isStarred,
|
||||||
},
|
);
|
||||||
);
|
},
|
||||||
},
|
);
|
||||||
);
|
},
|
||||||
}).toList();
|
);
|
||||||
},
|
}).toList();
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// 3-dot menu
|
||||||
PopupMenuButton<String>(
|
PopupMenuButton<String>(
|
||||||
icon: const Icon(Icons.more_vert, color: Colors.white),
|
icon: const Icon(Icons.more_vert, color: Colors.white),
|
||||||
itemBuilder: (BuildContext context) => [
|
itemBuilder: (BuildContext context) => [
|
||||||
@ -265,6 +262,7 @@ class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateM
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// Main content with TabBarView
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
@ -333,10 +331,3 @@ class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateM
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyHomePage extends StatefulWidget {
|
|
||||||
const MyHomePage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
_MyHomePageState createState() => _MyHomePageState();
|
|
||||||
}
|
|
10
dialer/lib/widgets/loading_indicator.dart
Normal file
10
dialer/lib/widgets/loading_indicator.dart
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class LoadingIndicatorWidget extends StatelessWidget {
|
||||||
|
const LoadingIndicatorWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
}
|
@ -1,79 +0,0 @@
|
|||||||
# client_state.py
|
|
||||||
from queue import Queue
|
|
||||||
from session import NoiseXKSession
|
|
||||||
import time
|
|
||||||
|
|
||||||
class ClientState:
|
|
||||||
def __init__(self, client_id):
|
|
||||||
self.client_id = client_id
|
|
||||||
self.command_queue = Queue()
|
|
||||||
self.initiator = None
|
|
||||||
self.keypair = None
|
|
||||||
self.peer_pubkey = None
|
|
||||||
self.session = None
|
|
||||||
self.handshake_in_progress = False
|
|
||||||
self.handshake_start_time = None
|
|
||||||
self.call_active = False
|
|
||||||
|
|
||||||
def process_command(self, client):
|
|
||||||
"""Process commands from the queue."""
|
|
||||||
if not self.command_queue.empty():
|
|
||||||
print(f"Client {self.client_id} processing command queue, size: {self.command_queue.qsize()}")
|
|
||||||
command = self.command_queue.get()
|
|
||||||
if command == "handshake":
|
|
||||||
try:
|
|
||||||
print(f"Client {self.client_id} starting handshake, initiator: {self.initiator}")
|
|
||||||
self.session = NoiseXKSession(self.keypair, self.peer_pubkey)
|
|
||||||
self.session.handshake(client.sock, self.initiator)
|
|
||||||
print(f"Client {self.client_id} handshake complete")
|
|
||||||
client.send("HANDSHAKE_DONE")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Client {self.client_id} handshake failed: {e}")
|
|
||||||
client.state_changed.emit("CALL_END", "", self.client_id)
|
|
||||||
finally:
|
|
||||||
self.handshake_in_progress = False
|
|
||||||
self.handshake_start_time = None
|
|
||||||
|
|
||||||
def start_handshake(self, initiator, keypair, peer_pubkey):
|
|
||||||
"""Queue handshake command."""
|
|
||||||
self.initiator = initiator
|
|
||||||
self.keypair = keypair
|
|
||||||
self.peer_pubkey = peer_pubkey
|
|
||||||
print(f"Client {self.client_id} queuing handshake, initiator: {initiator}")
|
|
||||||
self.handshake_in_progress = True
|
|
||||||
self.handshake_start_time = time.time()
|
|
||||||
self.command_queue.put("handshake")
|
|
||||||
|
|
||||||
def handle_data(self, client, data):
|
|
||||||
"""Handle received data (control or audio)."""
|
|
||||||
try:
|
|
||||||
decoded_data = data.decode('utf-8').strip()
|
|
||||||
print(f"Client {self.client_id} received raw: {decoded_data}")
|
|
||||||
if decoded_data in ["RINGING", "CALL_END", "CALL_DROPPED", "IN_CALL", "HANDSHAKE", "HANDSHAKE_DONE"]:
|
|
||||||
client.state_changed.emit(decoded_data, decoded_data, self.client_id)
|
|
||||||
if decoded_data == "HANDSHAKE":
|
|
||||||
self.handshake_in_progress = True
|
|
||||||
elif decoded_data == "HANDSHAKE_DONE":
|
|
||||||
self.call_active = True
|
|
||||||
else:
|
|
||||||
print(f"Client {self.client_id} ignored unexpected text message: {decoded_data}")
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
if self.call_active and self.session:
|
|
||||||
try:
|
|
||||||
print(f"Client {self.client_id} received audio packet, length={len(data)}")
|
|
||||||
decrypted_data = self.session.decrypt(data)
|
|
||||||
print(f"Client {self.client_id} decrypted audio packet, length={len(decrypted_data)}")
|
|
||||||
client.data_received.emit(decrypted_data, self.client_id)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Client {self.client_id} failed to process audio packet: {e}")
|
|
||||||
else:
|
|
||||||
print(f"Client {self.client_id} ignored non-text message: {data.hex()}")
|
|
||||||
|
|
||||||
def check_handshake_timeout(self, client):
|
|
||||||
"""Check for handshake timeout."""
|
|
||||||
if self.handshake_in_progress and self.handshake_start_time:
|
|
||||||
if time.time() - self.handshake_start_time > 30:
|
|
||||||
print(f"Client {self.client_id} handshake timeout after 30s")
|
|
||||||
client.state_changed.emit("CALL_END", "", self.client_id)
|
|
||||||
self.handshake_in_progress = False
|
|
||||||
self.handshake_start_time = None
|
|
@ -1,215 +0,0 @@
|
|||||||
import sys
|
|
||||||
from PyQt5.QtWidgets import (
|
|
||||||
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
|
||||||
QPushButton, QLabel, QFrame, QSizePolicy, QStyle
|
|
||||||
)
|
|
||||||
from PyQt5.QtCore import Qt, QSize
|
|
||||||
from PyQt5.QtGui import QFont
|
|
||||||
from phone_manager import PhoneManager
|
|
||||||
from waveform_widget import WaveformWidget
|
|
||||||
from phone_state import PhoneState
|
|
||||||
|
|
||||||
class PhoneUI(QMainWindow):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.setWindowTitle("Enhanced Dual Phone Interface")
|
|
||||||
self.setGeometry(100, 100, 900, 750)
|
|
||||||
self.setStyleSheet("""
|
|
||||||
QMainWindow { background-color: #333333; }
|
|
||||||
QLabel { color: #E0E0E0; font-size: 14px; }
|
|
||||||
QPushButton {
|
|
||||||
background-color: #0078D4; color: white; border: none;
|
|
||||||
padding: 10px 15px; border-radius: 5px; font-size: 14px;
|
|
||||||
min-height: 30px;
|
|
||||||
}
|
|
||||||
QPushButton:hover { background-color: #005A9E; }
|
|
||||||
QPushButton:pressed { background-color: #003C6B; }
|
|
||||||
QPushButton#settingsButton { background-color: #555555; }
|
|
||||||
QPushButton#settingsButton:hover { background-color: #777777; }
|
|
||||||
QFrame#phoneDisplay {
|
|
||||||
background-color: #1E1E1E; border: 2px solid #0078D4;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
QLabel#phoneTitleLabel {
|
|
||||||
font-size: 18px; font-weight: bold; padding-bottom: 5px;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
QLabel#mainTitleLabel {
|
|
||||||
font-size: 24px; font-weight: bold; color: #00A2E8;
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
QWidget#phoneWidget {
|
|
||||||
border: 1px solid #4A4A4A; border-radius: 8px;
|
|
||||||
padding: 10px; background-color: #3A3A3A;
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
|
|
||||||
self.manager = PhoneManager()
|
|
||||||
self.manager.initialize_phones()
|
|
||||||
|
|
||||||
# Main widget and layout
|
|
||||||
main_widget = QWidget()
|
|
||||||
self.setCentralWidget(main_widget)
|
|
||||||
main_layout = QVBoxLayout()
|
|
||||||
main_layout.setSpacing(20)
|
|
||||||
main_layout.setContentsMargins(20, 20, 20, 20)
|
|
||||||
main_layout.setAlignment(Qt.AlignCenter)
|
|
||||||
main_widget.setLayout(main_layout)
|
|
||||||
|
|
||||||
# App Title
|
|
||||||
app_title_label = QLabel("Dual Phone Control Panel")
|
|
||||||
app_title_label.setObjectName("mainTitleLabel")
|
|
||||||
app_title_label.setAlignment(Qt.AlignCenter)
|
|
||||||
main_layout.addWidget(app_title_label)
|
|
||||||
|
|
||||||
# Phone displays layout
|
|
||||||
phone_controls_layout = QHBoxLayout()
|
|
||||||
phone_controls_layout.setSpacing(50)
|
|
||||||
phone_controls_layout.setAlignment(Qt.AlignCenter)
|
|
||||||
main_layout.addLayout(phone_controls_layout)
|
|
||||||
|
|
||||||
# Setup UI for phones
|
|
||||||
for phone in self.manager.phones:
|
|
||||||
phone_container_widget, phone_display_frame, phone_button, waveform_widget, sent_waveform_widget, phone_status_label = self._create_phone_ui(
|
|
||||||
f"Phone {phone['id']+1}", lambda checked, pid=phone['id']: self.manager.phone_action(pid, self)
|
|
||||||
)
|
|
||||||
phone['button'] = phone_button
|
|
||||||
phone['waveform'] = waveform_widget
|
|
||||||
phone['sent_waveform'] = sent_waveform_widget
|
|
||||||
phone['status_label'] = phone_status_label
|
|
||||||
phone_controls_layout.addWidget(phone_container_widget)
|
|
||||||
phone['client'].data_received.connect(lambda data, cid=phone['id']: self.manager.update_waveform(cid, data))
|
|
||||||
phone['client'].state_changed.connect(lambda state, num, cid=phone['id']: self.set_phone_state(cid, state, num))
|
|
||||||
phone['client'].start()
|
|
||||||
|
|
||||||
# Spacer
|
|
||||||
main_layout.addStretch(1)
|
|
||||||
|
|
||||||
# Settings Button
|
|
||||||
self.settings_button = QPushButton("Settings")
|
|
||||||
self.settings_button.setObjectName("settingsButton")
|
|
||||||
self.settings_button.setFixedWidth(180)
|
|
||||||
self.settings_button.setIcon(self.style().standardIcon(QStyle.SP_FileDialogDetailedView))
|
|
||||||
self.settings_button.setIconSize(QSize(20, 20))
|
|
||||||
self.settings_button.clicked.connect(self.settings_action)
|
|
||||||
settings_layout = QHBoxLayout()
|
|
||||||
settings_layout.addStretch()
|
|
||||||
settings_layout.addWidget(self.settings_button)
|
|
||||||
settings_layout.addStretch()
|
|
||||||
main_layout.addLayout(settings_layout)
|
|
||||||
|
|
||||||
# Initialize UI
|
|
||||||
for phone in self.manager.phones:
|
|
||||||
self.update_phone_ui(phone['id'])
|
|
||||||
|
|
||||||
def _create_phone_ui(self, title, action_slot):
|
|
||||||
phone_container_widget = QWidget()
|
|
||||||
phone_container_widget.setObjectName("phoneWidget")
|
|
||||||
phone_layout = QVBoxLayout()
|
|
||||||
phone_layout.setAlignment(Qt.AlignCenter)
|
|
||||||
phone_layout.setSpacing(15)
|
|
||||||
phone_container_widget.setLayout(phone_layout)
|
|
||||||
|
|
||||||
phone_title_label = QLabel(title)
|
|
||||||
phone_title_label.setObjectName("phoneTitleLabel")
|
|
||||||
phone_title_label.setAlignment(Qt.AlignCenter)
|
|
||||||
phone_layout.addWidget(phone_title_label)
|
|
||||||
|
|
||||||
phone_display_frame = QFrame()
|
|
||||||
phone_display_frame.setObjectName("phoneDisplay")
|
|
||||||
phone_display_frame.setFixedSize(250, 350)
|
|
||||||
phone_display_frame.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
|
||||||
|
|
||||||
display_content_layout = QVBoxLayout(phone_display_frame)
|
|
||||||
display_content_layout.setAlignment(Qt.AlignCenter)
|
|
||||||
phone_status_label = QLabel("Idle")
|
|
||||||
phone_status_label.setAlignment(Qt.AlignCenter)
|
|
||||||
phone_status_label.setFont(QFont("Arial", 16))
|
|
||||||
display_content_layout.addWidget(phone_status_label)
|
|
||||||
phone_layout.addWidget(phone_display_frame, alignment=Qt.AlignCenter)
|
|
||||||
|
|
||||||
phone_button = QPushButton()
|
|
||||||
phone_button.setFixedWidth(120)
|
|
||||||
phone_button.setIconSize(QSize(20, 20))
|
|
||||||
phone_button.clicked.connect(action_slot)
|
|
||||||
phone_layout.addWidget(phone_button, alignment=Qt.AlignCenter)
|
|
||||||
|
|
||||||
# Received waveform
|
|
||||||
waveform_label = QLabel(f"{title} Received Audio")
|
|
||||||
waveform_label.setAlignment(Qt.AlignCenter)
|
|
||||||
waveform_label.setStyleSheet("font-size: 14px; color: #E0E0E0;")
|
|
||||||
phone_layout.addWidget(waveform_label)
|
|
||||||
waveform_widget = WaveformWidget(dynamic=False)
|
|
||||||
phone_layout.addWidget(waveform_widget, alignment=Qt.AlignCenter)
|
|
||||||
|
|
||||||
# Sent waveform
|
|
||||||
sent_waveform_label = QLabel(f"{title} Sent Audio")
|
|
||||||
sent_waveform_label.setAlignment(Qt.AlignCenter)
|
|
||||||
sent_waveform_label.setStyleSheet("font-size: 14px; color: #E0E0E0;")
|
|
||||||
phone_layout.addWidget(sent_waveform_label)
|
|
||||||
sent_waveform_widget = WaveformWidget(dynamic=False)
|
|
||||||
phone_layout.addWidget(sent_waveform_widget, alignment=Qt.AlignCenter)
|
|
||||||
|
|
||||||
return phone_container_widget, phone_display_frame, phone_button, waveform_widget, sent_waveform_widget, phone_status_label
|
|
||||||
|
|
||||||
def update_phone_ui(self, phone_id):
|
|
||||||
phone = self.manager.phones[phone_id]
|
|
||||||
other_phone = self.manager.phones[1 - phone_id]
|
|
||||||
state = phone['state']
|
|
||||||
phone_number = other_phone['number'] if state != PhoneState.IDLE else ""
|
|
||||||
button = phone['button']
|
|
||||||
status_label = phone['status_label']
|
|
||||||
|
|
||||||
if state == PhoneState.IDLE:
|
|
||||||
button.setText("Call")
|
|
||||||
button.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
|
|
||||||
status_label.setText("Idle")
|
|
||||||
button.setStyleSheet("background-color: #0078D4;")
|
|
||||||
elif state == PhoneState.CALLING:
|
|
||||||
button.setText("Cancel")
|
|
||||||
button.setIcon(self.style().standardIcon(QStyle.SP_MediaStop))
|
|
||||||
status_label.setText(f"Calling {phone_number}...")
|
|
||||||
button.setStyleSheet("background-color: #E81123;")
|
|
||||||
elif state == PhoneState.IN_CALL:
|
|
||||||
button.setText("Hang Up")
|
|
||||||
button.setIcon(self.style().standardIcon(QStyle.SP_DialogCancelButton))
|
|
||||||
status_label.setText(f"In Call with {phone_number}")
|
|
||||||
button.setStyleSheet("background-color: #E81123;")
|
|
||||||
elif state == PhoneState.RINGING:
|
|
||||||
button.setText("Answer")
|
|
||||||
button.setIcon(self.style().standardIcon(QStyle.SP_DialogApplyButton))
|
|
||||||
status_label.setText(f"Incoming Call from {phone_number}")
|
|
||||||
button.setStyleSheet("background-color: #107C10;")
|
|
||||||
|
|
||||||
def set_phone_state(self, client_id, state_str, number):
|
|
||||||
state = self.manager.map_state(state_str)
|
|
||||||
phone = self.manager.phones[client_id]
|
|
||||||
other_phone = self.manager.phones[1 - client_id]
|
|
||||||
print(f"Setting state for Phone {client_id + 1}: {state}, number: {number}, is_initiator: {phone['is_initiator']}")
|
|
||||||
phone['state'] = state
|
|
||||||
if state == PhoneState.IN_CALL:
|
|
||||||
print(f"Phone {client_id + 1} confirmed in IN_CALL state")
|
|
||||||
if number == "IN_CALL" and phone['is_initiator']:
|
|
||||||
print(f"Phone {client_id + 1} (initiator) starting handshake")
|
|
||||||
phone['client'].send("HANDSHAKE")
|
|
||||||
phone['client'].start_handshake(initiator=True, keypair=phone['keypair'], peer_pubkey=other_phone['public_key'])
|
|
||||||
elif number == "HANDSHAKE" and not phone['is_initiator']:
|
|
||||||
print(f"Phone {client_id + 1} (responder) starting handshake")
|
|
||||||
phone['client'].start_handshake(initiator=False, keypair=phone['keypair'], peer_pubkey=other_phone['public_key'])
|
|
||||||
elif number == "HANDSHAKE_DONE":
|
|
||||||
self.manager.start_audio(client_id, parent=self) # Pass self as parent
|
|
||||||
self.update_phone_ui(client_id)
|
|
||||||
|
|
||||||
def settings_action(self):
|
|
||||||
print("Settings clicked")
|
|
||||||
|
|
||||||
def closeEvent(self, event):
|
|
||||||
for phone in self.manager.phones:
|
|
||||||
phone['client'].stop()
|
|
||||||
event.accept()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app = QApplication(sys.argv)
|
|
||||||
window = PhoneUI()
|
|
||||||
window.show()
|
|
||||||
sys.exit(app.exec_())
|
|
@ -1,110 +0,0 @@
|
|||||||
import socket
|
|
||||||
import time
|
|
||||||
import select
|
|
||||||
from PyQt5.QtCore import QThread, pyqtSignal
|
|
||||||
from client_state import ClientState
|
|
||||||
|
|
||||||
class PhoneClient(QThread):
|
|
||||||
data_received = pyqtSignal(bytes, int)
|
|
||||||
state_changed = pyqtSignal(str, str, int)
|
|
||||||
|
|
||||||
def __init__(self, client_id):
|
|
||||||
super().__init__()
|
|
||||||
self.host = "localhost"
|
|
||||||
self.port = 12345
|
|
||||||
self.client_id = client_id
|
|
||||||
self.sock = None
|
|
||||||
self.running = True
|
|
||||||
self.state = ClientState(client_id)
|
|
||||||
|
|
||||||
def connect_socket(self):
|
|
||||||
retries = 3
|
|
||||||
for attempt in range(retries):
|
|
||||||
try:
|
|
||||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
|
||||||
self.sock.settimeout(120)
|
|
||||||
self.sock.connect((self.host, self.port))
|
|
||||||
print(f"Client {self.client_id} connected to {self.host}:{self.port}")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Client {self.client_id} connection attempt {attempt + 1} failed: {e}")
|
|
||||||
if attempt < retries - 1:
|
|
||||||
time.sleep(1)
|
|
||||||
self.sock = None
|
|
||||||
return False
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
while self.running:
|
|
||||||
if not self.sock:
|
|
||||||
if not self.connect_socket():
|
|
||||||
print(f"Client {self.client_id} failed to connect after retries")
|
|
||||||
self.state_changed.emit("CALL_END", "", self.client_id)
|
|
||||||
break
|
|
||||||
try:
|
|
||||||
while self.running:
|
|
||||||
self.state.process_command(self)
|
|
||||||
self.state.check_handshake_timeout(self)
|
|
||||||
if not self.state.handshake_in_progress:
|
|
||||||
if self.sock is None:
|
|
||||||
print(f"Client {self.client_id} socket is None, exiting inner loop")
|
|
||||||
break
|
|
||||||
readable, _, _ = select.select([self.sock], [], [], 0.01)
|
|
||||||
if readable:
|
|
||||||
try:
|
|
||||||
if self.sock is None:
|
|
||||||
print(f"Client {self.client_id} socket is None before recv, exiting")
|
|
||||||
break
|
|
||||||
data = self.sock.recv(1024)
|
|
||||||
if not data:
|
|
||||||
print(f"Client {self.client_id} disconnected")
|
|
||||||
self.state_changed.emit("CALL_END", "", self.client_id)
|
|
||||||
break
|
|
||||||
self.state.handle_data(self, data)
|
|
||||||
except socket.error as e:
|
|
||||||
print(f"Client {self.client_id} socket error: {e}")
|
|
||||||
self.state_changed.emit("CALL_END", "", self.client_id)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
self.msleep(20)
|
|
||||||
print(f"Client {self.client_id} yielding during handshake")
|
|
||||||
self.msleep(1)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Client {self.client_id} unexpected error in run loop: {e}")
|
|
||||||
self.state_changed.emit("CALL_END", "", self.client_id)
|
|
||||||
break
|
|
||||||
finally:
|
|
||||||
if self.sock:
|
|
||||||
try:
|
|
||||||
self.sock.close()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Client {self.client_id} error closing socket: {e}")
|
|
||||||
self.sock = None
|
|
||||||
|
|
||||||
def send(self, message):
|
|
||||||
if self.sock and self.running:
|
|
||||||
try:
|
|
||||||
if isinstance(message, str):
|
|
||||||
data = message.encode('utf-8')
|
|
||||||
self.sock.send(data)
|
|
||||||
print(f"Client {self.client_id} sent: {message}, length={len(data)}")
|
|
||||||
else:
|
|
||||||
self.sock.send(message)
|
|
||||||
print(f"Client {self.client_id} sent binary data, length={len(message)}")
|
|
||||||
except socket.error as e:
|
|
||||||
print(f"Client {self.client_id} send error: {e}")
|
|
||||||
self.state_changed.emit("CALL_END", "", self.client_id)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.running = False
|
|
||||||
if self.sock:
|
|
||||||
try:
|
|
||||||
self.sock.close()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Client {self.client_id} error closing socket in stop: {e}")
|
|
||||||
self.sock = None
|
|
||||||
self.quit()
|
|
||||||
self.wait(1000)
|
|
||||||
|
|
||||||
def start_handshake(self, initiator, keypair, peer_pubkey):
|
|
||||||
self.state.start_handshake(initiator, keypair, peer_pubkey)
|
|
@ -1,98 +0,0 @@
|
|||||||
import secrets
|
|
||||||
from PyQt5.QtCore import QTimer
|
|
||||||
from phone_client import PhoneClient
|
|
||||||
from session import NoiseXKSession
|
|
||||||
from phone_state import PhoneState # Added import
|
|
||||||
|
|
||||||
class PhoneManager:
|
|
||||||
def __init__(self):
|
|
||||||
self.phones = []
|
|
||||||
self.handshake_done_count = 0
|
|
||||||
|
|
||||||
def initialize_phones(self):
|
|
||||||
for i in range(2):
|
|
||||||
client = PhoneClient(i)
|
|
||||||
keypair = NoiseXKSession.generate_keypair()
|
|
||||||
phone = {
|
|
||||||
'id': i,
|
|
||||||
'client': client,
|
|
||||||
'state': PhoneState.IDLE,
|
|
||||||
'number': "123-4567" if i == 0 else "987-6543",
|
|
||||||
'audio_timer': None,
|
|
||||||
'keypair': keypair,
|
|
||||||
'public_key': keypair.public,
|
|
||||||
'is_initiator': False
|
|
||||||
}
|
|
||||||
self.phones.append(phone)
|
|
||||||
|
|
||||||
self.phones[0]['peer_public_key'] = self.phones[1]['public_key']
|
|
||||||
self.phones[1]['peer_public_key'] = self.phones[0]['public_key']
|
|
||||||
|
|
||||||
def phone_action(self, phone_id, ui_manager):
|
|
||||||
phone = self.phones[phone_id]
|
|
||||||
other_phone = self.phones[1 - phone_id]
|
|
||||||
print(f"Phone {phone_id + 1} Action, current state: {phone['state']}, is_initiator: {phone['is_initiator']}")
|
|
||||||
|
|
||||||
if phone['state'] == PhoneState.IDLE:
|
|
||||||
phone['state'] = PhoneState.CALLING
|
|
||||||
other_phone['state'] = PhoneState.RINGING
|
|
||||||
phone['is_initiator'] = True
|
|
||||||
other_phone['is_initiator'] = False
|
|
||||||
phone['client'].send("RINGING")
|
|
||||||
elif phone['state'] == PhoneState.RINGING:
|
|
||||||
phone['state'] = other_phone['state'] = PhoneState.IN_CALL
|
|
||||||
phone['client'].send("IN_CALL")
|
|
||||||
elif phone['state'] in [PhoneState.IN_CALL, PhoneState.CALLING]:
|
|
||||||
if not phone['client'].state.handshake_in_progress and phone['state'] != PhoneState.CALLING:
|
|
||||||
phone['state'] = other_phone['state'] = PhoneState.IDLE
|
|
||||||
phone['client'].send("CALL_END")
|
|
||||||
for p in [phone, other_phone]:
|
|
||||||
if p['audio_timer']:
|
|
||||||
p['audio_timer'].stop()
|
|
||||||
else:
|
|
||||||
print(f"Phone {phone_id + 1} cannot hang up during handshake or call setup")
|
|
||||||
|
|
||||||
ui_manager.update_phone_ui(phone_id)
|
|
||||||
ui_manager.update_phone_ui(1 - phone_id)
|
|
||||||
|
|
||||||
def send_audio(self, phone_id):
|
|
||||||
phone = self.phones[phone_id]
|
|
||||||
if phone['state'] == PhoneState.IN_CALL and phone['client'].state.session and phone['client'].sock:
|
|
||||||
mock_audio = secrets.token_bytes(16)
|
|
||||||
try:
|
|
||||||
self.update_sent_waveform(phone_id, mock_audio)
|
|
||||||
phone['client'].state.session.send(phone['client'].sock, mock_audio)
|
|
||||||
print(f"Client {phone_id} sent encrypted audio packet, length=32")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Client {phone_id} failed to send audio: {e}")
|
|
||||||
|
|
||||||
def start_audio(self, client_id, parent=None):
|
|
||||||
self.handshake_done_count += 1
|
|
||||||
print(f"HANDSHAKE_DONE received for client {client_id}, count: {self.handshake_done_count}")
|
|
||||||
if self.handshake_done_count == 2:
|
|
||||||
for phone in self.phones:
|
|
||||||
if phone['state'] == PhoneState.IN_CALL:
|
|
||||||
if not phone['audio_timer'] or not phone['audio_timer'].isActive():
|
|
||||||
phone['audio_timer'] = QTimer(parent) # Parent to PhoneUI
|
|
||||||
phone['audio_timer'].timeout.connect(lambda pid=phone['id']: self.send_audio(pid))
|
|
||||||
phone['audio_timer'].start(100)
|
|
||||||
self.handshake_done_count = 0
|
|
||||||
|
|
||||||
def update_waveform(self, client_id, data):
|
|
||||||
self.phones[client_id]['waveform'].set_data(data)
|
|
||||||
|
|
||||||
def update_sent_waveform(self, client_id, data):
|
|
||||||
self.phones[client_id]['sent_waveform'].set_data(data)
|
|
||||||
|
|
||||||
def map_state(self, state_str):
|
|
||||||
if state_str == "RINGING":
|
|
||||||
return PhoneState.RINGING
|
|
||||||
elif state_str in ["CALL_END", "CALL_DROPPED"]:
|
|
||||||
return PhoneState.IDLE
|
|
||||||
elif state_str == "IN_CALL":
|
|
||||||
return PhoneState.IN_CALL
|
|
||||||
elif state_str == "HANDSHAKE":
|
|
||||||
return PhoneState.IN_CALL
|
|
||||||
elif state_str == "HANDSHAKE_DONE":
|
|
||||||
return PhoneState.IN_CALL
|
|
||||||
return PhoneState.IDLE
|
|
@ -1,5 +0,0 @@
|
|||||||
class PhoneState:
|
|
||||||
IDLE = 0
|
|
||||||
CALLING = 1
|
|
||||||
IN_CALL = 2
|
|
||||||
RINGING = 3
|
|
@ -1,196 +0,0 @@
|
|||||||
import socket
|
|
||||||
import logging
|
|
||||||
from dissononce.processing.impl.handshakestate import HandshakeState
|
|
||||||
from dissononce.processing.impl.symmetricstate import SymmetricState
|
|
||||||
from dissononce.processing.impl.cipherstate import CipherState
|
|
||||||
from dissononce.processing.handshakepatterns.interactive.XK import XKHandshakePattern
|
|
||||||
from dissononce.cipher.chachapoly import ChaChaPolyCipher
|
|
||||||
from dissononce.dh.x25519.x25519 import X25519DH
|
|
||||||
from dissononce.dh.keypair import KeyPair
|
|
||||||
from dissononce.dh.x25519.public import PublicKey
|
|
||||||
from dissononce.hash.sha256 import SHA256Hash
|
|
||||||
|
|
||||||
# Configure root logger for debug output
|
|
||||||
logging.basicConfig(level=logging.DEBUG, format="%(message)s")
|
|
||||||
|
|
||||||
class NoiseXKSession:
|
|
||||||
@staticmethod
|
|
||||||
def generate_keypair() -> KeyPair:
|
|
||||||
"""
|
|
||||||
Generate a static X25519 KeyPair.
|
|
||||||
Returns:
|
|
||||||
KeyPair object with .private and .public attributes.
|
|
||||||
"""
|
|
||||||
return X25519DH().generate_keypair()
|
|
||||||
|
|
||||||
def __init__(self, local_kp: KeyPair, peer_pubkey: PublicKey):
|
|
||||||
"""
|
|
||||||
Initialize with our KeyPair and the peer's PublicKey.
|
|
||||||
"""
|
|
||||||
self.local_kp: KeyPair = local_kp
|
|
||||||
self.peer_pubkey: PublicKey = peer_pubkey
|
|
||||||
|
|
||||||
# Build the Noise handshake state (X25519 DH, ChaChaPoly cipher, SHA256 hash)
|
|
||||||
cipher = ChaChaPolyCipher()
|
|
||||||
dh = X25519DH()
|
|
||||||
hshash = SHA256Hash()
|
|
||||||
symmetric = SymmetricState(CipherState(cipher), hshash)
|
|
||||||
self._hs = HandshakeState(symmetric, dh)
|
|
||||||
|
|
||||||
self._send_cs = None # type: CipherState
|
|
||||||
self._recv_cs = None
|
|
||||||
|
|
||||||
def handshake(self, sock: socket.socket, initiator: bool) -> None:
|
|
||||||
"""
|
|
||||||
Perform the XK handshake over the socket. Branches on initiator/responder
|
|
||||||
so that each side reads or writes in the correct message order.
|
|
||||||
On completion, self._send_cs and self._recv_cs hold the two CipherStates.
|
|
||||||
"""
|
|
||||||
logging.debug(f"[handshake] start (initiator={initiator})")
|
|
||||||
# initialize with our KeyPair and their PublicKey
|
|
||||||
if initiator:
|
|
||||||
# initiator knows peer’s static out-of-band
|
|
||||||
self._hs.initialize(
|
|
||||||
XKHandshakePattern(),
|
|
||||||
True,
|
|
||||||
b'',
|
|
||||||
s=self.local_kp,
|
|
||||||
rs=self.peer_pubkey
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logging.debug("[handshake] responder initializing without rs")
|
|
||||||
# responder must NOT supply rs here
|
|
||||||
self._hs.initialize(
|
|
||||||
XKHandshakePattern(),
|
|
||||||
False,
|
|
||||||
b'',
|
|
||||||
s=self.local_kp
|
|
||||||
)
|
|
||||||
|
|
||||||
cs_pair = None
|
|
||||||
if initiator:
|
|
||||||
# 1) -> e
|
|
||||||
buf1 = bytearray()
|
|
||||||
cs_pair = self._hs.write_message(b'', buf1)
|
|
||||||
logging.debug(f"[-> e] {buf1.hex()}")
|
|
||||||
self._send_all(sock, buf1)
|
|
||||||
|
|
||||||
# 2) <- e, es, s, ss
|
|
||||||
msg2 = self._recv_all(sock)
|
|
||||||
logging.debug(f"[<- msg2] {msg2.hex()}")
|
|
||||||
self._hs.read_message(msg2, bytearray())
|
|
||||||
|
|
||||||
# 3) -> se (final)
|
|
||||||
buf3 = bytearray()
|
|
||||||
cs_pair = self._hs.write_message(b'', buf3)
|
|
||||||
logging.debug(f"[-> se] {buf3.hex()}")
|
|
||||||
self._send_all(sock, buf3)
|
|
||||||
else:
|
|
||||||
# 1) <- e
|
|
||||||
msg1 = self._recv_all(sock)
|
|
||||||
logging.debug(f"[<- e] {msg1.hex()}")
|
|
||||||
self._hs.read_message(msg1, bytearray())
|
|
||||||
|
|
||||||
# 2) -> e, es, s, ss
|
|
||||||
buf2 = bytearray()
|
|
||||||
cs_pair = self._hs.write_message(b'', buf2)
|
|
||||||
logging.debug(f"[-> msg2] {buf2.hex()}")
|
|
||||||
self._send_all(sock, buf2)
|
|
||||||
|
|
||||||
# 3) <- se (final)
|
|
||||||
msg3 = self._recv_all(sock)
|
|
||||||
logging.debug(f"[<- se] {msg3.hex()}")
|
|
||||||
cs_pair = self._hs.read_message(msg3, bytearray())
|
|
||||||
|
|
||||||
# on the final step, we must get exactly two CipherStates
|
|
||||||
if not cs_pair or len(cs_pair) != 2:
|
|
||||||
raise RuntimeError("Handshake did not complete properly")
|
|
||||||
cs0, cs1 = cs_pair
|
|
||||||
# the library returns (cs_encrypt_for_initiator, cs_decrypt_for_initiator)
|
|
||||||
if initiator:
|
|
||||||
# initiator: cs0 encrypts, cs1 decrypts
|
|
||||||
self._send_cs, self._recv_cs = cs0, cs1
|
|
||||||
else:
|
|
||||||
# responder must swap
|
|
||||||
self._send_cs, self._recv_cs = cs1, cs0
|
|
||||||
|
|
||||||
# dump the raw symmetric keys & nonces (if available)
|
|
||||||
self._dump_cipherstate("HANDSHAKE→ SEND", self._send_cs)
|
|
||||||
self._dump_cipherstate("HANDSHAKE→ RECV", self._recv_cs)
|
|
||||||
|
|
||||||
def send(self, sock: socket.socket, plaintext: bytes) -> None:
|
|
||||||
"""
|
|
||||||
Encrypt and send a message.
|
|
||||||
"""
|
|
||||||
if self._send_cs is None:
|
|
||||||
raise RuntimeError("Handshake not complete")
|
|
||||||
ct = self._send_cs.encrypt_with_ad(b'', plaintext)
|
|
||||||
logging.debug(f"[ENCRYPT] {ct.hex()}")
|
|
||||||
# self._dump_cipherstate("SEND→ after encrypt", self._send_cs)
|
|
||||||
self._send_all(sock, ct)
|
|
||||||
|
|
||||||
def receive(self, sock: socket.socket) -> bytes:
|
|
||||||
"""
|
|
||||||
Receive and decrypt a message.
|
|
||||||
"""
|
|
||||||
if self._recv_cs is None:
|
|
||||||
raise RuntimeError("Handshake not complete")
|
|
||||||
ct = self._recv_all(sock)
|
|
||||||
logging.debug(f"[CIPHERTEXT] {ct.hex()}")
|
|
||||||
# self._dump_cipherstate("RECV→ before decrypt", self._recv_cs)
|
|
||||||
pt = self._recv_cs.decrypt_with_ad(b'', ct)
|
|
||||||
logging.debug(f"[DECRYPT] {pt!r}")
|
|
||||||
return pt
|
|
||||||
|
|
||||||
def decrypt(self, ciphertext: bytes) -> bytes:
|
|
||||||
"""
|
|
||||||
Decrypt a ciphertext received as bytes.
|
|
||||||
"""
|
|
||||||
if self._recv_cs is None:
|
|
||||||
raise RuntimeError("Handshake not complete")
|
|
||||||
# Remove 2-byte length prefix if present
|
|
||||||
if len(ciphertext) >= 2 and int.from_bytes(ciphertext[:2], 'big') == len(ciphertext) - 2:
|
|
||||||
logging.debug(f"[DECRYPT] Stripping 2-byte length prefix from {len(ciphertext)}-byte input")
|
|
||||||
ciphertext = ciphertext[2:]
|
|
||||||
logging.debug(f"[CIPHERTEXT] {ciphertext.hex()}")
|
|
||||||
# self._dump_cipherstate("DECRYPT→ before decrypt", self._recv_cs)
|
|
||||||
pt = self._recv_cs.decrypt_with_ad(b'', ciphertext)
|
|
||||||
logging.debug(f"[DECRYPT] {pt!r}")
|
|
||||||
return pt
|
|
||||||
|
|
||||||
def _send_all(self, sock: socket.socket, data: bytes) -> None:
|
|
||||||
# Length-prefix (2 bytes big-endian) + data
|
|
||||||
length = len(data).to_bytes(2, 'big')
|
|
||||||
logging.debug(f"[SEND] length={length.hex()}, data={data.hex()}")
|
|
||||||
sock.sendall(length + data)
|
|
||||||
|
|
||||||
def _recv_all(self, sock: socket.socket) -> bytes:
|
|
||||||
# Read 2-byte length prefix, then the payload
|
|
||||||
hdr = self._read_exact(sock, 2)
|
|
||||||
length = int.from_bytes(hdr, 'big')
|
|
||||||
logging.debug(f"[RECV] length={length} ({hdr.hex()})")
|
|
||||||
data = self._read_exact(sock, length)
|
|
||||||
logging.debug(f"[RECV] data={data.hex()}")
|
|
||||||
return data
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _read_exact(sock: socket.socket, n: int) -> bytes:
|
|
||||||
buf = bytearray()
|
|
||||||
while len(buf) < n:
|
|
||||||
chunk = sock.recv(n - len(buf))
|
|
||||||
if not chunk:
|
|
||||||
raise ConnectionError("Socket closed during read")
|
|
||||||
buf.extend(chunk)
|
|
||||||
return bytes(buf)
|
|
||||||
|
|
||||||
def _dump_cipherstate(self, label: str, cs: CipherState) -> None:
|
|
||||||
"""
|
|
||||||
Print the symmetric key (cs._k) and nonce counter (cs._n) for inspection.
|
|
||||||
"""
|
|
||||||
key = cs._key
|
|
||||||
nonce = getattr(cs, "_n", None)
|
|
||||||
if isinstance(key, (bytes, bytearray)):
|
|
||||||
key_hex = key.hex()
|
|
||||||
else:
|
|
||||||
key_hex = repr(key)
|
|
||||||
logging.debug(f"[{label}] key={key_hex}")
|
|
@ -1,46 +0,0 @@
|
|||||||
import random
|
|
||||||
from PyQt5.QtWidgets import QWidget
|
|
||||||
from PyQt5.QtCore import QTimer, QSize, QPointF
|
|
||||||
from PyQt5.QtGui import QPainter, QColor, QPen, QLinearGradient, QBrush
|
|
||||||
|
|
||||||
class WaveformWidget(QWidget):
|
|
||||||
def __init__(self, parent=None, dynamic=False):
|
|
||||||
super().__init__(parent)
|
|
||||||
self.dynamic = dynamic
|
|
||||||
self.setMinimumSize(200, 80)
|
|
||||||
self.setMaximumHeight(100)
|
|
||||||
self.waveform_data = [random.randint(10, 90) for _ in range(50)]
|
|
||||||
if self.dynamic:
|
|
||||||
self.timer = QTimer(self)
|
|
||||||
self.timer.timeout.connect(self.update_waveform)
|
|
||||||
self.timer.start(100)
|
|
||||||
|
|
||||||
def update_waveform(self):
|
|
||||||
self.waveform_data = self.waveform_data[1:] + [random.randint(10, 90)]
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
def set_data(self, data):
|
|
||||||
amplitude = sum(byte for byte in data) % 90 + 10
|
|
||||||
self.waveform_data = self.waveform_data[1:] + [amplitude]
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
def paintEvent(self, event):
|
|
||||||
painter = QPainter(self)
|
|
||||||
painter.setRenderHint(QPainter.Antialiasing)
|
|
||||||
painter.fillRect(self.rect(), QColor("#2D2D2D"))
|
|
||||||
gradient = QLinearGradient(0, 0, 0, self.height())
|
|
||||||
gradient.setColorAt(0.0, QColor("#0078D4"))
|
|
||||||
gradient.setColorAt(1.0, QColor("#50E6A4"))
|
|
||||||
pen = QPen(QBrush(gradient), 2)
|
|
||||||
painter.setPen(pen)
|
|
||||||
bar_width = self.width() / len(self.waveform_data)
|
|
||||||
max_h = self.height() - 10
|
|
||||||
for i, val in enumerate(self.waveform_data):
|
|
||||||
bar_height = (val / 100.0) * max_h
|
|
||||||
x = i * bar_width
|
|
||||||
y = (self.height() - bar_height) / 2
|
|
||||||
painter.drawLine(QPointF(x + bar_width / 2, y), QPointF(x + bar_width / 2, y + bar_height))
|
|
||||||
|
|
||||||
def resizeEvent(self, event):
|
|
||||||
super().resizeEvent(event)
|
|
||||||
self.update()
|
|
@ -1,13 +0,0 @@
|
|||||||
simulator/
|
|
||||||
├── gsm_simulator.py # gsm_simulator
|
|
||||||
├── launch_gsm_simulator.sh # use to start docker and simulator, run in terminal
|
|
||||||
|
|
||||||
2 clients nect to gsm_simulator and simulate a call using noise protocol
|
|
||||||
UI/
|
|
||||||
├── main.py # UI setup and event handling
|
|
||||||
├── phone_manager.py # Phone state, client init, audio logic
|
|
||||||
├── phone_client.py # Socket communication and threading
|
|
||||||
├── client_state.py # Client state and command processing
|
|
||||||
├── session.py # Noise XK crypto session
|
|
||||||
├── waveform_widget.py # Waveform UI component
|
|
||||||
├── phone_state.py # State constants
|
|
@ -1,14 +0,0 @@
|
|||||||
# Use official Python image
|
|
||||||
FROM python:3.9-slim
|
|
||||||
|
|
||||||
# Set working directory
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy the simulator script
|
|
||||||
COPY gsm_simulator.py .
|
|
||||||
|
|
||||||
# Expose the port
|
|
||||||
EXPOSE 12345
|
|
||||||
|
|
||||||
# Run the simulator
|
|
||||||
CMD ["python", "gsm_simulator.py"]
|
|
@ -1,85 +0,0 @@
|
|||||||
import socket
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
HOST = "0.0.0.0"
|
|
||||||
PORT = 12345
|
|
||||||
FRAME_SIZE = 1000
|
|
||||||
FRAME_DELAY = 0.02
|
|
||||||
|
|
||||||
clients = []
|
|
||||||
clients_lock = threading.Lock()
|
|
||||||
|
|
||||||
def handle_client(client_sock, client_id):
|
|
||||||
print(f"Starting handle_client for Client {client_id}")
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
other_client = None
|
|
||||||
with clients_lock:
|
|
||||||
if len(clients) == 2 and client_id < len(clients):
|
|
||||||
other_client = clients[1 - client_id]
|
|
||||||
print(f"Client {client_id} waiting for data, other_client exists: {other_client is not None}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = client_sock.recv(1024)
|
|
||||||
if not data:
|
|
||||||
print(f"Client {client_id} disconnected or no data received")
|
|
||||||
break
|
|
||||||
if other_client:
|
|
||||||
for i in range(0, len(data), FRAME_SIZE):
|
|
||||||
frame = data[i:i + FRAME_SIZE]
|
|
||||||
other_client.send(frame)
|
|
||||||
time.sleep(FRAME_DELAY)
|
|
||||||
print(f"Forwarded {len(data)} bytes from Client {client_id} to Client {1 - client_id}")
|
|
||||||
except socket.error as e:
|
|
||||||
print(f"Socket error with Client {client_id}: {e}")
|
|
||||||
break
|
|
||||||
finally:
|
|
||||||
print(f"Closing connection for Client {client_id}")
|
|
||||||
with clients_lock:
|
|
||||||
if client_id < len(clients) and clients[client_id] == client_sock:
|
|
||||||
clients.pop(client_id)
|
|
||||||
print(f"Removed Client {client_id} from clients list")
|
|
||||||
client_sock.close()
|
|
||||||
|
|
||||||
def start_simulator():
|
|
||||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
||||||
server.bind((HOST, PORT))
|
|
||||||
server.listen(2)
|
|
||||||
print(f"GSM Simulator listening on {HOST}:{PORT}...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
client_sock, addr = server.accept()
|
|
||||||
client_sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) # Keep connection alive
|
|
||||||
with clients_lock:
|
|
||||||
if len(clients) < 2:
|
|
||||||
clients.append(client_sock)
|
|
||||||
client_id = len(clients) - 1
|
|
||||||
else:
|
|
||||||
# Replace a closed socket or reject if both slots are active
|
|
||||||
replaced = False
|
|
||||||
for i in range(len(clients)):
|
|
||||||
if clients[i].fileno() == -1: # Check if socket is closed
|
|
||||||
clients[i] = client_sock
|
|
||||||
client_id = i
|
|
||||||
replaced = True
|
|
||||||
break
|
|
||||||
if not replaced:
|
|
||||||
print(f"Rejecting new client from {addr}: max clients (2) reached")
|
|
||||||
client_sock.close()
|
|
||||||
continue
|
|
||||||
print(f"Client {client_id} connected from {addr}")
|
|
||||||
threading.Thread(target=handle_client, args=(client_sock, client_id), daemon=True).start()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Shutting down simulator...")
|
|
||||||
finally:
|
|
||||||
with clients_lock:
|
|
||||||
for client in clients:
|
|
||||||
client.close()
|
|
||||||
clients.clear()
|
|
||||||
server.close()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
start_simulator()
|
|
@ -1,68 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Script to launch the GSM Simulator in Docker
|
|
||||||
|
|
||||||
# Variables
|
|
||||||
IMAGE_NAME="gsm-simulator"
|
|
||||||
CONTAINER_NAME="gsm-sim"
|
|
||||||
PORT="12345"
|
|
||||||
LOG_FILE="gsm_simulator.log"
|
|
||||||
|
|
||||||
# Check if Docker is installed
|
|
||||||
if ! command -v docker &> /dev/null; then
|
|
||||||
echo "Error: Docker is not installed. Please install Docker and try again."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if gsm_simulator.py exists
|
|
||||||
if [ ! -f "gsm_simulator.py" ]; then
|
|
||||||
echo "Error: gsm_simulator.py not found in the current directory."
|
|
||||||
echo "Please ensure gsm_simulator.py is present and try again."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create Dockerfile if it doesn't exist
|
|
||||||
if [ ! -f "Dockerfile" ]; then
|
|
||||||
echo "Creating Dockerfile..."
|
|
||||||
cat <<EOF > Dockerfile
|
|
||||||
FROM python:3.9-slim
|
|
||||||
WORKDIR /app
|
|
||||||
COPY gsm_simulator.py .
|
|
||||||
EXPOSE 12345
|
|
||||||
CMD ["python", "gsm_simulator.py"]
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ensure log file is writable
|
|
||||||
touch $LOG_FILE
|
|
||||||
chmod 666 $LOG_FILE
|
|
||||||
|
|
||||||
# Build the Docker image
|
|
||||||
echo "Building Docker image: $IMAGE_NAME..."
|
|
||||||
docker build -t $IMAGE_NAME .
|
|
||||||
|
|
||||||
# Check if the build was successful
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Error: Failed to build Docker image."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Stop and remove any existing container
|
|
||||||
if [ "$(docker ps -q -f name=$CONTAINER_NAME)" ]; then
|
|
||||||
echo "Stopping existing container: $CONTAINER_NAME..."
|
|
||||||
docker stop $CONTAINER_NAME
|
|
||||||
fi
|
|
||||||
if [ "$(docker ps -aq -f name=$CONTAINER_NAME)" ]; then
|
|
||||||
echo "Removing existing container: $CONTAINER_NAME..."
|
|
||||||
docker rm $CONTAINER_NAME
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean up dangling images
|
|
||||||
docker image prune -f
|
|
||||||
|
|
||||||
# Run the Docker container interactively
|
|
||||||
echo "Launching GSM Simulator in Docker container: $CONTAINER_NAME..."
|
|
||||||
docker run -it --rm -p $PORT:$PORT --name $CONTAINER_NAME $IMAGE_NAME | tee $LOG_FILE
|
|
||||||
|
|
||||||
# Note: Script will block here until container exits
|
|
||||||
echo "GSM Simulator stopped. Logs saved to $LOG_FILE."
|
|
@ -1,24 +0,0 @@
|
|||||||
#external_caller.py
|
|
||||||
import socket
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
def connect():
|
|
||||||
caller_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
caller_socket.connect(('localhost', 12345))
|
|
||||||
caller_socket.send("CALLER".encode())
|
|
||||||
print("Connected to GSM simulator as CALLER")
|
|
||||||
time.sleep(2) # Wait 2 seconds for receiver to connect
|
|
||||||
|
|
||||||
for i in range(5):
|
|
||||||
message = f"Audio packet {i + 1}"
|
|
||||||
caller_socket.send(message.encode())
|
|
||||||
print(f"Sent: {message}")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
caller_socket.send("CALL_END".encode())
|
|
||||||
print("Call ended.")
|
|
||||||
caller_socket.close()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
connect()
|
|
@ -1,37 +0,0 @@
|
|||||||
#external_receiver.py
|
|
||||||
import socket
|
|
||||||
|
|
||||||
def connect():
|
|
||||||
receiver_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
receiver_socket.settimeout(15) # Increase timeout to 15 seconds
|
|
||||||
receiver_socket.connect(('localhost', 12345))
|
|
||||||
receiver_socket.send("RECEIVER".encode())
|
|
||||||
print("Connected to GSM simulator as RECEIVER")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
data = receiver_socket.recv(1024).decode().strip()
|
|
||||||
if not data:
|
|
||||||
print("No data received. Connection closed.")
|
|
||||||
break
|
|
||||||
if data == "RINGING":
|
|
||||||
print("Incoming call... ringing")
|
|
||||||
elif data == "CALL_END":
|
|
||||||
print("Call ended by caller.")
|
|
||||||
break
|
|
||||||
elif data == "CALL_DROPPED":
|
|
||||||
print("Call dropped by network.")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print(f"Received: {data}")
|
|
||||||
except socket.timeout:
|
|
||||||
print("Timed out waiting for data.")
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Receiver error: {e}")
|
|
||||||
break
|
|
||||||
|
|
||||||
receiver_socket.close()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
connect()
|
|
@ -1,86 +0,0 @@
|
|||||||
import socket
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
HOST = "localhost"
|
|
||||||
PORT = 12345
|
|
||||||
INPUT_FILE = "wav/input.wav"
|
|
||||||
OUTPUT_FILE = "wav/received.wav"
|
|
||||||
|
|
||||||
|
|
||||||
def encrypt_data(data):
|
|
||||||
return data # Replace with your encryption protocol
|
|
||||||
|
|
||||||
|
|
||||||
def decrypt_data(data):
|
|
||||||
return data # Replace with your decryption protocol
|
|
||||||
|
|
||||||
|
|
||||||
def run_protocol(send_mode=True):
|
|
||||||
"""Connect to the simulator and send/receive data."""
|
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
sock.connect((HOST, PORT))
|
|
||||||
print(f"Connected to simulator at {HOST}:{PORT}")
|
|
||||||
|
|
||||||
if send_mode:
|
|
||||||
# Sender mode: Encode audio with toast
|
|
||||||
os.system(f"toast -p -l {INPUT_FILE}") # Creates input.wav.gsm
|
|
||||||
input_gsm_file = f"{INPUT_FILE}.gsm"
|
|
||||||
if not os.path.exists(input_gsm_file):
|
|
||||||
print(f"Error: {input_gsm_file} not created")
|
|
||||||
sock.close()
|
|
||||||
return
|
|
||||||
with open(input_gsm_file, "rb") as f:
|
|
||||||
voice_data = f.read()
|
|
||||||
|
|
||||||
encrypted_data = encrypt_data(voice_data)
|
|
||||||
sock.send(encrypted_data)
|
|
||||||
print(f"Sent {len(encrypted_data)} bytes")
|
|
||||||
os.remove(input_gsm_file) # Clean up
|
|
||||||
else:
|
|
||||||
# Receiver mode: Wait for and receive data
|
|
||||||
print("Waiting for data from sender...")
|
|
||||||
received_data = b""
|
|
||||||
sock.settimeout(5.0)
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
print("Calling recv()...")
|
|
||||||
data = sock.recv(1024)
|
|
||||||
print(f"Received {len(data)} bytes")
|
|
||||||
if not data:
|
|
||||||
print("Connection closed by sender or simulator")
|
|
||||||
break
|
|
||||||
received_data += data
|
|
||||||
except socket.timeout:
|
|
||||||
print("Timed out waiting for data")
|
|
||||||
|
|
||||||
if received_data:
|
|
||||||
with open("received.gsm", "wb") as f:
|
|
||||||
f.write(decrypt_data(received_data))
|
|
||||||
print(f"Wrote {len(received_data)} bytes to received.gsm")
|
|
||||||
# Decode with untoast, then convert to WAV with sox
|
|
||||||
result = subprocess.run(["untoast", "received.gsm"], capture_output=True, text=True)
|
|
||||||
print(f"untoast return code: {result.returncode}")
|
|
||||||
print(f"untoast stderr: {result.stderr}")
|
|
||||||
if result.returncode == 0:
|
|
||||||
if os.path.exists("received"):
|
|
||||||
# Convert raw PCM to WAV (8 kHz, mono, 16-bit)
|
|
||||||
subprocess.run(["sox", "-t", "raw", "-r", "8000", "-e", "signed", "-b", "16", "-c", "1", "received",
|
|
||||||
OUTPUT_FILE])
|
|
||||||
os.remove("received")
|
|
||||||
print(f"Received and saved {len(received_data)} bytes to {OUTPUT_FILE}")
|
|
||||||
else:
|
|
||||||
print("Error: 'received' file not created by untoast")
|
|
||||||
else:
|
|
||||||
print(f"untoast failed: {result.stderr}")
|
|
||||||
else:
|
|
||||||
print("No data received from simulator")
|
|
||||||
|
|
||||||
sock.close()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
mode = input("Enter 'send' to send data or 'receive' to receive data: ").strip().lower()
|
|
||||||
run_protocol(send_mode=(mode == "send"))
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,805 +0,0 @@
|
|||||||
<mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/26.0.16 Chrome/132.0.6834.196 Electron/34.2.0 Safari/537.36" version="26.0.16" pages="4">
|
|
||||||
<diagram id="C5RBs43oDa-KdzZeNtuy" name="Logique">
|
|
||||||
<mxGraphModel dx="735" dy="407" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
|
||||||
<root>
|
|
||||||
<mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
|
|
||||||
<mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
|
|
||||||
<mxCell id="WIyWlLk6GJQsqaUBKTNV-2" value="" style="rounded=0;html=1;jettySize=auto;orthogonalLoop=1;fontSize=11;endArrow=block;endFill=0;endSize=8;strokeWidth=1;shadow=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-3" target="WIyWlLk6GJQsqaUBKTNV-6" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="WIyWlLk6GJQsqaUBKTNV-3" value="Alice appelle Bob" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=3;shadow=0;strokeColor=light-dark(#000000,#370FFF);" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="300" y="90" width="120" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="WIyWlLk6GJQsqaUBKTNV-4" value="Yes" style="rounded=0;html=1;jettySize=auto;orthogonalLoop=1;fontSize=11;endArrow=block;endFill=0;endSize=8;strokeWidth=1;shadow=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-0" target="WIyWlLk6GJQsqaUBKTNV-10" edge="1">
|
|
||||||
<mxGeometry y="20" relative="1" as="geometry">
|
|
||||||
<mxPoint as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="WIyWlLk6GJQsqaUBKTNV-5" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;jettySize=auto;orthogonalLoop=1;fontSize=11;endArrow=block;endFill=0;endSize=8;strokeWidth=1;shadow=0;labelBackgroundColor=none;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-6" target="WIyWlLk6GJQsqaUBKTNV-7" edge="1">
|
|
||||||
<mxGeometry y="10" relative="1" as="geometry">
|
|
||||||
<mxPoint as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="WIyWlLk6GJQsqaUBKTNV-6" value="Bob répond ?" style="rhombus;whiteSpace=wrap;html=1;shadow=0;fontFamily=Helvetica;fontSize=12;align=center;strokeWidth=1;spacing=6;spacingTop=-4;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="310" y="180" width="100" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="WIyWlLk6GJQsqaUBKTNV-7" value="Rien ne se passe" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="460" y="200" width="120" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="WIyWlLk6GJQsqaUBKTNV-8" value="Négativement" style="rounded=0;html=1;jettySize=auto;orthogonalLoop=1;fontSize=11;endArrow=block;endFill=0;endSize=8;strokeWidth=1;shadow=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-10" target="WIyWlLk6GJQsqaUBKTNV-11" edge="1">
|
|
||||||
<mxGeometry y="40" relative="1" as="geometry">
|
|
||||||
<mxPoint as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="WIyWlLk6GJQsqaUBKTNV-9" value="Positivement" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;jettySize=auto;orthogonalLoop=1;fontSize=11;endArrow=block;endFill=0;endSize=8;strokeWidth=1;shadow=0;labelBackgroundColor=none;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-10" target="WIyWlLk6GJQsqaUBKTNV-12" edge="1">
|
|
||||||
<mxGeometry y="10" relative="1" as="geometry">
|
|
||||||
<mxPoint as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="WIyWlLk6GJQsqaUBKTNV-10" value="Bob ping..." style="whiteSpace=wrap;html=1;shadow=0;fontFamily=Helvetica;fontSize=12;align=center;strokeWidth=1;spacing=6;spacingTop=-4;shape=hexagon;perimeter=hexagonPerimeter2;fixedSize=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="310" y="390" width="100" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="WIyWlLk6GJQsqaUBKTNV-11" value="Protocole échoué<div>-</div><div>Passage en clair</div>" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=3;shadow=0;strokeColor=light-dark(#000000,#FF1414);" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="300" y="520" width="120" height="50" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;endArrow=block;endFill=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-12" target="FXGPDhTRSO2FZSW48CnP-8" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<mxPoint x="599.9999999999998" y="500" as="targetPoint" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="WIyWlLk6GJQsqaUBKTNV-12" value="Alice envoi son<span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">&nbsp;handshake a Bob</span>" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="540" y="410" width="120" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-1" value="" style="rounded=0;html=1;jettySize=auto;orthogonalLoop=1;fontSize=11;endArrow=block;endFill=0;endSize=8;strokeWidth=1;shadow=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-6" target="FXGPDhTRSO2FZSW48CnP-0" edge="1">
|
|
||||||
<mxGeometry y="20" relative="1" as="geometry">
|
|
||||||
<mxPoint as="offset" />
|
|
||||||
<mxPoint x="360" y="260" as="sourcePoint" />
|
|
||||||
<mxPoint x="360" y="390" as="targetPoint" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-0" value="Le dialer d'Alice envoi un PING a Bob" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="300" y="300" width="120" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-2" value="" style="endArrow=block;html=1;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;endFill=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;edgeStyle=orthogonalEdgeStyle;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-10" target="FXGPDhTRSO2FZSW48CnP-4" edge="1">
|
|
||||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
|
||||||
<mxPoint x="480" y="450" as="sourcePoint" />
|
|
||||||
<mxPoint x="190" y="430" as="targetPoint" />
|
|
||||||
<Array as="points">
|
|
||||||
<mxPoint x="280" y="430" />
|
|
||||||
<mxPoint x="280" y="460" />
|
|
||||||
</Array>
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-3" value="Ne ping pas" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="FXGPDhTRSO2FZSW48CnP-2" vertex="1" connectable="0">
|
|
||||||
<mxGeometry x="-0.2695" relative="1" as="geometry">
|
|
||||||
<mxPoint x="-16" y="-10" as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-4" target="FXGPDhTRSO2FZSW48CnP-0" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<Array as="points">
|
|
||||||
<mxPoint x="130" y="320" />
|
|
||||||
</Array>
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-6" value="Reping" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="FXGPDhTRSO2FZSW48CnP-5" vertex="1" connectable="0">
|
|
||||||
<mxGeometry x="0.0817" relative="1" as="geometry">
|
|
||||||
<mxPoint x="23" y="-10" as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-4" value="Attendre 1s ? 0.5s ?" style="rounded=1;whiteSpace=wrap;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="70" y="440" width="120" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-12" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;endArrow=block;endFill=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-8" target="FXGPDhTRSO2FZSW48CnP-11" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<mxPoint x="530.0380952380951" y="585.0038095238094" as="sourcePoint" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-13" value="Non" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="FXGPDhTRSO2FZSW48CnP-12" vertex="1" connectable="0">
|
|
||||||
<mxGeometry x="-0.4964" y="-1" relative="1" as="geometry">
|
|
||||||
<mxPoint as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-43" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;endArrow=block;endFill=0;strokeColor=light-dark(#000000,#FF0000);" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-8" target="FXGPDhTRSO2FZSW48CnP-27" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<mxPoint x="600" y="800" as="targetPoint" />
|
|
||||||
<Array as="points">
|
|
||||||
<mxPoint x="600" y="660" />
|
|
||||||
<mxPoint x="570" y="660" />
|
|
||||||
<mxPoint x="570" y="700" />
|
|
||||||
<mxPoint x="210" y="700" />
|
|
||||||
</Array>
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-44" value="Oui" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="FXGPDhTRSO2FZSW48CnP-43" vertex="1" connectable="0">
|
|
||||||
<mxGeometry x="-0.8049" y="1" relative="1" as="geometry">
|
|
||||||
<mxPoint x="8" y="-25" as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-8" value="Bob reconnait la clé publique d'Alice ?" style="rhombus;whiteSpace=wrap;html=1;fontSize=10;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="540" y="545" width="120" height="75" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-11" target="WIyWlLk6GJQsqaUBKTNV-11" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<mxPoint x="360" y="600" as="targetPoint" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-17" value="Non" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="FXGPDhTRSO2FZSW48CnP-16" vertex="1" connectable="0">
|
|
||||||
<mxGeometry x="-0.1233" y="-2" relative="1" as="geometry">
|
|
||||||
<mxPoint as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-20" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-11" target="FXGPDhTRSO2FZSW48CnP-19" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-21" value="Oui" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="FXGPDhTRSO2FZSW48CnP-20" vertex="1" connectable="0">
|
|
||||||
<mxGeometry x="-0.275" y="1" relative="1" as="geometry">
|
|
||||||
<mxPoint as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-11" value="Bob accepte la clé d'Alice?" style="rhombus;whiteSpace=wrap;html=1;fontSize=10;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="354" y="620" width="120" height="75" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-23" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-19" target="FXGPDhTRSO2FZSW48CnP-22" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-19" value="Bob envoi sa clé publique en handshake" style="whiteSpace=wrap;html=1;fontSize=10;rounded=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="160" y="636.25" width="120" height="42.5" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-24" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=block;endFill=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-22" target="WIyWlLk6GJQsqaUBKTNV-11" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<Array as="points">
|
|
||||||
<mxPoint x="70" y="545" />
|
|
||||||
</Array>
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-25" value="Non" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="FXGPDhTRSO2FZSW48CnP-24" vertex="1" connectable="0">
|
|
||||||
<mxGeometry x="-0.7543" y="3" relative="1" as="geometry">
|
|
||||||
<mxPoint as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-28" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-22" target="FXGPDhTRSO2FZSW48CnP-27" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<Array as="points">
|
|
||||||
<mxPoint x="70" y="750" />
|
|
||||||
</Array>
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-22" value="Alice accepte la clé publique de Bob<span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">&nbsp;?</span>" style="rhombus;whiteSpace=wrap;html=1;fontSize=10;rounded=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="30" y="617.5" width="80" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-47" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-26" target="FXGPDhTRSO2FZSW48CnP-46" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-26" value="Alice et Bob sont d'accord sur la clé symmétrique a utiliser" style="rounded=0;whiteSpace=wrap;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="340" y="820" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-31" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=block;endFill=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-27" target="FXGPDhTRSO2FZSW48CnP-30" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-45" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=light-dark(#000000,#FF1616);endArrow=block;endFill=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-27" target="FXGPDhTRSO2FZSW48CnP-26" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<Array as="points">
|
|
||||||
<mxPoint x="210" y="850" />
|
|
||||||
</Array>
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-27" value="Alice et Bob calculent le secret partagé de leur côté" style="whiteSpace=wrap;html=1;fontSize=10;rounded=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="150" y="720" width="120" height="50" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-33" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;jumpStyle=sharp;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-29" target="FXGPDhTRSO2FZSW48CnP-34" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<mxPoint x="640" y="680" as="targetPoint" />
|
|
||||||
<Array as="points">
|
|
||||||
<mxPoint x="700" y="745" />
|
|
||||||
</Array>
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-36" value="Non" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="FXGPDhTRSO2FZSW48CnP-33" vertex="1" connectable="0">
|
|
||||||
<mxGeometry x="-0.1536" y="1" relative="1" as="geometry">
|
|
||||||
<mxPoint as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-41" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-29" target="FXGPDhTRSO2FZSW48CnP-26" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<Array as="points">
|
|
||||||
<mxPoint x="525" y="798" />
|
|
||||||
<mxPoint x="510" y="798" />
|
|
||||||
<mxPoint x="510" y="850" />
|
|
||||||
</Array>
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-42" value="Oui" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="FXGPDhTRSO2FZSW48CnP-41" vertex="1" connectable="0">
|
|
||||||
<mxGeometry x="-0.7774" y="1" relative="1" as="geometry">
|
|
||||||
<mxPoint as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-29" value="Ils sont d'accord ?" style="rhombus;whiteSpace=wrap;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="460" y="715" width="130" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-32" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-30" target="FXGPDhTRSO2FZSW48CnP-29" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-30" value="Alice et Bob lisent à haute voix la phrase de sécurité" style="whiteSpace=wrap;html=1;fontSize=10;rounded=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="300" y="720" width="120" height="50" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-37" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-34" target="WIyWlLk6GJQsqaUBKTNV-12" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-38" value="Oui" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="FXGPDhTRSO2FZSW48CnP-37" vertex="1" connectable="0">
|
|
||||||
<mxGeometry x="-0.3086" y="-2" relative="1" as="geometry">
|
|
||||||
<mxPoint as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-39" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;jumpStyle=sharp;jumpSize=8;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-34" target="WIyWlLk6GJQsqaUBKTNV-11" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<Array as="points">
|
|
||||||
<mxPoint x="530" y="690" />
|
|
||||||
<mxPoint x="530" y="545" />
|
|
||||||
</Array>
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-40" value="Non" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="FXGPDhTRSO2FZSW48CnP-39" vertex="1" connectable="0">
|
|
||||||
<mxGeometry x="-0.7617" relative="1" as="geometry">
|
|
||||||
<mxPoint as="offset" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-34" value="Ré-essayer ?" style="rhombus;whiteSpace=wrap;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="660" y="650" width="80" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-49" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="FXGPDhTRSO2FZSW48CnP-46" target="FXGPDhTRSO2FZSW48CnP-48" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-46" value="Alice et Bob utilisent la clé symmétrique pour chiffrer leurs transmissions" style="whiteSpace=wrap;html=1;rounded=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="340" y="920" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="FXGPDhTRSO2FZSW48CnP-48" value="" style="rhombus;whiteSpace=wrap;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
|
|
||||||
<mxGeometry x="360" y="1020" width="80" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
</root>
|
|
||||||
</mxGraphModel>
|
|
||||||
</diagram>
|
|
||||||
<diagram id="c7L-flsM9ZWaCx455Pfy" name="Transport Layer - 0">
|
|
||||||
<mxGraphModel dx="1434" dy="835" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
|
||||||
<root>
|
|
||||||
<mxCell id="0" />
|
|
||||||
<mxCell id="1" parent="0" />
|
|
||||||
</root>
|
|
||||||
</mxGraphModel>
|
|
||||||
</diagram>
|
|
||||||
<diagram id="4Sb7mgJDpsadGym-U4wz" name="Protocol Layer - 0">
|
|
||||||
<mxGraphModel dx="1434" dy="835" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
|
||||||
<root>
|
|
||||||
<mxCell id="0" />
|
|
||||||
<mxCell id="1" parent="0" />
|
|
||||||
<mxCell id="b_xV4iUWIxmdZCAYY4YR-1" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0;dx=10;notch=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="160" y="120" width="440" height="120" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="O_eM33N56VtHnDaMz1H4-1" value="ALICE" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};participant=umlEntity;strokeWidth=2;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="120" y="40" width="40" height="3110" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="O_eM33N56VtHnDaMz1H4-2" value="BOB" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};participant=umlEntity;strokeWidth=2;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="690" y="40" width="40" height="3110" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="b_xV4iUWIxmdZCAYY4YR-2" value="PING" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=23;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="385" y="65" width="80" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="n3lF8vaYaHAhAfaeaFZn-1" value="Nonce" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="180" y="130" width="105" height="80" as="geometry">
|
|
||||||
<mxRectangle x="210" y="130" width="80" height="30" as="alternateBounds" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="n3lF8vaYaHAhAfaeaFZn-6" value="<div>sha256 (</div><div>numéro alice +</div><div>numéro bob +</div><div>timestamp +</div><div>random<br></div><div>) / ~2 (left part)<br></div>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=7;" parent="n3lF8vaYaHAhAfaeaFZn-1" vertex="1">
|
|
||||||
<mxGeometry y="25" width="100" height="55" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="n3lF8vaYaHAhAfaeaFZn-2" value="Version" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="305" y="130" width="58.75" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-1" value="(0-128)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="n3lF8vaYaHAhAfaeaFZn-2" vertex="1">
|
|
||||||
<mxGeometry x="3.75" y="30" width="51.25" height="25" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="n3lF8vaYaHAhAfaeaFZn-4" value="Checksum" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="455" y="130" width="90" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="n3lF8vaYaHAhAfaeaFZn-7" value="CRC-32" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="n3lF8vaYaHAhAfaeaFZn-4" vertex="1">
|
|
||||||
<mxGeometry x="15" y="25" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="XvZTtdEB18xY6m2a5fJO-1" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0;dx=10;notch=0;rotation=-180;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="280" y="280" width="410" height="190" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="XvZTtdEB18xY6m2a5fJO-2" value="Timestamp" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="300" y="290" width="90" height="60" as="geometry">
|
|
||||||
<mxRectangle x="210" y="130" width="80" height="30" as="alternateBounds" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="XvZTtdEB18xY6m2a5fJO-3" value="timestamp" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;strokeWidth=1;" parent="XvZTtdEB18xY6m2a5fJO-2" vertex="1">
|
|
||||||
<mxGeometry x="11.25" y="27.5" width="67.5" height="25" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="XvZTtdEB18xY6m2a5fJO-4" value="Version" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="405.63" y="290" width="58.75" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="XvZTtdEB18xY6m2a5fJO-11" value="0" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="XvZTtdEB18xY6m2a5fJO-4" vertex="1">
|
|
||||||
<mxGeometry y="25" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="XvZTtdEB18xY6m2a5fJO-5" value="Checksum" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="590" y="380" width="85" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="XvZTtdEB18xY6m2a5fJO-6" value="CRC-32" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="XvZTtdEB18xY6m2a5fJO-5" vertex="1">
|
|
||||||
<mxGeometry x="12.5" y="25" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="XvZTtdEB18xY6m2a5fJO-9" value="Answer" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="482.5" y="290" width="57.5" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="XvZTtdEB18xY6m2a5fJO-10" value="<div>YES</div><div>NO<br></div>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="484.38" y="315" width="53.75" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="XvZTtdEB18xY6m2a5fJO-13" value="HANDSHAKE" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=23;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="350" y="510" width="170" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="XvZTtdEB18xY6m2a5fJO-14" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0;dx=10;notch=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="160" y="570" width="410" height="220" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="XvZTtdEB18xY6m2a5fJO-15" value="Clé éphémère" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="170" y="580" width="105" height="80" as="geometry">
|
|
||||||
<mxRectangle x="210" y="130" width="80" height="30" as="alternateBounds" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-13" value="Clé (publique) générée aléatoirement" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;" parent="XvZTtdEB18xY6m2a5fJO-15" vertex="1">
|
|
||||||
<mxGeometry y="30" width="100" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="XvZTtdEB18xY6m2a5fJO-17" value="Signature" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="285" y="580" width="105" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-14" value="PubkeyFixe. sign(clé éphémère)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="XvZTtdEB18xY6m2a5fJO-17" vertex="1">
|
|
||||||
<mxGeometry y="20" width="100" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="XvZTtdEB18xY6m2a5fJO-18" value="Checksum" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="486.25" y="580" width="65" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="XvZTtdEB18xY6m2a5fJO-19" value="CRC-32" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="XvZTtdEB18xY6m2a5fJO-18" vertex="1">
|
|
||||||
<mxGeometry x="2.5" y="30" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-15" value="PFS" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="402.81" y="580" width="71.88" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-16" value="hash( preuve de convo précédente)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;" parent="pP7SjZfcCiBg3d1TCkzP-15" vertex="1">
|
|
||||||
<mxGeometry x="6.57" y="30" width="60" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-17" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0;dx=10;notch=0;rotation=-180;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="285" y="830" width="410" height="180" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-43" value="Clé éphémère" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="305" y="840" width="105" height="80" as="geometry">
|
|
||||||
<mxRectangle x="210" y="130" width="80" height="30" as="alternateBounds" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-44" value="Clé (publique) générée aléatoirement" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;" parent="pP7SjZfcCiBg3d1TCkzP-43" vertex="1">
|
|
||||||
<mxGeometry y="30" width="100" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-45" value="Signature" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="420" y="840" width="105" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-46" value="PubkeyFixe. sign(clé éphémère)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="pP7SjZfcCiBg3d1TCkzP-45" vertex="1">
|
|
||||||
<mxGeometry y="20" width="100" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-47" value="Checksum" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="621.25" y="840" width="65" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-48" value="CRC-32" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="pP7SjZfcCiBg3d1TCkzP-47" vertex="1">
|
|
||||||
<mxGeometry x="2.5" y="30" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-49" value="PFS" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="537.81" y="840" width="71.88" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-50" value="hash( preuve de convo précédente )" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;" parent="pP7SjZfcCiBg3d1TCkzP-49" vertex="1">
|
|
||||||
<mxGeometry x="6.57" y="30" width="60" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-54" value="Timestamp" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="182.5" y="690" width="80" height="70" as="geometry">
|
|
||||||
<mxRectangle x="210" y="130" width="80" height="30" as="alternateBounds" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-55" value="timestamp" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;strokeWidth=1;" parent="pP7SjZfcCiBg3d1TCkzP-54" vertex="1">
|
|
||||||
<mxGeometry x="6.25" y="32.5" width="67.5" height="25" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-56" value="Timestamp" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="606.25" y="930" width="80" height="70" as="geometry">
|
|
||||||
<mxRectangle x="210" y="130" width="80" height="30" as="alternateBounds" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-57" value="timestamp" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;strokeWidth=1;" parent="pP7SjZfcCiBg3d1TCkzP-56" vertex="1">
|
|
||||||
<mxGeometry x="6.25" y="32.5" width="67.5" height="25" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-58" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0;dx=10;notch=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="160" y="1160" width="450" height="200" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-59" value="ENCRYPTED COMS" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=23;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="305" y="1100" width="240" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-60" value="129b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="200" y="210" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-61" value="7b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="303.75" y="210" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-62" value="32b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="470" y="210" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-63" value="= 172b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="530" y="210" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-66" value="32b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="313" y="350" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-67" value="7b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="406.75" y="350" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-68" value="1b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="479.25" y="350" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-69" value="32b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="600.5" y="440" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-70" value="= 76b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="426.25" y="420" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-71" value="264b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="193" y="660" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-72" value="512b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="307.5" y="660" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-73" value="256b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="409.38" y="660" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-74" value="32b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="488.75" y="660" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-75" value="32b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="192.5" y="760" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-76" value="=1096b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="315" y="750" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pP7SjZfcCiBg3d1TCkzP-77" value="=1096b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="327.5" y="970" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="_H5URFloX_BVB2BL7kO6-1" value="CRC ?" style="swimlane;whiteSpace=wrap;html=1;fillColor=#008a00;fontColor=#ffffff;strokeColor=#005700;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="375" y="1270" width="63.25" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="_H5URFloX_BVB2BL7kO6-2" value="CRC-32" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="_H5URFloX_BVB2BL7kO6-1" vertex="1">
|
|
||||||
<mxGeometry x="1.6199999999999992" y="25" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="_H5URFloX_BVB2BL7kO6-3" value="Flag" style="swimlane;whiteSpace=wrap;html=1;fillColor=#008a00;fontColor=#ffffff;strokeColor=#005700;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="180" y="1170" width="65" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="_H5URFloX_BVB2BL7kO6-4" value="To determine" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="_H5URFloX_BVB2BL7kO6-3" vertex="1">
|
|
||||||
<mxGeometry x="2.5" y="25" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="_H5URFloX_BVB2BL7kO6-5" value="nbretry" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="344.38" y="1170" width="65" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="_H5URFloX_BVB2BL7kO6-6" value="y" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="_H5URFloX_BVB2BL7kO6-5" vertex="1">
|
|
||||||
<mxGeometry x="2.5" y="25" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="_H5URFloX_BVB2BL7kO6-7" value="msg_len" style="swimlane;whiteSpace=wrap;html=1;fillColor=#008a00;fontColor=#ffffff;strokeColor=#005700;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="262.5" y="1170" width="65" height="60" as="geometry">
|
|
||||||
<mxRectangle x="262.5" y="1170" width="90" height="30" as="alternateBounds" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="_H5URFloX_BVB2BL7kO6-8" value="XXX" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="_H5URFloX_BVB2BL7kO6-7" vertex="1">
|
|
||||||
<mxGeometry x="2.5" y="25" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="_H5URFloX_BVB2BL7kO6-9" value="msg" style="swimlane;whiteSpace=wrap;html=1;fillColor=#0050ef;fontColor=#ffffff;strokeColor=#001DBC;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="187.5" y="1270" width="65" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="_H5URFloX_BVB2BL7kO6-10" value="BBB" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="_H5URFloX_BVB2BL7kO6-9" vertex="1">
|
|
||||||
<mxGeometry x="2.5" y="30" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="_H5URFloX_BVB2BL7kO6-11" value="16b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="180" y="1230" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="_H5URFloX_BVB2BL7kO6-12" value="8b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="349.38" y="1230" width="55" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="_H5URFloX_BVB2BL7kO6-13" value="16b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="267.5" y="1230" width="55" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="_H5URFloX_BVB2BL7kO6-14" value="96b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="510" y="1230" width="55" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="_H5URFloX_BVB2BL7kO6-15" value="32b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="379.12" y="1330" width="55" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="_H5URFloX_BVB2BL7kO6-16" value="= (180b ~ 212b) + yyy" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="465" y="1285" width="130" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-2" value="Cypher" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="375" y="130" width="58.75" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-3" value="(0-16)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="pWkGvNQAXuiST1IiWYlx-2" vertex="1">
|
|
||||||
<mxGeometry x="3.75" y="30" width="51.25" height="25" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-4" value="4b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="375" y="210" width="55" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-5" value="Cypher" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="600" y="290" width="58.75" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-6" value="(0-16)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="pWkGvNQAXuiST1IiWYlx-5" vertex="1">
|
|
||||||
<mxGeometry x="3.75" y="30" width="51.25" height="25" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-7" value="4b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="601.88" y="350" width="55" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-8" value="status" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="425" y="1170" width="65" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-9" value="CRC ?" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="pWkGvNQAXuiST1IiWYlx-8" vertex="1">
|
|
||||||
<mxGeometry x="2.5" y="25" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-10" value="4b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="428.75" y="1230" width="55" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-11" value="iv" style="swimlane;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="505" y="1170" width="65" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-12" value="random<div>(+Z)</div>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="pWkGvNQAXuiST1IiWYlx-11" vertex="1">
|
|
||||||
<mxGeometry x="2.5" y="25" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-13" value="BBB b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="193" y="1330" width="55" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-14" value="MAC" style="swimlane;whiteSpace=wrap;html=1;fillColor=#008a00;fontColor=#ffffff;strokeColor=#005700;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="286.13" y="1270" width="63.25" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-15" value="AEAD" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="pWkGvNQAXuiST1IiWYlx-14" vertex="1">
|
|
||||||
<mxGeometry x="1.6199999999999992" y="25" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-16" value="128b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="290.25" y="1330" width="55" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-17" value="Green = clear data" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#008a00;fontColor=#ffffff;strokeColor=#005700;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="10" y="1170" width="110" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-18" value="<font style="color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">White = additional data</font>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=none;strokeColor=light-dark(#6C8EBF,#FFFFFF);" parent="1" vertex="1">
|
|
||||||
<mxGeometry y="1220" width="130" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="pWkGvNQAXuiST1IiWYlx-19" value="Blue = encrypted data" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#0050ef;fontColor=#ffffff;strokeColor=#001DBC;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="10" y="1270" width="110" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
</root>
|
|
||||||
</mxGraphModel>
|
|
||||||
</diagram>
|
|
||||||
<diagram name="Protocol Layer - 1" id="_rkrwzJg5buKJxYS8faK">
|
|
||||||
<mxGraphModel dx="1062" dy="1719" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
|
||||||
<root>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-0" />
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-1" parent="0ZE-dMPOFneTTZtpNr96-0" />
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-2" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0;dx=10;notch=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="160" y="120" width="260" height="120" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-3" value="<font style="color: rgb(255, 65, 27);"><b>ALICE&nbsp; (INITIATOR)</b></font>" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};participant=umlEntity;strokeWidth=2;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="120" y="40" width="40" height="3110" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-4" value="<font>BOB</font>" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};participant=umlEntity;strokeWidth=2;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="690" y="40" width="40" height="3110" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-5" value="PING" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=23;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="385" y="65" width="80" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-8" value="Version" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="171.25" y="130" width="58.75" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-9" value="(0-128)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-8">
|
|
||||||
<mxGeometry x="3.75" y="30" width="51.25" height="25" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-12" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0;dx=10;notch=0;rotation=-180;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="350" y="280" width="340" height="190" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-13" value="Timestamp" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="380" y="290" width="90" height="60" as="geometry">
|
|
||||||
<mxRectangle x="210" y="130" width="80" height="30" as="alternateBounds" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-14" value="timestamp" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;strokeWidth=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-13">
|
|
||||||
<mxGeometry x="11.25" y="27.5" width="67.5" height="25" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-15" value="Version" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="479.76" y="290" width="58.75" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-16" value="0" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-15">
|
|
||||||
<mxGeometry y="25" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-19" value="Answer" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="548.76" y="290" width="57.5" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-20" value="<div>YES</div><div>NO<br></div>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="533.76" y="315" width="53.75" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-21" value="HANDSHAKE" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=23;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="350" y="510" width="170" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-22" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0;dx=10;notch=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="160" y="570" width="410" height="220" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-23" value="Clé éphémère" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="170" y="580" width="105" height="80" as="geometry">
|
|
||||||
<mxRectangle x="210" y="130" width="80" height="30" as="alternateBounds" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-24" value="Clé (publique) générée aléatoirement" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-23">
|
|
||||||
<mxGeometry y="30" width="100" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-25" value="" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="285" y="580" width="105" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-29" value="" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="402.81" y="580" width="71.88" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-31" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0;dx=10;notch=0;rotation=-180;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="285" y="830" width="410" height="180" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-32" value="Clé éphémère" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="305" y="840" width="105" height="80" as="geometry">
|
|
||||||
<mxRectangle x="210" y="130" width="80" height="30" as="alternateBounds" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-33" value="Clé (publique) générée aléatoirement" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=11;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-32">
|
|
||||||
<mxGeometry y="30" width="100" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-34" value="" style="swimlane;whiteSpace=wrap;html=1;startSize=23;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="420" y="840" width="105" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-38" value="" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="537.81" y="840" width="71.88" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-40" value="Timestamp" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="182.5" y="690" width="80" height="70" as="geometry">
|
|
||||||
<mxRectangle x="210" y="130" width="80" height="30" as="alternateBounds" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-41" value="timestamp" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;strokeWidth=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-40">
|
|
||||||
<mxGeometry x="6.25" y="32.5" width="67.5" height="25" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-42" value="Timestamp" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="606.25" y="930" width="80" height="70" as="geometry">
|
|
||||||
<mxRectangle x="210" y="130" width="80" height="30" as="alternateBounds" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-43" value="timestamp" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=10;strokeWidth=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-42">
|
|
||||||
<mxGeometry x="6.25" y="32.5" width="67.5" height="25" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-44" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0;dx=10;notch=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="160" y="1160" width="450" height="200" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-45" value="ENCRYPTED COMS" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=23;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="305" y="1100" width="240" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-47" value="7b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="170" y="210" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-49" value="= 48b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="350" y="210" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-50" value="16b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="393" y="350" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-51" value="7b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="479.13" y="350" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-52" value="1b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="545.51" y="350" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-54" value="= 32b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="465" y="395" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-55" value="264b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="193" y="660" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-56" value="512b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="307.5" y="660" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-57" value="256b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="409.38" y="660" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-59" value="16b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="192.5" y="760" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-60" value="=1096b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="315" y="750" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-61" value="=1096b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="327.5" y="970" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-66" value="nbretry" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="344.38" y="1170" width="65" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-67" value="y" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-66">
|
|
||||||
<mxGeometry x="2.5" y="25" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-70" value="msg" style="swimlane;whiteSpace=wrap;html=1;fillColor=#0050ef;fontColor=#ffffff;strokeColor=#001DBC;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="187.5" y="1270" width="65" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-71" value="BBB" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-70">
|
|
||||||
<mxGeometry x="2.5" y="30" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-73" value="8b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="349.38" y="1230" width="55" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-77" value="= (180b ~ 212b) + yyy" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="465" y="1285" width="130" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-78" value="Cypher" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="240" y="130" width="58.75" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-79" value="(0-32)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-78">
|
|
||||||
<mxGeometry x="3.75" y="30" width="51.25" height="25" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-80" value="5b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="240" y="210" width="55" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-81" value="Cypher Offset" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="616.88" y="290" width="58.75" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-82" value="(0-8)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-81">
|
|
||||||
<mxGeometry x="3.75" y="30" width="51.25" height="25" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-83" value="4b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="618.76" y="350" width="55" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-84" value="status" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="425" y="1170" width="65" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-85" value="CRC ?" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-84">
|
|
||||||
<mxGeometry x="2.5" y="25" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-86" value="4b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="428.75" y="1230" width="55" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-89" value="BBB b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="193" y="1330" width="55" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-90" value="MAC" style="swimlane;whiteSpace=wrap;html=1;fillColor=#008a00;fontColor=#ffffff;strokeColor=#005700;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="286.13" y="1270" width="63.25" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-91" value="AEAD" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-90">
|
|
||||||
<mxGeometry x="1.6199999999999992" y="25" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-92" value="128b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="290.25" y="1330" width="55" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-93" value="Green = clear data" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#008a00;fontColor=#ffffff;strokeColor=#005700;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="10" y="1170" width="110" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-94" value="<font style="color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">White = additional data</font>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=none;strokeColor=light-dark(#6C8EBF,#FFFFFF);" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry y="1220" width="130" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="0ZE-dMPOFneTTZtpNr96-95" value="Blue = encrypted data" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fillColor=#0050ef;fontColor=#ffffff;strokeColor=#001DBC;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="10" y="1270" width="110" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="el23RTDFL3Ay36EOE6FN-0" value="Quality" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="308.75" y="130" width="58.75" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="el23RTDFL3Ay36EOE6FN-1" value="(0-16)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="el23RTDFL3Ay36EOE6FN-0">
|
|
||||||
<mxGeometry x="3.75" y="30" width="51.25" height="25" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="el23RTDFL3Ay36EOE6FN-2" value="4b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="308.75" y="210" width="55" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="el23RTDFL3Ay36EOE6FN-3" value="4b" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="616.88" y="440" width="55" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="el23RTDFL3Ay36EOE6FN-4" value="Quality" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="616.88" y="380" width="58.75" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="el23RTDFL3Ay36EOE6FN-5" value="(0-16)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="el23RTDFL3Ay36EOE6FN-4">
|
|
||||||
<mxGeometry x="3.75" y="30" width="51.25" height="25" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="1WmqBiAd3lf2sxgMsw2H-0" value="Best Case Scenario<div>Noise_XK_25519_ChaChaPoly_SHA256</div>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=25;fontStyle=1" vertex="1" parent="0ZE-dMPOFneTTZtpNr96-1">
|
|
||||||
<mxGeometry x="223" y="-20" width="405" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
</root>
|
|
||||||
</mxGraphModel>
|
|
||||||
</diagram>
|
|
||||||
</mxfile>
|
|
@ -1,91 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import threading
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from noise_xk.session import NoiseXKSession
|
|
||||||
from noise_xk.transport import P2PTransport
|
|
||||||
from dissononce.dh.x25519.public import PublicKey
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(prog="noise_xk")
|
|
||||||
parser.add_argument(
|
|
||||||
"--listen-port", type=int, required=True,
|
|
||||||
help="Port on which to bind+listen"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--peer-host", type=str, default="127.0.0.1",
|
|
||||||
help="Peer host to dial (default: 127.0.0.1)"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--peer-port", type=int, required=True,
|
|
||||||
help="Peer port to dial"
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# 1. Generate static keypair and print our static public key
|
|
||||||
kp = NoiseXKSession.generate_keypair()
|
|
||||||
# kp.public is a PublicKey; .data holds raw bytes
|
|
||||||
local_priv = kp.private # carried implicitly in NoiseXKSession
|
|
||||||
local_pub = kp.public
|
|
||||||
print(f"[My static pubkey:] {local_pub.data.hex()}")
|
|
||||||
|
|
||||||
# 2. Read peer pubkey from user input
|
|
||||||
peer_pubkey = None
|
|
||||||
while True:
|
|
||||||
line = input(">>> ").strip()
|
|
||||||
if line.startswith("peer_pubkey "):
|
|
||||||
hexstr = line.split(None, 1)[1]
|
|
||||||
try:
|
|
||||||
raw = bytes.fromhex(hexstr)
|
|
||||||
peer_pubkey = PublicKey(raw) # wrap raw bytes in PublicKey
|
|
||||||
break
|
|
||||||
except ValueError:
|
|
||||||
print("Invalid hex; please retry.")
|
|
||||||
else:
|
|
||||||
print("Use: peer_pubkey <hex>")
|
|
||||||
|
|
||||||
# 3. Establish P2P connection (race listen vs. dial)
|
|
||||||
transport = P2PTransport(
|
|
||||||
listen_port=args.listen_port,
|
|
||||||
peer_host=args.peer_host,
|
|
||||||
peer_port=args.peer_port
|
|
||||||
)
|
|
||||||
print(
|
|
||||||
f"Racing connect/listen on ports "
|
|
||||||
f"{args.listen_port} ⇆ {args.peer_host}:{args.peer_port}…"
|
|
||||||
)
|
|
||||||
sock, initiator = transport.connect()
|
|
||||||
print(f"Connected (initiator={initiator}); performing handshake…")
|
|
||||||
|
|
||||||
# 4. Perform Noise XK handshake
|
|
||||||
session = NoiseXKSession(kp, peer_pubkey)
|
|
||||||
session.handshake(sock, initiator)
|
|
||||||
print("Handshake complete! You can now type messages.")
|
|
||||||
|
|
||||||
# 5. Reader thread for incoming messages
|
|
||||||
def reader():
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
pt = session.receive(sock)
|
|
||||||
print(f"\n< {pt.decode()}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"\n[Receive error ({type(e).__name__}): {e!r}]")
|
|
||||||
break
|
|
||||||
|
|
||||||
thread = threading.Thread(target=reader, daemon=True)
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
# 6. Main loop: send user input
|
|
||||||
try:
|
|
||||||
for line in sys.stdin:
|
|
||||||
text = line.rstrip("\n")
|
|
||||||
if not text:
|
|
||||||
continue
|
|
||||||
session.send(sock, text.encode())
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
sock.close()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,179 +0,0 @@
|
|||||||
# noise_xk/session.py
|
|
||||||
|
|
||||||
import socket
|
|
||||||
import logging
|
|
||||||
from dissononce.processing.impl.handshakestate import HandshakeState
|
|
||||||
from dissononce.processing.impl.symmetricstate import SymmetricState
|
|
||||||
from dissononce.processing.impl.cipherstate import CipherState
|
|
||||||
from dissononce.processing.handshakepatterns.interactive.XK import XKHandshakePattern
|
|
||||||
from dissononce.cipher.chachapoly import ChaChaPolyCipher
|
|
||||||
from dissononce.dh.x25519.x25519 import X25519DH
|
|
||||||
from dissononce.dh.keypair import KeyPair
|
|
||||||
from dissononce.dh.x25519.public import PublicKey
|
|
||||||
from dissononce.hash.sha256 import SHA256Hash
|
|
||||||
|
|
||||||
# Configure root logger for debug output
|
|
||||||
logging.basicConfig(level=logging.DEBUG, format="%(message)s")
|
|
||||||
|
|
||||||
class NoiseXKSession:
|
|
||||||
@staticmethod
|
|
||||||
def generate_keypair() -> KeyPair:
|
|
||||||
"""
|
|
||||||
Generate a static X25519 KeyPair.
|
|
||||||
Returns:
|
|
||||||
KeyPair object with .private and .public attributes.
|
|
||||||
"""
|
|
||||||
return X25519DH().generate_keypair()
|
|
||||||
|
|
||||||
def __init__(self, local_kp: KeyPair, peer_pubkey: PublicKey):
|
|
||||||
"""
|
|
||||||
Initialize with our KeyPair and the peer's PublicKey.
|
|
||||||
"""
|
|
||||||
self.local_kp: KeyPair = local_kp
|
|
||||||
self.peer_pubkey: PublicKey = peer_pubkey
|
|
||||||
|
|
||||||
# Build the Noise handshake state (X25519 DH, ChaChaPoly cipher, SHA256 hash)
|
|
||||||
cipher = ChaChaPolyCipher()
|
|
||||||
dh = X25519DH()
|
|
||||||
hshash = SHA256Hash()
|
|
||||||
symmetric = SymmetricState(CipherState(cipher), hshash)
|
|
||||||
self._hs = HandshakeState(symmetric, dh)
|
|
||||||
|
|
||||||
self._send_cs = None # type: CipherState
|
|
||||||
self._recv_cs = None
|
|
||||||
|
|
||||||
def handshake(self, sock: socket.socket, initiator: bool) -> None:
|
|
||||||
"""
|
|
||||||
Perform the XK handshake over the socket. Branches on initiator/responder
|
|
||||||
so that each side reads or writes in the correct message order.
|
|
||||||
On completion, self._send_cs and self._recv_cs hold the two CipherStates.
|
|
||||||
"""
|
|
||||||
|
|
||||||
logging.debug(f"[handshake] start (initiator={initiator})")
|
|
||||||
# initialize with our KeyPair and their PublicKey
|
|
||||||
if initiator:
|
|
||||||
# initiator knows peer’s static out-of-band
|
|
||||||
self._hs.initialize(
|
|
||||||
XKHandshakePattern(),
|
|
||||||
True,
|
|
||||||
b'',
|
|
||||||
s=self.local_kp,
|
|
||||||
rs=self.peer_pubkey
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logging.debug("[handshake] responder initializing without rs")
|
|
||||||
# responder must NOT supply rs here
|
|
||||||
self._hs.initialize(
|
|
||||||
XKHandshakePattern(),
|
|
||||||
False,
|
|
||||||
b'',
|
|
||||||
s=self.local_kp
|
|
||||||
)
|
|
||||||
|
|
||||||
cs_pair = None
|
|
||||||
if initiator:
|
|
||||||
# 1) -> e
|
|
||||||
buf1 = bytearray()
|
|
||||||
cs_pair = self._hs.write_message(b'', buf1)
|
|
||||||
logging.debug(f"[-> e] {buf1.hex()}")
|
|
||||||
self._send_all(sock, buf1)
|
|
||||||
|
|
||||||
# 2) <- e, es, s, ss
|
|
||||||
msg2 = self._recv_all(sock)
|
|
||||||
logging.debug(f"[<- msg2] {msg2.hex()}")
|
|
||||||
self._hs.read_message(msg2, bytearray())
|
|
||||||
|
|
||||||
# 3) -> se (final)
|
|
||||||
buf3 = bytearray()
|
|
||||||
cs_pair = self._hs.write_message(b'', buf3)
|
|
||||||
logging.debug(f"[-> se] {buf3.hex()}")
|
|
||||||
self._send_all(sock, buf3)
|
|
||||||
else:
|
|
||||||
# 1) <- e
|
|
||||||
msg1 = self._recv_all(sock)
|
|
||||||
logging.debug(f"[<- e] {msg1.hex()}")
|
|
||||||
self._hs.read_message(msg1, bytearray())
|
|
||||||
|
|
||||||
# 2) -> e, es, s, ss
|
|
||||||
buf2 = bytearray()
|
|
||||||
cs_pair = self._hs.write_message(b'', buf2)
|
|
||||||
logging.debug(f"[-> msg2] {buf2.hex()}")
|
|
||||||
self._send_all(sock, buf2)
|
|
||||||
|
|
||||||
# 3) <- se (final)
|
|
||||||
msg3 = self._recv_all(sock)
|
|
||||||
logging.debug(f"[<- se] {msg3.hex()}")
|
|
||||||
cs_pair = self._hs.read_message(msg3, bytearray())
|
|
||||||
|
|
||||||
# on the final step, we must get exactly two CipherStates
|
|
||||||
if not cs_pair or len(cs_pair) != 2:
|
|
||||||
raise RuntimeError("Handshake did not complete properly")
|
|
||||||
cs0, cs1 = cs_pair
|
|
||||||
# the library returns (cs_encrypt_for_initiator, cs_decrypt_for_initiator)
|
|
||||||
if initiator:
|
|
||||||
# initiator: cs0 encrypts, cs1 decrypts
|
|
||||||
self._send_cs, self._recv_cs = cs0, cs1
|
|
||||||
else:
|
|
||||||
# responder must swap
|
|
||||||
self._send_cs, self._recv_cs = cs1, cs0
|
|
||||||
|
|
||||||
# dump the raw symmetric keys & nonces (if available)
|
|
||||||
self._dump_cipherstate("HANDSHAKE→ SEND", self._send_cs)
|
|
||||||
self._dump_cipherstate("HANDSHAKE→ RECV", self._recv_cs)
|
|
||||||
|
|
||||||
def send(self, sock: socket.socket, plaintext: bytes) -> None:
|
|
||||||
"""
|
|
||||||
Encrypt and send a message.
|
|
||||||
"""
|
|
||||||
if self._send_cs is None:
|
|
||||||
raise RuntimeError("Handshake not complete")
|
|
||||||
ct = self._send_cs.encrypt_with_ad(b'', plaintext)
|
|
||||||
logging.debug(f"[ENCRYPT] {ct.hex()}")
|
|
||||||
self._dump_cipherstate("SEND→ after encrypt", self._send_cs)
|
|
||||||
self._send_all(sock, ct)
|
|
||||||
|
|
||||||
def receive(self, sock: socket.socket) -> bytes:
|
|
||||||
"""
|
|
||||||
Receive and decrypt a message.
|
|
||||||
"""
|
|
||||||
if self._recv_cs is None:
|
|
||||||
raise RuntimeError("Handshake not complete")
|
|
||||||
ct = self._recv_all(sock)
|
|
||||||
logging.debug(f"[CIPHERTEXT] {ct.hex()}")
|
|
||||||
self._dump_cipherstate("RECV→ before decrypt", self._recv_cs)
|
|
||||||
pt = self._recv_cs.decrypt_with_ad(b'', ct)
|
|
||||||
logging.debug(f"[DECRYPT] {pt!r}")
|
|
||||||
return pt
|
|
||||||
|
|
||||||
def _send_all(self, sock: socket.socket, data: bytes) -> None:
|
|
||||||
# Length-prefix (2 bytes big-endian) + data
|
|
||||||
length = len(data).to_bytes(2, 'big')
|
|
||||||
sock.sendall(length + data)
|
|
||||||
|
|
||||||
def _recv_all(self, sock: socket.socket) -> bytes:
|
|
||||||
# Read 2-byte length prefix, then the payload
|
|
||||||
hdr = self._read_exact(sock, 2)
|
|
||||||
length = int.from_bytes(hdr, 'big')
|
|
||||||
return self._read_exact(sock, length)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _read_exact(sock: socket.socket, n: int) -> bytes:
|
|
||||||
buf = bytearray()
|
|
||||||
while len(buf) < n:
|
|
||||||
chunk = sock.recv(n - len(buf))
|
|
||||||
if not chunk:
|
|
||||||
raise ConnectionError("Socket closed during read")
|
|
||||||
buf.extend(chunk)
|
|
||||||
return bytes(buf)
|
|
||||||
|
|
||||||
def _dump_cipherstate(self, label: str, cs: CipherState) -> None:
|
|
||||||
"""
|
|
||||||
Print the symmetric key (cs._k) and nonce counter (cs._n) for inspection.
|
|
||||||
"""
|
|
||||||
key = cs._key
|
|
||||||
nonce = getattr(cs, "_n", None)
|
|
||||||
if isinstance(key, (bytes, bytearray)):
|
|
||||||
key_hex = key.hex()
|
|
||||||
else:
|
|
||||||
key_hex = repr(key)
|
|
||||||
logging.debug(f"[{label}] key={key_hex}")
|
|
@ -1,99 +0,0 @@
|
|||||||
import socket
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
class P2PTransport:
|
|
||||||
def __init__(self, listen_port: int, peer_host: str, peer_port: int):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
listen_port: port to bind() and accept()
|
|
||||||
peer_host: host to dial()
|
|
||||||
peer_port: port to dial()
|
|
||||||
"""
|
|
||||||
self.listen_port = listen_port
|
|
||||||
self.peer_host = peer_host
|
|
||||||
self.peer_port = peer_port
|
|
||||||
|
|
||||||
def connect(self) -> (socket.socket, bool):
|
|
||||||
"""
|
|
||||||
Race bind+listen vs. dial:
|
|
||||||
- If dial succeeds first, return (sock, True) # we are initiator
|
|
||||||
- If accept succeeds first, return (sock, False) # we are responder
|
|
||||||
"""
|
|
||||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
||||||
server.bind(('0.0.0.0', self.listen_port))
|
|
||||||
server.listen(1)
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
event = threading.Event()
|
|
||||||
lock = threading.Lock()
|
|
||||||
|
|
||||||
def accept_thread():
|
|
||||||
try:
|
|
||||||
conn, _ = server.accept()
|
|
||||||
with lock:
|
|
||||||
if not event.is_set():
|
|
||||||
result['sock'] = conn
|
|
||||||
result['initiator'] = False
|
|
||||||
event.set()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def dial_thread():
|
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
sock.settimeout(1.0)
|
|
||||||
while not event.is_set():
|
|
||||||
try:
|
|
||||||
sock.connect((self.peer_host, self.peer_port))
|
|
||||||
with lock:
|
|
||||||
if not event.is_set():
|
|
||||||
result['sock'] = sock
|
|
||||||
result['initiator'] = True
|
|
||||||
event.set()
|
|
||||||
return
|
|
||||||
except (ConnectionRefusedError, socket.timeout):
|
|
||||||
time.sleep(0.1)
|
|
||||||
except Exception:
|
|
||||||
break
|
|
||||||
|
|
||||||
t1 = threading.Thread(target=accept_thread, daemon=True)
|
|
||||||
t2 = threading.Thread(target=dial_thread, daemon=True)
|
|
||||||
t1.start()
|
|
||||||
t2.start()
|
|
||||||
|
|
||||||
event.wait()
|
|
||||||
sock, initiator = result['sock'], result['initiator']
|
|
||||||
# close the listening socket—we’ve got our P2P link
|
|
||||||
server.close()
|
|
||||||
# ensure this socket is in blocking mode (no lingering timeouts)
|
|
||||||
sock.settimeout(None)
|
|
||||||
return sock, initiator
|
|
||||||
|
|
||||||
def send_packet(self, sock: socket.socket, data: bytes) -> None:
|
|
||||||
"""
|
|
||||||
Send a 2-byte big-endian length prefix followed by data.
|
|
||||||
"""
|
|
||||||
length = len(data).to_bytes(2, 'big')
|
|
||||||
sock.sendall(length + data)
|
|
||||||
|
|
||||||
def recv_packet(self, sock: socket.socket) -> bytes:
|
|
||||||
"""
|
|
||||||
Receive a 2-byte length prefix, then that many payload bytes.
|
|
||||||
"""
|
|
||||||
hdr = self._read_exact(sock, 2)
|
|
||||||
length = int.from_bytes(hdr, 'big')
|
|
||||||
return self._read_exact(sock, length)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _read_exact(sock: socket.socket, n: int) -> bytes:
|
|
||||||
buf = bytearray()
|
|
||||||
while len(buf) < n:
|
|
||||||
chunk = sock.recv(n - len(buf))
|
|
||||||
if not chunk:
|
|
||||||
raise ConnectionError("Socket closed during read")
|
|
||||||
buf.extend(chunk)
|
|
||||||
return bytes(buf)
|
|
||||||
|
|
||||||
def close(self, sock: socket.socket) -> None:
|
|
||||||
sock.close()
|
|
@ -1,7 +0,0 @@
|
|||||||
# System install
|
|
||||||
Docker
|
|
||||||
Python3
|
|
||||||
|
|
||||||
# Venv install
|
|
||||||
PyQt5
|
|
||||||
dissononce
|
|
Loading…
Reference in New Issue
Block a user