app-starlock/lib/talk/startChart/start_chart_manage.dart

380 lines
12 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 ?? '';
}
}