Package com.aelitis.azureus.core.versioncheck

Source Code of com.aelitis.azureus.core.versioncheck.VersionCheckClient

/*
* Created on Dec 20, 2004
* Created by Alon Rohter
* Copyright (C) 2004-2005 Aelitis, All Rights Reserved.
*
* 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) any later version.
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*
* AELITIS, SAS au capital de 46,603.30 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*
*/

package com.aelitis.azureus.core.versioncheck;


import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.*;
import java.util.*;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.download.impl.DownloadManagerStateImpl;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.logging.*;
import org.gudy.azureus2.core3.stats.transfer.*;
import org.gudy.azureus2.core3.util.*;

import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.utils.DelayedTask;
import org.gudy.azureus2.pluginsimpl.local.utils.UtilitiesImpl;

import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.core.AzureusCoreFactory;
import com.aelitis.azureus.core.impl.AzureusCoreImpl;
import com.aelitis.azureus.core.clientmessageservice.*;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdmin;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdminASN;
import com.aelitis.azureus.core.security.CryptoManagerFactory;
import com.aelitis.azureus.core.util.DNSUtils;
import com.aelitis.net.udp.uc.PRUDPPacketHandler;
import com.aelitis.net.udp.uc.PRUDPPacketHandlerFactory;
import com.aelitis.net.udp.uc.PRUDPReleasablePacketHandler;



/**
* Client for checking version information from a remote server.
*/
public class VersionCheckClient {
  private static final LogIDs LOGID = LogIDs.CORE;

  public static final String  REASON_UPDATE_CHECK_START    = "us";
  public static final String  REASON_UPDATE_CHECK_PERIODIC  = "up";
  public static final String  REASON_CHECK_SWT        = "sw";
  public static final String  REASON_DHT_EXTENDED_ALLOWED    = "dx";
  public static final String  REASON_DHT_ENABLE_ALLOWED    = "de";
  public static final String  REASON_EXTERNAL_IP        = "ip";
  public static final String  REASON_RECOMMENDED_PLUGINS    = "rp";
  public static final String  REASON_SECONDARY_CHECK      = "sc";
  public static final String  REASON_PLUGIN_UPDATE      = "pu";


  private static final String   AZ_MSG_SERVER_ADDRESS_V4   = Constants.VERSION_SERVER_V4;
  private static final int     AZ_MSG_SERVER_PORT       = 27001;
  private static final String   MESSAGE_TYPE_ID       = "AZVER";

  public static final String     HTTP_SERVER_ADDRESS_V4    = AZ_MSG_SERVER_ADDRESS_V4;
  public static final int     HTTP_SERVER_PORT       = 80;

  public static final String     TCP_SERVER_ADDRESS_V4    = AZ_MSG_SERVER_ADDRESS_V4;
  public static final int     TCP_SERVER_PORT       = 80;

  public static final String     UDP_SERVER_ADDRESS_V4    = AZ_MSG_SERVER_ADDRESS_V4;
  public static final int     UDP_SERVER_PORT       = 2080;

  public static final String    AZ_MSG_SERVER_ADDRESS_V6   = Constants.VERSION_SERVER_V6;
  public static final String    HTTP_SERVER_ADDRESS_V6     = AZ_MSG_SERVER_ADDRESS_V6;
  public static final String    TCP_SERVER_ADDRESS_V6     = AZ_MSG_SERVER_ADDRESS_V6;
  public static final String    UDP_SERVER_ADDRESS_V6     = AZ_MSG_SERVER_ADDRESS_V6;


  private static final long    CACHE_PERIOD  = 5*60*1000;
  private static boolean secondary_check_done;

  private final List<VersionCheckClientListener> listeners = new ArrayList<VersionCheckClientListener>(1);
  private boolean startCheckRan = false;

  static{
    VersionCheckClientUDPCodecs.registerCodecs();
  }

  private static final int  AT_V4    = 1;
  private static final int  AT_V6    = 2;
  private static final int  AT_EITHER  = 3;

  private static VersionCheckClient instance;

  /**
   * Get the singleton instance of the version check client.
   * @return version check client
   */
  public static synchronized VersionCheckClient
  getSingleton()
  {
    if ( instance == null ){

      instance = new VersionCheckClient();
    }

    return( instance );
  }

  private boolean  enable_v6;
  private boolean  prefer_v6;

  private Map last_check_data_v4 = null;
  private Map last_check_data_v6 = null;

  private final AEMonitor check_mon = new AEMonitor( "versioncheckclient" );

  private long last_check_time_v4 = 0;
  private long last_check_time_v6 = 0;

  private long last_feature_flag_cache;
  private long last_feature_flag_cache_time;


  private
  VersionCheckClient()
  {
    COConfigurationManager.addAndFireParameterListeners(
        new String[]{ "IPV6 Prefer Addresses", "IPV6 Enable Support" },
        new ParameterListener()
        {
          public void
          parameterChanged(
              String   name )
          {
            enable_v6  = COConfigurationManager.getBooleanParameter( "IPV6 Enable Support" );
            prefer_v6   = COConfigurationManager.getBooleanParameter( "IPV6 Prefer Addresses" );
          }
        });
  }

