fix:调整新的中继协议中的上报签名逻辑

This commit is contained in:
liyi 2024-12-03 14:44:29 +08:00
parent 8c75a8db1c
commit 063fc90a29
10 changed files with 265 additions and 165 deletions

View File

@ -239,7 +239,6 @@ class _StarLockLoginPageState extends State<StarLockLoginPage> {
await StartChartManage().init();
},
),
SubmitBtn(
btnName: '发送回声测试消息',
onClick: () {

View File

@ -68,4 +68,6 @@ class StartChartApi extends BaseProvider {
);
return response;
}
}

View File

@ -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<int> 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;
}

View File

@ -1,6 +1,6 @@
class IpConstant {
// ip
static const List<String> reportExcludeIp = ['127.0.0.1','::1%1'];
static const List<String> reportExcludeIp = ['127.0.0.1','::1%1','::1'];
static const String udpUrl = 'udp://';
static const String tcpUrl = 'tcp://';
static const String httpsUrl = 'https://';

View File

@ -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;
}
//
static const int loginSuccessResponse = 110;
//
static const int heartHeatSuccessResponse = 0;
}

View File

@ -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<int> 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<int> 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<int> serialize(HeartbeatResponse response) {
final List<int> bytes = [];
//
bytes.add(response.statusCode);
// ping时间2
bytes.add(response.nextPingTime & 0xFF);
bytes.add((response.nextPingTime >> 8) & 0xFF);
return bytes;
}
}
}

View File

@ -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<int> 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}';
}
}

View File

@ -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) {
// 110HeartbeatResponse类型
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<int>.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<int> 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<int> 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<int> 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;
}
}
}

View File

@ -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<void> 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<void> _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<int> 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<void> _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<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) {
@ -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<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编码的字符串
@ -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);
}
}

View File

@ -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