Android Open Source - azilink Tcp To Nio






From Project

Back to project page azilink.

License

The source code is released under:

GNU General Public License

If you think the Android project azilink listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/* AziLink: USB tethering for Android
 * Copyright (C) 2009 by James Perry//from w ww  .j  a  v  a 2  s .c  o m
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.lfx.azilink.net;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;

import android.util.Log;

/**
 * This class translates between TcpDriver and NIO. 
 * @author Jim Perry
 *
 */
public class TcpToNio extends SocketHandler implements TcpDriverCallback, TimerCallback {
  /** socket for the NIO link */
  SocketChannel mChannel;
  /** select() in SelectThread */
  Selector mSelect;
  /** input buffer (NIO) */
  ByteBuffer mInBuffer = ByteBuffer.allocate( 16 * 1024 );
  /** output buffer (NIO) */
  ByteBuffer mOutBuffer = ByteBuffer.allocate( 16 * 1024 );
  /** Current socket state */
  State mState = State.STATE_NONE;
  /** TCP driver for this connection */
  TcpDriver mTCP;
  /** Selection key for this connection */
  SelectionKey mKey;
  /** TCP engine for all connections */
  TcpEngine mEngine;
  /** Transfer statistics for all links */
  TransferStatistics mStats;
  /** Close the link when the final byte is transmitted? */
  boolean mCloseWhenDoneXmit = false;
  /** Timer key for T-Mobile workaround */
  long mTimerKey;
  /** Where are we connection to? */
  InetSocketAddress mAddr;
  
  enum State {
    /** Socket is not connected */
    STATE_NONE,
    /** Socket is in the process of connecting */
    STATE_CONNECTING,
    /** Connection was accepted, but maybe it's T-Mobile's fake link */
    STATE_CONNECT_MAYBE,
    /** Connection is established */
    STATE_CONNECTED
  };
  
  /**
   * Construct a new link between the tcp driver and a socket.  Cannot use class until setDriver has been called.
   * 
   * @param engine TCP engine
   * @param select select in SelectThread
   * @throws IOException
   */
  TcpToNio(TcpEngine engine, Selector select) throws IOException {
    super( SocketChannel.open() );
    mEngine = engine;
    mStats = mEngine.mEngine;
    mChannel = (SocketChannel) super.mChannel;
    mChannel.configureBlocking(false);
    mChannel.socket().setTcpNoDelay(true);
    mSelect = select;
    mKey = mChannel.register( mSelect, SelectionKey.OP_CONNECT, this );
    mKey.interestOps(0);
  }
  
  /**
   * Set the TCP driver this connection is linked with
   * @param tcp tcp driver
   */
  public void setDriver(TcpDriver tcp) {
    mTCP = tcp;
  }

  /**
   * TCP driver just received a SYN packet for this link.  Begin the connect process.
   * @param address where to connect
   */
  public void onBeginBind(InetSocketAddress address) throws IOException {
    if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onBeginBind");
    if( mState != State.STATE_NONE ) throw new IOException( "onBeginBind in invalid state" );
    mState = State.STATE_CONNECTING;
    mAddr = address;
    mKey = mChannel.register( mSelect, SelectionKey.OP_CONNECT, this );
    mChannel.connect(address);    
  }
  
  /**
   * NIO just completed the connection.
   * @param k selection key
   */
  public void onConnect( SelectionKey k ) throws IOException {
    if( mState != State.STATE_CONNECTING ) throw new IOException( "onConnect in invalid state" );
    boolean success = false;
    mKey.interestOps( SelectionKey.OP_READ );
    
    try {
      success = mChannel.finishConnect();
    } catch (IOException e) {
    }
    if( success ) {
      boolean doWorkaround = false;
      if( mEngine.mEngine.mTMobileWorkaround ) {
        // Has this connection been accepted in the past?  If so, bypass the workaround.
        doWorkaround = !mEngine.mTM.check(mAddr);
      }
      if( !doWorkaround ) {
        // TMobile wrokaround is not active -> immediately accept
        mState = State.STATE_CONNECTED;
        if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onConnect " + success);
        mTCP.onBindComplete(success);
      } else {
        // TMobile workaround active -> delay the accept for nn seconds or until data received
        mState = State.STATE_CONNECT_MAYBE;
        if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onConnect tmobile workaround" );
        mTimerKey = mEngine.mEngine.mTimers.addTimer(mEngine.mEngine.mTMobileWorkaroundTimeout, this);
        return;
      }
    } else {      
      // Connection failed -> report to tcp driver
      mState = State.STATE_NONE;
      if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onConnect " + success);
      mTCP.onBindComplete(success);
    }
    
  }
  
  /**
   * New data from NIO, so pass it to the tcp driver
   * @param k selection key
   */
  public void onRead( SelectionKey k ) {
    k.interestOps(k.interestOps() & ~SelectionKey.OP_READ);
    if( mState == State.STATE_CONNECT_MAYBE ) {
      // Is there actually any data?
      mInBuffer.limit(1);
      try {
        int len = mChannel.read(mInBuffer);
        if( len > 0 ) {
          mStats.addBytes(len,0);
        }
      } catch (IOException e) {}
      if( mInBuffer.position() == 0 ) {
        // 0 byte read indicates that the connection was lost
        mState = State.STATE_NONE;
        if(VpnNatEngine.sLog) Log.v("AziLink","TMobile failed onRead");
        mEngine.mEngine.mTimers.killTimer(mTimerKey, this);
        mTCP.onBindComplete(false);
        return;
      } else {
        // New data means exit T-Mobile workaround state
        mEngine.mTM.mark(mAddr);
        mState = State.STATE_CONNECTED;
        if(VpnNatEngine.sLog) Log.v("AziLink","TMobile passwd onRead");
        mKey.interestOps( SelectionKey.OP_READ );
        mEngine.mEngine.mTimers.killTimer(mTimerKey, this);
        mTCP.onBindComplete(true);
      }
    }
    if( mState != State.STATE_CONNECTED ) return;
    
    mInBuffer.limit(mInBuffer.capacity());
    
    int maxLen = Math.min( mTCP.getWriteAvailableSize() - mInBuffer.position(), mInBuffer.remaining() );
    if( maxLen <= 0 ) {
      if(VpnNatEngine.sLog) Log.v("AziLink", "Nio::onRead is returning because max read length is 0" );
      return;
    }
    
    mInBuffer.limit( maxLen );
    
    int bytesRead = -1;
    try {
      bytesRead = mChannel.read(mInBuffer);
    } catch (IOException e) {}  
    if( bytesRead < 0 ) {
      if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onRead lost");
      mState = State.STATE_NONE;
      mTCP.close();
      return;
    }
    mStats.addBytes(bytesRead,0);
    if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onRead pass to driver - maxLen="+maxLen+", bytesRead="+bytesRead+"inpos="+mInBuffer.position());
    mInBuffer.flip();
    mTCP.write( mInBuffer );
    mInBuffer.clear();
    
    if( mTCP.getWriteAvailableSize() != 0 ) {
      if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onRead remains on");
      k.interestOps(k.interestOps() | SelectionKey.OP_READ);
    } else {
      if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onRead disabled - buffer full");
    }
  }
  
  /**
   * The NIO link can now accept more data.  Transmit anything in our outbound buffer.  If the outbound
   * buffer is empty, then ask the TCP driver for something. 
   * @param k selection key
   */
  public void onWrite( SelectionKey k ) {
    k.interestOps(k.interestOps() & ~SelectionKey.OP_WRITE);
    if( mState != State.STATE_CONNECTED ) return;    
    if( mOutBuffer.position() == 0 ) {
      if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onWrite called but no pending data");
      return;
    }
    mOutBuffer.flip();
    int bytesWritten = -1;
    try {
      bytesWritten = mChannel.write(mOutBuffer);
    } catch (IOException e) {}
    if( bytesWritten < 0 ) {
      if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onWrite failed");
      mState = State.STATE_NONE;
      mTCP.destroy();
      return;
    }
    mStats.addBytes(0,bytesWritten);
    mOutBuffer.compact();
    if( mOutBuffer.position() != 0 ) {
      if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onWrite has more data, staying on");
      k.interestOps(k.interestOps() | SelectionKey.OP_WRITE);
    } else if( mTCP.getReadAvailableSize() != 0 ) {
      if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onWrite recursing into onNewDataAvailable");
      onNewDataAvailable();
    } else if( mCloseWhenDoneXmit ) {
      if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onWrite closing because xmit is finished");
      mState = State.STATE_NONE;
      mTCP.close();
    }
  }

