1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
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
;
22 * Represents a packet for JDWP.
24 * This class is mutable and as such must be thread safe.
28 public final class JDWPPacket
31 /** Flag used for replies. */
32 public static final short FLAG_REPLY
=
36 private static final byte _GROW_SIZE
=
39 /** The queue where packets will go when done. */
40 private final Reference
<Deque
<JDWPPacket
>> _queue
;
42 /** The ID of this packet. */
45 /** The flags for this packet. */
48 /** The command set (if not a reply). */
49 volatile int _commandSet
=
52 /* The command (if not a reply). */
53 volatile int _command
=
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
;
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.
85 JDWPPacket(Deque
<JDWPPacket
> __queue
)
86 throws NullPointerException
89 throw new NullPointerException("NARG");
91 this._queue
= new WeakReference
<>(__queue
);
98 @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
105 // Ignore if closed already (prevent double queue add)
112 // Return to the queue
113 Deque
<JDWPPacket
> queue
= this._queue
.get();
123 * Returns the command Id.
125 * @return The command Id.
132 // Ensure it is valid
134 this.__checkType(false);
136 return this._command
;
141 * Returns the command set for the packet.
143 * @return The command set.
146 public JDWPCommandSet
commandSet()
150 // Ensure it is valid
152 this.__checkType(false);
155 return JDWPCommandSet
.of(this._commandSet
);
160 * Returns the raw command set id.
162 * @return The command set id.
165 public int commandSetId()
169 // Ensure it is valid
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.
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
198 JDWPErrorType errorCode
;
202 synchronized (__packet
)
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
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
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
236 this._data
= data
.clone();
244 * Returns the error for this packet.
246 * @return The packet's error, if there is one.
249 public JDWPErrorType
error()
253 // Ensure this is open
256 return this._errorCode
;
261 * Does this packet have any error?
263 * @return If this has an error.
266 public boolean hasError()
270 // Ensure this is open
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
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.
287 public boolean hasError(JDWPErrorType __error
)
288 throws NullPointerException
291 throw new NullPointerException("NARG");
295 // Ensure this is open
298 return this._errorCode
== __error
;
303 * Returns the packet ID.
305 * @return The packet it.
312 // Ensure this is open
320 * Returns the current ID sizes in use.
322 * @return The used ID sizes.
325 public JDWPIdSizes
idSizes()
331 return this._idSizes
;
336 * Is this a reply packet?
338 * @return If this is a reply.
341 public boolean isReply()
345 // Ensure this is open
348 return (this._flags
& JDWPPacket
.FLAG_REPLY
) != 0;
353 * Returns the length of this packet.
355 * @return The packet length.
362 // Ensure this is open
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.
376 public boolean readBoolean()
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.
389 public final byte readByte()
394 // Ensure this is open
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;
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.
419 public final int readId()
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
432 * @throws NullPointerException On null arguments.
435 public JDWPId
readId(JDWPIdKind __kind
)
436 throws JDWPException
, NullPointerException
439 throw new NullPointerException("NARG");
443 // Ensure this is open
446 /* {@squirreljme.error AG0z ID Sizes not currently known.} */
447 JDWPIdSizes idSizes
= this._idSizes
;
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.
464 public final int readInt()
469 // Ensure this is open
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.
487 public long readLong()
492 // Ensure this is open
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.
514 public final String
readString()
519 // Ensure this is open
523 int len
= this.readInt();
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.
552 public final long readVariable(int __width
)
553 throws IllegalArgumentException
, JDWPException
556 throw new IllegalArgumentException("NEGV");
562 // Ensure this is open
566 for (int i
= 0; i
< __width
; i
++)
568 // Shift up and read in
570 result
|= (this.readByte() & 0xFF);
578 * Returns the byte array of the packet.
580 * @return The packet data as a byte array.
583 public byte[] toByteArray()
587 // Ensure this is open
590 // If there is no data, then return a blank array
591 byte[] data
= this._data
;
595 // Otherwise make a copy of it
596 int len
= this._length
;
597 byte[] result
= new byte[len
];
598 System
.arraycopy(data
, 0,
611 public final String
toString()
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
++)
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
))),
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.
664 public void write(byte[] __b
, int __o
, int __l
)
665 throws IndexOutOfBoundsException
, JDWPException
, NullPointerException
668 throw new NullPointerException("NARG");
669 if (__o
< 0 || __l
< 0 || (__o
+ __l
) < 0 || (__o
+ __l
) > __b
.length
)
670 throw new IndexOutOfBoundsException("IOOB");
674 // Must be an open packet
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.
689 public void writeBoolean(boolean __b
)
692 this.writeByte((__b ?
1 : 0));
696 * Writes the given byte.
698 * @param __v The value to write.
701 public void writeByte(int __v
)
705 // Must be an open packet
708 // Where this will be going
709 int length
= this._length
;
710 byte[] data
= this._data
;
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
)));
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.
731 public void writeId(int __v
)
738 * Writes an integer to the output.
740 * @param __v The value to write.
741 * @throws JDWPException If it could not be written.
744 public void writeInt(int __v
)
749 // Must be an open packet
753 this.writeByte(__v
>> 24);
754 this.writeByte(__v
>> 16);
755 this.writeByte(__v
>> 8);
761 * Writes a long to the output.
763 * @param __v The value to write.
764 * @throws JDWPException If it could not be written.
767 public void writeLong(long __v
)
772 // Must be an open packet
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.
794 public void writeShort(int __v
)
799 // Must be an open packet
803 this.writeByte(__v
>> 8);
809 * Writes the string to the output.
811 * @param __string The string to write.
812 * @throws JDWPException If it could not be written.
815 public void writeString(String __string
)
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
);
833 this.writeInt(bytes
.length
);
835 // Write all the bytes
842 * Writes to the output.
844 * @param __out The output.
845 * @throws IOException On write errors.
846 * @throws NullPointerException On null arguments.
849 public void writeTo(DataOutputStream __out
)
850 throws IOException
, NullPointerException
853 throw new NullPointerException("NARG");
857 // Must be an open packet
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
);
867 __out
.writeShort(this._errorCode
.id
);
872 __out
.writeByte(this._commandSet
);
873 __out
.writeByte(this._command
);
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.
888 public void writeVoid()
893 // Must be an open packet
901 * Checks if the packet is open.
903 * @throws IllegalStateException If it is not open.
907 throws IllegalStateException
909 /* {@squirreljme.error AG0b Packet not 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.
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.
938 void __load(byte[] __header
, byte[] __data
, int __dataLen
)
939 throws NullPointerException
943 /* {@squirreljme.error AG0a Packet is already 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,
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);
963 this._flags
= ((flags
= __header
[8]) & 0xFF);
966 if ((flags
& JDWPPacket
.FLAG_REPLY
) != 0)
968 // These are not used
969 this._commandSet
= -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
);
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;
997 * Resets and opens the packet.
999 * @param __open Should this be opened?
1000 * @param __idSizes ID sizes.
1003 final void __resetAndOpen(boolean __open
, JDWPIdSizes __idSizes
)
1007 /* {@squirreljme.error AG05 Cannot reset an open packet.} */
1009 throw new JDWPException("AG05");
1013 this._commandSet
= -1;
1015 this._errorCode
= null;
1016 this._rawErrorCode
= -1;
1019 this._idSizes
= __idSizes
;
1022 this._open
= __open
;