dorkbox.network.serialization.Serialization.java Source code

Java tutorial

Introduction

Here is the source code for dorkbox.network.serialization.Serialization.java

Source

/*
 * Copyright 2010 dorkbox, llc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dorkbox.network.serialization;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.IESParameters;
import org.bouncycastle.crypto.params.IESWithCipherParameters;
import org.slf4j.Logger;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.factories.ReflectionSerializerFactory;
import com.esotericsoftware.kryo.factories.SerializerFactory;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.serializers.CollectionSerializer;
import com.esotericsoftware.kryo.serializers.FieldSerializer;
import com.esotericsoftware.kryo.util.IdentityMap;
import com.esotericsoftware.kryo.util.MapReferenceResolver;
import com.esotericsoftware.kryo.util.Util;

import dorkbox.network.connection.Connection_;
import dorkbox.network.connection.KryoExtra;
import dorkbox.network.connection.ping.PingMessage;
import dorkbox.network.rmi.CachedMethod;
import dorkbox.network.rmi.InvocationHandlerSerializer;
import dorkbox.network.rmi.InvocationResultSerializer;
import dorkbox.network.rmi.InvokeMethod;
import dorkbox.network.rmi.InvokeMethodResult;
import dorkbox.network.rmi.InvokeMethodSerializer;
import dorkbox.network.rmi.RemoteObjectSerializer;
import dorkbox.network.rmi.RmiRegistration;
import dorkbox.network.rmi.RmiRegistrationSerializer;
import dorkbox.network.rmi.RmiUtils;
import dorkbox.objectPool.ObjectPool;
import dorkbox.objectPool.PoolableObject;
import dorkbox.util.Property;
import dorkbox.util.collections.IntMap;
import dorkbox.util.serialization.ArraysAsListSerializer;
import dorkbox.util.serialization.EccPrivateKeySerializer;
import dorkbox.util.serialization.EccPublicKeySerializer;
import dorkbox.util.serialization.IesParametersSerializer;
import dorkbox.util.serialization.IesWithCipherParametersSerializer;
import dorkbox.util.serialization.UnmodifiableCollectionsSerializer;
import io.netty.bootstrap.DatagramCloseMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;

/**
 * Threads reading/writing, it messes up a single instance. it is possible to use a single kryo with the use of synchronize, however - that
 * defeats the point of having multi-threaded serialization.
 * <p>
 * Additionally, this serialization manager will register the entire class+interface hierarchy for an object. If you want to specify a
 * serialization scheme for a specific class in an objects hierarchy, you must register that first.
 */
@SuppressWarnings({ "StaticNonFinalField" })
public class Serialization implements NetworkSerializationManager {

    private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory
            .getLogger(Serialization.class.getSimpleName());

    /**
     * Specify if we want KRYO to use unsafe memory for serialization, or to use the ASM backend. Unsafe memory use is WAY faster, but is
     * limited to the "same endianess" on all endpoints, and unsafe DOES NOT work on android.
     */
    @Property
    public static boolean useUnsafeMemory = false;

    public static Serialization DEFAULT() {
        return DEFAULT(true, true, null);
    }

    /**
     * By default, the serialization manager will compress+encrypt data to connections with remote IPs, and only compress on the loopback IP
     * <p>
     * Additionally, this serialization manager will register the entire class+interface hierarchy for an object. If you want to specify a
     * serialization scheme for a specific class in an objects hierarchy, you must register that first.
     *
     * @param references If true, each appearance of an object in the graph after the first is stored as an integer ordinal. When set to true,
     *         {@link MapReferenceResolver} is used. This enables references to the same object and cyclic graphs to be serialized,
     *         but typically adds overhead of one byte per object. (should be true)
     * @param registrationRequired If true, an exception is thrown when an unregistered class is encountered.
     *         <p>
     *         If false, when an unregistered class is encountered, its fully qualified class name will be serialized and the {@link
     *         Kryo#addDefaultSerializer(Class, Class) default serializer} for the class used to serialize the object. Subsequent
     *         appearances of the class within the same object graph are serialized as an int id.
     *         <p>
     *         Registered classes are serialized as an int id, avoiding the overhead of serializing the class name, but have the
     *         drawback of needing to know the classes to be serialized up front.
     * @param factory Sets the serializer factory to use when no {@link Kryo#addDefaultSerializer(Class, Class) default serializers} match
     *         an object's type. Default is {@link ReflectionSerializerFactory} with {@link FieldSerializer}. @see
     *         Kryo#newDefaultSerializer(Class)
     */
    public static <C extends Connection_> Serialization DEFAULT(final boolean references,
            final boolean registrationRequired, final SerializerFactory factory) {

        final Serialization serialization = new Serialization(references, registrationRequired, factory);

        serialization.register(PingMessage.class);
        serialization.register(DatagramCloseMessage.class);
        serialization.register(byte[].class);

        serialization.register(IESParameters.class, new IesParametersSerializer());
        serialization.register(IESWithCipherParameters.class, new IesWithCipherParametersSerializer());
        serialization.register(ECPublicKeyParameters.class, new EccPublicKeySerializer());
        serialization.register(ECPrivateKeyParameters.class, new EccPrivateKeySerializer());
        serialization.register(ClassRegistration.class);
        serialization.register(dorkbox.network.connection.registration.Registration.class); // must use full package name!

        // necessary for the transport of exceptions.
        serialization.register(ArrayList.class, new CollectionSerializer());
        serialization.register(StackTraceElement.class);
        serialization.register(StackTraceElement[].class);

        // extra serializers
        //noinspection ArraysAsListWithZeroOrOneArgument
        serialization.register(Arrays.asList("").getClass(), new ArraysAsListSerializer());

        UnmodifiableCollectionsSerializer.registerSerializers(serialization);

        return serialization;
    }

    private boolean initialized = false;
    private final ObjectPool<KryoExtra> kryoPool;

    private final boolean registrationRequired;

    // used by operations performed during kryo initialization, which are by default package access (since it's an anon-inner class)
    // All registration MUST happen in-order of when the register(*) method was called, otherwise there are problems.
    // Object checking is performed during actual registration.
    private final List<ClassRegistration> classesToRegister = new ArrayList<ClassRegistration>();
    private ClassRegistration[] mergedRegistrations;
    private byte[] savedRegistrationDetails;

    private boolean usesRmi = false;

    private InvokeMethodSerializer methodSerializer = null;
    private Serializer<Object> invocationSerializer = null;

    // the purpose of the method cache, is to accelerate looking up methods for specific class
    private IntMap<CachedMethod[]> methodCache;
    private RemoteObjectSerializer remoteObjectSerializer;
    private RmiRegistrationSerializer rmiRegistrationSerializer;

    private final IdentityMap<Class<?>, Class<?>> rmiIfaceToImpl = new IdentityMap<Class<?>, Class<?>>();
    private final IdentityMap<Class<?>, Class<?>> rmiImplToIface = new IdentityMap<Class<?>, Class<?>>();

    // reflectASM doesn't work on android
    private final boolean useAsm = !useUnsafeMemory && !Util.IS_ANDROID;
    private Logger wireReadLogger;
    private Logger wireWriteLogger;

    /**
     * By default, the serialization manager will compress+encrypt data to connections with remote IPs, and only compress on the loopback IP
     * <p>
     * Additionally, this serialization manager will register the entire class+interface hierarchy for an object. If you want to specify a
     * serialization scheme for a specific class in an objects hierarchy, you must register that first.
     *
     * @param references If true, each appearance of an object in the graph after the first is stored as an integer ordinal. When set to true,
     *         {@link MapReferenceResolver} is used. This enables references to the same object and cyclic graphs to be serialized,
     *         but typically adds overhead of one byte per object. (should be true)
     *         <p>
     * @param registrationRequired If true, an exception is thrown when an unregistered class is encountered.
     *         <p>
     *         If false, when an unregistered class is encountered, its fully qualified class name will be serialized and the {@link
     *         Kryo#addDefaultSerializer(Class, Class) default serializer} for the class used to serialize the object. Subsequent
     *         appearances of the class within the same object graph are serialized as an int id.
     *         <p>
     *         Registered classes are serialized as an int id, avoiding the overhead of serializing the class name, but have the
     *         drawback of needing to know the classes to be serialized up front.
     *         <p>
     * @param factory Sets the serializer factory to use when no {@link Kryo#addDefaultSerializer(Class, Class) default serializers} match
     *         an object's type. Default is {@link ReflectionSerializerFactory} with {@link FieldSerializer}. @see
     *         Kryo#newDefaultSerializer(Class)
     */
    public Serialization(final boolean references, final boolean registrationRequired,
            final SerializerFactory factory) {

        this.registrationRequired = registrationRequired;

        this.kryoPool = ObjectPool.NonBlockingSoftReference(new PoolableObject<KryoExtra>() {
            @Override
            public KryoExtra create() {
                synchronized (Serialization.this) {
                    // we HAVE to pre-allocate the KRYOs
                    KryoExtra kryo = new KryoExtra(Serialization.this);

                    kryo.getFieldSerializerConfig().setUseAsm(useAsm);
                    kryo.setRegistrationRequired(registrationRequired);

                    kryo.setReferences(references);

                    if (usesRmi) {
                        kryo.register(Class.class);
                        kryo.register(RmiRegistration.class, rmiRegistrationSerializer);
                        kryo.register(InvokeMethod.class, methodSerializer);
                        kryo.register(Object[].class);

                        // This has to be for each kryo instance!
                        InvocationResultSerializer resultSerializer = new InvocationResultSerializer(kryo);
                        resultSerializer.removeField("rmiObjectId");

                        kryo.register(InvokeMethodResult.class, resultSerializer);
                        kryo.register(InvocationHandler.class, invocationSerializer);
                    }

                    // All registration MUST happen in-order of when the register(*) method was called, otherwise there are problems.
                    // additionally, if a class registered is an INTERFACE, then we register it as an RMI class.
                    for (ClassRegistration clazz : classesToRegister) {
                        clazz.register(kryo, remoteObjectSerializer);
                    }

                    if (factory != null) {
                        kryo.setDefaultSerializer(factory);
                    }

                    return kryo;
                }
            }
        });
    }

    /**
     * Registers the class using the lowest, next available integer ID and the {@link Kryo#getDefaultSerializer(Class) default serializer}.
     * If the class is already registered, the existing entry is updated with the new serializer.
     * <p>
     * Registering a primitive also affects the corresponding primitive wrapper.
     * <p>
     * Because the ID assigned is affected by the IDs registered before it, the order classes are registered is important when using this
     * method. The order must be the same at deserialization as it was for serialization.
     */
    @Override
    public synchronized NetworkSerializationManager register(Class<?> clazz) {
        if (initialized) {
            logger.warn("Serialization manager already initialized. Ignoring duplicate register(Class) call.");
        } else {
            if (clazz.isInterface()) {
                // we have to enable RMI
                usesRmi = true;
            }

            classesToRegister.add(new ClassRegistration(clazz));
        }

        return this;
    }

    /**
     * Registers the class using the specified ID. If the ID is already in use by the same type, the old entry is overwritten. If the ID
     * is already in use by a different type, a {@link KryoException} is thrown.
     * <p>
     * Registering a primitive also affects the corresponding primitive wrapper.
     * <p>
     * IDs must be the same at deserialization as they were for serialization.
     *
     * @param id Must be >= 0. Smaller IDs are serialized more efficiently. IDs 0-8 are used by default for primitive types and String, but
     *         these IDs can be repurposed.
     */
    @Override
    public synchronized NetworkSerializationManager register(Class<?> clazz, int id) {
        if (initialized) {
            logger.warn("Serialization manager already initialized. Ignoring duplicate register(Class, int) call.");
        } else if (clazz.isInterface()) {
            throw new IllegalArgumentException(
                    "Cannot register an interface with specified ID for serialization. It must be an implementation.");
        } else {
            classesToRegister.add(new ClassSerializer1(clazz, id));
        }

        return this;
    }

    /**
     * Registers the class using the lowest, next available integer ID and the specified serializer. If the class is already registered,
     * the existing entry is updated with the new serializer.
     * <p>
     * Registering a primitive also affects the corresponding primitive wrapper.
     * <p>
     * Because the ID assigned is affected by the IDs registered before it, the order classes are registered is important when using this
     * method. The order must be the same at deserialization as it was for serialization.
     */
    @Override
    public synchronized NetworkSerializationManager register(Class<?> clazz, Serializer<?> serializer) {
        if (initialized) {
            logger.warn(
                    "Serialization manager already initialized. Ignoring duplicate register(Class, Serializer) call.");
        } else if (clazz.isInterface()) {
            throw new IllegalArgumentException(
                    "Cannot register an interface with specified serializer for serialization. It must be an implementation.");
        } else {
            classesToRegister.add(new ClassSerializer(clazz, serializer));
        }

        return this;
    }

