dorkbox.network.connection.wrapper.ChannelNetworkWrapper.java Source code

Java tutorial

Introduction

Here is the source code for dorkbox.network.connection.wrapper.ChannelNetworkWrapper.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.connection.wrapper;

import java.net.InetSocketAddress;

import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;

import dorkbox.network.connection.ConnectionImpl;
import dorkbox.network.connection.ConnectionPoint;
import dorkbox.network.connection.EndPoint;
import dorkbox.network.connection.ISessionManager;
import dorkbox.network.connection.registration.MetaChannel;
import dorkbox.util.FastThreadLocal;
import io.netty.bootstrap.DatagramCloseMessage;
import io.netty.util.NetUtil;

public class ChannelNetworkWrapper implements ChannelWrapper {

    private final int sessionId;

    private final ChannelNetwork tcp;
    private final ChannelNetwork udp;

    // did the remote connection public ECC key change?
    private final boolean remotePublicKeyChanged;

    private final String remoteAddress;
    private final boolean isLoopback;

    // GCM IV. hacky way to prevent tons of GC and to not clobber the original parameters
    private final byte[] aesKey; // AES-256 requires 32 bytes
    private final byte[] aesIV; // AES-GCM requires 12 bytes

    private final FastThreadLocal<ParametersWithIV> cryptoParameters;

    public ChannelNetworkWrapper(final MetaChannel metaChannel, final InetSocketAddress remoteAddress) {

        this.sessionId = metaChannel.sessionId;
        this.isLoopback = remoteAddress.getAddress().equals(NetUtil.LOCALHOST);

        if (metaChannel.tcpChannel != null) {
            this.tcp = new ChannelNetwork(metaChannel.tcpChannel);
        } else {
            this.tcp = null;
        }

        if (metaChannel.udpChannel != null) {
            this.udp = new ChannelNetwork(metaChannel.udpChannel);
        } else {
            this.udp = null;
        }

        this.remoteAddress = remoteAddress.getAddress().getHostAddress();
        this.remotePublicKeyChanged = metaChannel.changedRemoteKey;

        // AES key & IV (only for networked connections)
        aesKey = metaChannel.aesKey;
        aesIV = metaChannel.aesIV;

        cryptoParameters = new FastThreadLocal<ParametersWithIV>() {
            @Override
            public ParametersWithIV initialValue() {
                return new ParametersWithIV(new KeyParameter(aesKey), aesIV);
            }
        };
    }

    public final boolean remoteKeyChanged() {
        return this.remotePublicKeyChanged;
    }

    @Override
    public ConnectionPoint tcp() {
        return this.tcp;
    }

    @Override
    public ConnectionPoint udp() {
        return this.udp;
    }

    /**
     * Flushes the contents of the TCP/UDP/etc pipes to the actual transport.
     */
    @Override
    public void flush() {
        if (this.tcp != null) {
            this.tcp.flush();
        }

        if (this.udp != null) {
            this.udp.flush();
        }
    }

    /**
     * @return a threadlocal AES key + IV. key=32 byte, iv=12 bytes (AES-GCM implementation). This is a threadlocal
     *          because multiple protocols can be performing crypto AT THE SAME TIME, and so we have to make sure that operations don't
     *          clobber each other
     */
    @Override
    public ParametersWithIV cryptoParameters() {
        return this.cryptoParameters.get();
    }

    @Override
    public boolean isLoopback() {
        return isLoopback;
    }

    @Override
    public String getRemoteHost() {
        return this.remoteAddress;
    }

    @Override
    public void close(final ConnectionImpl connection, final ISessionManager sessionManager, boolean hintedClose) {
        long maxShutdownWaitTimeInMilliSeconds = EndPoint.maxShutdownWaitTimeInMilliSeconds;

        if (this.tcp != null) {
            this.tcp.close(0, maxShutdownWaitTimeInMilliSeconds);
        }

        if (this.udp != null) {
            if (hintedClose) {
                // we already hinted that we should close this channel... don't do it again!
                this.udp.close(0, maxShutdownWaitTimeInMilliSeconds);
            } else {
                // send a hint to the other connection that we should close. While not always 100% successful, this helps clean up connections
                // on the remote end
                try {
                    this.udp.write(new DatagramCloseMessage());
                    this.udp.flush();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                this.udp.close(200, maxShutdownWaitTimeInMilliSeconds);
            }
        }

        // we need to yield the thread here, so that the socket has a chance to close
        Thread.yield();
    }

    @Override
    public int id() {
        return this.sessionId;
    }

    @Override
    public int hashCode() {
        // a unique ID for every connection. However, these ID's can also be reused
        return this.sessionId;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!super.equals(obj)) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }

        ChannelNetworkWrapper other = (ChannelNetworkWrapper) obj;
        if (this.remoteAddress == null) {
            if (other.remoteAddress != null) {
                return false;
            }
        } else if (!this.remoteAddress.equals(other.remoteAddress)) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "NetworkConnection [" + getRemoteHost() + "]";
    }
}