00001
00002
00003
00004
00005 package pbx;
00006
00007 import java.io.BufferedReader;
00008 import java.io.IOException;
00009 import java.io.InputStreamReader;
00010 import java.io.OutputStreamWriter;
00011 import java.io.PrintWriter;
00012 import java.net.Socket;
00013 import java.net.UnknownHostException;
00014 import java.util.regex.Pattern;
00015
00016 import utils.Log;
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031 public class PBXClient extends Thread
00032 {
00033
00034
00035
00036 public enum CMType
00037 {
00038
00039 _INVALID_,
00040
00041
00042 INVITE,
00043
00044
00045 RING,
00046
00047
00048 ACCEPT,
00049
00050
00051 BYE,
00052
00053
00054 INSTANTMESSAGE,
00055
00056
00057 LIST,
00058
00059
00060 ALIVE
00061 }
00062
00063
00064
00065
00066 public class ControlMessage
00067 {
00068 public CMType msgType;
00069 public String peerUserId;
00070 public String localUserId;
00071 public String peerAddr;
00072 public int peerPort;
00073 public String secret;
00074
00075 public ControlMessage( CMType msgToken, String peerUserId, String localUserId,
00076 String peerAddr, int peerPort, String secret )
00077 {
00078 this.msgType = msgToken;
00079 this.peerUserId = peerUserId;
00080 this.localUserId = localUserId;
00081 this.peerAddr = peerAddr;
00082 this.peerPort = peerPort;
00083 this.secret = secret;
00084 }
00085
00086 public String getVerboseRemote () {
00087 if ( peerAddr.length () == 0 || peerPort == 0 ) {
00088 return "'" + peerUserId + "'";
00089 } else {
00090 return "'" + peerUserId + "' at " + peerAddr + ":" + peerPort;
00091 }
00092 }
00093 }
00094
00095
00096
00097
00098
00099
00100 public interface Context
00101 {
00102
00103
00104
00105 public abstract String getUserId ();
00106
00107
00108
00109
00110 public abstract void setPbxStatus( String str );
00111
00112
00113
00114
00115 public abstract void report( String style, String str );
00116
00117
00118
00119
00120 public void reportIncomingTextMessage( String cssClass,
00121 String userId, String message );
00122
00123
00124
00125
00126 public abstract void onInvite( final ControlMessage cmsg );
00127
00128
00129
00130
00131 public abstract void onRing( final ControlMessage cmsg );
00132
00133
00134
00135
00136 public abstract void onAccept( final ControlMessage cmsg );
00137
00138
00139
00140
00141 public abstract void onBye( final ControlMessage cmsg );
00142
00143
00144
00145
00146 public abstract void onInstantMessage( final ControlMessage cmsg );
00147 }
00148
00149
00150
00151
00152
00153
00154 private final static String WARN = "logWarn";
00155
00156
00157
00158
00159 private final static String INFO = "logInfo";
00160
00161
00162
00163
00164 private String host = null;
00165
00166
00167
00168
00169 private int port = -1;
00170
00171
00172
00173
00174 private String myID;
00175
00176
00177
00178
00179 private PrintWriter out = null;
00180
00181
00182
00183
00184 private Socket socket = null;
00185
00186
00187
00188
00189 private volatile boolean running = false;
00190
00191
00192
00193
00194 private Context context = null;
00195
00196
00197
00198
00199
00200
00201
00202
00203
00204
00205
00206 public PBXClient( String host, int port, Context context )
00207 {
00208 super( "Chat-" + host + ":" + port );
00209
00210 this.host = host;
00211 this.port = port;
00212 this.context = context;
00213
00214 this.myID = host + ":" + port;
00215 }
00216
00217
00218
00219
00220 public String getLocalAddress ()
00221 {
00222 return socket.getLocalAddress ().getHostAddress ();
00223 }
00224
00225
00226
00227
00228 @Override
00229 public void start ()
00230 {
00231 if ( isAlive () || running ) {
00232 return;
00233 }
00234
00235 running = true;
00236 super.start ();
00237 }
00238
00239
00240
00241
00242
00243
00244 public void send( String message )
00245 {
00246 synchronized( this )
00247 {
00248 if ( out == null || message == null ) {
00249 return;
00250 }
00251
00252 out.println( message );
00253 out.flush ();
00254 }
00255 }
00256
00257
00258
00259
00260
00261
00262
00263 public void send( String message, String userId )
00264 {
00265
00266
00267 userId = userId.trim().replaceAll( "\\s{1,}", "-" );
00268
00269
00270
00271 send( userId + " :: " + message );
00272 }
00273
00274
00275
00276
00277 public void close ()
00278 {
00279 synchronized( this )
00280 {
00281 running = false;
00282
00283 if ( socket != null && ! socket.isClosed () ) {
00284 try {
00285 socket.close ();
00286 } catch( IOException e ) {
00287
00288 }
00289 }
00290 }
00291 }
00292
00293
00294
00295
00296 private void report( String style, String str )
00297 {
00298 context.report( style, str );
00299 }
00300
00301
00302
00303
00304 private void reportIncomingTextMessage( String userId, String message )
00305 {
00306 context.reportIncomingTextMessage( "chatMessage", userId, message );
00307 }
00308
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319
00320
00321 private void parseInputMessage( String message )
00322 {
00323
00324
00325
00326 String[] parts = message.split( ":: ", 2 );
00327 String userId = "[Anonymous]";
00328
00329 if ( message.startsWith( "WWHHOO: " ) ) {
00330 userId = "WWHHOO";
00331 message = message.substring( 8 );
00332 } else if ( parts.length == 0 ) {
00333 message = parts[0];
00334 } else if ( parts[0].trim().length () == 0 && parts.length >= 2 ) {
00335 message = parts[1];
00336 } else if ( parts.length >= 2 ) {
00337 userId = parts[0].trim ();
00338 message = parts[1];
00339 } else {
00340 message = parts[0];
00341 }
00342
00343
00344
00345
00346 parts = message.trim().split( "\\s{1,}" );
00347
00348 if ( ! userId.equals( "[Anonymous]" )
00349 && parts.length >= 1
00350 && parts[0].equals( "[$]" ) )
00351 {
00352 parseControlMessage( userId, parts, message );
00353 }
00354 else
00355 {
00356 reportIncomingTextMessage( userId, message );
00357 }
00358 }
00359
00360
00361
00362
00363
00364
00365
00366
00367
00368
00369
00370
00371
00372
00373
00374
00375
00376
00377 private void parseControlMessage( String remoteUserId, String[] args, String original )
00378 {
00379 assert args.length >= 1 && args[0].equals( "[$]" );
00380
00381
00382
00383 CMType cmType = CMType._INVALID_;
00384
00385 if ( args[1].equalsIgnoreCase( "invite" ) ) {
00386 cmType = CMType.INVITE;
00387 } else if ( args[1].equalsIgnoreCase( "ring" ) ) {
00388 cmType = CMType.RING;
00389 } else if ( args[1].equalsIgnoreCase( "accept" ) ) {
00390 cmType = CMType.ACCEPT;
00391 } else if ( args[1].equalsIgnoreCase( "bye" ) ) {
00392 cmType = CMType.BYE;
00393 } else if ( args[1].equalsIgnoreCase( "imsg" ) ) {
00394 cmType = CMType.INSTANTMESSAGE;
00395 } else if ( args[1].equalsIgnoreCase( "list" ) ) {
00396 cmType = CMType.LIST;
00397 } else if ( args[1].equalsIgnoreCase( "alive" ) ) {
00398 cmType = CMType.ALIVE;
00399 } else {
00400 return;
00401 }
00402
00403 String destinationUserId = null;
00404
00405
00406
00407
00408 if ( cmType != CMType.LIST && cmType != CMType.ALIVE && args.length >= 3 )
00409 {
00410 destinationUserId = args[2];
00411
00412 if ( remoteUserId.equalsIgnoreCase( destinationUserId ) ) {
00413 return;
00414 } else if ( ! destinationUserId.equalsIgnoreCase( context.getUserId () ) ) {
00415 return;
00416 }
00417 }
00418
00419
00420
00421
00422
00423 if ( args.length >= 5 && args[1].equalsIgnoreCase( "invite" ) )
00424 {
00425 String publicKey = args.length >= 6 ? args[5] : null;
00426
00427 try
00428 {
00429 int port = Integer.parseInt( args[4] );
00430
00431 context.onInvite( new ControlMessage( CMType.INVITE,
00432 remoteUserId, destinationUserId, args[3], port, publicKey ) );
00433 }
00434 catch( NumberFormatException e )
00435 {
00436
00437 }
00438 }
00439
00440
00441
00442
00443 else if ( args.length >= 5 && args[1].equalsIgnoreCase( "ring" ) )
00444 {
00445 String publicKey = args.length >= 6 ? args[5] : null;
00446
00447 try
00448 {
00449 int port = Integer.parseInt( args[4] );
00450
00451 context.onRing( new ControlMessage( CMType.RING,
00452 remoteUserId, destinationUserId, args[3], port, publicKey ) );
00453 }
00454 catch( NumberFormatException e )
00455 {
00456
00457 }
00458 }
00459
00460
00461
00462
00463 else if ( args.length >= 5 && args[1].equalsIgnoreCase( "accept" ) )
00464 {
00465 String secretKey = args.length >= 6 ? args[5] : null;
00466
00467 try
00468 {
00469 int port = Integer.parseInt( args[4] );
00470
00471 context.onAccept( new ControlMessage( CMType.ACCEPT,
00472 remoteUserId, destinationUserId, args[3], port, secretKey ) );
00473 }
00474 catch( NumberFormatException e )
00475 {
00476
00477 }
00478 }
00479
00480
00481
00482
00483 else if ( args.length >= 3 && args[1].equalsIgnoreCase( "bye" ) )
00484 {
00485 try
00486 {
00487 String host = args.length >= 4 ? args[3] : "";
00488 int port = args.length >= 5 ? Integer.parseInt( args[4] ) : 0;
00489
00490 context.onBye( new ControlMessage( CMType.BYE,
00491 remoteUserId, destinationUserId, host, port, null ) );
00492 }
00493 catch( NumberFormatException e )
00494 {
00495
00496 }
00497 }
00498
00499
00500
00501
00502 else if ( args.length >= 4 && args[1].equalsIgnoreCase( "imsg" ) )
00503 {
00504 context.onInstantMessage( new ControlMessage( CMType.INSTANTMESSAGE,
00505 remoteUserId, destinationUserId, "", 0, args[3] ) );
00506 }
00507
00508
00509
00510
00511 else if ( args.length >= 2 && args[1].equalsIgnoreCase( "list" ) )
00512 {
00513 String myUserId = context.getUserId ();
00514
00515 report( INFO, "Listing users..." );
00516
00517 if ( ! myUserId.isEmpty () )
00518 {
00519 if ( args.length < 3 )
00520 {
00521
00522
00523 send( "[$] ALIVE", myUserId );
00524 }
00525 else
00526 {
00527 Pattern p = null;
00528 try {
00529 p = Pattern.compile( args[2], Pattern.CASE_INSENSITIVE );
00530 } catch ( Throwable e ) {
00531
00532 }
00533 if ( p != null && p.matcher( myUserId ).find () )
00534 {
00535
00536
00537 send( "[$] ALIVE", myUserId );
00538 }
00539 }
00540 }
00541 }
00542
00543
00544
00545
00546 else if ( args.length >= 2
00547 && args[1].equalsIgnoreCase( "alive" ) )
00548 {
00549 report( INFO, "-- User '" + remoteUserId + "' is alive." );
00550
00551
00552
00553 }
00554 }
00555
00556
00557
00558
00559 public void sendInvite( String remoteUserId,
00560 String localIpAddress, int localUdpPort, String publicKey )
00561 {
00562 this.send( "[$] INVITE " + remoteUserId + " "
00563 + localIpAddress + " " + localUdpPort
00564 + ( publicKey != null ? " " + publicKey : "" ),
00565 context.getUserId () );
00566 }
00567
00568
00569
00570
00571 public void sendRing( String remoteUserId,
00572 String localIpAddress, int localUdpPort, String publicKey )
00573 {
00574 this.send( "[$] RING " + remoteUserId + " "
00575 + localIpAddress + " " + localUdpPort
00576 + ( publicKey != null ? " " + publicKey : "" ),
00577 context.getUserId () );
00578 }
00579
00580
00581
00582
00583 public void sendAccept( String remoteUserId,
00584 String localIpAddress, int localUdpPort, String publicKey )
00585 {
00586 this.send( "[$] ACCEPT " + remoteUserId + " "
00587 + localIpAddress + " " + localUdpPort
00588 + ( publicKey != null ? " " + publicKey : "" ),
00589 context.getUserId () );
00590 }
00591
00592
00593
00594
00595 public void sendBye( String remoteUserId,
00596 String localIpAddress, int localUdpPort )
00597 {
00598 this.send( "[$] BYE " + remoteUserId + " "
00599 + localIpAddress + " " + localUdpPort,
00600 context.getUserId () );
00601 }
00602
00603
00604
00605
00606 public void sendInstantMessage( String remoteUserId, String encryptedMessage )
00607 {
00608 this.send( "[$] IMSG " + remoteUserId + " " + encryptedMessage,
00609 context.getUserId () );
00610 }
00611
00612
00613
00614
00615 public void sendListPeers( String regex )
00616 {
00617 this.send( "[$] LIST" + ( regex != null ? " " + regex : "" ),
00618 context.getUserId () );
00619 }
00620
00621
00622
00623
00624
00625
00626
00627
00628 @Override
00629 public void run ()
00630 {
00631 Log.trace( "Thread started" );
00632
00633
00634
00635
00636 report( INFO, "Connecting to " + myID + "..." );
00637 context.setPbxStatus( "Connecting to " + myID + "..." );
00638
00639 try
00640 {
00641 synchronized( this ) {
00642 socket = new Socket( host, port );
00643 }
00644 }
00645 catch( UnknownHostException e )
00646 {
00647 report( WARN, "'Unknown host' exception while creating socket" );
00648 report( WARN, e.toString () );
00649 running = false;
00650 }
00651 catch( IOException e )
00652 {
00653 report( WARN, "I/O exception while connecting" );
00654 report( WARN, e.toString () );
00655 running = false;
00656 }
00657
00658
00659
00660
00661
00662 InputStreamReader reader = null;
00663 try
00664 {
00665 if ( socket != null ) {
00666 reader = new InputStreamReader( socket.getInputStream (), "utf-8" );
00667 }
00668 }
00669 catch( IOException e )
00670 {
00671 report( WARN, "I/O exception while getting input stream" );
00672 report( WARN, e.toString () );
00673 running = false;
00674 reader = null;
00675 }
00676
00677
00678
00679
00680 try
00681 {
00682 if ( socket != null ) {
00683 out = new PrintWriter(
00684 new OutputStreamWriter( socket.getOutputStream(), "utf-8" ),
00685 true );
00686 }
00687 }
00688 catch( IOException e )
00689 {
00690 report( WARN, "I/O exception while getting output stream" );
00691 report( WARN, e.toString () );
00692 running = false;
00693 out = null;
00694 }
00695
00696
00697
00698
00699 BufferedReader in = null;
00700 if ( running )
00701 {
00702 context.setPbxStatus( "Connected to " + myID );
00703 report( "logOk", "Connected to " + myID + ". Ready to communicate..." );
00704
00705 in = new BufferedReader( reader );
00706 }
00707
00708
00709
00710
00711 while( running )
00712 {
00713 try
00714 {
00715 parseInputMessage( in.readLine () );
00716 }
00717 catch( IOException e )
00718 {
00719 report( WARN, "Connection lost!" );
00720 report( WARN, e.toString () );
00721 running = false;
00722 }
00723 }
00724
00725 report( INFO, "Closing connection " + myID + "..." );
00726
00727
00728
00729
00730 try
00731 {
00732 if ( out != null ) {
00733 out.close ();
00734 }
00735 if ( in != null ) {
00736 in.close ();
00737 }
00738 synchronized( this )
00739 {
00740 if ( socket != null && ! socket.isClosed () ) {
00741 socket.close ();
00742 }
00743 }
00744 }
00745 catch( IOException e )
00746 {
00747 report( WARN, "I/O exception while closing connection" );
00748 report( WARN, e.toString () );
00749 }
00750
00751 report( INFO, "... connection closed " + myID );
00752 Log.trace( "Thread completed" );
00753 }
00754 }