StructDecoder.java

/*
 * SPDX-FileCopyrightText: 2023-2025 Lucimber UG
 * SPDX-License-Identifier: Apache-2.0
 */
package com.lucimber.dbus.codec.decoder;

import com.lucimber.dbus.type.DBusSignature;
import com.lucimber.dbus.type.DBusStruct;
import com.lucimber.dbus.type.DBusType;
import com.lucimber.dbus.type.Type;
import com.lucimber.dbus.util.LoggerUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A decoder which unmarshals a struct from the byte stream format used by D-Bus.
 *
 * @see Decoder
 * @see DBusStruct
 */
public final class StructDecoder implements Decoder<ByteBuffer, DBusStruct> {

    private static final Logger LOGGER = LoggerFactory.getLogger(StructDecoder.class);

    private final DBusSignature signature;

    /**
     * Creates a new instance with mandatory parameters.
     *
     * @param signature a {@link DBusSignature}; must describe a struct
     */
    public StructDecoder(DBusSignature signature) {
        this.signature = Objects.requireNonNull(signature, "signature must not be null");
        if (!signature.isStruct()) {
            throw new IllegalArgumentException("signature must describe a struct");
        }
    }

    @Override
    public DecoderResult<DBusStruct> decode(ByteBuffer buffer, int offset) throws DecoderException {
        Objects.requireNonNull(buffer, "buffer must not be null");
        try {
            // Skip alignment padding
            int padding = DecoderUtils.skipPadding(buffer, offset, Type.STRUCT);
            int consumedBytes = padding;

            // Decode fields
            List<DBusType> values = new ArrayList<>();
            DBusSignature subSignature = signature.subContainer();
            List<DBusSignature> components =
                    (subSignature.getQuantity() == 1)
                            ? List.of(subSignature)
                            : subSignature.getChildren();

            for (DBusSignature sig : components) {
                int fieldOffset = offset + consumedBytes;
                DecoderResult<? extends DBusType> result =
                        DecoderUtils.decode(sig, buffer, fieldOffset);
                values.add(result.getValue());
                consumedBytes += result.getConsumedBytes();
            }

            DBusStruct struct = new DBusStruct(signature, values);
            DecoderResult<DBusStruct> result = new DecoderResultImpl<>(consumedBytes, struct);

            LOGGER.debug(
                    LoggerUtils.MARSHALLING,
                    "STRUCT: {}; Offset: {}; Padding: {}; Consumed bytes: {};",
                    signature,
                    offset,
                    padding,
                    consumedBytes);

            return result;
        } catch (Exception e) {
            throw new DecoderException("Could not decode STRUCT.", e);
        }
    }
}