/* Copyright (C) 2024 Damien Gaignon, José Rebelo, Martin.JM This file is part of Gadgetbridge. Gadgetbridge is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gadgetbridge is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.TreeMap; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import org.json.JSONException; import org.json.JSONObject; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiUtil; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupplier.HuaweiDeviceType; import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV.TLV; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; // TODO: complete responses public class DeviceConfig { public static final byte id = 0x01; public static class LinkParams { public static final byte id = 0x01; public static class Request extends HuaweiPacket { public Request(ParamsProvider paramsProvider, HuaweiDeviceType deviceType) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01) .put(0x02) .put(0x03) .put(0x04); if (deviceType == HuaweiDeviceType.AW) { this.tlv.put(0x06); } this.complete = true; this.isEncrypted = false; this.isSliced = false; } } public static class Response extends HuaweiPacket { public byte protocolVersion = 0x00; public short mtu = 0x0014; public short sliceSize = 0x00f4; public byte authVersion = 0x00; public byte[] serverNonce = new byte[16]; public byte deviceSupportType = 0x00; public byte authAlgo = 0x00; public byte bondState = 0x00; public short interval = 0x0; public byte encryptMethod = 0x00; public Response(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.isEncrypted = false; } @Override public void parseTlv() throws ParseException { if (this.tlv.contains(0x01)) this.protocolVersion = this.tlv.getByte(0x01); if (this.tlv.contains(0x02)) this.sliceSize = this.tlv.getShort(0x02); if (this.tlv.contains(0x03)) this.mtu = this.tlv.getShort(0x03); if (this.tlv.contains(0x04)) this.interval = this.tlv.getShort(0x04); System.arraycopy(this.tlv.getBytes(0x05), 2, this.serverNonce, 0, 16); this.authVersion = (byte)this.tlv.getBytes(0x05)[1]; if (this.tlv.contains(0x07)) this.deviceSupportType = this.tlv.getByte(0x07); if (this.tlv.contains(0x08)) this.authAlgo = this.tlv.getByte(0x08); if (this.tlv.contains(0x09)) this.bondState = this.tlv.getByte(0x09); if (this.tlv.contains(0x0C)) this.encryptMethod = this.tlv.getByte(0x0C); } } } public static class SupportedServices { public static final byte id = 0x02; // notDeviceCapabilities = 0x1C, 0x1E, 0x1F, 0x28, 0x29, 0x2C, 0x2F, 0x31 // but services = 0x1E, 0x28, 0x2C, 0x31 // service 0x21 depends on MiddleWear support public static final byte[] knownSupportedServices = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1D, 0x20, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x2A, 0x2B, 0x2D, 0x2E, 0x30, 0x32, 0x33, 0x34, 0x35 }; public static class Request extends HuaweiPacket { public Request(ParamsProvider paramsProvider, byte[] allSupportedServices) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01, allSupportedServices); this.complete = true; } } public static class Response extends HuaweiPacket { public byte[] supportedServices; public Response(ParamsProvider paramsProvider) { super(paramsProvider); } @Override public void parseTlv() throws ParseException { this.supportedServices = this.tlv.getBytes(0x02); } } public static class OutgoingRequest extends HuaweiPacket { public byte[] allSupportedServices = null; public OutgoingRequest(ParamsProvider paramsProvider) { super(paramsProvider); this.complete = false; } @Override public List serialize() throws CryptoException { return null; } @Override public void parseTlv() throws ParseException { this.allSupportedServices = this.tlv.getBytes(0x01); } } } public static class SupportedCommands { public static final byte id = 0x03; public static final TreeMap commandsPerService = new TreeMap() {{ put(0x01, new byte[] {0x04, 0x07, 0x08, 0x09, 0x0A, 0x0D, 0x0E, 0x10, 0x11, 0x12, 0x13, 0x14, 0x1B, 0x1A, 0x1D, 0x21, 0x22, 0x23, 0x24, 0x29, 0x2A, 0x2B, 0x32, 0x2E, 0x31, 0x30, 0x35, 0x36, 0x37, 0x2F}); put(0x02, new byte[] {0x01, 0x04, 0x05, 0x06, 0x07, 0x08}); put(0x03, new byte[] {0x01, 0x03, 0x04}); put(0x04, new byte[] {0x01}); put(0x05, new byte[] {0x01}); put(0x06, new byte[] {0x01}); put(0x07, new byte[] {0x01, 0x03, 0x05, 0x07, 0x08, 0x09, 0x0A, 0x0E, 0x10, 0x13, 0x16, 0x15, 0x17, 0x18, 0x1B, 0x1C, 0x1D, 0x1E, 0x21, 0x22, 0x23, 0x24, 0x25, 0x28, 0x29, 0x06, 0x1F}); put(0x08, new byte[] {0x01, 0x02, 0x03}); put(0x09, new byte[] {0x01, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}); put(0x0A, new byte[] {0x01, 0x09, 0x0A}); put(0x0B, new byte[] {0x01, 0x03}); put(0x0C, new byte[] {0x01}); put(0x0D, new byte[] {0x01}); put(0x0F, new byte[] {0x01, 0x03, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C}); put(0x10, new byte[] {0x01}); put(0x11, new byte[] {0x01}); put(0x12, new byte[] {0x01}); put(0x13, new byte[] {0x01}); put(0x14, new byte[] {0x01}); put(0x15, new byte[] {0x01}); put(0x16, new byte[] {0x01, 0x03, 0x07}); put(0x17, new byte[] {0x01, 0x04, 0x06, 0x07, 0x0B, 0x0C, 0x10, 0x12, 0x15, 0x17}); put(0x18, new byte[] {0x01, 0x02, 0x04, 0x05, 0x06, 0x09}); put(0x19, new byte[] {0x01, 0x04}); put(0x1A, new byte[] {0x01, 0x03, 0x07, 0x05, 0x06}); put(0x1B, new byte[] {0x01, 0x0F, 0x19, 0x1A}); // No 0x1C put(0x1D, new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}); // No 0x1E // No 0x1F put(0x20, new byte[] {0x01, 0x02, 0x03, 0x04, 0x09, 0x0A}); //put(0x21, new byte[] {0x01}); put(0x22, new byte[] {0x01}); put(0x23, new byte[] {0x02, 0x0B}); put(0x24, new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}); put(0x25, new byte[] {0x02, 0x04, 0x0E}); put(0x26, new byte[] {0x02, 0x03}); put(0x27, new byte[] {0x01, 0x0E}); // No 0x28 // No 0x29 put(0x2A, new byte[] {0x01, 0x06}); put(0x2B, new byte[] {0x12}); // No 0x2C put(0x2D, new byte[] {0x01}); put(0x2E, new byte[] {0x01, 0x02, 0x03}); // No 0x2F put(0x30, new byte[] {0x01}); // No 0x31 put(0x32, new byte[] {0x01}); put(0x33, new byte[] {0x01, 0x2}); put(0x34, new byte[] {0x01}); put(0x35, new byte[] {0x03, 0x04}); }}; public static class Request extends HuaweiPacket { private int maxSize; public Request(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV(); // slice size is the max size // But there is a 4 byte header, // a two byte body header, // a two byte footer, // three bytes get added for the 0x81 tag, // and 21 for the encryption stuff this.maxSize = this.paramsProvider.getSliceSize() - 32; // Now it gets aligned to 16 byte blocks // And it gets a header and two length bytes // For CBC there is also at least one byte padding this.maxSize = this.maxSize - (this.maxSize % 16) - 4; } public boolean addCommandsForService(byte service, byte[] commands) { // tlv length is what we have already // commands is what we'll add // together with a 3-byte 0x02 tag and two bytes for length if (this.tlv.length() + commands.length + 5 > this.maxSize) { //this.paramsProvider.mtu - 5 - 2 - 2 - 2 - 21) { return false; } this.tlv.put(0x02, service).put(0x03, commands); return true; } @Override public List serialize() throws CryptoException { this.tlv = new HuaweiTLV() .put(0x81, this.tlv); this.complete = true; return super.serialize(); } } public static class Response extends HuaweiPacket { public static class CommandsList { public int service; public byte[] commands; @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("CommandsList{service="); sb.append(Integer.toHexString(service)); sb.append(", commands=["); for (byte b : commands) { sb.append(Integer.toHexString(b)); if (b != commands[commands.length - 1]) // Elements should be unique sb.append(", "); } sb.append("]}"); return sb.toString(); } } public List commandsLists; public Response(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.isEncrypted = false; } @Override public void parseTlv() throws ParseException { this.commandsLists = new ArrayList<>(); CommandsList commandsList = null; HuaweiTLV containerTLV = this.tlv.getObject(0x81); for (HuaweiTLV.TLV tlv : containerTLV.get()) { if ((int) tlv.getTag() == 0x02) { commandsList = new CommandsList(); commandsList.service = ByteBuffer.wrap(tlv.getValue()).get(); } else if ((int) tlv.getTag() == 0x04) { if (commandsList == null) throw new SupportedCommandsListException("Commandslist is not yet set"); ByteBuffer buffer = ByteBuffer.allocate(tlv.getValue().length); for (int i = 0; i < tlv.getValue().length; i++) { if ((int) tlv.getValue()[i] == 1) buffer.put((byte) (commandsPerService.get(commandsList.service)[i])); } commandsList.commands = new byte[buffer.position()]; ((ByteBuffer) buffer.rewind()).get(commandsList.commands); this.commandsLists.add(commandsList); } else throw new SupportedCommandsListException("Unknown tag encountered"); } if (this.commandsLists.isEmpty()) throw new SupportedCommandsListException("CommandLists is empty"); } } // TODO: LogRequest } public static class DateFormat { public static final byte id = 0x04; public static class Request extends HuaweiPacket { public Request( ParamsProvider paramsProvider, byte dateFormat, byte timeFormat ) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x81, new HuaweiTLV() .put(0x02, dateFormat) .put(0x03, timeFormat) ); this.complete = true; } } public static class OutgoingRequest extends HuaweiPacket { public byte dateFormat; public byte timeFormat; public OutgoingRequest(ParamsProvider paramsProvider) { super(paramsProvider); this.complete = false; } @Override public void parseTlv() throws ParseException { if (this.tlv.contains(0x02)) this.dateFormat = this.tlv.getByte(0x02); if (this.tlv.contains(0x03)) this.timeFormat = this.tlv.getByte(0x03); } } } public static class TimeRequest extends HuaweiPacket { public static final byte id = 0x05; public TimeRequest(ParamsProvider paramsProvider, final Calendar now) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; ByteBuffer timeAndZoneId = ByteBuffer.wrap(HuaweiUtil.getTimeAndZoneId(now)); this.tlv = new HuaweiTLV() .put(0x01, timeAndZoneId.getInt(0)) .put(0x02, timeAndZoneId.getShort(4)); this.complete = true; } public TimeRequest(ParamsProvider paramsProvider) { this(paramsProvider, Calendar.getInstance()); } public static class Response extends HuaweiPacket { public int deviceTime=0; public Response(ParamsProvider paramsProvider) { super(paramsProvider); } @Override public void parseTlv() throws ParseException { if (this.tlv.contains(0x01)) { this.deviceTime = this.tlv.getInteger(0x01); } } } // TODO: implement parsing this request for the log parser support } public static class ProductInfo { public static final byte id = 0x07; public static class Request extends HuaweiPacket { public Request(ParamsProvider paramsProvider, HuaweiDeviceType deviceType) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV(); if (deviceType == HuaweiDeviceType.AW) { int[] tags = {0x01, 0x02, 0x03, 0x07, 0x09, 0x0A, 0x0c, 0x11}; for (int i: tags) { this.tlv.put(i); } } else { int[] tags = {0x01, 0x02, 0x07, 0x09, 0x0A, 0x11, 0x12, 0x16, 0x1A, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23}; for (int i : tags) { this.tlv.put(i); } } // else if (AW Compatible) // this.tlv).put(0x01).put(0x02).put(0x07).put(0x09); // } this.complete = true; } } public static class Response extends HuaweiPacket { // TODO: extend: // public static final int BTVersion = 0x01; // public static final int productType = 0x02; // public static final int phoneNumber = 0x04; // public static final int macAddress = 0x05; // public static final int IMEI = 0x06; // public static final int openSourceVersion = 0x08; // public static final int serialNumber = 0x09; // public static final int eMMCId = 0x0B; // public static final int healthAppSupport = 0x0D; public String hardwareVersion; public String softwareVersion; public String productModel; public Response(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.isEncrypted = false; } @Override public void parseTlv() throws ParseException { if (this.tlv.contains(0x03)) this.hardwareVersion = this.tlv.getString(0x03); this.softwareVersion = this.tlv.getString(0x07); this.productModel = this.tlv.getString(0x0A).trim(); } } // TODO: implement parsing this request for the log parser support } public static class Bond { public static final byte id = 0x0E; public static class Request extends HuaweiPacket { public Request( ParamsProvider paramsProvider, byte[] clientSerial, byte[] key, byte[] iv ) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01) .put(0x03, (byte) 0x00) .put(0x05, clientSerial) .put(0x06, key) .put(0x07, iv); this.isEncrypted = false; this.complete = true; } } public static class OutgoingRequest extends HuaweiPacket { public byte[] clientSerial; public byte[] bondingKey; public byte[] iv; public OutgoingRequest(ParamsProvider paramsProvider) { super(paramsProvider); this.complete = false; } @Override public void parseTlv() throws ParseException { this.clientSerial = this.tlv.getBytes(0x05); this.bondingKey = this.tlv.getBytes(0x06); this.iv = this.tlv.getBytes(0x07); } } } public static class BondParams { public static final byte id = 0x0F; public static class Request extends HuaweiPacket { public Request( ParamsProvider paramsProvider, byte[] clientSerial, byte[] mac ) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01) .put(0x03, clientSerial) .put(0x04, (byte) 0x02) .put(0x05) .put(0x07, mac) .put(0x09); this.isEncrypted = false; this.complete = true; } } public static class Response extends HuaweiPacket { public byte status; public long encryptionCounter; public Response(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.isEncrypted = false; } @Override public void parseTlv() throws ParseException { this.status = this.tlv.getByte(0x01); this.encryptionCounter = this.tlv.getInteger(0x09) & 0xFFFFFFFFL; } } // TODO: implement parsing this request for the log parser support } public static class ActivityType { public static final int id = 0x12; public static class Request extends HuaweiPacket { public Request(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01); this.complete = true; } } public static class Response extends HuaweiPacket { public Response(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; } @Override public void parseTlv() throws ParseException { } } } public static class Auth { public static final byte id = 0x13; public static class Request extends HuaweiPacket { public Request( ParamsProvider paramsProvider, byte[] challenge, byte[] nonce ) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01, challenge) .put(0x02, nonce); if (paramsProvider.getAuthMode() == 0x02) this.tlv.put(0x03, paramsProvider.getAuthAlgo()); this.isEncrypted = false; this.complete = true; } } public static class OutgoingRequest extends HuaweiPacket { public byte[] challenge; public byte[] nonce; public OutgoingRequest(ParamsProvider paramsProvider) { super(paramsProvider); this.complete = false; } @Override public void parseTlv() throws ParseException { this.challenge = this.tlv.getBytes(0x01); this.nonce = this.tlv.getBytes(0x02); } } public static class Response extends HuaweiPacket { public byte[] challengeResponse; public Response(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.isEncrypted = false; } @Override public void parseTlv() throws ParseException { this.challengeResponse = this.tlv.getBytes(0x01); } } // TODO: implement parsing this request for the log parser support } public static class BatteryLevel { public static final byte id = 0x08; public static final byte id_change = 0x27; // Same format, async (receive) only public static class Request extends HuaweiPacket { public Request(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01); this.complete = true; } } public static class Response extends HuaweiPacket { public byte level; public byte[] multi_level; public byte[] status; // TODO: enum public Response(ParamsProvider paramsProvider) { super(paramsProvider); // This differs per watch, so we handle it ourselves in parseTlv this.isEncrypted = false; } @Override public void parseTlv() throws ParseException { this.level = this.tlv.getByte(0x01); this.multi_level = this.tlv.getBytes(0x02, null); this.status = this.tlv.getBytes(0x03, null); } } // TODO: implement parsing this request for the log parser support } public static class ActivateOnLiftRequest extends HuaweiPacket { public static final byte id = 0x09; public ActivateOnLiftRequest(ParamsProvider paramsProvider, boolean activate) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01, activate); this.complete = true; } // TODO: implement parsing this request for the log parser support } public static class DndDeleteRequest extends HuaweiPacket { public static final int id = 0x0B; public DndDeleteRequest(HuaweiPacket.ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x81, new HuaweiTLV() .put(0x02, (byte) 0x01) ); this.complete = true; } // TODO: implement parsing this request for the log parser support } public static class DndAddRequest extends HuaweiPacket { public static final int id = 0x0C; public DndAddRequest( HuaweiPacket.ParamsProvider paramsProvider, boolean dndEnable, byte[] start, byte[] end, int cycle, int dndLiftWristType, boolean allowDndLiftWrist ) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; HuaweiTLV dndPacket = new HuaweiTLV() .put(0x02, (byte) 0x01) .put(0x03, dndEnable) .put(0x04, (byte) 0x00) .put(0x05, start) .put(0x06, end) .put(0x07, (byte) cycle); if (allowDndLiftWrist) { dndPacket.put(0x08, (short) (dndLiftWristType)); } this.tlv = new HuaweiTLV() .put(0x81, dndPacket); this.complete = true; } // TODO: implement parsing this request for the log parser support } public static class FactoryResetRequest extends HuaweiPacket { public static final byte id = 0x0D; public FactoryResetRequest(HuaweiPacket.ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01, (byte) 0x01); this.complete = true; } // TODO: implement parsing this request for the log parser support } public static class PhoneInfo { public static final byte id = 0x10; public static class Request extends HuaweiPacket { public Request(HuaweiPacket.ParamsProvider paramsProvider, byte[] phoneInfo) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV(); for (byte b : phoneInfo) { switch (b) { case 0x0: break; case 0xf: break; case 0x11: this.tlv.put(b, 1400106310); // Force AppVersion to 14.1.6.310 break; case 0x15: this.tlv.put(b); // Force buildOSPlatformVersion to "" break; case 0x10: // Force EmuiBuildVersion to 0x00 case 0x13: // Force buildOsEnable to 0x00 case 0x14: // Force buildOSApiVersion to 0x00 default: this.tlv.put(b, (byte)00); } } this.complete = true; } } public static class Response extends HuaweiPacket { public boolean isAck = false; public byte[] info; public Response(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; } @Override public void parseTlv() throws MissingTagException { if (this.tlv.get().size() == 1 && this.tlv.contains(0x7f) && this.tlv.getInteger(0x7f) == 0x186A0) { this.isAck = true; return; } info = new byte[this.tlv.length()]; int i = 0; for (TLV tlv : this.tlv.get()) { info[i] = tlv.getTag(); i += 1; } } } // TODO: implement parsing this request for the log parser support } public static class DeviceStatus { public static final byte id = 0x16; public static class Request extends HuaweiPacket { public Request(HuaweiPacket.ParamsProvider paramsProvider, boolean askStatus) { // status or notify super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV(); if (askStatus) { this.tlv.put(0x01); } else { this.tlv.put(0x02, (byte)0x00); } this.isEncrypted = false; this.complete = true; } } public static class Response extends HuaweiPacket { public byte status = -1; public Response(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.isEncrypted = false; } @Override public void parseTlv() throws ParseException { // AW70 doesn't seem to have this if (this.tlv.contains(0x01)) this.status = this.tlv.getByte(0x01); } } // TODO: implement parsing this request for the log parser support } public static class NavigateOnRotateRequest extends HuaweiPacket { public static final byte id = 0x1B; public NavigateOnRotateRequest(HuaweiPacket.ParamsProvider paramsProvider, boolean navigate) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01, navigate); this.complete = true; } // TODO: implement parsing this request for the log parser support } public static class WearLocationRequest extends HuaweiPacket { public static final byte id = 0x1A; public WearLocationRequest(HuaweiPacket.ParamsProvider paramsProvider, byte location) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01, location); this.complete = true; } // TODO: implement parsing this request for the log parser support } public static class DndLiftWristType { public static final int id = 0x1D; public static class Request extends HuaweiPacket { public Request(HuaweiPacket.ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01); this.complete = true; } } public static class Response extends HuaweiPacket { public int dndLiftWristType; public Response(ParamsProvider paramsProvider) { super(paramsProvider); } @Override public void parseTlv() throws ParseException { this.dndLiftWristType = (int) this.tlv.getShort(0x01); } } // TODO: implement parsing this request for the log parser support } // TODO: set (earphone) double tap action 0x1f // TODO: get (earphone) double tap action 0x20 public static class HiChain { public static final int id = 0x28; public static class Request { private final int operationCode; private final long requestId; private final byte[] selfAuthId; private final String groupId; private JSONObject version = null; private JSONObject jsonPayload = null; private JSONObject value = null; public Request (int operationCode, long requestId, byte[] selfAuthId, String groupId) { this.operationCode = operationCode; this.requestId = requestId; this.selfAuthId = selfAuthId; this.groupId = groupId; } public class BaseStep extends HuaweiPacket { public BaseStep (HuaweiPacket.ParamsProvider paramsProvider, int messageId) throws SerializeException { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = HiChain.id; this.isSliced = true; this.isEncrypted = false; this.complete = true; version = new JSONObject(); jsonPayload = new JSONObject(); value = new JSONObject(); createJson(messageId); } } public class StepOne extends BaseStep { public StepOne ( HuaweiPacket.ParamsProvider paramsProvider, int messageId, byte[] isoSalt, byte[] seed ) throws SerializeException { super(paramsProvider, messageId); // createJson(1); //messageId); try { jsonPayload .put("isoSalt", StringUtils.bytesToHex(isoSalt)) .put("peerAuthId", StringUtils.bytesToHex(selfAuthId)) .put("operationCode", operationCode) .put("seed", StringUtils.bytesToHex(seed)) .put("peerUserType", 0x00); if (operationCode == 0x02) { jsonPayload .put("pkgName", "com.huawei.devicegroupmanage") .put("serviceType", groupId) .put("keyLength", 0x20); value.put("isDeviceLevel", false); } this.tlv = new HuaweiTLV() .put(0x01, value.toString()) .put(0x02, (byte)operationCode) .put(0x03, ByteBuffer.allocate(8).putLong(requestId).array()); //.put(0x04, 0x00) //.put(0x05, 0x00); } catch (JSONException e) { throw new SerializeException("HiChain Step1 JSON exception", e); } } } public class StepTwo extends BaseStep { public StepTwo ( HuaweiPacket.ParamsProvider paramsProvider, int messageId, byte[] token ) throws SerializeException { super(paramsProvider, messageId); // createJson(2); //messageId); try { jsonPayload .put("peerAuthId", StringUtils.bytesToHex(selfAuthId)) .put("token", StringUtils.bytesToHex(token)); if (operationCode == 0x02) value.put("isDeviceLevel", false); this.tlv = new HuaweiTLV() .put(0x01, value.toString()) .put(0x02, (byte)operationCode) .put(0x03, ByteBuffer.allocate(8).putLong(requestId).array()); } catch (JSONException e) { throw new SerializeException("HiChain Step 2 JSON exception", e); } } } public class StepThree extends BaseStep { public StepThree ( HuaweiPacket.ParamsProvider paramsProvider, int messageId, byte[] nonce, byte[] encData ) throws SerializeException { super(paramsProvider, messageId); // createJson(3); try { jsonPayload .put("nonce", StringUtils.bytesToHex(nonce)) .put("encData", StringUtils.bytesToHex(encData)); this.tlv = new HuaweiTLV() .put(0x01, value.toString()) .put(0x02, (byte)operationCode) .put(0x03, ByteBuffer.allocate(8).putLong(requestId).array()); } catch (JSONException e) { throw new SerializeException("HiChain Step 3 JSON exception", e); } } } public class StepFour extends BaseStep { public StepFour ( HuaweiPacket.ParamsProvider paramsProvider, int messageId, byte[] nonce, byte[] encResult ) throws SerializeException { super(paramsProvider, messageId); // if (operationCode == 0x01) { // createJson(4); //messageId); // } else { // createJson(3); // } try { jsonPayload .put("nonce", StringUtils.bytesToHex(nonce)) //generateRandom .put("encResult", StringUtils.bytesToHex(encResult)) .put("operationCode", operationCode); this.tlv = new HuaweiTLV() .put(0x01, value.toString()) .put(0x02, (byte)operationCode) .put(0x03, ByteBuffer.allocate(8).putLong(requestId).array()); } catch (JSONException e) { throw new SerializeException("HiChain Step 4 JSON exception", e); } } } private void createJson(int messageId) throws HuaweiPacket.SerializeException { if (operationCode == 0x02) { messageId |= 0x10; } try { version .put("minVersion", "1.0.0") .put("currentVersion", "2.0.16"); jsonPayload .put("version", version); value .put("authForm", 0x00) .put("payload", jsonPayload) .put("groupAndModuleVersion", "2.0.1") .put("message", messageId); if (operationCode == 0x01) { value .put("requestId", Long.toString(requestId)) .put("groupId", groupId) .put("groupName", "health_group_name") .put("groupOp", 2) .put("groupType", 256) .put("peerDeviceId", new String(selfAuthId, StandardCharsets.UTF_8)) .put("connDeviceId", new String(selfAuthId, StandardCharsets.UTF_8)) .put("appId", "com.huawei.health") .put("ownerName", ""); } } catch (JSONException e) { throw new HuaweiPacket.SerializeException("Create json Exception", e); } } } public static class Response extends HuaweiPacket { // TODO: get rid of GB import... // TODO: add operation code public static class Step1Data { public byte[] isoSalt; public byte[] peerAuthId; public int peerUserType; public byte[] token; public Step1Data(JSONObject payload) throws JSONException { this.isoSalt = GB.hexStringToByteArray(payload.getString("isoSalt")); this.peerAuthId = GB.hexStringToByteArray(payload.getString("peerAuthId")); this.peerUserType = payload.getInt("peerUserType"); this.token = GB.hexStringToByteArray(payload.getString("token")); } @Override public String toString() { return "Step1Data{" + "isoSalt=" + StringUtils.bytesToHex(isoSalt) + ", peerAuthId=" + StringUtils.bytesToHex(peerAuthId) + ", peerUserType=" + peerUserType + ", token=" + StringUtils.bytesToHex(token) + '}'; } } public static class Step2Data { public byte[] returnCodeMac; public Step2Data(JSONObject payload) throws JSONException { this.returnCodeMac = GB.hexStringToByteArray(payload.getString("returnCodeMac")); } @Override public String toString() { return "Step2Data{" + "returnCodeMac=" + StringUtils.bytesToHex(returnCodeMac) + '}'; } } public static class Step3Data { public byte[] nonce; public byte[] encAuthToken; public Step3Data(JSONObject payload) throws JSONException { this.nonce = GB.hexStringToByteArray(payload.getString("nonce")); this.encAuthToken = GB.hexStringToByteArray(payload.getString("encAuthToken")); } @Override public String toString() { return "Step3Data{" + "nonce=" + StringUtils.bytesToHex(nonce) + ", encAuthToken=" + StringUtils.bytesToHex(encAuthToken) + '}'; } } public static class Step4Data { public String data; public Step4Data(HuaweiTLV tlv) throws ParseException { if (tlv.contains(0x01)) this.data = tlv.getString(0x01); } @Override public String toString() { return "Step4Data{" + "data='" + data + '\'' + '}'; } } // TODO: enum? // 0 is json? // 2 is raw string? public byte type; public JSONObject value; public JSONObject jsonPayload; public byte step; // public int operationCode; // TODO public Step1Data step1Data; public Step2Data step2Data; public Step3Data step3Data; public Step4Data step4Data; public int errorCode = 0; public Response(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = HiChain.id; this.isEncrypted = false; } @Override public void parseTlv() throws ParseException { this.type = this.tlv.getByte(0x04); if (this.type == 0x00) { try { this.value = new JSONObject(this.tlv.getString(0x01)); this.jsonPayload = value.getJSONObject("payload"); // Ugly, but should work if (jsonPayload.has("isoSalt")) { this.step = 0x01; this.step1Data = new Step1Data(jsonPayload); } else if (jsonPayload.has("returnCodeMac")) { this.step = 0x02; this.step2Data = new Step2Data(jsonPayload); } else if (jsonPayload.has("encAuthToken")) { this.step = 0x03; this.step3Data = new Step3Data(jsonPayload); } if (jsonPayload.has("errorCode")) { this.errorCode = jsonPayload.getInt("errorCode"); } } catch (JSONException e) { throw new JsonException("", e); } } else { this.step = 0x04; this.step4Data = new Step4Data(this.tlv); } } } public static class OutgoingRequest extends HuaweiPacket { public static class Step1Data { public byte[] isoSalt; public byte[] peerAuthId; public byte[] seed; public int peerUserType; String serviceType; // Optional public Step1Data(JSONObject payload) throws JSONException { this.isoSalt = GB.hexStringToByteArray(payload.getString("isoSalt")); this.peerAuthId = GB.hexStringToByteArray(payload.getString("peerAuthId")); this.seed = GB.hexStringToByteArray(payload.getString("seed")); this.peerUserType = payload.getInt("peerUserType"); if (payload.has("serviceType")) this.serviceType = payload.getString("serviceType"); } @Override public String toString() { return "Step1Data{" + "isoSalt=" + StringUtils.bytesToHex(isoSalt) + ", peerAuthId=" + StringUtils.bytesToHex(peerAuthId) + ", seed=" + StringUtils.bytesToHex(seed) + ", peerUserType=" + peerUserType + ", serviceType='" + serviceType + '\'' + '}'; } } public static class Step2Data { public byte[] peerAuthId; public byte[] token; public boolean isDeviceLevel = false; // Optional public Step2Data(JSONObject payload) throws JSONException { this.peerAuthId = GB.hexStringToByteArray(payload.getString("peerAuthId")); this.token = GB.hexStringToByteArray(payload.getString("token")); if (payload.has("isDeviceLevel")) this.isDeviceLevel = payload.getBoolean("isDeviceLevel"); } @Override public String toString() { return "Step2Data{" + "peerAuthId=" + StringUtils.bytesToHex(peerAuthId) + ", token=" + StringUtils.bytesToHex(token) + ", isDeviceLevel=" + isDeviceLevel + '}'; } } public static class Step3Data { public byte[] nonce; public byte[] encData; public Step3Data(JSONObject payload) throws JSONException { this.nonce = GB.hexStringToByteArray(payload.getString("nonce")); this.encData = GB.hexStringToByteArray(payload.getString("encData")); } @Override public String toString() { return "Step3Data{" + "nonce=" + StringUtils.bytesToHex(nonce) + ", encData=" + StringUtils.bytesToHex(encData) + '}'; } } public static class Step4Data { public byte[] nonce; public byte[] encResult; public Step4Data(JSONObject payload) throws JSONException { this.nonce = GB.hexStringToByteArray(payload.getString("nonce")); this.encResult = GB.hexStringToByteArray(payload.getString("encResult")); } @Override public String toString() { return "Step4Data{" + "nonce=" + StringUtils.bytesToHex(nonce) + ", encResult=" + StringUtils.bytesToHex(encResult) + '}'; } } public int step; public long requestId; public byte[] selfAuthId; public String groupId; public JSONObject jsonPayload = null; public JSONObject value = null; public Step1Data step1Data; public Step2Data step2Data; public Step3Data step3Data; public Step4Data step4Data; public OutgoingRequest(ParamsProvider paramsProvider) { super(paramsProvider); this.complete = false; } @Override public void parseTlv() throws ParseException { try { value = new JSONObject(this.tlv.getString(0x01)); jsonPayload = value.getJSONObject("payload"); if (jsonPayload.has("isoSalt")) { this.step = 1; this.step1Data = new Step1Data(jsonPayload); } else if (jsonPayload.has("token")) { this.step = 2; this.step2Data = new Step2Data(jsonPayload); } else if (jsonPayload.has("encData")) { this.step = 3; this.step3Data = new Step3Data(jsonPayload); } else if (jsonPayload.has("encResult")) { this.step = 4; this.step4Data = new Step4Data(jsonPayload); } } catch (JSONException e) { throw new JsonException("Cannot parse JSON", e); } if (this.tlv.contains(0x03)) this.requestId = ByteBuffer.wrap(this.tlv.getBytes(0x03)).getLong(); } } } public static class PinCode { public static final int id = 0x2C; public static class Request extends HuaweiPacket { public Request(HuaweiPacket.ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01); this.complete = true; this.isEncrypted = false; } } public static class Response extends HuaweiPacket { public byte[] pinCode; public Response(HuaweiPacket.ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.isEncrypted = false; } @Override public void parseTlv() throws ParseException { byte[] message = this.tlv.getBytes(0x01); byte[] iv = this.tlv.getBytes(0x02); HuaweiCrypto huaweiCrypto = new HuaweiCrypto(paramsProvider.getAuthVersion(), paramsProvider.getAuthAlgo(), paramsProvider.getDeviceSupportType(), paramsProvider.getAuthMode()); try { pinCode = huaweiCrypto.decryptPinCode(paramsProvider.getEncryptMethod(), message, iv); } catch (HuaweiCrypto.CryptoException e) { throw new CryptoException("Could not decrypt pinCode", e); } } } // TODO: implement parsing this request for the log parser support } public static class AcceptAgreement { public static final int id = 0x30; public static class Request extends HuaweiPacket { public Request(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; int timestamp = (int) (System.currentTimeMillis() / 1000); HuaweiTLV software = new HuaweiTLV() .put(0x03, "software_update_service_statement") .put(0x04, 0x01) .put(0x05, "20230508-20230508-0-0") .put(0x06, timestamp); HuaweiTLV device_information = new HuaweiTLV() .put(0x03, "device_information_management") .put(0x04,0x01) .put(0x05, "20230508-20230508-0-0") .put(0x06,timestamp); HuaweiTLV user_license = new HuaweiTLV() .put(0x03, "user_license_agreement") .put(0x04,0x01) .put(0x05, "20230508-20230508-0-0") .put(0x06,timestamp); HuaweiTLV tlvList = new HuaweiTLV() .put(0x82, software) .put(0x82,device_information) .put(0x82,user_license); this.tlv = new HuaweiTLV() .put(0x81, tlvList); } } } public static class SettingRelated { public static final int id = 0x31; public static class Request extends HuaweiPacket { public Request(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01) .put(0x02) .put(0x03) .put(0x04) .put(0x05) .put(0x06); this.complete = true; } } public static class Response extends HuaweiPacket { public boolean truSleepNewSync = false; public boolean gpsNewSync = false; public Response(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; } @Override public void parseTlv() throws ParseException { // Works with bitmaps // Tag 1 -> LegalStuff if (this.tlv.contains(0x02)) { // Tag 2 -> File support byte value = this.tlv.getByte(0x02); truSleepNewSync = (value & 2) != 0; gpsNewSync = (value & 8) != 0; } // Tag 3 -> SmartWatchVersion // Tag 4 to 6 are HMS related } } } public static class TimeZoneIdRequest extends HuaweiPacket { public static final byte id = 0x32; public TimeZoneIdRequest( ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; ByteBuffer timeAndZoneId = ByteBuffer.wrap(HuaweiUtil.getTimeAndZoneId(Calendar.getInstance())); this.tlv = new HuaweiTLV() .put(0x01, timeAndZoneId.getInt()) .put(0x02, timeAndZoneId.getShort()); byte[] zoneId = new byte[timeAndZoneId.remaining()]; timeAndZoneId.get(zoneId, 0, timeAndZoneId.remaining()); this.tlv.put(0x03, zoneId); this.complete = true; } // TODO: implement parsing this request for the log parser support } public static class SecurityNegotiation { public static final int id = 0x33; public static class Request extends HuaweiPacket { public Request ( HuaweiPacket.ParamsProvider paramsProvider, byte authMode, byte[] deviceUUID, String phoneModel) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01, authMode); if (authMode == 0x02 || authMode == 0x04) this.tlv.put(0x02, (byte)0x01); //force to not reconnected else 0x02 this.tlv.put(0x05, deviceUUID) .put(0x03, (byte)0x01) .put(0x04, (byte)0x00); if (authMode == 0x04) this.tlv.put(0x06) .put(0x07, phoneModel); if (paramsProvider.getEncryptMethod() == 1 ) this.tlv.put(0xd, (byte)0x1); this.complete = true; this.isEncrypted = false; } // TODO: implement parsing this request for the log parser support } public static class Response extends HuaweiPacket { public int authType; public Response(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; } @Override public void parseTlv() throws ParseException { this.authType = -0x1; int pw = -0x1; if (this.tlv.contains(0x01)) { if (this.tlv.getByte(0x01) == 0x01) this.authType = 0x0186A0; if (this.tlv.getByte(0x01) == 0x04) pw = 4; } if (this.tlv.contains(0x02)) { this.authType = (int)this.tlv.getByte(0x02); if (pw != -0x1) this.authType ^= pw; } if (this.tlv.contains(0x7F)) this.authType = (int)this.tlv.getByte(0x7F); } } } public static class ConnectStatusRequest extends HuaweiPacket { public static final int id = 0x35; public ConnectStatusRequest (HuaweiPacket.ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01,(byte)0x01); this.complete = true; } // TODO: implement parsing this request for the log parser support } public static class ExpandCapability { public static final int id = 0x37; public static class Request extends HuaweiPacket { public Request(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01); this.complete = true; } } public static class Response extends HuaweiPacket { public byte[] expandCapabilities; public Response(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; } @Override public void parseTlv() throws ParseException { this.expandCapabilities = this.tlv.getBytes(0x01); } } } public static class WearStatus { public static final int id = 0x3D; public static class Request extends HuaweiPacket { public Request(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01); this.complete = true; } } public static class Response extends HuaweiPacket { public Response(ParamsProvider paramsProvider) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; } @Override public void parseTlv() throws ParseException { } } } public static class SetUpDeviceStatusRequest extends HuaweiPacket { public static final int id = 0x3E; public SetUpDeviceStatusRequest(ParamsProvider paramsProvider, int relationShip, String deviceName) { super(paramsProvider); this.serviceId = DeviceConfig.id; this.commandId = id; this.tlv = new HuaweiTLV() .put(0x01, (byte) relationShip) .put(0x02, deviceName) .put(0x03, (byte)0x00); this.complete = true; } } // TODO: wear location enum? public static class Date { // TODO: enum? public static final int yearFirst = 0x01; public static final int monthFirst = 0x02; public static final int dayFirst = 0x03; } public static class Time { // TODO: enum? public static final int hours12 = 0x01; public static final int hours24 = 0x02; } }