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/entity/scp_message.dart';
|
||||||
import 'package:star_lock/talk/startChart/proto/gateway_reset.pb.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/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_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_expect.pb.dart';
|
||||||
import 'package:star_lock/talk/startChart/proto/talk_hangup.pb.dart';
|
import 'package:star_lock/talk/startChart/proto/talk_hangup.pb.dart';
|
||||||
import 'package:star_lock/talk/startChart/proto/talk_ping.pb.dart';
|
import 'package:star_lock/talk/startChart/proto/talk_ping.pb.dart';
|
||||||
@ -375,6 +375,32 @@ class MessageCommand {
|
|||||||
return _hexToBytes(serializedBytesString);
|
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进制字符串转换为字节列表
|
// 辅助方法:将16进制字符串转换为字节列表
|
||||||
static List<int> _hexToBytes(String hex) {
|
static List<int> _hexToBytes(String hex) {
|
||||||
final bytes = <int>[];
|
final bytes = <int>[];
|
||||||
|
|||||||
@ -55,4 +55,13 @@ class PayloadTypeConstant {
|
|||||||
|
|
||||||
// 通话中挂断
|
// 通话中挂断
|
||||||
static const int talkHangup = 1668;
|
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();
|
return UdpTalkDataHandler();
|
||||||
case PayloadTypeConstant.talkHangup:
|
case PayloadTypeConstant.talkHangup:
|
||||||
return UdpTalkHangUpHandler();
|
return UdpTalkHangUpHandler();
|
||||||
|
case PayloadTypeConstant.RbcuInfo:
|
||||||
|
return UdpTalkHangUpHandler();
|
||||||
default:
|
default:
|
||||||
return UnKnowPayloadTypeHandler();
|
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 'dart:async';
|
||||||
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'dart:io';
|
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/app_settings/app_settings.dart';
|
||||||
import 'package:star_lock/flavors.dart';
|
import 'package:star_lock/flavors.dart';
|
||||||
import 'package:star_lock/login/login/entity/LoginData.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/scp_message.dart';
|
||||||
import 'package:star_lock/talk/startChart/entity/star_chart_register_node_entity.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/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_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_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/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_handle.dart';
|
||||||
import 'package:star_lock/talk/startChart/handle/scp_message_handler_factory.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_data.pb.dart';
|
||||||
import 'package:star_lock/talk/startChart/proto/talk_expect.pb.dart';
|
import 'package:star_lock/talk/startChart/proto/talk_expect.pb.dart';
|
||||||
import 'package:star_lock/talk/startChart/proto/talk_expect.pbserver.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:star_lock/tools/storage.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:asn1lib/asn1lib.dart' as asn1lib; // Prefix for asn1lib
|
|
||||||
|
|
||||||
class StartChartManage {
|
class StartChartManage {
|
||||||
// 私有构造函数,防止外部直接new对象
|
// 私有构造函数,防止外部直接new对象
|
||||||
StartChartManage._internal();
|
StartChartManage._internal();
|
||||||
@ -64,16 +57,13 @@ class StartChartManage {
|
|||||||
final String _productName = F.navTitle;
|
final String _productName = F.navTitle;
|
||||||
|
|
||||||
RawDatagramSocket? _udpSocket;
|
RawDatagramSocket? _udpSocket;
|
||||||
final Map<String, Completer<void>> _completers = {}; // 发送消息的请求是否完成
|
final Uuid _uuid = Uuid(); // 随机UUID,用于匹配接下来的打洞会话
|
||||||
final Uuid _uuid = Uuid(); // 用于区分发送的消息的唯一id
|
|
||||||
int _messageMaxTimeout = 5; // 消息最大超时时间(s)
|
|
||||||
late String remoteHost = ''; // 远程主机地址(服务器返回)
|
late String remoteHost = ''; // 远程主机地址(服务器返回)
|
||||||
late int remotePort = 0; // 远程主机端口(服务器返回)
|
late int remotePort = 0; // 远程主机端口(服务器返回)
|
||||||
final int localPort = 62289; // 本地端口
|
final int localPort = 62289; // 本地端口
|
||||||
String localPublicHost = ''; // 本地公网ip地址
|
String localPublicHost = ''; // 本地公网ip地址
|
||||||
|
|
||||||
int heartbeatIntervalTime = 1; // 心跳包间隔时间(s)
|
int heartbeatIntervalTime = 1; // 心跳包间隔时间(s)
|
||||||
|
|
||||||
Timer? _heartBeatTimer; // 心跳包定时器
|
Timer? _heartBeatTimer; // 心跳包定时器
|
||||||
bool _heartBeatTimerRunning = false; // 心跳包定时任务发送状态
|
bool _heartBeatTimerRunning = false; // 心跳包定时任务发送状态
|
||||||
|
|
||||||
@ -84,15 +74,14 @@ class StartChartManage {
|
|||||||
final String echoPeerId = '3phX8Ng2cZHz5NtP8xAf6nYy2z1BYytoejgjoHrWMGhH';
|
final String echoPeerId = '3phX8Ng2cZHz5NtP8xAf6nYy2z1BYytoejgjoHrWMGhH';
|
||||||
|
|
||||||
bool isOnlineStartChartServer = false; // 星图是否上线成功
|
bool isOnlineStartChartServer = false; // 星图是否上线成功
|
||||||
int reStartOnlineStartChartServerIntervalTime = 1; // 重新上线星图服务任务间隔(s)
|
|
||||||
Timer? reStartOnlineStartChartServerTimer; // 重新上线定时器
|
Timer? reStartOnlineStartChartServerTimer; // 重新上线定时器
|
||||||
int talkPingIntervalTime = 1; // 发送通话保持消息间隔(s)
|
|
||||||
Timer? talkPingTimer; // 发送通话保持消息定时器
|
Timer? talkPingTimer; // 发送通话保持消息定时器
|
||||||
int talkExpectIntervalTime = 1; // 发送通话预期数据的消息间隔(s)
|
|
||||||
Timer? talkExpectTimer; // 发送通话预期消息定时器
|
Timer? talkExpectTimer; // 发送通话预期消息定时器
|
||||||
Timer? talkAcceptTimer; // 重发同意接听消息定时器
|
Timer? talkAcceptTimer; // 重发同意接听消息定时器
|
||||||
int talkDataIntervalTime = 10; // 通话数据的消息间隔(ms)
|
int talkDataIntervalTime = 10; // 发送通话数据的消息间隔(ms)
|
||||||
|
int _defaultIntervalTime = 1; // 默认定时发送间隔(s)
|
||||||
Timer? talkDataTimer; // 发送通话数据消息定时器
|
Timer? talkDataTimer; // 发送通话数据消息定时器
|
||||||
|
Timer? rbcuInfoTimer; // p2p地址交换定时器
|
||||||
|
|
||||||
final int _maxPayloadSize = 8 * 1024; // 分包大小
|
final int _maxPayloadSize = 8 * 1024; // 分包大小
|
||||||
|
|
||||||
@ -102,9 +91,6 @@ class StartChartManage {
|
|||||||
audioType: [AudioTypeE.G711],
|
audioType: [AudioTypeE.G711],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 默认通话数据
|
|
||||||
TalkData _defaultTalkData = TalkData();
|
|
||||||
|
|
||||||
String relayPeerId = ''; // 中继peerId
|
String relayPeerId = ''; // 中继peerId
|
||||||
|
|
||||||
// 获取 StartChartTalkStatus 的唯一实例
|
// 获取 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 {
|
Future<void> _sendOnlineMessage() async {
|
||||||
if (isOnlineStartChartServer) {
|
if (isOnlineStartChartServer) {
|
||||||
@ -296,14 +341,6 @@ class StartChartManage {
|
|||||||
}
|
}
|
||||||
// 分包发送完了递增一下id
|
// 分包发送完了递增一下id
|
||||||
MessageCommand.getNextMessageId(ToPeerId);
|
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(
|
reStartOnlineStartChartServerTimer ??= Timer.periodic(
|
||||||
Duration(
|
Duration(
|
||||||
seconds: reStartOnlineStartChartServerIntervalTime,
|
seconds: _defaultIntervalTime,
|
||||||
),
|
),
|
||||||
(Timer timer) async {
|
(Timer timer) async {
|
||||||
// 重新发送上线消息
|
// 重新发送上线消息
|
||||||
@ -575,7 +612,7 @@ class StartChartManage {
|
|||||||
// 获取私钥
|
// 获取私钥
|
||||||
final privateKey = await getPrivateKey();
|
final privateKey = await getPrivateKey();
|
||||||
// 生成签名
|
// 生成签名
|
||||||
final sign = await _generateSign(
|
final sign = await DoSign().generateSign(
|
||||||
currentTimestamp: relayInfoEntity!.time ?? 0,
|
currentTimestamp: relayInfoEntity!.time ?? 0,
|
||||||
privateKeyHex: privateKey,
|
privateKeyHex: privateKey,
|
||||||
);
|
);
|
||||||
@ -697,135 +734,14 @@ class StartChartManage {
|
|||||||
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join('');
|
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 {
|
Future<String> getPublicKey() async {
|
||||||
// 从缓存中获取星图注册节点信息
|
|
||||||
// final StarChartRegisterNodeEntity? starChartRegisterNodeInfo =
|
|
||||||
// await Storage.getStarChartRegisterNodeInfo();
|
|
||||||
// return starChartRegisterNodeInfo?.peer?.publicKey ?? '';
|
|
||||||
final loginData = await Storage.getLoginData();
|
final loginData = await Storage.getLoginData();
|
||||||
return loginData?.starchart?.starchartPeerPublicKey ?? '';
|
return loginData?.starchart?.starchartPeerPublicKey ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取私钥
|
/// 获取私钥
|
||||||
Future<String> getPrivateKey() async {
|
Future<String> getPrivateKey() async {
|
||||||
// 从缓存中获取星图注册节点信息
|
|
||||||
// final StarChartRegisterNodeEntity? starChartRegisterNodeInfo =
|
|
||||||
// await Storage.getStarChartRegisterNodeInfo();
|
|
||||||
// return starChartRegisterNodeInfo?.peer?.privateKey ?? '';
|
|
||||||
final loginData = await Storage.getLoginData();
|
final loginData = await Storage.getLoginData();
|
||||||
return loginData?.starchart?.starchartPeerPrivateKey ?? '';
|
return loginData?.starchart?.starchartPeerPrivateKey ?? '';
|
||||||
}
|
}
|
||||||
@ -881,7 +797,7 @@ class StartChartManage {
|
|||||||
void startTalkPingMessageTimer() {
|
void startTalkPingMessageTimer() {
|
||||||
talkPingTimer ??= Timer.periodic(
|
talkPingTimer ??= Timer.periodic(
|
||||||
Duration(
|
Duration(
|
||||||
seconds: talkPingIntervalTime,
|
seconds: _defaultIntervalTime,
|
||||||
),
|
),
|
||||||
(Timer timer) async {
|
(Timer timer) async {
|
||||||
await sendTalkPingMessage(
|
await sendTalkPingMessage(
|
||||||
@ -902,7 +818,7 @@ class StartChartManage {
|
|||||||
void startTalkExpectTimer() {
|
void startTalkExpectTimer() {
|
||||||
talkExpectTimer ??= Timer.periodic(
|
talkExpectTimer ??= Timer.periodic(
|
||||||
Duration(
|
Duration(
|
||||||
seconds: talkExpectIntervalTime,
|
seconds: _defaultIntervalTime,
|
||||||
),
|
),
|
||||||
(Timer timer) {
|
(Timer timer) {
|
||||||
// 发送期望接受消息
|
// 发送期望接受消息
|
||||||
@ -917,7 +833,7 @@ class StartChartManage {
|
|||||||
void startTalkAcceptTimer() {
|
void startTalkAcceptTimer() {
|
||||||
talkAcceptTimer ??= Timer.periodic(
|
talkAcceptTimer ??= Timer.periodic(
|
||||||
Duration(
|
Duration(
|
||||||
seconds: talkExpectIntervalTime,
|
seconds: _defaultIntervalTime,
|
||||||
),
|
),
|
||||||
(Timer timer) {
|
(Timer timer) {
|
||||||
sendTalkAcceptMessage();
|
sendTalkAcceptMessage();
|
||||||
@ -930,102 +846,6 @@ class StartChartManage {
|
|||||||
talkAcceptTimer = null; // 清除定时器引用
|
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() {
|
void stopTalkDataTimer() {
|
||||||
talkDataTimer?.cancel();
|
talkDataTimer?.cancel();
|
||||||
@ -1061,7 +881,6 @@ class StartChartManage {
|
|||||||
talkExpect: talkExpectReq);
|
talkExpect: talkExpectReq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// 修改预期接收到的数据
|
/// 修改预期接收到的数据
|
||||||
void sendImageVideoAndG711AudioTalkExpectData() {
|
void sendImageVideoAndG711AudioTalkExpectData() {
|
||||||
final talkExpectReq = TalkExpectReq(
|
final talkExpectReq = TalkExpectReq(
|
||||||
@ -1080,15 +899,18 @@ class StartChartManage {
|
|||||||
stopTalkPingMessageTimer();
|
stopTalkPingMessageTimer();
|
||||||
stopReStartOnlineStartChartServer();
|
stopReStartOnlineStartChartServer();
|
||||||
stopTalkDataTimer();
|
stopTalkDataTimer();
|
||||||
|
stopSendingRbcuInfoMessages();
|
||||||
_resetData();
|
_resetData();
|
||||||
await Storage.removerRelayInfo();
|
await Storage.removerRelayInfo();
|
||||||
await Storage.removerStarChartRegisterNodeInfo();
|
await Storage.removerStarChartRegisterNodeInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 重置数据
|
||||||
void _resetData() {
|
void _resetData() {
|
||||||
_defaultTalkExpect = TalkExpectReq(
|
_defaultTalkExpect = TalkExpectReq(
|
||||||
videoType: [VideoTypeE.IMAGE],
|
videoType: [VideoTypeE.IMAGE],
|
||||||
audioType: [AudioTypeE.G711],
|
audioType: [AudioTypeE.G711],
|
||||||
);
|
);
|
||||||
|
isOnlineStartChartServer = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -199,15 +199,16 @@ class _TalkViewPageState extends State<TalkViewPage>
|
|||||||
// 录制
|
// 录制
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (state.talkStatus.value == TalkStatus.answeredSuccessfully) {
|
logic.showToast('功能暂未开放'.tr);
|
||||||
if (state.isRecordingScreen.value) {
|
// if (state.talkStatus.value == TalkStatus.answeredSuccessfully) {
|
||||||
await logic.stopRecording();
|
// if (state.isRecordingScreen.value) {
|
||||||
print('停止录屏');
|
// await logic.stopRecording();
|
||||||
} else {
|
// print('停止录屏');
|
||||||
await logic.startRecording();
|
// } else {
|
||||||
print('开始录屏');
|
// await logic.startRecording();
|
||||||
}
|
// print('开始录屏');
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 50.w,
|
width: 50.w,
|
||||||
|
|||||||
@ -260,6 +260,7 @@ dependencies:
|
|||||||
flutter_screen_recording: 2.0.16
|
flutter_screen_recording: 2.0.16
|
||||||
#图库保存
|
#图库保存
|
||||||
gallery_saver: ^2.3.2
|
gallery_saver: ^2.3.2
|
||||||
|
fixnum: ^1.1.1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user