1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *
15 *  See the License for the specific language governing permissions and
16 *  limitations under the License.
17 */
18
19/**
20 * @author Ivan G. Popov
21 */
22
23/**
24 * Created on 05.23.2004
25 */
26package org.apache.harmony.jpda.tests.framework.jdwp;
27
28
29import java.net.InetAddress;
30import java.net.ServerSocket;
31import java.net.Socket;
32import java.net.SocketTimeoutException;
33import java.io.InputStream;
34import java.io.InterruptedIOException;
35import java.io.OutputStream;
36import java.io.IOException;
37
38import org.apache.harmony.jpda.tests.framework.jdwp.Packet;
39
40/**
41 * This class provides TransportWrapper for row TCP/IP socket connection.
42 *
43 */
44public class SocketTransportWrapper implements TransportWrapper {
45
46    public static final String HANDSHAKE_STRING = "JDWP-Handshake";
47
48    private ServerSocket serverSocket;
49    private Socket transportSocket;
50    private InputStream input;
51    private OutputStream output;
52
53    /**
54     * Starts listening for connection on given or default address.
55     *
56     * @param address address to listen or null for default address
57     * @return string representation of listening address
58     */
59    public String startListening(String address) throws IOException {
60        String hostName = null;
61        InetAddress hostAddr = null;
62        int port = 0;
63        if (address != null) {
64            String portName = null;
65            int i = address.indexOf(':');
66            if (i < 0) {
67                portName = address;
68            } else {
69                hostName = address.substring(0, i);
70                portName = address.substring(i+1);
71            }
72            try {
73                port = Integer.parseInt(portName);
74            } catch (NumberFormatException e) {
75                throw new IOException("Illegal port number in socket address: " + address);
76            }
77        }
78
79        if (hostName != null) {
80            hostAddr = InetAddress.getByName(hostName);
81            serverSocket = new ServerSocket(port, 0, hostAddr);
82        } else {
83            serverSocket = new ServerSocket(port);
84        }
85
86        // use as workaround for unspecified behaviour of isAnyLocalAddress()
87        InetAddress iAddress = null;
88        if (hostName != null) {
89            iAddress = serverSocket.getInetAddress();
90        } else {
91            iAddress = InetAddress.getLocalHost();
92        }
93
94        address = iAddress.getHostName() + ":" + serverSocket.getLocalPort();
95        return address;
96    }
97
98    /**
99     * Stops listening for connection on current address.
100     */
101    public void stopListening() throws IOException {
102        if (serverSocket != null) {
103            serverSocket.close();
104        }
105    }
106
107    /**
108     * Accepts transport connection for currently listened address and performs handshaking
109     * for specified timeout.
110     *
111     * @param acceptTimeout timeout for accepting in milliseconds
112     * @param handshakeTimeout timeout for handshaking in milliseconds
113     */
114    public void accept(long acceptTimeout, long handshakeTimeout) throws IOException {
115        synchronized (serverSocket) {
116            serverSocket.setSoTimeout((int) acceptTimeout);
117            try {
118                transportSocket = serverSocket.accept();
119            } finally {
120                serverSocket.setSoTimeout(0);
121            }
122        }
123        createStreams();
124        handshake(handshakeTimeout);
125    }
126
127    /**
128     * Attaches transport connection to given address and performs handshaking
129     * for specified timeout.
130     *
131     * @param address address for attaching
132     * @param attachTimeout timeout for attaching in milliseconds
133     * @param handshakeTimeout timeout for handshaking in milliseconds
134     */
135    public void attach(String address, long attachTimeout, long handshakeTimeout) throws IOException {
136        if (address == null) {
137            throw new IOException("Illegal socket address: " + address);
138        }
139
140        String hostName = null;
141        int port = 0;
142        {
143            String portName = null;
144            int i = address.indexOf(':');
145            if (i < 0) {
146                throw new IOException("Illegal socket address: " + address);
147            } else {
148                hostName = address.substring(0, i);
149                portName = address.substring(i+1);
150            }
151            try {
152                port = Integer.parseInt(portName);
153            } catch (NumberFormatException e) {
154                throw new IOException("Illegal port number in socket address: " + address);
155            }
156        }
157
158        long finishTime = System.currentTimeMillis() + attachTimeout;
159        long sleepTime = 4 * 1000; // millesecinds
160        IOException exception = null;
161        try {
162            do {
163                try {
164                    transportSocket = new Socket(hostName, port);
165                    break;
166                } catch (IOException e) {
167                    Thread.sleep(sleepTime);
168                }
169            } while (attachTimeout == 0 || System.currentTimeMillis() < finishTime);
170        } catch (InterruptedException e) {
171            throw new InterruptedIOException("Interruption in attaching to " + address);
172        }
173
174        if (transportSocket == null) {
175            if (exception != null) {
176                throw exception;
177            } else {
178                throw new SocketTimeoutException("Timeout exceeded in attaching to " + address);
179            }
180        }
181
182        createStreams();
183        handshake(handshakeTimeout);
184    }
185
186    /**
187     * Closes transport connection.
188     */
189    public void close() throws IOException {
190        if (input != null) {
191            input.close();
192        }
193        if (output != null) {
194            output.close();
195        }
196
197        if (transportSocket != null && input == null && output == null && !transportSocket.isClosed()) {
198            transportSocket.close();
199        }
200        if (serverSocket != null) {
201            serverSocket.close();
202        }
203    }
204
205    /**
206     * Checks if transport connection is open.
207     *
208     * @return true if transport connection is open
209     */
210    public boolean isOpen() {
211        return (transportSocket != null
212                    && transportSocket.isConnected()
213                    && !transportSocket.isClosed());
214    }
215
216    /**
217     * Reads packet bytes from transport connection.
218     *
219     * @return packet as byte array or null or empty packet if connection was closed
220     */
221    public byte[] readPacket() throws IOException {
222
223        // read packet header
224        byte[] header = new byte[Packet.HEADER_SIZE];
225        int off = 0;
226
227        while (off < Packet.HEADER_SIZE) {
228            try {
229                int bytesRead = input.read(header, off, Packet.HEADER_SIZE - off);
230                if (bytesRead < 0) {
231                    break;
232                }
233                off += bytesRead;
234            } catch (IOException e) {
235                // workaround for "Socket Closed" exception if connection was closed
236                break;
237            }
238        }
239
240        if (off == 0) {
241            return null;
242        }
243        if (off < Packet.HEADER_SIZE) {
244            throw new IOException("Connection closed in reading packet header");
245        }
246
247        // extract packet length
248        int len = Packet.getPacketLength(header);
249        if (len < Packet.HEADER_SIZE) {
250            throw new IOException("Wrong packet size detected: " + len);
251        }
252
253        // allocate packet bytes and store header there
254        byte[] bytes = new byte[len];
255        System.arraycopy(header, 0, bytes, 0, Packet.HEADER_SIZE);
256
257        // read packet data
258        while (off < len) {
259            int bytesRead = input.read(bytes, off, len - off);
260            if (bytesRead < 0) {
261                break;
262            }
263            off += bytesRead;
264        }
265        if (off < len) {
266            throw new IOException("Connection closed in reading packet data");
267        }
268
269        return bytes;
270    }
271
272    /**
273     * Writes packet bytes to transport connection.
274     *
275     * @param packet
276     *            packet as byte array
277     */
278    public void writePacket(byte[] packet) throws IOException {
279        output.write(packet);
280        output.flush();
281    }
282
283    /**
284     * Performs handshaking for given timeout.
285     *
286     * @param handshakeTimeout timeout for handshaking in milliseconds
287     */
288    protected void handshake(long handshakeTimeout) throws IOException {
289        transportSocket.setSoTimeout((int) handshakeTimeout);
290
291        try {
292            output.write(HANDSHAKE_STRING.getBytes());
293            output.flush();
294            int len = HANDSHAKE_STRING.length();
295            byte[] bytes = new byte[len];
296            int off = 0;
297            while (off < len) {
298                int bytesRead = input.read(bytes, off, len - off);
299                if (bytesRead < 0) {
300                    break;
301                }
302                off += bytesRead;
303            }
304            String response = new String(bytes, 0, off);
305            if (!response.equals(HANDSHAKE_STRING)) {
306                throw new IOException("Unexpected handshake response: " + response);
307            }
308        } finally {
309            transportSocket.setSoTimeout(0);
310        }
311    }
312
313    /**
314     * Creates input/output streams for connection socket.
315     */
316    protected void createStreams() throws IOException {
317        input = transportSocket.getInputStream();
318        output = transportSocket.getOutputStream();
319    }
320}
321