/*
* Created on 03-Nov-2005
* Created by Paul Gardner
* Copyright (C) 2005, 2006 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 40,000 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*
*/
package com.aelitis.azureus.core.clientmessageservice.secure.impl;
import java.security.interfaces.RSAPublicKey;
import java.util.*;
import org.gudy.azureus2.core3.util.AEMonitor;
import org.gudy.azureus2.core3.util.AESemaphore;
import org.gudy.azureus2.core3.util.AEThread;
import org.gudy.azureus2.core3.util.SystemTime;
import com.aelitis.azureus.core.clientmessageservice.ClientMessageService;
import com.aelitis.azureus.core.clientmessageservice.secure.SecureMessageServiceClient;
import com.aelitis.azureus.core.clientmessageservice.secure.SecureMessageServiceClientAdapter;
import com.aelitis.azureus.core.clientmessageservice.secure.SecureMessageServiceClientListener;
import com.aelitis.azureus.core.clientmessageservice.secure.SecureMessageServiceClientMessage;
public class
SecureMessageServiceClientImpl
implements SecureMessageServiceClient
{
// these also occur in the server
public static final int STATUS_OK = 0;
public static final int STATUS_LOGON_FAIL = 1;
public static final int STATUS_INVALID_SEQUENCE = 2;
public static final int STATUS_FAILED = 3;
public static final int STATUS_ABORT = 4;
public static final String SERVICE_NAME = "SecureMsgServ";
private static final long MIN_RETRY_PERIOD = 5*60*1000;
private static final long MAX_RETRY_PERIOD = 2*60*60*1000;
private String host;
private int port;
private int timeout_secs;
private RSAPublicKey public_key;
private SecureMessageServiceClientAdapter adapter;
private long retry_millis = MIN_RETRY_PERIOD;
private int connect_failure_count = 0;
private AEMonitor message_mon;
private AESemaphore message_sem;
private String last_failed_user_pw = "";
private long last_failed_user_pw_time;
private List messages = new ArrayList();
private List listeners = new ArrayList();
public
SecureMessageServiceClientImpl(
String _host,
int _port,
int _timeout_secs,
RSAPublicKey _key,
SecureMessageServiceClientAdapter _adapter )
{
host = _host;
port = _port;
timeout_secs = _timeout_secs;
public_key = _key;
adapter = _adapter;
message_mon = new AEMonitor( "SecureService:messages" );
message_sem = new AESemaphore( "SecureService:messages" );
new AEThread( "SecureService::messageSender", true )
{
public void
runSupport()
{
while( true ){
long time = retry_millis;
if ( connect_failure_count > 0 ){
for (int i=0;i<connect_failure_count;i++){
time = time + time;
if ( time > MAX_RETRY_PERIOD ){
time = MAX_RETRY_PERIOD;
break;
}
}
}
message_sem.reserve( time );
try{
sendMessagesSupport();
}catch( Throwable e ){
adapter.log( "Request processing failed", e);
}
}
}
}.start();
}
public void
sendMessages()
{
message_sem.release();
}
protected void
sendMessagesSupport()
{
String user = adapter.getUser();
byte[] password = adapter.getPassword();
String user_password = user + "/" + new String( password );
// user name must be defined, however we allow a blank password
if ( user.length() == 0 ){
adapter.authenticationFailed();
return;
}
// if user-name + password hasn't changed recently and logon failed then
// don't re-attempt
if ( user_password.equals( last_failed_user_pw )){
final long now =SystemTime.getCurrentTime();
if (now >last_failed_user_pw_time &&now -last_failed_user_pw_time <60 *1000){
adapter.authenticationFailed();
return;
}
}
List outstanding_messages;
try{
message_mon.enter();
outstanding_messages = new ArrayList( messages );
}finally{
message_mon.exit();
}
if ( outstanding_messages.size() == 0 ){
return;
}
List complete_messages = new ArrayList();
boolean failed = false;
try{
Iterator it = outstanding_messages.iterator();
while( it.hasNext() && !failed ){
SecureMessageServiceClientMessageImpl message = (SecureMessageServiceClientMessageImpl)it.next();
boolean retry = true;
int retry_count = 0;
while( retry && !failed ){
retry = false;
ClientMessageService message_service = null;
boolean got_reply = false;
try{
Map content = new HashMap();
long sequence = adapter.getMessageSequence();
content.put( "user", user );
content.put( "password", password );
content.put( "seq", new Long( sequence ));
content.put( "request", message.getRequest());
last_failed_user_pw = "";
message_service = SecureMessageServiceClientHelper.getServerService( host, port, timeout_secs, SERVICE_NAME, public_key );
message_service.sendMessage( content );
Map reply = message_service.receiveMessage();
got_reply = true;
long status = ((Long)reply.get( "status" )).longValue();
Long new_retry = (Long)reply.get( "retry" );
if ( new_retry != null ){
retry_millis = new_retry.longValue();
if ( retry_millis < MIN_RETRY_PERIOD ){
retry_millis = MIN_RETRY_PERIOD;
}
adapter.log( "Server requested retry period of " + (retry_millis/1000) + " seconds" );
}else{
retry_millis = MIN_RETRY_PERIOD;
}
if ( status == STATUS_OK ){
message.setReply( (Map)reply.get( "reply" ));
adapter.log( "Request successfully sent: " + message.getRequest() + "->" + message.getReply());
adapter.setMessageSequence( sequence + 1 );
adapter.serverOK();
for (Iterator l_it=listeners.iterator();l_it.hasNext();){
try{
((SecureMessageServiceClientListener)l_it.next()).complete( message );
}catch( Throwable e ){
e.printStackTrace();
}
}
complete_messages.add( message );
}else if ( status == STATUS_LOGON_FAIL ){
last_failed_user_pw = user_password;
last_failed_user_pw_time = SystemTime.getCurrentTime();
adapter.serverOK();
adapter.authenticationFailed();
failed = true;
}else if ( status == STATUS_INVALID_SEQUENCE ){
if ( retry_count == 1 ){
adapter.serverFailed( new Exception( "Sequence resynchronisation failed" ));
failed = true;
}else{
retry_count++;
retry = true;
long expected_sequence = ((Long)reply.get( "seq" )).longValue();
adapter.log( "Sequence resynchronise: local = " + sequence + ", remote = " + expected_sequence );
adapter.setMessageSequence( expected_sequence );
}
}else if ( status == STATUS_FAILED ){
adapter.serverFailed( new Exception( new String( (byte[])reply.get( "error" ))));
failed = true;
}else{// if ( status == STATUS_ABORT ){
// this is when things have gone badly wrong server-side - we just
// dump the message
adapter.serverFailed( new Exception( "Server requested abort" ));
for (Iterator l_it=listeners.iterator();l_it.hasNext();){
try{
((SecureMessageServiceClientListener)l_it.next()).aborted(
message,
new String( (byte[])reply.get( "error" )));
}catch( Throwable e ){
e.printStackTrace();
}
}
complete_messages.add( message );
}
}catch( Throwable e ){
adapter.serverFailed( e );
failed = true;
}finally{
if ( got_reply ){
connect_failure_count = 0;
}else{
connect_failure_count++;
if ( connect_failure_count > 1 ){
try{
adapter.log( "Failed to contact server " + connect_failure_count + " times in a row" );
}catch( Throwable e ){
e.printStackTrace();
}
}
}
if ( message_service != null ){
message_service.close();
}
}
}
}
}catch( Throwable e ){
adapter.serverFailed( e );
}finally{
try{
message_mon.enter();
messages.removeAll( complete_messages );
}finally{
message_mon.exit();
}
}
}
public SecureMessageServiceClientMessage
sendMessage(
Map request,
Object data,
String description )
{
try{
message_mon.enter();
SecureMessageServiceClientMessage res = new SecureMessageServiceClientMessageImpl( this, request, data, description );
messages.add( res );
message_sem.release();
return( res );
}finally{
message_mon.exit();
}
}
protected void
cancel(
SecureMessageServiceClientMessage message )
{
boolean inform = false;
try{
message_mon.enter();
if ( messages.remove( message )){
inform = true;
}
}finally{
message_mon.exit();
}
if ( inform ){
for (Iterator it=listeners.iterator();it.hasNext();){
try{
((SecureMessageServiceClientListener)it.next()).cancelled( message );
}catch( Throwable e ){
e.printStackTrace();
}
}
}
}
public SecureMessageServiceClientMessage[]
getMessages()
{
try{
message_mon.enter();
return((SecureMessageServiceClientMessage[])messages.toArray( new SecureMessageServiceClientMessage[ messages.size()]));
}finally{
message_mon.exit();
}
}
public void
addListener(
SecureMessageServiceClientListener l )
{
listeners.add( l );
}
public void
removeListener(
SecureMessageServiceClientListener l )
{
listeners.remove( l );
}
}