diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..08303a5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cmake.sourceDirectory": "/run/media/invisedivine/Drive/! My projects/Komet/linux/runner" +} \ No newline at end of file diff --git a/lib/backend/api.dart b/lib/backend/api.dart index 92f4e2c..f014ad8 100644 --- a/lib/backend/api.dart +++ b/lib/backend/api.dart @@ -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 connect() async { if (_sessionState != SessionState.disconnected) return; - + // Ставим автоматический реконнект и статус подключения _autoReconnect = true; _setSessionState(SessionState.connecting); @@ -88,27 +99,75 @@ class Api { } /// Отправляет хэндшейк (opcode 6). - Future sendHandshake() { - // TODO: заменить захардкоженные данные на реальные + Future 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 = { '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; diff --git a/lib/core/protocol/packet.dart b/lib/core/protocol/packet.dart index d7c95f9..a7c2d62 100644 --- a/lib/core/protocol/packet.dart +++ b/lib/core/protocol/packet.dart @@ -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 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 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); } } diff --git a/pubspec.lock b/pubspec.lock index 3e29bd2..bc155f7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" clock: dependency: transitive description: @@ -57,6 +57,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: "4df8babf73058181227e18b08e6ea3520cf5fc5d796888d33b7cb0f33f984b7c" + url: "https://pub.dev" + source: hosted + version: "12.3.0" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f + url: "https://pub.dev" + source: hosted + version: "7.0.3" + equatable: + dependency: transitive + description: + name: equatable + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" + url: "https://pub.dev" + source: hosted + version: "2.0.8" fake_async: dependency: transitive description: @@ -65,6 +89,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" flutter: dependency: "direct main" description: flutter @@ -83,6 +123,35 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_timezone: + dependency: "direct main" + description: + name: flutter_timezone + sha256: "978192f2f9ea6d019a4de4f0211d76a9af955ca24865828fa98ca4e20cf0cb3c" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" leak_tracker: dependency: transitive description: @@ -127,18 +196,18 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: @@ -163,6 +232,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" sky_engine: dependency: transitive description: flutter @@ -212,10 +289,26 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.10" + timezone: + dependency: "direct main" + description: + name: timezone + sha256: "784a5e34d2eb62e1326f24d6f600aaaee452eb8ca8ef2f384a59244e292d158b" + url: "https://pub.dev" + source: hosted + version: "0.11.0" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: @@ -232,6 +325,30 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" + url: "https://pub.dev" + source: hosted + version: "2.1.0" sdks: dart: ">=3.10.4 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index a704a79..252d74e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,9 @@ dependencies: dart_lz4: ^1.0.0 msgpack_dart: ^1.0.1 logger: ^2.6.2 + device_info_plus: ^12.3.0 + flutter_timezone: ^5.0.1 + timezone: ^0.11.0 dev_dependencies: flutter_test: