public static WALObjectHeader unmarshal(ByteBuf buf) throws UnmarshalException {
        buf.markReaderIndex();

        int size = buf.readableBytes();
        if (size < 4) {
            throw new UnmarshalException(String.format("Insufficient bytes to read magic code, Recovered: [%d] expect: [%d]", size, 4));
        }

        int magicCode = buf.readInt();
        if (!WAL_HEADER_SIZES.containsKey(magicCode)) {
            throw new UnmarshalException(String.format("WALHeader magic code not match, Recovered: [%d] expect: [%s]", magicCode, WAL_HEADER_SIZES.keySet()));
        }
        if (size < WAL_HEADER_SIZES.get(magicCode)) {
            throw new UnmarshalException(String.format("WALHeader does not have enough bytes, Recovered: [%d] expect: [%d]", size, WAL_HEADER_SIZES.get(magicCode)));
        }

        WALObjectHeader header = null;
        if (magicCode == WAL_HEADER_MAGIC_CODE_V1) {
            header = new WALObjectHeader(buf.readLong(), buf.readLong(), buf.readLong(), buf.readInt(), buf.readLong(), buf.readLong());
        } else if (magicCode == WAL_HEADER_MAGIC_CODE_V0) {
            header = new WALObjectHeader(buf.readLong(), buf.readLong(), buf.readLong(), buf.readInt(), buf.readLong());
        }

        buf.resetReaderIndex();
        return header;
    }
In the historical version V0, the endOffset of each WAL object is calculated directly from the path and size of the object.
This method is used to be compatible with this case.

@param startOffset the start offset of the WAL object, get from the path
@param length the size of the WAL object
@return the end offset of the WAL object
s3stream/src/main/java/com/automq/stream/s3/wal/impl/object/WALObjectHeader.java