From a92ae906ef141bc75ec5764c743babd08ad2f047 Mon Sep 17 00:00:00 2001 From: liyi Date: Sat, 30 Nov 2024 15:39:06 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E6=96=B0=E7=9A=84?= =?UTF-8?q?=E4=B8=AD=E7=BB=A7=E5=8D=8F=E8=AE=AE=E4=B8=AD=E7=9A=84=E4=B8=8A?= =?UTF-8?q?=E6=8A=A5=E7=AD=BE=E5=90=8D=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/login/login/starLock_login_page.dart | 22 +-- lib/network/start_chart_api.dart | 6 +- .../startChart/command/message_command.dart | 31 ++-- .../constant/message_type_constant.dart | 23 +++ lib/talk/startChart/entity/scp_message.dart | 99 +++++++++++- lib/talk/startChart/start_chart_manage.dart | 143 ++++++++---------- 6 files changed, 215 insertions(+), 109 deletions(-) create mode 100644 lib/talk/startChart/constant/message_type_constant.dart diff --git a/lib/login/login/starLock_login_page.dart b/lib/login/login/starLock_login_page.dart index ca3615b2..701eddc9 100755 --- a/lib/login/login/starLock_login_page.dart +++ b/lib/login/login/starLock_login_page.dart @@ -234,28 +234,20 @@ class _StarLockLoginPageState extends State { } : null)), SubmitBtn( - btnName: '发送上线请求', + btnName: '初始化星图服务', onClick: () async { - // 注册星图节点信息 - await StartChartManage().clientRegister(); - // 查询中继信息 - await StartChartManage().relayQuery(); - // 发送上线请求 - await StartChartManage().onlineRelayService(); + await StartChartManage().init(); }, ), + SubmitBtn( - btnName: '启动心跳包', + btnName: '发送回声测试消息', onClick: () { - StartChartManage().sendHeartbeatMessage(); - }, - ), - SubmitBtn( - btnName: '结束心跳包', - onClick: () { - StartChartManage().stopHeartbeat(); + StartChartManage().sendEchoMessage( + ToPeerId: '3phX8Ng2cZHz5NtP8xAf6nYy2z1BYytoejgjoHrWMGhH'); }, ), + SizedBox(height: 50.w), Row( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/network/start_chart_api.dart b/lib/network/start_chart_api.dart index 26e148d6..925ce779 100644 --- a/lib/network/start_chart_api.dart +++ b/lib/network/start_chart_api.dart @@ -45,7 +45,7 @@ class StartChartApi extends BaseProvider { } // 星图--上报信息至发现服务 - Future reportInformation({ + Future reportInformation({ required ReportInformationData reportInformationData, }) async { final response = await post( @@ -54,10 +54,11 @@ class StartChartApi extends BaseProvider { isUnShowLoading: true, isUserBaseUrl: false, ); + return response; } // 星图--解析对端信息 - Future analyzeInformationOtherEnd({ + Future analyzeInformationOtherEnd({ required String peerId, }) async { final response = await get( @@ -65,5 +66,6 @@ class StartChartApi extends BaseProvider { isUnShowLoading: true, isUserBaseUrl: false, ); + return response; } } diff --git a/lib/talk/startChart/command/message_command.dart b/lib/talk/startChart/command/message_command.dart index 0082d83c..d05fc0c5 100644 --- a/lib/talk/startChart/command/message_command.dart +++ b/lib/talk/startChart/command/message_command.dart @@ -1,3 +1,4 @@ +import 'package:star_lock/talk/startChart/constant/message_type_constant.dart'; import 'package:star_lock/talk/startChart/constant/payload_type_constant.dart'; import 'package:star_lock/talk/startChart/constant/protocol_flag_constant.dart'; import 'package:star_lock/talk/startChart/entity/scp_message.dart'; @@ -7,10 +8,16 @@ class MessageCommand { static List goOnlineRelay() { String serializedBytesString = ScpMessage( ProtocolFlag: ProtocolFlagConstant.scp01, - MessageType: PayloadTypeConstant.goOnline, + MessageType: MessageTypeConstant.Req, MessageId: 1, SpTotal: 0, SpIndex: 0, + FromPeerId: 'ToPeerId', + ToPeerId: 'ToPeerId', + Payload: 'hello', + PayloadCRC: 55230, + PayloadLength: 5, + PayloadType: PayloadTypeConstant.goOnline, ).serialize(); return _hexToBytes(serializedBytesString); } @@ -21,8 +28,8 @@ class MessageCommand { required String FromPeerId, }) { ScpMessage message = ScpMessage( - ProtocolFlag: ProtocolFlagConstant.scp01, - MessageType: PayloadTypeConstant.echoTest, + ProtocolFlag: ProtocolFlagConstant.scp01, + MessageType: MessageTypeConstant.Req, MessageId: 1, SpTotal: 0, SpIndex: 0, @@ -31,7 +38,7 @@ class MessageCommand { Payload: 'hello', PayloadCRC: 55230, PayloadLength: 5, - PayloadType: 1, + PayloadType: PayloadTypeConstant.echoTest, ); String serializedBytesString = message.serialize(); @@ -41,17 +48,17 @@ class MessageCommand { // 心跳消息 static List heartbeatMessage() { ScpMessage message = ScpMessage( - ProtocolFlag: ProtocolFlagConstant.scp01, - MessageType: PayloadTypeConstant.heartbeat, + ProtocolFlag: ProtocolFlagConstant.scp01, + MessageType: MessageTypeConstant.Req, MessageId: 1, SpTotal: 0, SpIndex: 0, - // FromPeerId: FromPeerId, - // ToPeerId: ToPeerId, - // Payload: 'hello', - // PayloadCRC: 55230, - // PayloadLength: 5, - // PayloadType: 1, + FromPeerId: 'FromPeerId', + ToPeerId: 'ToPeerId', + Payload: 'hello', + PayloadCRC: 55230, + PayloadLength: 5, + PayloadType: PayloadTypeConstant.heartbeat, ); String serializedBytesString = message.serialize(); diff --git a/lib/talk/startChart/constant/message_type_constant.dart b/lib/talk/startChart/constant/message_type_constant.dart new file mode 100644 index 00000000..ea674e8d --- /dev/null +++ b/lib/talk/startChart/constant/message_type_constant.dart @@ -0,0 +1,23 @@ +class MessageTypeConstant { + // Req 普通的主动消息, 一般用于指令类消息, 需要对方回复, 即使没有任何回复内容, 也需要回复一个空包 + // 如果没有收到Resp, 则应在超时时间后重发Req, 重发次数不限, 但是重发间隔应逐渐增加 + // 直到收到Resp后才可以从缓存区域删除Req + static const int Req = 1; + + // Resp 回复消息, 用于回复Req消息 + // 一般用于指令类消息的完成后回复, 如果指令耗时较长也没关系, 例如重发req的超时时间为1秒, 那么我执行指令超过1秒导致请求方重发了,也没事呀. + // 对于超时超长的异步任务,不应该使用默认的Req/Resp模式,而应该在收到请求后即回复确认, 在异步完成任务后再主动发送req告诉发起处任务已完成. + static const int Resp = 2; + + // RealTimeData 实时数据, 无需回复, 丢包不重发,乱序不重排, 一般用于音视频场景 + // 需要 额外的手段来检测在线状态, 否则会无限发送导致变成Dos攻击 + // 例如发送方需要每秒发送1个普通的状态确认包(Req)询问接收方是否仍处于等待接收状态, 如果询问 结果为否/超时,则停止发送. + // 这种补充手段是硬性要求, 不能期望接收方主动发消息来停止, 因为接收方可能不是该业务端口, 或者是掉线了. + static const int RealTimeData = 3; + + // InvalidReq Req/Resp模型中的error Resp,即为告知无效请求,类似HTTP协议的500服务器错误,payload里面是一个utf8的字符串表明错误信息 + // 可能的情况举例:1.0的终端收到了1.1版本的新指令;APP终端收到了关闭电源指令(无法处理) + // 对于用户界面,建议的交互为“未知错误” + // 对于程序开发,需要避免出现,消息处理函数将该事件记录到错误日志 + static const int InvalidReq = 4; +} diff --git a/lib/talk/startChart/entity/scp_message.dart b/lib/talk/startChart/entity/scp_message.dart index f7dc124e..113cd974 100644 --- a/lib/talk/startChart/entity/scp_message.dart +++ b/lib/talk/startChart/entity/scp_message.dart @@ -58,6 +58,14 @@ class ScpMessage { 'PayloadLength': PayloadLength, 'Payload': Payload, }; + + + + } + + @override + String toString() { + return 'ScpMessage{ProtocolFlag: $ProtocolFlag, MessageType: $MessageType, MessageId: $MessageId, SpTotal: $SpTotal, SpIndex: $SpIndex, FromPeerId: $FromPeerId, ToPeerId: $ToPeerId, PayloadType: $PayloadType, PayloadCRC: $PayloadCRC, PayloadLength: $PayloadLength, Payload: $Payload}'; } String serialize() { @@ -133,11 +141,100 @@ class ScpMessage { // 转16进制字符串 final bytesToHexString = bytesToHex(bytes); - return bytesToHexString; } + // 反序列化方法 + static ScpMessage deserialize(List bytes) { + final message = ScpMessage(); + + int offset = 0; + + // ProtocolFlag (4 bytes) + if (bytes.length - offset >= 4) { + message.ProtocolFlag = utf8.decode(bytes.sublist(offset, offset + 4)); + offset += 4; + } + + // MessageType (1 byte) + if (bytes.length - offset >= 1) { + message.MessageType = bytes[offset]; + offset += 1; + } + + // MessageId (2 bytes, little-endian) + if (bytes.length - offset >= 2) { + message.MessageId = (bytes[offset + 1] << 8) | bytes[offset]; + offset += 2; + } + + // SpTotal (1 byte) + if (bytes.length - offset >= 1) { + message.SpTotal = bytes[offset]; + offset += 1; + } + + // SpIndex (1 byte) + if (bytes.length - offset >= 1) { + message.SpIndex = bytes[offset]; + offset += 1; + } + + // FromPeerId (字符串,长度固定为44字节) + if (bytes.length - offset >= 44) { + message.FromPeerId = utf8.decode(bytes.sublist(offset, offset + 44)); + offset += 44; + } + + // ToPeerId (字符串,长度固定为44字节) + if (bytes.length - offset >= 44) { + message.ToPeerId = utf8.decode(bytes.sublist(offset, offset + 44)); + offset += 44; + } + + // PayloadType (2 bytes, little-endian) + if (bytes.length - offset >= 2) { + message.PayloadType = (bytes[offset + 1] << 8) | bytes[offset]; + offset += 2; + } + + // PayloadCRC (2 bytes, little-endian) + if (bytes.length - offset >= 2) { + message.PayloadCRC = (bytes[offset + 1] << 8) | bytes[offset]; + offset += 2; + } + + // PayloadLength (4 bytes, big-endian) + if (bytes.length - offset >= 4) { + message.PayloadLength = (bytes[offset] | + (bytes[offset + 1] << 8) | + (bytes[offset + 2] << 16) | + (bytes[offset + 3] << 24)); + offset += 4; + } + + // Payload (字符串,转换为字节) + if (message.PayloadLength != null && + bytes.length - offset >= message.PayloadLength!) { + message.Payload = + utf8.decode(bytes.sublist(offset, offset + message.PayloadLength!)); + offset += message.PayloadLength!; + } + + return message; + } + static String bytesToHex(List bytes) { return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(''); } + + // 辅助函数:将16进制字符串转换为字节数组 + static List hexToBytes(String hexString) { + final bytes = []; + for (int i = 0; i < hexString.length; i += 2) { + final hexByte = hexString.substring(i, i + 2); + bytes.add(int.parse(hexByte, radix: 16)); + } + return bytes; + } } diff --git a/lib/talk/startChart/start_chart_manage.dart b/lib/talk/startChart/start_chart_manage.dart index e5330c81..9d67903d 100644 --- a/lib/talk/startChart/start_chart_manage.dart +++ b/lib/talk/startChart/start_chart_manage.dart @@ -5,6 +5,8 @@ import 'dart:typed_data'; 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:star_lock/app_settings/app_settings.dart'; import 'package:star_lock/flavors.dart'; import 'package:star_lock/network/start_chart_api.dart'; @@ -13,6 +15,7 @@ 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'; @@ -43,6 +46,7 @@ class StartChartManage { late String remoteHost = ''; // 远程主机地址(服务器返回) late int remotePort = 0; // 远程主机端口(服务器返回) final int localPort = 62289; // 本地端口 + String localPublicHost = ''; // 本地公网ip地址 int heartbeatIntervalTime = 1; // 心跳包间隔时间(s) Timer? _heartBeatTimer; // 心跳包定时器 @@ -50,27 +54,47 @@ class StartChartManage { String ToPeerId = ''; // 对端ID String FromPeerId = ''; // 我的ID + + bool _isLoginSuccessfulToStartChart = false; // 是否在星图登录成功 + + // 星图服务初始化 + Future init() async { + // 客户端注册 + await _clientRegister(); + + // 中继查询 + await _relayQuery(); + + // 上报 + await reportInformation(); + + // 初始化udp服务 + await _onlineRelayService(); + + // 发送心跳消息 + _sendHeartbeatMessage(); + + // 发送送上线消息 + await _sendOnlineMessage(); + } + /// 客户端注册 - Future 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 _clientRegister() async { + _log(text: '开始注册客户端'); + final StarChartRegisterNodeEntity requestStarChartRegisterNode = + await _requestStarChartRegisterNode(); + await _saveStarChartRegisterNodeToStorage(requestStarChartRegisterNode); + _log(text: '获取到星图注册节点信息:$requestStarChartRegisterNode'); } // 中继查询 - Future relayQuery() async { + Future _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]; @@ -78,7 +102,7 @@ class StartChartManage { final parseUdpUrl = _parseUdpUrl(data?.listenAddr ?? ''); remoteHost = parseUdpUrl['host'] ?? ''; remotePort = parseUdpUrl['port'] ?? ''; - _log(text: '中继信息:${data}'); + _log(text: '中继信息----》${data}'); } } @@ -88,9 +112,8 @@ class StartChartManage { } } - // 在中继服务器中上线 - Future onlineRelayService() async { - await relayQuery(); + // 初始化udp + Future _onlineRelayService() async { var addressIListenFrom = InternetAddress.anyIPv4; RawDatagramSocket.bind(addressIListenFrom, localPort) .then((RawDatagramSocket socket) { @@ -101,14 +124,6 @@ class StartChartManage { /// 设置数据接收回调 _onReceiveData(_udpSocket!); - - // 发送上线消息 - //_sendOnlineMessage(); - // 发送回声测试消息 - //_sendEchoMessage(); - - // 上报信息 - reportInformation(); }).catchError((error) { _log(text: 'Failed to bind UDP socket: $error'); }); @@ -121,6 +136,10 @@ class StartChartManage { Datagram? dg = socket.receive(); try { _log(text: '收到消息---> 长度:${dg?.data?.length}, 数据:${dg?.data}'); + if (dg?.data != null) { + final deserialize = ScpMessage.deserialize(dg!.data); + _log(text: 'Udp收到结构体数据---》$deserialize'); + } } catch (e) { _log(text: '❌ Udp ----> $e'); } @@ -133,29 +152,33 @@ class StartChartManage { _log(text: '上报信息至发现服务'); // 构建参数 ReportInformationData data = await _makeReportInformationData(); - await StartChartApi.to.reportInformation( + final response = await StartChartApi.to.reportInformation( reportInformationData: data, ); + if (response.statusCode == 200) { + // TODO 登录成功之后的逻辑 + _log(text: '星图登录成功'); + } } // 发送上线消息 - void _sendOnlineMessage() { + Future _sendOnlineMessage() async { // 组装上线消息 final message = MessageCommand.goOnlineRelay(); - _sendMessage(message: message); + await _sendMessage(message: message); } // 发送回声测试消息 - void _sendEchoMessage() { + void sendEchoMessage({required String ToPeerId}) async { final message = MessageCommand.echoMessage( ToPeerId: ToPeerId, FromPeerId: FromPeerId, ); - _sendMessage(message: message); + await _sendMessage(message: message); } // 发送心跳包消息 - void sendHeartbeatMessage() { + void _sendHeartbeatMessage() { if (_heartBeatTimerRunning) { _log(text: '心跳已经开始了. 请勿重复发送心跳包消息'); return; @@ -164,9 +187,9 @@ class StartChartManage { Duration( seconds: heartbeatIntervalTime, ), - (Timer timer) { + (Timer timer) async { final List message = MessageCommand.heartbeatMessage(); - _sendMessage(message: message); + await _sendMessage(message: message); }, ); _heartBeatTimerRunning = true; @@ -181,9 +204,14 @@ class StartChartManage { } // 发送消息 - void _sendMessage({required List message}) { + Future _sendMessage({required List message}) async { _log(text: '发送给中继的消息体:${message},序列化之后的数据:【${bytesToHex(message)}】'); - _udpSocket!.send(message, InternetAddress(remoteHost), remotePort); + var result = await _udpSocket?.send( + message, InternetAddress(remoteHost), remotePort); + if (result != message.length) { + AppLog.log('❌Udp ----> send data error $result ${message.length}'); + _udpSocket = null; + } } // 请求注册节点 @@ -334,7 +362,6 @@ class StartChartManage { return {'host': ip, 'port': port}; } } - throw FormatException('无法解析 URL 格式: $url'); } @@ -424,48 +451,6 @@ class StartChartManage { return signer.generateSignature(Uint8List.fromList(data)).bytes; } - // 解析 RSA 私钥 - // RSAPrivateKey parseRSAPrivateKey1(Uint8List bytes) { - // // 这里假设私钥是以 PKCS#1 DER 编码的 - // ASN1Parser parser = ASN1Parser(bytes); - // ASN1Sequence seq = parser.nextObject() as ASN1Sequence; - // if (seq.elements == null || seq.elements!.length < 9) { - // throw ArgumentError("Invalid RSA private key"); - // } - // - // // 解析各个元素 - // ASN1Integer version = seq.elements![0] as ASN1Integer; - // ASN1Integer modulus = seq.elements![1] as ASN1Integer; - // // ASN1Integer publicExponent = seq.elements![2] as ASN1Integer; - // ASN1Integer privateExponent = seq.elements![3] as ASN1Integer; - // ASN1Integer p = seq.elements![2] as ASN1Integer; - // ASN1Integer q = seq.elements![4] as ASN1Integer; - // ASN1Integer dP = seq.elements![5] as ASN1Integer; - // ASN1Integer dQ = seq.elements![6] as ASN1Integer; - // ASN1Integer qInv = seq.elements![7] as ASN1Integer; - // - // // 将 ASN1Integer 转换为 BigInt - // BigInt modulusValue = _convertToBigInt(modulus); - // // BigInt publicExponentValue = _convertToBigInt(publicExponent); - // BigInt privateExponentValue = _convertToBigInt(privateExponent); - // BigInt pValue = _convertToBigInt(p); - // BigInt qValue = _convertToBigInt(q); - // BigInt dPValue = _convertToBigInt(dP); - // BigInt dQPValue = _convertToBigInt(dQ); - // BigInt qInvQPValue = _convertToBigInt(qInv); - // - // // 创建 RSAPrivateKey 对象 - // return RSAPrivateKey(modulusValue, privateExponentValue, pValue, qValue); - // } - // - // // 将 ASN1Integer 转换为 BigInt - // BigInt _convertToBigInt(ASN1Integer integer) { - // // 获取 ASN1Integer 的字节数据 - // Uint8List bytes = integer.valueBytes!; - // // 将字节数据转换为 BigInt - // return BigInt.parse(hex.encode(bytes), radix: 16); - // } - Future getPublicKey() async { // 从缓存中获取星图注册节点信息 final StarChartRegisterNodeEntity? starChartRegisterNodeInfo =