DecoderUtils.java
/*
* SPDX-FileCopyrightText: 2023-2025 Lucimber UG
* SPDX-License-Identifier: Apache-2.0
*/
package com.lucimber.dbus.codec.decoder;
import com.lucimber.dbus.type.DBusBasicType;
import com.lucimber.dbus.type.DBusContainerType;
import com.lucimber.dbus.type.DBusSignature;
import com.lucimber.dbus.type.DBusType;
import com.lucimber.dbus.type.DBusUInt32;
import com.lucimber.dbus.type.Type;
import com.lucimber.dbus.type.TypeCode;
import com.lucimber.dbus.type.TypeUtils;
import com.lucimber.dbus.util.LoggerUtils;
import java.nio.ByteBuffer;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Utility methods used by the ByteBuffer-based implementations of the decoders. */
public final class DecoderUtils {
static final int MAX_ARRAY_LENGTH = 67108864;
private static final Logger LOGGER = LoggerFactory.getLogger(DecoderUtils.class);
private static final DecoderFactory DECODER_FACTORY = new DefaultDecoderFactory();
private DecoderUtils() {
// Utility class
}
/**
* Skips alignment padding bytes in the buffer for the given type.
*
* @param buffer the ByteBuffer to advance
* @param offset the current byte offset
* @param type the D-Bus type requiring alignment
* @return the number of padding bytes skipped
*/
public static int skipPadding(ByteBuffer buffer, int offset, Type type) {
int padding = calculateAlignmentPadding(type, offset);
if (padding > 0) {
buffer.position(buffer.position() + padding);
}
return padding;
}
/**
* Calculates the number of padding bytes needed for proper alignment.
*
* @param type the D-Bus type requiring alignment
* @param byteCount the current byte count/offset
* @return the number of padding bytes needed
*/
public static int calculateAlignmentPadding(Type type, int byteCount) {
int alignment = type.getAlignment().getAlignment();
int remainder = byteCount % alignment;
if (remainder > 0) {
LOGGER.debug(
LoggerUtils.MARSHALLING,
"Calculating alignment padding: alignment={}; remainder={}",
alignment,
remainder);
return alignment - remainder;
} else {
return 0;
}
}
/**
* Verifies that an array length is within D-Bus limits.
*
* @param length the array length to verify
* @throws DecoderException if the length exceeds maximum allowed
*/
public static void verifyArrayLength(DBusUInt32 length) {
LOGGER.trace(LoggerUtils.MARSHALLING, "Verifying length of D-Bus array.");
Objects.requireNonNull(length, "length must not be null");
if (Integer.compareUnsigned(length.getDelegate(), MAX_ARRAY_LENGTH) > 0) {
String msg =
String.format(
"Array length (%s) exceeds maximum length (%s)",
length, Integer.toUnsignedString(MAX_ARRAY_LENGTH));
throw new DecoderException(msg);
}
}
/**
* Decodes D-Bus data types from a buffer.
*
* @param signature signature of the expected data type
* @param buffer buffer containing the expected data type
* @param offset offset inside the buffer
* @param <R> expected data type
* @return decoded data type
* @throws DecoderException If the expected data type could not be decoded from the buffer.
*/
@SuppressWarnings("unchecked")
public static <R extends DBusType> DecoderResult<R> decode(
DBusSignature signature, ByteBuffer buffer, int offset) throws DecoderException {
Objects.requireNonNull(signature, "signature must not be null");
Objects.requireNonNull(buffer, "buffer must not be null");
if (signature.isContainerType()) {
return (DecoderResult<R>) decodeContainerType(signature, buffer, offset);
} else {
char c = signature.toString().charAt(0);
TypeCode code =
TypeUtils.getCodeFromChar(c)
.orElseThrow(
() -> new DecoderException("Cannot map char to code: " + c));
return (DecoderResult<R>) decodeBasicType(code, buffer, offset);
}
}
/**
* Decodes a D-Bus container type from the buffer.
*
* @param signature signature of the container type
* @param buffer buffer containing the data
* @param offset offset inside the buffer
* @param <R> expected container type
* @return decoded container type
* @throws DecoderException if decoding fails
*/
@SuppressWarnings("unchecked")
public static <R extends DBusContainerType> DecoderResult<R> decodeContainerType(
DBusSignature signature, ByteBuffer buffer, int offset) throws DecoderException {
if (signature.isArray()) {
return (DecoderResult<R>) new ArrayDecoder<>(signature).decode(buffer, offset);
} else if (signature.isDictionary()) {
return (DecoderResult<R>) new DictDecoder<>(signature).decode(buffer, offset);
} else if (signature.isDictionaryEntry()) {
return (DecoderResult<R>) new DictEntryDecoder<>(signature).decode(buffer, offset);
} else if (signature.isStruct()) {
return (DecoderResult<R>) new StructDecoder(signature).decode(buffer, offset);
} else if (signature.isVariant()) {
return (DecoderResult<R>) new VariantDecoder().decode(buffer, offset);
} else {
throw new DecoderException("Unsupported container type");
}
}
/**
* Decodes a D-Bus basic type from the buffer.
*
* @param code the type code of the basic type
* @param buffer buffer containing the data
* @param offset offset inside the buffer
* @param <R> expected basic type
* @return decoded basic type
* @throws DecoderException if decoding fails
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static <R extends DBusBasicType> DecoderResult<R> decodeBasicType(
TypeCode code, ByteBuffer buffer, int offset) throws DecoderException {
Decoder<ByteBuffer, DBusType> decoder = DECODER_FACTORY.createDecoder(code);
DecoderResult result = decoder.decode(buffer, offset);
return (DecoderResult<R>) result;
}
}