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 cc
.squirreljme
.runtime
.cldc
.debug
.Debugging
;
13 import java
.io
.Closeable
;
14 import java
.io
.DataInputStream
;
15 import java
.io
.DataOutputStream
;
16 import java
.io
.EOFException
;
17 import java
.io
.IOException
;
18 import java
.io
.InputStream
;
19 import java
.io
.InterruptedIOException
;
20 import java
.io
.OutputStream
;
21 import java
.util
.Arrays
;
22 import java
.util
.Deque
;
23 import java
.util
.LinkedList
;
26 * This handles the input and output communication of JDWPa.
30 public final class JDWPCommLink
33 /** Should debugging be enabled? */
34 static final boolean _DEBUG
=
35 Boolean
.getBoolean("cc.squirreljme.jdwp.debug");
37 /** Handshake sequence, sent by both sides. */
38 private static final byte[] _HANDSHAKE_SEQUENCE
=
39 {'J', 'D', 'W', 'P', '-', 'H', 'a', 'n', 'd', 's', 'h', 'a', 'k', 'e'};
41 /** Initial data buffer length. */
42 private static final int _INIT_DATA_LEN
=
45 /** The size of the packet header. */
46 static final int _HEADER_SIZE
=
49 /** The input communication stream. */
50 protected final DataInputStream in
;
52 /** The output communication stream. */
53 protected final DataOutputStream out
;
55 /** The direction of the channel. */
56 protected final JDWPCommLinkDirection direction
;
58 /** The queue of packets which are freed, they will go back here. */
59 private final Deque
<JDWPPacket
> _freePackets
=
62 /** The header bytes. */
63 private final byte[] _header
=
64 new byte[JDWPCommLink
._HEADER_SIZE
];
66 /** The monitor used for the output object. */
67 private final Object _outMonitor
=
70 /** Identifier sizes, needed for reading IDs. */
71 private volatile JDWPIdSizes _idSizes
;
73 /** Read position for the header. */
74 private volatile int _headerAt
;
77 private volatile byte[] _data
=
78 new byte[JDWPCommLink
._INIT_DATA_LEN
];
80 /** Read position for read data. */
81 private volatile int _dataAt
;
83 /** Length of read data. */
84 private volatile int _dataLen
=
87 /** Did we do our handshake? */
88 private volatile boolean _didHandshake
;
90 /** Are we in shutdown? */
91 volatile boolean _shutdown
;
93 /** Next packet ID number. */
94 private volatile int _nextId
;
97 * Initializes the communication link.
99 * @param __in The input stream to read from.
100 * @param __out The output stream to write to.
101 * @throws NullPointerException On null arguments.
104 public JDWPCommLink(InputStream __in
, OutputStream __out
)
105 throws NullPointerException
107 this(__in
, __out
, JDWPCommLinkDirection
.CLIENT_TO_DEBUGGER
);
111 * Initializes the communication link.
113 * @param __in The input stream to read from.
114 * @param __out The output stream to write to.
115 * @param __direction The direction of communication.
116 * @throws NullPointerException On null arguments.
119 public JDWPCommLink(InputStream __in
, OutputStream __out
,
120 JDWPCommLinkDirection __direction
)
121 throws NullPointerException
123 if (__in
== null || __out
== null || __direction
== null)
124 throw new NullPointerException("NARG");
126 this.in
= new DataInputStream(__in
);
127 this.out
= new DataOutputStream(__out
);
128 this.direction
= __direction
;
132 * Are the ID sizes now known?
134 * @return If the sizes are known.
137 public boolean areSizesKnown()
141 return this._idSizes
!= null;
153 IOException fail
= null;
155 // Enter shut-down mode
158 this._shutdown
= true;
166 catch (IOException e
)
176 catch (IOException e
)
181 fail
.addSuppressed(e
);
184 /* {@squirreljme.error AG09 Could not close communication link.} */
186 throw new JDWPException("AG09", fail
);
190 * Gets a blank packet.
192 * @return A blank packet.
195 public JDWPPacket
getPacket()
197 return this.__getPacket(true);
201 * Returns the ID sizes of the communication link.
203 * @return The link's ID sizes.
206 public JDWPIdSizes
idSizes()
210 return this._idSizes
;
215 * Is the debug link shutdown?
217 * @return If this is shutdown.
220 public boolean isShutdown()
224 return this._shutdown
;
229 * The next ID number.
231 * @return Returns a new ID number.
234 public final int nextId()
238 return ++this._nextId
;
243 * Polls for the next event, if there is any.
245 * @throws JDWPException If there is an issue with the connection.
246 * @return A packet or {@code null} if there are none.
249 @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
250 public JDWPPacket
poll()
253 // If the handshake did not happen, do it now
256 if (!this._didHandshake
)
260 // Used to read in the header as needed
261 byte[] header
= this._header
;
262 int headerAt
= this._headerAt
;
264 // Data and whatever length
265 byte[] data
= this._data
;
266 int dataLen
= this._dataLen
;
267 int dataAt
= this._dataAt
;
269 // Constant reading in loop
270 for (InputStream in
= this.in
;;)
277 // Still reading in the header?
278 int headerLeft
= JDWPCommLink
._HEADER_SIZE
- headerAt
;
281 int rc
= in
.read(header
, headerAt
, headerLeft
);
286 /* {@squirreljme.error AG07 Short header read.} */
288 throw new EOFException("AG07");
290 this._shutdown
= true;
298 // Still need the header to be read?
302 // Do not know the data length yet?
305 // Figure out the data length, note that it includes our
307 dataLen
= Math
.max((((header
[0] & 0xFF) << 24) |
308 ((header
[1] & 0xFF) << 16) |
309 ((header
[2] & 0xFF) << 8) |
310 (header
[3] & 0xFF)) - 11, 0);
312 // If our buffer is too small, grow it just enough to fit
313 if (dataLen
> data
.length
)
314 data
= new byte[dataLen
];
317 // Read in any associated data
318 int dataLeft
= dataLen
- dataAt
;
319 if (dataLen
>= 0 && dataLeft
> 0)
321 int rc
= in
.read(data
, dataAt
, dataLeft
);
326 /* {@squirreljme.error AG08 Short header read.} */
328 throw new EOFException("AG08");
330 this._shutdown
= true;
338 // Still need more data to be read
339 if (dataLen
>= 0 && dataLeft
> 0)
342 // Setup a fresh packet
343 JDWPPacket packet
= this.__getPacket(false);
344 packet
.__load(header
, data
, dataLen
);
346 // Reset state for the next run
351 // This packet is ready so use it now
355 // If we get interrupted, we just either shutdown or continue
356 catch (InterruptedIOException ignored
)
361 /* {@squirreljme.error AG06 Read error.} */
362 catch (IOException e
)
365 this._shutdown
= true;
368 throw new JDWPException("AG06", e
);
371 // Store resultant state
374 this._headerAt
= headerAt
;
376 this._dataLen
= dataLen
;
377 this._dataAt
= dataAt
;
382 * Creates a reply packet.
384 * @param __id The raw packet ID that is being responded to.
385 * @param __error The error to use for the packet.
386 * @return The resultant reply packet.
387 * @throws NullPointerException On null arguments.
390 public JDWPPacket
reply(int __id
, JDWPErrorType __error
)
391 throws NullPointerException
394 throw new NullPointerException("NARG");
396 JDWPPacket rv
= this.__getPacket(true);
399 rv
._errorCode
= __error
;
400 rv
._rawErrorCode
= __error
.id
;
401 rv
._flags
= JDWPPacket
.FLAG_REPLY
;
407 * Creates a reply packet.
409 * @param __packet The packet to reply to.
410 * @param __error The error to use for the packet.
411 * @return The resultant reply packet.
412 * @throws NullPointerException On null arguments.
415 public JDWPPacket
reply(JDWPPacket __packet
, JDWPErrorType __error
)
416 throws NullPointerException
418 if (__packet
== null || __error
== null)
419 throw new NullPointerException("NARG");
421 return this.reply(__packet
.id(), __error
);
425 * Creates a packet for a request.
427 * @param __commandSet The command set to use.
428 * @param __command The command to use.
429 * @return The newly created packet.
430 * @throws NullPointerException On null arguments.
433 public JDWPPacket
request(JDWPCommandSet __commandSet
,
434 JDWPCommand __command
)
435 throws NullPointerException
437 if (__commandSet
== null || __command
== null)
438 throw new NullPointerException("NARG");
441 return this.request(__commandSet
.id
, __command
.debuggerId());
445 * Creates a packet for a request.
447 * @param __commandSet The command set to use.
448 * @param __command The command to use.
449 * @return The newly created packet.
450 * @throws NullPointerException On null arguments.
453 public JDWPPacket
request(int __commandSet
, int __command
)
455 JDWPPacket rv
= this.__getPacket(true);
457 // Use the next ID for this packet
458 rv
._id
= this.nextId();
460 // Packet type information
461 rv
._commandSet
= __commandSet
;
462 rv
._command
= __command
;
465 // There is no error technically
466 rv
._errorCode
= JDWPErrorType
.NO_ERROR
;
467 rv
._rawErrorCode
= 0;
473 * Sends the packet to the remote end.
475 * @param __packet The packet to send.
476 * @throws JDWPException If the packet could not be sent.
477 * @throws NullPointerException On null arguments.
480 public void send(JDWPPacket __packet
)
481 throws JDWPException
, NullPointerException
483 if (__packet
== null)
484 throw new NullPointerException("NARG");
486 // If the handshake did not happen, do it now
489 if (!this._didHandshake
)
494 if (JDWPCommLink
._DEBUG
)
495 Debugging
.debugNote("JDWP: -> %s", __packet
);
497 // Write to the destination
500 // Different threads could be sending different replies, so if
501 // these get mashed together then that would be a very bad thing
502 // But we only need to protect the output
503 synchronized (this._outMonitor
)
505 // Write then make sure it is instantly available
506 __packet
.writeTo(this.out
);
511 /* {@squirreljme.error AG01 Could not send the packet. (The packet)} */
512 catch (IOException e
)
514 throw new JDWPException("AG01 " + __packet
, e
);
521 * @param __idSizes The ID sizes to set.
522 * @throws NullPointerException On null arguments.
525 public final void setIdSizes(JDWPIdSizes __idSizes
)
526 throws NullPointerException
528 if (__idSizes
== null)
529 throw new NullPointerException("NARG");
533 this._idSizes
= __idSizes
;
538 * Returns a fresh packet.
540 * @param __open Should this be opened?
541 * @return A packet that is fresh, this may be recycled from a previous
542 * packet or taken from another.
545 @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
546 JDWPPacket
__getPacket(boolean __open
)
548 Deque
<JDWPPacket
> freePackets
= this._freePackets
;
549 synchronized (freePackets
)
551 // If there are no free packets then make a new one
553 if (freePackets
.isEmpty())
554 rv
= new JDWPPacket(freePackets
);
556 // Grab the next free one
558 rv
= freePackets
.remove();
560 // Clear it for the next run
561 rv
.__resetAndOpen(__open
, this._idSizes
);
568 * Performs the handshake for JDWP.
570 * @throws JDWPException If the handshake could not happen.
573 private void __handshake()
579 if (JDWPCommLink
._DEBUG
)
580 Debugging
.debugNote("JDWP: Handshake.");
582 // The debugger sends the handshake sequence first, so as a client
583 // we read from the remote end
584 if (this.direction
== JDWPCommLinkDirection
.CLIENT_TO_DEBUGGER
)
586 this.__handshakeRead();
587 this.__handshakeWrite();
590 // Otherwise we are the debugger, so we write our handshake first
593 this.__handshakeWrite();
594 this.__handshakeRead();
597 // We did the handshake
598 this._didHandshake
= true;
601 if (JDWPCommLink
._DEBUG
)
602 Debugging
.debugNote("JDWP: Hands shaken at a distance.");
604 catch (IOException e
)
606 /* {@squirreljme.error AG04 Failed to handshake.} */
607 throw new JDWPException("AG04", e
);
612 * Reads the handshake.
614 * @throws IOException On read errors.
617 private void __handshakeRead()
620 // The debugger sends the handshake sequence first
621 int seqLen
= JDWPCommLink
._HANDSHAKE_SEQUENCE
.length
;
622 byte[] debuggerShake
= new byte[seqLen
];
624 // Read in the handshake
625 for (int i
= 0; i
< seqLen
; i
++)
627 int read
= this.in
.read();
629 /* {@squirreljme.error AG02 EOF reading handshake.} */
631 throw new JDWPException("AG02");
633 debuggerShake
[i
] = (byte)read
;
636 /* {@squirreljme.error AG03 Debugger sent an invalid handshake.} */
637 if (!Arrays
.equals(debuggerShake
, JDWPCommLink
._HANDSHAKE_SEQUENCE
))
638 throw new JDWPException("AG03");
642 * Writes the handshake.
644 * @throws IOException On write errors.
647 private void __handshakeWrite()
650 // We then reply with our own handshake
651 this.out
.write(JDWPCommLink
._HANDSHAKE_SEQUENCE
);