mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Feat] (Plugin, P11 B1): the broker presents the PairBind pair at hand-off (PairBindFrames, the encoder's bytes) and answers v2 with that flag and the apply core its supervisor announced; the helper passes MOBILEGL_IPC_FD_PAIRED=1 on, and Java no longer re-derives the reserved core
This commit is contained in:
@@ -25,8 +25,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* receiver checks the token against the running server's own ({@code MOBILEGL_IPC_TOKEN}), connects
|
||||
* TWICE to the server's unix endpoint - or, when the server listens on TCP only, to the private
|
||||
* abstract listener {@link MobileGLServerService} runs beside it for this - and hands both client
|
||||
* ends back as {@link ParcelFileDescriptor}s. It writes nothing on them: the client presents its own
|
||||
* PairBind pair ({@code MOBILEGL_IPC_CONTROL=fd:<control>,<aux>}).
|
||||
* ends back as {@link ParcelFileDescriptor}s. Ruling ID-P11-12: whoever opens the connections presents
|
||||
* the pair's identity, so it writes one {@code PairBind} pair on them at once ({@link PairBindFrames}:
|
||||
* a fresh nonce, control then aux) and says so in its answer; the helper tells the program
|
||||
* ({@code MOBILEGL_IPC_FD_PAIRED=1}) not to present a second one. The server pairs the connections
|
||||
* now, not when the program - which may spend many seconds loading first - reaches its first EGL call.
|
||||
*
|
||||
* <p>EVERY request is answered on the callback - the two descriptors, or a refusal with its name -
|
||||
* so a helper never has to time out to learn it was refused. The refusals: {@code bad-request},
|
||||
@@ -47,13 +50,17 @@ public final class ExternalClientBroker extends BroadcastReceiver {
|
||||
public static final String KEY_CALLBACK = "callback";
|
||||
public static final String KEY_TOKEN = "token";
|
||||
public static final String KEY_VERSION = "version";
|
||||
public static final int PROTOCOL_VERSION = 1;
|
||||
/** 2: the answer carries the paired-at-hand-off flag (a v1 helper would add a second PairBind pair). */
|
||||
public static final int PROTOCOL_VERSION = 2;
|
||||
|
||||
/**
|
||||
* The broker's answer: int version, the control and aux descriptors, then long applyCore - the cpu
|
||||
* mask the server's `auto` policy reserves for this session's apply thread (0 = none).
|
||||
* The broker's answer: int version, the control and aux descriptors, long applyCore - the cpu mask
|
||||
* the running supervisor announced it reserves for this session's apply thread (0 = none) - and
|
||||
* int flags ({@link #FLAG_PAIRED_AT_HANDOFF}).
|
||||
*/
|
||||
public static final int REPLY_FDS = IBinder.FIRST_CALL_TRANSACTION;
|
||||
/** The broker already wrote the PairBind pair on the two connections. */
|
||||
public static final int FLAG_PAIRED_AT_HANDOFF = 1;
|
||||
/** The broker's refusal: String code, String message. */
|
||||
public static final int REPLY_REFUSED = IBinder.FIRST_CALL_TRANSACTION + 1;
|
||||
|
||||
@@ -160,21 +167,42 @@ public final class ExternalClientBroker extends BroadcastReceiver {
|
||||
+ ": " + failure[0]);
|
||||
return;
|
||||
}
|
||||
// ID-P11-12: the pair's identity, presented by whoever opened the connections - here, now. The
|
||||
// server's PairAcceptor pairs them within its 2000 ms budget however long the program the helper
|
||||
// execs takes to reach its Hello (the paired session then waits for that Hello until EOF).
|
||||
try {
|
||||
byte[] nonce = new byte[PairBindFrames.NONCE_BYTES];
|
||||
new java.security.SecureRandom().nextBytes(nonce);
|
||||
java.io.OutputStream controlOut = sockets[0].getOutputStream();
|
||||
controlOut.write(PairBindFrames.frame(nonce, false));
|
||||
controlOut.flush();
|
||||
java.io.OutputStream auxOut = sockets[1].getOutputStream();
|
||||
auxOut.write(PairBindFrames.frame(nonce, true));
|
||||
auxOut.flush();
|
||||
} catch (Exception error) {
|
||||
closeQuietly(sockets[0]);
|
||||
closeQuietly(sockets[1]);
|
||||
refuse(callback, REFUSE_CONNECT_FAILED, "could not present the PairBind pair on " + target.endpoint
|
||||
+ ": " + error);
|
||||
return;
|
||||
}
|
||||
ParcelFileDescriptor control = null;
|
||||
ParcelFileDescriptor aux = null;
|
||||
Parcel data = Parcel.obtain();
|
||||
long applyCore = Math.max(0, target.applyCore.get()); // -1: not announced yet -> no mask
|
||||
try {
|
||||
control = ParcelFileDescriptor.dup(sockets[0].getFileDescriptor());
|
||||
aux = ParcelFileDescriptor.dup(sockets[1].getFileDescriptor());
|
||||
data.writeInt(PROTOCOL_VERSION);
|
||||
control.writeToParcel(data, 0);
|
||||
aux.writeToParcel(data, 0);
|
||||
// The core the server reserves for this session's apply thread (0 = none): the helper keeps
|
||||
// the client off it. Appended after the descriptors; a reader that stops before it is fine.
|
||||
data.writeLong(target.applyCore);
|
||||
// The core the running supervisor announced it reserves for this session's apply thread
|
||||
// (0 = none): the helper keeps the client off it.
|
||||
data.writeLong(applyCore);
|
||||
data.writeInt(FLAG_PAIRED_AT_HANDOFF);
|
||||
boolean delivered = callback.transact(REPLY_FDS, data, null, IBinder.FLAG_ONEWAY);
|
||||
Log.i(TAG, "brokered a connection pair to " + target.endpoint + " for a client (delivered="
|
||||
+ delivered + ", apply core 0x" + Long.toHexString(target.applyCore) + ")");
|
||||
Log.i(TAG, "brokered a connection pair to " + target.endpoint + " for a client, paired at hand-off "
|
||||
+ "(delivered=" + delivered + ", apply core 0x" + Long.toHexString(applyCore) + ")");
|
||||
} catch (Exception error) {
|
||||
Log.e(TAG, "could not hand the connection pair to the client", error);
|
||||
} finally {
|
||||
|
||||
@@ -43,7 +43,9 @@ import java.util.Map;
|
||||
* that enforces it there breaks this route; CONTRACT-P11 B1), and waits for the broker's answer.
|
||||
* Two connected sockets: it clears their close-on-exec flag and {@code execve}s the program with
|
||||
* {@code MOBILEGL_TRANSPORT=spawn}, {@code MOBILEGL_IPC_CONTROL=fd:<control>,<aux>},
|
||||
* {@code MOBILEGL_IPC_DATA=shm} and {@code MOBILEGL_IPC_TOKEN} (the session's Hello carries it too).
|
||||
* {@code MOBILEGL_IPC_DATA=shm} and {@code MOBILEGL_IPC_TOKEN} (the session's Hello carries it too), plus
|
||||
* {@code MOBILEGL_IPC_FD_PAIRED=1} when the broker already presented the PairBind pair at hand-off
|
||||
* (ID-P11-12) - the program may then take as long as it likes to reach its first EGL call.
|
||||
* When the broker names a cpu core the server reserves for the session's apply thread, the program is
|
||||
* exec'd through {@code taskset} on every other core (see {@link #clientMask}; {@code --keep-affinity}
|
||||
* opts out). A refusal: it prints the broker's name for it and exits with that refusal's code. No
|
||||
@@ -147,15 +149,18 @@ public final class ExternalClientHelper {
|
||||
ParcelFileDescriptor control = ParcelFileDescriptor.CREATOR.createFromParcel(data);
|
||||
ParcelFileDescriptor aux = ParcelFileDescriptor.CREATOR.createFromParcel(data);
|
||||
long applyCore = data.dataAvail() >= 8 ? data.readLong() : 0;
|
||||
int replyFlags = data.dataAvail() >= 4 ? data.readInt() : 0;
|
||||
boolean paired = (replyFlags & ExternalClientBroker.FLAG_PAIRED_AT_HANDOFF) != 0;
|
||||
int controlFd = inheritable(control);
|
||||
int auxFd = inheritable(aux);
|
||||
long mask = keepAffinity ? 0 : clientMask(applyCore);
|
||||
say("broker v" + version + " handed over fd:" + controlFd + "," + auxFd
|
||||
+ (paired ? " paired at hand-off" : " unpaired")
|
||||
+ (mask != 0 ? "; the server's apply core 0x" + Long.toHexString(applyCore)
|
||||
+ " reserved, the program runs on cpu mask 0x" + Long.toHexString(mask)
|
||||
: "; no cpu mask applied")
|
||||
+ "; exec " + programPath);
|
||||
exec(programPath, argv, controlFd, auxFd, presented, mask);
|
||||
exec(programPath, argv, controlFd, auxFd, presented, mask, paired);
|
||||
} catch (Throwable error) {
|
||||
say("could not exec " + programPath + ": " + error);
|
||||
finish(EXIT_EXEC_FAILED);
|
||||
@@ -267,8 +272,8 @@ public final class ExternalClientHelper {
|
||||
return pfd.detachFd();
|
||||
}
|
||||
|
||||
private static void exec(String path, List<String> argv, int control, int aux, String token, long cpuMask)
|
||||
throws Exception {
|
||||
private static void exec(String path, List<String> argv, int control, int aux, String token, long cpuMask,
|
||||
boolean pairedAtHandoff) throws Exception {
|
||||
Map<String, String> env = new LinkedHashMap<>();
|
||||
String[] inherited = Os.environ();
|
||||
if (inherited != null) {
|
||||
@@ -283,6 +288,9 @@ public final class ExternalClientHelper {
|
||||
env.put("MOBILEGL_IPC_CONTROL", "fd:" + control + "," + aux);
|
||||
env.put("MOBILEGL_IPC_DATA", "shm");
|
||||
env.put("MOBILEGL_IPC_TOKEN", token);
|
||||
// ID-P11-12: the broker presented the PairBind pair; the program's fd: endpoint must not add one.
|
||||
if (pairedAtHandoff) env.put("MOBILEGL_IPC_FD_PAIRED", "1");
|
||||
else env.remove("MOBILEGL_IPC_FD_PAIRED");
|
||||
List<String> envp = new ArrayList<>();
|
||||
for (Map.Entry<String, String> entry : env.entrySet()) envp.add(entry.getKey() + "=" + entry.getValue());
|
||||
if (cpuMask != 0 && new File(TASKSET).canExecute()) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/** Entry point for the offscreen TCP supervisor; EGL belongs to its session children. */
|
||||
public final class MobileGLServerService extends Service {
|
||||
@@ -40,12 +41,12 @@ public final class MobileGLServerService extends Service {
|
||||
final String endpoint;
|
||||
final String token;
|
||||
final Process process;
|
||||
// The core the server's `auto` policy gives a dialled-in shared-segment client's apply thread
|
||||
// (ServerEnvironment.reservedApplyCore); the broker hands it to the helper, which keeps the
|
||||
// client off it. 0 = none reserved.
|
||||
final long applyCore;
|
||||
// The core THIS supervisor reserves for a dialled-in shared-segment client's apply thread, as it
|
||||
// announced on stdout at start-up (ServerEnvironment.parseReservedApplyCore); the broker hands
|
||||
// it to the helper, which keeps the client off it. -1 until the line is read, 0 = none.
|
||||
final AtomicLong applyCore;
|
||||
|
||||
BrokerTarget(String endpoint, String token, Process process, long applyCore) {
|
||||
BrokerTarget(String endpoint, String token, Process process, AtomicLong applyCore) {
|
||||
this.endpoint = endpoint;
|
||||
this.token = token;
|
||||
this.process = process;
|
||||
@@ -53,21 +54,6 @@ public final class MobileGLServerService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
/** cpuinfo_max_freq of every cpu this process can count (0 where unreadable). */
|
||||
private static long[] cpuMaxFrequenciesKHz() {
|
||||
int count = Math.min(64, Math.max(1, Runtime.getRuntime().availableProcessors()));
|
||||
long[] frequencies = new long[count];
|
||||
for (int cpu = 0; cpu < count; cpu++) {
|
||||
File file = new File("/sys/devices/system/cpu/cpu" + cpu + "/cpufreq/cpuinfo_max_freq");
|
||||
try (BufferedReader reader = new BufferedReader(new java.io.FileReader(file))) {
|
||||
frequencies[cpu] = Long.parseLong(reader.readLine().trim());
|
||||
} catch (Exception unreadable) {
|
||||
frequencies[cpu] = 0;
|
||||
}
|
||||
}
|
||||
return frequencies;
|
||||
}
|
||||
|
||||
// Set when a supervisor the broker can reach is running, cleared when it stops. The broker runs
|
||||
// in this same process (:mglsrv), so a broadcast that finds null here finds no server.
|
||||
private static volatile BrokerTarget brokerTarget;
|
||||
@@ -155,30 +141,26 @@ public final class MobileGLServerService extends Service {
|
||||
if (endpoint == null) endpoint = "tcp://127.0.0.1:40613";
|
||||
final String envExtra = intent == null ? null : intent.getStringExtra("env");
|
||||
final String token = intent == null ? null : intent.getStringExtra("token");
|
||||
java.util.Map<String, String> requested = new java.util.HashMap<>();
|
||||
ServerEnvironment.apply(requested, envExtra);
|
||||
final long applyCore = ServerEnvironment.reservedApplyCore(cpuMaxFrequenciesKHz(),
|
||||
requested.get("MOBILEGL_IPC_SERVER_AFFINITY"));
|
||||
final AtomicLong primaryCore = new AtomicLong(-1);
|
||||
try {
|
||||
supervisor = startSupervisor(endpoint, envExtra, token, "mgl-supervisor-log", startId, true);
|
||||
supervisor = startSupervisor(endpoint, envExtra, token, "mgl-supervisor-log", startId, true, primaryCore);
|
||||
// P11 B1: the broker's target. A unix endpoint is reachable by this app as it is (a
|
||||
// relative path resolves against filesDir, the supervisor's working directory); a TCP
|
||||
// one gets a private abstract listener of its own (BrokerTarget).
|
||||
if (endpoint.startsWith("tcp://")) {
|
||||
String privateName = "@" + getPackageName() + ".broker." + Long.toHexString(
|
||||
new java.security.SecureRandom().nextLong() & 0xffffffffL);
|
||||
final AtomicLong brokerCore = new AtomicLong(-1);
|
||||
brokerSupervisor = startSupervisor(privateName, envExtra, token, "mgl-broker-supervisor-log",
|
||||
startId, false);
|
||||
brokerTarget = new BrokerTarget(privateName, token, brokerSupervisor, applyCore);
|
||||
startId, false, brokerCore);
|
||||
brokerTarget = new BrokerTarget(privateName, token, brokerSupervisor, brokerCore);
|
||||
Log.i(TAG, "same-device external clients: broker listener " + privateName
|
||||
+ " beside the TCP-only supervisor on " + endpoint + "; apply core reserved for them 0x"
|
||||
+ Long.toHexString(applyCore));
|
||||
+ " beside the TCP-only supervisor on " + endpoint);
|
||||
} else {
|
||||
String unix = endpoint.startsWith("@") || endpoint.startsWith("/") ? endpoint
|
||||
: new File(getFilesDir(), endpoint).getAbsolutePath();
|
||||
brokerTarget = new BrokerTarget(unix, token, supervisor, applyCore);
|
||||
Log.i(TAG, "same-device external clients: the broker connects to " + unix
|
||||
+ "; apply core reserved for them 0x" + Long.toHexString(applyCore));
|
||||
brokerTarget = new BrokerTarget(unix, token, supervisor, primaryCore);
|
||||
Log.i(TAG, "same-device external clients: the broker connects to " + unix);
|
||||
}
|
||||
} catch (Exception error) {
|
||||
Log.e(TAG, "cannot start TCP supervisor", error);
|
||||
@@ -190,10 +172,11 @@ public final class MobileGLServerService extends Service {
|
||||
|
||||
/**
|
||||
* Execs one {@code libMobileGLServer.so <endpoint> --serve} with the server role's environment and
|
||||
* logs its output. When {@code primary}, its exit stops the service.
|
||||
* logs its output. When {@code primary}, its exit stops the service. {@code reservedCore} receives the
|
||||
* apply core the supervisor announces at start-up (P11 B1).
|
||||
*/
|
||||
private Process startSupervisor(String endpoint, String envExtra, String token, String logThreadName,
|
||||
int startId, boolean primary) throws java.io.IOException {
|
||||
int startId, boolean primary, AtomicLong reservedCore) throws java.io.IOException {
|
||||
File executable = new File(getApplicationInfo().nativeLibraryDir, "libMobileGLServer.so");
|
||||
ProcessBuilder builder = new ProcessBuilder(executable.getAbsolutePath(), endpoint, "--serve");
|
||||
builder.directory(getFilesDir()).redirectErrorStream(true);
|
||||
@@ -207,7 +190,11 @@ public final class MobileGLServerService extends Service {
|
||||
new Thread(() -> {
|
||||
try (BufferedReader output = new BufferedReader(new InputStreamReader(child.getInputStream()))) {
|
||||
String line;
|
||||
while ((line = output.readLine()) != null) Log.i(TAG, line);
|
||||
while ((line = output.readLine()) != null) {
|
||||
Log.i(TAG, line);
|
||||
long announced = ServerEnvironment.parseReservedApplyCore(line);
|
||||
if (announced != -1) reservedCore.set(announced); // one core, so never all 64 bits
|
||||
}
|
||||
Log.i(TAG, (primary ? "supervisor" : "broker supervisor") + " exited " + child.waitFor());
|
||||
} catch (java.io.InterruptedIOException stopped) {
|
||||
// onDestroy's Process.destroy() closes this stream under the read: the service
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package top.mobilegl.plugin;
|
||||
|
||||
/**
|
||||
* P11 B1 (CONTRACT-P11 B1, ruling ID-P11-12): the two framed {@code PairBind} control messages the
|
||||
* broker writes at hand-off - {@code PairBind{nonce, aux=false}} first on the control connection and
|
||||
* {@code PairBind{nonce, aux=true}} on the aux one - so the server's PairAcceptor pairs the two
|
||||
* connections the moment the broker opens them, not when the program it opened them for reaches its
|
||||
* first EGL call.
|
||||
*
|
||||
* <p>The bytes are the native encoder's own ({@code MG_Remote/Transport/PairBind.h EncodePairBind}
|
||||
* framed by {@code Framing.h AppendFrame}): a flatbuffers {@code CtrlEnvelope} whose layout for a
|
||||
* 16-byte nonce is fixed, with the nonce as its last 16 bytes. The two prefixes below are that
|
||||
* encoder's output up to the nonce, and {@code ServerSpawnTest.TheBrokersPairBindTemplatesAreTheEncoders}
|
||||
* reads them out of this file and compares against the encoder for random nonces - a schema change
|
||||
* that moves a byte turns that test red, not a device.
|
||||
*
|
||||
* <p>No android.* import: tools/trace_replay/test_android_lifecycle.py compiles and runs it with javac
|
||||
* (PairBindFramesTest).
|
||||
*/
|
||||
public final class PairBindFrames {
|
||||
private PairBindFrames() {}
|
||||
|
||||
public static final int NONCE_BYTES = 16;
|
||||
/** Framing.h kFrameMagic, little-endian on the wire. */
|
||||
public static final int FRAME_MAGIC = 0x464C474D;
|
||||
|
||||
// EncodePairBind(nonce, aux=false) up to the nonce (the nonce is the payload's last 16 bytes).
|
||||
static final String CONTROL_PREFIX_HEX =
|
||||
"100000004d474c4308000e0007000800080000000000000f0c0000000000060008000400060000000400000010000000";
|
||||
// EncodePairBind(nonce, aux=true) up to the nonce.
|
||||
static final String AUX_PREFIX_HEX =
|
||||
"100000004d474c4308000c0007000800080000000000000f0c00000008000c000800070008000000000000010400000010000000";
|
||||
|
||||
/** One framed PairBind: 8-byte header (magic, payload length; little-endian) + the payload. */
|
||||
public static byte[] frame(byte[] nonce, boolean aux) {
|
||||
if (nonce == null || nonce.length != NONCE_BYTES) {
|
||||
throw new IllegalArgumentException("a PairBind nonce is " + NONCE_BYTES + " bytes");
|
||||
}
|
||||
byte[] prefix = hex(aux ? AUX_PREFIX_HEX : CONTROL_PREFIX_HEX);
|
||||
int payload = prefix.length + NONCE_BYTES;
|
||||
byte[] out = new byte[8 + payload];
|
||||
putLittleEndian(out, 0, FRAME_MAGIC);
|
||||
putLittleEndian(out, 4, payload);
|
||||
System.arraycopy(prefix, 0, out, 8, prefix.length);
|
||||
System.arraycopy(nonce, 0, out, 8 + prefix.length, NONCE_BYTES);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static void putLittleEndian(byte[] out, int offset, int value) {
|
||||
for (int i = 0; i < 4; i++) out[offset + i] = (byte) (value >>> (8 * i));
|
||||
}
|
||||
|
||||
static byte[] hex(String text) {
|
||||
byte[] out = new byte[text.length() / 2];
|
||||
for (int i = 0; i < out.length; i++) out[i] = (byte) Integer.parseInt(text.substring(2 * i, 2 * i + 2), 16);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -81,32 +81,25 @@ public final class ServerEnvironment {
|
||||
return edits;
|
||||
}
|
||||
|
||||
/** The supervisor's start-up announcement (MG_Remote/Server/ServerMain.cpp), up to the mask. */
|
||||
static final String RESERVED_CORE_ANNOUNCEMENT =
|
||||
"MG_Remote server: apply core reserved for dialled-in shared-segment clients: 0x";
|
||||
|
||||
/**
|
||||
* P11 B1 (CONTRACT-P11 B1): the core the server's `auto` policy reserves for the apply thread of a
|
||||
* dialled-in shared-segment client - MG_Remote/Server/ApplyThreadPolicy.h ReservedApplyCore over
|
||||
* ServerLoop.cpp's DetectBigCoreMask, mirrored here so the broker can tell the helper which core to
|
||||
* keep the client off. {@code maxFreqKHz[i]} is cpu i's cpuinfo_max_freq (0 = unreadable, which
|
||||
* like the native probe gives no answer). A cpu is big when its ceiling is within 15% of the peak;
|
||||
* the reserved core is the LOWEST big one, and only when the big set is a strict subset of at least
|
||||
* two. 0 = none. {@code affinityOverride} is the server's MOBILEGL_IPC_SERVER_AFFINITY: anything
|
||||
* but unset / empty / {@code auto} is an operator's own placement, and then nothing is reserved.
|
||||
* P11 B1 (CONTRACT-P11 B1): the core the running supervisor reserves for a dialled-in shared-segment
|
||||
* client's apply thread, read off the line it prints on stdout at start-up (the native policy,
|
||||
* ApplyThreadPolicy.h ReservedApplyCoreForConfig, is the one source of it; nothing here re-derives
|
||||
* it). The mask, or -1 when {@code line} is not that announcement.
|
||||
*/
|
||||
public static long reservedApplyCore(long[] maxFreqKHz, String affinityOverride) {
|
||||
if (affinityOverride != null && !affinityOverride.isEmpty() && !affinityOverride.equals("auto")) return 0;
|
||||
int count = Math.min(64, maxFreqKHz.length);
|
||||
long peak = 0;
|
||||
for (int cpu = 0; cpu < count; cpu++) {
|
||||
if (maxFreqKHz[cpu] <= 0) return 0;
|
||||
peak = Math.max(peak, maxFreqKHz[cpu]);
|
||||
}
|
||||
if (count == 0 || peak == 0) return 0;
|
||||
long big = 0;
|
||||
for (int cpu = 0; cpu < count; cpu++) {
|
||||
if (maxFreqKHz[cpu] >= peak * 85 / 100) big |= 1L << cpu;
|
||||
}
|
||||
long online = count >= 64 ? ~0L : (1L << count) - 1;
|
||||
if ((big & online) == online || Long.bitCount(big) < 2) return 0;
|
||||
return Long.lowestOneBit(big);
|
||||
public static long parseReservedApplyCore(String line) {
|
||||
if (line == null) return -1;
|
||||
int at = line.indexOf(RESERVED_CORE_ANNOUNCEMENT);
|
||||
if (at < 0) return -1;
|
||||
int start = at + RESERVED_CORE_ANNOUNCEMENT.length();
|
||||
int end = start;
|
||||
while (end < line.length() && Character.digit(line.charAt(end), 16) >= 0) end++;
|
||||
if (end == start || end - start > 16) return -1;
|
||||
return Long.parseUnsignedLong(line.substring(start, end), 16);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user