From 063fc90a2926b8caebf44f6ba3114338d4401c4a Mon Sep 17 00:00:00 2001 From: liyi Date: Tue, 3 Dec 2024 14:44:29 +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 | 1 - lib/network/start_chart_api.dart | 2 + .../startChart/command/message_command.dart | 6 +- lib/talk/startChart/constant/ip_constant.dart | 2 +- .../constant/payload_type_constant.dart | 10 +- .../startChart/entity/heartbeat_response.dart | 62 +++--- .../startChart/entity/login_response.dart | 59 ++++++ lib/talk/startChart/entity/scp_message.dart | 107 ++++------- lib/talk/startChart/start_chart_manage.dart | 179 ++++++++++++------ pubspec.yaml | 2 + 10 files changed, 265 insertions(+), 165 deletions(-) create mode 100644 lib/talk/startChart/entity/login_response.dart diff --git a/lib/login/login/starLock_login_page.dart b/lib/login/login/starLock_login_page.dart index 9c982a13..253d807c 100755 --- a/lib/login/login/starLock_login_page.dart +++ b/lib/login/login/starLock_login_page.dart @@ -239,7 +239,6 @@ class _StarLockLoginPageState extends State { await StartChartManage().init(); }, ), - SubmitBtn( btnName: '发送回声测试消息', onClick: () { diff --git a/lib/network/start_chart_api.dart b/lib/network/start_chart_api.dart index 925ce779..bcccaf9a 100644 --- a/lib/network/start_chart_api.dart +++ b/lib/network/start_chart_api.dart @@ -68,4 +68,6 @@ class StartChartApi extends BaseProvider { ); return response; } + + } diff --git a/lib/talk/startChart/command/message_command.dart b/lib/talk/startChart/command/message_command.dart index 76a272a0..72159b62 100644 --- a/lib/talk/startChart/command/message_command.dart +++ b/lib/talk/startChart/command/message_command.dart @@ -5,12 +5,11 @@ import 'package:star_lock/talk/startChart/constant/protocol_flag_constant.dart'; import 'package:star_lock/talk/startChart/entity/scp_message.dart'; class MessageCommand { - /// 客户端去中继上线命令 static List goOnlineRelay({ required String FromPeerId, required String ToPeerId, -}) { + }) { String serializedBytesString = ScpMessage( ProtocolFlag: ProtocolFlagConstant.scp01, MessageType: MessageTypeConstant.Req, @@ -68,7 +67,6 @@ class MessageCommand { PayloadLength: 5, PayloadType: PayloadTypeConstant.heartbeat, ); - String serializedBytesString = message.serialize(); return _hexToBytes(serializedBytesString); } @@ -82,7 +80,7 @@ class MessageCommand { return bytes; } - static int calculationCrc(payload){ + static int calculationCrc(payload) { var checkSumResult = Crc32.calculate(payload); return checkSumResult; } diff --git a/lib/talk/startChart/constant/ip_constant.dart b/lib/talk/startChart/constant/ip_constant.dart index bf1f2d40..5c3f1401 100644 --- a/lib/talk/startChart/constant/ip_constant.dart +++ b/lib/talk/startChart/constant/ip_constant.dart @@ -1,6 +1,6 @@ class IpConstant { // 上报时需要排除的ip - static const List reportExcludeIp = ['127.0.0.1','::1%1']; + static const List reportExcludeIp = ['127.0.0.1','::1%1','::1']; static const String udpUrl = 'udp://'; static const String tcpUrl = 'tcp://'; static const String httpsUrl = 'https://'; diff --git a/lib/talk/startChart/constant/payload_type_constant.dart b/lib/talk/startChart/constant/payload_type_constant.dart index eec9bd75..9aef47c2 100644 --- a/lib/talk/startChart/constant/payload_type_constant.dart +++ b/lib/talk/startChart/constant/payload_type_constant.dart @@ -1,10 +1,18 @@ class PayloadTypeConstant { // 上线 static const int goOnline = 100; + // 回声测试 static const int echoTest = 8; + // 心跳 static const int heartbeat = 110; + // UDP协议的SCD发现服务器查询中继信息 static const int query = 120; -} \ No newline at end of file + + // 登录成功 + static const int loginSuccessResponse = 110; + // 心跳响应成功 + static const int heartHeatSuccessResponse = 0; +} diff --git a/lib/talk/startChart/entity/heartbeat_response.dart b/lib/talk/startChart/entity/heartbeat_response.dart index 263c3d42..4b71ca0e 100644 --- a/lib/talk/startChart/entity/heartbeat_response.dart +++ b/lib/talk/startChart/entity/heartbeat_response.dart @@ -1,39 +1,37 @@ class HeartbeatResponse { - int statusCode; // 状态码,1字节无符号 - int nextPingTime; // 下次ping时间秒数,2字节无符号 + int? statusCode; + int? nextPingTime; - HeartbeatResponse({required this.statusCode, required this.nextPingTime}); + HeartbeatResponse({ + this.statusCode, + this.nextPingTime, + }); + + factory HeartbeatResponse.fromBytes(List bytes) { + final message = HeartbeatResponse(); + int offset = 0; + + // Set default value for statusCode + message.statusCode = 0; // 或者其他默认值 + + // Check if the entire array has at least 2 bytes for nextPingTime + if (bytes.length < 2) { + throw FormatException("Insufficient data for HeartbeatResponse"); + } + + // nextPingTime (2 bytes, little-endian) + if (bytes.length - offset >= 2) { + message.nextPingTime = (bytes[offset + 1] << 8) | bytes[offset]; + offset += 2; + } else { + throw FormatException("Invalid nextPingTime length"); + } + + return message; + } @override String toString() { return 'HeartbeatResponse{statusCode: $statusCode, nextPingTime: $nextPingTime}'; } - - // 反序列化方法 - static HeartbeatResponse deserialize(List bytes) { - if (bytes.length < 3) { - throw FormatException("Invalid HeartbeatResponse length"); - } - - final response = HeartbeatResponse( - statusCode: bytes[0], // 状态码,1字节 - nextPingTime: (bytes[2] << 8) | bytes[1], // 下次ping时间,2字节小端序 - ); - - return response; - } - - // 序列化方法 - static List serialize(HeartbeatResponse response) { - final List bytes = []; - - // 序列化状态码 - bytes.add(response.statusCode); - - // 序列化下次ping时间(2字节小端序) - bytes.add(response.nextPingTime & 0xFF); - bytes.add((response.nextPingTime >> 8) & 0xFF); - - return bytes; - } -} \ No newline at end of file +} diff --git a/lib/talk/startChart/entity/login_response.dart b/lib/talk/startChart/entity/login_response.dart new file mode 100644 index 00000000..2123068a --- /dev/null +++ b/lib/talk/startChart/entity/login_response.dart @@ -0,0 +1,59 @@ +import 'dart:convert'; + +class LoginResponse { + int? responseType; + int? rejectType; + int? nextPingTime; + String? clientAddr; + + LoginResponse({ + this.responseType, + this.rejectType, + this.nextPingTime, + this.clientAddr, + }); + + factory LoginResponse.fromBytes(List bytes) { + final message = LoginResponse(); + int offset = 0; + + // responseType (1 byte) + if (bytes.length - offset >= 1) { + message.responseType = bytes[offset]; + offset += 1; + } else { + throw FormatException("Invalid responseType length"); + } + + // rejectType (1 byte) + if (bytes.length - offset >= 1) { + message.rejectType = bytes[offset]; + offset += 1; + } else { + throw FormatException("Invalid rejectType length"); + } + + // nextPingTime (2 bytes, little-endian) + if (bytes.length - offset >= 2) { + message.nextPingTime = (bytes[offset + 1] << 8) | bytes[offset]; + offset += 2; + } else { + throw FormatException("Invalid nextPingTime length"); + } + + // ClientAddr (remaining bytes) + if (bytes.length > offset) { + message.clientAddr = + utf8.decode(bytes.sublist(offset).takeWhile((byte) => byte != 0).toList()); + } else { + throw FormatException("Invalid ClientAddr length"); + } + + return message; + } + + @override + String toString() { + return 'LoginResponse{responseType: $responseType, rejectType: $rejectType, nextPingTime: $nextPingTime, clientAddr: $clientAddr}'; + } +} diff --git a/lib/talk/startChart/entity/scp_message.dart b/lib/talk/startChart/entity/scp_message.dart index 68d805b5..ba26bc25 100644 --- a/lib/talk/startChart/entity/scp_message.dart +++ b/lib/talk/startChart/entity/scp_message.dart @@ -4,6 +4,7 @@ import 'package:crypto/crypto.dart'; import 'package:star_lock/app_settings/app_settings.dart'; import 'package:star_lock/talk/startChart/constant/payload_type_constant.dart'; import 'package:star_lock/talk/startChart/entity/heartbeat_response.dart'; +import 'package:star_lock/talk/startChart/entity/login_response.dart'; class ScpMessage { ScpMessage({ @@ -223,9 +224,6 @@ class ScpMessage { // PayloadLength (4 bytes, little-endian) if (bytes.length - offset >= 4) { - // 打印PayloadLength对应的4个字节 - print('PayloadLength bytes: ${bytes.sublist(offset, offset + 4)}'); - message.PayloadLength = (bytes[offset] | (bytes[offset + 1] << 8) | (bytes[offset + 2] << 16) | @@ -235,75 +233,25 @@ class ScpMessage { throw FormatException("Invalid PayloadLength length"); } - // Payload (字符串,转换为字节) - // if (message.PayloadLength != null && - // bytes.length - offset >= message.PayloadLength!) { - // message.Payload = - // utf8.decode(bytes.sublist(offset, offset + message.PayloadLength!)); - // offset += message.PayloadLength!; - // } else { - // throw FormatException("Invalid Payload or PayloadLength"); - // } - - // 打印解析后的 PayloadLength - print('Parsed PayloadLength: ${message.PayloadLength}'); - - // 解析Payload - if (message.PayloadType == PayloadTypeConstant.heartbeat) { - // 假设110表示HeartbeatResponse类型 - if (message.PayloadLength != null && - bytes.length - offset >= message.PayloadLength!) { - final payloadBytes = - bytes.sublist(offset, offset + message.PayloadLength!); - message.Payload = HeartbeatResponse.deserialize(payloadBytes); - offset += message.PayloadLength!; - } else { - throw FormatException("Invalid Payload or PayloadLength"); - } + // 处理其他类型的Payload + if (message.PayloadLength != null && + bytes.length - offset >= message.PayloadLength!) { + final sublist = bytes.sublist(offset, offset + message.PayloadLength!); + // print('sublist:$sublist'); + offset += message.PayloadLength!; + message.Payload = _handlePayLoad( + payloadType: message.PayloadType ?? 0, + byte: sublist, + offset: offset, + PayloadLength: message.PayloadLength, + ); } else { - // 处理其他类型的Payload - if (message.PayloadLength != null && - bytes.length - offset >= message.PayloadLength!) { - message.Payload = - utf8.decode(bytes.sublist(offset, offset + message.PayloadLength!)); - offset += message.PayloadLength!; - } else { - throw FormatException("Invalid Payload or PayloadLength"); - } + throw FormatException("Invalid Payload or PayloadLength"); } - // 验证PayloadCRC - // if (message.Payload != null) { - // var crcBytes = List.from(utf8.encode(message.Payload!)); - // var calculatedCrc = _calculateCrc16(crcBytes); - // if (calculatedCrc != message.PayloadCRC) { - // throw FormatException("PayloadCRC verification failed. Expected: ${message.PayloadCRC}, Actual: $calculatedCrc"); - // } - // } - return message; } - // CRC-16 计算函数(示例实现,可能需要根据具体协议调整) - static int _calculateCrc16(List data) { - const poly = 0x8005; - int crc = 0xFFFF; - - for (final b in data) { - crc ^= b << 8; - for (int i = 0; i < 8; i++) { - if ((crc & 0x8000) != 0) { - crc = (crc << 1) ^ poly; - } else { - crc <<= 1; - } - crc &= 0xFFFF; - } - } - - return crc; - } - static String bytesToHex(List bytes) { return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(''); } @@ -317,4 +265,31 @@ class ScpMessage { } return bytes; } + + // 根据不同payloadType序列化对应的payload结构体 + static dynamic _handlePayLoad({ + required int payloadType, + required List byte, + int? offset, + int? PayloadLength, + }) { + switch (payloadType) { + case PayloadTypeConstant.goOnline: + // 上线 + LoginResponse loginResp = LoginResponse.fromBytes(byte); + return loginResp; + case PayloadTypeConstant.heartbeat: + // 心跳 + HeartbeatResponse heartbeatResponse = HeartbeatResponse.fromBytes(byte); + return heartbeatResponse; + case PayloadTypeConstant.echoTest: + // 回声测试 + String payload = utf8.decode(byte); + return payload; + default: + print('❌未知的payloadType类型,按照字符串解析'); + String payload = utf8.decode(byte); + return payload; + } + } } diff --git a/lib/talk/startChart/start_chart_manage.dart b/lib/talk/startChart/start_chart_manage.dart index 15efe208..3ea6abbb 100644 --- a/lib/talk/startChart/start_chart_manage.dart +++ b/lib/talk/startChart/start_chart_manage.dart @@ -6,6 +6,7 @@ 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:flutter_easyloading/flutter_easyloading.dart'; import 'package:get/get.dart'; import 'package:pointycastle/asn1/asn1_parser.dart'; import 'package:pointycastle/asn1/primitives/asn1_integer.dart'; @@ -19,6 +20,9 @@ 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/constant/payload_type_constant.dart'; +import 'package:star_lock/talk/startChart/entity/heartbeat_response.dart'; +import 'package:star_lock/talk/startChart/entity/login_response.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'; @@ -54,7 +58,7 @@ class StartChartManage { final int localPort = 62289; // 本地端口 String localPublicHost = ''; // 本地公网ip地址 - int heartbeatIntervalTime = 1; // 心跳包间隔时间(s) + int _heartbeatIntervalTime = 1; // 心跳包间隔时间(s) Timer? _heartBeatTimer; // 心跳包定时器 bool _heartBeatTimerRunning = false; // 心跳包定时任务发送状态 @@ -64,8 +68,19 @@ class StartChartManage { // echo测试peer对端 final String echoPeerId = '3phX8Ng2cZHz5NtP8xAf6nYy2z1BYytoejgjoHrWMGhH'; + bool _isOnlineStartChartServer = false; // 星图是否上线成功 + int _reStartOnlineStartChartServerIntervalTime = 1; // 重新上线星图服务任务间隔(s) + Timer? _reStartOnlineStartChartServerTimer; // 重新上线定时器 + bool _reStartOnlineStartChartServerTimerRunning = false; // 重新上线定时任务发送状态 + + String relayPeerId = ''; // 中继peerId + // 星图服务初始化 Future init() async { + if (_isOnlineStartChartServer && _udpSocket != null) { + // 如果已经上线就不进行初始化 + return; + } // 节点注册 await _clientRegister(); @@ -104,6 +119,7 @@ class StartChartManage { final parseUdpUrl = _parseUdpUrl(data?.listenAddr ?? ''); remoteHost = parseUdpUrl['host'] ?? ''; remotePort = parseUdpUrl['port'] ?? ''; + relayPeerId = data?.peerID ?? ''; } _log(text: '中继信息----》${relayInfoEntity}'); } @@ -137,12 +153,14 @@ class StartChartManage { 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'); + if (deserialize != null) { + _handleUdpResultData(deserialize); + } + if (deserialize.PayloadType != PayloadTypeConstant.heartbeat) { + _log(text: 'Udp收到结构体数据---》$deserialize'); + } } } catch (e) { _log(text: '❌ Udp ----> $e'); @@ -160,43 +178,36 @@ class StartChartManage { reportInformationData: data, ); if (response.statusCode == 200) { - _log(text: '星图登录成功'); - // 发送送上线消息 - await _sendOnlineMessage(); // 发送心跳消息 _sendHeartbeatMessage(); + // 发送送上线消息 + await _reStartOnlineStartChartServer(); } } // 发送上线消息 Future _sendOnlineMessage() async { + if (_isOnlineStartChartServer) { + _log(text: '星图已上线,请勿重复发送上线消息'); + return; + } // 组装上线消息 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, + ToPeerId: ToPeerId, ); 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, + seconds: _heartbeatIntervalTime, ), (Timer timer) async { final List message = MessageCommand.heartbeatMessage( @@ -209,17 +220,49 @@ class StartChartManage { _heartBeatTimerRunning = true; } + // 发送回声测试消息 + void sendEchoMessage({required String ToPeerId}) async { + final message = MessageCommand.echoMessage( + ToPeerId: ToPeerId, + FromPeerId: FromPeerId, + ); + await _sendMessage(message: message); + } + + // 重新上线 + Future _reStartOnlineStartChartServer() async { + if (_isOnlineStartChartServer) { + _log(text: '星图已上线,请勿重复发送上线消息'); + return; + } + _reStartOnlineStartChartServerTimer ??= Timer.periodic( + Duration( + seconds: _reStartOnlineStartChartServerIntervalTime, + ), + (Timer timer) async { + // 重新发送上线消息 + await _sendOnlineMessage(); + }, + ); + _reStartOnlineStartChartServerTimerRunning = true; + } + // 停止定时发送心跳包 void stopHeartbeat() { _heartBeatTimer?.cancel(); _heartBeatTimer = null; // 清除定时器引用 _heartBeatTimerRunning = false; - _log(text: '发送心跳包结束'); + } + + // 停止重新上线 + void stopReStartOnlineStartChartServer() { + _reStartOnlineStartChartServerTimer?.cancel(); + _reStartOnlineStartChartServerTimer = null; // 清除定时器引用 + _reStartOnlineStartChartServerTimerRunning = false; } // 发送消息 Future _sendMessage({required List message}) async { - // _log(text: '发送给中继的消息体:${message},序列化之后的数据:【${bytesToHex(message)}】'); var result = await _udpSocket?.send( message, InternetAddress(remoteHost), remotePort); if (result != message.length) { @@ -340,15 +383,10 @@ class StartChartManage { 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)) { @@ -359,7 +397,7 @@ class StartChartManage { } catch (e) { _log(text: '❌--->获取本机IP时出现错误: $e'); } - return ipAddresses ?? []; + return ipAddresses; // 注意:这里不需要 `?? []`,因为 `ipAddresses` 已经初始化为一个空列表。 } void _log({required String text}) { @@ -436,38 +474,6 @@ class StartChartManage { return bytes; } -// 将字节数组转换为 PEM 格式的公钥 - String convertToPemPublicKey(List 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 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 privateKeyBytes, {bool isPKCS8 = true}) { // 将字节数组转换为Base64编码的字符串 @@ -564,4 +570,57 @@ class StartChartManage { await Storage.getStarChartRegisterNodeInfo(); return starChartRegisterNodeInfo?.peer?.privateKey ?? ''; } + + // 处理udp返回的数据 + void _handleUdpResultData(ScpMessage scpMessage) { + final int payloadType = scpMessage.PayloadType ?? 0; + switch (payloadType) { + case PayloadTypeConstant.goOnline: + _handleUdpGoOnlineResultData(scpMessage); + break; + case PayloadTypeConstant.heartbeat: + _handleUdpHeartBeatResultData(scpMessage); + break; + case PayloadTypeConstant.echoTest: + _handleUdpEchoTestResultData(scpMessage); + break; + + default: + _log(text: '❌未知的payloadType类型'); + break; + } + } + + /// 处理上线命令udp返回的数据 + void _handleUdpGoOnlineResultData(ScpMessage scpMessage) { + final LoginResponse loginResponse = scpMessage.Payload; + final responseType = loginResponse.responseType; + if (responseType != null && + responseType == PayloadTypeConstant.loginSuccessResponse) { + _log(text: '星图登录成功'); + _isOnlineStartChartServer = true; + // 上线成功,停止重发 + stopReStartOnlineStartChartServer(); + } else { + // 上线失败,重发 + _reStartOnlineStartChartServer(); + } + } + + /// 处理心跳命令udp返回的数据 + void _handleUdpHeartBeatResultData(ScpMessage scpMessage) { + final HeartbeatResponse heartbeatResponse = scpMessage.Payload; + final statusCode = heartbeatResponse.statusCode; + if (statusCode != null && + statusCode != PayloadTypeConstant.heartHeatSuccessResponse) { + // 心跳响应失败,需要重新上线 + _reStartOnlineStartChartServer(); + } + _heartbeatIntervalTime = heartbeatResponse.nextPingTime ?? 1; + } + + /// 处理回声测试 + void _handleUdpEchoTestResultData(ScpMessage scpMessage) { + EasyLoading.showToast(scpMessage.Payload, duration: 2000.milliseconds); + } } diff --git a/pubspec.yaml b/pubspec.yaml index 262c8a95..c6f229f1 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -251,6 +251,8 @@ dependencies: cryptography: ^2.7.0 asn1lib: ^1.0.0 fast_rsa: ^3.6.6 + crc: ^0.0.2 + crclib: ^3.0.0 dependency_overrides: #强制设置google_maps_flutter_ios 为 2.5.2