  /**
   * TCP driver received end of stream.  Close the link once our output buffer has been finished.
   */
  public void onClosed() {
    if( mState != State.STATE_CONNECTED ) return;
    
    mCloseWhenDoneXmit = true;
    if( mOutBuffer.position() == 0 ) {
      if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onClosed is closing immediately since no buffer");
      mState = State.STATE_NONE;
      mTCP.close();      
    } else {
      if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onClosed is pending");
    }
  }

  /**
   * Immediately teardown this link.
   */
  public void onDestroy() {
    if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onDestroy");
    try {
      mChannel.close();
    } catch (IOException e) {}
    mState = State.STATE_NONE;
    mEngine.mNat.remove(mTCP.getKey());
  }

  /**
   * New data is available from the tcp driver.  If there's room, accept and transmit it.
   */
  public void onNewDataAvailable() {
    if( mState != State.STATE_CONNECTED ) {
      if( mTCP.getReadAvailableSize() != 0 ) {
        if(VpnNatEngine.sLog) Log.v("AziLink","onNewData in unconnected state with data -> destroy tcp");
        mTCP.destroy();
      }
      return;  
    }
    if( mOutBuffer.remaining() == 0 ) {
      if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onNewData, but no data");
      return;
    }
    mTCP.read(mOutBuffer);
    int bytesWritten = -1;
    mOutBuffer.flip();
    try {
      bytesWritten = mChannel.write(mOutBuffer);
    } catch( IOException err ) {}
    if( bytesWritten < 0 ) {
      if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onNewData failed");
      mTCP.destroy();
      return;
    }
    mStats.addBytes(0,bytesWritten);
    mOutBuffer.compact();
    if( mOutBuffer.position() != 0 ) {
      if(VpnNatEngine.sLog) Log.v("AziLink","Nio::onNewData could not write everything");
      mKey.interestOps(mKey.interestOps() | SelectionKey.OP_WRITE);
    } else {
      if(VpnNatEngine.sLog) Log.v("AziLink", "Nio::onNewData wrote all " + bytesWritten + " bytes");
    }
  }

  /**
   * Receive buffer has room, so re-enable the OP_READ key for select()
   */
  public void onRequestMoreData() {
    if(VpnNatEngine.sLog) Log.v("AziLink","onRequestMoreData called - turning on OP_READ");
    mKey.interestOps(mKey.interestOps() | SelectionKey.OP_READ );
  }

  /**
   * Used for the T-Mobile workaround.  If the connection is still active after the timeout, then
   * it must be a real connection.
   */
  public void onTimer() {
    if( mState == State.STATE_CONNECT_MAYBE ) {
      if(VpnNatEngine.sLog) Log.v("AziLink","Complete TMOBILE");
      mEngine.mTM.mark(mAddr);
      mState = State.STATE_CONNECTED;
      mTCP.onBindComplete(true);
    }
  }

}




Java Source Code List

org.lfx.azilink.AboutActivity.java
org.lfx.azilink.BootActivity.java
org.lfx.azilink.ForwardService.java
org.lfx.azilink.LinkStatistics.java
org.lfx.azilink.MainActivity.java
org.lfx.azilink.Reflection.java
org.lfx.azilink.net.IcmpKey.java
org.lfx.azilink.net.IcmpPacket.java
org.lfx.azilink.net.SelectThread.java
org.lfx.azilink.net.SocketHandler.java
org.lfx.azilink.net.TcpDriverCallback.java
org.lfx.azilink.net.TcpDriverImpl.java
org.lfx.azilink.net.TcpDriverPacketSink.java
org.lfx.azilink.net.TcpDriver.java
org.lfx.azilink.net.TcpEngine.java
org.lfx.azilink.net.TcpKey.java
org.lfx.azilink.net.TcpPacket.java
org.lfx.azilink.net.TcpToNio.java
org.lfx.azilink.net.TimerCallback.java
org.lfx.azilink.net.TimerQueue.java
org.lfx.azilink.net.TmAccept.java
org.lfx.azilink.net.TransferStatistics.java
org.lfx.azilink.net.UdpDriver.java
org.lfx.azilink.net.UdpEngine.java
org.lfx.azilink.net.UdpKey.java
org.lfx.azilink.net.UdpPacket.java
org.lfx.azilink.net.VpnLink.java
org.lfx.azilink.net.VpnNatEngineNotify.java
org.lfx.azilink.net.VpnNatEngine.java