    /**
     * Registers the class using the specified ID and serializer. If the ID is already in use by the same type, the old entry is
     * overwritten. If the ID is already in use by a different type, a {@link KryoException} is thrown.
     * <p>
     * Registering a primitive also affects the corresponding primitive wrapper.
     * <p>
     * IDs must be the same at deserialization as they were for serialization.
     *
     * @param id Must be >= 0. Smaller IDs are serialized more efficiently. IDs 0-8 are used by default for primitive types and String, but
     *         these IDs can be repurposed.
     */
    @Override
    public synchronized NetworkSerializationManager register(Class<?> clazz, Serializer<?> serializer, int id) {
        if (initialized) {
            logger.warn(
                    "Serialization manager already initialized. Ignoring duplicate register(Class, Serializer, int) call.");
        } else if (clazz.isInterface()) {
            throw new IllegalArgumentException(
                    "Cannot register an interface with specified ID/serializer for serialization. It must be an implementation.");
        } else {
            classesToRegister.add(new ClassSerializer2(clazz, serializer, id));
        }

        return this;
    }

    /**
     * Enable a "remote client" to access methods and create objects (RMI) for this endpoint. This is NOT bi-directional, and this endpoint cannot access or
     * create remote objects on the "remote client".
     * <p>
     * There is additional overhead to using RMI.
     * <p>
     * Specifically, It costs at least 2 bytes more to use remote method invocation than just sending the parameters. If the method has a
     * return value which is not {@link dorkbox.network.rmi.RemoteObject#setAsync(boolean) ignored}, an extra byte is written.
     * <p>
     * If the type of a parameter is not final (primitives are final) then an extra byte is written for that parameter.
     * <p>
     *
     * @throws IllegalArgumentException if the iface/impl have previously been overridden
     */
    @Override
    public synchronized <Iface, Impl extends Iface> NetworkSerializationManager registerRmi(Class<Iface> ifaceClass,
            Class<Impl> implClass) {
        if (initialized) {
            logger.warn(
                    "Serialization manager already initialized. Ignoring duplicate registerRmiImplementation(Class, Class) call.");
            return this;
        }

        if (!ifaceClass.isInterface()) {
            throw new IllegalArgumentException(
                    "Cannot register an implementation for RMI access. It must be an interface.");
        }
        if (implClass.isInterface()) {
            throw new IllegalArgumentException(
                    "Cannot register an interface for RMI implementations. It must be an implementation.");
        }

        usesRmi = true;
        classesToRegister.add(new ClassSerializerRmi(ifaceClass, implClass));

        // rmiIfaceToImpl tells us, "the server" how to create a (requested) remote object
        // this MUST BE UNIQUE otherwise unexpected and BAD things can happen.
        Class<?> a = this.rmiIfaceToImpl.put(ifaceClass, implClass);
        Class<?> b = this.rmiImplToIface.put(implClass, ifaceClass);

        if (a != null || b != null) {
            throw new IllegalArgumentException("Unable to override interface (" + ifaceClass
                    + ") and implementation (" + implClass + ") "
                    + "because they have already been overridden by something else. It is critical that they are"
                    + " both unique per JVM");
        }

        return this;
    }

    /**
     * Necessary to register classes for RMI, only called once if/when the RMI bridge is created.
     *
     * @return true if there are classes that have been registered for RMI
     */
    @Override
    public synchronized boolean initRmiSerialization() {
        if (!usesRmi) {
            return false;
        }

        if (initialized) {
            logger.warn(
                    "Serialization manager already initialized. Ignoring duplicate initRmiSerialization() call.");
            return true;
        }

        methodSerializer = new InvokeMethodSerializer();
        invocationSerializer = new InvocationHandlerSerializer(logger);
        remoteObjectSerializer = new RemoteObjectSerializer(rmiImplToIface);
        rmiRegistrationSerializer = new RmiRegistrationSerializer();
        methodCache = new IntMap<CachedMethod[]>();

        return true;
    }

