FIX: cmd в пакетах FEAT: получение user agent'а
This commit is contained in:
+72
-12
@@ -10,7 +10,18 @@ import '../core/transport/receiver.dart';
|
||||
import '../core/transport/sender.dart';
|
||||
import '../core/utils/logger.dart';
|
||||
|
||||
enum SessionState { disconnected, connecting, connected, online }
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'dart:io';
|
||||
import 'package:timezone/data/latest_all.dart' as tz;
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
import 'package:flutter_timezone/flutter_timezone.dart';
|
||||
|
||||
enum SessionState {
|
||||
disconnected,
|
||||
connecting,
|
||||
connected,
|
||||
online
|
||||
}
|
||||
|
||||
/// Клиент API.
|
||||
///
|
||||
@@ -39,7 +50,7 @@ class Api {
|
||||
/// Подключается к серверу, шлёт хэндшейк, запускает пинг.
|
||||
Future<void> connect() async {
|
||||
if (_sessionState != SessionState.disconnected) return;
|
||||
|
||||
// Ставим автоматический реконнект и статус подключения
|
||||
_autoReconnect = true;
|
||||
_setSessionState(SessionState.connecting);
|
||||
|
||||
@@ -88,27 +99,75 @@ class Api {
|
||||
}
|
||||
|
||||
/// Отправляет хэндшейк (opcode 6).
|
||||
Future<Packet> sendHandshake() {
|
||||
// TODO: заменить захардкоженные данные на реальные
|
||||
Future<Packet> sendHandshake() async {
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
|
||||
// Если платформа Linux или Windows, то ставим DESKTOP, если нет, то проверяем на Android или IOS;
|
||||
String deviceType = (Platform.isLinux || Platform.isWindows) ? "DESKTOP" : (Platform.isAndroid) ? "ANDROID" : "IOS";
|
||||
String osVersion = "";
|
||||
String deviceName = "Unknown";
|
||||
String architecture = "arm64";
|
||||
|
||||
tz.initializeTimeZones();
|
||||
|
||||
final now = DateTime.now();
|
||||
String timezone = "Europe/Moscow";
|
||||
|
||||
tz.initializeTimeZones();
|
||||
final timeZoneName = await FlutterTimezone.getLocalTimezone();
|
||||
timezone = timeZoneName.identifier;
|
||||
|
||||
// На каждой платформе свое инфо, поэтому делаем такую проверку
|
||||
if (Platform.isLinux) {
|
||||
LinuxDeviceInfo linuxInfo = await deviceInfo.linuxInfo;
|
||||
|
||||
osVersion = linuxInfo.name;
|
||||
// Platform.version содержит в себе что-то такое
|
||||
// 3.11.1 (stable) (Tue Feb 24 00:03:07 2026 -0800) on "linux_x64"
|
||||
// Поэтому мы находим '_', прибавляем к его индексу 1 и берем символы до length - 1
|
||||
architecture = Platform.version.substring(Platform.version.indexOf('_') + 1, Platform.version.length - 1);
|
||||
} else if (Platform.isIOS) {
|
||||
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
|
||||
|
||||
osVersion = iosInfo.systemVersion;
|
||||
deviceName = iosInfo.utsname.machine;
|
||||
} else if (Platform.isAndroid) {
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
|
||||
osVersion = "Android ${androidInfo.version.release}";
|
||||
deviceName = "${androidInfo.manufacturer} ${androidInfo.model}";
|
||||
architecture = androidInfo.supportedAbis.first;
|
||||
} else if (Platform.isWindows) {
|
||||
WindowsDeviceInfo windowsInfo = await deviceInfo.windowsInfo;
|
||||
|
||||
osVersion = windowsInfo.productName;
|
||||
architecture = Platform.version.substring(Platform.version.indexOf('_') + 1, Platform.version.length - 1);
|
||||
}
|
||||
|
||||
print(deviceType);
|
||||
final payload = <dynamic, dynamic>{
|
||||
'mt_instanceid': '550e8400-e29b-41d4-a716-446655440000',
|
||||
'clientSessionId': 42,
|
||||
'deviceId': 'a1b2c3d4e5f6a7b8',
|
||||
'userAgent': {
|
||||
'deviceType': "ANDROID",
|
||||
'locale': 'en',
|
||||
'deviceLocale': 'en_US',
|
||||
'osVersion': 'Ondroid 14',
|
||||
'deviceName': 'KometPhone',
|
||||
'deviceType': deviceType,
|
||||
// Первые два символа из locale это и есть нужный нам аргумент
|
||||
'locale': "ru",
|
||||
'deviceLocale': Platform.localeName.substring(0, 2),
|
||||
'osVersion': osVersion,
|
||||
'deviceName': deviceName,
|
||||
'appVersion': '26.8.1',
|
||||
'screen': '1920x1080',
|
||||
'timezone': 'Europe/Moscow',
|
||||
// 'screen': screenSize.width + 'x' + screenSize.height,
|
||||
'timezone': timezone,
|
||||
'pushDeviceType': 'GCM',
|
||||
'arch': 'arm64',
|
||||
'arch': architecture,
|
||||
'buildNumber': 6606,
|
||||
},
|
||||
};
|
||||
|
||||
print(payload);
|
||||
print(Platform.version);
|
||||
return sendRequest(Opcode.sessionInit, payload);
|
||||
}
|
||||
|
||||
@@ -142,7 +201,8 @@ class Api {
|
||||
_stateController.close();
|
||||
}
|
||||
|
||||
// Внутрянка
|
||||
// Внутрянка
|
||||
|
||||
|
||||
void _setSessionState(SessionState state) {
|
||||
if (_sessionState == state) return;
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:msgpack_dart/msgpack_dart.dart' as msgpack;
|
||||
|
||||
import '../utils/logger.dart';
|
||||
|
||||
/// ver(1) + cmd(2) + seq(1) + opcode(2) + packedLen(4) = 10
|
||||
/// ver(1) + cmd(1) + seq(2) + opcode(2) + packedLen(4) = 10
|
||||
const int headerSize = 10;
|
||||
const int _maxDecompressedSize = 1048576; // 1 MB
|
||||
|
||||
@@ -12,18 +12,19 @@ const int _maxDecompressedSize = 1048576; // 1 MB
|
||||
abstract class CmdType {
|
||||
static const int request = 0; // запрос клиента
|
||||
static const int push = 1; // пуш от сервера
|
||||
static const int ok = 0x100; // ответ: ок
|
||||
static const int notFound = 0x200; // ответ: не найдено
|
||||
static const int error = 0x300; // ответ: ошибка
|
||||
|
||||
static const int ok = 1; // ответ: ок
|
||||
static const int notFound = 2; // ответ: не найдено
|
||||
static const int error = 3; // ответ: ошибка
|
||||
}
|
||||
|
||||
/// Бинарный пакет
|
||||
/// Распакованный бинарный пакет
|
||||
///
|
||||
/// Формат заголовка (10 байт):
|
||||
/// ```
|
||||
/// [0] ver — версия протокола (uint8)
|
||||
/// [1..2] cmd — тип команды (uint16 BE)
|
||||
/// [3] seq — порядковый номер 0..255 (uint8)
|
||||
/// [0] ver — версия протокола (uint8) (по умолчанию 10)
|
||||
/// [1] cmd — тип команды (uint8) (при отправке от клиента равно 0)
|
||||
/// [2..3] seq — порядковый номер (uint16 BE)
|
||||
/// [4..5] opcode — код операции (uint16 BE)
|
||||
/// [6..9] packedLen — флаг сжатия [6] + длина payload [7..9] (uint32 BE)
|
||||
/// [10..] payload — данные в MsgPack, опционально сжатые LZ4
|
||||
@@ -56,8 +57,8 @@ class Packet {
|
||||
Uint8List packPacket(int opcode, Map<dynamic, dynamic> payload, {int seq = 0}) {
|
||||
final header = ByteData(headerSize);
|
||||
header.setUint8(0, 10);
|
||||
header.setUint16(1, CmdType.request, Endian.big);
|
||||
header.setUint8(3, seq);
|
||||
header.setUint8(1, CmdType.request);
|
||||
header.setUint16(2, seq, Endian.big);
|
||||
header.setUint16(4, opcode, Endian.big);
|
||||
|
||||
final payloadBytes = msgpack.serialize(payload);
|
||||
@@ -69,28 +70,38 @@ Uint8List packPacket(int opcode, Map<dynamic, dynamic> payload, {int seq = 0}) {
|
||||
|
||||
/// Распаковка пакета от сервера
|
||||
Packet unpackPacket(Uint8List packet) {
|
||||
final data = ByteData.view(
|
||||
// Для удобства расшифровки пакета переводим в ByteData
|
||||
ByteData packetData = ByteData.view(
|
||||
packet.buffer,
|
||||
packet.offsetInBytes,
|
||||
packet.lengthInBytes,
|
||||
);
|
||||
|
||||
final apiVer = data.getUint8(0);
|
||||
final cmd = data.getUint16(1, Endian.big);
|
||||
final seq = data.getUint8(3);
|
||||
final opcode = data.getUint16(4, Endian.big);
|
||||
final packedLen = data.getUint32(6, Endian.big);
|
||||
// Объяснение каждой переменной смотри в классе Packet
|
||||
|
||||
// API версия и cmd представляют из себя 8 битные числа
|
||||
final apiVer = packetData.getUint8(0) & 0xFF;
|
||||
final cmd = packetData.getUint8(1) & 0xFF;
|
||||
|
||||
// Sequence и OPCode представляют из себя 16 битные числа
|
||||
final seq = packetData.getUint16(2) & 0xFFFF;
|
||||
final opcode = packetData.getUint16(4) & 0xFFFF;
|
||||
|
||||
// После базовых переменных идет длина пакета, является 32 битным числом
|
||||
final packedLen = packetData.getUint32(6);
|
||||
|
||||
// Compression flag показывает, сжат ли payload
|
||||
final compFlag = packedLen >> 24;
|
||||
|
||||
// Длина payload'а
|
||||
final payloadLength = packedLen & 0xFFFFFF;
|
||||
|
||||
var payloadBytes = Uint8List.sublistView(
|
||||
packet,
|
||||
headerSize,
|
||||
headerSize + payloadLength,
|
||||
);
|
||||
dynamic payload;
|
||||
// Байты payload'а, могут быть сжаты LZ4
|
||||
var payloadBytes = packet.buffer.asUint8List(10, payloadLength);
|
||||
|
||||
dynamic payload;
|
||||
print(compFlag);
|
||||
// Если payload пустой, ничего не делаем (так может быть при получении пинга)
|
||||
if (payloadBytes.isNotEmpty) {
|
||||
if (compFlag != 0) {
|
||||
try {
|
||||
@@ -98,6 +109,7 @@ Packet unpackPacket(Uint8List packet) {
|
||||
payloadBytes,
|
||||
decompressedSize: _maxDecompressedSize,
|
||||
);
|
||||
|
||||
} catch (_) {
|
||||
// dart_lz4 не умеет block-формат, фолбэк на ручной декомпрессор
|
||||
try {
|
||||
@@ -110,7 +122,7 @@ Packet unpackPacket(Uint8List packet) {
|
||||
|
||||
try {
|
||||
payload = msgpack.deserialize(payloadBytes);
|
||||
} catch (e) {
|
||||
} catch (e) {
|
||||
logger.e("Ошибка десериализации MsgPack: $e", error: e);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user