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

568 lines
19 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:io';
import 'dart:typed_data';
import 'package:convert/convert.dart';
import 'package:cryptography/cryptography.dart';
import 'package:encrypt/encrypt.dart';
import 'package:fast_rsa/fast_rsa.dart' as fastRsa;
import 'package:get/get.dart';
import 'package:pointycastle/asn1/asn1_parser.dart';
import 'package:pointycastle/asn1/primitives/asn1_integer.dart';
import 'package:pointycastle/asn1/primitives/asn1_sequence.dart';
import 'package:pointycastle/asymmetric/api.dart';
import 'package:pointycastle/digests/sha256.dart';
import 'package:pointycastle/export.dart' as pc;
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/scp_message.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';
import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:pointycastle/export.dart' as pc; // 为 Pointy Castle 添加命名空间
import 'package:asn1lib/asn1lib.dart' as asn1lib; // Prefix for asn1lib
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; // 本地端口
String localPublicHost = ''; // 本地公网ip地址
int heartbeatIntervalTime = 1; // 心跳包间隔时间s
Timer? _heartBeatTimer; // 心跳包定时器
bool _heartBeatTimerRunning = false; // 心跳包定时任务发送状态
String ToPeerId = ''; // 对端ID
String FromPeerId = ''; // 我的ID
// echo测试peer对端
final String echoPeerId = '3phX8Ng2cZHz5NtP8xAf6nYy2z1BYytoejgjoHrWMGhH';
// 星图服务初始化
Future<void> init() async {
// 节点注册
await _clientRegister();
// 中继查询
await _relayQuery();
// 上报
await reportInformation();
// 初始化udp服务
await _onlineRelayService();
}
/// 客户端注册
Future<void> _clientRegister() async {
_log(text: '开始注册客户端');
final StarChartRegisterNodeEntity requestStarChartRegisterNode =
await _requestStarChartRegisterNode();
await _saveStarChartRegisterNodeToStorage(requestStarChartRegisterNode);
_log(text: '获取到星图注册节点信息:$requestStarChartRegisterNode');
FromPeerId = requestStarChartRegisterNode.peer!.id ?? '';
ToPeerId = echoPeerId;
}
// 中继查询
Future<void> _relayQuery() async {
final RelayInfoEntity relayInfoEntity =
await StartChartApi.to.relayQueryInfo();
_saveRelayInfoEntityToStorage(relayInfoEntity);
if (relayInfoEntity.client_addr != null) {
localPublicHost = relayInfoEntity.client_addr!;
}
if (relayInfoEntity.relay_list?.length != 0) {
final data = relayInfoEntity.relay_list?[0];
final parseUdpUrl = _parseUdpUrl(data?.listenAddr ?? '');
remoteHost = parseUdpUrl['host'] ?? '';
remotePort = parseUdpUrl['port'] ?? '';
}
_log(text: '中继信息----》${relayInfoEntity}');
}
void closeUdpSocket() {
if (_udpSocket != null) {
_udpSocket?.close();
}
}
// 初始化udp
Future<void> _onlineRelayService() async {
var addressIListenFrom = InternetAddress.anyIPv4;
RawDatagramSocket.bind(addressIListenFrom, localPort)
.then((RawDatagramSocket socket) {
_udpSocket = socket;
/// 广播功能
_udpSocket!.broadcastEnabled = true;
/// 设置数据接收回调
_onReceiveData(_udpSocket!);
}).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}');
if (dg?.data != null) {
_log(text: '=============${bytesToHex(dg!.data)}');
final deserialize = ScpMessage.deserialize(dg!.data);
_log(text: 'Udp收到结构体数据---》$deserialize');
}
} catch (e) {
_log(text: '❌ Udp ----> $e');
}
}
});
}
// 上报信息至发现服务
Future<void> reportInformation() async {
_log(text: '上报信息至发现服务');
// 构建参数
ReportInformationData data = await _makeReportInformationData();
final response = await StartChartApi.to.reportInformation(
reportInformationData: data,
);
if (response.statusCode == 200) {
_log(text: '星图登录成功');
// 发送送上线消息
await _sendOnlineMessage();
// 发送心跳消息
_sendHeartbeatMessage();
}
}
// 发送上线消息
Future<void> _sendOnlineMessage() async {
// 组装上线消息
final message = MessageCommand.goOnlineRelay(
FromPeerId: FromPeerId, ToPeerId: ToPeerId);
await _sendMessage(message: message);
}
// 发送回声测试消息
void sendEchoMessage({required String ToPeerId}) async {
final message = MessageCommand.echoMessage(
ToPeerId: ToPeerId,
FromPeerId: FromPeerId,
);
await _sendMessage(message: message);
}
// 发送心跳包消息
void _sendHeartbeatMessage() async {
// 从缓存中获取中继信息
final RelayInfoEntity? relayInfoEntity = await Storage.getRelayInfo();
final String relayPeerId = relayInfoEntity?.relay_list?[0].peerID ?? '';
if (_heartBeatTimerRunning) {
_log(text: '心跳已经开始了. 请勿重复发送心跳包消息');
return;
}
_heartBeatTimer ??= Timer.periodic(
Duration(
seconds: heartbeatIntervalTime,
),
(Timer timer) async {
final List<int> message = MessageCommand.heartbeatMessage(
FromPeerId: FromPeerId,
ToPeerId: relayPeerId,
);
await _sendMessage(message: message);
},
);
_heartBeatTimerRunning = true;
}
// 停止定时发送心跳包
void stopHeartbeat() {
_heartBeatTimer?.cancel();
_heartBeatTimer = null; // 清除定时器引用
_heartBeatTimerRunning = false;
_log(text: '发送心跳包结束');
}
// 发送消息
Future<void> _sendMessage({required List<int> message}) async {
// _log(text: '发送给中继的消息体:${message},序列化之后的数据:【${bytesToHex(message)}】');
var result = await _udpSocket?.send(
message, InternetAddress(remoteHost), remotePort);
if (result != message.length) {
AppLog.log('❌Udp ----> send data error $result ${message.length}');
_udpSocket = null;
}
}
// 请求注册节点
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 {
// 从缓存中获取中继信息
final RelayInfoEntity? relayInfoEntity = await Storage.getRelayInfo();
// 获取公钥
final publicKey = await getPublicKey();
// 获取私钥
final privateKey = await getPrivateKey();
// 生成签名
final sign = await _generateSign(
currentTimestamp: relayInfoEntity!.time ?? 0,
privateKeyHex: privateKey,
);
// 获取本机所有ip地址和中继返回的外网地址
final List<ListenAddrData> listenAddrDataList =
await _makeListenAddrDataList();
//
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: relayInfoEntity.time ?? 0,
sign: sign,
);
return data;
}
// 获取本机所有ip地址和中继返回的外网地址
Future<List<ListenAddrData>> _makeListenAddrDataList() async {
final List<ListenAddrData> listenAddrDataList = [];
final List<String> localIp = await _getAllIpAddresses();
// 从缓存中获取中继信息取出返回的客户端ip地址
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) {
// 获取原始 IP 地址
String ipAddress = address.address;
// 解码 URL 编码的字符串
ipAddress = Uri.decodeFull(ipAddress);
// 移除 IPv6 地址中的接口标识符(如果有)
if (ipAddress.contains('%')) {
ipAddress = ipAddress.split('%').first;
}
// 确保 IP 地址不为空且不在排除列表中
if (ipAddress.isNotEmpty &&
!IpConstant.reportExcludeIp.contains(ipAddress)) {
ipAddresses.add(ipAddress);
}
}
}
} 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 privateKeyHex,
}) async {
String resultSign = '';
try {
// 1. 将 32 位时间戳以小端字节序编码为二进制数据
Uint8List signData = encodeTimestampToLittleEndianBytes(currentTimestamp);
// 2.将十六进制字符串转换为字节数组
List<int> privateKeyBytes = hexToBytes(privateKeyHex);
// 3.将私钥转换为 PEM 格式
final pemPrivateKey =
convertToPemPrivateKey(privateKeyBytes, isPKCS8: true);
// 4.签名
var result = await fastRsa.RSA
.signPKCS1v15Bytes(signData, fastRsa.Hash.SHA256, pemPrivateKey);
resultSign = hex.encode(result);
} catch (e) {
_log(text: '❌--->上报信息生成签名时出现错误: $e');
e.printError();
}
return resultSign ?? '';
}
// 将 32 位时间戳以小端字节序编码为二进制数据
Uint8List encodeTimestampToLittleEndianBytes(int timestamp) {
// 创建一个 4 字节的 ByteData 对象
ByteData byteData = ByteData(4);
// 将 32 位时间戳写入 ByteData使用小端字节序
byteData.setUint32(0, timestamp, Endian.little);
// 将 ByteData 转换为 Uint8List
Uint8List bytes = byteData.buffer.asUint8List();
return bytes;
}
// 将字节数组转换为 PEM 格式的公钥
String convertToPemPublicKey(List<int> publicKeyBytes,
{bool isPKCS8 = true}) {
// 将字节数组转换为Base64编码的字符串
String base64PublicKey = base64Encode(publicKeyBytes);
// 添加PEM格式的头尾标签
String pemHeader;
String pemFooter;
if (isPKCS8) {
// 添加PEM格式的头尾标签
pemHeader = "-----BEGIN PUBLIC KEY-----";
pemFooter = "-----END PUBLIC KEY-----";
} else {
// 添加PEM格式的头尾标签
pemHeader = "-----BEGIN RSA PUBLIC KEY-----";
pemFooter = "-----END RSA PUBLIC KEY-----";
}
// 将Base64字符串分行为每行64个字符
const lineLength = 64;
List<String> lines = [];
for (int i = 0; i < base64PublicKey.length; i += lineLength) {
int end = (i + lineLength < base64PublicKey.length)
? i + lineLength
: base64PublicKey.length;
lines.add(base64PublicKey.substring(i, end));
}
// 组合成完整的PEM格式字符串
return "$pemHeader\n${lines.join('\n')}\n$pemFooter";
}
String convertToPemPrivateKey(List<int> privateKeyBytes,
{bool isPKCS8 = true}) {
// 将字节数组转换为Base64编码的字符串
String base64PrivateKey = base64Encode(privateKeyBytes);
// 添加PEM格式的头尾标签
String pemHeader;
String pemFooter;
if (isPKCS8) {
pemHeader = "-----BEGIN PRIVATE KEY-----";
pemFooter = "-----END PRIVATE KEY-----";
} else {
pemHeader = "-----BEGIN RSA PRIVATE KEY-----";
pemFooter = "-----END RSA PRIVATE KEY-----";
}
// 将Base64字符串分行为每行64个字符
const lineLength = 64;
List<String> lines = []; // 用于存储每一行
for (int i = 0; i < base64PrivateKey.length; i += lineLength) {
int end = (i + lineLength < base64PrivateKey.length)
? i + lineLength
: base64PrivateKey.length;
lines.add(base64PrivateKey.substring(i, end));
}
// 组合成完整的PEM格式字符串
return "$pemHeader\n${lines.join('\n')}\n$pemFooter";
}
/// 自定义 PEM 格式的 RSA 私钥解析器
pc.RSAPrivateKey loadPrivateKey(String privateKeyHex) {
// 将十六进制字符串转换为字节数组
final uint8list = Uint8List.fromList(hexToBytes(privateKeyHex));
try {
// 使用 asn1lib 的 ASN1Parser 解析
final asn1Parser = asn1lib.ASN1Parser(uint8list);
final topLevelSeq = asn1Parser.nextObject() as asn1lib.ASN1Sequence;
final modulus = bytesToBigInt(
(topLevelSeq.elements[1] as asn1lib.ASN1Integer).valueBytes());
final privateExponent = bytesToBigInt(
(topLevelSeq.elements[3] as asn1lib.ASN1Integer).valueBytes());
final p = bytesToBigInt(
(topLevelSeq.elements[4] as asn1lib.ASN1Integer).valueBytes());
final q = bytesToBigInt(
(topLevelSeq.elements[5] as asn1lib.ASN1Integer).valueBytes());
return pc.RSAPrivateKey(modulus, privateExponent, p, q);
} catch (e) {
// 如果发生解码错误,打印错误信息
print("Error decoding private key: $e");
rethrow;
}
}
// 解析对端数据
Future<void> analyzeInformationOtherEnd() async {
await StartChartApi.to.analyzeInformationOtherEnd(peerId: ToPeerId);
}
// 将十六进制字符串转换为字节数组
List<int> hexToBytes(String hex) {
return List<int>.generate(hex.length ~/ 2,
(i) => int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16));
}
BigInt bytesToBigInt(Uint8List bytes) {
return BigInt.parse(
bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(),
radix: 16,
);
}
/// 使用 RSA 私钥进行 PKCS#1 v1.5 签名
Uint8List rsaSign(pc.RSAPrivateKey privateKey, List<int> data) {
final signer = pc.RSASigner(pc.SHA256Digest(), '06052b24030203')
..init(true, pc.PrivateKeyParameter<pc.RSAPrivateKey>(privateKey));
return signer.generateSignature(Uint8List.fromList(data)).bytes;
}
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 ?? '';
}
}