    /**
     * Called when initialization is complete. This is to prevent (and recognize) out-of-order class/serializer registration. If an ID
     * is already in use by a different type, a {@link KryoException} is thrown.
     */
    @Override
    public synchronized void finishInit(final Logger wireReadLogger, final Logger wireWriteLogger) {
        this.wireReadLogger = wireReadLogger;
        this.wireWriteLogger = wireWriteLogger;

        initialized = true;

        // initialize the kryo pool with at least 1 kryo instance. This ALSO makes sure that all of our class registration is done
        // correctly and (if not) we are are notified on the initial thread (instead of on the network update thread)
        KryoExtra kryo = kryoPool.take();
        try {

            // now MERGE all of the registrations (since we can have registrations overwrite newer/specific registrations

            ArrayList<ClassRegistration> mergedRegistrations = new ArrayList<ClassRegistration>();

            for (ClassRegistration registration : classesToRegister) {
                int id = registration.id;

                // if we ALREADY contain this registration (based ONLY on ID), then overwrite the existing one and REMOVE the current one
                boolean found = false;
                for (int index = 0; index < mergedRegistrations.size(); index++) {
                    final ClassRegistration existingRegistration = mergedRegistrations.get(index);
                    if (existingRegistration.id == id) {
                        mergedRegistrations.set(index, registration);
                        found = true;
                        break;
                    }
                }

                if (!found) {
                    mergedRegistrations.add(registration);
                }
            }

            // now all of the registrations are IN ORDER and MERGED
            this.mergedRegistrations = mergedRegistrations.toArray(new ClassRegistration[0]);

            Object[][] registrationDetails = new Object[mergedRegistrations.size()][3];

            for (int i = 0; i < mergedRegistrations.size(); i++) {
                final ClassRegistration registration = mergedRegistrations.get(i);
                registration.log(logger);

                // now save all of the registration IDs for quick verification/access
                registrationDetails[i] = new Object[] { registration.id, registration.clazz.getName(),
                        registration.serializer.getClass().getName() };

                // now we have to manage caching methods (only as necessary)
                if (registration.clazz.isInterface()) {
                    // can be a normal class or an RMI class...
                    Class<?> implClass = null;

                    if (registration instanceof ClassSerializerRmi) {
                        implClass = ((ClassSerializerRmi) registration).implClass;
                    }

                    CachedMethod[] cachedMethods = RmiUtils.getCachedMethods(Serialization.logger, kryo, useAsm,
                            registration.clazz, implClass, registration.id);
                    methodCache.put(registration.id, cachedMethods);
                }
            }

            // save this as a byte array (so registration is faster)
            ByteBuf buffer = Unpooled.buffer(480);

            kryo.setRegistrationRequired(false);
            try {
                kryo.writeCompressed(buffer, registrationDetails);
            } catch (Exception e) {
                logger.error("Unable to write compressed data for registration details");
            }

            int length = buffer.readableBytes();

            savedRegistrationDetails = new byte[length];
            buffer.getBytes(0, savedRegistrationDetails, 0, length);

            buffer.release();
        } finally {
            if (registrationRequired) {
                kryo.setRegistrationRequired(true);
            }

            kryoPool.put(kryo);
        }
    }

    /**
     * NOTE: When this fails, the CLIENT will just time out. We DO NOT want to send an error message to the client
     *   (it should check for updates or something else). We do not want to give "rogue" clients knowledge of the
     *   server, thus preventing them from trying to probe the server data structures.
     *
     * @return a compressed byte array of the details of all registration IDs -> Class name -> Serialization type used by kryo
     */
    @Override
    public byte[] getKryoRegistrationDetails() {
        return savedRegistrationDetails;
    }

