fix:增加p2p调试命令

This commit is contained in:
liyi 2025-01-06 09:52:14 +08:00
parent 3bd96e58b9
commit c16c9be4c6
15 changed files with 768 additions and 280 deletions

File diff suppressed because one or more lines are too long

View File

@ -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>[];

View File

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

View 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;
}
}

View 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,
);
}
}

View File

@ -54,6 +54,8 @@ class ScpMessageHandlerFactory {
return UdpTalkDataHandler();
case PayloadTypeConstant.talkHangup:
return UdpTalkHangUpHandler();
case PayloadTypeConstant.RbcuInfo:
return UdpTalkHangUpHandler();
default:
return UnKnowPayloadTypeHandler();
}

View 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();
}
}

View 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');

View 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

View 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=');

View 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';

View File

@ -0,0 +1,39 @@
//Relay-Based Connection Upgrade (RBCU) P2P
//## RBCU
//1. 2. socket对应的外网地址端口STUN或其他
//AB
//1. A获取地址列表RbcuInfo发送到BRbcuInfo
//2. B收到RbcuInfoRbcuResp作为响应
//4. UDPAB向对方的地址列表发送RbcuProbe
//5. UDPAB收到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; // , falsetrue
}
// 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())
}

View File

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

View File

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

View File

@ -260,6 +260,7 @@ dependencies:
flutter_screen_recording: 2.0.16
#图库保存
gallery_saver: ^2.3.2
fixnum: ^1.1.1