/* Copyright (C) 2024 Jonathan Gobbo 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.service.devices.redmibuds5pro.protocol; import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump; import androidx.annotation.NonNull; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Message { private static final byte[] MESSAGE_HEADER = {(byte) 0xfe, (byte) 0xdc, (byte) 0xba}; private static final byte MESSAGE_TRAILER = (byte) 0xef; private static final int MESSAGE_OFFSET = MESSAGE_HEADER.length; private final MessageType type; private final Opcode opcode; private final byte sequenceNumber; private final byte[] payload; public Message(final MessageType type, final Opcode opcode, final byte sequenceNumber, final byte[] payload) { this.type = type; this.opcode = opcode; this.sequenceNumber = sequenceNumber; this.payload = payload; } public static Message fromBytes(byte[] message) { MessageType type = MessageType.fromCode(message[MESSAGE_OFFSET]); Opcode opcode = Opcode.fromCode(message[MESSAGE_OFFSET + 1]); int payloadOffset = MESSAGE_OFFSET + ((!type.isRequest()) ? 6 : 5); byte sequenceNumber = message[payloadOffset - 1]; int actualPayloadLength = message.length - payloadOffset - 1; byte[] payload = new byte[actualPayloadLength]; System.arraycopy(message, payloadOffset, payload, 0, actualPayloadLength); return new Message(type, opcode, sequenceNumber, payload); } public static List splitPiggybackedMessages(byte[] input) { List messages = new ArrayList<>(); List startHeader = new ArrayList<>(); for (int i = 0; i < input.length - 3; i++) { if (input[i] == MESSAGE_HEADER[0] && input[i + 1] == MESSAGE_HEADER[1] && input[i + 2] == MESSAGE_HEADER[2]) { startHeader.add(i); } } for (int i = 0; i < startHeader.size(); i++) { if (i == startHeader.size() - 1) { messages.add(fromBytes(Arrays.copyOfRange(input, startHeader.get(i), input.length))); } else { messages.add(fromBytes(Arrays.copyOfRange(input, startHeader.get(i), startHeader.get(i + 1)))); } } return messages; } public byte[] encode() { int size = (!type.isRequest()) ? 2 : 1; final ByteBuffer buf = ByteBuffer.allocate(payload.length + 8 + size); int payloadLength = payload.length + size; buf.order(ByteOrder.BIG_ENDIAN); buf.put(MESSAGE_HEADER); buf.put(type.getCode()); buf.put(opcode.getOpcode()); buf.putShort((short) payloadLength); if (!type.isRequest()) { buf.put((byte) 0x00); } buf.put(sequenceNumber); buf.put(payload); buf.put(MESSAGE_TRAILER); return buf.array(); } @NonNull @Override public String toString() { return "Message{" + "type=" + type + ", opcode=" + opcode + ", sequenceNumber=" + sequenceNumber + ", payload=" + hexdump(payload) + '}'; } public MessageType getType() { return type; } public Opcode getOpcode() { return opcode; } public byte getSequenceNumber() { return sequenceNumber; } public byte[] getPayload() { return payload; } }