Created
February 20, 2018 18:52
-
-
Save Exerosis/07df700f2c295c71ac997b474c71729f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.nio.ByteBuffer | |
import java.nio.channels.DatagramChannel | |
import java.util.concurrent.ExecutorService | |
import java.util.function.BiConsumer | |
import java.util.function.BiFunction | |
import java.util.function.Consumer | |
import java.util.function.Function | |
import static java.lang.Integer.parseInt | |
import static java.lang.String.format | |
import static java.nio.ByteBuffer.allocateDirect | |
import static java.nio.channels.DatagramChannel.open | |
import static java.util.concurrent.Executors.newSingleThreadExecutor | |
import static java.util.concurrent.ThreadLocalRandom.current | |
group 'me.exerosis.debugging' | |
version '1.0.0' | |
apply plugin: 'java' | |
sourceCompatibility = 1.8 | |
repositories { | |
mavenCentral() | |
} | |
dependencies { | |
compile 'org.apache.commons:commons-lang3:3.3.2' | |
compile files("/libs/tools.jar") | |
} | |
def connection = new DebugConnection(25569) | |
task listPlugins() { | |
connection.list(new InetSocketAddress("localhost", parseInt(port)), filter, { plugins -> | |
plugins.each { pl -> | |
println 'Plugins' | |
println '===[Name]=====[Enabled]===' | |
println "${pl.name()} ${pl.isEnabled()}" | |
} | |
}) | |
} | |
class DebugConnection { | |
private final byte OP_TOGGLE = 0 | |
private final byte OP_LIST = 1 | |
private final byte OP_CHECK = 2 | |
private final DebugServer server | |
DebugConnection(Number port) { | |
server = new DebugServer(port, 512) | |
} | |
private static String ERROR_CHECK = "\tPlugin check failed.\n\tPlugin: %s\n\tCode: %s" | |
private final Map<String, Plugin> plugins = new HashMap<>(); | |
Plugin getPlugin(SocketAddress address, String name) { | |
return plugins.computeIfAbsent(name, { plugin -> | |
Plugin pl = new Plugin(name) { | |
@Override | |
boolean setEnabled(boolean enabled, Number delay) { | |
return false | |
} | |
@Override | |
boolean isEnabled(boolean refresh) { | |
if (refresh) { | |
server.request(OP_CHECK, address, { request -> | |
putString(request, name); | |
return { response -> | |
byte error = response.get(); | |
if (error != -1) | |
throw new RuntimeException(format(ERROR_CHECK, name, error)) | |
enabled = response.get() == 1; | |
} | |
}) | |
} | |
return enabled | |
} | |
} | |
pl.isEnabled(true) | |
return pl; | |
}) | |
} | |
void toggle(SocketAddress address, Consumer<Boolean> callback, PluginAction... actions) { | |
server.request(OP_TOGGLE, address, { request -> | |
request.put((byte) actions.length) | |
actions.each { action -> | |
putString(request, action.name) | |
request.putLong(action.delay) | |
request.put((byte) (action.enable ? 1 : 0)) | |
} | |
return { response -> | |
byte[] errors = new byte[actions.length] | |
response.get(errors) | |
errors.each { code -> | |
if (code != -1) { | |
callback.accept(false) | |
return; | |
} | |
} | |
callback.accept(true) | |
} | |
}) | |
} | |
void list(SocketAddress address, String filter, Consumer<List<Plugin>> callback) { | |
server.request(OP_LIST, address, { request -> | |
println 'test0' | |
putString(request, filter) | |
return { response -> | |
List<Plugin> plugins = new ArrayList<>() | |
byte length; | |
while ((length = response.get()) != 0) { | |
byte[] bytes = new byte[length] | |
response.get(bytes) | |
plugins.add(getPlugin(address, new String(bytes))) | |
} | |
callback.accept(plugins) | |
} | |
}) | |
Thread.sleep(10_000) | |
} | |
static abstract class Plugin { | |
private final String name | |
private boolean enabled = false | |
private Plugin(String name) { | |
this.name = name | |
} | |
abstract boolean setEnabled(boolean enabled, Number delay = 5_000) | |
abstract boolean isEnabled(boolean refresh = true) | |
String name() { | |
return name | |
} | |
void enable(Number delay = 5_000) { | |
if (!enabled && setEnabled(true, delay)) | |
enabled = true | |
} | |
void disable(Number delay = 5_000) { | |
if (enabled && setEnabled(false, delay)) | |
enabled = false | |
} | |
void toggle(Number delay = 5_000) { | |
if (setEnabled(!enabled, delay)) | |
enabled = !enabled | |
} | |
} | |
static class PluginAction { | |
private final boolean enable | |
private final long delay | |
private final String name | |
private PluginAction(String name, boolean enable, Number delay) { | |
this.name = name | |
this.delay = delay.longValue() | |
this.enable = enable | |
} | |
} | |
static void putString(ByteBuffer buffer, String string, Consumer<Number> length = { buffer.put(it.byteValue()) }) { | |
length.accept(string.length()) | |
buffer.put(string.bytes) | |
} | |
} | |
class DebugServer implements Closeable { | |
private final Map<Number, BiConsumer<ByteBuffer, SocketAddress>> handlers = new HashMap<>() | |
private final ExecutorService executor = newSingleThreadExecutor() | |
private final DatagramChannel channel | |
private final ByteBuffer buffer | |
DebugServer(Number port, Number length) throws IOException { | |
this(port, length, { buffer -> | |
buffer.put(length.byteValue()) | |
}) | |
} | |
DebugServer(Number port, Number length, Function<ByteBuffer, Number> opcodes) throws IOException { | |
buffer = allocateDirect(length.intValue()) | |
channel = open() | |
channel.configureBlocking(true) | |
def randoPort = current().nextInt(9999) | |
println randoPort | |
channel.bind(new InetSocketAddress("localhost", randoPort)) | |
executor.submit({ | |
while (channel.isOpen()) { | |
println 'listening' | |
SocketAddress address = channel.receive(buffer) | |
println "responded: ${buffer.remaining()}" | |
buffer.flip() | |
handlers.get(opcodes.apply(buffer)).accept(buffer, address) | |
buffer.clear() | |
} | |
return null | |
}) | |
} | |
void send(SocketAddress address, Number opcode, Consumer<ByteBuffer> packet) { | |
try { | |
synchronized (buffer) { | |
buffer.clear() | |
buffer.put(opcode.byteValue()) | |
packet.accept(buffer) | |
buffer.flip() | |
channel.send(buffer, address) | |
} | |
} catch (IOException e) { | |
throw new RuntimeException(e) | |
} | |
} | |
void receive(Number opcode, BiConsumer<ByteBuffer, SocketAddress> handler) { | |
handlers.put(opcode, handler) | |
} | |
void request(Number opcode, SocketAddress address, Function<ByteBuffer, Consumer<ByteBuffer>> handler) { | |
send(address, opcode, { request -> | |
def callback = handler.apply(request) | |
receive(opcode, { response, ignored -> | |
callback.apply(response) | |
handlers.remove(opcode) | |
}) | |
} as Consumer) | |
} | |
void respond(Number opcode, Function<ByteBuffer, Consumer<ByteBuffer>> handler) { | |
respond(opcode, { request, address -> handler.apply(request) } as BiFunction) | |
} | |
void respond(Number opcode, BiFunction<ByteBuffer, SocketAddress, Consumer<ByteBuffer>> handler) { | |
receive(opcode, { request, address -> | |
Consumer<ByteBuffer> response = handler.apply(request, address) | |
buffer.clear() | |
buffer.put(opcode.byteValue()) | |
response.accept(request) | |
channel.send(buffer, address) | |
} as BiConsumer) | |
} | |
@Override | |
void close() throws IOException { | |
channel.close() | |
executor.shutdown() | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment