mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-04-20 22:13:48 +00:00
formats all dart files using `dart format .` from the root project dir this makes the code style repeatable by new contributors and makes PR review easier
208 lines
5.9 KiB
Dart
208 lines
5.9 KiB
Dart
import 'dart:typed_data';
|
|
import '../connector/meshcore_protocol.dart';
|
|
|
|
class Contact {
|
|
final Uint8List publicKey;
|
|
final String name;
|
|
final int type;
|
|
final int pathLength; // -1 = flood, 0+ = direct hops (from device)
|
|
final Uint8List path; // Path bytes from device
|
|
final int?
|
|
pathOverride; // User's path override: -1 = force flood, null = auto
|
|
final Uint8List? pathOverrideBytes; // User's path override bytes
|
|
final double? latitude;
|
|
final double? longitude;
|
|
final DateTime lastSeen;
|
|
final DateTime lastMessageAt;
|
|
|
|
Contact({
|
|
required this.publicKey,
|
|
required this.name,
|
|
required this.type,
|
|
required this.pathLength,
|
|
required this.path,
|
|
this.pathOverride,
|
|
this.pathOverrideBytes,
|
|
this.latitude,
|
|
this.longitude,
|
|
required this.lastSeen,
|
|
DateTime? lastMessageAt,
|
|
}) : lastMessageAt = lastMessageAt ?? lastSeen;
|
|
|
|
String get publicKeyHex => pubKeyToHex(publicKey);
|
|
|
|
String get typeLabel {
|
|
switch (type) {
|
|
case advTypeChat:
|
|
return 'Chat';
|
|
case advTypeRepeater:
|
|
return 'Repeater';
|
|
case advTypeRoom:
|
|
return 'Room';
|
|
case advTypeSensor:
|
|
return 'Sensor';
|
|
default:
|
|
return 'Unknown';
|
|
}
|
|
}
|
|
|
|
String get pathLabel {
|
|
if (pathOverride != null) {
|
|
if (pathOverride! < 0) return 'Flood (forced)';
|
|
if (pathOverride == 0) return 'Direct (forced)';
|
|
return '$pathOverride hops (forced)';
|
|
}
|
|
if (pathLength < 0) return 'Flood';
|
|
if (pathLength == 0) return 'Direct';
|
|
return '$pathLength hops';
|
|
}
|
|
|
|
bool get hasLocation => latitude != null && longitude != null;
|
|
|
|
Contact copyWith({
|
|
Uint8List? publicKey,
|
|
String? name,
|
|
int? type,
|
|
int? pathLength,
|
|
Uint8List? path,
|
|
int? pathOverride,
|
|
Uint8List? pathOverrideBytes,
|
|
bool clearPathOverride = false,
|
|
double? latitude,
|
|
double? longitude,
|
|
DateTime? lastSeen,
|
|
DateTime? lastMessageAt,
|
|
}) {
|
|
return Contact(
|
|
publicKey: publicKey ?? this.publicKey,
|
|
name: name ?? this.name,
|
|
type: type ?? this.type,
|
|
pathLength: pathLength ?? this.pathLength,
|
|
path: path ?? this.path,
|
|
pathOverride: clearPathOverride
|
|
? null
|
|
: (pathOverride ?? this.pathOverride),
|
|
pathOverrideBytes: clearPathOverride
|
|
? null
|
|
: (pathOverrideBytes ?? this.pathOverrideBytes),
|
|
latitude: latitude ?? this.latitude,
|
|
longitude: longitude ?? this.longitude,
|
|
lastSeen: lastSeen ?? this.lastSeen,
|
|
lastMessageAt: lastMessageAt ?? this.lastMessageAt,
|
|
);
|
|
}
|
|
|
|
String get pathIdList {
|
|
final pathBytes = _pathBytesForDisplay;
|
|
if (pathBytes.isEmpty) return '';
|
|
final parts = <String>[];
|
|
final groupSize = pathHashSize;
|
|
for (int i = 0; i < pathBytes.length; i += groupSize) {
|
|
final end = (i + groupSize) <= pathBytes.length
|
|
? (i + groupSize)
|
|
: pathBytes.length;
|
|
final chunk = pathBytes.sublist(i, end);
|
|
parts.add(
|
|
chunk
|
|
.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase())
|
|
.join(),
|
|
);
|
|
}
|
|
return parts.join(',');
|
|
}
|
|
|
|
String get shortPubKeyHex {
|
|
return "<${publicKeyHex.substring(0, 8)}...${publicKeyHex.substring(publicKeyHex.length - 8)}>";
|
|
}
|
|
|
|
Uint8List? get traceRouteBytes {
|
|
final pathBytes = _pathBytesForDisplay;
|
|
Uint8List? traceBytes;
|
|
|
|
if (pathLength <= 0) {
|
|
traceBytes = Uint8List(1);
|
|
traceBytes[0] = publicKey[0];
|
|
return traceBytes;
|
|
}
|
|
|
|
if (type == advTypeRepeater || type == advTypeRoom) {
|
|
final len = (pathBytes.length + pathBytes.length + 1);
|
|
traceBytes = Uint8List(len);
|
|
traceBytes[pathBytes.length] = publicKey[0];
|
|
for (int i = 0; i < pathBytes.length; i++) {
|
|
traceBytes[i] = pathBytes[i];
|
|
if (i < pathBytes.length) {
|
|
traceBytes[len - 1 - i] = pathBytes[i];
|
|
}
|
|
}
|
|
} else {
|
|
if (pathBytes.length < 2) {
|
|
return pathBytes[0] == 0 ? null : pathBytes;
|
|
}
|
|
final len = (pathBytes.length + pathBytes.length - 1);
|
|
traceBytes = Uint8List(len);
|
|
for (int i = 0; i < pathBytes.length; i++) {
|
|
traceBytes[i] = pathBytes[i];
|
|
if (i < pathBytes.length - 1) {
|
|
traceBytes[len - 1 - i] = pathBytes[i];
|
|
}
|
|
}
|
|
}
|
|
return traceBytes;
|
|
}
|
|
|
|
Uint8List get _pathBytesForDisplay {
|
|
if (pathOverride != null) {
|
|
if (pathOverride! < 0) return Uint8List(0);
|
|
return pathOverrideBytes ?? Uint8List(0);
|
|
}
|
|
return path;
|
|
}
|
|
|
|
static Contact? fromFrame(Uint8List data) {
|
|
if (data.length < contactFrameSize) return null;
|
|
if (data[0] != respCodeContact) return null;
|
|
|
|
final pubKey = Uint8List.fromList(
|
|
data.sublist(contactPubKeyOffset, contactPubKeyOffset + pubKeySize),
|
|
);
|
|
final type = data[contactTypeOffset];
|
|
final pathLen = data[contactPathLenOffset].toSigned(8);
|
|
final safePathLen = pathLen > 0
|
|
? (pathLen > maxPathSize ? maxPathSize : pathLen)
|
|
: 0;
|
|
final pathBytes = safePathLen > 0
|
|
? Uint8List.fromList(
|
|
data.sublist(contactPathOffset, contactPathOffset + safePathLen),
|
|
)
|
|
: Uint8List(0);
|
|
final name = readCString(data, contactNameOffset, maxNameSize);
|
|
final lastmod = readUint32LE(data, contactLastmodOffset);
|
|
|
|
double? lat, lon;
|
|
final latRaw = readInt32LE(data, contactLatOffset);
|
|
final lonRaw = readInt32LE(data, contactLonOffset);
|
|
if (latRaw != 0 || lonRaw != 0) {
|
|
lat = latRaw / 1e6;
|
|
lon = lonRaw / 1e6;
|
|
}
|
|
|
|
return Contact(
|
|
publicKey: pubKey,
|
|
name: name.isEmpty ? 'Unknown' : name,
|
|
type: type,
|
|
pathLength: pathLen,
|
|
path: pathBytes,
|
|
latitude: lat,
|
|
longitude: lon,
|
|
lastSeen: DateTime.fromMillisecondsSinceEpoch(lastmod * 1000),
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
other is Contact && publicKeyHex == other.publicKeyHex;
|
|
|
|
@override
|
|
int get hashCode => publicKeyHex.hashCode;
|
|
}
|