Add read of ID that uses kinds.
[SquirrelJME.git] / modules / debug-jdwp / src / main / java / cc / squirreljme / jdwp / JDWPPacket.java
blob9b4cfc3ae1722f377cf7be9106e87253b7af9579
1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
3 // SquirrelJME
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the Mozilla Public License Version 2.0.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package cc.squirreljme.jdwp;
12 import java.io.Closeable;
13 import java.io.DataOutputStream;
14 import java.io.IOException;
15 import java.io.UnsupportedEncodingException;
16 import java.lang.ref.Reference;
17 import java.lang.ref.WeakReference;
18 import java.util.Arrays;
19 import java.util.Deque;
21 /**
22 * Represents a packet for JDWP.
24 * This class is mutable and as such must be thread safe.
26 * @since 2021/03/10
28 public final class JDWPPacket
29 implements Closeable
31 /** Flag used for replies. */
32 public static final short FLAG_REPLY =
33 0x80;
35 /** Grow size. */
36 private static final byte _GROW_SIZE =
37 32;
39 /** The queue where packets will go when done. */
40 private final Reference<Deque<JDWPPacket>> _queue;
42 /** The ID of this packet. */
43 volatile int _id;
45 /** The flags for this packet. */
46 volatile int _flags;
48 /** The command set (if not a reply). */
49 volatile int _commandSet =
50 -1;
52 /* The command (if not a reply). */
53 volatile int _command =
54 -1;
56 /** The raw error code. */
57 volatile int _rawErrorCode;
59 /** The error code (if a reply). */
60 volatile JDWPErrorType _errorCode;
62 /** The packet data. */
63 private volatile byte[] _data;
65 /** Identifier sizes. */
66 private volatile JDWPIdSizes _idSizes;
68 /** The length of the data. */
69 private volatile int _length;
71 /** The read position. */
72 private volatile int _readPos;
74 /** Is this packet open? */
75 private volatile boolean _open;
77 /**
78 * Initializes the packet with the queue it will go back into whenever
79 * it is done being used.
81 * @param __queue The queue for packets.
82 * @throws NullPointerException On null arguments.
83 * @since 2021/03/10
85 JDWPPacket(Deque<JDWPPacket> __queue)
86 throws NullPointerException
88 if (__queue == null)
89 throw new NullPointerException("NARG");
91 this._queue = new WeakReference<>(__queue);
94 /**
95 * {@inheritDoc}
96 * @since 2021/03/10
98 @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
99 @Override
100 public void close()
101 throws JDWPException
103 synchronized (this)
105 // Ignore if closed already (prevent double queue add)
106 if (!this._open)
107 return;
109 // Set to closed
110 this._open = false;
112 // Return to the queue
113 Deque<JDWPPacket> queue = this._queue.get();
114 if (queue != null)
115 synchronized (queue)
117 queue.add(this);
123 * Returns the command Id.
125 * @return The command Id.
126 * @since 2021/03/13
128 public int command()
130 synchronized (this)
132 // Ensure it is valid
133 this.__checkOpen();
134 this.__checkType(false);
136 return this._command;
141 * Returns the command set for the packet.
143 * @return The command set.
144 * @since 2021/03/12
146 public JDWPCommandSet commandSet()
148 synchronized (this)
150 // Ensure it is valid
151 this.__checkOpen();
152 this.__checkType(false);
154 // Map the ID
155 return JDWPCommandSet.of(this._commandSet);
160 * Returns the raw command set id.
162 * @return The command set id.
163 * @since 2024/01/19
165 public int commandSetId()
167 synchronized (this)
169 // Ensure it is valid
170 this.__checkOpen();
171 this.__checkType(false);
173 // Return the raw command set
174 return this._commandSet;
179 * Returns a copy of the given packet.
181 * @param __packet The packet to copy from.
182 * @return {@code this}.
183 * @throws NullPointerException On null arguments.
184 * @since 2021/04/30
186 @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
187 public JDWPPacket copyOf(JDWPPacket __packet)
188 throws NullPointerException
190 if (__packet == null)
191 throw new NullPointerException("NARG");
193 // Read from other packet to prevent potential deadlock
194 int id;
195 int flags;
196 int commandSet;
197 int command;
198 JDWPErrorType errorCode;
199 byte[] data;
200 int length;
201 int readPos;
202 synchronized (__packet)
204 id = __packet._id;
205 flags = __packet._flags;
206 commandSet = __packet._commandSet;
207 command = __packet._command;
208 errorCode = __packet._errorCode;
209 data = __packet._data;
210 length = __packet._length;
211 readPos = __packet._readPos;
214 // Set packet details
215 synchronized (this)
217 this._id = id;
218 this._flags = flags;
219 this._commandSet = commandSet;
220 this._command = command;
221 this._errorCode = errorCode;
222 this._length = length;
223 this._readPos = readPos;
225 // Data might not even be valid here
226 if (data != null)
228 // Can we get away with only copying part of the array?
229 byte[] ourData = this._data;
230 if (ourData != null && ourData.length >= data.length)
231 System.arraycopy(data, 0,
232 ourData, 0, data.length);
234 // Larger size, which will require array re-allocation
235 else
236 this._data = data.clone();
240 return this;
244 * Returns the error for this packet.
246 * @return The packet's error, if there is one.
247 * @since 2024/01/22
249 public JDWPErrorType error()
251 synchronized (this)
253 // Ensure this is open
254 this.__checkOpen();
256 return this._errorCode;
261 * Does this packet have any error?
263 * @return If this has an error.
264 * @since 2024/01/21
266 public boolean hasError()
268 synchronized (this)
270 // Ensure this is open
271 this.__checkOpen();
273 return this._errorCode != JDWPErrorType.NO_ERROR;
278 * Does this packet have the given error? This should be called when there
279 * are other possible error states, but we only want to match against a
280 * specific case.
282 * @param __error The error to check against.
283 * @return If this has an error, and it is set to {@code __error}.
284 * @throws NullPointerException On null arguments.
285 * @since 2024/01/21
287 public boolean hasError(JDWPErrorType __error)
288 throws NullPointerException
290 if (__error == null)
291 throw new NullPointerException("NARG");
293 synchronized (this)
295 // Ensure this is open
296 this.__checkOpen();
298 return this._errorCode == __error;
303 * Returns the packet ID.
305 * @return The packet it.
306 * @since 2021/03/12
308 public int id()
310 synchronized (this)
312 // Ensure this is open
313 this.__checkOpen();
315 return this._id;
320 * Returns the current ID sizes in use.
322 * @return The used ID sizes.
323 * @since 2024/01/22
325 public JDWPIdSizes idSizes()
327 synchronized (this)
329 this.__checkOpen();
331 return this._idSizes;
336 * Is this a reply packet?
338 * @return If this is a reply.
339 * @since 2021/03/11
341 public boolean isReply()
343 synchronized (this)
345 // Ensure this is open
346 this.__checkOpen();
348 return (this._flags & JDWPPacket.FLAG_REPLY) != 0;
353 * Returns the length of this packet.
355 * @return The packet length.
356 * @since 2024/01/19
358 public int length()
360 synchronized (this)
362 // Ensure this is open
363 this.__checkOpen();
365 return this._length;
370 * Reads a single boolean from the packet
372 * @return The single read value.
373 * @throws JDWPException If the end of the packet was reached.
374 * @since 2021/04/17
376 public boolean readBoolean()
377 throws JDWPException
379 return this.readByte() != 0;
383 * Reads a single byte from the packet
385 * @return The single read value.
386 * @throws JDWPException If the end of the packet was reached.
387 * @since 2021/03/13
389 public final byte readByte()
390 throws JDWPException
392 synchronized (this)
394 // Ensure this is open
395 this.__checkOpen();
397 /* {@squirreljme.error AG0d End of packet reached.
398 (The packet size)} */
399 int readPos = this._readPos;
400 if (readPos >= this._length)
401 throw new JDWPException("AG0d " + readPos);
403 // Read in and increment the position
404 byte rv = this._data[readPos];
405 this._readPos = readPos + 1;
406 return rv;
411 * Reads an identifier from the packet.
413 * @return The single read value.
414 * @throws JDWPException If the end of the packet was reached.
415 * @deprecated Do not use.
416 * @since 2021/03/13
418 @Deprecated
419 public final int readId()
420 throws JDWPException
422 return this.readInt();
426 * Reads the ID based on the kind from the packet.
428 * @param __kind The kind of value to read.
429 * @return The resultant ID.
430 * @throws JDWPException If the kind is not valid or ID sizes are not yet
431 * known.
432 * @throws NullPointerException On null arguments.
433 * @since 2024/01/23
435 public JDWPId readId(JDWPIdKind __kind)
436 throws JDWPException, NullPointerException
438 if (__kind == null)
439 throw new NullPointerException("NARG");
441 synchronized (this)
443 // Ensure this is open
444 this.__checkOpen();
446 /* {@squirreljme.error AG0z ID Sizes not currently known.} */
447 JDWPIdSizes idSizes = this._idSizes;
448 if (idSizes == null)
449 throw new JDWPException("AG0z");
451 /* Read in the variably sized entry. */
452 return JDWPId.of(__kind,
453 this.readVariable(idSizes.getSize(__kind)));
458 * Reads an integer from the packet
460 * @return The single read value.
461 * @throws JDWPException If the end of the packet was reached.
462 * @since 2021/03/13
464 public final int readInt()
465 throws JDWPException
467 synchronized (this)
469 // Ensure this is open
470 this.__checkOpen();
472 // Read in each byte
473 return ((this.readByte() & 0xFF) << 24) |
474 ((this.readByte() & 0xFF) << 16) |
475 ((this.readByte() & 0xFF) << 8) |
476 (this.readByte() & 0xFF);
481 * Reads aa long from the packet
483 * @return The single read value.
484 * @throws JDWPException If the end of the packet was reached.
485 * @since 2021/03/17
487 public long readLong()
488 throws JDWPException
490 synchronized (this)
492 // Ensure this is open
493 this.__checkOpen();
495 // Read in each byte
496 return ((this.readByte() & 0xFFL) << 56) |
497 ((this.readByte() & 0xFFL) << 48) |
498 ((this.readByte() & 0xFFL) << 40) |
499 ((this.readByte() & 0xFFL) << 32) |
500 ((this.readByte() & 0xFFL) << 24) |
501 ((this.readByte() & 0xFF) << 16) |
502 ((this.readByte() & 0xFF) << 8) |
503 (this.readByte() & 0xFF);
508 * Reads the specified string.
510 * @return The read string.
511 * @throws JDWPException If it could not be read.
512 * @since 2021/03/13
514 public final String readString()
515 throws JDWPException
517 synchronized (this)
519 // Ensure this is open
520 this.__checkOpen();
522 // Read length
523 int len = this.readInt();
525 // Read in UTF data
526 byte[] utf = new byte[len];
527 for (int i = 0; i < len; i++)
528 utf[i] = this.readByte();
530 // Build final string
533 return new String(utf, "utf-8");
535 catch (UnsupportedEncodingException __e)
537 /* {@squirreljme.error AG0f UTF-8 not supported?} */
538 throw new JDWPException("AG0f", __e);
544 * Reads a variable width value from the packet
546 * @param __width The width of the value.
547 * @return The single read value.
548 * @throws IllegalArgumentException If the width is zero or negative.
549 * @throws JDWPException If the end of the packet was reached.
550 * @since 2021/03/13
552 public final long readVariable(int __width)
553 throws IllegalArgumentException, JDWPException
555 if (__width <= 0)
556 throw new IllegalArgumentException("NEGV");
558 long result = 0;
560 synchronized (this)
562 // Ensure this is open
563 this.__checkOpen();
565 // Read in each byte
566 for (int i = 0; i < __width; i++)
568 // Shift up and read in
569 result <<= 8;
570 result |= (this.readByte() & 0xFF);
574 return result;
578 * Returns the byte array of the packet.
580 * @return The packet data as a byte array.
581 * @since 2024/01/22
583 public byte[] toByteArray()
585 synchronized (this)
587 // Ensure this is open
588 this.__checkOpen();
590 // If there is no data, then return a blank array
591 byte[] data = this._data;
592 if (data == null)
593 return new byte[0];
595 // Otherwise make a copy of it
596 int len = this._length;
597 byte[] result = new byte[len];
598 System.arraycopy(data, 0,
599 result, 0, len);
601 // Use the result
602 return result;
607 * {@inheritDoc}
608 * @since 2021/03/13
610 @Override
611 public final String toString()
613 synchronized (this)
615 if (!this._open)
616 return "JDWPPacket:Closed";
618 // Find the command set
619 JDWPCommandSet commandSet = JDWPCommandSet.of(this._commandSet);
620 JDWPCommand command = (commandSet == null ? null :
621 commandSet.command(this._command));
623 // Put in the actual packet data
624 int length = this._length;
625 byte[] data = this._data;
626 StringBuilder sb = new StringBuilder(length * 2);
627 for (int i = 0; i < length; i++)
629 byte b = data[i];
631 sb.append(Character
632 .forDigit(((b & 0xF0) >>> 4) & 0xF, 16));
633 sb.append(Character.forDigit(b & 0xF, 16));
636 int flags = this._flags;
637 return String.format("JDWPPacket[id=%08x,flags=%02x,len=%d]:%s:%s",
638 this._id, flags, length,
639 ((flags & JDWPPacket.FLAG_REPLY) != 0 ?
640 (this._errorCode == JDWPErrorType.NO_ERROR ? "" :
641 String.format("[error=%s(%d)]",
642 this._errorCode, this._rawErrorCode)) :
643 String.format("[cmdSet=%s;cmd=%s]",
644 (commandSet == null ||
645 commandSet == JDWPCommandSet.UNKNOWN ?
646 this._commandSet : commandSet),
647 (command == null ? this._command : command))),
648 sb);
653 * Writes to the given packet.
655 * @param __b The buffer.
656 * @param __o The offset.
657 * @param __l The length.
658 * @throws IndexOutOfBoundsException If the offset and/or length are
659 * negative or exceed the array bounds.
660 * @throws JDWPException If this could not be written.
661 * @throws NullPointerException On null arguments.
662 * @since 2021/03/21
664 public void write(byte[] __b, int __o, int __l)
665 throws IndexOutOfBoundsException, JDWPException, NullPointerException
667 if (__b == null)
668 throw new NullPointerException("NARG");
669 if (__o < 0 || __l < 0 || (__o + __l) < 0 || (__o + __l) > __b.length)
670 throw new IndexOutOfBoundsException("IOOB");
672 synchronized (this)
674 // Must be an open packet
675 this.__checkOpen();
677 for (int i = 0; i < __l; i++)
678 this.writeByte(__b[__o + i]);
683 * Writes the boolean to the output.
685 * @param __b The boolean to write.
686 * @throws JDWPException If it could not be written.
687 * @since 2021/03/13
689 public void writeBoolean(boolean __b)
690 throws JDWPException
692 this.writeByte((__b ? 1 : 0));
696 * Writes the given byte.
698 * @param __v The value to write.
699 * @since 2021/03/12
701 public void writeByte(int __v)
703 synchronized (this)
705 // Must be an open packet
706 this.__checkOpen();
708 // Where this will be going
709 int length = this._length;
710 byte[] data = this._data;
712 // Too small?
713 if (data == null || length + 1 > data.length)
714 this._data = (data = (data == null ?
715 new byte[JDWPPacket._GROW_SIZE] : Arrays.copyOf(
716 data, data.length + JDWPPacket._GROW_SIZE)));
718 // Write the byte
719 data[length] = (byte)__v;
720 this._length = length + 1;
725 * Writes an ID to the output.
727 * @param __v The value to write.
728 * @throws JDWPException If it could not be written.
729 * @since 2021/04/10
731 public void writeId(int __v)
732 throws JDWPException
734 this.writeInt(__v);
738 * Writes an integer to the output.
740 * @param __v The value to write.
741 * @throws JDWPException If it could not be written.
742 * @since 2021/03/12
744 public void writeInt(int __v)
745 throws JDWPException
747 synchronized (this)
749 // Must be an open packet
750 this.__checkOpen();
752 // Write the data
753 this.writeByte(__v >> 24);
754 this.writeByte(__v >> 16);
755 this.writeByte(__v >> 8);
756 this.writeByte(__v);
761 * Writes a long to the output.
763 * @param __v The value to write.
764 * @throws JDWPException If it could not be written.
765 * @since 2021/03/13
767 public void writeLong(long __v)
768 throws JDWPException
770 synchronized (this)
772 // Must be an open packet
773 this.__checkOpen();
775 // Write the data
776 this.writeByte((byte)(__v >> 56));
777 this.writeByte((byte)(__v >> 48));
778 this.writeByte((byte)(__v >> 40));
779 this.writeByte((byte)(__v >> 32));
780 this.writeByte((byte)(__v >> 24));
781 this.writeByte((byte)(__v >> 16));
782 this.writeByte((byte)(__v >> 8));
783 this.writeByte((byte)(__v));
788 * Writes a short to the output.
790 * @param __v The value to write.
791 * @throws JDWPException If it could not be written.
792 * @since 2021/03/19
794 public void writeShort(int __v)
795 throws JDWPException
797 synchronized (this)
799 // Must be an open packet
800 this.__checkOpen();
802 // Write the data
803 this.writeByte(__v >> 8);
804 this.writeByte(__v);
809 * Writes the string to the output.
811 * @param __string The string to write.
812 * @throws JDWPException If it could not be written.
813 * @since 2021/03/13
815 public void writeString(String __string)
816 throws JDWPException
818 synchronized (this)
820 byte[] bytes;
823 bytes = __string.getBytes("utf-8");
826 /* {@squirreljme.error AG0e UTF-8 is not supported?} */
827 catch (UnsupportedEncodingException __e)
829 throw new JDWPException("AG0e", __e);
832 // Write length
833 this.writeInt(bytes.length);
835 // Write all the bytes
836 for (byte b : bytes)
837 this.writeByte(b);
842 * Writes to the output.
844 * @param __out The output.
845 * @throws IOException On write errors.
846 * @throws NullPointerException On null arguments.
847 * @since 2021/03/12
849 public void writeTo(DataOutputStream __out)
850 throws IOException, NullPointerException
852 if (__out == null)
853 throw new NullPointerException("NARG");
855 synchronized (this)
857 // Must be an open packet
858 this.__checkOpen();
860 // Write shared header (includes header size)
861 __out.writeInt(this._length + JDWPCommLink._HEADER_SIZE);
862 __out.writeInt(this._id);
863 __out.writeByte(this._flags);
865 // Reply packet
866 if (this.isReply())
867 __out.writeShort(this._errorCode.id);
869 // Command packet
870 else
872 __out.writeByte(this._commandSet);
873 __out.writeByte(this._command);
876 // Write output data
877 if (this._length > 0)
878 __out.write(this._data, 0, this._length);
883 * Writes a void type to the output.
885 * @throws JDWPException If it failed to write.
886 * @since 2021/03/19
888 public void writeVoid()
889 throws JDWPException
891 synchronized (this)
893 // Must be an open packet
894 this.__checkOpen();
896 this.writeByte('V');
901 * Checks if the packet is open.
903 * @throws IllegalStateException If it is not open.
904 * @since 2021/03/12
906 void __checkOpen()
907 throws IllegalStateException
909 /* {@squirreljme.error AG0b Packet not open.} */
910 if (!this._open)
911 throw new IllegalStateException("AG0b");
915 * Checks if this is a reply packet or not.
917 * @param __isReply If this should be reply.
918 * @throws IllegalStateException If it is not a reply packet.
919 * @since 2021/03/12
921 void __checkType(boolean __isReply)
922 throws IllegalStateException
924 /* {@squirreljme.error AG0c Packet type mismatched. (Requested reply?)} */
925 if (__isReply != this.isReply())
926 throw new IllegalStateException("AG0c " + __isReply);
930 * Loads the packet data within.
932 * @param __header The header.
933 * @param __data The packet data.
934 * @param __dataLen The data length.
935 * @throws NullPointerException On null arguments.
936 * @since 2021/03/10
938 void __load(byte[] __header, byte[] __data, int __dataLen)
939 throws NullPointerException
941 synchronized (this)
943 /* {@squirreljme.error AG0a Packet is already open.} */
944 if (this._open)
945 throw new IllegalStateException("AG0a");
947 // Grow (or allocate) to fit the data
948 byte[] data = this._data;
949 if (data == null || data.length < __dataLen)
950 this._data = (data = new byte[__dataLen]);
952 // Copy it in quickly
953 System.arraycopy(__data, 0,
954 data, 0, __dataLen);
956 // Common header bits
957 this._length = __dataLen;
958 this._id = ((__header[4] & 0xFF) << 24) |
959 ((__header[5] & 0xFF) << 16) |
960 ((__header[6] & 0xFF) << 8) |
961 (__header[7] & 0xFF);
962 int flags;
963 this._flags = ((flags = __header[8]) & 0xFF);
965 // Reply type
966 if ((flags & JDWPPacket.FLAG_REPLY) != 0)
968 // These are not used
969 this._commandSet = -1;
970 this._command = -1;
972 // Read just the error code
973 int rawErrorCode = ((__header[9] & 0xFF) << 8) |
974 (__header[10] & 0xFF);
975 this._rawErrorCode = rawErrorCode;
976 this._errorCode = JDWPErrorType.of(rawErrorCode);
979 // Non-reply
980 else
982 // These are not used
983 this._rawErrorCode = 0;
984 this._errorCode = null;
986 // Read the command used
987 this._commandSet = __header[9] & 0xFF;
988 this._command = __header[10] & 0xFF;
991 // Becomes open now
992 this._open = true;
997 * Resets and opens the packet.
999 * @param __open Should this be opened?
1000 * @param __idSizes ID sizes.
1001 * @since 2021/03/12
1003 final void __resetAndOpen(boolean __open, JDWPIdSizes __idSizes)
1005 synchronized (this)
1007 /* {@squirreljme.error AG05 Cannot reset an open packet.} */
1008 if (this._open)
1009 throw new JDWPException("AG05");
1011 this._id = 0;
1012 this._flags = 0;
1013 this._commandSet = -1;
1014 this._command = -1;
1015 this._errorCode = null;
1016 this._rawErrorCode = -1;
1017 this._length = 0;
1018 this._readPos = 0;
1019 this._idSizes = __idSizes;
1021 // Mark as open?
1022 this._open = __open;