Skip to content

Instantly share code, notes, and snippets.

@Exerosis
Created February 20, 2018 17:41
Show Gist options
  • Save Exerosis/fd6fd4f8b7a5a5cff2366f4394348b8a to your computer and use it in GitHub Desktop.
Save Exerosis/fd6fd4f8b7a5a5cff2366f4394348b8a to your computer and use it in GitHub Desktop.
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.String.*
import static java.nio.ByteBuffer.allocateDirect
import static java.nio.channels.DatagramChannel.*
import static java.util.concurrent.Executors.newSingleThreadExecutor
group 'me.exerosis.debugging'
version '1.0.0'
apply plugin: 'java'
sourceCompatibility = 1.8
repositories {
mavenCentral()
maven {
url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/'
}
}
dependencies {
compile 'org.spigotmc:spigot:1.12.2-R0.1-SNAPSHOT'
compile 'org.apache.commons:commons-lang3:3.3.2'
compile files("/libs/tools.jar")
}
task test(type: TestTask) {
doFirst {
}
}
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 ->
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)
}
})
}
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)
channel.bind(new InetSocketAddress(port.intValue()))
executor.submit({
while (channel.isOpen()) {
SocketAddress address = channel.receive(buffer)
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)
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 ->
receive(opcode, { response, ignored ->
handler.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()
}
}
class TestTask extends DefaultTask {
@TaskAction
def tryConnect() {
println 'hello from GreetingTask'
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment