SquirrelJME/SquirrelJME

View on GitHub
modules/debug-jdwp/src/main/java/cc/squirreljme/jdwp/JDWPPacket.java

Summary

Maintainability
A
0 mins
Test Coverage
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// SquirrelJME
//     Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------

package cc.squirreljme.jdwp;

import cc.squirreljme.runtime.cldc.debug.ErrorCode;
import java.io.Closeable;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Deque;

/**
 * Represents a packet for JDWP.
 * 
 * This class is mutable and as such must be thread safe.
 *
 * @since 2021/03/10
 */
public final class JDWPPacket
    implements Closeable
{
    /** Flag used for replies. */
    public static final short FLAG_REPLY =
        0x80;
    
    /** Grow size. */
    private static final byte _GROW_SIZE =
        32;
    
    /** The queue where packets will go when done. */
    private final Reference<Deque<JDWPPacket>> _queue;
    
    /** The ID of this packet. */
    volatile int _id;
    
    /** The flags for this packet. */
    volatile int _flags;
    
    /** The command set (if not a reply). */
    volatile int _commandSet =
        -1;
    
    /* The command (if not a reply). */
    volatile int _command =
        -1;
    
    /** The raw error code. */
    volatile int _rawErrorCode;
    
    /** The error code (if a reply). */
    volatile JDWPErrorType _errorCode;
    
    /** The packet data. */
    private volatile byte[] _data;
    
    /** Identifier sizes. */
    private volatile JDWPIdSizes _idSizes;
    
    /** The length of the data. */
    private volatile int _length;
    
    /** The read position. */
    private volatile int _readPos;
    
    /** Is this packet open? */
    private volatile boolean _open;
    
    /**
     * Initializes the packet with the queue it will go back into whenever
     * it is done being used.
     * 
     * @param __queue The queue for packets.
     * @throws NullPointerException On null arguments.
     * @since 2021/03/10
     */
    JDWPPacket(Deque<JDWPPacket> __queue)
        throws NullPointerException
    {
        if (__queue == null)
            throw new NullPointerException("NARG");
        
        this._queue = new WeakReference<>(__queue);
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/03/10
     */
    @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
    @Override
    public void close()
        throws JDWPException
    {
        synchronized (this)
        {
            // Ignore if closed already (prevent double queue add)
            if (!this._open)
                return;
            
            // Set to closed
            this._open = false;
            
            // Return to the queue
            Deque<JDWPPacket> queue = this._queue.get();
            if (queue != null)
                synchronized (queue)
                {
                    queue.add(this);
                }
        }
    }
    
    /**
     * Returns the command Id.
     * 
     * @return The command Id.
     * @since 2021/03/13
     */
    public int command()
    {
        synchronized (this)
        {
            // Ensure it is valid
            this.__checkOpen();
            this.__checkType(false);
            
            return this._command;
        }
    }
    
    /**
     * Returns the command set for the packet.
     * 
     * @return The command set.
     * @since 2021/03/12
     */
    public JDWPCommandSet commandSet()
    {
        synchronized (this)
        {
            // Ensure it is valid
            this.__checkOpen();
            this.__checkType(false);
            
            // Map the ID
            return JDWPCommandSet.of(this._commandSet);
        }
    }
    
    /**
     * Returns the raw command set id.
     *
     * @return The command set id.
     * @since 2024/01/19
     */
    public int commandSetId()
    {
        synchronized (this)
        {
            // Ensure it is valid
            this.__checkOpen();
            this.__checkType(false);
            
            // Return the raw command set
            return this._commandSet;
        }
    }
    
    /**
     * Copies from the given packet into this one.
     * 
     * @param __packet The packet to copy from.
     * @return {@code this}.
     * @throws NullPointerException On null arguments.
     * @since 2021/04/30
     */
    @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
    public JDWPPacket copyOf(JDWPPacket __packet)
        throws NullPointerException
    {
        if (__packet == null)
            throw new NullPointerException("NARG");
        
        // Read from other packet to prevent potential deadlock
        int id;
        int flags;
        int commandSet;
        int command;
        JDWPErrorType errorCode;
        byte[] data;
        int length;
        int readPos;
        JDWPIdSizes idSizes;
        synchronized (__packet)
        {
            id = __packet._id;
            flags = __packet._flags;
            commandSet = __packet._commandSet;
            command = __packet._command;
            errorCode = __packet._errorCode;
            length = __packet._length;
            readPos = __packet._readPos;
            idSizes = __packet._idSizes;
            
            // We need a copy of the data as it is now since if that gets
            // closed somewhere, the data can become corrupted essentially
            if (__packet._data != null)
                data = __packet._data.clone();
            else
                data = null;
        }
        
        // Set packet details
        synchronized (this)
        {
            this._id = id;
            this._flags = flags;
            this._commandSet = commandSet;
            this._command = command;
            this._errorCode = errorCode;
            this._length = length;
            this._readPos = readPos;
            this._idSizes = idSizes;
            
            // Data might not even be valid here
            if (data != null)
            {
                // Can we get away with only copying part of the array?
                byte[] ourData = this._data;
                if (ourData != null && ourData.length >= data.length)
                    System.arraycopy(data, 0,
                        ourData, 0, data.length);
                
                // Larger size, we already did clone the source array so
                // use that here
                else
                    this._data = data;
            }
        }
        
        return this;
    }
    
    /**
     * Returns the error for this packet.
     *
     * @return The packet's error, if there is one.
     * @since 2024/01/22
     */
    public JDWPErrorType error()
    {
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            return this._errorCode;
        }
    }
    
    /**
     * Does this packet have any error?
     *
     * @return If this has an error.
     * @since 2024/01/21
     */
    public boolean hasError()
    {
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            return this._errorCode != JDWPErrorType.NO_ERROR;
        }
    }
    
    /**
     * Does this packet have the given error? This should be called when there
     * are other possible error states, but we only want to match against a
     * specific case.
     *
     * @param __error The error to check against.
     * @return If this has an error, and it is set to {@code __error}.
     * @throws NullPointerException On null arguments.
     * @since 2024/01/21
     */
    public boolean hasError(JDWPErrorType __error)
        throws NullPointerException
    {
        if (__error == null)
            throw new NullPointerException("NARG");
        
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            return this._errorCode == __error;
        }
    }
    
    /**
     * Returns the packet ID.
     * 
     * @return The packet it.
     * @since 2021/03/12
     */
    public int id()
    {
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            return this._id;
        }
    }
    
    /**
     * Returns the current ID sizes in use.
     *
     * @return The used ID sizes.
     * @since 2024/01/22
     */
    public JDWPIdSizes idSizes()
    {
        synchronized (this)
        {
            this.__checkOpen();
            
            return this._idSizes;
        }
    }
    
    /**
     * Is this a reply packet?
     * 
     * @return If this is a reply.
     * @since 2021/03/11
     */
    public boolean isReply()
    {
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            return (this._flags & JDWPPacket.FLAG_REPLY) != 0;
        }
    }
    
    /**
     * Returns the length of this packet.
     *
     * @return The packet length.
     * @since 2024/01/19
     */
    public int length()
    {
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            return this._length;
        }
    }
    
    /**
     * Reads a single boolean from the packet
     * 
     * @return The single read value.
     * @throws JDWPException If the end of the packet was reached.
     * @since 2021/04/17
     */
    public boolean readBoolean()
        throws JDWPException
    {
        return this.readByte() != 0;
    }
    
    /**
     * Reads a single byte from the packet
     * 
     * @return The single read value.
     * @throws JDWPException If the end of the packet was reached.
     * @since 2021/03/13
     */
    public final byte readByte()
        throws JDWPException
    {
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            /* {@squirreljme.error AG0d End of packet reached. 
            (The packet size; The packet length)} */
            int readPos = this._readPos;
            if (readPos >= this._length)
                throw new JDWPException(ErrorCode.__error__(
                    "AG0d", readPos, this._length));
            
            // Read in and increment the position
            byte rv = this._data[readPos];
            this._readPos = readPos + 1;
            return rv;
        }
    }
    
    /**
     * Fully reads an array of byte values.
     *
     * @param __length The number of bytes to read.
     * @return The resultant array.
     * @throws IllegalArgumentException If the length is negative.
     * @throws JDWPException If the data could not be read.
     * @since 2024/01/23
     */
    public byte[] readFully(int __length)
        throws IllegalArgumentException, JDWPException
    {
        if (__length < 0)
            throw new IllegalArgumentException("NEGV");
        
        return this.readFully(new byte[__length], 0, __length);
    }
    
    /**
     * Fully reads an array of byte values.
     *
     * @param __buf The buffer to read into.
     * @return The resultant array.
     * @throws JDWPException If the data could not be read.
     * @throws NullPointerException On null arguments.
     * @since 2024/01/23
     */
    public byte[] readFully(byte[] __buf)
        throws JDWPException, NullPointerException
    {
        if (__buf == null)
            throw new NullPointerException("NARG");
        
        return this.readFully(__buf, 0, __buf.length);
    }
    
    /**
     * Fully reads an array of byte values.
     *
     * @param __buf The buffer to read into.
     * @param __off The offset into the buffer.
     * @param __len The number of bytes to read.
     * @return The resultant array.
     * @throws IndexOutOfBoundsException If the offset and/or length are
     * negative or exceed the array bounds.
     * @throws JDWPException If the data could not be read.
     * @throws NullPointerException On null arguments.
     * @since 2024/01/23
     */
    public byte[] readFully(byte[] __buf, int __off, int __len)
        throws IndexOutOfBoundsException, JDWPException, NullPointerException
    {
        if (__buf == null)
            throw new NullPointerException("NARG");
        
        int bufLen = __buf.length;
        if (__off < 0 || __len < 0 || (__off + __len) > bufLen ||
            (__off + __len) < 0)
            throw new IndexOutOfBoundsException("IOOB");
        
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            // Keep reading in bytes
            for (int i = 0, at = __off; i < __len; i++, at++)
                __buf[at] = this.readByte();
        }
        
        // Return the passed buffer
        return __buf;
    }
    
    /**
     * Reads an identifier from the packet.
     * 
     * @return The single read value.
     * @throws JDWPException If the end of the packet was reached.
     * @deprecated Use {@link #readId(JDWPIdKind)} instead.
     * @since 2021/03/13
     */
    @Deprecated
    public final int readId()
        throws JDWPException
    {
        return this.readId(JDWPIdKind.UNKNOWN).intValue();
    }
    
    /**
     * Reads the ID based on the kind from the packet.
     *
     * @param __kind The kind of value to read.
     * @return The resultant ID.
     * @throws JDWPException If the kind is not valid or ID sizes are not yet
     * known.
     * @throws NullPointerException On null arguments.
     * @since 2024/01/23
     */
    public JDWPId readId(JDWPIdKind __kind)
        throws JDWPException, NullPointerException
    {
        if (__kind == null)
            throw new NullPointerException("NARG");
        
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            // Read in the variably sized entry.
            return JDWPId.of(__kind,
                this.readVariable(this.__sizeOf(__kind)));
        }
    }
    
    /**
     * Reads an integer from the packet
     * 
     * @return The single read value.
     * @throws JDWPException If the end of the packet was reached.
     * @since 2021/03/13
     */
    public final int readInt()
        throws JDWPException
    {
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            // Read in each byte
            return ((this.readByte() & 0xFF) << 24) |
                ((this.readByte() & 0xFF) << 16) |
                ((this.readByte() & 0xFF) << 8) |
                (this.readByte() & 0xFF);
        }
    }
    
    /**
     * Reads aa long from the packet
     * 
     * @return The single read value.
     * @throws JDWPException If the end of the packet was reached.
     * @since 2021/03/17
     */
    public long readLong()
        throws JDWPException
    {
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            // Read in each byte
            return ((this.readByte() & 0xFFL) << 56) |
                ((this.readByte() & 0xFFL) << 48) |
                ((this.readByte() & 0xFFL) << 40) |
                ((this.readByte() & 0xFFL) << 32) |
                ((this.readByte() & 0xFFL) << 24) |
                ((this.readByte() & 0xFF) << 16) |
                ((this.readByte() & 0xFF) << 8) |
                (this.readByte() & 0xFF);
        }
    }
    
    /**
     * Reads a short value.
     *
     * @return The read value.
     * @throws JDWPException If the packet could not be read.
     * @since 2024/01/26
     */
    public short readShort()
        throws JDWPException
    {
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            // Read in each byte
            return (short)(((this.readByte() & 0xFF) << 8) |
                (this.readByte() & 0xFF));
        }
    }
    
    /**
     * Reads the specified string.
     * 
     * @return The read string.
     * @throws JDWPException If it could not be read.
     * @since 2021/03/13
     */
    public final String readString()
        throws JDWPException
    {
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            // Read length
            int len = this.readInt();
            
            // Read in UTF data
            byte[] utf = new byte[len];
            for (int i = 0; i < len; i++)
                utf[i] = this.readByte();
            
            // Build final string
            try
            {
                return new String(utf, "utf-8");
            }
            catch (UnsupportedEncodingException __e)
            {
                /* {@squirreljme.error AG0f UTF-8 not supported?} */
                throw new JDWPException("AG0f", __e);
            }
        }
    }
    
    /**
     * Reads the tagged object ID.
     *
     * @return The tagged on.
     * @throws JDWPException If it could not be read.
     * @since 2024/01/27
     */
    public JDWPId readTaggedObjectId()
        throws JDWPException
    {
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            // Read the tag, assume object if unspecified.
            JDWPValueTag tag = JDWPValueTag.fromTag(this.readByte());
            if (tag == null)
                tag = JDWPValueTag.OBJECT;
            
            // Which kind does this map to?
            JDWPIdKind kind;
            switch (tag)
            {
                case THREAD:
                    kind = JDWPIdKind.THREAD_ID;
                    break;
                    
                case CLASS_OBJECT:
                    kind = JDWPIdKind.REFERENCE_TYPE_ID;
                    break;
                    
                    // Assume object
                default:
                    kind = JDWPIdKind.OBJECT_ID;
                    break;
            }
            
            // Read ID of this kind
            return this.readId(kind);
        }
    }
    
    /**
     * Reads a single value.
     *
     * @return The resultant value.
     * @throws JDWPException On read errors.
     * @since 2024/01/26
     */
    public JDWPValue readValue()
        throws JDWPException
    {
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            // Read tag
            byte rawTag = this.readByte();
            JDWPValueTag tag = JDWPValueTag.fromTag(rawTag);
            if (tag == null)
                throw new JDWPException("ITAG " + rawTag);
            
            // Read based on tag value
            Object value;
            switch (tag)
            {
                    // Void, which is nothing
                case VOID:
                    value = null;
                    break;
                
                    // Objects
                case ARRAY:
                case OBJECT:
                case STRING:
                case CLASS_OBJECT:
                case CLASS_LOADER:
                case THREAD_GROUP:
                    value = this.readId(JDWPIdKind.OBJECT_ID);
                    break;
                    
                case THREAD:
                    value = this.readId(JDWPIdKind.THREAD_ID);
                    break;
                    
                case BOOLEAN:
                    value = (this.readByte() != 0);
                    break;
                    
                case BYTE:
                    value = this.readByte();
                    break;
                    
                case SHORT:
                    value = this.readShort();
                    break;
                    
                case CHARACTER:
                    value = (char)this.readShort();
                    break;
                    
                case INTEGER:
                    value = this.readInt();
                    break;
                    
                case LONG:
                    value = this.readLong();
                    break;
                    
                case FLOAT:
                    value = Float.intBitsToFloat(this.readInt());
                    break;
                    
                case DOUBLE:
                    value = Double.longBitsToDouble(this.readLong());
                    break;
                    
                    // Unknown value
                default:
                    throw new JDWPException("ITAG " + rawTag);
            }
            
            // Return the resultant value
            return new JDWPValue(tag, value);
        }
    }
    
    /**
     * Reads a variable width value from the packet
     * 
     * @param __width The width of the value.
     * @return The single read value.
     * @throws IllegalArgumentException If the width is zero or negative.
     * @throws JDWPException If the end of the packet was reached.
     * @since 2021/03/13
     */
    public final long readVariable(int __width)
        throws IllegalArgumentException, JDWPException
    {
        if (__width <= 0)
            throw new IllegalArgumentException("NEGV");
        
        long result = 0;
        
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            // Read in each byte
            for (int i = 0; i < __width; i++)
            {
                // Shift up and read in
                result <<= 8;
                result |= (this.readByte() & 0xFF);
            }
        }
        
        return result;
    }
    
    /**
     * Resets the read position of the packet. 
     *
     * @return {@code this}.
     * @since 2024/01/23
     */
    public final JDWPPacket resetReadPosition()
    {
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            // Reset position
            this._readPos = 0;
        }
        
        // Always return self
        return this;
    }
    
    /**
     * Implicitly sets the ID sizes of this packet.
     *
     * @param __sizes The sizes to use.
     * @throws NullPointerException On null arguments.
     * @since 2024/01/23
     */
    public void setIdSizes(JDWPIdSizes __sizes)
        throws NullPointerException
    {
        if (__sizes == null)
            throw new NullPointerException("NARG");
        
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            this._idSizes = __sizes;
        }
    }
    
    /**
     * Returns the byte array of the packet.
     *
     * @return The packet data as a byte array.
     * @since 2024/01/22
     */
    public byte[] toByteArray()
    {
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            // If there is no data, then return a blank array
            byte[] data = this._data;
            if (data == null)
                return new byte[0];
            
            // Otherwise make a copy of it
            int len = this._length;
            byte[] result = new byte[len];
            System.arraycopy(data, 0,
                result, 0, len);
            
            // Use the result
            return result;
        }
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/03/13
     */
    @Override
    public final String toString()
    {
        synchronized (this)
        {
            if (!this._open)
                return "JDWPPacket:Closed";
            
            // Find the command set
            JDWPCommandSet commandSet = JDWPCommandSet.of(this._commandSet);
            JDWPCommand command = (commandSet == null ? null :
                commandSet.command(this._command));
            
            // Put in the actual packet data
            int length = this._length;
            byte[] data = this._data;
            StringBuilder sb = new StringBuilder(length * 2);
            for (int i = 0; i < length; i++)
            {
                byte b = data[i];
                
                sb.append(Character
                    .forDigit(((b & 0xF0) >>> 4) & 0xF, 16));
                sb.append(Character.forDigit(b & 0xF, 16));
            }
            
            int flags = this._flags;
            return String.format("JDWPPacket[id=%08x,flags=%02x,len=%d]:%s:%s",
                this._id, flags, length,
                ((flags & JDWPPacket.FLAG_REPLY) != 0 ?
                    (this._errorCode == JDWPErrorType.NO_ERROR ? "" :
                        String.format("[error=%s(%d)]",
                            this._errorCode, this._rawErrorCode)) :
                    String.format("[cmdSet=%s;cmd=%s]",
                        (commandSet == null ||
                            commandSet == JDWPCommandSet.UNKNOWN ?
                            this._commandSet : commandSet),
                        (command == null ? this._command : command))),
                sb);
        }
    }
    
    /**
     * Writes to the given packet.
     * 
     * @param __b The buffer.
     * @param __o The offset.
     * @param __l The length.
     * @throws IndexOutOfBoundsException If the offset and/or length are
     * negative or exceed the array bounds.
     * @throws JDWPException If this could not be written.
     * @throws NullPointerException On null arguments.
     * @since 2021/03/21
     */
    public void write(byte[] __b, int __o, int __l)
        throws IndexOutOfBoundsException, JDWPException, NullPointerException
    {
        if (__b == null)
            throw new NullPointerException("NARG");
        if (__o < 0 || __l < 0 || (__o + __l) < 0 || (__o + __l) > __b.length)
            throw new IndexOutOfBoundsException("IOOB");
        
        synchronized (this)
        {
            // Must be an open packet
            this.__checkOpen();
            
            for (int i = 0; i < __l; i++)
                this.writeByte(__b[__o + i]);
        }
    }
    
    /**
     * Writes the boolean to the output.
     * 
     * @param __b The boolean to write.
     * @throws JDWPException If it could not be written.
     * @since 2021/03/13
     */
    public void writeBoolean(boolean __b)
        throws JDWPException
    {
        this.writeByte((__b ? 1 : 0));
    }
    
    /**
     * Writes the given byte.
     * 
     * @param __v The value to write.
     * @since 2021/03/12
     */
    public void writeByte(int __v)
    {
        synchronized (this)
        {
            // Must be an open packet
            this.__checkOpen();
            
            // Where this will be going
            int length = this._length;
            byte[] data = this._data;
            
            // Too small?
            if (data == null || length + 1 > data.length)
                this._data = (data = (data == null ?
                    new byte[JDWPPacket._GROW_SIZE] : Arrays.copyOf(
                    data, data.length + JDWPPacket._GROW_SIZE)));
            
            // Write the byte
            data[length] = (byte)__v;
            this._length = length + 1;
        }
    }
    
    /**
     * Writes an ID to the output.
     * 
     * @param __v The value to write.
     * @throws JDWPException If it could not be written.
     * @deprecated Use {@link #writeId(JDWPId)} instead.
     * @since 2021/04/10
     */
    @Deprecated
    public void writeId(int __v)
        throws JDWPException
    {
        this.writeId(JDWPId.of(JDWPIdKind.UNKNOWN, __v));
    }
    
    /**
     * Writes the given ID.
     *
     * @param __id The ID to write.
     * @throws NullPointerException On null arguments.
     * @throws JDWPException If it could not be written or the sizes are not
     * known.
     * @since 2024/01/23
     */
    public void writeId(JDWPId __id)
        throws NullPointerException, JDWPException
    {
        if (__id == null)
            throw new NullPointerException("NARG");
        
        // Write variable sized data
        synchronized (this)
        {
            // Must be an open packet
            this.__checkOpen();
            
            // Write the variable sized identifier
            this.writeVariable(this.__sizeOf(__id.kind), __id.longValue());
        }
    }
    
    /**
     * Writes an integer to the output.
     * 
     * @param __v The value to write.
     * @throws JDWPException If it could not be written.
     * @since 2021/03/12
     */
    public void writeInt(int __v)
        throws JDWPException
    {
        synchronized (this)
        {
            // Must be an open packet
            this.__checkOpen();
            
            // Write the data
            this.writeByte(__v >> 24);
            this.writeByte(__v >> 16);
            this.writeByte(__v >> 8);
            this.writeByte(__v);
        }
    }
    
    /**
     * Writes a long to the output.
     * 
     * @param __v The value to write.
     * @throws JDWPException If it could not be written.
     * @since 2021/03/13
     */
    public void writeLong(long __v)
        throws JDWPException
    {
        synchronized (this)
        {
            // Must be an open packet
            this.__checkOpen();
            
            // Write the data
            this.writeByte((byte)(__v >> 56));
            this.writeByte((byte)(__v >> 48));
            this.writeByte((byte)(__v >> 40));
            this.writeByte((byte)(__v >> 32));
            this.writeByte((byte)(__v >> 24));
            this.writeByte((byte)(__v >> 16));
            this.writeByte((byte)(__v >> 8));
            this.writeByte((byte)(__v));
        }
    }
    
    /**
     * Writes a short to the output.
     * 
     * @param __v The value to write.
     * @throws JDWPException If it could not be written.
     * @since 2021/03/19
     */
    public void writeShort(int __v)
        throws JDWPException
    {
        synchronized (this)
        {
            // Must be an open packet
            this.__checkOpen();
            
            // Write the data
            this.writeByte(__v >> 8);
            this.writeByte(__v);
        }
    }
    
    /**
     * Writes the string to the output.
     * 
     * @param __string The string to write.
     * @throws JDWPException If it could not be written.
     * @since 2021/03/13
     */
    public void writeString(String __string)
        throws JDWPException
    {
        synchronized (this)
        {
            byte[] bytes;
            try
            {
                bytes = __string.getBytes("utf-8");
            }
            
            /* {@squirreljme.error AG0e UTF-8 is not supported?} */
            catch (UnsupportedEncodingException __e)
            {
                throw new JDWPException("AG0e", __e);
            }
            
            // Write length
            this.writeInt(bytes.length);
            
            // Write all the bytes
            for (byte b : bytes)
                this.writeByte(b);
        }
    }
    
    /**
     * Writes to the output.
     * 
     * @param __out The output.
     * @throws IOException On write errors.
     * @throws NullPointerException On null arguments.
     * @since 2021/03/12
     */
    public void writeTo(DataOutputStream __out)
        throws IOException, NullPointerException
    {
        if (__out == null)
            throw new NullPointerException("NARG");
        
        synchronized (this)
        {
            // Must be an open packet
            this.__checkOpen();
            
            // Write shared header (includes header size)
            __out.writeInt(this._length + JDWPCommLink._HEADER_SIZE);
            __out.writeInt(this._id);
            __out.writeByte(this._flags);
            
            // Reply packet
            if (this.isReply())
                __out.writeShort(this._errorCode.id);
            
            // Command packet
            else
            {
                __out.writeByte(this._commandSet);
                __out.writeByte(this._command);
            }
            
            // Write output data
            if (this._length > 0)
                __out.write(this._data, 0, this._length);
        }
    }
    
    /**
     * Writes a variable width value.
     *
     * @param __width The width of the value.
     * @param __v The value to write.
     * @since 2024/01/23
     */
    public void writeVariable(int __width, long __v)
        throws IllegalArgumentException, JDWPException
    {
        if (__width <= 0)
            throw new IllegalArgumentException("NEGV");
        
        // Reverse so that MSB is on the lower bits, shifting down is needed
        // so that if the width is smaller than 8 the MSB is in the correct
        // location instead of being 00.
        __v = Long.reverseBytes(__v) >>> (64 - (__width * 8));
        
        // Lock
        synchronized (this)
        {
            // Ensure this is open
            this.__checkOpen();
            
            // Write in each byte, MSB is on the lower bits due to reversal
            for (int i = 0; i < __width; i++)
            {
                // Write then shift down
                this.writeByte((byte)__v);
                __v >>>= 8;
            }
        }
    }
    
    /**
     * Writes a void type to the output.
     * 
     * @throws JDWPException If it failed to write.
     * @since 2021/03/19
     */
    public void writeVoid()
        throws JDWPException
    {
        synchronized (this)
        {
            // Must be an open packet
            this.__checkOpen();
            
            this.writeByte('V');
        }
    }
    
    /**
     * Checks if the packet is open.
     * 
     * @throws IllegalStateException If it is not open.
     * @since 2021/03/12
     */
    void __checkOpen()
        throws IllegalStateException
    {
        /* {@squirreljme.error AG0b Packet not open.} */
        if (!this._open)
            throw new IllegalStateException("AG0b");
    }
    
    /**
     * Checks if this is a reply packet or not.
     * 
     * @param __isReply If this should be reply.
     * @throws IllegalStateException If it is not a reply packet.
     * @since 2021/03/12
     */
    void __checkType(boolean __isReply)
        throws IllegalStateException
    {
        /* {@squirreljme.error AG0c Packet type mismatched. (Requested reply?)} */
        if (__isReply != this.isReply())
            throw new IllegalStateException("AG0c " + __isReply);
    }
    
    /**
     * Loads the packet data within.
     * 
     * @param __header The header.
     * @param __data The packet data.
     * @param __dataLen The data length.
     * @throws NullPointerException On null arguments.
     * @since 2021/03/10
     */
    void __load(byte[] __header, byte[] __data, int __dataLen)
        throws NullPointerException
    {
        synchronized (this)
        {
            /* {@squirreljme.error AG0a Packet is already open.} */
            if (this._open)
                throw new IllegalStateException("AG0a");
            
            // Grow (or allocate) to fit the data
            byte[] data = this._data;
            if (data == null || data.length < __dataLen)
                this._data = (data = new byte[__dataLen]);
            
            // Copy it in quickly
            System.arraycopy(__data, 0,
                data, 0, __dataLen);
            
            // Common header bits
            this._length = __dataLen;
            this._id = ((__header[4] & 0xFF) << 24) |
                ((__header[5] & 0xFF) << 16) |
                ((__header[6] & 0xFF) << 8) |
                (__header[7] & 0xFF);
            int flags;
            this._flags = ((flags = __header[8]) & 0xFF);
            
            // Reply type
            if ((flags & JDWPPacket.FLAG_REPLY) != 0)
            {
                // These are not used
                this._commandSet = -1;
                this._command = -1;
                
                // Read just the error code
                int rawErrorCode = ((__header[9] & 0xFF) << 8) |
                    (__header[10] & 0xFF);
                this._rawErrorCode = rawErrorCode;
                this._errorCode = JDWPErrorType.of(rawErrorCode);
            }
            
            // Non-reply
            else
            {
                // These are not used
                this._rawErrorCode = 0;
                this._errorCode = null;
                
                // Read the command used
                this._commandSet = __header[9] & 0xFF;
                this._command = __header[10] & 0xFF;
            }
            
            // Becomes open now
            this._open = true;
        }
    }
    
    /**
     * Resets and opens the packet.
     *
     * @param __open Should this be opened?
     * @param __idSizes ID sizes.
     * @since 2021/03/12
     */
    final void __resetAndOpen(boolean __open, JDWPIdSizes __idSizes)
    {
        synchronized (this)
        {
            /* {@squirreljme.error AG05 Cannot reset an open packet.} */
            if (this._open)
                throw new JDWPException("AG05");
            
            this._id = 0;
            this._flags = 0;
            this._commandSet = -1;
            this._command = -1;
            this._errorCode = null;
            this._rawErrorCode = -1;
            this._length = 0;
            this._readPos = 0;
            this._idSizes = __idSizes;
            
            // Mark as open?
            this._open = __open;
        }
    }
    
    /**
     * Returns the size of the given kind. 
     *
     * @param __kind The kind to get.
     * @return The size of the resultant kind.
     * @since 2024/01/23
     */
    private int __sizeOf(JDWPIdKind __kind)
    {
        synchronized (this)
        {
            /* {@squirreljme.error AG0z ID Sizes not currently known.} */
            JDWPIdSizes idSizes = this._idSizes;
            if (idSizes == null)
                throw new JDWPIdSizeUnknownException("AG0z");
            
            /* Read in the variably sized entry. */
            return idSizes.getSize(__kind);
        }
    }
}