  public void
  initialise()
  {
    DelayedTask delayed_task =
      UtilitiesImpl.addDelayedTask(
          "VersionCheck",
          new Runnable()
          {
            public void
            run()
            {
              final AESemaphore sem = new AESemaphore( "VCC:init" );

              new AEThread2( "VCC:init", true )
              {
                public void
                run()
                {
                  try{
                    getVersionCheckInfo( REASON_UPDATE_CHECK_START );

                  }finally{

                    sem.release();
                  }
                }
              }.start();

              if ( !sem.reserve( 5000 )){

                Debug.out( "Timeout waiting for version check to complete" );
              }
            }
          });

    delayed_task.queue();
  }






  /**
   * Get the version check reply info.
   * @return reply data, possibly cached, if the server was already checked within the last minute
   */

  public Map
  getVersionCheckInfo(
      String   reason )
  {
    return( getVersionCheckInfo( reason, AT_EITHER ));
  }

  public Map
  getVersionCheckInfo(
      String   reason,
      int    address_type )
  {
    if ( address_type == AT_V4 ){

      return( getVersionCheckInfoSupport( reason, false, false, false ));

    }else if ( address_type == AT_V6 ){

      return( getVersionCheckInfoSupport( reason, false, false, true ));

    }else{

      Map  reply = getVersionCheckInfoSupport( reason, false, false, prefer_v6 );

      if ( reply == null || reply.size() == 0 ){

        reply =  getVersionCheckInfoSupport( reason, false, false, !prefer_v6 );
      }

      return( reply );
    }
  }


  protected Map
  getVersionCheckInfoSupport(
    String   reason,
    boolean only_if_cached,
    boolean force,
    boolean  v6 )
  {
    try {
      synchronized (listeners) {
        if (REASON_UPDATE_CHECK_START.equals(reason)) {
          startCheckRan = true;
        }
        for (VersionCheckClientListener l : listeners) {
          l.versionCheckStarted(reason);
        }
      }
    } catch (Throwable t) {
      Debug.out(t);
    }
   
    if ( v6 ){

      if ( enable_v6 ){

        try check_mon.enter();

        long time_diff = SystemTime.getCurrentTime() - last_check_time_v6;

        force = force || time_diff > CACHE_PERIOD || time_diff < 0;

        if( last_check_data_v6 == null || last_check_data_v6.size() == 0 || force ) {
          // if we've never checked before then we go ahead even if the "only_if_cached"
          // flag is set as its had not chance of being cached yet!
          if ( only_if_cached && last_check_data_v6 != null ){
            return( new HashMap() );
          }
          try {
            last_check_data_v6 = performVersionCheck( constructVersionCheckMessage( reason ), true, true, true );

            if ( last_check_data_v6 != null && last_check_data_v6.size() > 0 ){

              COConfigurationManager.setParameter( "versioncheck.cache.v6", last_check_data_v6 );
            }
          }
          catch(SocketException t) {
            // internet is broken
            // Debug.out(t.getClass().getName() + ": " + t.getMessage());
          }
          catch(UnknownHostException t) {
            // dns is broken
            // Debug.out(t.getClass().getName() + ": " + t.getMessage());
          }
          catch( Throwable t ) {
            Debug.out(t);
            last_check_data_v6 = new HashMap();
          }
        }
        else {
          Logger.log(new LogEvent(LOGID, "VersionCheckClient is using "
              + "cached version check info. Using " + last_check_data_v6.size()
              + " reply keys."));
        }
        }
        finally check_mon.exit()}
      }

