-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLocalSocketManager.java
More file actions
450 lines (376 loc) · 18.7 KB
/
Copy pathLocalSocketManager.java
File metadata and controls
450 lines (376 loc) · 18.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
package com.termux.shared.net.socket.local;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.termux.shared.errors.Error;
import com.termux.shared.jni.models.JniResult;
import com.termux.shared.logger.Logger;
/**
* Manager for an AF_UNIX/SOCK_STREAM local server.
*
* Usage:
* 1. Implement the {@link ILocalSocketManager} that will receive call backs from the server including
* when client connects via {@link ILocalSocketManager#onClientAccepted(LocalSocketManager, LocalClientSocket)}.
* Optionally extend the {@link LocalSocketManagerClientBase} class that provides base implementation.
* 2. Create a {@link LocalSocketRunConfig} instance with the run config of the server.
* 3. Create a {@link LocalSocketManager} instance and call {@link #start()}.
* 4. Stop server if needed with a call to {@link #stop()}.
*/
public class LocalSocketManager {
public static final String LOG_TAG = "LocalSocketManager";
/** The native JNI local socket library. */
protected static String LOCAL_SOCKET_LIBRARY = "local-socket";
/** Whether {@link #LOCAL_SOCKET_LIBRARY} has been loaded or not. */
protected static boolean localSocketLibraryLoaded;
/** The {@link Context} that may needed for various operations. */
@NonNull protected final Context mContext;
/** The {@link LocalSocketRunConfig} containing run config for the {@link LocalSocketManager}. */
@NonNull protected final LocalSocketRunConfig mLocalSocketRunConfig;
/** The {@link LocalServerSocket} for the {@link LocalSocketManager}. */
@NonNull protected final LocalServerSocket mServerSocket;
/** The {@link ILocalSocketManager} client for the {@link LocalSocketManager}. */
@NonNull protected final ILocalSocketManager mLocalSocketManagerClient;
/** The {@link Thread.UncaughtExceptionHandler} used for client thread started by {@link LocalSocketManager}. */
@NonNull protected final Thread.UncaughtExceptionHandler mLocalSocketManagerClientThreadUEH;
/** Whether the {@link LocalServerSocket} managed by {@link LocalSocketManager} in running or not. */
protected boolean mIsRunning;
/**
* Create an new instance of {@link LocalSocketManager}.
*
* @param context The {@link #mContext} value.
* @param localSocketRunConfig The {@link #mLocalSocketRunConfig} value.
*/
public LocalSocketManager(@NonNull Context context, @NonNull LocalSocketRunConfig localSocketRunConfig) {
mContext = context.getApplicationContext();
mLocalSocketRunConfig = localSocketRunConfig;
mServerSocket = new LocalServerSocket(this);
mLocalSocketManagerClient = mLocalSocketRunConfig.getLocalSocketManagerClient();
mLocalSocketManagerClientThreadUEH = getLocalSocketManagerClientThreadUEHOrDefault();
mIsRunning = false;
}
/**
* Create the {@link LocalServerSocket} and start listening for new {@link LocalClientSocket}.
*/
public synchronized Error start() {
Logger.logDebugExtended(LOG_TAG, "start\n" + mLocalSocketRunConfig);
if (!localSocketLibraryLoaded) {
try {
Logger.logDebug(LOG_TAG, "Loading \"" + LOCAL_SOCKET_LIBRARY + "\" library");
System.loadLibrary(LOCAL_SOCKET_LIBRARY);
localSocketLibraryLoaded = true;
} catch (Throwable t) {
Error error = LocalSocketErrno.ERRNO_START_LOCAL_SOCKET_LIB_LOAD_FAILED_WITH_EXCEPTION.getError(t, LOCAL_SOCKET_LIBRARY, t.getMessage());
Logger.logErrorExtended(LOG_TAG, error.getErrorLogString());
return error;
}
}
mIsRunning = true;
return mServerSocket.start();
}
/**
* Stop the {@link LocalServerSocket} and stop listening for new {@link LocalClientSocket}.
*/
public synchronized Error stop() {
if (mIsRunning) {
Logger.logDebugExtended(LOG_TAG, "stop\n" + mLocalSocketRunConfig);
mIsRunning = false;
return mServerSocket.stop();
}
return null;
}
/*
Note: Exceptions thrown from JNI must be caught with Throwable class instead of Exception,
otherwise exception will be sent to UncaughtExceptionHandler of the thread.
*/
/**
* Creates an AF_UNIX/SOCK_STREAM local server socket at {@code path}, with the specified backlog.
*
* @param serverTitle The server title used for logging and errors.
* @param path The path at which to create the socket.
* For a filesystem socket, this must be an absolute path to the socket file.
* For an abstract namespace socket, the first byte must be a null `\0` character.
* Max allowed length is 108 bytes as per sun_path size (UNIX_PATH_MAX) on Linux.
* @param backlog The maximum length to which the queue of pending connections for the socket
* may grow. This value may be ignored or may not have one-to-one mapping
* in kernel implementation. Value must be greater than 0.
* @return Returns the {@link JniResult}. If server creation was successful, then
* {@link JniResult#retval} will be 0 and {@link JniResult#intData} will contain the server socket
* fd.
*/
@Nullable
public static JniResult createServerSocket(@NonNull String serverTitle, @NonNull byte[] path, int backlog) {
try {
return createServerSocketNative(serverTitle, path, backlog);
} catch (Throwable t) {
String message = "Exception in createServerSocketNative()";
Logger.logStackTraceWithMessage(LOG_TAG, message, t);
return new JniResult(message, t);
}
}
/**
* Closes the socket with fd.
*
* @param serverTitle The server title used for logging and errors.
* @param fd The socket fd.
* @return Returns the {@link JniResult}. If closing socket was successful, then
* {@link JniResult#retval} will be 0.
*/
@Nullable
public static JniResult closeSocket(@NonNull String serverTitle, int fd) {
try {
return closeSocketNative(serverTitle, fd);
} catch (Throwable t) {
String message = "Exception in closeSocketNative()";
Logger.logStackTraceWithMessage(LOG_TAG, message, t);
return new JniResult(message, t);
}
}
/**
* Accepts a connection on the supplied server socket fd.
*
* @param serverTitle The server title used for logging and errors.
* @param fd The server socket fd.
* @return Returns the {@link JniResult}. If accepting socket was successful, then
* {@link JniResult#retval} will be 0 and {@link JniResult#intData} will contain the client socket
* fd.
*/
@Nullable
public static JniResult accept(@NonNull String serverTitle, int fd) {
try {
return acceptNative(serverTitle, fd);
} catch (Throwable t) {
String message = "Exception in acceptNative()";
Logger.logStackTraceWithMessage(LOG_TAG, message, t);
return new JniResult(message, t);
}
}
/**
* Attempts to read up to data buffer length bytes from file descriptor fd into the data buffer.
* On success, the number of bytes read is returned (zero indicates end of file).
* It is not an error if bytes read is smaller than the number of bytes requested; this may happen
* for example because fewer bytes are actually available right now (maybe because we were close
* to end-of-file, or because we are reading from a pipe), or because read() was interrupted by
* a signal. On error, the {@link JniResult#errno} and {@link JniResult#errmsg} will be set.
*
* If while reading the deadline elapses but all the data has not been read, the call will fail.
*
* @param serverTitle The server title used for logging and errors.
* @param fd The socket fd.
* @param data The data buffer to read bytes into.
* @param deadline The deadline milliseconds since epoch.
* @return Returns the {@link JniResult}. If reading was successful, then {@link JniResult#retval}
* will be 0 and {@link JniResult#intData} will contain the bytes read.
*/
@Nullable
public static JniResult read(@NonNull String serverTitle, int fd, @NonNull byte[] data, long deadline) {
try {
return readNative(serverTitle, fd, data, deadline);
} catch (Throwable t) {
String message = "Exception in readNative()";
Logger.logStackTraceWithMessage(LOG_TAG, message, t);
return new JniResult(message, t);
}
}
/**
* Attempts to send data buffer to the file descriptor. On error, the {@link JniResult#errno} and
* {@link JniResult#errmsg} will be set.
*
* If while sending the deadline elapses but all the data has not been sent, the call will fail.
*
* @param serverTitle The server title used for logging and errors.
* @param fd The socket fd.
* @param data The data buffer containing bytes to send.
* @param deadline The deadline milliseconds since epoch.
* @return Returns the {@link JniResult}. If sending was successful, then {@link JniResult#retval}
* will be 0.
*/
@Nullable
public static JniResult send(@NonNull String serverTitle, int fd, @NonNull byte[] data, long deadline) {
try {
return sendNative(serverTitle, fd, data, deadline);
} catch (Throwable t) {
String message = "Exception in sendNative()";
Logger.logStackTraceWithMessage(LOG_TAG, message, t);
return new JniResult(message, t);
}
}
/**
* Gets the number of bytes available to read on the socket.
*
* @param serverTitle The server title used for logging and errors.
* @param fd The socket fd.
* @return Returns the {@link JniResult}. If checking availability was successful, then
* {@link JniResult#retval} will be 0 and {@link JniResult#intData} will contain the bytes available.
*/
@Nullable
public static JniResult available(@NonNull String serverTitle, int fd) {
try {
return availableNative(serverTitle, fd);
} catch (Throwable t) {
String message = "Exception in availableNative()";
Logger.logStackTraceWithMessage(LOG_TAG, message, t);
return new JniResult(message, t);
}
}
/**
* Set receiving (SO_RCVTIMEO) timeout in milliseconds for socket.
*
* @param serverTitle The server title used for logging and errors.
* @param fd The socket fd.
* @param timeout The timeout value in milliseconds.
* @return Returns the {@link JniResult}. If setting timeout was successful, then
* {@link JniResult#retval} will be 0.
*/
@Nullable
public static JniResult setSocketReadTimeout(@NonNull String serverTitle, int fd, int timeout) {
try {
return setSocketReadTimeoutNative(serverTitle, fd, timeout);
} catch (Throwable t) {
String message = "Exception in setSocketReadTimeoutNative()";
Logger.logStackTraceWithMessage(LOG_TAG, message, t);
return new JniResult(message, t);
}
}
/**
* Set sending (SO_SNDTIMEO) timeout in milliseconds for fd.
*
* @param serverTitle The server title used for logging and errors.
* @param fd The socket fd.
* @param timeout The timeout value in milliseconds.
* @return Returns the {@link JniResult}. If setting timeout was successful, then
* {@link JniResult#retval} will be 0.
*/
@Nullable
public static JniResult setSocketSendTimeout(@NonNull String serverTitle, int fd, int timeout) {
try {
return setSocketSendTimeoutNative(serverTitle, fd, timeout);
} catch (Throwable t) {
String message = "Exception in setSocketSendTimeoutNative()";
Logger.logStackTraceWithMessage(LOG_TAG, message, t);
return new JniResult(message, t);
}
}
/**
* Get the {@link PeerCred} for the socket.
*
* @param serverTitle The server title used for logging and errors.
* @param fd The socket fd.
* @param peerCred The {@link PeerCred} object that should be filled.
* @return Returns the {@link JniResult}. If setting timeout was successful, then
* {@link JniResult#retval} will be 0.
*/
@Nullable
public static JniResult getPeerCred(@NonNull String serverTitle, int fd, PeerCred peerCred) {
try {
return getPeerCredNative(serverTitle, fd, peerCred);
} catch (Throwable t) {
String message = "Exception in getPeerCredNative()";
Logger.logStackTraceWithMessage(LOG_TAG, message, t);
return new JniResult(message, t);
}
}
/** Wrapper for {@link #onError(LocalClientSocket, Error)} for {@code null} {@link LocalClientSocket}. */
public void onError(@NonNull Error error) {
onError(null, error);
}
/** Wrapper to call {@link ILocalSocketManager#onError(LocalSocketManager, LocalClientSocket, Error)} in a new thread. */
public void onError(@Nullable LocalClientSocket clientSocket, @NonNull Error error) {
startLocalSocketManagerClientThread(() ->
mLocalSocketManagerClient.onError(this, clientSocket, error));
}
/** Wrapper to call {@link ILocalSocketManager#onDisallowedClientConnected(LocalSocketManager, LocalClientSocket, Error)} in a new thread. */
public void onDisallowedClientConnected(@NonNull LocalClientSocket clientSocket, @NonNull Error error) {
startLocalSocketManagerClientThread(() ->
mLocalSocketManagerClient.onDisallowedClientConnected(this, clientSocket, error));
}
/** Wrapper to call {@link ILocalSocketManager#onClientAccepted(LocalSocketManager, LocalClientSocket)} in a new thread. */
public void onClientAccepted(@NonNull LocalClientSocket clientSocket) {
startLocalSocketManagerClientThread(() ->
mLocalSocketManagerClient.onClientAccepted(this, clientSocket));
}
/** All client accept logic must be run on separate threads so that incoming client acceptance is not blocked. */
public void startLocalSocketManagerClientThread(@NonNull Runnable runnable) {
Thread thread = new Thread(runnable);
thread.setUncaughtExceptionHandler(getLocalSocketManagerClientThreadUEH());
try {
thread.start();
} catch (Exception e) {
Logger.logStackTraceWithMessage(LOG_TAG, "LocalSocketManagerClientThread start failed", e);
}
}
/** Get {@link #mContext}. */
public Context getContext() {
return mContext;
}
/** Get {@link #mLocalSocketRunConfig}. */
public LocalSocketRunConfig getLocalSocketRunConfig() {
return mLocalSocketRunConfig;
}
/** Get {@link #mLocalSocketManagerClient}. */
public ILocalSocketManager getLocalSocketManagerClient() {
return mLocalSocketManagerClient;
}
/** Get {@link #mServerSocket}. */
public LocalServerSocket getServerSocket() {
return mServerSocket;
}
/** Get {@link #mLocalSocketManagerClientThreadUEH}. */
public Thread.UncaughtExceptionHandler getLocalSocketManagerClientThreadUEH() {
return mLocalSocketManagerClientThreadUEH;
}
/**
* Get {@link Thread.UncaughtExceptionHandler} returned by call to
* {@link ILocalSocketManager#getLocalSocketManagerClientThreadUEH(LocalSocketManager)}
* or the default handler that just logs the exception.
*/
protected Thread.UncaughtExceptionHandler getLocalSocketManagerClientThreadUEHOrDefault() {
Thread.UncaughtExceptionHandler uncaughtExceptionHandler =
mLocalSocketManagerClient.getLocalSocketManagerClientThreadUEH(this);
if (uncaughtExceptionHandler == null)
uncaughtExceptionHandler = (t, e) ->
Logger.logStackTraceWithMessage(LOG_TAG, "Uncaught exception for " + t + " in " + mLocalSocketRunConfig.getTitle() + " server", e);
return uncaughtExceptionHandler;
}
/** Get {@link #mIsRunning}. */
public boolean isRunning() {
return mIsRunning;
}
/** Get an error log {@link String} for the {@link LocalSocketManager}. */
public static String getErrorLogString(@NonNull Error error,
@NonNull LocalSocketRunConfig localSocketRunConfig,
@Nullable LocalClientSocket clientSocket) {
StringBuilder logString = new StringBuilder();
logString.append(localSocketRunConfig.getTitle()).append(" Socket Server Error:\n");
logString.append(error.getErrorLogString());
logString.append("\n\n\n");
logString.append(localSocketRunConfig.getLogString());
if (clientSocket != null) {
logString.append("\n\n\n");
logString.append(clientSocket.getLogString());
}
return logString.toString();
}
/** Get an error markdown {@link String} for the {@link LocalSocketManager}. */
public static String getErrorMarkdownString(@NonNull Error error,
@NonNull LocalSocketRunConfig localSocketRunConfig,
@Nullable LocalClientSocket clientSocket) {
StringBuilder markdownString = new StringBuilder();
markdownString.append(error.getErrorMarkdownString());
markdownString.append("\n##\n\n\n");
markdownString.append(localSocketRunConfig.getMarkdownString());
if (clientSocket != null) {
markdownString.append("\n\n\n");
markdownString.append(clientSocket.getMarkdownString());
}
return markdownString.toString();
}
@Nullable private static native JniResult createServerSocketNative(@NonNull String serverTitle, @NonNull byte[] path, int backlog);
@Nullable private static native JniResult closeSocketNative(@NonNull String serverTitle, int fd);
@Nullable private static native JniResult acceptNative(@NonNull String serverTitle, int fd);
@Nullable private static native JniResult readNative(@NonNull String serverTitle, int fd, @NonNull byte[] data, long deadline);
@Nullable private static native JniResult sendNative(@NonNull String serverTitle, int fd, @NonNull byte[] data, long deadline);
@Nullable private static native JniResult availableNative(@NonNull String serverTitle, int fd);
private static native JniResult setSocketReadTimeoutNative(@NonNull String serverTitle, int fd, int timeout);
@Nullable private static native JniResult setSocketSendTimeoutNative(@NonNull String serverTitle, int fd, int timeout);
@Nullable private static native JniResult getPeerCredNative(@NonNull String serverTitle, int fd, PeerCred peerCred);
}