    /**
     * NOTE: When this fails, the CLIENT will just time out. We DO NOT want to send an error message to the client
     *   (it should check for updates or something else). We do not want to give "rogue" clients knowledge of the
     *   server, thus preventing them from trying to probe the server data structures.
     *
     * @return true if kryo registration is required for all classes sent over the wire
     */
    @SuppressWarnings("Duplicates")
    @Override
    public boolean verifyKryoRegistration(byte[] otherRegistrationData) {
        // verify the registration IDs if necessary with our own. The CLIENT does not verify anything, only the server!
        byte[] kryoRegistrationDetails = savedRegistrationDetails;
        boolean equals = java.util.Arrays.equals(kryoRegistrationDetails, otherRegistrationData);
        if (equals) {
            return true;
        }

        // now we need to figure out WHAT was screwed up so we know what to fix
        KryoExtra kryo = takeKryo();

        ByteBuf byteBuf = Unpooled.wrappedBuffer(otherRegistrationData);

        try {
            kryo.setRegistrationRequired(false);
            @SuppressWarnings("unchecked")
            Object[][] classRegistrations = (Object[][]) kryo.readCompressed(byteBuf, otherRegistrationData.length);

            int lengthOrg = mergedRegistrations.length;
            int lengthNew = classRegistrations.length;
            int index = 0;

            // list all of the registrations that are mis-matched between the server/client
            for (; index < lengthOrg; index++) {
                final ClassRegistration classOrg = mergedRegistrations[index];

                if (index >= lengthNew) {
                    logger.error("Missing client registration for {} -> {}", classOrg.id, classOrg.clazz.getName());
                } else {
                    Object[] classNew = classRegistrations[index];
                    int idNew = (Integer) classNew[0];
                    String nameNew = (String) classNew[1];
                    String serializerNew = (String) classNew[2];

                    int idOrg = classOrg.id;
                    String nameOrg = classOrg.clazz.getName();
                    String serializerOrg = classOrg.serializer.getClass().getName();

                    if (idNew != idOrg || !nameOrg.equals(nameNew)
                            || !serializerNew.equalsIgnoreCase(serializerOrg)) {
                        logger.error("Registration {} Client -> {} ({})", idNew, nameNew, serializerNew);
                        logger.error("Registration {} Server -> {} ({})", idOrg, nameOrg, serializerOrg);
                    }
                }
            }

            // list all of the registrations that are missing on the server
            if (index < lengthNew) {
                for (; index < lengthNew; index++) {
                    Object[] holderClass = classRegistrations[index];
                    int id = (Integer) holderClass[0];
                    String name = (String) holderClass[1];
                    String serializer = (String) holderClass[2];

                    logger.error("Missing server registration : {} -> {} ({})", id, name, serializer);
                }
            }
        } catch (Exception e) {
            logger.error("Error [{}] during registration validation", e.getMessage());

        } finally {
            if (registrationRequired) {
                kryo.setRegistrationRequired(true);
            }

            returnKryo(kryo);
            byteBuf.release();
        }

        return false;
    }

    /**
     * @return takes a kryo instance from the pool.
     */
    @Override
    public KryoExtra takeKryo() {
        return kryoPool.take();
    }

    /**
     * Returns a kryo instance to the pool.
     */
    @SuppressWarnings("unchecked")
    @Override
    public void returnKryo(KryoExtra kryo) {
        kryoPool.put(kryo);
    }

    /**
     * Gets the RMI interface based on the specified implementation
     *
     * @return the corresponding interface
     */
    @Override
    public Class<?> getRmiImpl(Class<?> iface) {
        return rmiIfaceToImpl.get(iface);
    }

    @Override
    public CachedMethod[] getMethods(final int classId) {
        return methodCache.get(classId);
    }

    @Override
    public synchronized boolean initialized() {
        return initialized;
    }

    /**
     * Waits until a kryo is available to write, using CAS operations to prevent having to synchronize.
     * <p>
     * No crypto and no sequence number
     * <p>
     * There is a small speed penalty if there were no kryo's available to use.
     */
    @Override
    public final void write(final ByteBuf buffer, final Object message) throws IOException {
        final KryoExtra kryo = kryoPool.take();
        try {
            if (wireWriteLogger.isTraceEnabled()) {
                int start = buffer.writerIndex();
                kryo.write(buffer, message);
                int end = buffer.writerIndex();

                wireWriteLogger.trace(ByteBufUtil.hexDump(buffer, start, end - start));
            } else {
                kryo.write(buffer, message);
            }
        } finally {
            kryoPool.put(kryo);
        }
    }

    /**
     * Reads an object from the buffer.
     * <p>
     * No crypto and no sequence number
     *
     * @param length should ALWAYS be the length of the expected object!
     */
    @Override
    public final Object read(final ByteBuf buffer, final int length) throws IOException {
        final KryoExtra kryo = kryoPool.take();
        try {
            if (wireReadLogger.isTraceEnabled()) {
                int start = buffer.readerIndex();
                Object object = kryo.read(buffer);
                int end = buffer.readerIndex();

                wireReadLogger.trace(ByteBufUtil.hexDump(buffer, start, end - start));

                return object;
            } else {
                return kryo.read(buffer);
            }
        } finally {
            kryoPool.put(kryo);
        }
    }

