Correct double read of thread information on VM_START, this took awhile to find lol.
[SquirrelJME.git] / modules / debug-jdwp / src / main / java / cc / squirreljme / jdwp / JDWPCommLink.java
blobcd9a50487fe4e48de6bc430a1c6ec47e2dd336a2
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 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;
25 /**
26 * This handles the input and output communication of JDWPa.
28 * @since 2021/03/09
30 public final class JDWPCommLink
31 implements Closeable
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 =
43 1024;
45 /** The size of the packet header. */
46 static final int _HEADER_SIZE =
47 11;
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 =
60 new LinkedList<>();
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 =
68 new Object();
70 /** Identifier sizes, needed for reading IDs. */
71 private volatile JDWPIdSizes _idSizes;
73 /** Read position for the header. */
74 private volatile int _headerAt;
76 /** The data. */
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 =
85 -1;
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;
96 /**
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.
102 * @since 2021/03/08
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.
117 * @since 2021/03/08
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.
135 * @since 2024/01/23
137 public boolean areSizesKnown()
139 synchronized (this)
141 return this._idSizes != null;
146 * {@inheritDoc}
147 * @since 2021/03/08
149 @Override
150 public void close()
151 throws JDWPException
153 IOException fail = null;
155 // Enter shut-down mode
156 synchronized (this)
158 this._shutdown = true;
161 // Close the input
164 this.in.close();
166 catch (IOException e)
168 fail = e;
171 // And the output
174 this.out.close();
176 catch (IOException e)
178 if (fail == null)
179 fail = e;
180 else
181 fail.addSuppressed(e);
184 /* {@squirreljme.error AG09 Could not close communication link.} */
185 if (fail != null)
186 throw new JDWPException("AG09", fail);
190 * Gets a blank packet.
192 * @return A blank packet.
193 * @since 2024/01/22
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.
204 * @since 2024/01/23
206 public JDWPIdSizes idSizes()
208 synchronized (this)
210 return this._idSizes;
215 * Is the debug link shutdown?
217 * @return If this is shutdown.
218 * @since 2024/01/19
220 public boolean isShutdown()
222 synchronized (this)
224 return this._shutdown;
229 * The next ID number.
231 * @return Returns a new ID number.
232 * @since 2021/03/13
234 public final int nextId()
236 synchronized (this)
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.
247 * @since 2021/03/10
249 @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
250 public JDWPPacket poll()
251 throws JDWPException
253 // If the handshake did not happen, do it now
254 synchronized (this)
256 if (!this._didHandshake)
257 this.__handshake();
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;;)
273 // Shutting down?
274 if (this._shutdown)
275 return null;
277 // Still reading in the header?
278 int headerLeft = JDWPCommLink._HEADER_SIZE - headerAt;
279 if (headerLeft > 0)
281 int rc = in.read(header, headerAt, headerLeft);
283 // EOF?
284 if (rc < 0)
286 /* {@squirreljme.error AG07 Short header read.} */
287 if (headerAt > 0)
288 throw new EOFException("AG07");
290 this._shutdown = true;
291 return null;
294 headerLeft -= rc;
295 headerAt += rc;
298 // Still need the header to be read?
299 if (headerLeft > 0)
300 continue;
302 // Do not know the data length yet?
303 if (dataLen < 0)
305 // Figure out the data length, note that it includes our
306 // own header!!
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);
323 // EOF?
324 if (rc < 0)
326 /* {@squirreljme.error AG08 Short header read.} */
327 if (dataAt > 0)
328 throw new EOFException("AG08");
330 this._shutdown = true;
331 return null;
334 dataLeft -= rc;
335 dataAt += rc;
338 // Still need more data to be read
339 if (dataLen >= 0 && dataLeft > 0)
340 continue;
342 // Setup a fresh packet
343 JDWPPacket packet = this.__getPacket(false);
344 packet.__load(header, data, dataLen);
346 // Reset state for the next run
347 headerAt = 0;
348 dataAt = 0;
349 dataLen = -1;
351 // This packet is ready so use it now
352 return packet;
355 // If we get interrupted, we just either shutdown or continue
356 catch (InterruptedIOException ignored)
358 return null;
361 /* {@squirreljme.error AG06 Read error.} */
362 catch (IOException e)
364 // Shutdown the link
365 this._shutdown = true;
367 // Fail here
368 throw new JDWPException("AG06", e);
371 // Store resultant state
372 finally
374 this._headerAt = headerAt;
375 this._data = data;
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.
388 * @since 2024/01/19
390 public JDWPPacket reply(int __id, JDWPErrorType __error)
391 throws NullPointerException
393 if (__error == null)
394 throw new NullPointerException("NARG");
396 JDWPPacket rv = this.__getPacket(true);
398 rv._id = __id;
399 rv._errorCode = __error;
400 rv._rawErrorCode = __error.id;
401 rv._flags = JDWPPacket.FLAG_REPLY;
403 return rv;
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.
413 * @since 2024/01/19
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.
431 * @since 2024/01/19
433 public JDWPPacket request(JDWPCommandSet __commandSet,
434 JDWPCommand __command)
435 throws NullPointerException
437 if (__commandSet == null || __command == null)
438 throw new NullPointerException("NARG");
440 // Forward
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.
451 * @since 2024/01/19
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;
463 rv._flags = 0;
465 // There is no error technically
466 rv._errorCode = JDWPErrorType.NO_ERROR;
467 rv._rawErrorCode = 0;
469 return rv;
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.
478 * @since 2021/03/12
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
487 synchronized (this)
489 if (!this._didHandshake)
490 this.__handshake();
493 // Debug
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);
507 this.out.flush();
511 /* {@squirreljme.error AG01 Could not send the packet. (The packet)} */
512 catch (IOException e)
514 throw new JDWPException("AG01 " + __packet, e);
519 * Sets the ID sizes.
521 * @param __idSizes The ID sizes to set.
522 * @throws NullPointerException On null arguments.
523 * @since 2024/01/22
525 public final void setIdSizes(JDWPIdSizes __idSizes)
526 throws NullPointerException
528 if (__idSizes == null)
529 throw new NullPointerException("NARG");
531 synchronized (this)
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.
543 * @since 2021/03/10
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
552 JDWPPacket rv;
553 if (freePackets.isEmpty())
554 rv = new JDWPPacket(freePackets);
556 // Grab the next free one
557 else
558 rv = freePackets.remove();
560 // Clear it for the next run
561 rv.__resetAndOpen(__open, this._idSizes);
563 return rv;
568 * Performs the handshake for JDWP.
570 * @throws JDWPException If the handshake could not happen.
571 * @since 2021/03/08
573 private void __handshake()
574 throws JDWPException
578 // Debug
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
591 else
593 this.__handshakeWrite();
594 this.__handshakeRead();
597 // We did the handshake
598 this._didHandshake = true;
600 // Debug
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.
615 * @since 2024/01/19
617 private void __handshakeRead()
618 throws IOException
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.} */
630 if (read < 0)
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.
645 * @since 2024/01/19
647 private void __handshakeWrite()
648 throws IOException
650 // We then reply with our own handshake
651 this.out.write(JDWPCommLink._HANDSHAKE_SEQUENCE);
652 this.out.flush();