      if( last_check_data_v6 == null last_check_data_v6 = new HashMap();

      return last_check_data_v6;

    }else{

      try check_mon.enter();

      long time_diff = SystemTime.getCurrentTime() - last_check_time_v4;

      force = force || time_diff > CACHE_PERIOD || time_diff < 0;

      if( last_check_data_v4 == null || last_check_data_v4.size() == 0 || force ) {
        // if we've never checked before then we go ahead even if the "only_if_cached"
        // flag is set as its had not chance of being cached yet!
        if ( only_if_cached && last_check_data_v4 != null ){
          return( new HashMap() );
        }
        try {
          last_check_data_v4 = performVersionCheck( constructVersionCheckMessage( reason ), true, true, false );

          if ( last_check_data_v4 != null && last_check_data_v4.size() > 0 ){

            COConfigurationManager.setParameter( "versioncheck.cache.v4", last_check_data_v4 );
          }

          // clear down any plugin-specific data that has successfully been sent to the version server

          try{
            if ( AzureusCoreFactory.isCoreAvailable()){

              //installed plugin IDs
              PluginInterface[] plugins = AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaces();

              for (int i=0;i<plugins.length;i++){

                PluginInterface    plugin = plugins[i];

                Map  data = plugin.getPluginconfig().getPluginMapParameter( "plugin.versionserver.data", null );

                if ( data != null ){

                  plugin.getPluginconfig().setPluginMapParameter( "plugin.versionserver.data", new HashMap());
                }
              }
            }
          }catch( Throwable e ){
          }
        }
        catch( UnknownHostException t ) {
          // no internet
          Debug.outNoStack("VersionCheckClient - " + t.getClass().getName() + ": " + t.getMessage());
        }
        catch (IOException t) {
          // General connection problem.
          Debug.outNoStack("VersionCheckClient - " + t.getClass().getName() + ": " + t.getMessage());
        }
        catch( Throwable t ) {
          Debug.out(t);
          last_check_data_v4 = new HashMap();
        }
      }
      else {
        if (Logger.isEnabled())
          Logger.log(new LogEvent(LOGID, "VersionCheckClient is using "
              + "cached version check info. Using " + last_check_data_v4.size()
              + " reply keys."));
      }
      }
      finally check_mon.exit()}

      if( last_check_data_v4 == null last_check_data_v4 = new HashMap();

      last_feature_flag_cache_time = 0;

      return last_check_data_v4;
    }
  }

  public Map
  getMostRecentVersionCheckData()
  {
    // currently we maintain v4 much more accurately than v6

    if ( last_check_data_v4 != null ){

      return( last_check_data_v4 );
    }

    Map  res = COConfigurationManager.getMapParameter( "versioncheck.cache.v4", null );

    if ( res != null ){

      return( res );
    }

    if ( last_check_data_v6 != null ){

      return( last_check_data_v6 );
    }

    res = COConfigurationManager.getMapParameter( "versioncheck.cache.v6", null );

    return( res );
  }

  private boolean
  isVersionCheckDataValid(
    int    address_type )
  {   
    boolean v6_ok = last_check_data_v6 != null && last_check_data_v6.size() > 0;
    boolean v4_ok = last_check_data_v4 != null && last_check_data_v4.size() > 0;

    if ( address_type == AT_V4 ){

      return( v4_ok );

    }else if ( address_type == AT_V6 ){

      return( v6_ok );

    }else{

      return( v4_ok | v6_ok );
    }
  }

  public long
  getCacheTime(
      boolean v6 )
  {
    return( v6?last_check_time_v6:last_check_time_v4);
  }

  public void
  clearCache()
  {
    last_check_time_v6  = 0;
    last_check_time_v4  = 0;
  }

  public long
  getFeatureFlags()
  {
    long  now = SystemTime.getCurrentTime();

    if ( now > last_feature_flag_cache_time && now - last_feature_flag_cache_time < 60000 ){

      return( last_feature_flag_cache );
    }

    Map m = getMostRecentVersionCheckData();

    long  result;

    if ( m == null ){

      result = 0;

    }else{

      byte[]  b_feat_flags = (byte[])m.get( "feat_flags" );

      if ( b_feat_flags != null ){

        try{

          result = Long.parseLong(new String((byte[])b_feat_flags));

        }catch( Throwable e ){

          result  = 0;
        }
      }else{

        result = 0;
      }
    }

    last_feature_flag_cache     = result;
    last_feature_flag_cache_time  = now;

    return( result );
  }

  public Set<String>
  getDisabledPluginIDs()
  { 
    Set<String>  result = new HashSet<String>();

    Map m = getMostRecentVersionCheckData();

    if ( m != null ){

      byte[]  x = (byte[])m.get( "disabled_pids" );

      if ( x != null ){

        String str = new String( x );

        String latest = COConfigurationManager.getStringParameter( "vc.disabled_pids.latest", "" );

        if ( !str.equals( latest )){

          byte[]  sig = (byte[])m.get( "disabled_pids_sig" );

          if ( sig == null ){

            Debug.out( "disabled plugins sig missing" );

            return( result );
          }

          try{
            AEVerifier.verifyData( str, sig );

            COConfigurationManager.setParameter( "vc.disabled_pids.latest", str );

          }catch( Throwable e ){

            return( result );
          }
        }

        String[] bits = str.split( "," );

        for ( String b: bits ){

          b = b.trim();

          if ( b.length() > 0 ){

            result.add( b );
          }
        }
      }
    }

    return( result );
  }

  public Set<String>
  getAutoInstallPluginIDs()
  { 
    Set<String>  result = new HashSet<String>();

    Map m = getMostRecentVersionCheckData();

    if ( m != null ){

      byte[]  x = (byte[])m.get( "autoinstall_pids" );

      if ( x != null ){

        String str = new String( x );

        String latest = COConfigurationManager.getStringParameter( "vc.autoinstall_pids.latest", "" );

        if ( !str.equals( latest )){

          byte[]  sig = (byte[])m.get( "autoinstall_pids_sig" );

          if ( sig == null ){

            Debug.out( "autoinstall plugins sig missing" );

            return( result );
          }

          try{
            AEVerifier.verifyData( str, sig );

            COConfigurationManager.setParameter( "vc.autoinstall_pids.latest", str );

          }catch( Throwable e ){

            return( result );
          }
        }

        String[] bits = str.split( "," );

        for ( String b: bits ){

          b = b.trim();

          if ( b.length() > 0 ){

            result.add( b );
          }
        }
      }
    }

    return( result );
  }

  /**
   * Get the ip address seen by the version check server.
   * NOTE: This information may be cached, see getVersionCheckInfo().
   * @return external ip address, or empty string if no address information found
   */

  public String
  getExternalIpAddress(
      boolean  only_if_cached,
      boolean  v6 )
  {
    Map reply = getVersionCheckInfoSupport( REASON_EXTERNAL_IP, only_if_cached, false, v6 );

    byte[] address = (byte[])reply.get( "source_ip_address" );
    if( address != null ) {
      return new String( address );
    }

    return( null );
  }


  /**
   * Is the DHT plugin allowed to be enabled.
   * @return true if DHT can be enabled, false if it should not be enabled
   */
  public boolean DHTEnableAllowed() {
    Map reply = getVersionCheckInfo( REASON_DHT_ENABLE_ALLOWED, AT_EITHER );

    boolean  res = false;

    byte[] value = (byte[])reply.get( "enable_dht" );

    if( value != null ) {

      res = new String( value ).equalsIgnoreCase( "true" );
    }

    // we take the view that if the version check failed then we go ahead
    // and enable the DHT (i.e. we're being optimistic)

    if ( !res ){
      res = !isVersionCheckDataValid( AT_EITHER );
    }

    return res;
  }


  /**
   * Is the DHT allowed to be used by external plugins.
   * @return true if extended DHT use is allowed, false if not allowed
   */
  public boolean
  DHTExtendedUseAllowed()
  {
    Map reply = getVersionCheckInfo( REASON_DHT_EXTENDED_ALLOWED, AT_EITHER );

    boolean  res = false;

    byte[] value = (byte[])reply.get( "enable_dht_extended_use" );
    if( value != null ) {
      res = new String( value ).equalsIgnoreCase( "true" );
    }

    // be generous and enable extended use if check failed

    if ( !res ){
      res = !isVersionCheckDataValid( AT_EITHER );
    }

    return res;
  }

  public String[]
  getRecommendedPlugins()
  {
    Map reply = getVersionCheckInfo( REASON_RECOMMENDED_PLUGINS, AT_EITHER );

    List  l = (List)reply.get( "recommended_plugins" );

    if ( l == null ){

      return( new String[0] );
    }

    String[]  res = new String[l.size()];

    for (int i=0;i<l.size();i++){

      res[i] = new String((byte[])l.get(i));
    }

    return( res );
  }

  public Map<String,Object>
  getCountryInfo()
  {
    Map reply = getVersionCheckInfo( REASON_EXTERNAL_IP, AT_EITHER );

    Map<String,Object> info = (Map<String,Object>)reply.get( "source_info" );

    if ( info == null ){

      return( new HashMap<String,Object>());

    }else{

      return( BDecoder.decodeStrings( info ));
    }
  }

  /**
   * Perform the actual version check by connecting to the version server.
   * @param data_to_send version message
   * @return version reply
   * @throws Exception if the server check connection fails
   */
  private Map
  performVersionCheck(
    Map   data_to_send,
    boolean  use_az_message,
    boolean  use_http,
    boolean  v6 )

    throws Exception
  {
    Exception   error   = null;
    Map      reply  = null;


    if ( use_http ){

      try{
        reply = executeHTTP( data_to_send, v6 );

        reply.put( "protocol_used", "HTTP" );

        error = null;
      }
      catch (IOException e) {
        error = e;
      }
      catch (Exception e){
        Debug.printStackTrace(e);
        error = e;

      }
    }

    if ( reply == null && use_az_message ){

      try{
        reply = executeAZMessage( data_to_send, v6 );

        reply.put( "protocol_used", "AZMSG" );
      }
      catch (IOException e) {
        error = e;
      }
      catch (Exception e) {
        Debug.printStackTrace( e );
        error = e;
      }
    }
    if ( error != null ){

      throw( error );
    }

    if (Logger.isEnabled())
      Logger.log(new LogEvent(LOGID, "VersionCheckClient server "
          + "version check successful. Received " + reply.size()
          + " reply keys."));

    if ( v6 ){

      last_check_time_v6 = SystemTime.getCurrentTime();

    }else{

      last_check_time_v4 = SystemTime.getCurrentTime();
    }

    return reply;
  }

  private Map
  executeAZMessage(
    Map    data_to_send,
    boolean  v6 )

    throws Exception
  {
    if ( v6 && !enable_v6 ){

      throw( new Exception( "IPv6 is disabled" ));
    }

    String  host = getHost( v6, AZ_MSG_SERVER_ADDRESS_V6, AZ_MSG_SERVER_ADDRESS_V4 );

    if (Logger.isEnabled())
      Logger.log(new LogEvent(LOGID, "VersionCheckClient retrieving "
          + "version information from " + host + ":" + AZ_MSG_SERVER_PORT));

    ClientMessageService   msg_service = null;
    Map           reply    = null

    try{
      msg_service = ClientMessageServiceClient.getServerService( host, AZ_MSG_SERVER_PORT, 20, MESSAGE_TYPE_ID );

      msg_service.sendMessage( data_to_send )//send our version message

      reply = msg_service.receiveMessage()//get the server reply

      preProcessReply( reply, v6 );

    }finally{

      if ( msg_service != null ){

        msg_service.close();
      }
    }

    return( reply );
  }

  private Map
  executeHTTP(
    Map    data_to_send,
    boolean  v6 )

    throws Exception
  {
    if ( v6 && !enable_v6 ){

      throw( new Exception( "IPv6 is disabled" ));
    }

    String  host = getHost(v6, HTTP_SERVER_ADDRESS_V6, HTTP_SERVER_ADDRESS_V4 );

    if (Logger.isEnabled())
      Logger.log(new LogEvent(LOGID, "VersionCheckClient retrieving "
          + "version information from " + host + ":" + HTTP_SERVER_PORT + " via HTTP" ));

    String  url_str = "http://" + (v6?UrlUtils.convertIPV6Host(host):host) + (HTTP_SERVER_PORT==80?"":(":" + HTTP_SERVER_PORT)) + "/version?";

    url_str += URLEncoder.encode( new String( BEncoder.encode( data_to_send ), "ISO-8859-1" ), "ISO-8859-1" );

    URL  url = new URL( url_str );

    HttpURLConnection  url_connection = (HttpURLConnection)url.openConnection();

    url_connection.setConnectTimeout( 10*1000 );
    url_connection.setReadTimeout( 10*1000 );

    url_connection.connect();

    try{
      InputStream  is = url_connection.getInputStream();

      Map  reply = BDecoder.decode( new BufferedInputStream( is ));

      preProcessReply( reply, v6 );

      return( reply );

    }finally{

      url_connection.disconnect();
    }
  }

  public String
  getHTTPGetString(
    boolean  for_proxy,
    boolean  v6 )
  {
    return( getHTTPGetString( new HashMap(), for_proxy, v6 ));
  }

  private String
  getHTTPGetString(
    Map    content,
    boolean  for_proxy,
    boolean  v6 )
  {
    String  host = getHost( v6, HTTP_SERVER_ADDRESS_V6, HTTP_SERVER_ADDRESS_V4 );

    String  get_str = "GET " + (for_proxy?("http://" + (v6?UrlUtils.convertIPV6Host( host ):host ) + ":" + HTTP_SERVER_PORT ):"") +"/version?";

    try{
      get_str += URLEncoder.encode( new String( BEncoder.encode( content ), "ISO-8859-1" ), "ISO-8859-1" );

    }catch( Throwable e ){
    }

    get_str +=" HTTP/1.1" + "\015\012" + "\015\012";

    return( get_str );
  }

  private Map
  executeTCP(
    Map        data_to_send,
    InetAddress    bind_ip,
    int        bind_port,
    boolean      v6 )

    throws Exception
  {
    if ( v6 && !enable_v6 ){

      throw( new Exception( "IPv6 is disabled" ));
    }

    String  host = getHost(v6, TCP_SERVER_ADDRESS_V6, TCP_SERVER_ADDRESS_V4 );

    if (Logger.isEnabled())
      Logger.log(new LogEvent(LOGID, "VersionCheckClient retrieving "
          + "version information from " + host + ":" + TCP_SERVER_PORT + " via TCP" ));

    String  get_str = getHTTPGetString( data_to_send, false, v6 );

    Socket  socket = null;

    try{
      socket = new Socket();

      if ( bind_ip != null ){

        socket.bind( new InetSocketAddress( bind_ip, bind_port ));

      }else if ( bind_port != 0 ){

        socket.bind( new InetSocketAddress( bind_port ));
      }

      socket.setSoTimeout( 10000 );

      socket.connect( new InetSocketAddress( host, TCP_SERVER_PORT ), 10000 );

      OutputStream  os = socket.getOutputStream();

      os.write( get_str.getBytes( "ISO-8859-1" ));

      os.flush();

      InputStream  is = socket.getInputStream();

      byte[]  buffer = new byte[1];

      String  header = "";

      int content_length = -1;

      while( true ){

        int  len = is.read( buffer );

        if ( len <= 0 ){

          break;
        }

        header += (char)buffer[0];

        if ( header.endsWith( "\r\n\r\n" )){

          header = header.toLowerCase( MessageText.LOCALE_ENGLISH );

          int  pos = header.indexOf( "content-length:" );

          if ( pos == -1 ){

            throw( new IOException( "content length missing" ));
          }

          header = header.substring( pos+15 );

          pos = header.indexOf( '\r' );

          header = header.substring(0,pos).trim();

          content_length = Integer.parseInt( header );

          if ( content_length > 10000 ){

            throw( new IOException( "content length too large" ));
          }

          break;
        }

        if ( header.length() > 2048 ){

          throw( new IOException( "header too large" ));
        }
      }

      ByteArrayOutputStream  baos = new ByteArrayOutputStream( content_length );

      buffer = new byte[content_length];

      while( content_length > 0 ){

        int  len = is.read( buffer );

        if ( len <= 0 ){

          break;
        }

        baos.write( buffer, 0, len );

        content_length -= len;
      }

      if ( content_length != 0 ){

        throw( new IOException( "error reading reply" ));
      }

      byte[]  reply_bytes = baos.toByteArray();

      Map reply = BDecoder.decode( new BufferedInputStream( new ByteArrayInputStream( reply_bytes )));

      preProcessReply( reply, v6 );

      return( reply );

    }finally{

      if ( socket != null ){

        try{
          socket.close();

        }catch( Throwable e ){

        }
      }
    }
  }

  private Map
  executeUDP(
    Map        data_to_send,
    InetAddress    bind_ip,
    int        bind_port,
    boolean      v6 )

    throws Exception
  {
    if ( v6 && !enable_v6 ){

      throw( new Exception( "IPv6 is disabled" ));
    }

    String  host = getHost( v6, UDP_SERVER_ADDRESS_V6, UDP_SERVER_ADDRESS_V4 );

    PRUDPReleasablePacketHandler handler = PRUDPPacketHandlerFactory.getReleasableHandler( bind_port );

    PRUDPPacketHandler  packet_handler = handler.getHandler();

    long timeout = 5;

    Random random = new Random();

    try{
      Exception  last_error = null;

      packet_handler.setExplicitBindAddress( bind_ip );   

      for (int i=0;i<3;i++){

        try{
          // connection ids for requests must always have their msb set...
          // apart from the original darn udp tracker spec....

          long connection_id = 0x8000000000000000L | random.nextLong();

          VersionCheckClientUDPRequest  request_packet = new VersionCheckClientUDPRequest( connection_id );

          request_packet.setPayload( data_to_send );

          VersionCheckClientUDPReply reply_packet = (VersionCheckClientUDPReply)packet_handler.sendAndReceive( null, request_packet, new InetSocketAddress( host, UDP_SERVER_PORT ), timeout );

          Map  reply = reply_packet.getPayload();

          preProcessReply( reply, v6 );

          return( reply );

        }catch( Exception e){

          last_error  = e;

          timeout = timeout * 2;
        }
      }

      if ( last_error != null ){

        throw( last_error );
      }

      throw( new Exception( "Timeout" ));

    }finally{

      packet_handler.setExplicitBindAddress( null );

      handler.release();
    }
  }

  protected void
  preProcessReply(
    Map          reply,
    final boolean    v6 )
  {
    NetworkAdmin admin = NetworkAdmin.getSingleton();

    try{
      byte[] address = (byte[])reply.get( "source_ip_address" );

      InetAddress my_ip = InetAddress.getByName( new String( address ));

      NetworkAdminASN old_asn = admin.getCurrentASN();

      NetworkAdminASN new_asn = admin.lookupCurrentASN( my_ip );

      if ( !new_asn.sameAs( old_asn )){

        // kick off a secondary version check to communicate the new information

        if ( !secondary_check_done ){

          secondary_check_done  = true;

          new AEThread( "Secondary version check", true )
          {
            public void
            runSupport()
            {
              getVersionCheckInfoSupport( REASON_SECONDARY_CHECK, false, true, v6 );
            }
          }.start();
        }
      }
    }catch( Throwable e ){

      Debug.printStackTrace(e);
    }

    Long  as_advice = (Long)reply.get( "as_advice" );

    if ( as_advice != null ){

      NetworkAdminASN current_asn = admin.getCurrentASN();

      String  asn = current_asn.getASName();

      if ( asn != null ){

        long  advice = as_advice.longValue();

        if ( advice != 0 ){

          // require crypto

          String  done_asn = COConfigurationManager.getStringParameter( "ASN Advice Followed", "" );

          if ( !done_asn.equals( asn )){

            COConfigurationManager.setParameter( "ASN Advice Followed", asn );

            boolean  change   = advice == 1 || advice == 2;
            boolean  alert   = advice == 1 || advice == 3;

            if ( !COConfigurationManager.getBooleanParameter( "network.transport.encrypted.require" )){

              if ( change ){

                COConfigurationManager.setParameter( "network.transport.encrypted.require", true );
              }

              if ( alert ){

                String  msg =
                  MessageText.getString(
                      "crypto.alert.as.warning",
                      new String[]{ asn });

                Logger.log( new LogAlert( false, LogAlert.AT_WARNING, msg ));
              }
            }
          }
        }
      }
    }

    // set ui.toolbar.uiswitcher based on instructions from tracker
    // Really shouldn't be in VersionCheck client, but instead have some
    // listener and have the code elsewhere.  Simply calling
    //getVersionCheckInfo from "code elsewhere" (to get the cached result)
    //caused a deadlock at startup.
    Long lEnabledUISwitcher = (Long) reply.get("ui.toolbar.uiswitcher");
    if (lEnabledUISwitcher != null) {
      COConfigurationManager.setBooleanDefault("ui.toolbar.uiswitcher",
          lEnabledUISwitcher.longValue() == 1);
    }
  }

  public InetAddress
  getExternalIpAddressHTTP(
    boolean    v6 )

    throws Exception
  {
    Map reply = executeHTTP( new HashMap(), v6 );

    byte[] address = (byte[])reply.get( "source_ip_address" );

    return( InetAddress.getByName( new String( address )));
  }

  public InetAddress
  getExternalIpAddressTCP(
    InetAddress     bind_ip,
    int        bind_port,
    boolean      v6 )

    throws Exception
  {
    Map reply = executeTCP( new HashMap(), bind_ip, bind_port, v6 );

    byte[] address = (byte[])reply.get( "source_ip_address" );

    return( InetAddress.getByName( new String( address )));
  }

  public InetAddress
  getExternalIpAddressUDP(
    InetAddress     bind_ip,
    int        bind_port,
    boolean      v6 )

    throws Exception
  {
    Map reply = executeUDP( new HashMap(), bind_ip, bind_port, v6 );

    byte[] address = (byte[])reply.get( "source_ip_address" );

    return( InetAddress.getByName( new String( address )));
  }

  protected String
  getHost(
    boolean    v6,
    String    v6_address,
    String    v4_address )
  {
    if ( v6 ){

      v6_address = getTestAddress( true, v6_address );

      try{
        return( InetAddress.getByName( v6_address ).getHostAddress());

      }catch( UnknownHostException e ){

        try{
          return( DNSUtils.getIPV6ByName( v6_address ).getHostAddress());

        }catch( UnknownHostException f ){

          return( v6_address );
        }
      }
    }else{

      v4_address = getTestAddress( false, v4_address );

      return( v4_address );
    }
  }

  private String
  getTestAddress(
    boolean  v6,
    String  address )
  {
    return( COConfigurationManager.getStringParameter( "versioncheck.test.address." + v6, address ));
  }

  /**
   * Construct the default version check message.
   * @return message to send
   */
  public static Map<String,Object>
  constructVersionCheckMessage(
    String reason )
  {

    //only send if anonymous-check flag is not set

    boolean send_info = COConfigurationManager.getBooleanParameter( "Send Version Info" );

    Map<String,Object> message = new HashMap<String,Object>();

    //always send
    message.put( "appid",   SystemProperties.getApplicationIdentifier());
    message.put( "appname", SystemProperties.getApplicationName());
    message.put( "version", Constants.AZUREUS_VERSION );

    String  sub_ver = Constants.AZUREUS_SUBVER;

    if ( sub_ver.length() > 0 ){
      message.put( "subver", sub_ver );
    }

    if ( COConfigurationManager.getBooleanParameter( "Beta Programme Enabled" )){

      message.put( "beta_prog", "true" );
    }

    message.put( "ui",      COConfigurationManager.getStringParameter( "ui", "unknown" ) );
    message.put( "os",      Constants.OSName );
    message.put( "os_version", System.getProperty( "os.version" ) );
    message.put( "os_arch", System.getProperty( "os.arch" ) );   //see http://lopica.sourceforge.net/os.html

    boolean using_phe = COConfigurationManager.getBooleanParameter( "network.transport.encrypted.require" );
    message.put( "using_phe", using_phe ? new Long(1) : new Long(0) );

    //swt stuff
    try {
      Class c = Class.forName( "org.eclipse.swt.SWT" );

      String swt_platform = (String)c.getMethod( "getPlatform", new Class[]{} ).invoke( null, new Object[]{} );
      message.put( "swt_platform", swt_platform );

      Integer swt_version = (Integer)c.getMethod( "getVersion", new Class[]{} ).invoke( null, new Object[]{} );
      message.put( "swt_version", new Long( swt_version.longValue() ) );

      if ( send_info ){
        c = Class.forName("org.gudy.azureus2.ui.swt.mainwindow.MainWindow");
        if (c != null) {
          c.getMethod("addToVersionCheckMessage", new Class[] { Map.class }).invoke(
              null, new Object[] { message });
        }  
      }
    }
    catch( ClassNotFoundException e ) {  /* ignore */ }
    catch( NoClassDefFoundError er ) {  /* ignore */ }
    catch( InvocationTargetException err ) {  /* ignore */ }
    catch( Throwable t ) {  t.printStackTrace()}


    int last_send_time = COConfigurationManager.getIntParameter( "Send Version Info Last Time", -1 );
    int current_send_time = (int)(SystemTime.getCurrentTime()/1000);   
    COConfigurationManager.setParameter( "Send Version Info Last Time", current_send_time );


    String id = COConfigurationManager.getStringParameter( "ID", null );

    if( id != null && send_info ) {     
      message.put( "id", id );   

      try{
        byte[] id2 = CryptoManagerFactory.getSingleton().getSecureID();

        message.put( "id2", id2 );

      }catch( Throwable e ){
      }

      if ( last_send_time != -1 && last_send_time < current_send_time ){       
        // time since last       
        message.put( "tsl", new Long(current_send_time-last_send_time));
      }

      message.put( "reason", reason );

      String  java_version = System.getProperty( "java.version" );
      if ( java_version == null ){  java_version = "unknown"}
      message.put( "java", java_version );


      String  java_vendor = System.getProperty( "java.vm.vendor" );
      if ( java_vendor == null ){   java_vendor = "unknown"}
      message.put( "javavendor", java_vendor );


      long  max_mem = Runtime.getRuntime().maxMemory()/(1024*1024);
      message.put( "javamx", new Long( max_mem ) );

      String java_rt_name = System.getProperty("java.runtime.name");
      if (java_rt_name != null) {
        message.put( "java_rt_name", java_rt_name);
      }

      String java_rt_version = System.getProperty("java.runtime.version");
      if (java_rt_version != null) {
        message.put( "java_rt_version", java_rt_version);
      }

      OverallStats  stats = StatsFactory.getStats();

      if ( stats != null ){

        //long total_bytes_downloaded   = stats.getDownloadedBytes();
        //long total_bytes_uploaded    = stats.getUploadedBytes();
        long total_uptime       = stats.getTotalUpTime();

        //removed due to complaints about anonymous stats collection
        //message.put( "total_bytes_downloaded", new Long( total_bytes_downloaded ) );
        //message.put( "total_bytes_uploaded", new Long( total_bytes_uploaded ) );
        message.put( "total_uptime", new Long( total_uptime ) );
        //message.put( "dlstats", stats.getDownloadStats());
      }

      try{
        NetworkAdminASN current_asn = NetworkAdmin.getSingleton().getCurrentASN();

        String  as = current_asn.getAS();

        message.put( "ip_as", current_asn.getAS());

        String  asn = current_asn.getASName();

        if ( asn.length() > 64 ){

          asn = asn.substring( 0, 64 );
        }

        message.put( "ip_asn", asn );

      }catch( Throwable e ){

        Debug.out( e );
      }

      // send locale, so we can determine which languages need attention
      message.put("locale", Locale.getDefault().toString());
      String originalLocale = System.getProperty("user.language") + "_"
      + System.getProperty("user.country");
      String variant = System.getProperty("user.variant");
      if (variant != null && variant.length() > 0) {
        originalLocale += "_" + variant;
      }
      message.put("orig_locale", originalLocale);

      // We may want to reply differently if the user is in Beginner mode vs Advanced
      message.put("user_mode",
          COConfigurationManager.getIntParameter("User Mode", -1));

      Set<String> features = UtilitiesImpl.getFeaturesInstalled();

      if ( features.size() > 0 ){

        String str = "";

        for ( String f: features ){
          str += (str.length()==0?"":",") + f;
        }

        message.put( "vzfeatures", str );
      }

      try{
        if ( AzureusCoreFactory.isCoreAvailable()){

          //installed plugin IDs
          PluginInterface[] plugins = AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaces();

          List pids = new ArrayList();

          List vs_data = new ArrayList();

          for (int i=0;i<plugins.length;i++){

            PluginInterface    plugin = plugins[i];

            String  pid = plugin.getPluginID();

            String  info = plugin.getPluginconfig().getPluginStringParameter( "plugin.info" );

            // filter out built-in and core ones
            if (   ( info != null && info.length() > 0 ) ||
                !pid.startsWith( "<" ) &&
                    !pid.startsWith( "azbp" ) &&
                    !pid.startsWith( "azupdater" ) &&
                    !pid.startsWith( "azplatform" ) &&
                    !pids.contains( pid ))){

              if ( info != null && info.length() > 0 ){

                if( info.length() < 256 ){

                  pid += ":" + info;

                }else{

                  Debug.out( "Plugin '" + pid + "' reported excessive info string '" + info + "'" );
                }
              }

              pids.add( pid );
            }

            Map  data = plugin.getPluginconfig().getPluginMapParameter( "plugin.versionserver.data", null );

            if ( data != null ){

              Map payload = new HashMap();

              byte[]  data_bytes = BEncoder.encode( data );

              if ( data_bytes.length > 16*1024 ){

                Debug.out( "Plugin '" + pid + "' reported excessive version server data (length=" + data_bytes.length + ")" );

                payload.put( "error", "data too long: " + data_bytes.length );

              }else{

                payload.put( "data", data_bytes );
              }

              payload.put( "id", pid);
              payload.put( "version", plugin.getPluginVersion());

              vs_data.add( payload );
            }
          }
          message.put( "plugins", pids );

          if ( vs_data.size() > 0 ){

            message.put( "plugin_data", vs_data );
          }
        }
      }catch( Throwable e ){

        Debug.out( e );
      }
    }


    return message;
  }

  public void
  addVersionCheckClientListener(
      boolean triggerStartListener,
      VersionCheckClientListener l)
  {
    synchronized (listeners) {
      listeners.add(l);

      if (triggerStartListener && startCheckRan) {
        try {
          l.versionCheckStarted(REASON_UPDATE_CHECK_START);
        } catch (Exception e) {
          Debug.out(e);
        }
      }
    }
  }

  public void
  removeVersionCheckClientListener(
    VersionCheckClientListener l)
  {
    synchronized (listeners) {
      listeners.remove(l);
    }
  }


  public static void
  main(
    String[]  args )
  {
    try{
      COConfigurationManager.initialise();

      boolean v6= true;

      // Test connectivity.
      if (true) {
        // System.out.println( "UDP:  " + getSingleton().getExternalIpAddressUDP(null,0,v6));
        // System.out.println( "TCP:  " + getSingleton().getExternalIpAddressTCP(null,0,v6));
        // System.out.println( "HTTP: " + getSingleton().getExternalIpAddressHTTP(v6));
      }

      Map data = constructVersionCheckMessage(VersionCheckClient.REASON_UPDATE_CHECK_START);
      System.out.println("Sending (pre-initialisation):");
      printDataMap(data);
      System.out.println("-----------");

      System.out.println("Receiving (pre-initialisation):");
      printDataMap(getSingleton().getVersionCheckInfo(VersionCheckClient.REASON_UPDATE_CHECK_START));
      System.out.println("-----------");

      System.out.println();
      System.out.print("Initialising core... ");

      /**
       * Suppress all of these errors being displayed in the output.
       *
       * These things should be temporary...
       */
      AzureusCoreImpl.SUPPRESS_CLASSLOADER_ERRORS = true;
      DownloadManagerStateImpl.SUPPRESS_FIXUP_ERRORS = true;

      AzureusCore core = AzureusCoreFactory.create();
      core.start();
      System.out.println("done.");
      System.out.println();
      System.out.println("-----------");

      data = constructVersionCheckMessage(VersionCheckClient.REASON_UPDATE_CHECK_START);
      System.out.println("Sending (post-initialisation):");
      printDataMap(data);
      System.out.println("-----------");

      System.out.println("Receiving (post-initialisation):");
      printDataMap(getSingleton().getVersionCheckInfo(VersionCheckClient.REASON_UPDATE_CHECK_START));
      System.out.println("-----------");
      System.out.println();

      System.out.print("Shutting down core... ");
      core.stop();
      System.out.println("done.");

    }catch( Throwable e){
      e.printStackTrace();
    }
  }

  // Used for debugging in main.
  private static void
  printDataMap(Map map)
    throws Exception
  {
    TreeMap res = new TreeMap(map);
   
    Iterator key_itr = map.keySet().iterator();
   
    while (key_itr.hasNext()) {
      Object key = key_itr.next();
      Object val = map.get(key);
      if (val instanceof byte[]) {
        String as_bytes = ByteFormatter.nicePrint((byte[])val);
        String as_text = new String((byte[])val, Constants.BYTE_ENCODING);
        res.put(key, as_text + " [" + as_bytes + "]");
      }
    }

    Iterator entries = res.entrySet().iterator();
    Map.Entry entry;
    while (entries.hasNext()) {
      entry = (Map.Entry)entries.next();
      System.out.print("  ");
      System.out.print(entry.getKey());
      System.out.print(": ");
      System.out.print(entry.getValue());
      System.out.println();
    }
  }
}
TOP

Related Classes of com.aelitis.azureus.core.versioncheck.VersionCheckClient

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.