/* Copyright (C) 2020-2024 Yukai Li 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.lefun.commands; import java.nio.ByteBuffer; import java.nio.ByteOrder; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; /** * Base class for Lefun Bluetooth commands and responses */ public abstract class BaseCommand { // Common constants /** * Common get operation type */ public static final byte OP_GET = 0; /** * Common set operation type */ public static final byte OP_SET = 1; /** * Calculates command checksum * * @param data the data to generate checksum from * @param offset the offset in data to start calculating from * @param length the number of bytes to include in calculation * @return the computed checksum */ public static byte calculateChecksum(byte[] data, int offset, int length) { int checksum = 0; for (int i = offset; i < offset + length; ++i) { byte b = data[i]; for (int j = 0; j < 8; ++j) { if (((b ^ checksum) & 1) == 0) { checksum >>= 1; } else { checksum = (checksum ^ 0x18) >> 1 | 0x80; } b >>= 1; } } return (byte) checksum; } /** * When implemented in a subclass, parses the response from a device * * @param id the command ID * @param params the params buffer */ abstract protected void deserializeParams(byte id, ByteBuffer params); /** * When implemented in a subclass, provides the arguments to send in the command * * @param params the params buffer to write to * @return the command ID */ abstract protected byte serializeParams(ByteBuffer params); /** * Deserialize a response from the device * * @param response the response data to deserialize */ public void deserialize(byte[] response) { if (response.length < LefunConstants.CMD_HEADER_LENGTH || response.length < response[1]) throw new IllegalArgumentException("Response is too short"); if (calculateChecksum(response, 0, response[1] - 1) != response[response[1] - 1]) throw new IllegalArgumentException("Incorrect message checksum"); ByteBuffer buffer = ByteBuffer.wrap(response, LefunConstants.CMD_HEADER_LENGTH - 1, response[1] - LefunConstants.CMD_HEADER_LENGTH); buffer.order(ByteOrder.BIG_ENDIAN); deserializeParams(response[2], buffer); } /** * Serializes a command to send to the device * * @return the data to send to the device */ public byte[] serialize() { ByteBuffer buffer = ByteBuffer.allocate(LefunConstants.CMD_MAX_LENGTH - LefunConstants.CMD_HEADER_LENGTH); buffer.order(ByteOrder.BIG_ENDIAN); byte id = serializeParams(buffer); return makeCommand(id, buffer); } /** * Builds a command given ID and parameters buffer. * * @param id the command ID * @param params the parameters buffer * @return the assembled command buffer */ protected byte[] makeCommand(byte id, ByteBuffer params) { if (params.position() > LefunConstants.CMD_MAX_LENGTH - LefunConstants.CMD_HEADER_LENGTH) throw new IllegalArgumentException("params is too long to fit"); int paramsLength = params.position(); byte[] request = new byte[paramsLength + LefunConstants.CMD_HEADER_LENGTH]; request[0] = LefunConstants.CMD_REQUEST_ID; request[1] = (byte) request.length; request[2] = id; params.flip(); params.get(request, LefunConstants.CMD_HEADER_LENGTH - 1, paramsLength); request[request.length - 1] = calculateChecksum(request, 0, request.length - 1); return request; } /** * Throws a standard parameters length exception */ protected void throwUnexpectedLength() { throw new IllegalArgumentException("Unexpected parameters length"); } /** * Checks for valid command ID and throws if wrong ID provided * * @param id command ID from device * @param expectedId expected command ID */ protected void validateId(byte id, byte expectedId) { if (id != expectedId) throw new IllegalArgumentException("Wrong command ID"); } /** * Checks for valid command ID and command length * * @param id command ID from device * @param params params buffer from device * @param expectedId expected command ID * @param expectedLength expected params length */ protected void validateIdAndLength(byte id, ByteBuffer params, byte expectedId, int expectedLength) { validateId(id, expectedId); if (params.limit() - params.position() != expectedLength) throwUnexpectedLength(); } /** * Gets whether a bit is set * * @param value the value to check against * @param mask the bitmask * @return whether the bits indicated by the bitmask are set */ protected boolean getBit(int value, int mask) { return (value & mask) != 0; } /** * Sets a bit in a value * * @param value the value to modify * @param mask the bitmask * @param set whether to set or clear the bits * @return the modified value */ protected int setBit(int value, int mask, boolean set) { if (set) { return value | mask; } else { return value & ~mask; } } /** * Sets a bit in a value * * @param value the value to modify * @param mask the bitmask * @param set whether to set or clear the bits * @return the modified value */ protected short setBit(short value, int mask, boolean set) { if (set) { return (short) (value | mask); } else { return (short) (value & ~mask); } } /** * Sets a bit in a value * * @param value the value to modify * @param mask the bitmask * @param set whether to set or clear the bits * @return the modified value */ protected byte setBit(byte value, int mask, boolean set) { if (set) { return (byte) (value | mask); } else { return (byte) (value & ~mask); } } /** * Find index of first bit that is set * * @param value the value to look at * @return the index of the lowest set bit, starting at 0 for least significant bit; -1 if no bits set */ protected int getLowestSetBitIndex(int value) { if (value == 0) return -1; int i = 0; while ((value & 1) == 0) { ++i; value >>= 1; } return i; } }