meshcore-open/lib/models/channel.dart
zjs81 14ff8250c0 Add support for private and hashtag channels in localization and channel management
- Updated Polish, Portuguese, Slovak, Slovenian, Swedish, and Chinese localization files to include new strings for creating and joining private channels, as well as joining hashtag channels.
- Enhanced the channel management UI to allow users to create and join private channels, join public channels, and join channels via hashtags.
- Implemented PSK derivation from hashtags using SHA256 in the Channel model.
- Improved the translation script to handle missing keys and translate all locales efficiently.
2026-01-16 19:06:39 -07:00

85 lines
2.3 KiB
Dart

import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart' as crypto;
import '../connector/meshcore_protocol.dart';
class Channel {
final int index;
final String name;
final Uint8List psk; // 16 bytes
Channel({
required this.index,
required this.name,
required this.psk,
});
String get pskHex => _bytesToHex(psk);
bool get isEmpty => name.isEmpty && psk.every((b) => b == 0);
bool get isPublicChannel => pskHex == publicChannelPsk;
static Channel? fromFrame(Uint8List data) {
// CHANNEL_INFO format:
// [0] = RESP_CODE_CHANNEL_INFO (18)
// [1] = channel_idx
// [2-33] = name (32 bytes, null-terminated)
// [34-49] = psk (16 bytes)
if (data.length < 50) return null;
if (data[0] != respCodeChannelInfo) return null;
final index = data[1];
final name = readCString(data, 2, 32);
final psk = Uint8List.fromList(data.sublist(34, 50));
return Channel(index: index, name: name, psk: psk);
}
static Channel empty(int index) {
return Channel(
index: index,
name: '',
psk: Uint8List(16),
);
}
static Channel fromHex(int index, String name, String pskHex) {
final psk = parsePskHex(pskHex);
return Channel(index: index, name: name, psk: psk);
}
static Uint8List parsePskHex(String hex) {
final cleaned = hex.replaceAll(RegExp(r'[^0-9a-fA-F]'), '');
if (cleaned.length != 32) {
throw const FormatException('PSK must be 32 hex characters');
}
final bytes = Uint8List(16);
for (int i = 0; i < 16; i++) {
final start = i * 2;
bytes[i] = int.parse(cleaned.substring(start, start + 2), radix: 16);
}
return bytes;
}
/// Derive PSK from hashtag name using SHA256.
/// The hashtag is normalized to include '#' prefix.
/// Returns first 16 bytes of SHA256 hash as PSK.
static Uint8List derivePskFromHashtag(String hashtag) {
final name = hashtag.startsWith('#') ? hashtag : '#$hashtag';
final hash = crypto.sha256.convert(utf8.encode(name)).bytes;
return Uint8List.fromList(hash.sublist(0, 16));
}
static String formatPskHex(Uint8List psk) {
return _bytesToHex(psk);
}
static String _bytesToHex(Uint8List bytes) {
return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
}
static const String publicChannelPsk = '8b3387e9c5cdea6ac9e5edbaa115cd72';
}