/// /// Channel-level CCC profile derivation. /// import 'package:letusmsg/ccc/ccc_kdf.dart'; import 'package:letusmsg/ccc/cipher_constants.dart'; import 'package:letusmsg/ccc/ccc_provider_spec.dart'; import 'package:letusmsg/data/ccc_data.dart'; /// One resolved cipher execution step for a channel CCC route. /// /// The step identifies a primary provider and optional ordered fallbacks. class CccRouteStep { final int cipher; final CccCryptoProvider provider; final List fallbackProviders; const CccRouteStep({ required this.cipher, required this.provider, required this.fallbackProviders, }); Map toMap() { return { 'cipher': cipher, 'provider': provider.name, 'fallbackProviders': fallbackProviders.map((item) => item.name).toList(growable: false), }; } } /// Concrete cipher configuration derived from per-channel `CCCData`. class ChannelCccProfile { final int combo; final int iterations; final CccExecutionMode executionMode; final bool allowFallback; final List providers; final List route; final List cipherSequence; final Map cipherParams; const ChannelCccProfile({ required this.combo, required this.iterations, required this.executionMode, required this.allowFallback, required this.providers, required this.route, required this.cipherSequence, required this.cipherParams, }); static const int _maxIterations = 16; factory ChannelCccProfile.fromCccData(CCCData? cccData) { final combo = cccData?.combo ?? 0; final rawIterations = cccData?.iterrations ?? 0; final kdfFunctionValue = normalizeCccKdfFunctionValue(cccData?.kdfFunction); final kdfFunction = cccKdfFunctionFromInt(kdfFunctionValue); final executionMode = cccExecutionModeFromString(cccData?.executionMode ?? 'auto'); final iterations = rawIterations <= 0 ? 1 : rawIterations.clamp(1, _maxIterations); final allowFallback = executionMode != CccExecutionMode.strict; final providers = _providersForCombo(combo); final route = _resolveRoute(providers, executionMode: executionMode, allowFallback: allowFallback); final baseSequence = route.map((item) => item.cipher).toList(growable: false); final expandedSequence = []; for (var i = 0; i < iterations; i++) { expandedSequence.addAll(baseSequence); } final params = { ...CipherConstants.DEFAULT_CIPHER_PARAMS, 'ccc_combo': combo, 'ccc_iterations': iterations, 'ccc_kdf_function': kdfFunction.name, 'ccc_kdf_function_value': kdfFunction.value, 'ccc_execution_mode': executionMode.name, 'ccc_allow_fallback': allowFallback, 'ccc_providers': providers.map((provider) => provider.toMap()).toList(growable: false), 'ccc_route': route.map((step) => step.toMap()).toList(growable: false), // Phase 3: length-hiding padding parameters 'ccc_min_padding_bytes': cccData?.minPaddingBytes ?? 32, 'ccc_block_size': cccData?.blockSize ?? 256, }; return ChannelCccProfile( combo: combo, iterations: iterations, executionMode: executionMode, allowFallback: allowFallback, providers: providers, route: route, cipherSequence: expandedSequence, cipherParams: params, ); } static List _resolveRoute( List providers, { required CccExecutionMode executionMode, required bool allowFallback, }) { final strictRoute = []; for (final provider in providers) { for (final cipher in provider.ciphers) { strictRoute.add(CccRouteStep( cipher: cipher, provider: provider.provider, fallbackProviders: const [], )); } } if (executionMode == CccExecutionMode.strict) { return strictRoute; } final optimizedRoute = []; for (final step in strictRoute) { final ordered = _orderedProviderCandidates( cipher: step.cipher, configuredProviders: providers.map((item) => item.provider).toSet(), executionMode: executionMode, ); final primary = ordered.isNotEmpty ? ordered.first : step.provider; final fallbacks = allowFallback ? ordered.where((provider) => provider != primary).toList(growable: false) : const []; optimizedRoute.add(CccRouteStep( cipher: step.cipher, provider: primary, fallbackProviders: fallbacks, )); } return optimizedRoute; } static List _orderedProviderCandidates({ required int cipher, required Set configuredProviders, required CccExecutionMode executionMode, }) { final configured = configuredProviders.where((provider) { return CccProviderCatalog.supports(provider, cipher); }).toList(growable: false); if (executionMode == CccExecutionMode.efficient) { final rankedConfigured = List.from(configured) ..sort((a, b) { final capA = CccProviderCatalog.capability(a, cipher); final capB = CccProviderCatalog.capability(b, cipher); final perf = (capB?.efficiencyScore ?? 0).compareTo(capA?.efficiencyScore ?? 0); if (perf != 0) return perf; return (capB?.reliabilityScore ?? 0).compareTo(capA?.reliabilityScore ?? 0); }); return rankedConfigured; } final availableRanked = CccProviderCatalog.providersSupporting(cipher, availableOnly: true) .where(configuredProviders.contains) .toList(growable: false); final unavailableRanked = CccProviderCatalog.providersSupporting(cipher, availableOnly: false) .where((provider) => configuredProviders.contains(provider) && !availableRanked.contains(provider)) .toList(growable: false); return [ ...availableRanked, ...unavailableRanked, ]; } static List _providersForCombo(int combo) { switch (combo) { case 0: // Combo 0 is reserved as the plaintext / unencrypted legacy combo. // It produces an empty cipher sequence so the CCC pipeline serializes // and deserializes JSON only, with no encryption layers applied. // Backwards-compatible with all channels created before Normal Mode. // When Normal Mode is complete, CCCData.blank() will be updated to a // real encrypted combo (e.g. combo 5 or higher) and combo 0 will // remain available only for migration/legacy channels. return []; case 1: return [ CccProviderSpec( provider: CccCryptoProvider.wolfssl, ciphers: [ CipherConstants.AES_GCM_256, CipherConstants.CHACHA20_POLY1305, ], ), CccProviderSpec( provider: CccCryptoProvider.cryptography, ciphers: [ CipherConstants.HMAC_SHA512, CipherConstants.BLAKE2B, ], ), ]; case 2: return [ CccProviderSpec( provider: CccCryptoProvider.boringssl, ciphers: [ CipherConstants.CHACHA20_POLY1305, CipherConstants.XCHACHA20_POLY1305, ], ), CccProviderSpec( provider: CccCryptoProvider.cryptography, ciphers: [ CipherConstants.AES_GCM_256, ], ), ]; case 3: return [ CccProviderSpec( provider: CccCryptoProvider.openssl, ciphers: [ CipherConstants.AES_GCM_256, ], ), CccProviderSpec( provider: CccCryptoProvider.wolfssl, ciphers: [ CipherConstants.CHACHA20_POLY1305, ], ), CccProviderSpec( provider: CccCryptoProvider.cryptography, ciphers: [ CipherConstants.BLAKE2B, ], ), ]; case 4: return [ CccProviderSpec( provider: CccCryptoProvider.wolfssl, ciphers: [ CipherConstants.XCHACHA20_POLY1305, ], ), CccProviderSpec( provider: CccCryptoProvider.openssl, ciphers: [ CipherConstants.HMAC_SHA512, ], ), CccProviderSpec( provider: CccCryptoProvider.cryptography, ciphers: [ CipherConstants.BLAKE2B, ], ), ]; // --- Basic / user-selectable combos (5-9) --- case 5: // Basic: single AES-256-GCM return [ CccProviderSpec( provider: CccCryptoProvider.cryptography, ciphers: List.from(CipherConstants.BASIC_AES_SEQUENCE), ), ]; case 6: // Basic: single ChaCha20-Poly1305 return [ CccProviderSpec( provider: CccCryptoProvider.cryptography, ciphers: List.from(CipherConstants.BASIC_CHACHA_SEQUENCE), ), ]; case 7: // Basic: single XChaCha20-Poly1305 return [ CccProviderSpec( provider: CccCryptoProvider.cryptography, ciphers: List.from(CipherConstants.BASIC_XCHACHA_SEQUENCE), ), ]; case 8: // Dual AEAD: AES-256-GCM + ChaCha20-Poly1305 return [ CccProviderSpec( provider: CccCryptoProvider.cryptography, ciphers: List.from(CipherConstants.DUAL_AEAD_SEQUENCE), ), ]; case 9: // Triple AEAD: AES + ChaCha20 + XChaCha20 return [ CccProviderSpec( provider: CccCryptoProvider.cryptography, ciphers: List.from(CipherConstants.TRIPLE_AEAD_SEQUENCE), ), ]; default: // Unknown combo falls back to standard 5-layer return [ CccProviderSpec( provider: CccCryptoProvider.cryptography, ciphers: List.from(CipherConstants.PHASE1_SEQUENCE), ), ]; } } }