    /**
     * Writes the class and object using an available kryo instance
     */
    @Override
    public void writeFullClassAndObject(final Output output, final Object value) throws IOException {
        KryoExtra kryo = kryoPool.take();
        boolean prev = false;

        try {
            prev = kryo.isRegistrationRequired();
            kryo.setRegistrationRequired(false);
            kryo.writeClassAndObject(output, value);
        } catch (Exception ex) {
            final String msg = "Unable to serialize buffer";
            if (logger != null) {
                logger.error(msg, ex);
            }
            throw new IOException(msg, ex);
        } finally {
            kryo.setRegistrationRequired(prev);
            kryoPool.put(kryo);
        }
    }

    /**
     * Returns a class read from the input
     */
    @Override
    public Object readFullClassAndObject(final Input input) throws IOException {
        KryoExtra kryo = kryoPool.take();
        boolean prev = false;

        try {
            prev = kryo.isRegistrationRequired();
            kryo.setRegistrationRequired(false);
            return kryo.readClassAndObject(input);
        } catch (Exception ex) {
            final String msg = "Unable to deserialize buffer";
            if (logger != null) {
                logger.error(msg, ex);
            }
            throw new IOException(msg, ex);
        } finally {
            kryo.setRegistrationRequired(prev);
            kryoPool.put(kryo);
        }
    }

    /**
     * Waits until a kryo is available to write, using CAS operations to prevent having to synchronize.
     * <p>
     * There is a small speed penalty if there were no kryo's available to use.
     */
    @Override
    public final void writeWithCrypto(final Connection_ connection, final ByteBuf buffer, final Object message)
            throws IOException {
        final KryoExtra kryo = kryoPool.take();
        try {
            // we only need to encrypt when NOT on loopback, since encrypting on loopback is a waste of CPU
            if (connection.isLoopback()) {
                if (wireWriteLogger.isTraceEnabled()) {
                    int start = buffer.writerIndex();
                    kryo.writeCompressed(connection, buffer, message);
                    int end = buffer.writerIndex();

                    wireWriteLogger.trace(ByteBufUtil.hexDump(buffer, start, end - start));
                } else {
                    kryo.writeCompressed(connection, buffer, message);
                }
            } else {
                if (wireWriteLogger.isTraceEnabled()) {
                    int start = buffer.writerIndex();
                    kryo.writeCrypto(connection, buffer, message);
                    int end = buffer.writerIndex();

                    wireWriteLogger.trace(ByteBufUtil.hexDump(buffer, start, end - start));
                } else {
                    kryo.writeCrypto(connection, buffer, message);
                }
            }
        } finally {
            kryoPool.put(kryo);
        }
    }

    /**
     * Reads an object from the buffer.
     * <p>
     * Crypto + sequence number
     *
     * @param connection can be NULL
     * @param length should ALWAYS be the length of the expected object!
     */
    @SuppressWarnings("Duplicates")
    @Override
    public final Object readWithCrypto(final Connection_ connection, final ByteBuf buffer, final int length)
            throws IOException {
        final KryoExtra kryo = kryoPool.take();
        try {
            // we only need to encrypt when NOT on loopback, since encrypting on loopback is a waste of CPU
            if (connection.isLoopback()) {
                if (wireReadLogger.isTraceEnabled()) {
                    int start = buffer.readerIndex();
                    Object object = kryo.readCompressed(connection, buffer, length);
                    int end = buffer.readerIndex();

                    wireReadLogger.trace(ByteBufUtil.hexDump(buffer, start, end - start));

                    return object;
                } else {
                    return kryo.readCompressed(connection, buffer, length);
                }
            } else {
                if (wireReadLogger.isTraceEnabled()) {
                    int start = buffer.readerIndex();
                    Object object = kryo.readCrypto(connection, buffer, length);
                    int end = buffer.readerIndex();

                    wireReadLogger.trace(ByteBufUtil.hexDump(buffer, start, end - start));

                    return object;
                } else {
                    return kryo.readCrypto(connection, buffer, length);
                }
            }
        } finally {
            kryoPool.put(kryo);
        }
    }
}