1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.newsclub.net.unix.rmi;
19
20 import java.io.Closeable;
21 import java.io.DataInputStream;
22 import java.io.DataOutputStream;
23 import java.io.Externalizable;
24 import java.io.FileDescriptor;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.ObjectInput;
28 import java.io.ObjectOutput;
29 import java.io.OutputStream;
30 import java.net.SocketException;
31 import java.util.Objects;
32 import java.util.concurrent.ScheduledFuture;
33 import java.util.concurrent.ThreadLocalRandom;
34 import java.util.concurrent.TimeUnit;
35 import java.util.concurrent.atomic.AtomicReference;
36
37 import org.newsclub.net.unix.AFServerSocket;
38 import org.newsclub.net.unix.AFSocket;
39 import org.newsclub.net.unix.AFSocketAddress;
40 import org.newsclub.net.unix.AFUNIXSocket;
41 import org.newsclub.net.unix.FileDescriptorAccess;
42 import org.newsclub.net.unix.server.AFSocketServer;
43
44 import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
45
46
47
48
49
50
51
52
53
54 public abstract class RemoteFileDescriptorBase<T> implements Externalizable, Closeable,
55 FileDescriptorAccess {
56 private static final String PROP_SERVER_TIMEOUT =
57 "org.newsclub.net.unix.rmi.rfd-server-timeout-millis";
58 private static final String PROP_CONNECT_TIMEOUT =
59 "org.newsclub.net.unix.rmi.rfd-connect-timeout-millis";
60
61 private static final int SERVER_TIMEOUT =
62 parseTimeoutMillis(System.getProperty(PROP_SERVER_TIMEOUT, "10000"), false);
63 private static final int CONNECT_TIMEOUT =
64 parseTimeoutMillis(System.getProperty(PROP_CONNECT_TIMEOUT, "1000"), true);
65
66 static final int MAGIC_VALUE_MASK = 0x00FD0000;
67 static final int BIT_READABLE = 1 << 0;
68 static final int BIT_WRITABLE = 1 << 1;
69
70 private static final long serialVersionUID = 1L;
71
72 private final AtomicReference<DataInputStream> remoteConnection = new AtomicReference<>();
73 private final AtomicReference<AFUNIXSocket> remoteServer = new AtomicReference<>();
74
75
76
77
78
79
80
81
82 protected final transient AtomicReference<T> resource = new AtomicReference<>();
83
84 private int magicValue;
85 private transient FileDescriptor fd;
86 private AFUNIXRMISocketFactory socketFactory;
87
88
89
90
91
92
93 public RemoteFileDescriptorBase() {
94 }
95
96 RemoteFileDescriptorBase(AFUNIXRMISocketFactory socketFactory, T stream, FileDescriptor fd,
97 int magicValue) {
98 this.resource.set(stream);
99 this.socketFactory = socketFactory;
100 this.fd = fd;
101 this.magicValue = magicValue;
102 }
103
104 @Override
105 @SuppressWarnings("PMD.ExceptionAsFlowControl")
106 @SuppressFBWarnings("PREDICTABLE_RANDOM")
107 public final synchronized void writeExternal(ObjectOutput objOut) throws IOException {
108 if (fd == null || !fd.valid()) {
109 throw new IOException("No or invalid file descriptor");
110 }
111 final int randomValue = ThreadLocalRandom.current().nextInt();
112
113 int localPort;
114 try {
115 AFServerSocket<?> serverSocket = (AFServerSocket<?>) socketFactory.createServerSocket(0);
116 localPort = serverSocket.getLocalPort();
117
118 AFSocketServer<?> server = new AFSocketServer<AFSocketAddress>(serverSocket) {
119 @Override
120 protected void doServeSocket(AFSocket<?> socket) throws IOException {
121 AFUNIXSocket unixSocket = (AFUNIXSocket) socket;
122 try (DataOutputStream out = new DataOutputStream(socket.getOutputStream());
123 InputStream in = socket.getInputStream();) {
124 unixSocket.setOutboundFileDescriptors(fd);
125 out.writeInt(randomValue);
126
127 try {
128 socket.setSoTimeout(CONNECT_TIMEOUT);
129 } catch (IOException e) {
130
131 }
132
133
134 int response = in.read();
135 if (response != 1) {
136 if (response == -1) {
137
138 } else {
139 throw new IOException("Unexpected response: " + response);
140 }
141 }
142 } finally {
143 stop();
144 }
145 }
146
147 @Override
148 protected void onServerStopped(AFServerSocket<?> socket) {
149 try {
150 serverSocket.close();
151 } catch (IOException e) {
152
153 }
154 }
155
156 };
157 @SuppressWarnings("unused")
158 ScheduledFuture<IOException> unused = server.startThenStopAfter(SERVER_TIMEOUT,
159 TimeUnit.MILLISECONDS);
160 } catch (IOException e) {
161 objOut.writeObject(e);
162 throw e;
163 }
164
165 objOut.writeObject(socketFactory);
166 objOut.writeInt(magicValue);
167 objOut.writeInt(randomValue);
168 objOut.writeInt(localPort);
169 objOut.flush();
170 }
171
172 @SuppressWarnings("resource")
173 @SuppressFBWarnings("OBJECT_DESERIALIZATION")
174 @Override
175 public final synchronized void readExternal(ObjectInput objIn) throws IOException,
176 ClassNotFoundException {
177 DataInputStream in1 = remoteConnection.getAndSet(null);
178 if (in1 != null) {
179 in1.close();
180 }
181
182 Object obj = objIn.readObject();
183 if (obj instanceof IOException) {
184 IOException e = new IOException("Could not read RemoteFileDescriptor");
185 e.addSuppressed((IOException) obj);
186 throw e;
187 }
188 this.socketFactory = (AFUNIXRMISocketFactory) obj;
189
190
191
192 this.magicValue = objIn.readInt();
193 if ((magicValue & MAGIC_VALUE_MASK) != MAGIC_VALUE_MASK) {
194 throw new IOException("Unexpected magic value: " + Integer.toHexString(magicValue));
195 }
196 final int randomValue = objIn.readInt();
197 int port = objIn.readInt();
198
199 AFUNIXSocket socket = (AFUNIXSocket) socketFactory.createSocket("", port);
200 if (remoteServer.getAndSet(socket) != null) {
201 throw new IllegalStateException("remoteServer was not null");
202 }
203
204 try {
205 socket.setSoTimeout(CONNECT_TIMEOUT);
206 } catch (IOException e) {
207
208 }
209
210 in1 = new DataInputStream(socket.getInputStream());
211 this.remoteConnection.set(in1);
212 socket.ensureAncillaryReceiveBufferSize(128);
213
214 int random = in1.readInt();
215
216 if (random != randomValue) {
217 throw new IOException("Invalid socket connection");
218 }
219 FileDescriptor[] descriptors = socket.getReceivedFileDescriptors();
220
221 if (descriptors == null || descriptors.length != 1) {
222 throw new IOException("Did not receive exactly 1 file descriptor but " + (descriptors == null
223 ? 0 : descriptors.length));
224 }
225
226 this.fd = descriptors[0];
227 }
228
229
230
231
232
233
234
235
236
237 @Override
238 @SuppressFBWarnings("EI_EXPOSE_REP")
239 public final FileDescriptor getFileDescriptor() {
240 return fd;
241 }
242
243
244
245
246
247
248
249
250
251
252 protected final int getMagicValue() {
253 return magicValue;
254 }
255
256 @SuppressWarnings("resource")
257 @Override
258 public void close() throws IOException {
259 DataInputStream in1 = remoteConnection.getAndSet(null);
260 if (in1 != null) {
261 try {
262 in1.close();
263 } catch (SocketException e) {
264
265 }
266 }
267
268 AFUNIXSocket remoteSocket = remoteServer.getAndSet(null);
269 if (remoteSocket != null) {
270 try (OutputStream out = remoteSocket.getOutputStream()) {
271 out.write(1);
272 } catch (SocketException e) {
273
274 }
275 remoteSocket.close();
276 }
277
278 @SuppressWarnings("null")
279 T c = this.resource.getAndSet(null);
280 if (c != null) {
281 if (c instanceof Closeable) {
282 ((Closeable) c).close();
283 }
284 }
285 }
286
287 private static int parseTimeoutMillis(String s, boolean zeroPermitted) {
288 Objects.requireNonNull(s);
289 int duration;
290 try {
291 duration = Integer.parseInt(s);
292 } catch (Exception e) {
293 throw new IllegalArgumentException("Illegal timeout value: " + s, e);
294 }
295 if (duration < 0 || (duration == 0 && !zeroPermitted)) {
296 throw new IllegalArgumentException("Illegal timeout value: " + s);
297 }
298 return duration;
299 }
300 }