380 lines
12 KiB
Dart
380 lines
12 KiB
Dart
import 'dart:async';
|
||
import 'dart:convert';
|
||
import 'dart:io';
|
||
import 'dart:typed_data';
|
||
|
||
import 'package:convert/convert.dart';
|
||
import 'package:crypto/crypto.dart';
|
||
import 'package:encrypt/encrypt.dart';
|
||
import 'package:pointycastle/asymmetric/api.dart';
|
||
import 'package:star_lock/app_settings/app_settings.dart';
|
||
import 'package:star_lock/flavors.dart';
|
||
import 'package:star_lock/network/start_chart_api.dart';
|
||
import 'package:star_lock/talk/startChart/command/message_command.dart';
|
||
import 'package:star_lock/talk/startChart/constant/ip_constant.dart';
|
||
import 'package:star_lock/talk/startChart/constant/listen_addr_type_constant.dart';
|
||
import 'package:star_lock/talk/startChart/entity/relay_info_entity.dart';
|
||
import 'package:star_lock/talk/startChart/entity/report_information_data.dart';
|
||
import 'package:star_lock/talk/startChart/entity/star_chart_register_node_entity.dart';
|
||
import 'package:star_lock/tools/deviceInfo_utils.dart';
|
||
import 'package:star_lock/tools/storage.dart';
|
||
import 'package:uuid/uuid.dart';
|
||
|
||
class StartChartManage {
|
||
// 私有构造函数,防止外部直接new对象
|
||
StartChartManage._internal();
|
||
|
||
// 单例对象
|
||
static final StartChartManage _instance = StartChartManage._internal();
|
||
|
||
// 工厂构造函数,返回单例对象
|
||
factory StartChartManage() {
|
||
return _instance;
|
||
}
|
||
|
||
// 产品昵称
|
||
final String _productName = F.navTitle;
|
||
|
||
RawDatagramSocket? _udpSocket;
|
||
late String remoteHost = ''; // 远程主机地址(服务器返回)
|
||
late int remotePort = 0; // 远程主机端口(服务器返回)
|
||
final int localPort = 62289; // 本地端口
|
||
|
||
int heartbeatIntervalTime = 1; // 心跳包间隔时间(s)
|
||
Timer? _heartBeatTimer; // 心跳包定时器
|
||
bool _heartBeatTimerRunning = false; // 心跳包定时任务发送状态
|
||
|
||
String ToPeerId = ''; // 对端ID
|
||
String FromPeerId = ''; // 我的ID
|
||
/// 客户端注册
|
||
Future<void> clientRegister() async {
|
||
// 从缓存中获取星图注册节点信息
|
||
final StarChartRegisterNodeEntity? starChartRegisterNodeInfo =
|
||
await Storage.getStarChartRegisterNodeInfo();
|
||
if (starChartRegisterNodeInfo == null) {
|
||
_log(text: '开始注册客户端');
|
||
final StarChartRegisterNodeEntity requestStarChartRegisterNode =
|
||
await _requestStarChartRegisterNode();
|
||
_saveStarChartRegisterNodeToStorage(requestStarChartRegisterNode);
|
||
} else {
|
||
final entity = await Storage.getStarChartRegisterNodeInfo();
|
||
_log(text: '获取到星图注册节点信息:$entity');
|
||
}
|
||
}
|
||
|
||
// 中继查询
|
||
Future<void> relayQuery() async {
|
||
final RelayInfoEntity relayInfoEntity =
|
||
await StartChartApi.to.relayQueryInfo();
|
||
_saveRelayInfoEntityToStorage(relayInfoEntity);
|
||
|
||
if (relayInfoEntity.relay_list?.length != 0) {
|
||
final data = relayInfoEntity.relay_list?[0];
|
||
FromPeerId = data?.peerID ?? '';
|
||
final parseUdpUrl = _parseUdpUrl(data?.listenAddr ?? '');
|
||
remoteHost = parseUdpUrl['host'] ?? '';
|
||
remotePort = parseUdpUrl['port'] ?? '';
|
||
_log(text: '中继信息:${data}');
|
||
}
|
||
}
|
||
|
||
void closeUdpSocket() {
|
||
if (_udpSocket != null) {
|
||
_udpSocket?.close();
|
||
}
|
||
}
|
||
|
||
// 在中继服务器中上线
|
||
Future<void> onlineRelayService() async {
|
||
await relayQuery();
|
||
var addressIListenFrom = InternetAddress.anyIPv4;
|
||
RawDatagramSocket.bind(addressIListenFrom, localPort)
|
||
.then((RawDatagramSocket socket) {
|
||
_udpSocket = socket;
|
||
|
||
/// 广播功能
|
||
_udpSocket!.broadcastEnabled = true;
|
||
|
||
/// 设置数据接收回调
|
||
_onReceiveData(_udpSocket!);
|
||
|
||
// 发送上线消息
|
||
//_sendOnlineMessage();
|
||
// 发送回声测试消息
|
||
//_sendEchoMessage();
|
||
|
||
// 上报信息
|
||
reportInformation();
|
||
}).catchError((error) {
|
||
_log(text: 'Failed to bind UDP socket: $error');
|
||
});
|
||
}
|
||
|
||
// 接收返回的数据
|
||
void _onReceiveData(RawDatagramSocket socket) {
|
||
socket.listen((RawSocketEvent event) {
|
||
if (event == RawSocketEvent.read) {
|
||
Datagram? dg = socket.receive();
|
||
try {
|
||
_log(text: '收到消息---> 长度:${dg?.data?.length}, 数据:${dg?.data}');
|
||
} catch (e) {
|
||
_log(text: '❌ Udp ----> $e');
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 上报信息至发现服务
|
||
Future<void> reportInformation() async {
|
||
_log(text: '上报信息至发现服务');
|
||
// 构建参数
|
||
ReportInformationData data = await _makeReportInformationData();
|
||
await StartChartApi.to.reportInformation(
|
||
reportInformationData: data,
|
||
);
|
||
}
|
||
|
||
// 发送上线消息
|
||
void _sendOnlineMessage() {
|
||
// 组装上线消息
|
||
final message = MessageCommand.goOnlineRelay();
|
||
_sendMessage(message: message);
|
||
}
|
||
|
||
// 发送回声测试消息
|
||
void _sendEchoMessage() {
|
||
final message = MessageCommand.echoMessage(
|
||
ToPeerId: ToPeerId,
|
||
FromPeerId: FromPeerId,
|
||
);
|
||
_sendMessage(message: message);
|
||
}
|
||
|
||
// 发送心跳包消息
|
||
void sendHeartbeatMessage() {
|
||
if (_heartBeatTimerRunning) {
|
||
_log(text: '心跳已经开始了. 请勿重复发送心跳包消息');
|
||
return;
|
||
}
|
||
_heartBeatTimer ??= Timer.periodic(
|
||
Duration(
|
||
seconds: heartbeatIntervalTime,
|
||
),
|
||
(Timer timer) {
|
||
final List<int> message = MessageCommand.heartbeatMessage();
|
||
_sendMessage(message: message);
|
||
},
|
||
);
|
||
_heartBeatTimerRunning = true;
|
||
}
|
||
|
||
// 停止定时发送心跳包
|
||
void stopHeartbeat() {
|
||
_heartBeatTimer?.cancel();
|
||
_heartBeatTimer = null; // 清除定时器引用
|
||
_heartBeatTimerRunning = false;
|
||
_log(text: '发送心跳包结束');
|
||
}
|
||
|
||
// 发送消息
|
||
void _sendMessage({required List<int> message}) {
|
||
_log(text: '发送给中继的消息体:${message},序列化之后的数据:【${bytesToHex(message)}】');
|
||
_udpSocket!.send(message, InternetAddress(remoteHost), remotePort);
|
||
}
|
||
|
||
// 请求注册节点
|
||
Future<StarChartRegisterNodeEntity> _requestStarChartRegisterNode() async {
|
||
// 获取设备信息
|
||
final Map<String, String> deviceInfo = await _getDeviceInfo();
|
||
// 发送注册节点请求
|
||
final StarChartRegisterNodeEntity response =
|
||
await StartChartApi.to.starChartRegisterNode(
|
||
product: _productName,
|
||
model: '${deviceInfo['brand']}_${deviceInfo['model']}',
|
||
name: '${deviceInfo['id']}',
|
||
unique: deviceInfo['id'] ?? Uuid().v1(),
|
||
);
|
||
return response;
|
||
}
|
||
|
||
// 保存星图注册节点信息至缓存
|
||
Future<void> _saveStarChartRegisterNodeToStorage(
|
||
StarChartRegisterNodeEntity starChartRegisterNodeEntity) async {
|
||
if (starChartRegisterNodeEntity != null) {
|
||
await Storage.saveStarChartRegisterNodeInfo(starChartRegisterNodeEntity);
|
||
_log(text: '注册成功');
|
||
}
|
||
}
|
||
|
||
// 保存星图中继服务器信息至缓存
|
||
Future<void> _saveRelayInfoEntityToStorage(
|
||
RelayInfoEntity relayInfoEntity) async {
|
||
if (relayInfoEntity != null) {
|
||
await Storage.saveRelayInfo(relayInfoEntity);
|
||
}
|
||
}
|
||
|
||
// 构造上报信息数据参数
|
||
Future<ReportInformationData> _makeReportInformationData() async {
|
||
// 获取当前时间戳
|
||
int currentTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||
// 获取公钥
|
||
final publicKey = await getPublicKey();
|
||
// 获取私钥
|
||
final privateKey = await getPrivateKey();
|
||
// 生成签名
|
||
final sign = await _generateSign(
|
||
currentTimestamp: currentTimestamp,
|
||
privateKey: privateKey,
|
||
);
|
||
|
||
// 获取本机所有ip地址和中继返回的外网地址
|
||
final List<ListenAddrData> listenAddrDataList =
|
||
await _makeListenAddrDataList();
|
||
|
||
// 从缓存中获取中继信息
|
||
final RelayInfoEntity? relayInfoEntity = await Storage.getRelayInfo();
|
||
final RelayServiceData relayServiceData = RelayServiceData(
|
||
name: relayInfoEntity?.relay_list?[0].name ?? '',
|
||
listen_addr: relayInfoEntity?.relay_list?[0].listenAddr ?? '',
|
||
peers_max: relayInfoEntity?.relay_list?[0].peerMax ?? 0,
|
||
peers_current: relayInfoEntity?.relay_list?[0].peerCurrent ?? 0,
|
||
);
|
||
|
||
ReportInformationData data = ReportInformationData(
|
||
id: FromPeerId,
|
||
public_key: publicKey,
|
||
listen_addr: listenAddrDataList,
|
||
relay_service: relayServiceData,
|
||
time: currentTimestamp,
|
||
sign: sign,
|
||
);
|
||
|
||
return data;
|
||
}
|
||
|
||
// 获取本机所有ip地址和中继返回的外网地址
|
||
Future<List<ListenAddrData>> _makeListenAddrDataList() async {
|
||
final List<ListenAddrData> listenAddrDataList = [];
|
||
final List<String> localIp = await _getAllIpAddresses();
|
||
// 从缓存中获取中继信息
|
||
final RelayInfoEntity? relayInfoEntity = await Storage.getRelayInfo();
|
||
if (relayInfoEntity != null && relayInfoEntity.client_addr != null) {
|
||
listenAddrDataList.add(
|
||
ListenAddrData(
|
||
type: ListenAddrTypeConstant.relay,
|
||
address: IpConstant.udpUrl +
|
||
relayInfoEntity.client_addr! +
|
||
':' +
|
||
localPort.toString(),
|
||
),
|
||
);
|
||
}
|
||
localIp.forEach((element) {
|
||
listenAddrDataList.add(
|
||
ListenAddrData(
|
||
type: ListenAddrTypeConstant.local,
|
||
address: IpConstant.udpUrl + element + ':' + localPort.toString(),
|
||
),
|
||
);
|
||
});
|
||
return listenAddrDataList ?? [];
|
||
}
|
||
|
||
/// 获取本机所有ip
|
||
Future<List<String>> _getAllIpAddresses() async {
|
||
final List<String> ipAddresses = [];
|
||
try {
|
||
final List<NetworkInterface> interfaces = await NetworkInterface.list(
|
||
includeLoopback: true,
|
||
type: InternetAddressType.any,
|
||
);
|
||
|
||
for (final interface in interfaces) {
|
||
for (final address in interface.addresses) {
|
||
if (address.address.isNotEmpty &&
|
||
!IpConstant.reportExcludeIp.contains(address.address)) {
|
||
ipAddresses.add(address.address);
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {
|
||
_log(text: '❌--->获取本机IP时出现错误: $e');
|
||
}
|
||
return ipAddresses ?? [];
|
||
}
|
||
|
||
void _log({required String text}) {
|
||
AppLog.log('$_productName=====${text}');
|
||
}
|
||
|
||
/// 获取设备信息
|
||
Future<Map<String, String>> _getDeviceInfo() async {
|
||
final Map<String, String> deviceInfo =
|
||
await DeviceInfoUtils.getDeviceInfo();
|
||
return deviceInfo;
|
||
}
|
||
|
||
/// 解析 UDP URL 并提取 IP 地址和端口号
|
||
Map<String, dynamic> _parseUdpUrl(String url) {
|
||
// 使用正则表达式匹配 IP 地址和端口号
|
||
final regex = RegExp(r'udp://(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)')
|
||
.firstMatch(url);
|
||
|
||
if (regex != null) {
|
||
final ip = regex.group(1);
|
||
final portStr = regex.group(2);
|
||
final port = int.tryParse(portStr ?? '');
|
||
|
||
if (ip != null && port != null) {
|
||
return {'host': ip, 'port': port};
|
||
}
|
||
}
|
||
|
||
throw FormatException('无法解析 URL 格式: $url');
|
||
}
|
||
|
||
String bytesToHex(List<int> bytes) {
|
||
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join('');
|
||
}
|
||
|
||
// 生成签名sing
|
||
Future<String> _generateSign({
|
||
required int currentTimestamp,
|
||
required String privateKey,
|
||
}) async {
|
||
String resultSign = '';
|
||
try {
|
||
// 2. 将时间戳编码为小端字节序(Little Endian)
|
||
Uint8List signData = Uint8List(4);
|
||
signData.buffer
|
||
.asByteData()
|
||
.setUint32(0, currentTimestamp, Endian.little);
|
||
|
||
// 3. 使用 SHA-256 对 signData 进行哈希运算
|
||
final sha256Hash = sha256.convert(signData);
|
||
var parser = RSAKeyParser();
|
||
final RSAPrivateKey parsePrivateKey =
|
||
parser.parse('-----BEGIN RSA PRIVATE KEY-----\n' + privateKey)
|
||
as RSAPrivateKey;
|
||
} catch (e) {
|
||
_log(text: '❌--->生成签名时出现错误: $e');
|
||
}
|
||
return resultSign ?? '';
|
||
}
|
||
|
||
Future<String> getPublicKey() async {
|
||
// 从缓存中获取星图注册节点信息
|
||
final StarChartRegisterNodeEntity? starChartRegisterNodeInfo =
|
||
await Storage.getStarChartRegisterNodeInfo();
|
||
return starChartRegisterNodeInfo?.peer?.publicKey ?? '';
|
||
}
|
||
|
||
Future<String> getPrivateKey() async {
|
||
// 从缓存中获取星图注册节点信息
|
||
final StarChartRegisterNodeEntity? starChartRegisterNodeInfo =
|
||
await Storage.getStarChartRegisterNodeInfo();
|
||
return starChartRegisterNodeInfo?.peer?.privateKey ?? '';
|
||
}
|
||
}
|