Gadgetbridge/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java

814 lines
33 KiB
Java

/* Copyright (C) 2024 Damien Gaignon, 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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
import static nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants.HUAWEI_MAGIC;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Alarms;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.AccountRelated;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Calls;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.GpsAndTime;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Watchface;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.DeviceConfig;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FindPhone;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Notifications;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FileUpload;
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
public class HuaweiPacket {
private static final Logger LOG = LoggerFactory.getLogger(HuaweiPacket.class);
public static class ParamsProvider {
protected byte authVersion;
protected byte deviceSupportType;
protected byte[] secretKey;
protected int slicesize = 0xf4;
protected boolean transactionsCrypted = true;
protected int mtu = 65535;
protected long encryptionCounter = 0;
protected byte[] pinCode = null;
protected byte interval;
protected byte authAlgo;
protected byte encryptMethod;
protected byte[] firstKey;
public void setAuthVersion(byte authVersion) {
this.authVersion = authVersion;
}
public byte getAuthVersion() {
return this.authVersion;
}
public void setDeviceSupportType(byte deviceSupportType) {
this.deviceSupportType = deviceSupportType;
}
public byte getDeviceSupportType(){
return this.deviceSupportType;
}
public void setSecretKey(byte[] secretKey) {
this.secretKey = secretKey;
}
public byte[] getSecretKey() {
return this.secretKey;
}
public void setTransactionsCrypted(boolean transactionsCrypted) {
this.transactionsCrypted = transactionsCrypted;
}
public boolean areTransactionsCrypted() {
return this.transactionsCrypted;
}
public void setMtu(int mtu) {
this.mtu = mtu;
}
public int getMtu() {
return this.mtu;
}
public void setSliceSize(int sliceSize) {
this.slicesize = sliceSize;
}
public int getSliceSize() {
return this.slicesize;
}
public void setPinCode(byte[] pinCode) {
this.pinCode = pinCode;
}
public byte[] getPinCode() {
return this.pinCode;
}
public void setInterval(byte interval) {
this.interval = interval;
}
public byte getInterval() {
return this.interval;
}
public byte[] getIv() {
byte[] iv = null;
if (this.deviceSupportType == 0x04) {
iv = HuaweiCrypto.generateNonce();
} else {
ByteBuffer ivCounter = HuaweiCrypto.initializationVector(this.encryptionCounter);
iv = ivCounter.array();
this.encryptionCounter = (long)ivCounter.getInt(12) & 0xFFFFFFFFL;
}
return iv;
}
public void setEncryptionCounter(long counter) {
this.encryptionCounter = counter;
}
public void setAuthAlgo(byte authAlgo) {
this.authAlgo = authAlgo;
}
public byte getAuthAlgo () {
return this.authAlgo;
}
public void setEncryptMethod(byte encryptMethod) {
this.encryptMethod = encryptMethod;
}
public byte getEncryptMethod () {
return this.encryptMethod;
}
public void setFirstKey(byte[] firstKey) {
this.firstKey = firstKey;
}
public byte[] getFirstKey() {
return firstKey;
}
}
public static abstract class ParseException extends Exception {
ParseException(String message) {
super(message);
}
ParseException(String message, Exception e) {
super(message, e);
}
}
public static class LengthMismatchException extends ParseException {
public LengthMismatchException(String message) {
super(message);
}
}
public static class MagicMismatchException extends ParseException {
MagicMismatchException(String message) {
super(message);
}
}
public static class ChecksumIncorrectException extends ParseException {
ChecksumIncorrectException(String message) {
super(message);
}
}
public static class MissingTagException extends ParseException {
public MissingTagException(int tag) {
super("Missing tag: " + Integer.toHexString(tag));
}
}
public static class CryptoException extends ParseException {
public CryptoException(String message, Exception e) {
super(message, e);
}
}
public static class JsonException extends ParseException {
public JsonException(String message, Exception e) {
super(message, e);
}
}
public static class SupportedCommandsListException extends ParseException {
public SupportedCommandsListException(String message) {
super(message);
}
}
public static class SerializeException extends Exception {
public SerializeException(String message, Exception e) {
super(message, e);
}
}
protected static final int PACKET_MINIMAL_SIZE = 6;
protected ParamsProvider paramsProvider;
public byte serviceId = 0;
public byte commandId = 0;
protected HuaweiTLV tlv = null;
private byte[] partialPacket = null;
private byte[] payload = null;
public boolean complete = false;
// Encryption is enabled by default, packets which don't use it must disable it
protected boolean isEncrypted = true;
protected boolean isSliced = false;
public HuaweiPacket(ParamsProvider paramsProvider) {
this.paramsProvider = paramsProvider;
}
public boolean attemptDecrypt() throws ParseException {
if (paramsProvider == null || paramsProvider.getSecretKey() == null)
return false;
if (this.tlv == null)
return false;
if (this.tlv.contains(0x7C) && this.tlv.getBoolean(0x7C)) {
try {
this.tlv.decrypt(paramsProvider);
return true;
} catch (HuaweiCrypto.CryptoException e) {
throw new CryptoException("Decrypt exception", e);
}
} else {
if (this.isEncrypted && paramsProvider.areTransactionsCrypted()) {
// TODO: potentially a log message? We expect it to be encrypted, but it isn't.
}
}
return false;
}
/*
* This function is to convert the Packet into the proper subclass
*/
protected HuaweiPacket fromPacket(HuaweiPacket packet) throws ParseException {
this.paramsProvider = packet.paramsProvider;
this.serviceId = packet.serviceId;
this.commandId = packet.commandId;
this.tlv = packet.tlv;
this.partialPacket = packet.partialPacket;
this.payload = packet.payload;
this.complete = packet.complete;
if (packet.isEncrypted)
this.isEncrypted = true;
else
this.isEncrypted = this.attemptDecrypt();
return this;
}
/*
* This function is to set up the subclass for easy usage
* Needs to be called separately so the exceptions can be used more easily
*/
public void parseTlv() throws ParseException {}
private void parseData(byte[] data) throws ParseException {
if (partialPacket != null) {
int newCapacity = partialPacket.length + data.length;
data = ByteBuffer.allocate(newCapacity)
.put(partialPacket)
.put(data)
.array();
}
ByteBuffer buffer = ByteBuffer.wrap(data);
if (buffer.capacity() < PACKET_MINIMAL_SIZE) {
throw new LengthMismatchException("Packet length mismatch : "
+ buffer.capacity()
+ " != 6");
}
byte magic = buffer.get();
short expectedSize = buffer.getShort();
int isSliced = buffer.get();
if (isSliced == 1 || isSliced == 2 || isSliced == 3) {
buffer.get(); // Throw away slice flag
}
byte[] newPayload = new byte[buffer.remaining() - 2];
buffer.get(newPayload, 0, buffer.remaining() - 2);
short expectedChecksum = buffer.getShort();
buffer.rewind();
if (magic != HUAWEI_MAGIC) {
throw new MagicMismatchException("Magic mismatch : "
+ Integer.toHexString(magic)
+ " != 0x5A");
}
int newPayloadLen = newPayload.length + 1;
if (isSliced == 1 || isSliced == 2 || isSliced == 3) {
newPayloadLen = newPayload.length + 2;
}
if (expectedSize != (short) newPayloadLen) {
if (expectedSize > (short) newPayloadLen) {
// Older band and BT version do not handle message with more than 256 bits.
this.partialPacket = data;
return;
} else {
throw new LengthMismatchException("Expected length mismatch : "
+ expectedSize
+ " < "
+ (short) newPayloadLen);
}
}
this.partialPacket = null;
byte[] dataNoCRC = new byte[buffer.capacity() - 2];
buffer.get(dataNoCRC, 0, buffer.capacity() - 2);
short actualChecksum = (short) CheckSums.getCRC16(dataNoCRC, 0x0000);
if (actualChecksum != expectedChecksum) {
throw new ChecksumIncorrectException("Checksum mismatch : "
+ String.valueOf(actualChecksum)
+ " != "
+ String.valueOf(expectedChecksum));
}
if (isSliced == 1 || isSliced == 2 || isSliced == 3) {
if (payload != null) {
int newCapacity = payload.length + newPayload.length;
newPayload = ByteBuffer.allocate(newCapacity)
.put(payload)
.put(newPayload)
.array();
}
if (isSliced != 3) {
// Sliced packet isn't complete yet
this.payload = newPayload;
return;
}
}
this.serviceId = newPayload[0];
this.commandId = newPayload[1];
this.complete = true;
if (
(serviceId == 0x0a && commandId == 0x05) ||
(serviceId == 0x28 && commandId == 0x06)
) {
// TODO: this doesn't seem to be TLV
return;
}
this.tlv = new HuaweiTLV();
this.tlv.parse(newPayload, 2, newPayload.length - 2);
}
public HuaweiPacket parse(byte[] data) throws ParseException {
this.isEncrypted = false; // Will be changed if decrypt has been performed
parseData(data);
if (!this.complete)
return this;
switch (this.serviceId) {
case DeviceConfig.id:
switch (this.commandId) {
case DeviceConfig.LinkParams.id:
return new DeviceConfig.LinkParams.Response(paramsProvider).fromPacket(this);
case DeviceConfig.SupportedServices.id:
return new DeviceConfig.SupportedServices.Response(paramsProvider).fromPacket(this);
case DeviceConfig.SupportedCommands.id:
return new DeviceConfig.SupportedCommands.Response(paramsProvider).fromPacket(this);
case DeviceConfig.ProductInfo.id:
return new DeviceConfig.ProductInfo.Response(paramsProvider).fromPacket(this);
case DeviceConfig.BondParams.id:
return new DeviceConfig.BondParams.Response(paramsProvider).fromPacket(this);
case DeviceConfig.Auth.id:
return new DeviceConfig.Auth.Response(paramsProvider).fromPacket(this);
case DeviceConfig.BatteryLevel.id:
return new DeviceConfig.BatteryLevel.Response(paramsProvider).fromPacket(this);
case DeviceConfig.DeviceStatus.id:
return new DeviceConfig.DeviceStatus.Response(paramsProvider).fromPacket(this);
case DeviceConfig.DndLiftWristType.id:
return new DeviceConfig.DndLiftWristType.Response(paramsProvider).fromPacket(this);
case DeviceConfig.HiChain.id:
return new DeviceConfig.HiChain.Response(paramsProvider).fromPacket(this);
case DeviceConfig.PinCode.id:
return new DeviceConfig.PinCode.Response(paramsProvider).fromPacket(this);
case DeviceConfig.ExpandCapability.id:
return new DeviceConfig.ExpandCapability.Response(paramsProvider).fromPacket(this);
case DeviceConfig.ActivityType.id:
return new DeviceConfig.ActivityType.Response(paramsProvider).fromPacket(this);
case DeviceConfig.SettingRelated.id:
return new DeviceConfig.SettingRelated.Response(paramsProvider).fromPacket(this);
case DeviceConfig.SecurityNegotiation.id:
return new DeviceConfig.SecurityNegotiation.Response(paramsProvider).fromPacket(this);
case DeviceConfig.WearStatus.id:
return new DeviceConfig.WearStatus.Response(paramsProvider).fromPacket(this);
default:
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
return this;
}
case Notifications.id:
switch (this.commandId) {
case Notifications.NotificationConstraints.id:
return new Notifications.NotificationConstraints.Response(paramsProvider).fromPacket(this);
case Notifications.NotificationCapabilities.id:
return new Notifications.NotificationCapabilities.Response(paramsProvider).fromPacket(this);
default:
return this;
}
case Calls.id:
if (this.commandId == Calls.AnswerCallResponse.id)
return new Calls.AnswerCallResponse(paramsProvider).fromPacket(this);
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
return this;
case FitnessData.id:
switch (this.commandId) {
case FitnessData.FitnessTotals.id:
return new FitnessData.FitnessTotals.Response(paramsProvider).fromPacket(this);
case FitnessData.MessageCount.stepId:
return new FitnessData.MessageCount.Response(paramsProvider).fromPacket(this);
case FitnessData.MessageData.stepId:
return new FitnessData.MessageData.StepResponse(paramsProvider).fromPacket(this);
case FitnessData.MessageCount.sleepId:
return new FitnessData.MessageCount.Response(paramsProvider).fromPacket(this);
case FitnessData.MessageData.sleepId:
return new FitnessData.MessageData.SleepResponse(paramsProvider).fromPacket(this);
default:
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
return this;
}
case Alarms.id:
switch (this.commandId) {
case Alarms.EventAlarmsList.id:
return new Alarms.EventAlarmsList.Response(paramsProvider).fromPacket(this);
case Alarms.SmartAlarmList.id:
return new Alarms.SmartAlarmList.Response(paramsProvider).fromPacket(this);
default:
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
return this;
}
case FindPhone.id:
if (this.commandId == FindPhone.Response.id)
return new FindPhone.Response(paramsProvider).fromPacket(this);
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
return this;
case Weather.id:
switch (this.commandId) {
case Weather.WeatherSupport.id:
return new Weather.WeatherSupport.Response(paramsProvider).fromPacket(this);
case Weather.WeatherExtendedSupport.id:
return new Weather.WeatherExtendedSupport.Response(paramsProvider).fromPacket(this);
case Weather.WeatherStart.id:
return new Weather.WeatherStart.Response(paramsProvider).fromPacket(this);
case Weather.WeatherSunMoonSupport.id:
return new Weather.WeatherSunMoonSupport.Response(paramsProvider).fromPacket(this);
default:
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
return this;
}
case Workout.id:
switch (this.commandId) {
case Workout.WorkoutCount.id:
return new Workout.WorkoutCount.Response(paramsProvider).fromPacket(this);
case Workout.WorkoutTotals.id:
return new Workout.WorkoutTotals.Response(paramsProvider).fromPacket(this);
case Workout.WorkoutData.id:
return new Workout.WorkoutData.Response(paramsProvider).fromPacket(this);
case Workout.WorkoutPace.id:
return new Workout.WorkoutPace.Response(paramsProvider).fromPacket(this);
default:
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
return this;
}
case GpsAndTime.id:
switch (this.commandId) {
case GpsAndTime.GpsParameters.id:
return new GpsAndTime.GpsParameters.Response(paramsProvider).fromPacket(this);
case GpsAndTime.GpsStatus.id:
return new GpsAndTime.GpsStatus.Response(paramsProvider).fromPacket(this);
case GpsAndTime.GpsData.id:
return new GpsAndTime.GpsData.Response(paramsProvider).fromPacket(this);
default:
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
return this;
}
case MusicControl.id:
switch (this.commandId) {
case MusicControl.MusicStatusResponse.id:
return new MusicControl.MusicStatusResponse(paramsProvider).fromPacket(this);
case MusicControl.MusicInfo.id:
return new MusicControl.MusicInfo.Response(paramsProvider).fromPacket(this);
case MusicControl.Control.id:
return new MusicControl.Control.Response(paramsProvider).fromPacket(this);
default:
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
return this;
}
case AccountRelated.id:
switch(this.commandId) {
case AccountRelated.SendAccountToDevice.id:
return new AccountRelated.SendAccountToDevice.Response(paramsProvider).fromPacket(this);
case AccountRelated.SendExtendedAccountToDevice.id:
return new AccountRelated.SendExtendedAccountToDevice.Response(paramsProvider).fromPacket(this);
default:
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
return this;
}
case FileUpload.id:
switch(this.commandId) {
case FileUpload.FileNextChunkParams.id:
return new FileUpload.FileNextChunkParams(paramsProvider).fromPacket(this);
case FileUpload.FileUploadConsultAck.id:
return new FileUpload.FileUploadConsultAck.Response(paramsProvider).fromPacket(this);
default:
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
return this;
}
case Watchface.id:
switch (this.commandId) {
case Watchface.WatchfaceParams.id:
return new Watchface.WatchfaceParams.Response(paramsProvider).fromPacket(this);
case Watchface.DeviceWatchInfo.id:
return new Watchface.DeviceWatchInfo.Response(paramsProvider).fromPacket(this);
case Watchface.WatchfaceNameInfo.id:
return new Watchface.WatchfaceNameInfo.Response(paramsProvider).fromPacket(this);
default:
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
return this;
}
default:
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
return this;
}
}
public HuaweiPacket parseOutgoing(byte[] data) throws ParseException {
this.isEncrypted = false; // Will be changed if decrypt has been performed
parseData(data);
if (!this.complete)
return this;
// TODO: complete
switch (this.serviceId) {
case DeviceConfig.id:
switch (this.commandId) {
case DeviceConfig.SupportedServices.id:
return new DeviceConfig.SupportedServices.OutgoingRequest(paramsProvider).fromPacket(this);
case DeviceConfig.DateFormat.id:
return new DeviceConfig.DateFormat.OutgoingRequest(paramsProvider).fromPacket(this);
case DeviceConfig.Bond.id:
return new DeviceConfig.Bond.OutgoingRequest(paramsProvider).fromPacket(this);
case DeviceConfig.HiChain.id:
return new DeviceConfig.HiChain.OutgoingRequest(paramsProvider).fromPacket(this);
default:
return this;
}
case Weather.id:
switch (this.commandId) {
case Weather.WeatherForecastData.id:
return new Weather.WeatherForecastData.OutgoingRequest(paramsProvider).fromPacket(this);
default:
return this;
}
default:
return this;
}
}
private List<byte[]> serializeSliced(byte[] serializedTLV) {
List<byte[]> retv = new ArrayList<>();
int headerLength = 5; // Magic + (short)(bodyLength + 1) + 0x00 + extra slice info
int bodyHeaderLength = 2; // sID + cID
int footerLength = 2; //CRC16
int maxBodySize = paramsProvider.getSliceSize() - headerLength - footerLength;
int packetCount = (int) Math.ceil(((double) serializedTLV.length + (double) bodyHeaderLength) / (double) maxBodySize);
if (packetCount == 1)
return serializeUnsliced(serializedTLV);
ByteBuffer buffer = ByteBuffer.wrap(serializedTLV);
byte slice = 0x01;
byte flag = 0x00;
for (int i = 0; i < packetCount; i++) {
short packetSize = (short) Math.min(paramsProvider.getSliceSize(), buffer.remaining() + headerLength + footerLength);
ByteBuffer packet = ByteBuffer.allocate(packetSize);
short contentSize = (short) (packetSize - headerLength - footerLength);
int start = packet.position();
packet.put((byte) 0x5a); // Magic byte
packet.putShort((short) (packetSize - headerLength)); // Length
if (i == packetCount - 1)
slice = 0x03;
packet.put(slice); // Slice
packet.put(flag); // Flag
flag += 1;
if (slice == 0x01) {
packet.put(this.serviceId); // Service ID
packet.put(this.commandId); // Command ID
slice = 0x02;
contentSize -= 2; // To prevent taking too much data
}
byte[] packetContent = new byte[contentSize];
buffer.get(packetContent);
packet.put(packetContent); // Packet data
int length = packet.position() - start;
if (length != packetSize - footerLength) {
// TODO: exception?
LOG.error(String.format(GBApplication.getLanguage(), "Packet lengths don't match! %d != %d", length, packetSize + headerLength));
}
byte[] complete = new byte[length];
packet.position(start);
packet.get(complete, 0, length);
int crc16 = CheckSums.getCRC16(complete, 0x0000);
packet.putShort((short) crc16); // CRC16
retv.add(packet.array());
}
return retv;
}
private List<byte[]> serializeUnsliced(byte[] serializedTLV) {
List<byte[]> retv = new ArrayList<>();
int headerLength = 4; // Magic + (short)(bodyLength + 1) + 0x00
int bodyHeaderLength = 2; // sID + cID
int footerLength = 2; //CRC16
int bodyLength = bodyHeaderLength + serializedTLV.length;
ByteBuffer buffer = ByteBuffer.allocate(headerLength + bodyLength);
buffer.put((byte) 0x5A);
buffer.putShort((short)(bodyLength + 1));
buffer.put((byte) 0x00);
buffer.put(this.serviceId);
buffer.put(this.commandId);
buffer.put(serializedTLV);
int crc16 = CheckSums.getCRC16(buffer.array(), 0x0000);
ByteBuffer finalBuffer = ByteBuffer.allocate(buffer.capacity() + footerLength);
finalBuffer.put(buffer.array());
finalBuffer.putShort((short)crc16);
retv.add(finalBuffer.array());
return retv;
}
public List<byte[]> serializeFileChunk(byte[] fileChunk, int uploadPosition, short unitSize) {
List<byte[]> retv = new ArrayList<>();
int headerLength = 5; // Magic + (short)(bodyLength + 1) + 0x00
int sliceHeaderLenght =7;
int footerLength = 2; //CRC16
int packetCount = (int) Math.ceil(((double) fileChunk.length ) / (double) unitSize);
ByteBuffer buffer = ByteBuffer.wrap(fileChunk);
byte fileType = 0x01; //TODO: 1 - watchface, 2 - music
int sliceStart = uploadPosition;
for (int i = 0; i < packetCount; i++) {
short contentSize = (short) Math.min(unitSize, buffer.remaining());
short packetSize = (short)(contentSize + headerLength + sliceHeaderLenght + footerLength);
ByteBuffer packet = ByteBuffer.allocate(packetSize);
int start = packet.position();
packet.put((byte) 0x5a); // Magic byte
packet.putShort((short) (packetSize - headerLength)); // Length
packet.put((byte) 0x00);
packet.put(this.serviceId);
packet.put(this.commandId);
packet.put(fileType); // Slice
packet.put((byte)i); // Flag
packet.putInt(sliceStart);
byte[] packetContent = new byte[contentSize];
buffer.get(packetContent);
packet.put(packetContent); // Packet databyte[] packetContent = new byte[contentSize];
int length = packet.position() - start;
if (length != packetSize - footerLength) {
// TODO: exception?
LOG.error(String.format(GBApplication.getLanguage(), "Packet lengths don't match! %d != %d", length, packetSize + headerLength));
}
byte[] complete = new byte[length];
packet.position(start);
packet.get(complete, 0, length);
int crc16 = CheckSums.getCRC16(complete, 0x0000);
packet.putShort((short) crc16); // CRC16
sliceStart += contentSize;
retv.add(packet.array());
}
return retv;
}
public List<byte[]> serialize() throws CryptoException {
// TODO: necessary for this to work:
// - serviceId
// - commandId
// - tlv
// TODO: maybe use the complete flag to know if it can be serialized?
HuaweiTLV serializableTlv;
if (this.isEncrypted && this.paramsProvider.areTransactionsCrypted()) {
try {
serializableTlv = this.tlv.encrypt(paramsProvider);
} catch (HuaweiCrypto.CryptoException e) {
throw new CryptoException("Encrypt exception", e);
}
} else {
serializableTlv = this.tlv;
}
byte[] serializedTLV = serializableTlv.serialize();
List<byte[]> retv;
if (isSliced) {
retv = serializeSliced(serializedTLV);
} else {
retv = serializeUnsliced(serializedTLV);
}
return retv;
}
public HuaweiTLV getTlv() {
return this.tlv;
}
public void setTlv(HuaweiTLV tlv) {
this.tlv = tlv;
}
public void setEncryption(boolean b) {
this.isEncrypted = b;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HuaweiPacket that = (HuaweiPacket) o;
if (serviceId != that.serviceId) return false;
if (commandId != that.commandId) return false;
if (complete != that.complete) return false;
if (isEncrypted != that.isEncrypted) return false;
return Objects.equals(tlv, that.tlv);
}
@Override
public String toString() {
return "HuaweiPacket{" +
"paramsProvider=" + paramsProvider +
", serviceId=" + serviceId +
", commandId=" + commandId +
", tlv=" + tlv +
", partialPacket=" + Arrays.toString(partialPacket) +
", payload=" + Arrays.toString(payload) +
", complete=" + complete +
", isEncrypted=" + isEncrypted +
", isSliced=" + isSliced +
'}';
}
}