fix:增加p2p调试命令
This commit is contained in:
parent
3bd96e58b9
commit
c16c9be4c6
File diff suppressed because one or more lines are too long
@ -8,8 +8,8 @@ import 'package:star_lock/talk/startChart/constant/udp_constant.dart';
|
||||
import 'package:star_lock/talk/startChart/entity/scp_message.dart';
|
||||
import 'package:star_lock/talk/startChart/proto/gateway_reset.pb.dart';
|
||||
import 'package:star_lock/talk/startChart/proto/generic.pb.dart';
|
||||
import 'package:star_lock/talk/startChart/proto/rbcu.pb.dart';
|
||||
import 'package:star_lock/talk/startChart/proto/talk_accept.pb.dart';
|
||||
import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart';
|
||||
import 'package:star_lock/talk/startChart/proto/talk_expect.pb.dart';
|
||||
import 'package:star_lock/talk/startChart/proto/talk_hangup.pb.dart';
|
||||
import 'package:star_lock/talk/startChart/proto/talk_ping.pb.dart';
|
||||
@ -375,6 +375,32 @@ class MessageCommand {
|
||||
return _hexToBytes(serializedBytesString);
|
||||
}
|
||||
|
||||
// RbcuInfo 地址交换消息
|
||||
static List<int> genericRbcuInfoMessage({
|
||||
required String FromPeerId,
|
||||
required String ToPeerId,
|
||||
required RbcuInfo rbcuInfo,
|
||||
int? MessageId,
|
||||
String? errorMessageText,
|
||||
}) {
|
||||
final payload = rbcuInfo.writeToBuffer();
|
||||
ScpMessage message = ScpMessage(
|
||||
ProtocolFlag: ProtocolFlagConstant.scp01,
|
||||
MessageType: MessageTypeConstant.Resp,
|
||||
MessageId: MessageId,
|
||||
SpTotal: 1,
|
||||
SpIndex: 1,
|
||||
FromPeerId: FromPeerId,
|
||||
ToPeerId: ToPeerId,
|
||||
Payload: payload,
|
||||
PayloadCRC: calculationCrc(payload),
|
||||
PayloadLength: payload.length,
|
||||
PayloadType: PayloadTypeConstant.RbcuInfo,
|
||||
);
|
||||
String serializedBytesString = message.serialize();
|
||||
return _hexToBytes(serializedBytesString);
|
||||
}
|
||||
|
||||
// 辅助方法:将16进制字符串转换为字节列表
|
||||
static List<int> _hexToBytes(String hex) {
|
||||
final bytes = <int>[];
|
||||
|
||||
@ -55,4 +55,13 @@ class PayloadTypeConstant {
|
||||
|
||||
// 通话中挂断
|
||||
static const int talkHangup = 1668;
|
||||
|
||||
// Rbcu地址交换
|
||||
static const int RbcuInfo = 150;
|
||||
|
||||
// Rbcu探测
|
||||
static const int RbcuProbe = 152;
|
||||
|
||||
// Rbcu确认
|
||||
static const int RbcuConfirm = 154;
|
||||
}
|
||||
|
||||
69
lib/talk/startChart/handle/impl/udp_rbcuInfo_handler.dart
Normal file
69
lib/talk/startChart/handle/impl/udp_rbcuInfo_handler.dart
Normal file
@ -0,0 +1,69 @@
|
||||
import 'package:star_lock/login/selectCountryRegion/common/index.dart';
|
||||
import 'package:star_lock/talk/startChart/constant/message_type_constant.dart';
|
||||
import 'package:star_lock/talk/startChart/entity/scp_message.dart';
|
||||
import 'package:star_lock/talk/startChart/handle/scp_message_base_handle.dart';
|
||||
import 'package:star_lock/talk/startChart/handle/scp_message_handle.dart';
|
||||
import 'package:star_lock/talk/startChart/p2p/p2p_manage.dart';
|
||||
import 'package:star_lock/talk/startChart/proto/generic.pb.dart';
|
||||
import 'package:star_lock/talk/startChart/proto/rbcu.pb.dart';
|
||||
import 'package:star_lock/talk/startChart/proto/talk_request.pb.dart';
|
||||
|
||||
class UdpRbcuInfoHandler extends ScpMessageBaseHandle
|
||||
implements ScpMessageHandler {
|
||||
@override
|
||||
deserializePayload(
|
||||
{required int payloadType,
|
||||
required int messageType,
|
||||
required List<int> byte,
|
||||
int? offset,
|
||||
int? PayloadLength,
|
||||
int? spTotal,
|
||||
int? spIndex,
|
||||
int? messageId}) {
|
||||
if (messageType == MessageTypeConstant.Resp) {
|
||||
final GenericResp genericResp = GenericResp();
|
||||
genericResp.mergeFromBuffer(byte);
|
||||
return genericResp;
|
||||
} else if (messageType == MessageTypeConstant.Req) {
|
||||
final RbcuInfo rbcuInfo = RbcuInfo();
|
||||
rbcuInfo.mergeFromBuffer(byte);
|
||||
return rbcuInfo;
|
||||
} else {
|
||||
String payload = utf8.decode(byte);
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void handleInvalidReq(ScpMessage scpMessage) {
|
||||
// TODO: implement handleInvalidReq
|
||||
}
|
||||
|
||||
@override
|
||||
void handleRealTimeData(ScpMessage scpMessage) {
|
||||
// TODO: implement handleRealTimeData
|
||||
}
|
||||
|
||||
@override
|
||||
void handleReq(ScpMessage scpMessage) {
|
||||
final RbcuInfo rbcuInfo = scpMessage.Payload();
|
||||
if (rbcuInfo.isResp) {
|
||||
// 如果是回复的消息
|
||||
_handleResultRbcuInfo(rbcuInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void handleResp(ScpMessage scpMessage) {
|
||||
final GenericResp genericResp = scpMessage.Payload();
|
||||
if (checkGenericRespSuccess(genericResp)) {
|
||||
// 收到回复之后停止重发
|
||||
startChartManage.stopSendingRbcuInfoMessages();
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理回复的rbcuInfo消息
|
||||
void _handleResultRbcuInfo(RbcuInfo rbcuInfo) {
|
||||
P2pManage().communicationObjectRbcuInfo = rbcuInfo;
|
||||
}
|
||||
}
|
||||
124
lib/talk/startChart/handle/other/do_sign.dart
Normal file
124
lib/talk/startChart/handle/other/do_sign.dart
Normal file
@ -0,0 +1,124 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'dart:typed_data';
|
||||
import 'package:convert/convert.dart';
|
||||
import 'package:fast_rsa/fast_rsa.dart' as fastRsa;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:pointycastle/export.dart' as pc;
|
||||
import 'dart:convert';
|
||||
import 'package:asn1lib/asn1lib.dart' as asn1lib;
|
||||
import 'package:star_lock/talk/startChart/exception/start_chart_message_exception.dart'; // Prefix for asn1lib
|
||||
|
||||
class DoSign {
|
||||
// 生成签名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) {
|
||||
throw StartChartMessageException('❌--->上报信息生成签名时出现错误: $e');
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/// 转换私钥格式
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 将十六进制字符串转换为字节数组
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -54,6 +54,8 @@ class ScpMessageHandlerFactory {
|
||||
return UdpTalkDataHandler();
|
||||
case PayloadTypeConstant.talkHangup:
|
||||
return UdpTalkHangUpHandler();
|
||||
case PayloadTypeConstant.RbcuInfo:
|
||||
return UdpTalkHangUpHandler();
|
||||
default:
|
||||
return UnKnowPayloadTypeHandler();
|
||||
}
|
||||
|
||||
21
lib/talk/startChart/p2p/p2p_manage.dart
Normal file
21
lib/talk/startChart/p2p/p2p_manage.dart
Normal file
@ -0,0 +1,21 @@
|
||||
import 'package:star_lock/talk/startChart/proto/rbcu.pb.dart';
|
||||
|
||||
class P2pManage {
|
||||
RbcuInfo? communicationObjectRbcuInfo;
|
||||
|
||||
|
||||
void init(){
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 解析 address 属性,提取对方的 IP 和端口
|
||||
List<Map<String, String>> parseRemoteAddresses() {
|
||||
final addresses = communicationObjectRbcuInfo?.address ?? [];
|
||||
return addresses.map((addr) {
|
||||
final parts = addr.split(':');
|
||||
return {'ip': parts[0], 'port': parts[1]};
|
||||
}).toList();
|
||||
}
|
||||
|
||||
}
|
||||
278
lib/talk/startChart/proto/rbcu.pb.dart
Normal file
278
lib/talk/startChart/proto/rbcu.pb.dart
Normal file
@ -0,0 +1,278 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: rbcu.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:fixnum/fixnum.dart' as $fixnum;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
/// RbcuInfo 地址交换
|
||||
class RbcuInfo extends $pb.GeneratedMessage {
|
||||
factory RbcuInfo({
|
||||
$core.String? sessionId,
|
||||
$core.String? name,
|
||||
$core.Iterable<$core.String>? address,
|
||||
$fixnum.Int64? time,
|
||||
$core.bool? isResp,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (sessionId != null) {
|
||||
$result.sessionId = sessionId;
|
||||
}
|
||||
if (name != null) {
|
||||
$result.name = name;
|
||||
}
|
||||
if (address != null) {
|
||||
$result.address.addAll(address);
|
||||
}
|
||||
if (time != null) {
|
||||
$result.time = time;
|
||||
}
|
||||
if (isResp != null) {
|
||||
$result.isResp = isResp;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
RbcuInfo._() : super();
|
||||
factory RbcuInfo.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory RbcuInfo.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'RbcuInfo', package: const $pb.PackageName(_omitMessageNames ? '' : 'main'), createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'SessionId', protoName: 'SessionId')
|
||||
..aOS(2, _omitFieldNames ? '' : 'Name', protoName: 'Name')
|
||||
..pPS(4, _omitFieldNames ? '' : 'Address', protoName: 'Address')
|
||||
..a<$fixnum.Int64>(5, _omitFieldNames ? '' : 'Time', $pb.PbFieldType.OU6, protoName: 'Time', defaultOrMaker: $fixnum.Int64.ZERO)
|
||||
..aOB(6, _omitFieldNames ? '' : 'isResp', protoName: 'isResp')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
RbcuInfo clone() => RbcuInfo()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
RbcuInfo copyWith(void Function(RbcuInfo) updates) => super.copyWith((message) => updates(message as RbcuInfo)) as RbcuInfo;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static RbcuInfo create() => RbcuInfo._();
|
||||
RbcuInfo createEmptyInstance() => create();
|
||||
static $pb.PbList<RbcuInfo> createRepeated() => $pb.PbList<RbcuInfo>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static RbcuInfo getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RbcuInfo>(create);
|
||||
static RbcuInfo? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get sessionId => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set sessionId($core.String v) { $_setString(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasSessionId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearSessionId() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get name => $_getSZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set name($core.String v) { $_setString(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasName() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearName() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.List<$core.String> get address => $_getList(2);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$fixnum.Int64 get time => $_getI64(3);
|
||||
@$pb.TagNumber(5)
|
||||
set time($fixnum.Int64 v) { $_setInt64(3, v); }
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasTime() => $_has(3);
|
||||
@$pb.TagNumber(5)
|
||||
void clearTime() => clearField(5);
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool get isResp => $_getBF(4);
|
||||
@$pb.TagNumber(6)
|
||||
set isResp($core.bool v) { $_setBool(4, v); }
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasIsResp() => $_has(4);
|
||||
@$pb.TagNumber(6)
|
||||
void clearIsResp() => clearField(6);
|
||||
}
|
||||
|
||||
/// RbcuProbe 万箭齐发
|
||||
class RbcuProbe extends $pb.GeneratedMessage {
|
||||
factory RbcuProbe({
|
||||
$core.String? sessionId,
|
||||
$core.String? data,
|
||||
$core.String? targetAddress,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (sessionId != null) {
|
||||
$result.sessionId = sessionId;
|
||||
}
|
||||
if (data != null) {
|
||||
$result.data = data;
|
||||
}
|
||||
if (targetAddress != null) {
|
||||
$result.targetAddress = targetAddress;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
RbcuProbe._() : super();
|
||||
factory RbcuProbe.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory RbcuProbe.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'RbcuProbe', package: const $pb.PackageName(_omitMessageNames ? '' : 'main'), createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'SessionId', protoName: 'SessionId')
|
||||
..aOS(2, _omitFieldNames ? '' : 'Data', protoName: 'Data')
|
||||
..aOS(3, _omitFieldNames ? '' : 'TargetAddress', protoName: 'TargetAddress')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
RbcuProbe clone() => RbcuProbe()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
RbcuProbe copyWith(void Function(RbcuProbe) updates) => super.copyWith((message) => updates(message as RbcuProbe)) as RbcuProbe;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static RbcuProbe create() => RbcuProbe._();
|
||||
RbcuProbe createEmptyInstance() => create();
|
||||
static $pb.PbList<RbcuProbe> createRepeated() => $pb.PbList<RbcuProbe>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static RbcuProbe getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RbcuProbe>(create);
|
||||
static RbcuProbe? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get sessionId => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set sessionId($core.String v) { $_setString(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasSessionId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearSessionId() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get data => $_getSZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set data($core.String v) { $_setString(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasData() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearData() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.String get targetAddress => $_getSZ(2);
|
||||
@$pb.TagNumber(3)
|
||||
set targetAddress($core.String v) { $_setString(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasTargetAddress() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearTargetAddress() => clearField(3);
|
||||
}
|
||||
|
||||
/// RbcuConfirm 连通确认
|
||||
class RbcuConfirm extends $pb.GeneratedMessage {
|
||||
factory RbcuConfirm({
|
||||
$core.String? sessionId,
|
||||
$core.String? probeAddress,
|
||||
$core.String? receiveAddress,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (sessionId != null) {
|
||||
$result.sessionId = sessionId;
|
||||
}
|
||||
if (probeAddress != null) {
|
||||
$result.probeAddress = probeAddress;
|
||||
}
|
||||
if (receiveAddress != null) {
|
||||
$result.receiveAddress = receiveAddress;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
RbcuConfirm._() : super();
|
||||
factory RbcuConfirm.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory RbcuConfirm.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'RbcuConfirm', package: const $pb.PackageName(_omitMessageNames ? '' : 'main'), createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'SessionId', protoName: 'SessionId')
|
||||
..aOS(2, _omitFieldNames ? '' : 'ProbeAddress', protoName: 'ProbeAddress')
|
||||
..aOS(3, _omitFieldNames ? '' : 'ReceiveAddress', protoName: 'ReceiveAddress')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
RbcuConfirm clone() => RbcuConfirm()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
RbcuConfirm copyWith(void Function(RbcuConfirm) updates) => super.copyWith((message) => updates(message as RbcuConfirm)) as RbcuConfirm;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static RbcuConfirm create() => RbcuConfirm._();
|
||||
RbcuConfirm createEmptyInstance() => create();
|
||||
static $pb.PbList<RbcuConfirm> createRepeated() => $pb.PbList<RbcuConfirm>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static RbcuConfirm getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RbcuConfirm>(create);
|
||||
static RbcuConfirm? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get sessionId => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set sessionId($core.String v) { $_setString(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasSessionId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearSessionId() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get probeAddress => $_getSZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set probeAddress($core.String v) { $_setString(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasProbeAddress() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearProbeAddress() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.String get receiveAddress => $_getSZ(2);
|
||||
@$pb.TagNumber(3)
|
||||
set receiveAddress($core.String v) { $_setString(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasReceiveAddress() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearReceiveAddress() => clearField(3);
|
||||
}
|
||||
|
||||
|
||||
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
|
||||
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
|
||||
11
lib/talk/startChart/proto/rbcu.pbenum.dart
Normal file
11
lib/talk/startChart/proto/rbcu.pbenum.dart
Normal file
@ -0,0 +1,11 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: rbcu.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
64
lib/talk/startChart/proto/rbcu.pbjson.dart
Normal file
64
lib/talk/startChart/proto/rbcu.pbjson.dart
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: rbcu.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:convert' as $convert;
|
||||
import 'dart:core' as $core;
|
||||
import 'dart:typed_data' as $typed_data;
|
||||
|
||||
@$core.Deprecated('Use rbcuInfoDescriptor instead')
|
||||
const RbcuInfo$json = {
|
||||
'1': 'RbcuInfo',
|
||||
'2': [
|
||||
{'1': 'SessionId', '3': 1, '4': 1, '5': 9, '10': 'SessionId'},
|
||||
{'1': 'Name', '3': 2, '4': 1, '5': 9, '10': 'Name'},
|
||||
{'1': 'Address', '3': 4, '4': 3, '5': 9, '10': 'Address'},
|
||||
{'1': 'Time', '3': 5, '4': 1, '5': 4, '10': 'Time'},
|
||||
{'1': 'isResp', '3': 6, '4': 1, '5': 8, '10': 'isResp'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RbcuInfo`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List rbcuInfoDescriptor = $convert.base64Decode(
|
||||
'CghSYmN1SW5mbxIcCglTZXNzaW9uSWQYASABKAlSCVNlc3Npb25JZBISCgROYW1lGAIgASgJUg'
|
||||
'ROYW1lEhgKB0FkZHJlc3MYBCADKAlSB0FkZHJlc3MSEgoEVGltZRgFIAEoBFIEVGltZRIWCgZp'
|
||||
'c1Jlc3AYBiABKAhSBmlzUmVzcA==');
|
||||
|
||||
@$core.Deprecated('Use rbcuProbeDescriptor instead')
|
||||
const RbcuProbe$json = {
|
||||
'1': 'RbcuProbe',
|
||||
'2': [
|
||||
{'1': 'SessionId', '3': 1, '4': 1, '5': 9, '10': 'SessionId'},
|
||||
{'1': 'Data', '3': 2, '4': 1, '5': 9, '10': 'Data'},
|
||||
{'1': 'TargetAddress', '3': 3, '4': 1, '5': 9, '10': 'TargetAddress'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RbcuProbe`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List rbcuProbeDescriptor = $convert.base64Decode(
|
||||
'CglSYmN1UHJvYmUSHAoJU2Vzc2lvbklkGAEgASgJUglTZXNzaW9uSWQSEgoERGF0YRgCIAEoCV'
|
||||
'IERGF0YRIkCg1UYXJnZXRBZGRyZXNzGAMgASgJUg1UYXJnZXRBZGRyZXNz');
|
||||
|
||||
@$core.Deprecated('Use rbcuConfirmDescriptor instead')
|
||||
const RbcuConfirm$json = {
|
||||
'1': 'RbcuConfirm',
|
||||
'2': [
|
||||
{'1': 'SessionId', '3': 1, '4': 1, '5': 9, '10': 'SessionId'},
|
||||
{'1': 'ProbeAddress', '3': 2, '4': 1, '5': 9, '10': 'ProbeAddress'},
|
||||
{'1': 'ReceiveAddress', '3': 3, '4': 1, '5': 9, '10': 'ReceiveAddress'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RbcuConfirm`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List rbcuConfirmDescriptor = $convert.base64Decode(
|
||||
'CgtSYmN1Q29uZmlybRIcCglTZXNzaW9uSWQYASABKAlSCVNlc3Npb25JZBIiCgxQcm9iZUFkZH'
|
||||
'Jlc3MYAiABKAlSDFByb2JlQWRkcmVzcxImCg5SZWNlaXZlQWRkcmVzcxgDIAEoCVIOUmVjZWl2'
|
||||
'ZUFkZHJlc3M=');
|
||||
|
||||
14
lib/talk/startChart/proto/rbcu.pbserver.dart
Normal file
14
lib/talk/startChart/proto/rbcu.pbserver.dart
Normal file
@ -0,0 +1,14 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: rbcu.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
export 'rbcu.pb.dart';
|
||||
|
||||
39
lib/talk/startChart/proto/rbcu.proto
Normal file
39
lib/talk/startChart/proto/rbcu.proto
Normal file
@ -0,0 +1,39 @@
|
||||
//Relay-Based Connection Upgrade (RBCU) 是通信流程中的一个步骤。它通过中继路径确保基础通信的可用性,并在此基础上,尝试升级到更高效的直连(P2P)方式。
|
||||
|
||||
//## RBCU 流程
|
||||
//前提:1. 已建立的中继连接, 2. 可以获取自己socket对应的外网地址端口(STUN或其他)。
|
||||
//设定主动方为A,被动方为B
|
||||
//1. 【基于中继】A获取地址列表,形成RbcuInfo发送到B,并等待RbcuInfo。
|
||||
//2. 【基于中继】B收到RbcuInfo,将自己的地址列表形成RbcuResp作为响应。
|
||||
//4. 【基于UDP】AB向对方的地址列表发送RbcuProbe
|
||||
//5. 【基于UDP】AB收到RbcuProbe则认为直连成功,并回复RbcuConfirm
|
||||
// 参阅:https://docs.star-lock.cn/zh/starchart/rbcu
|
||||
|
||||
syntax = "proto3";
|
||||
package main;
|
||||
option go_package = "./spb/rbcu";
|
||||
|
||||
// RbcuInfo 地址交换
|
||||
message RbcuInfo {
|
||||
string SessionId = 1; // 随机UUID,用于匹配接下来的打洞会话
|
||||
string Name = 2; // 用于标识自己的名字
|
||||
repeated string Address = 4; // 地址+端口 列表
|
||||
uint64 Time = 5; // 开始时间戳
|
||||
bool isResp = 6; // 是否是响应, 如果是响应,就不用回复了, 第一个发起的人这里传false,后续收到并发出的人这里传true
|
||||
}
|
||||
|
||||
// RbcuProbe 万箭齐发
|
||||
message RbcuProbe {
|
||||
string SessionId = 1; // RbcuInfo的UUID,用于匹配
|
||||
string Data = 2; // 100字节随机数据,因为有些防火墙会拦截小包(空白也行,但是会被pb压缩,所以最好填充随机或顺序数据)
|
||||
string TargetAddress = 3; // 目标地址,例如这个包我发往192.168.1.2:9000 那么这个字段就是192.168.1.2:9000
|
||||
}
|
||||
|
||||
// RbcuConfirm 连通确认
|
||||
message RbcuConfirm {
|
||||
string SessionId = 1; // RbcuInfo的UUID,用于匹配
|
||||
string ProbeAddress = 2; // 地址+端口 从RbcuProbe里面取的TargetAddress
|
||||
string ReceiveAddress = 3; // 地址+端口 收到RbcuProbe的来源地址(udp_read()函数的返回值)
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:convert/convert.dart';
|
||||
import 'package:fast_rsa/fast_rsa.dart' as fastRsa;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.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/login/login/entity/LoginData.dart';
|
||||
@ -25,11 +19,13 @@ 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/talk/startChart/exception/start_chart_message_exception.dart';
|
||||
import 'package:star_lock/talk/startChart/handle/other/do_sign.dart';
|
||||
import 'package:star_lock/talk/startChart/handle/other/talke_data_over_time_timer_manager.dart';
|
||||
import 'package:star_lock/talk/startChart/handle/other/talke_ping_over_time_timer_manager.dart';
|
||||
import 'package:star_lock/talk/startChart/handle/other/talke_request_over_time_timer_manager.dart';
|
||||
import 'package:star_lock/talk/startChart/handle/scp_message_handle.dart';
|
||||
import 'package:star_lock/talk/startChart/handle/scp_message_handler_factory.dart';
|
||||
import 'package:star_lock/talk/startChart/proto/rbcu.pb.dart';
|
||||
import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart';
|
||||
import 'package:star_lock/talk/startChart/proto/talk_expect.pb.dart';
|
||||
import 'package:star_lock/talk/startChart/proto/talk_expect.pbserver.dart';
|
||||
@ -39,9 +35,6 @@ import 'package:star_lock/tools/deviceInfo_utils.dart';
|
||||
import 'package:star_lock/tools/storage.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:asn1lib/asn1lib.dart' as asn1lib; // Prefix for asn1lib
|
||||
|
||||
class StartChartManage {
|
||||
// 私有构造函数,防止外部直接new对象
|
||||
StartChartManage._internal();
|
||||
@ -64,16 +57,13 @@ class StartChartManage {
|
||||
final String _productName = F.navTitle;
|
||||
|
||||
RawDatagramSocket? _udpSocket;
|
||||
final Map<String, Completer<void>> _completers = {}; // 发送消息的请求是否完成
|
||||
final Uuid _uuid = Uuid(); // 用于区分发送的消息的唯一id
|
||||
int _messageMaxTimeout = 5; // 消息最大超时时间(s)
|
||||
final Uuid _uuid = Uuid(); // 随机UUID,用于匹配接下来的打洞会话
|
||||
late String remoteHost = ''; // 远程主机地址(服务器返回)
|
||||
late int remotePort = 0; // 远程主机端口(服务器返回)
|
||||
final int localPort = 62289; // 本地端口
|
||||
String localPublicHost = ''; // 本地公网ip地址
|
||||
|
||||
int heartbeatIntervalTime = 1; // 心跳包间隔时间(s)
|
||||
|
||||
Timer? _heartBeatTimer; // 心跳包定时器
|
||||
bool _heartBeatTimerRunning = false; // 心跳包定时任务发送状态
|
||||
|
||||
@ -84,15 +74,14 @@ class StartChartManage {
|
||||
final String echoPeerId = '3phX8Ng2cZHz5NtP8xAf6nYy2z1BYytoejgjoHrWMGhH';
|
||||
|
||||
bool isOnlineStartChartServer = false; // 星图是否上线成功
|
||||
int reStartOnlineStartChartServerIntervalTime = 1; // 重新上线星图服务任务间隔(s)
|
||||
Timer? reStartOnlineStartChartServerTimer; // 重新上线定时器
|
||||
int talkPingIntervalTime = 1; // 发送通话保持消息间隔(s)
|
||||
Timer? talkPingTimer; // 发送通话保持消息定时器
|
||||
int talkExpectIntervalTime = 1; // 发送通话预期数据的消息间隔(s)
|
||||
Timer? talkExpectTimer; // 发送通话预期消息定时器
|
||||
Timer? talkAcceptTimer; // 重发同意接听消息定时器
|
||||
int talkDataIntervalTime = 10; // 通话数据的消息间隔(ms)
|
||||
int talkDataIntervalTime = 10; // 发送通话数据的消息间隔(ms)
|
||||
int _defaultIntervalTime = 1; // 默认定时发送间隔(s)
|
||||
Timer? talkDataTimer; // 发送通话数据消息定时器
|
||||
Timer? rbcuInfoTimer; // p2p地址交换定时器
|
||||
|
||||
final int _maxPayloadSize = 8 * 1024; // 分包大小
|
||||
|
||||
@ -102,9 +91,6 @@ class StartChartManage {
|
||||
audioType: [AudioTypeE.G711],
|
||||
);
|
||||
|
||||
// 默认通话数据
|
||||
TalkData _defaultTalkData = TalkData();
|
||||
|
||||
String relayPeerId = ''; // 中继peerId
|
||||
|
||||
// 获取 StartChartTalkStatus 的唯一实例
|
||||
@ -234,6 +220,65 @@ class StartChartManage {
|
||||
}
|
||||
}
|
||||
|
||||
// 发送RbcuInfo 地址交换消息
|
||||
void _sendRbcuInfoMessage() async {
|
||||
final uuid = _uuid.v1();
|
||||
final int timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
final Int64 int64Timestamp = Int64(timestamp); // 使用构造函数
|
||||
|
||||
// 获取本机所有ip地址和中继返回的外网地址
|
||||
final List<ListenAddrData> listenAddrDataList =
|
||||
await _makeListenAddrDataList();
|
||||
listenAddrDataList.insert(
|
||||
0, // 插入到头部
|
||||
ListenAddrData(
|
||||
type: ListenAddrTypeConstant.local,
|
||||
address: localPublicHost + ':' + localPort.toString(),
|
||||
),
|
||||
);
|
||||
|
||||
final address = listenAddrDataList
|
||||
.where((element) =>
|
||||
element.type == ListenAddrTypeConstant.local) // 过滤出本地地址
|
||||
.map((e) => e.address) // 转换为 List<String?>
|
||||
.where((addr) => addr != null) // 过滤掉 null 值
|
||||
.map(
|
||||
(addr) => addr!.replaceAll(IpConstant.udpUrl, ''),
|
||||
) // 去除 "udp://" 前缀
|
||||
.cast<
|
||||
String>(); // 转换为 Iterable<String>// 将 Iterable<String?> 转换为 Iterable<String>
|
||||
final RbcuInfo rbcuInfo = RbcuInfo(
|
||||
sessionId: uuid,
|
||||
name: uuid,
|
||||
address: address,
|
||||
time: int64Timestamp,
|
||||
isResp: false,
|
||||
);
|
||||
final message = MessageCommand.genericRbcuInfoMessage(
|
||||
ToPeerId: ToPeerId,
|
||||
FromPeerId: FromPeerId,
|
||||
MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true),
|
||||
rbcuInfo: rbcuInfo,
|
||||
);
|
||||
_sendMessage(message: message);
|
||||
}
|
||||
|
||||
// 启动定时任务
|
||||
void startSendingRbcuInfoMessages() {
|
||||
// 每隔 1 秒执行一次 _sendRbcuInfoMessage
|
||||
rbcuInfoTimer ??=
|
||||
Timer.periodic(Duration(seconds: _defaultIntervalTime), (timer) {
|
||||
// 发送RbcuInfo 地址交换消息
|
||||
_sendRbcuInfoMessage();
|
||||
});
|
||||
}
|
||||
|
||||
// 停止定时任务
|
||||
void stopSendingRbcuInfoMessages() {
|
||||
rbcuInfoTimer?.cancel(); // 取消定时器
|
||||
rbcuInfoTimer = null;
|
||||
}
|
||||
|
||||
// 发送上线消息
|
||||
Future<void> _sendOnlineMessage() async {
|
||||
if (isOnlineStartChartServer) {
|
||||
@ -296,14 +341,6 @@ class StartChartManage {
|
||||
}
|
||||
// 分包发送完了递增一下id
|
||||
MessageCommand.getNextMessageId(ToPeerId);
|
||||
//
|
||||
// final message = MessageCommand.talkDataMessage(
|
||||
// FromPeerId: FromPeerId,
|
||||
// ToPeerId: ToPeerId,
|
||||
// talkData: talkData,
|
||||
// MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true),
|
||||
// );
|
||||
// await _sendMessage(message: message);
|
||||
}
|
||||
|
||||
// 发送心跳包消息
|
||||
@ -495,7 +532,7 @@ class StartChartManage {
|
||||
}
|
||||
reStartOnlineStartChartServerTimer ??= Timer.periodic(
|
||||
Duration(
|
||||
seconds: reStartOnlineStartChartServerIntervalTime,
|
||||
seconds: _defaultIntervalTime,
|
||||
),
|
||||
(Timer timer) async {
|
||||
// 重新发送上线消息
|
||||
@ -575,7 +612,7 @@ class StartChartManage {
|
||||
// 获取私钥
|
||||
final privateKey = await getPrivateKey();
|
||||
// 生成签名
|
||||
final sign = await _generateSign(
|
||||
final sign = await DoSign().generateSign(
|
||||
currentTimestamp: relayInfoEntity!.time ?? 0,
|
||||
privateKeyHex: privateKey,
|
||||
);
|
||||
@ -697,135 +734,14 @@ class StartChartManage {
|
||||
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) {
|
||||
throw StartChartMessageException('❌--->上报信息生成签名时出现错误: $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;
|
||||
}
|
||||
|
||||
/// 转换私钥格式
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 将十六进制字符串转换为字节数组
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
/// 获取公钥
|
||||
Future<String> getPublicKey() async {
|
||||
// 从缓存中获取星图注册节点信息
|
||||
// final StarChartRegisterNodeEntity? starChartRegisterNodeInfo =
|
||||
// await Storage.getStarChartRegisterNodeInfo();
|
||||
// return starChartRegisterNodeInfo?.peer?.publicKey ?? '';
|
||||
final loginData = await Storage.getLoginData();
|
||||
return loginData?.starchart?.starchartPeerPublicKey ?? '';
|
||||
}
|
||||
|
||||
/// 获取私钥
|
||||
Future<String> getPrivateKey() async {
|
||||
// 从缓存中获取星图注册节点信息
|
||||
// final StarChartRegisterNodeEntity? starChartRegisterNodeInfo =
|
||||
// await Storage.getStarChartRegisterNodeInfo();
|
||||
// return starChartRegisterNodeInfo?.peer?.privateKey ?? '';
|
||||
final loginData = await Storage.getLoginData();
|
||||
return loginData?.starchart?.starchartPeerPrivateKey ?? '';
|
||||
}
|
||||
@ -881,7 +797,7 @@ class StartChartManage {
|
||||
void startTalkPingMessageTimer() {
|
||||
talkPingTimer ??= Timer.periodic(
|
||||
Duration(
|
||||
seconds: talkPingIntervalTime,
|
||||
seconds: _defaultIntervalTime,
|
||||
),
|
||||
(Timer timer) async {
|
||||
await sendTalkPingMessage(
|
||||
@ -902,7 +818,7 @@ class StartChartManage {
|
||||
void startTalkExpectTimer() {
|
||||
talkExpectTimer ??= Timer.periodic(
|
||||
Duration(
|
||||
seconds: talkExpectIntervalTime,
|
||||
seconds: _defaultIntervalTime,
|
||||
),
|
||||
(Timer timer) {
|
||||
// 发送期望接受消息
|
||||
@ -917,7 +833,7 @@ class StartChartManage {
|
||||
void startTalkAcceptTimer() {
|
||||
talkAcceptTimer ??= Timer.periodic(
|
||||
Duration(
|
||||
seconds: talkExpectIntervalTime,
|
||||
seconds: _defaultIntervalTime,
|
||||
),
|
||||
(Timer timer) {
|
||||
sendTalkAcceptMessage();
|
||||
@ -930,102 +846,6 @@ class StartChartManage {
|
||||
talkAcceptTimer = null; // 清除定时器引用
|
||||
}
|
||||
|
||||
/// 通话数据定时器
|
||||
void startTalkDataTimer() async {
|
||||
// 如果已经启动了就不运行
|
||||
if (talkDataTimer != null) return;
|
||||
// 读取 assets 文件
|
||||
final ByteData data = await rootBundle.load('assets/talk.h264');
|
||||
final List<int> byteData = data.buffer.asUint8List();
|
||||
int current = 0;
|
||||
int start = 0;
|
||||
int end = 0;
|
||||
final List<int> chunks = extractChunks(byteData);
|
||||
talkDataTimer ??= Timer.periodic(
|
||||
Duration(
|
||||
milliseconds: talkDataIntervalTime,
|
||||
),
|
||||
(Timer timer) {
|
||||
if (current >= chunks.length) {
|
||||
print('数据已经发完');
|
||||
start = 0;
|
||||
end = 0;
|
||||
current = 0;
|
||||
timer.cancel();
|
||||
return;
|
||||
}
|
||||
// 提取 NALU 边界并生成 chunks
|
||||
end = chunks[current];
|
||||
current++;
|
||||
List<int> frameData = byteData.sublist(start, end);
|
||||
if (frameData.length == 0) timer.cancel();
|
||||
_defaultTalkData = TalkData(
|
||||
content: frameData,
|
||||
contentType: TalkData_ContentTypeE.H264,
|
||||
);
|
||||
start = end;
|
||||
// 发送通话数据
|
||||
sendTalkDataMessage(talkData: _defaultTalkData);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<int> extractChunks(List<int> byteData) {
|
||||
int i = 0;
|
||||
int length = byteData.length;
|
||||
int naluCount = 0;
|
||||
int value;
|
||||
int state = 0;
|
||||
int lastIndex = 0;
|
||||
List<int> result = [];
|
||||
const minNaluPerChunk = 22; // 每个数据块包含的最小NALU数量
|
||||
|
||||
while (i < length) {
|
||||
value = byteData[i++];
|
||||
// finding 3 or 4-byte start codes (00 00 01 OR 00 00 00 01)
|
||||
switch (state) {
|
||||
case 0:
|
||||
if (value == 0) {
|
||||
state = 1;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (value == 0) {
|
||||
state = 2;
|
||||
} else {
|
||||
state = 0;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
if (value == 0) {
|
||||
state = 3;
|
||||
} else if (value == 1 && i < length) {
|
||||
if (lastIndex > 0) {
|
||||
naluCount++;
|
||||
}
|
||||
if (naluCount >= minNaluPerChunk) {
|
||||
result.add(lastIndex - state - 1);
|
||||
naluCount = 0;
|
||||
}
|
||||
state = 0;
|
||||
lastIndex = i;
|
||||
} else {
|
||||
state = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (naluCount > 0) {
|
||||
result.add(lastIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 停止发送通话数据
|
||||
void stopTalkDataTimer() {
|
||||
talkDataTimer?.cancel();
|
||||
@ -1061,7 +881,6 @@ class StartChartManage {
|
||||
talkExpect: talkExpectReq);
|
||||
}
|
||||
|
||||
|
||||
/// 修改预期接收到的数据
|
||||
void sendImageVideoAndG711AudioTalkExpectData() {
|
||||
final talkExpectReq = TalkExpectReq(
|
||||
@ -1080,15 +899,18 @@ class StartChartManage {
|
||||
stopTalkPingMessageTimer();
|
||||
stopReStartOnlineStartChartServer();
|
||||
stopTalkDataTimer();
|
||||
stopSendingRbcuInfoMessages();
|
||||
_resetData();
|
||||
await Storage.removerRelayInfo();
|
||||
await Storage.removerStarChartRegisterNodeInfo();
|
||||
}
|
||||
|
||||
/// 重置数据
|
||||
void _resetData() {
|
||||
_defaultTalkExpect = TalkExpectReq(
|
||||
videoType: [VideoTypeE.IMAGE],
|
||||
audioType: [AudioTypeE.G711],
|
||||
);
|
||||
isOnlineStartChartServer = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,15 +199,16 @@ class _TalkViewPageState extends State<TalkViewPage>
|
||||
// 录制
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
if (state.talkStatus.value == TalkStatus.answeredSuccessfully) {
|
||||
if (state.isRecordingScreen.value) {
|
||||
await logic.stopRecording();
|
||||
print('停止录屏');
|
||||
} else {
|
||||
await logic.startRecording();
|
||||
print('开始录屏');
|
||||
}
|
||||
}
|
||||
logic.showToast('功能暂未开放'.tr);
|
||||
// if (state.talkStatus.value == TalkStatus.answeredSuccessfully) {
|
||||
// if (state.isRecordingScreen.value) {
|
||||
// await logic.stopRecording();
|
||||
// print('停止录屏');
|
||||
// } else {
|
||||
// await logic.startRecording();
|
||||
// print('开始录屏');
|
||||
// }
|
||||
// }
|
||||
},
|
||||
child: Container(
|
||||
width: 50.w,
|
||||
|
||||
@ -260,6 +260,7 @@ dependencies:
|
||||
flutter_screen_recording: 2.0.16
|
||||
#图库保存
|
||||
gallery_saver: ^2.3.2
|
||||
fixnum: ^1.1.1
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user