diff --git a/config/pom.xml b/config/pom.xml index 9df70007..ad8761be 100644 --- a/config/pom.xml +++ b/config/pom.xml @@ -17,4 +17,18 @@ BungeeCord-Config Generic java configuration API intended for use with BungeeCord + + + + junit + junit + 4.11 + test + + + org.yaml + snakeyaml + 1.11 + + diff --git a/config/src/main/java/net/md_5/bungee/config/Configuration.java b/config/src/main/java/net/md_5/bungee/config/Configuration.java index f9d32c82..43af0fff 100644 --- a/config/src/main/java/net/md_5/bungee/config/Configuration.java +++ b/config/src/main/java/net/md_5/bungee/config/Configuration.java @@ -2,6 +2,7 @@ package net.md_5.bungee.config; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import lombok.AccessLevel; @@ -13,8 +14,8 @@ public final class Configuration private static final char SEPARATOR = '.'; private final Map self; - private final Map comments; - private final Map defaults; + private Map comments = new HashMap<>(); + private final Configuration defaults; private Map getHolder(String path, Map parent, boolean create) { @@ -41,12 +42,17 @@ public final class Configuration public T get(String path, T def) { Object val = get( path, self ); - return ( val != null && val.getClass().isInstance( def ) ) ? (T) val : (T) get( path, defaults ); + return ( val != null && val.getClass().isInstance( def ) ) ? (T) val : (T) defaults.get( path ); + } + + public Object get(String path) + { + return get( path, null ); } public Object getDefault(String path) { - return get( path, defaults ); + return defaults.get( path ); } public void set(String path, Object value, String comment) diff --git a/config/src/main/java/net/md_5/bungee/config/ConfigurationProvider.java b/config/src/main/java/net/md_5/bungee/config/ConfigurationProvider.java new file mode 100644 index 00000000..7d1b5327 --- /dev/null +++ b/config/src/main/java/net/md_5/bungee/config/ConfigurationProvider.java @@ -0,0 +1,29 @@ +package net.md_5.bungee.config; + +import java.io.File; +import java.io.Reader; +import java.util.HashMap; +import java.util.Map; + +public abstract class ConfigurationProvider +{ + + private static final Map, ConfigurationProvider> providers = new HashMap<>(); + + static + { + providers.put( YamlConfiguration.class, new YamlConfiguration() ); + } + + public ConfigurationProvider getProvider(Class provider) + { + return providers.get( provider ); + } + /*------------------------------------------------------------------------*/ + + public abstract Configuration load(File file); + + public abstract Configuration load(Reader reader); + + public abstract Configuration load(String string); +} diff --git a/config/src/main/java/net/md_5/bungee/config/YamlConfiguration.java b/config/src/main/java/net/md_5/bungee/config/YamlConfiguration.java new file mode 100644 index 00000000..74d84ab8 --- /dev/null +++ b/config/src/main/java/net/md_5/bungee/config/YamlConfiguration.java @@ -0,0 +1,53 @@ +package net.md_5.bungee.config; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.util.Map; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; + +public class YamlConfiguration extends ConfigurationProvider +{ + + private final ThreadLocal yaml = new ThreadLocal() + { + @Override + protected Yaml initialValue() + { + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle( DumperOptions.FlowStyle.BLOCK ); + return new Yaml( options ); + } + }; + + @Override + public Configuration load(File file) + { + try ( FileReader reader = new FileReader( file ) ) + { + return load( reader ); + } catch ( IOException ex ) + { + return null; + } + } + + @Override + @SuppressWarnings("unchecked") + public Configuration load(Reader reader) + { + Configuration conf = new Configuration( (Map) yaml.get().loadAs( reader, Map.class ), null ); + return conf; + } + + @Override + @SuppressWarnings("unchecked") + public Configuration load(String string) + { + Configuration conf = new Configuration( (Map) yaml.get().loadAs( string, Map.class ), null ); + return conf; + } +} diff --git a/protocol/pom.xml b/protocol/pom.xml index c87c8171..a347f5cc 100644 --- a/protocol/pom.xml +++ b/protocol/pom.xml @@ -17,4 +17,12 @@ BungeeCord-Protocol Minimal implementation of the Minecraft protocol for use in BungeeCord + + + + io.netty + netty-buffer + 4.0.0.Beta2 + + diff --git a/protocol/src/main/java/net/md_5/mendax/PacketDefinitions.java b/protocol/src/main/java/net/md_5/bungee/protocol/PacketDefinitions.java similarity index 95% rename from protocol/src/main/java/net/md_5/mendax/PacketDefinitions.java rename to protocol/src/main/java/net/md_5/bungee/protocol/PacketDefinitions.java index 4fdd1be6..bbe4243a 100644 --- a/protocol/src/main/java/net/md_5/mendax/PacketDefinitions.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/PacketDefinitions.java @@ -1,14 +1,13 @@ -package net.md_5.mendax; +package net.md_5.bungee.protocol; -import static net.md_5.mendax.PacketDefinitions.OpCode.*; +import static net.md_5.bungee.protocol.PacketDefinitions.OpCode.*; public class PacketDefinitions { - private static final int MAX_PACKET = 256; - public static final OpCode[][] opCodes = new OpCode[ MAX_PACKET * 2 ][]; + public static final OpCode[][] opCodes = new OpCode[ 512 ][]; public static final int VANILLA_PROTOCOL = 0; - public static final int FORGE_PROTOCOL = MAX_PACKET; + public static final int FORGE_PROTOCOL = 256; public enum OpCode { @@ -312,7 +311,8 @@ public class PacketDefinitions }; opCodes[0xFE] = new OpCode[] { - }; // Should be byte, screw you too bitchy server admins! + BYTE + }; opCodes[0xFF] = new OpCode[] { STRING diff --git a/protocol/src/main/java/net/md_5/mendax/datainput/BulkChunk.java b/protocol/src/main/java/net/md_5/bungee/protocol/netty/BulkChunk.java similarity index 54% rename from protocol/src/main/java/net/md_5/mendax/datainput/BulkChunk.java rename to protocol/src/main/java/net/md_5/bungee/protocol/netty/BulkChunk.java index 5f1b31b0..37499018 100644 --- a/protocol/src/main/java/net/md_5/mendax/datainput/BulkChunk.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/netty/BulkChunk.java @@ -1,17 +1,17 @@ -package net.md_5.mendax.datainput; +package net.md_5.bungee.protocol.netty; -import java.io.DataInput; +import io.netty.buffer.ByteBuf; import java.io.IOException; public class BulkChunk extends Instruction { @Override - void read(DataInput in, byte[] buffer) throws IOException + void read(ByteBuf in) throws IOException { short count = in.readShort(); int size = in.readInt(); in.readBoolean(); - skip( in, buffer, size + count * 12 ); + skip( in, size + count * 12 ); } } diff --git a/protocol/src/main/java/net/md_5/mendax/datainput/ByteHeader.java b/protocol/src/main/java/net/md_5/bungee/protocol/netty/ByteHeader.java similarity index 65% rename from protocol/src/main/java/net/md_5/mendax/datainput/ByteHeader.java rename to protocol/src/main/java/net/md_5/bungee/protocol/netty/ByteHeader.java index a4bc11da..24fd1ac4 100644 --- a/protocol/src/main/java/net/md_5/mendax/datainput/ByteHeader.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/netty/ByteHeader.java @@ -1,6 +1,6 @@ -package net.md_5.mendax.datainput; +package net.md_5.bungee.protocol.netty; -import java.io.DataInput; +import io.netty.buffer.ByteBuf; import java.io.IOException; class ByteHeader extends Instruction @@ -14,12 +14,12 @@ class ByteHeader extends Instruction } @Override - void read(DataInput in, byte[] buffer) throws IOException + void read(ByteBuf in) throws IOException { byte size = in.readByte(); for ( byte b = 0; b < size; b++ ) { - child.read( in, buffer ); + child.read( in ); } } } diff --git a/protocol/src/main/java/net/md_5/mendax/datainput/Instruction.java b/protocol/src/main/java/net/md_5/bungee/protocol/netty/Instruction.java similarity index 82% rename from protocol/src/main/java/net/md_5/mendax/datainput/Instruction.java rename to protocol/src/main/java/net/md_5/bungee/protocol/netty/Instruction.java index d246558a..6b84dc59 100644 --- a/protocol/src/main/java/net/md_5/mendax/datainput/Instruction.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/netty/Instruction.java @@ -1,6 +1,6 @@ -package net.md_5.mendax.datainput; +package net.md_5.bungee.protocol.netty; -import java.io.DataInput; +import io.netty.buffer.ByteBuf; import java.io.IOException; abstract class Instruction @@ -27,10 +27,10 @@ abstract class Instruction // Illegal forward references below this line static final Instruction BYTE_INT = new ByteHeader( INT ); - abstract void read(DataInput in, byte[] buffer) throws IOException; + abstract void read(ByteBuf in) throws IOException; - final void skip(DataInput in, byte[] buffer, int len) throws IOException + final void skip(ByteBuf in, int len) throws IOException { - in.readFully( buffer, 0, len ); + in.readerIndex( in.readerIndex() + len ); } } diff --git a/protocol/src/main/java/net/md_5/mendax/datainput/IntHeader.java b/protocol/src/main/java/net/md_5/bungee/protocol/netty/IntHeader.java similarity index 65% rename from protocol/src/main/java/net/md_5/mendax/datainput/IntHeader.java rename to protocol/src/main/java/net/md_5/bungee/protocol/netty/IntHeader.java index 31a5c18d..2f1a011b 100644 --- a/protocol/src/main/java/net/md_5/mendax/datainput/IntHeader.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/netty/IntHeader.java @@ -1,6 +1,6 @@ -package net.md_5.mendax.datainput; +package net.md_5.bungee.protocol.netty; -import java.io.DataInput; +import io.netty.buffer.ByteBuf; import java.io.IOException; class IntHeader extends Instruction @@ -14,12 +14,12 @@ class IntHeader extends Instruction } @Override - void read(DataInput in, byte[] buffer) throws IOException + void read(ByteBuf in) throws IOException { int size = in.readInt(); for ( int i = 0; i < size; i++ ) { - child.read( in, buffer ); + child.read( in ); } } } diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/netty/Item.java b/protocol/src/main/java/net/md_5/bungee/protocol/netty/Item.java new file mode 100644 index 00000000..5026b521 --- /dev/null +++ b/protocol/src/main/java/net/md_5/bungee/protocol/netty/Item.java @@ -0,0 +1,19 @@ +package net.md_5.bungee.protocol.netty; + +import io.netty.buffer.ByteBuf; +import java.io.IOException; + +class Item extends Instruction +{ + + @Override + void read(ByteBuf in) throws IOException + { + short type = in.readShort(); + if ( type >= 0 ) + { + skip( in, 3 ); + SHORT_BYTE.read( in ); + } + } +} diff --git a/protocol/src/main/java/net/md_5/mendax/datainput/Jump.java b/protocol/src/main/java/net/md_5/bungee/protocol/netty/Jump.java similarity index 62% rename from protocol/src/main/java/net/md_5/mendax/datainput/Jump.java rename to protocol/src/main/java/net/md_5/bungee/protocol/netty/Jump.java index 5bf82fc7..0c9ed2a4 100644 --- a/protocol/src/main/java/net/md_5/mendax/datainput/Jump.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/netty/Jump.java @@ -1,6 +1,6 @@ -package net.md_5.mendax.datainput; +package net.md_5.bungee.protocol.netty; -import java.io.DataInput; +import io.netty.buffer.ByteBuf; import java.io.IOException; class Jump extends Instruction @@ -18,8 +18,8 @@ class Jump extends Instruction } @Override - void read(DataInput in, byte[] buffer) throws IOException + void read(ByteBuf in) throws IOException { - skip( in, buffer, len ); + skip( in, len ); } } diff --git a/protocol/src/main/java/net/md_5/mendax/datainput/MetaData.java b/protocol/src/main/java/net/md_5/bungee/protocol/netty/MetaData.java similarity index 63% rename from protocol/src/main/java/net/md_5/mendax/datainput/MetaData.java rename to protocol/src/main/java/net/md_5/bungee/protocol/netty/MetaData.java index 6dc04584..7ebc3c16 100644 --- a/protocol/src/main/java/net/md_5/mendax/datainput/MetaData.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/netty/MetaData.java @@ -1,13 +1,13 @@ -package net.md_5.mendax.datainput; +package net.md_5.bungee.protocol.netty; -import java.io.DataInput; +import io.netty.buffer.ByteBuf; import java.io.IOException; class MetaData extends Instruction { @Override - void read(DataInput in, byte[] buffer) throws IOException + void read(ByteBuf in) throws IOException { int x = in.readUnsignedByte(); while ( x != 127 ) @@ -16,25 +16,25 @@ class MetaData extends Instruction switch ( type ) { case 0: - BYTE.read( in, buffer ); + BYTE.read( in ); break; case 1: - SHORT.read( in, buffer ); + SHORT.read( in ); break; case 2: - INT.read( in, buffer ); + INT.read( in ); break; case 3: - FLOAT.read( in, buffer ); + FLOAT.read( in ); break; case 4: - STRING.read( in, buffer ); + STRING.read( in ); break; case 5: - ITEM.read( in, buffer ); + ITEM.read( in ); break; case 6: - skip( in, buffer, 12 ); // int, int, int + skip( in, 12 ); // int, int, int break; default: throw new IllegalArgumentException( "Unknown metadata type " + type ); diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/netty/OptionalMotion.java b/protocol/src/main/java/net/md_5/bungee/protocol/netty/OptionalMotion.java new file mode 100644 index 00000000..7e304007 --- /dev/null +++ b/protocol/src/main/java/net/md_5/bungee/protocol/netty/OptionalMotion.java @@ -0,0 +1,18 @@ +package net.md_5.bungee.protocol.netty; + +import io.netty.buffer.ByteBuf; +import java.io.IOException; + +class OptionalMotion extends Instruction +{ + + @Override + void read(ByteBuf in) throws IOException + { + int data = in.readInt(); + if ( data > 0 ) + { + skip( in, 6 ); + } + } +} diff --git a/protocol/src/main/java/net/md_5/mendax/datainput/DataInputPacketReader.java b/protocol/src/main/java/net/md_5/bungee/protocol/netty/PacketReader.java similarity index 80% rename from protocol/src/main/java/net/md_5/mendax/datainput/DataInputPacketReader.java rename to protocol/src/main/java/net/md_5/bungee/protocol/netty/PacketReader.java index c697b9a0..0adfb864 100644 --- a/protocol/src/main/java/net/md_5/mendax/datainput/DataInputPacketReader.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/netty/PacketReader.java @@ -1,16 +1,16 @@ -package net.md_5.mendax.datainput; +package net.md_5.bungee.protocol.netty; -import java.io.DataInput; +import io.netty.buffer.ByteBuf; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import net.md_5.mendax.PacketDefinitions; -import net.md_5.mendax.PacketDefinitions.OpCode; +import net.md_5.bungee.protocol.PacketDefinitions; +import net.md_5.bungee.protocol.PacketDefinitions.OpCode; -public class DataInputPacketReader +public class PacketReader { - private static final Instruction[][] instructions = new Instruction[ 256 ][]; + private static final Instruction[][] instructions = new Instruction[ PacketDefinitions.opCodes.length ][]; static { @@ -59,7 +59,7 @@ public class DataInputPacketReader } } - private static void readPacket(int packetId, DataInput in, byte[] buffer, int protocol) throws IOException + private static void readPacket(int packetId, ByteBuf in, int protocol) throws IOException { Instruction[] packetDef = null; if ( packetId + protocol < instructions.length ) @@ -74,20 +74,20 @@ public class DataInputPacketReader throw new IOException( "Unknown packet id " + packetId ); } else { - readPacket( packetId, in, buffer, PacketDefinitions.VANILLA_PROTOCOL ); + readPacket( packetId, in, PacketDefinitions.VANILLA_PROTOCOL ); return; } } for ( Instruction instruction : packetDef ) { - instruction.read( in, buffer ); + instruction.read( in ); } } - public static void readPacket(DataInput in, byte[] buffer, int protocol) throws IOException + public static void readPacket(ByteBuf in, int protocol) throws IOException { int packetId = in.readUnsignedByte(); - readPacket( packetId, in, buffer, protocol ); + readPacket( packetId, in, protocol ); } } diff --git a/protocol/src/main/java/net/md_5/mendax/datainput/ShortHeader.java b/protocol/src/main/java/net/md_5/bungee/protocol/netty/ShortHeader.java similarity index 66% rename from protocol/src/main/java/net/md_5/mendax/datainput/ShortHeader.java rename to protocol/src/main/java/net/md_5/bungee/protocol/netty/ShortHeader.java index b274af61..ece0ae3c 100644 --- a/protocol/src/main/java/net/md_5/mendax/datainput/ShortHeader.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/netty/ShortHeader.java @@ -1,6 +1,6 @@ -package net.md_5.mendax.datainput; +package net.md_5.bungee.protocol.netty; -import java.io.DataInput; +import io.netty.buffer.ByteBuf; import java.io.IOException; class ShortHeader extends Instruction @@ -14,12 +14,12 @@ class ShortHeader extends Instruction } @Override - void read(DataInput in, byte[] buffer) throws IOException + void read(ByteBuf in) throws IOException { short size = in.readShort(); for ( short s = 0; s < size; s++ ) { - child.read( in, buffer ); + child.read( in ); } } } diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/netty/UnsignedShortByte.java b/protocol/src/main/java/net/md_5/bungee/protocol/netty/UnsignedShortByte.java new file mode 100644 index 00000000..2b409884 --- /dev/null +++ b/protocol/src/main/java/net/md_5/bungee/protocol/netty/UnsignedShortByte.java @@ -0,0 +1,15 @@ +package net.md_5.bungee.protocol.netty; + +import io.netty.buffer.ByteBuf; +import java.io.IOException; + +class UnsignedShortByte extends Instruction +{ + + @Override + void read(ByteBuf in) throws IOException + { + int size = in.readUnsignedShort(); + skip( in, size ); + } +} diff --git a/protocol/src/main/java/net/md_5/mendax/datainput/Item.java b/protocol/src/main/java/net/md_5/mendax/datainput/Item.java deleted file mode 100644 index 411c931b..00000000 --- a/protocol/src/main/java/net/md_5/mendax/datainput/Item.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.md_5.mendax.datainput; - -import java.io.DataInput; -import java.io.IOException; - -class Item extends Instruction -{ - - @Override - void read(DataInput in, byte[] buffer) throws IOException - { - short type = in.readShort(); - if ( type >= 0 ) - { - skip( in, buffer, 3 ); - SHORT_BYTE.read( in, buffer ); - } - } -} diff --git a/protocol/src/main/java/net/md_5/mendax/datainput/OptionalMotion.java b/protocol/src/main/java/net/md_5/mendax/datainput/OptionalMotion.java deleted file mode 100644 index 55c36f65..00000000 --- a/protocol/src/main/java/net/md_5/mendax/datainput/OptionalMotion.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.md_5.mendax.datainput; - -import java.io.DataInput; -import java.io.IOException; - -public class OptionalMotion extends Instruction -{ - - @Override - void read(DataInput in, byte[] buffer) throws IOException - { - int data = in.readInt(); - if ( data > 0 ) - { - skip( in, buffer, 6 ); - } - } -} diff --git a/protocol/src/main/java/net/md_5/mendax/datainput/UnsignedShortByte.java b/protocol/src/main/java/net/md_5/mendax/datainput/UnsignedShortByte.java deleted file mode 100644 index 4b1db5a2..00000000 --- a/protocol/src/main/java/net/md_5/mendax/datainput/UnsignedShortByte.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.md_5.mendax.datainput; - -import java.io.DataInput; -import java.io.IOException; - -public class UnsignedShortByte extends Instruction -{ - - @Override - void read(DataInput in, byte[] buffer) throws IOException - { - int size = in.readUnsignedShort(); - skip( in, buffer, size ); - } -} diff --git a/proxy/pom.xml b/proxy/pom.xml index 7163f7f9..210fde51 100644 --- a/proxy/pom.xml +++ b/proxy/pom.xml @@ -19,6 +19,11 @@ Proxy component of the Elastic Portal Suite + + io.netty + netty-all + 4.0.0.Beta2 + net.md-5 bungeecord-protocol diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java index c9dd9ad0..4ffd581f 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -1,5 +1,15 @@ package net.md_5.bungee; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.MultithreadEventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.handler.timeout.ReadTimeoutHandler; import net.md_5.bungee.config.Configuration; import java.io.BufferedReader; import java.io.File; @@ -10,13 +20,10 @@ import java.net.Socket; import java.util.Collection; import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -36,6 +43,9 @@ import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.PluginManager; import net.md_5.bungee.command.*; import net.md_5.bungee.config.YamlConfig; +import net.md_5.bungee.netty.ChannelBootstrapper; +import net.md_5.bungee.netty.HandlerBoss; +import net.md_5.bungee.netty.PacketDecoder; import net.md_5.bungee.packet.DefinedPacket; import net.md_5.bungee.packet.PacketFAPluginMessage; @@ -64,7 +74,7 @@ public class BungeeCord extends ProxyServer /** * Thread pool. */ - public final ExecutorService threadPool = Executors.newCachedThreadPool(); + public final MultithreadEventLoopGroup eventLoops = new NioEventLoopGroup( 8, new ThreadFactoryBuilder().setNameFormat( "Netty IO Thread - %1$d" ).build() ); /** * locations.yml save thread. */ @@ -72,7 +82,7 @@ public class BungeeCord extends ProxyServer /** * Server socket listener. */ - private Collection listeners = new HashSet<>(); + private Collection listeners = new HashSet<>(); /** * Fully qualified connections. */ @@ -149,6 +159,7 @@ public class BungeeCord extends ProxyServer * * @throws IOException */ + @Override public void start() throws IOException { File plugins = new File( "plugins" ); @@ -181,30 +192,26 @@ public class BungeeCord extends ProxyServer { for ( ListenerInfo info : config.getListeners() ) { - try - { - ListenThread listener = new ListenThread( info ); - listener.start(); - listeners.add( listener ); - $().info( "Listening on " + info.getHost() ); - } catch ( IOException ex ) - { - $().log( Level.SEVERE, "Could not start listener " + info, ex ); - } + Channel server = new ServerBootstrap() + .childHandler( ChannelBootstrapper.SERVER ) + .localAddress( info.getHost() ) + .group( eventLoops ) + .bind().channel(); + listeners.add( server ); + + $().info( "Listening on " + info.getHost() ); } } public void stopListeners() { - for ( ListenThread listener : listeners ) + for ( Channel listener : listeners ) { - $().log( Level.INFO, "Closing listen thread {0}", listener.socket ); + $().log( Level.INFO, "Closing listener {0}", listener ); try { - listener.interrupt(); - listener.socket.close(); - listener.join(); - } catch ( InterruptedException | IOException ex ) + listener.close().syncUninterruptibly(); + } catch ( ChannelException ex ) { $().severe( "Could not close listen thread" ); } @@ -219,7 +226,6 @@ public class BungeeCord extends ProxyServer stopListeners(); $().info( "Closing pending connections" ); - threadPool.shutdown(); $().info( "Disconnecting " + connections.size() + " connections" ); for ( UserConnection user : connections.values() ) @@ -227,6 +233,9 @@ public class BungeeCord extends ProxyServer user.disconnect( "Proxy restarting, brb." ); } + $().info( "Closing IO threads" ); + eventLoops.shutdown(); + $().info( "Saving reconnect locations" ); reconnectHandler.save(); saveThread.cancel(); @@ -241,20 +250,6 @@ public class BungeeCord extends ProxyServer System.exit( 0 ); } - /** - * Miscellaneous method to set options on a socket based on those in the - * configuration. - * - * @param socket to set the options on - * @throws IOException when the underlying set methods thrown an exception - */ - public void setSocketOptions(Socket socket) throws IOException - { - socket.setSoTimeout( config.getTimeout() ); - socket.setTrafficClass( 0x18 ); - socket.setTcpNoDelay( true ); - } - /** * Broadcasts a packet to all clients that is connected to this instance. * diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java b/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java index a17e08f7..efe6d0c2 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java @@ -15,7 +15,7 @@ import net.md_5.bungee.packet.DefinedPacket; import net.md_5.bungee.packet.PacketFAPluginMessage; import net.md_5.bungee.packet.PacketFFKick; import net.md_5.bungee.packet.PacketStream; -import net.md_5.mendax.PacketDefinitions; +import net.md_5.bungee.protocol.PacketDefinitions; public class BungeeServerInfo extends ServerInfo { diff --git a/proxy/src/main/java/net/md_5/bungee/EntityMap.java b/proxy/src/main/java/net/md_5/bungee/EntityMap.java index ce9ad4cb..5a7a4b36 100644 --- a/proxy/src/main/java/net/md_5/bungee/EntityMap.java +++ b/proxy/src/main/java/net/md_5/bungee/EntityMap.java @@ -1,5 +1,7 @@ package net.md_5.bungee; +import io.netty.buffer.ByteBuf; + /** * Class to rewrite integers within packets. */ @@ -113,20 +115,20 @@ public class EntityMap }; } - public static void rewrite(byte[] packet, int oldId, int newId) + public static void rewrite(ByteBuf packet, int oldId, int newId) { - int packetId = Util.getId( packet ); + int packetId = packet.getUnsignedShort( 0 ); if ( packetId == 0x1D ) { // bulk entity - for ( int pos = 2; pos < packet.length; pos += 4 ) + for ( int pos = 2; pos < packet.writerIndex(); pos += 4 ) { - int readId = readInt( packet, pos ); + int readId = packet.getInt( pos ); if ( readId == oldId ) { - setInt( packet, pos, newId ); + packet.setInt( pos, newId ); } else if ( readId == newId ) { - setInt( packet, pos, oldId ); + packet.setInt( pos, oldId ); } } } else @@ -136,29 +138,16 @@ public class EntityMap { for ( int pos : idArray ) { - int readId = readInt( packet, pos ); + int readId = packet.getInt( pos ); if ( readId == oldId ) { - setInt( packet, pos, newId ); + packet.setInt( pos, newId ); } else if ( readId == newId ) { - setInt( packet, pos, oldId ); + packet.setInt( pos, oldId ); } } } } } - - private static void setInt(byte[] buf, int pos, int i) - { - buf[pos] = (byte) ( i >> 24 ); - buf[pos + 1] = (byte) ( i >> 16 ); - buf[pos + 2] = (byte) ( i >> 8 ); - buf[pos + 3] = (byte) i; - } - - private static int readInt(byte[] buf, int pos) - { - return ( ( ( buf[pos] & 0xFF ) << 24 ) | ( ( buf[pos + 1] & 0xFF ) << 16 ) | ( ( buf[pos + 2] & 0xFF ) << 8 ) | buf[pos + 3] & 0xFF ); - } } diff --git a/proxy/src/main/java/net/md_5/bungee/InitialHandler.java b/proxy/src/main/java/net/md_5/bungee/InitialHandler.java index ab57c741..9238998f 100644 --- a/proxy/src/main/java/net/md_5/bungee/InitialHandler.java +++ b/proxy/src/main/java/net/md_5/bungee/InitialHandler.java @@ -32,7 +32,7 @@ import net.md_5.bungee.packet.PacketFEPing; import net.md_5.bungee.packet.PacketFFKick; import net.md_5.bungee.packet.PacketHandler; import net.md_5.bungee.packet.PacketStream; -import net.md_5.mendax.PacketDefinitions; +import net.md_5.bungee.protocol.PacketDefinitions; public class InitialHandler extends PacketHandler implements Runnable, PendingConnection { diff --git a/proxy/src/main/java/net/md_5/bungee/ListenThread.java b/proxy/src/main/java/net/md_5/bungee/ListenThread.java deleted file mode 100644 index d0e52973..00000000 --- a/proxy/src/main/java/net/md_5/bungee/ListenThread.java +++ /dev/null @@ -1,48 +0,0 @@ -package net.md_5.bungee; - -import java.io.IOException; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; -import static net.md_5.bungee.Logger.$; -import net.md_5.bungee.api.config.ListenerInfo; - -/** - * Thread to listen and dispatch incoming connections to the proxy. - */ -public class ListenThread extends Thread -{ - - public final ServerSocket socket; - private final ListenerInfo info; - - public ListenThread(ListenerInfo info) throws IOException - { - super( "Listen Thread - " + info ); - this.info = info; - socket = new ServerSocket(); - socket.bind( info.getHost() ); - } - - @Override - public void run() - { - while ( !isInterrupted() ) - { - try - { - Socket client = socket.accept(); - BungeeCord.getInstance().setSocketOptions( client ); - $().info( client.getInetAddress() + " has connected" ); - InitialHandler handler = new InitialHandler( client, info ); - BungeeCord.getInstance().threadPool.submit( handler ); - } catch ( SocketException ex ) - { - ex.printStackTrace(); // Now people can see why their operating system is failing them and stop bitching at me! - } catch ( IOException ex ) - { - ex.printStackTrace(); // TODO - } - } - } -} diff --git a/proxy/src/main/java/net/md_5/bungee/ServerConnector.java b/proxy/src/main/java/net/md_5/bungee/ServerConnector.java index b080164e..6b5ebe38 100644 --- a/proxy/src/main/java/net/md_5/bungee/ServerConnector.java +++ b/proxy/src/main/java/net/md_5/bungee/ServerConnector.java @@ -1,13 +1,16 @@ package net.md_5.bungee; import com.google.common.base.Preconditions; -import java.io.IOException; -import java.net.Socket; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.socket.nio.NioSocketChannel; import java.util.Queue; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.event.ServerConnectedEvent; +import net.md_5.bungee.netty.ChannelBootstrapper; import net.md_5.bungee.packet.DefinedPacket; import net.md_5.bungee.packet.Packet1Login; import net.md_5.bungee.packet.PacketCDClientStatus; @@ -39,6 +42,18 @@ public class ServerConnector extends PacketHandler { Preconditions.checkState( thisState == State.LOGIN, "Not exepcting LOGIN" ); loginPacket = login; + + ServerConnection server = new ServerConnection( socket, info, stream, connector.loginPacket ); + ServerConnectedEvent event = new ServerConnectedEvent( user, server ); + ProxyServer.getInstance().getPluginManager().callEvent( event ); + + stream.write( BungeeCord.getInstance().registerChannels() ); + + Queue packetQueue = ( (BungeeServerInfo) info ).getPacketQueue(); + while ( !packetQueue.isEmpty() ) + { + stream.write( packetQueue.poll() ); + } thisState = State.FINISHED; } @@ -55,63 +70,33 @@ public class ServerConnector extends PacketHandler throw new KickException( kick.message ); } - public static ServerConnection connect(UserConnection user, ServerInfo info, boolean retry) + public static void connect(final UserConnection user, final ServerInfo info, final boolean retry) { - Socket socket = null; - try + new Bootstrap() + .channel( NioSocketChannel.class ) + .group( BungeeCord.getInstance().eventLoops ) + .handler( ChannelBootstrapper.CLIENT ) + .remoteAddress( info.getAddress() ) + .connect().addListener( new ChannelFutureListener() { - socket = new Socket(); - socket.connect( info.getAddress(), BungeeCord.getInstance().config.getTimeout() ); - BungeeCord.getInstance().setSocketOptions( socket ); - PacketStream stream = new PacketStream( socket.getInputStream(), socket.getOutputStream(), user.stream.getProtocol() ); - - ServerConnector connector = new ServerConnector( stream ); - stream.write( user.handshake ); - stream.write( PacketCDClientStatus.CLIENT_LOGIN ); - - while ( connector.thisState != State.FINISHED ) + @Override + public void operationComplete(ChannelFuture future) throws Exception { - byte[] buf = stream.readPacket(); - DefinedPacket packet = DefinedPacket.packet( buf ); - packet.handle( connector ); - } - - ServerConnection server = new ServerConnection( socket, info, stream, connector.loginPacket ); - ServerConnectedEvent event = new ServerConnectedEvent( user, server ); - ProxyServer.getInstance().getPluginManager().callEvent( event ); - - stream.write( BungeeCord.getInstance().registerChannels() ); - - Queue packetQueue = ( (BungeeServerInfo) info ).getPacketQueue(); - while ( !packetQueue.isEmpty() ) - { - stream.write( packetQueue.poll() ); - } - return server; - } catch ( Exception ex ) - { - if ( socket != null ) - { - try + if ( future.isSuccess() ) { - socket.close(); - } catch ( IOException ioe ) + future.channel().write( user.handshake ); + future.channel().write( PacketCDClientStatus.CLIENT_LOGIN ); + } else { + future.channel().close(); + ServerInfo def = ProxyServer.getInstance().getServers().get( user.getPendingConnection().getListener().getDefaultServer() ); + if ( retry && !info.equals( def ) ) + { + user.sendMessage( ChatColor.RED + "Could not connect to target server, you have been moved to the default server" ); + connect( user, def, false ); + } } } - ServerInfo def = ProxyServer.getInstance().getServers().get( user.getPendingConnection().getListener().getDefaultServer() ); - if ( retry && !info.equals( def ) ) - { - user.sendMessage( ChatColor.RED + "Could not connect to target server, you have been moved to the default server" ); - return connect( user, def, false ); - } else - { - if ( ex instanceof RuntimeException ) - { - throw (RuntimeException) ex; - } - throw new RuntimeException( "Could not connect to target server " + Util.exception( ex ) ); - } - } + } ).channel(); } } diff --git a/proxy/src/main/java/net/md_5/bungee/Util.java b/proxy/src/main/java/net/md_5/bungee/Util.java index a96755bb..605cbe22 100644 --- a/proxy/src/main/java/net/md_5/bungee/Util.java +++ b/proxy/src/main/java/net/md_5/bungee/Util.java @@ -27,18 +27,6 @@ public class Util return new InetSocketAddress( split[0], port ); } - /** - * Gets the value of the first unsigned byte of the specified array. Useful - * for getting the id of a packet array . - * - * @param b the array to read from - * @return the unsigned value of the first byte - */ - public static int getId(byte[] b) - { - return b[0] & 0xFF; - } - /** * Normalizes a config path by prefix upper case letters with '_' and * turning them to lowercase. diff --git a/proxy/src/main/java/net/md_5/bungee/netty/ChannelBootstrapper.java b/proxy/src/main/java/net/md_5/bungee/netty/ChannelBootstrapper.java new file mode 100644 index 00000000..928ec25d --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/netty/ChannelBootstrapper.java @@ -0,0 +1,49 @@ +package net.md_5.bungee.netty; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.handler.timeout.ReadTimeoutHandler; +import java.lang.reflect.Constructor; +import java.util.concurrent.TimeUnit; +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.InitialHandler; +import net.md_5.bungee.ServerConnector; +import net.md_5.bungee.packet.PacketHandler; +import net.md_5.bungee.protocol.PacketDefinitions; + +public class ChannelBootstrapper extends ChannelInitializer +{ + + public static ChannelBootstrapper CLIENT = new ChannelBootstrapper( InitialHandler.class ); + public static ChannelBootstrapper SERVER = new ChannelBootstrapper( ServerConnector.class ); + private final Constructor initial; + + private ChannelBootstrapper(Class initialHandler) + { + try + { + this.initial = initialHandler.getDeclaredConstructor(); + } catch ( NoSuchMethodException ex ) + { + throw new ExceptionInInitializerError( ex ); + } + } + + @Override + protected void initChannel(Channel ch) throws Exception + { + try + { + ch.config().setOption( ChannelOption.IP_TOS, 0x18 ); + } catch ( ChannelException ex ) + { + // IP_TOS is not supported (Windows XP / Windows Server 2003) + } + ch.pipeline().addLast( "timer", new ReadTimeoutHandler( BungeeCord.getInstance().config.getTimeout(), TimeUnit.MILLISECONDS ) ); + ch.pipeline().addLast( "decoder", new PacketDecoder( PacketDefinitions.VANILLA_PROTOCOL ) ); + + ch.pipeline().addLast( "handler", new HandlerBoss( initial.newInstance() ) ); + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/netty/CipherCodec.java b/proxy/src/main/java/net/md_5/bungee/netty/CipherCodec.java new file mode 100644 index 00000000..b569e406 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/netty/CipherCodec.java @@ -0,0 +1,76 @@ +package net.md_5.bungee.netty; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToByteCodec; +import javax.crypto.Cipher; +import javax.crypto.ShortBufferException; + +/** + * This class is a complete solution for encrypting and decoding bytes in a + * Netty stream. It takes two {@link BufferedBlockCipher} instances, used for + * encryption and decryption respectively. + */ +public class CipherCodec extends ByteToByteCodec +{ + + private Cipher encrypt; + private Cipher decrypt; + private ByteBuf heapOut; + + public CipherCodec(Cipher encrypt, Cipher decrypt) + { + this.encrypt = encrypt; + this.decrypt = decrypt; + } + + @Override + public void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception + { + if ( heapOut == null ) + { + heapOut = ctx.alloc().heapBuffer(); + } + cipher( encrypt, in, heapOut ); + out.writeBytes( heapOut ); + heapOut.discardSomeReadBytes(); + } + + @Override + public void decode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception + { + cipher( decrypt, in, out ); + } + + @Override + public void freeInboundBuffer(ChannelHandlerContext ctx) throws Exception + { + super.freeInboundBuffer( ctx ); + decrypt = null; + } + + @Override + public void freeOutboundBuffer(ChannelHandlerContext ctx) throws Exception + { + super.freeOutboundBuffer( ctx ); + if ( heapOut != null ) + { + heapOut.release(); + heapOut = null; + } + encrypt = null; + } + + private void cipher(Cipher cipher, ByteBuf in, ByteBuf out) throws ShortBufferException + { + int available = in.readableBytes(); + int outputSize = cipher.getOutputSize( available ); + if ( out.capacity() < outputSize ) + { + out.capacity( outputSize ); + } + int processed = cipher.update( in.array(), in.arrayOffset() + in.readerIndex(), available, out.array(), out.arrayOffset() + out.writerIndex() ); + in.readerIndex( in.readerIndex() + processed ); + out.writerIndex( out.writerIndex() + processed ); + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/netty/HandlerBoss.java b/proxy/src/main/java/net/md_5/bungee/netty/HandlerBoss.java new file mode 100644 index 00000000..38f37419 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/netty/HandlerBoss.java @@ -0,0 +1,45 @@ +package net.md_5.bungee.netty; + +import com.google.common.base.Preconditions; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundMessageHandlerAdapter; +import net.md_5.bungee.packet.DefinedPacket; +import net.md_5.bungee.packet.PacketHandler; + +public class HandlerBoss extends ChannelInboundMessageHandlerAdapter +{ + + private PacketHandler handler; + + HandlerBoss(PacketHandler handler) + { + Preconditions.checkArgument( handler != null, "handler" ); + this.handler = handler; + } + + @Override + protected void messageReceived(ChannelHandlerContext ctx, ByteBuf msg) throws Exception + { + if ( ctx.channel().isActive() ) + { + DefinedPacket packet = DefinedPacket.packet( msg ); + if ( packet != null ) + { + handler.handle( packet ); + } else + { + handler.handle( msg ); + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception + { + if ( ctx.channel().isActive() ) + { + ctx.close(); + } + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/netty/PacketDecoder.java b/proxy/src/main/java/net/md_5/bungee/netty/PacketDecoder.java new file mode 100644 index 00000000..5dffe828 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/netty/PacketDecoder.java @@ -0,0 +1,25 @@ +package net.md_5.bungee.netty; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ReplayingDecoder; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import net.md_5.bungee.protocol.netty.PacketReader; + +@AllArgsConstructor +public class PacketDecoder extends ReplayingDecoder +{ + + @Getter + @Setter + private int protocol; + + @Override + protected ByteBuf decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception + { + PacketReader.readPacket( in, protocol ); + return in.copy(); + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/packet/DefinedPacket.java b/proxy/src/main/java/net/md_5/bungee/packet/DefinedPacket.java index d7c20cce..a3f53fca 100644 --- a/proxy/src/main/java/net/md_5/bungee/packet/DefinedPacket.java +++ b/proxy/src/main/java/net/md_5/bungee/packet/DefinedPacket.java @@ -3,6 +3,7 @@ package net.md_5.bungee.packet; import com.google.common.base.Preconditions; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; +import io.netty.buffer.ByteBuf; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.DataOutput; @@ -196,9 +197,9 @@ public abstract class DefinedPacket implements DataOutput public abstract void handle(PacketHandler handler) throws Exception; private static Class[] classes = new Class[ 256 ]; - public static DefinedPacket packet(byte[] buf) + public static DefinedPacket packet(ByteBuf buf) { - int id = Util.getId( buf ); + int id = buf.getUnsignedShort( 0); Class clazz = classes[id]; DefinedPacket ret = null; if ( clazz != null ) @@ -213,6 +214,8 @@ public abstract class DefinedPacket implements DataOutput } catch ( IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException ex ) { } + } else { + return null; } Preconditions.checkState( ret != null, "Don't know how to deal with packet ID %s", Util.hex( id ) ); diff --git a/proxy/src/main/java/net/md_5/bungee/packet/PacketHandler.java b/proxy/src/main/java/net/md_5/bungee/packet/PacketHandler.java index 04184256..7826d0af 100644 --- a/proxy/src/main/java/net/md_5/bungee/packet/PacketHandler.java +++ b/proxy/src/main/java/net/md_5/bungee/packet/PacketHandler.java @@ -1,11 +1,23 @@ package net.md_5.bungee.packet; +import io.netty.buffer.ByteBuf; + public abstract class PacketHandler { - private void nop(DefinedPacket packet) + private void nop(Object msg) { - throw new UnsupportedOperationException( "No handler defined for packet " + packet.getClass() ); + throw new UnsupportedOperationException( "No handler defined for packet " + msg.getClass() ); + } + + public void handle(ByteBuf buf) throws Exception + { + nop( buf ); + } + + public void handle(DefinedPacket packet) throws Exception + { + nop( packet ); } public void handle(Packet0KeepAlive alive) throws Exception diff --git a/proxy/src/main/java/net/md_5/bungee/packet/PacketStream.java b/proxy/src/main/java/net/md_5/bungee/packet/PacketStream.java deleted file mode 100644 index 01413568..00000000 --- a/proxy/src/main/java/net/md_5/bungee/packet/PacketStream.java +++ /dev/null @@ -1,101 +0,0 @@ -package net.md_5.bungee.packet; - -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import lombok.Getter; -import lombok.Setter; -import net.md_5.mendax.datainput.DataInputPacketReader; - -/** - * A specialized input stream to parse packets using the Mojang packet - * definitions and then return them as a byte array. - */ -public class PacketStream implements AutoCloseable -{ - - private final DataInputStream dataInput; - @Getter - private OutputStream out; - @Getter - @Setter - private int protocol; - private final TrackingInputStream tracker; - private final byte[] buffer = new byte[ 1 << 18 ]; - - public PacketStream(InputStream in, int protocol) - { - this( in, null, protocol ); - } - - public PacketStream(InputStream in, OutputStream out, int protocol) - { - tracker = new TrackingInputStream( in ); - dataInput = new DataInputStream( tracker ); - this.out = out; - this.protocol = protocol; - } - - public void write(byte[] b) throws IOException - { - out.write( b ); - } - - public void write(DefinedPacket packet) throws IOException - { - out.write( packet.getPacket() ); - } - - /** - * Read an entire packet from the stream and return it as a byte array. - * - * @return the read packet - * @throws IOException when the underlying input stream throws an exception - */ - public byte[] readPacket() throws IOException - { - tracker.out.reset(); - DataInputPacketReader.readPacket( dataInput, buffer, protocol ); - return tracker.out.toByteArray(); - } - - @Override - public void close() throws Exception - { - dataInput.close(); - } - - /** - * Input stream which will wrap another stream and copy all bytes read to a - * {@link ByteArrayOutputStream}. - */ - private class TrackingInputStream extends FilterInputStream - { - - private final ByteArrayOutputStream out = new ByteArrayOutputStream(); - - public TrackingInputStream(InputStream in) - { - super( in ); - } - - @Override - public int read() throws IOException - { - int ret = in.read(); - out.write( ret ); - return ret; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException - { - int ret = in.read( b, off, len ); - out.write( b, off, ret ); - return ret; - } - } -}