1 /*
2  * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 package nsk.share.jpda;
24 
25 import java.io.IOException;
26 import java.net.InetSocketAddress;
27 import java.net.ServerSocket;
28 import nsk.share.*;
29 
30 /*
31  * This class represents communication channel based on TCP/IP sockets.
32  * Usage of this class implies creation of objects of 2 types: server SocketIOPipe object
33  * (this object creates server socket and waits for incoming connection) and client
34  * SocketIOPipe (this object attaches to server).
35  *
36  * Server and client objects should be created using special static methods provided by this class,
37  * for example 'createServerIOPipe(Log log, int port, long timeout)' for server SocketIOPipe
38  * and 'createClientIOPipe(Log log, String host, int port, long timeout)' for client SocketIOPipe.
39  *
40  * When SocketIOPipe is created it can be used to send and receive strings using methods 'readln()' and 'println(String s)'.
41  * TCP/IP connection is established at the first attempt to read or write data.
42  *
43  * For example, if client process should send string 'OK' to the server process which is run
44  * at the host 'SERVER_HOST' following code can be written:
45  *
46  * Server side:
47  *
48  *  // SocketIOPipe creates ServerSocket listening given port
49  *  SocketIOPipe pipe = SocketIOPipe.createServerIOPipe(log, port, timeoutValue);
50  *
51  *  // SocketIOPipe waits connection from client and reads data sent by the client
52  *  String command = pipe.readln();
53  *
54  * Client side:
55  *
56  *  // initialize SocketIOPipe with given values of server host name and port
57  *  SocketIOPipe pipe = SocketIOPipe.createClientIOPipe(log, 'SERVER_HOST', port, timeoutValue);
58  *
59  *  String command = "OK";
60  *  // SocketIOPipe tries to create socket and send command to the server
61  *  pipe.println(command);
62  *
63  */
64 public class SocketIOPipe extends Log.Logger implements Finalizable {
65 
66     public static final int DEFAULT_TIMEOUT_VALUE = 1 * 60 * 1000;
67 
68     public static final String DEFAULT_PIPE_LOG_PREFIX = "SocketIOPipe> ";
69 
70     protected boolean listening;
71 
72     protected String host;
73 
74     protected int port;
75 
76     protected long timeout;
77 
78     protected SocketConnection connection;
79 
80     protected volatile boolean shouldStop;
81 
82     protected Process connectingProcess;
83 
84     protected ServerSocket serverSocket;
85 
86     protected String name;
87 
88     /**
89      * Make general <code>IOPipe</code> object with specified parameters.
90      */
SocketIOPipe(String name, Log log, String logPrefix, String host, int port, long timeout, boolean listening)91     protected SocketIOPipe(String name, Log log, String logPrefix, String host, int port, long timeout, boolean listening) {
92         super(log, logPrefix);
93         this.host = host;
94         this.port = port;
95         this.timeout = timeout;
96         this.listening = listening;
97         this.name = name;
98     }
99 
100     /**
101      * Make general <code>IOPipe</code> object with specified parameters.
102      */
SocketIOPipe(Log log, String logPrefix, String host, int port, long timeout, boolean listening)103     protected SocketIOPipe(Log log, String logPrefix, String host, int port, long timeout, boolean listening) {
104         super(log, logPrefix);
105         this.host = host;
106         this.port = port;
107         this.timeout = timeout;
108         this.listening = listening;
109     }
110 
111     /**
112      *  Create listening SocketIOPipe using given port
113      */
createServerIOPipe(Log log, int port, long timeout)114     public static SocketIOPipe createServerIOPipe(Log log, int port, long timeout) {
115         SocketIOPipe pipe = new SocketIOPipe(log, DEFAULT_PIPE_LOG_PREFIX, null, 0, timeout, true);
116 
117         try {
118             ServerSocket ss = new ServerSocket();
119             if (port == 0) {
120               // Only need SO_REUSEADDR if we're using a fixed port. If we
121               // start seeing EADDRINUSE due to collisions in free ports
122               // then we should retry the bind() a few times.
123               ss.setReuseAddress(false);
124             }
125             ss.bind(new InetSocketAddress(port));
126             pipe.setServerSocket(ss);
127         } catch (IOException e) {
128             e.printStackTrace(log.getOutStream());
129             throw new Failure("Caught IOException while binding for IOPipe connection: \n\t" + e);
130         }
131 
132         return pipe;
133     }
134 
135     /**
136      *  Create listening SocketIOPipe using any free port
137      */
createServerIOPipe(Log log, long timeout)138     public static SocketIOPipe createServerIOPipe(Log log, long timeout) {
139         return createServerIOPipe(log, 0, timeout);
140     }
141 
142     /**
143      *  Create attaching SocketIOPipe using given port and timeout
144      */
createClientIOPipe(Log log, String host, int port, long timeout)145     public static SocketIOPipe createClientIOPipe(Log log, String host, int port, long timeout) {
146         return new SocketIOPipe(log, DEFAULT_PIPE_LOG_PREFIX, host, port, timeout, false);
147     }
148 
149     /**
150      * Return true if <code>IOPipe</code> connection established.
151      */
isConnected()152     public boolean isConnected() {
153         return (connection != null && connection.isConnected());
154     }
155 
156     /**
157      * Returns port number used by SocketIOPipe
158      */
getPort()159     public int getPort() {
160         return port;
161     }
162 
setConnectingProcess(Process connectingProcess)163     protected void setConnectingProcess(Process connectingProcess) {
164         this.connectingProcess = connectingProcess;
165     }
166 
setServerSocket(ServerSocket serverSocket)167     protected void setServerSocket(ServerSocket serverSocket) {
168         this.serverSocket = serverSocket;
169         if (serverSocket != null)
170             port = serverSocket.getLocalPort();
171     }
172 
173     /**
174      * Write (and flush) given <code>line</code> to this
175      * <code>IOPipe</code> cnannel.
176      *
177      * @throws Failure if error occured while sending data
178      */
println(String line)179     public void println(String line) {
180         if (connection == null) {
181             connect();
182         }
183         connection.writeObject(line);
184     }
185 
186     /**
187      * Read a text line from this <code>IOPipe</code> channel,
188      * or return <i>null</i> if EOF reached.
189      *
190      * @throws Failure if error occured while reading data
191      */
readln()192     public String readln() {
193         if (connection == null) {
194             connect();
195         }
196         String line = (String) connection.readObject();
197         return line;
198     }
199 
200     /**
201      * Close this <code>IOPipe</code> connection.
202      */
close()203     public void close() {
204         shouldStop = true;
205         if (connection != null) {
206             connection.close();
207         }
208     }
209 
210     /**
211      * Establish <code>IOPipe</code> connection by attaching or accepting
212      * connection appropriately.
213      */
connect()214     protected void connect() {
215         if (connection != null) {
216             throw new TestBug("IOPipe connection is already established");
217         }
218 
219         if (shouldStop)
220             return;
221 
222         connection = new SocketConnection(this, getName());
223 
224         if (listening) {
225             connection.setConnectingProcess(connectingProcess);
226             if (serverSocket == null) {
227                 connection.bind(port, timeout);
228             } else {
229                 connection.setServerSocket(serverSocket);
230             }
231 
232             if (shouldStop)
233                 return;
234 
235             // wait for connection from remote host
236             connection.accept(timeout);
237 
238         } else {
239             // attach from the debuggee's side
240             connection.continueAttach(host, port, timeout);
241         }
242     }
243 
244     /**
245      * Set ping timeout in milliseconds (0 means don't use ping at all).
246      */
setPingTimeout(long timeout)247     public void setPingTimeout(long timeout) {
248         if (connection == null) {
249             throw new TestBug("Attempt to set ping timeout for not established connection");
250         }
251         connection.setPingTimeout(timeout);
252     }
253 
254     /**
255      * Returns value of current ping timeout in milliseconds (0 means ping is not used).
256      */
getPingTimeout()257     public long getPingTimeout() {
258         if (connection == null) {
259             throw new TestBug("Attempt to get ping timeout for not established connection");
260         }
261         return connection.getPingTimeout();
262     }
263 
264     /**
265      * Perform finalization of the object by invoking close().
266      */
finalize()267     protected void finalize() throws Throwable {
268         close();
269         super.finalize();
270     }
271 
272     /**
273      * Perform finalization of the object at exit by invoking finalize().
274      */
finalizeAtExit()275     public void finalizeAtExit() throws Throwable {
276         finalize();
277     }
278 
279     /**
280      * Field 'pipeCounter' and method 'getNextPipeNumber' are used to construct unique names for SocketIOPipes
281      */
282     private static int pipeCounter;
283 
getNextPipeNumber()284     private synchronized int getNextPipeNumber() {
285         return pipeCounter++;
286     }
287 
288     /**
289      * Construct name for SocketIOPipe if it wasn't specified
290      */
getName()291     private String getName() {
292         if (name == null) {
293             name = "SocketIOPipe-" + getNextPipeNumber();
294         }
295 
296         return name;
297     }
298 }
299