Initial revision
authorphasefx <phasefx@9efc2488-bf62-4759-914b-345cdb29e865>
Fri, 4 Feb 2005 22:08:15 +0000 (22:08 +0000)
committerphasefx <phasefx@9efc2488-bf62-4759-914b-345cdb29e865>
Fri, 4 Feb 2005 22:08:15 +0000 (22:08 +0000)
git-svn-id: svn://svn.open-ils.org/OpenSRF/trunk@2 9efc2488-bf62-4759-914b-345cdb29e865

80 files changed:
bin/jabber_users_create [new file with mode: 0755]
bin/opensrf_ctl [new file with mode: 0755]
doc/OpenSRF-Messaging-Protocol.html [new file with mode: 0644]
examples/math_bench.pl [new file with mode: 0755]
examples/math_shell.pl [new file with mode: 0755]
examples/math_simple.pl [new file with mode: 0755]
include/opensrf/generic_utils.h [new file with mode: 0644]
include/opensrf/transport_client.h [new file with mode: 0644]
include/opensrf/transport_message.h [new file with mode: 0644]
include/opensrf/transport_session.h [new file with mode: 0644]
include/opensrf/transport_socket.h [new file with mode: 0644]
src/javascript/JSON.js [new file with mode: 0644]
src/javascript/md5.js [new file with mode: 0644]
src/javascript/opensrf_app_session.js [new file with mode: 0644]
src/javascript/opensrf_config.js [new file with mode: 0644]
src/javascript/opensrf_dom_element.js [new file with mode: 0644]
src/javascript/opensrf_domain_object.js [new file with mode: 0644]
src/javascript/opensrf_jabber_transport.js [new file with mode: 0644]
src/javascript/opensrf_msg_stack.js [new file with mode: 0644]
src/javascript/opensrf_transport.js [new file with mode: 0644]
src/javascript/opensrf_utils.js [new file with mode: 0644]
src/libtransport/Makefile [new file with mode: 0644]
src/libtransport/basic_client.c [new file with mode: 0644]
src/libtransport/generic_utils.c [new file with mode: 0644]
src/libtransport/transport_client.c [new file with mode: 0644]
src/libtransport/transport_message.c [new file with mode: 0644]
src/libtransport/transport_session.c [new file with mode: 0644]
src/libtransport/transport_socket.c [new file with mode: 0644]
src/patch/README [new file with mode: 0644]
src/patch/mod_offline.c [new file with mode: 0644]
src/patch/nad.c [new file with mode: 0644]
src/perlmods/JSON.pm [new file with mode: 0644]
src/perlmods/OpenSRF.pm [new file with mode: 0644]
src/perlmods/OpenSRF/AppSession.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Application.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Application/Client.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Application/Demo/Math.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Application/Demo/MathDB.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/domainObject.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/domainObjectAttr.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/domainObjectCollection.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/param.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/params.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/searchCriteria.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/searchCriterium.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/searchTargetValue.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DOM/Element/userAuth.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DomainObject.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DomainObject/oilsMessage.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DomainObject/oilsMethod.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DomainObject/oilsMultiSearch.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DomainObject/oilsPrimitive.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DomainObject/oilsResponse.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DomainObject/oilsSearch.pm [new file with mode: 0644]
src/perlmods/OpenSRF/DomainObjectCollection.pm [new file with mode: 0644]
src/perlmods/OpenSRF/EX.pm [new file with mode: 0644]
src/perlmods/OpenSRF/System.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/Jabber.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/Jabber/JInbound.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/Jabber/JMessageWrapper.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/Jabber/JPeerConnection.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/Jabber/JabberClient.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/Listener.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/PeerHandle.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/SlimJabber.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/SlimJabber/Client.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/SlimJabber/Inbound.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/SlimJabber/MessageWrapper.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Transport/SlimJabber/PeerConnection.pm [new file with mode: 0644]
src/perlmods/OpenSRF/UnixServer.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Utils.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Utils/Cache.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Utils/Config.pm [new file with mode: 0755]
src/perlmods/OpenSRF/Utils/LogServer.pm [new file with mode: 0644]
src/perlmods/OpenSRF/Utils/Logger.pm [new file with mode: 0644]
src/router/Makefile [new file with mode: 0644]
src/router/router.c [new file with mode: 0644]
src/router/router.h [new file with mode: 0644]

diff --git a/bin/jabber_users_create b/bin/jabber_users_create
new file mode 100755 (executable)
index 0000000..16051a2
--- /dev/null
@@ -0,0 +1,50 @@
+#!/usr/bin/perl -w
+
+# Pulls the jabber users from the oils/jabber config files
+# and populates the mysql db for the jabber server with the users
+
+use DBI;
+use strict;
+use OpenILS::Utils::Config qw( /pines/conf/oils.conf );
+my $config = OpenILS::Utils::Config->current;
+
+if( @ARGV < 2 ) {
+       print "usage: perl jcreate.pl dbhost dbuser dbpass\n";
+       exit;
+}
+
+
+my $host = $ARGV[0];
+my $user       = $ARGV[1];
+my $pass = $ARGV[2];
+
+my $connection = DBI->connect( "DBI:mysql:jabberd2:$host", $user, $pass )
+       or die "Cannot connect to db: $! \n";
+
+my $jpass = $config->transport->auth->password;
+my $realm = $config->transport->server->primary;
+
+# Delete all users
+my $clean = "delete from authreg;";
+my $sth = $connection->prepare( $clean );
+$sth->execute();
+
+my @users = keys %{$config->transport->users};
+
+# Grab each user from the config and push them into mysql
+for my $user (@users) {
+       if( ! $user or $user eq "__id" or $user eq "__sub") { next; }
+       print "Inserting $user:  ";
+
+       my $sql = "insert into authreg (username, realm, password) values " .
+               "('$user', '$realm', '$jpass');";
+
+       print "[$sql]\n"; 
+
+       $sth = $connection->prepare( $sql );
+       $sth->execute();
+
+}
+
+$sth->finish();
+
diff --git a/bin/opensrf_ctl b/bin/opensrf_ctl
new file mode 100755 (executable)
index 0000000..c8469e0
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+# 
+#  Simple rc script for controlling the system
+#  Only works on linux because of 'ps' syntax
+#
+
+
+case $1 in 
+       "start")
+               perl -MOpenILS::System -e 'OpenILS::System->bootstrap()' & 
+               sleep 2;
+               $0 status;
+               echo;
+               ;;
+       "stop")
+               PID=$(ps ax | grep "[0-9] System$" | awk '{print $1}');
+               if [ -z $PID ]; then
+                       echo "OpenILS System is not running";
+                       exit;
+               fi
+               echo "Killing System...$PID";
+               kill -s INT $PID;
+               echo "Done";
+               ;;
+       "status")
+               PID=$(ps ax | grep "[0-9] System$" | awk '{print $1}');
+               if [ -z $PID ]; then
+                       echo "OpenILS System is not running";
+                       exit 0;
+               fi
+               echo "OpenILS System is running";
+               exit 1;
+               ;;
+       "restart")
+               $0 stop;
+               $0 start;
+               ;;
+       *)
+               echo "usage: system.sh [start|stop|restart|status]";
+               ;;
+esac
+
+
diff --git a/doc/OpenSRF-Messaging-Protocol.html b/doc/OpenSRF-Messaging-Protocol.html
new file mode 100644 (file)
index 0000000..4cb8f4e
--- /dev/null
@@ -0,0 +1,318 @@
+<html>
+
+       <head>
+
+               <title> OILS Messaging </title>
+
+       </head>
+
+       <body>
+
+
+               <h1> Abstract </h1>
+
+               <p>
+
+               The OILS messaging system works on two different primary layers: the transport layer and the
+               application layer.  The transport layer manages virtual connections between client and server,
+               while the application layer manages user/application level messages.  
+
+               All messages must declare which protocol version they are requesting.  The current protocol level
+               is 1.
+
+               <h1> Transport Layer </h1>
+
+               <p>
+               There are currently three types of messages in the transport layer: <b>CONNECT, STATUS, </b> and
+               <b>DISCONNECT</b>.    
+               
+               <p>
+               <b>STATUS</b> message provide general information to the transport layer are used in different 
+               ways throughout the system.  They are sent primarily by the server in response to client requests.  
+               Each message comes with 
+               a status and statusCode.  The actual status part of the STATUS message is just a helpful message (mostly for debugging).  The 
+               statusCode is an integer representing the exact status this message represents.  The status codes
+               are modeled after HTTP status codes.  Currently used codes consist of the following:
+
+               <b> <pre style="border: solid thin blue; margin: 2% 10% 2% 10%; padding-left: 50px">
+               100     STATUS_CONTINUE
+               200     STATUS_OK       
+               205     STATUS_COMPLETE
+               307     STATUS_REDIRECTED
+               400     STATUS_BADREQUEST
+               403     STATUS_FORBIDDEN
+               404     STATUS_NOTFOUND
+               408     STATUS_TIMEOUT
+               417     STATUS_EXPFAILED
+               </pre> </b>
+
+               <p>
+               This list is likely to change at least a little.
+
+
+               <p>
+               The <b>CONNECT</b> message initiates the virtual connection for a client and expects a <b>STATUS</b>
+               in return.  If the connection is successful, the statusCode for the <b>STATUS</b> message shall be
+               <b>STATUS_OK</b>.  If the authentication fails or if there is not actual authentication information
+               within the message, the statusCode for the returned message shall be <b>STATUS_FORBIDDEN</b>.  
+
+               <p>
+               If at any point the client sends a non-connect message to the server when the client is not connected or the 
+               connection has timed out, the <b>STATUS</b> that is returned shall have statusCode <b>STATUS_EXPFAILED</b>.
+               
+               <p>
+               The <b>DISCONNECT</b> message is sent by the client to the server to end the virtual session.  The server
+               shall not respond to any disconnect messages.
+       
+               
+               <h1> Message Layer </h1>
+
+               <p>
+               There are currently two types of message layer messages: <b>REQUEST</b> and <b>RESULT</b>.  <b>REQUEST</b>
+               messages represent application layer requests made by a client and <b>RESULT</b> messages are the servers 
+               response to such <b>REQUEST</b>'s.
+               
+               <p>
+               By design, all <b>CONNECT</b> and <b>REQUEST</b> messages sent by a client will be acknowledged by one or 
+               more responses from the server.  This is much like the SYN-ACK philosophy of TCP, however different in many 
+               ways.  The only guarantees made by the server are 1. you will know that we received your request and 2. you 
+               will know the final outcome of your request.  It is the responsibility of the actual application to send 
+               the requested application data (e.g. RESULT messages, intermediate STATUS messages).
+               
+               
+               <p>
+               The server responses are matched to client requests by a <b>threadTrace</b>.  A threadTrace is simply a 
+               number and all application layer messages and STATUS messages are required to have one.  (Note that the 
+               threadTrace contained within a STATUS message sent in response to a CONNECT will be ignored).  Currently, 
+               there is no restriction on the number other than it shall be unique within a given virtual connection.  
+               When the server receives a <b>REQUEST</b> message, it extracts the <b>threadTrace</b> from the message 
+               and all responses to that request will contain the same <b>threadTrace</b>.
+               
+               <p>
+               As mentioned above, every <b>CONNECT</b> message will be acknowledged by a single 
+               <b>STATUS</b> message.  <b>REQUEST</b>'s are a little more complex, however.  A <b>REQUEST</b> 
+               will receive one or more <b>RESULT</b>'s if the <b>REQUEST</b> warrants such a response.  A <b>REQUEST</b>
+               may even receive one or more intermediate <b>STATUS</b>'s (e.g. <b>STATUS_CONTINUE</b>).  (Consult the 
+               documentation on the application request the client is requesting for more information on the number and 
+               type of responses to that request).  All <b>REQUEST</b>'s, however, regardless of other response types,
+               shall receieve as the last response a <b>STATUS</b> message with statusCode <b>STATUS_COMPLETE</b>.  This
+               allows the client to wait for REQUEST "completeness" as opposed to waiting on or calculating individual 
+               responses.
+
+
+               <h1> Client Pseudocode </h1>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+send CONNECT
+
+msg = recv()
+
+if ( msg.statusCode == STATUS_OK ) 
+
+       OK. continue
+
+if ( msg.statusCode == STATUS_FORBIDDEN ) 
+
+       handle authentication failure and attempt another connect if requested
+
+while ( more requests ) {
+
+       /* you may send multiple requests before processing any responses.  For the sake
+               of this example, we will only walk through a single client request */
+
+       send REQUEST with threadTrace X 
+
+       while ( response = recv ) { 
+
+               if (  response.threadTrace != X ) 
+
+                       continue/ignore
+
+               if ( response.type == STATUS )
+               
+                       if (  response.statusCode == STATUS_TIMEOUT             or
+                                       response.statusCode == STATUS_REDIRECTED        or
+                                       response.statusCode == STATUS_EXPFAILED)
+
+                               resend the the request with threadTrace X because it was not honored.
+
+                       if ( response.statusCode == STATUS_COMPLETE ) 
+
+                               the request is now complete, nothing more to be done with this request
+                               break out of loop
+       
+               if ( response.typ == RESULT )
+
+                       pass result to the application layer for processing
+
+       } // receiving
+
+} // sending
+
+
+               </pre>
+
+               <br>
+               <h1> Server Pseudocode </h1>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+while( message = recv() ) {
+
+       if( message.type != CONNECT )
+
+               return a STATUS with statusCode STATUS_EXPFAILED
+               start loop over
+
+       if ( message.type == CONNECT and server is unable to authenticate the client )
+
+               return a STATUS with statusCode STATUS_FORBIDDEN
+               start loop over
+
+       if ( message.type == CONNECT and server is able to authenticate user )
+
+               return STATUS with statusCode STATUS_OK and continue
+
+       while ( msg = recv() and virtual session is active ) {
+
+
+               if ( msg.type == REQUEST )
+
+                       Record the threadTrace.  Pass the REQUEST to the application layer for processing.
+                       When the application layer has completed processing, respond to the client
+                       with a STATUS message with statusCode STATUS_COMPLETE and threadTrace matching
+                       the threadTrace of the REQUEST.  Once the final STATUS_COMPLETE message is sent,
+                       the session is over.  Return to outer server loop. 
+
+                       /* Note: during REQUEST processing by the application layer, the application may 
+                               opt to send RESULT and/or STATUS messages to the client.  The server side
+                               transport mechanism is not concerned with these messages.  The server only 
+                               needs to be notified when the REQUEST has been sucessfully completed. */
+
+               if( message.type == DISCONNECT )
+
+                       Virtual session has ended. Return to outer loop.
+
+
+       } // Sessin loop
+
+} // Main server loop
+
+
+
+               </pre>
+
+
+               <br>
+               <h1> XML Examples</h1>
+               <br>
+
+
+               <h2> Protocol Element </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObjectAttr value="1" name="protocol"/>
+
+               </pre>
+
+               <h2> threadTrace Element </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObjectAttr value="1" name="threadTrace"/>
+
+               </pre>
+
+               <h2> Type element </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:userAuth hashseed="237" secret="89dd8c65300d4af126cf467779ff1820" username="bill"/>
+
+               </pre>
+
+               <h2> CONNECT Message </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObjectAttr value="CONNECT" name="type"/>
+       &lt;oils:userAuth hashseed="237" secret="89dd8c65300d4af126cf467779ff1820" username="bill"/>
+       &lt;oils:domainObjectAttr value="1" name="threadTrace"/>
+       &lt;oils:domainObjectAttr value="1" name="protocol"/>
+&lt;/oils:domainObject>
+
+               </pre>
+
+
+               <h2> DISCONNECT Message </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObject name="oilsMessage">
+       &lt;oils:domainObjectAttr value="DISCONNECT" name="type"/>
+       &lt;oils:domainObjectAttr value="0" name="threadTrace"/>
+       &lt;oils:domainObjectAttr value="1" name="protocol"/>
+&lt;/oils:domainObject>
+
+               </pre>
+
+               <h2> STATUS Message </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObject name="oilsMessage">
+       &lt;oils:domainObjectAttr value="STATUS" name="type"/>
+       &lt;oils:domainObjectAttr value="0" name="threadTrace"/>
+       &lt;oils:domainObjectAttr value="1" name="protocol"/>
+       &lt;oils:domainObject name="oilsConnectStatus">
+               &lt;oils:domainObjectAttr value="Connection Successful" name="status"/>
+               &lt;oils:domainObjectAttr value="200" name="statusCode"/>
+       &lt;/oils:domainObject>
+&lt;/oils:domainObject>
+
+               </pre>
+
+               <h2> REQUEST Message </h2>
+
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObject name="oilsMessage">
+       &lt;oils:domainObjectAttr value="REQUEST" name="type"/>
+       &lt;oils:domainObjectAttr value="4" name="threadTrace"/>
+       &lt;oils:domainObjectAttr value="1" name="protocol"/>
+       &lt;oils:domainObject name="oilsMethod">
+               &lt;oils:domainObjectAttr value="mult" name="method"/>
+               &lt;oils:params>
+                       &lt;oils:param>1</oils:param>
+                       &lt;oils:param>2</oils:param>
+               &lt;/oils:params>
+       &lt;/oils:domainObject>
+&lt;/oils:domainObject>
+
+               </pre>
+
+               <h2> RESULT Message </h2>
+               
+               <pre style="border: solid thin blue; margin: 0% 10% 0% 10%; padding-left: 50px">
+
+&lt;oils:domainObject name="oilsMessage">
+       &lt;oils:domainObjectAttr value="RESULT" name="type"/>
+       &lt;oils:domainObjectAttr value="4" name="threadTrace"/>
+       &lt;oils:domainObjectAttr value="1" name="protocol"/>
+       &lt;oils:domainObject name="oilsResult">
+               &lt;oils:domainObjectAttr value="OK" name="status"/>
+               &lt;oils:domainObjectAttr value="200" name="statusCode"/>
+               &lt;oils:domainObject name="oilsScalar">2&lt;/oils:domainObject>
+       &lt;/oils:domainObject>
+&lt;/oils:domainObject>
+
+               </pre>
+               
+
+       </body>
+
+</html>
+
+
diff --git a/examples/math_bench.pl b/examples/math_bench.pl
new file mode 100755 (executable)
index 0000000..30f81af
--- /dev/null
@@ -0,0 +1,110 @@
+#!/usr/bin/perl -w
+use strict;use warnings;
+use OpenILS::System;
+use OpenILS::DOM::Element::userAuth;
+use OpenILS::Utils::Config;
+use OpenILS::DomainObject::oilsMethod;
+use OpenILS::DomainObject::oilsPrimitive;
+use Time::HiRes qw/time/;
+use OpenILS::EX qw/:try/;
+
+$| = 1;
+
+# ----------------------------------------------------------------------------------------
+# This is a quick and dirty script to perform benchmarking against the math server.
+# Note: 1 request performs a batch of 4 queries, one for each supported method: add, sub,
+# mult, div.
+# Usage: $ perl math_bench.pl <num_requests>
+# ----------------------------------------------------------------------------------------
+
+
+my $count = $ARGV[0];
+
+unless( $count ) {
+       print "usage: ./math_bench.pl <num_requests>\n";
+       exit;
+}
+
+warn "PID: $$\n";
+
+my $config = OpenILS::Utils::Config->current;
+OpenILS::System->bootstrap_client();
+
+my $session = OpenILS::AppSession->create( 
+               "math", username => 'math_bench', secret => '12345' );
+
+try {
+       if( ! ($session->connect()) ) { die "Connect timed out\n"; }
+
+} catch OpenILS::EX with {
+       my $e = shift;
+       warn "Connection Failed *\n";
+       die $e;
+}
+
+my @times;
+my %vals = ( add => 3, sub => -1, mult => 2, div => 0.5 );
+
+for my $x (1..100) {
+       if( $x % 10 ) { print ".";}
+       else{ print $x/10; };
+}
+print "\n";
+
+my $c = 0;
+
+for my $scale ( 1..$count ) {
+       for my $mname ( keys %vals ) {
+
+               my $method = OpenILS::DomainObject::oilsMethod->new( method => $mname );
+               $method->params( 1,2 );
+
+               my $req;
+               my $resp;
+               my $starttime;
+               try {
+
+                       $starttime = time();
+                       $req = $session->request( $method );
+                       $resp = $req->recv( timeout => 10 );
+                       push @times, time() - $starttime;
+
+               } catch OpenILS::EX with {
+                       my $e = shift;
+                       die "ERROR\n $e";
+
+               } catch Error with {
+                       my $e = shift;
+                       die "Caught unknown error: $e";
+               };
+
+
+               if( ! $req->complete ) { warn "\nIncomplete\n"; }
+
+
+               if ( $resp ) {
+
+                       my $ret = $resp->content();
+                       if( "$ret" eq $vals{$mname} ) { print "+"; }
+
+                       else { print "*BAD*\n" . $resp->toString(1) . "\n"; }
+
+               } else { print "*NADA*";        }
+
+               $req->finish();
+               $c++;
+
+       }
+       print "\n[$c] \n" unless $scale % 25;
+}
+
+$session->kill_me();
+
+my $total = 0;
+
+$total += $_ for (@times);
+
+$total /= scalar(@times);
+
+print "\n\n\tAverage Round Trip Time: $total Seconds\n";
+
diff --git a/examples/math_shell.pl b/examples/math_shell.pl
new file mode 100755 (executable)
index 0000000..b61157b
--- /dev/null
@@ -0,0 +1,139 @@
+#!/usr/bin/perl -w
+use strict;use warnings;
+use OpenILS::System;
+use OpenILS::Utils::Config;
+use OpenILS::DomainObject::oilsMethod;
+use OpenILS::DomainObject::oilsPrimitive;
+use OpenILS::EX qw/:try/;
+$| = 1;
+
+# ----------------------------------------------------------------------------------------
+# Simple math shell where you can test the transport system.
+# Enter simple, binary equations ony using +, -, *, and /
+# Example: # 1+1
+# Usage: % perl math_shell.pl
+# ----------------------------------------------------------------------------------------
+
+# load the config
+my $config = OpenILS::Utils::Config->current;
+
+# connect to the transport (jabber) server
+OpenILS::System->bootstrap_client();
+
+# build the AppSession object.
+my $session = OpenILS::AppSession->create( 
+       "math", username => 'math_bench', secret => '12345' );
+
+# launch the shell
+print "type 'exit' or 'quit' to leave the shell\n";
+print "# ";
+while( my $request = <> ) {
+
+       chomp $request ;
+
+       # exit loop if user enters 'exit' or 'quit'
+       if( $request =~ /exit/i or $request =~ /quit/i ) { last; }
+
+       # figure out what the user entered
+       my( $a, $mname, $b ) = parse_request( $request );
+
+       if( $a =~ /error/ ) {
+               print "Parse Error. Try again. \nExample # 1+1\n";
+               next;
+       }
+
+
+       try {
+
+               # Connect to the MATH server
+               if( ! ($session->connect()) ) { die "Connect timed out\n"; }
+
+       } catch OpenILS::EX with {
+               my $e = shift;
+               die "* * Connection Failed with:\n$e";
+       };
+
+       my $method = OpenILS::DomainObject::oilsMethod->new( method => $mname );
+       $method->params( $a, $b );
+
+       my $req;
+       my $resp;
+
+       try {
+               $req = $session->request( $method );
+
+               # we know that this request only has a single reply
+               # if your expecting a 'stream' of results, you can
+               # do: while( $resp = $req->recv( timeout => 10 ) ) {}
+               $resp = $req->recv( timeout => 10 );
+
+       } catch OpenILS::EX with {
+
+               # Any transport layer or server problems will launch an exception
+               my $e = shift;
+               die "ERROR Receiving\n $e";
+
+       } catch Error with {
+
+               # something just died somethere
+               my $e = shift;
+               die "Caught unknown error: $e";
+       };
+
+       if ( $resp ) {
+               # ----------------------------------------------------------------------------------------
+               # $resp is an OpenILS::DomainObject::oilsResponse object. $resp->content() returns whatever 
+               # data the object has.  If the server returns an exception that we're meant to see, then
+               # the data will be an exception object.  In this case, barring any exception, we know that 
+               # the data is an OpenILS::DomainObject::oilsScalar object which has a value() method 
+               # that returns a perl scalar.  For us, that scalar is just a number.
+               # ----------------------------------------------------------------------------------------
+
+               if( UNIVERSAL::isa( $resp, "OpenILS::EX" ) ) {
+                               throw $resp;
+               }
+
+               my $ret = $resp->content();
+               print $ret->value();
+       }
+
+       $req->finish();
+
+       print "\n# ";
+
+}
+
+# disconnect from the MATH server
+$session->kill_me();
+exit;
+
+# ------------------------------------------------------------------------------------
+# parse the user input string
+# returns a list of the form (first param, operation, second param)
+# These operations are what the MATH server recognizes as method names
+# ------------------------------------------------------------------------------------
+sub parse_request {
+       my $string = shift;
+       my $op;
+       my @ops;
+       
+       while( 1 ) {
+
+               @ops = split( /\+/, $string );
+               if( @ops > 1 ) { $op = "add"; last; }
+
+               @ops = split( /\-/, $string );
+               if( @ops > 1 ) { $op = "sub"; last; }
+
+               @ops = split( /\*/, $string );
+               if( @ops > 1 ) { $op = "mult", last; }
+
+               @ops = split( /\//, $string );
+               if( @ops > 1 ) { $op = "div"; last; }
+
+               return ("error");
+       }
+
+       return ($ops[0], $op, $ops[1]);
+}
+
diff --git a/examples/math_simple.pl b/examples/math_simple.pl
new file mode 100755 (executable)
index 0000000..73b015e
--- /dev/null
@@ -0,0 +1,86 @@
+#!/usr/bin/perl -w
+use strict;use warnings;
+use OpenILS::System;
+use OpenILS::Utils::Config;
+use OpenILS::DomainObject::oilsMethod;
+use OpenILS::DomainObject::oilsPrimitive;
+use OpenILS::EX qw/:try/;
+$| = 1;
+
+
+# ----------------------------------------------------------------------------------------
+# This script makes a single query, 1 + 2,  to the the MATH test app and prints the result
+# Usage: % perl math_simple.pl 
+# ----------------------------------------------------------------------------------------
+
+
+# connect to the transport (jabber) server
+OpenILS::System->bootstrap_client();
+
+# build the AppSession object.
+my $session = OpenILS::AppSession->create( 
+       "math", username => 'math_bench', secret => '12345' );
+
+try {
+
+       # Connect to the MATH server
+       if( ! ($session->connect()) ) { die "Connect timed out\n"; }
+
+} catch OpenILS::EX with {
+       my $e = shift;
+       die "* * Connection Failed with:\n$e";
+};
+
+my $method = OpenILS::DomainObject::oilsMethod->new( method => "add" );
+$method->params( 1, 2 );
+
+my $req;
+my $resp;
+
+try {
+       $req = $session->request( $method );
+
+       # we know that this request only has a single reply
+       # if your expecting a 'stream' of results, you can
+       # do: while( $resp = $req->recv( timeout => 10 ) ) {}
+       $resp = $req->recv( timeout => 10 );
+
+} catch OpenILS::EX with {
+
+       # Any transport layer or server problems will launch an exception
+       my $e = shift;
+       die "ERROR Receiving\n $e";
+
+} catch Error with {
+
+       # something just died somethere
+       my $e = shift;
+       die "Caught unknown error: $e";
+};
+
+if ( $resp ) {
+       # ----------------------------------------------------------------------------------------
+       # $resp is an OpenILS::DomainObject::oilsResponse object. $resp->content() returns whatever 
+       # data the object has.  If the server returns an exception that we're meant to see, then
+       # the data will be an exception object.  In this case, barring any exception, we know that 
+       # the data is an OpenILS::DomainObject::oilsScalar object which has a value() method 
+       # that returns a perl scalar.  For us, that scalar is just a number.
+       # ----------------------------------------------------------------------------------------
+
+       if( UNIVERSAL::isa( $resp, "OpenILS::EX" ) ) {
+                       throw $resp;
+       }
+
+       my $ret = $resp->content();
+       print "Should print 3 => " . $ret->value() . "\n";
+
+} else {
+       die "No Response from Server!\n";
+}
+
+$req->finish();
+
+# disconnect from the MATH server
+$session->kill_me();
+exit;
+
diff --git a/include/opensrf/generic_utils.h b/include/opensrf/generic_utils.h
new file mode 100644 (file)
index 0000000..aa74221
--- /dev/null
@@ -0,0 +1,80 @@
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+
+/* libxml stuff for the config reader */
+#include <libxml/xmlmemory.h>
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+#include <libxml/tree.h>
+
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+
+#ifndef GENERIC_UTILS_H
+#define GENERIC_UTILS_H
+
+
+/** Malloc's, checks for NULL, clears all memory bits and 
+  * returns the pointer
+  * 
+  * @param size How many bytes of memory to allocate
+  */
+inline void* safe_malloc( int size );
+
+/* 10M limit on buffers for overflow protection */
+#define BUFFER_MAX_SIZE 10485760 
+
+// ---------------------------------------------------------------------------------
+// Generic growing buffer. Add data all you want
+// ---------------------------------------------------------------------------------
+struct growing_buffer_struct {
+       char *buf;
+       int n_used;
+       int size;
+};
+typedef struct growing_buffer_struct growing_buffer;
+
+growing_buffer* buffer_init( int initial_num_bytes);
+int buffer_addchar(growing_buffer* gb, char c);
+int buffer_add(growing_buffer* gb, char* c);
+int buffer_reset( growing_buffer* gb);
+char* buffer_data( growing_buffer* gb);
+int buffer_free( growing_buffer* gb );
+
+
+void log_free(); 
+
+// Utility method
+void get_timestamp( char buf_25chars[]);
+
+// ---------------------------------------------------------------------------------
+// Error handling interface.
+// ---------------------------------------------------------------------------------
+
+void fatal_handler( char* message, ...);
+void warning_handler( char* message, ... );
+void info_handler( char* message, ... );
+
+// ---------------------------------------------------------------------------------
+// Config file module
+// ---------------------------------------------------------------------------------
+struct config_reader_struct {
+       xmlDocPtr config_doc;
+       xmlXPathContextPtr xpathCx;
+};
+typedef struct config_reader_struct config_reader;
+config_reader* conf_reader;
+
+void config_reader_init( char* config_file );
+
+void config_reader_free();
+
+// allocastes a char*. FREE me.
+char* config_value( const char* xpath_query, ... );
+
+#endif
diff --git a/include/opensrf/transport_client.h b/include/opensrf/transport_client.h
new file mode 100644 (file)
index 0000000..3d02add
--- /dev/null
@@ -0,0 +1,87 @@
+#include "transport_session.h"
+#include <time.h>
+
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+#ifndef TRANSPORT_CLIENT_H
+#define TRANSPORT_CLIENT_H
+
+#define MESSAGE_LIST_HEAD 1
+#define MESSAGE_LIST_ITEM 2
+
+
+// ---------------------------------------------------------------------------
+// Represents a node in a linked list.  The node holds a pointer to the next
+// node (which is null unless set), a pointer to a transport_message, and
+// and a type variable (which is not really curently necessary).
+// ---------------------------------------------------------------------------
+struct message_list_struct {
+       struct message_list_struct* next;
+       transport_message* message;
+       int type;
+};
+
+typedef struct message_list_struct transport_message_list;
+typedef struct message_list_struct transport_message_node;
+
+// ---------------------------------------------------------------------------
+// Our client struct.  We manage a list of messages and a controlling session
+// ---------------------------------------------------------------------------
+struct transport_client_struct {
+       transport_message_list* m_list;
+       transport_session* session;
+};
+typedef struct transport_client_struct transport_client;
+
+// ---------------------------------------------------------------------------
+// Allocates and initializes and transport_client.  This does no connecting
+// The user must call client_free(client) when finished with the allocated
+// object.
+// ---------------------------------------------------------------------------
+transport_client* client_init( char* server, int port );
+
+// ---------------------------------------------------------------------------
+// Connects to the Jabber server with the provided information. Returns 1 on
+// success, 0 otherwise.
+// ---------------------------------------------------------------------------
+int client_connect( transport_client* client, 
+               char* username, char* password, char* resource, int connect_timeout );
+
+int client_disconnect( transport_client* client );
+
+// ---------------------------------------------------------------------------
+// De-allocates memory associated with a transport_client object.  Users
+// must use this method when finished with a client object.
+// ---------------------------------------------------------------------------
+int client_free( transport_client* client );
+
+// ---------------------------------------------------------------------------
+//  Sends the given message.  The message must at least have the recipient
+// field set.
+// ---------------------------------------------------------------------------
+int client_send_message( transport_client* client, transport_message* msg );
+
+// ---------------------------------------------------------------------------
+// Returns 1 if this client is currently connected to the server, 0 otherwise
+// ---------------------------------------------------------------------------
+int client_connected( transport_client* client );
+
+// ---------------------------------------------------------------------------
+// This is the message handler required by transport_session.  This handler
+// takes all incoming messages and puts them into the back of a linked list
+// of messages.  
+// ---------------------------------------------------------------------------
+void client_message_handler( void* client, transport_message* msg );
+
+// ---------------------------------------------------------------------------
+// If there are any message in the message list, the 'oldest' message is
+// returned.  If not, this function will wait at most 'timeout' seconds 
+// for a message to arrive.  Specifying -1 means that this function will not
+// return unless a message arrives.
+// ---------------------------------------------------------------------------
+transport_message* client_recv( transport_client* client, int timeout );
+
+
+#endif
diff --git a/include/opensrf/transport_message.h b/include/opensrf/transport_message.h
new file mode 100644 (file)
index 0000000..21e3120
--- /dev/null
@@ -0,0 +1,94 @@
+#include "libxml.h"
+
+#include "generic_utils.h"
+
+#include <string.h>
+#include <libxml/globals.h>
+#include <libxml/xmlerror.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/debugXML.h>
+#include <libxml/xmlmemory.h>
+
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+#ifndef TRANSPORT_MESSAGE_H
+#define TRANSPORT_MESSAGE_H
+
+
+
+// ---------------------------------------------------------------------------------
+// Jabber message object.
+// ---------------------------------------------------------------------------------
+struct transport_message_struct {
+       char* body;
+       char* subject;
+       char* thread;
+       char* recipient;
+       char* sender;
+       char* router_from;
+       char* router_to;
+       char* router_class;
+       char* router_command;
+       int is_error;
+       char* error_type;
+       int error_code;
+       int broadcast;
+       char* msg_xml; /* the entire message as XML complete with entity encoding */
+};
+typedef struct transport_message_struct transport_message;
+
+// ---------------------------------------------------------------------------------
+// Allocates and returns a transport_message.  All chars are safely re-allocated
+// within this method.
+// Returns NULL on error
+// ---------------------------------------------------------------------------------
+transport_message* message_init( char* body, char* subject, 
+               char* thread, char* recipient, char* sender );
+
+
+void message_set_router_info( transport_message* msg, char* router_from,
+               char* router_to, char* router_class, char* router_command, int broadcast_enabled );
+
+// ---------------------------------------------------------------------------------
+// Formats the Jabber message as XML for encoding. 
+// Returns NULL on error
+// ---------------------------------------------------------------------------------
+char* message_to_xml( const transport_message* msg );
+
+
+// ---------------------------------------------------------------------------------
+// Call this to create the encoded XML for sending on the wire.
+// This is a seperate function so that encoding will not necessarily have
+// to happen on all messages (i.e. typically only occurs outbound messages).
+// ---------------------------------------------------------------------------------
+int message_prepare_xml( transport_message* msg );
+
+// ---------------------------------------------------------------------------------
+// Deallocates the memory used by the transport_message
+// Returns 0 on error
+// ---------------------------------------------------------------------------------
+int message_free( transport_message* msg );
+
+// ---------------------------------------------------------------------------------
+// Prepares the shared XML document
+// ---------------------------------------------------------------------------------
+//int message_init_xml();
+
+// ---------------------------------------------------------------------------------
+// Determines the username of a Jabber ID.  This expects a pre-allocated char 
+// array for the return value.
+// ---------------------------------------------------------------------------------
+void jid_get_username( const char* jid, char buf[] );
+
+// ---------------------------------------------------------------------------------
+// Determines the resource of a Jabber ID.  This expects a pre-allocated char 
+// array for the return value.
+// ---------------------------------------------------------------------------------
+void jid_get_resource( const char* jid, char buf[] );
+
+void set_msg_error( transport_message*, char* error_type, int error_code);
+
+#endif
diff --git a/include/opensrf/transport_session.h b/include/opensrf/transport_session.h
new file mode 100644 (file)
index 0000000..b78f20d
--- /dev/null
@@ -0,0 +1,217 @@
+// ---------------------------------------------------------------------------------
+// Manages the Jabber session.  Data is taken from the TCP object and pushed into
+// a SAX push parser as it arrives.  When key Jabber documetn elements are met, 
+// logic ensues.
+// ---------------------------------------------------------------------------------
+#include "libxml.h"
+#include "transport_socket.h"
+#include "transport_message.h"
+#include "generic_utils.h"
+
+#include <string.h>
+#include <libxml/globals.h>
+#include <libxml/xmlerror.h>
+#include <libxml/parser.h>
+#include <libxml/parserInternals.h> /* only for xmlNewInputFromFile() */
+#include <libxml/tree.h>
+#include <libxml/debugXML.h>
+#include <libxml/xmlmemory.h>
+
+#ifndef TRANSPORT_SESSION_H
+#define TRANSPORT_SESSION_H
+
+#define CONNECTING_1 1 /* just starting the connection to Jabber */
+#define CONNECTING_2 2 /* First <stream> packet sent and <stream> packet received from server */
+
+/* Note. these are growing buffers, so all that's necessary is a sane starting point */
+#define JABBER_BODY_BUFSIZE            4096
+#define JABBER_SUBJECT_BUFSIZE 64      
+#define JABBER_THREAD_BUFSIZE          64      
+#define JABBER_JID_BUFSIZE                     64      
+#define JABBER_STATUS_BUFSIZE          16 
+
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+// ---------------------------------------------------------------------------------
+// Takes data from the socket handler and pushes it directly into the push parser
+// ---------------------------------------------------------------------------------
+void grab_incoming( void * session, char* data );
+
+// ---------------------------------------------------------------------------------
+// Callback for handling the startElement event.  Much of the jabber logic occurs
+// in this and the characterHandler callbacks.
+// Here we check for the various top level jabber elements: body, iq, etc.
+// ---------------------------------------------------------------------------------
+void startElementHandler( 
+               void *session, const xmlChar *name, const xmlChar **atts);
+
+// ---------------------------------------------------------------------------------
+// Callback for handling the endElement event.  Updates the Jabber state machine
+// to let us know the element is over.
+// ---------------------------------------------------------------------------------
+void endElementHandler( void *session, const xmlChar *name);
+
+// ---------------------------------------------------------------------------------
+// This is where we extract XML text content.  In particular, this is useful for
+// extracting Jabber message bodies.
+// ---------------------------------------------------------------------------------
+void characterHandler(
+               void *session, const xmlChar *ch, int len);
+
+void  parseWarningHandler( void *session, const char* msg, ... );
+void  parseErrorHandler( void *session, const char* msg, ... );
+
+// ---------------------------------------------------------------------------------
+// Tells the SAX parser which functions will be used as event callbacks
+// ---------------------------------------------------------------------------------
+static xmlSAXHandler SAXHandlerStruct = {
+   NULL,                                                       /* internalSubset */
+   NULL,                                                       /* isStandalone */
+   NULL,                                                       /* hasInternalSubset */
+   NULL,                                                       /* hasExternalSubset */
+   NULL,                                                       /* resolveEntity */
+   NULL,                                                       /* getEntity */
+   NULL,                                                       /* entityDecl */
+   NULL,                                                       /* notationDecl */
+   NULL,                                                       /* attributeDecl */
+   NULL,                                                       /* elementDecl */
+   NULL,                                                       /* unparsedEntityDecl */
+   NULL,                                                       /* setDocumentLocator */
+   NULL,                                                       /* startDocument */
+   NULL,                                                       /* endDocument */
+       startElementHandler,            /* startElement */
+       endElementHandler,              /* endElement */
+   NULL,                                                       /* reference */
+       characterHandler,                       /* characters */
+   NULL,                                                       /* ignorableWhitespace */
+   NULL,                                                       /* processingInstruction */
+   NULL,                                                       /* comment */
+   parseWarningHandler,                /* xmlParserWarning */
+   parseErrorHandler,          /* xmlParserError */
+   NULL,                                                       /* xmlParserFatalError : unused */
+   NULL,                                                       /* getParameterEntity */
+   NULL,                                                       /* cdataBlock; */
+   NULL,                                                       /* externalSubset; */
+   1,
+   NULL,
+   NULL,                                                       /* startElementNs */
+   NULL,                                                       /* endElementNs */
+       NULL                                                    /* xmlStructuredErrorFunc */
+};
+
+// ---------------------------------------------------------------------------------
+// Our SAX handler pointer.
+// ---------------------------------------------------------------------------------
+static const xmlSAXHandlerPtr SAXHandler = &SAXHandlerStruct;
+
+// ---------------------------------------------------------------------------------
+// Jabber state machine.  This is how we know where we are in the Jabber
+// conversation.
+// ---------------------------------------------------------------------------------
+struct jabber_state_machine_struct {
+       int connected;
+       int connecting;
+       int in_message;
+       int in_message_body;
+       int in_thread;
+       int in_subject;
+       int in_error;
+       int in_message_error;
+       int in_iq;
+       int in_presence;
+       int in_status;
+};
+typedef struct jabber_state_machine_struct jabber_machine;
+
+// ---------------------------------------------------------------------------------
+// Transport session.  This maintains all the various parts of a session
+// ---------------------------------------------------------------------------------
+struct transport_session_struct {
+
+       /* our socket connection */
+       transport_socket* sock_obj;
+       /* our Jabber state machine */
+       jabber_machine* state_machine;
+       /* our SAX push parser context */
+       xmlParserCtxtPtr parser_ctxt;
+
+       /* our text buffers for holding text data */
+       growing_buffer* body_buffer;
+       growing_buffer* subject_buffer;
+       growing_buffer* thread_buffer;
+       growing_buffer* from_buffer;
+       growing_buffer* recipient_buffer;
+       growing_buffer* status_buffer;
+       growing_buffer* message_error_type;
+       int message_error_code;
+
+       /* for OILS extenstions */
+       growing_buffer* router_to_buffer;
+       growing_buffer* router_from_buffer;
+       growing_buffer* router_class_buffer;
+       growing_buffer* router_command_buffer;
+       int router_broadcast;
+
+       /* this can be anything.  It will show up in the 
+               callbacks for your convenience. We will *not*
+          deallocate whatever this is when we're done.  That's
+               your job.
+        */
+       void* user_data;
+
+       /* the Jabber message callback */
+       void (*message_callback) ( void* user_data, transport_message* msg );
+       //void (iq_callback) ( void* user_data, transport_iq_message* iq );
+};
+typedef struct transport_session_struct transport_session;
+
+
+// ------------------------------------------------------------------
+// Allocates and initializes the necessary transport session
+// data structures.
+// ------------------------------------------------------------------
+transport_session* init_transport(  char* server, int port, void* user_data );
+
+// ------------------------------------------------------------------
+// Returns the value of the given XML attribute
+// The xmlChar** construct is commonly returned from SAX event
+// handlers.  Pass that in with the name of the attribute you want
+// to retrieve.
+// ------------------------------------------------------------------
+char* get_xml_attr( const xmlChar** atts, char* attr_name );
+
+// ------------------------------------------------------------------
+// Waits  at most 'timeout' seconds  for data to arrive from the 
+// TCP handler. A timeout of -1 means to wait indefinitely.
+// ------------------------------------------------------------------
+int session_wait( transport_session* session, int timeout );
+
+// ---------------------------------------------------------------------------------
+// Sends the given Jabber message
+// ---------------------------------------------------------------------------------
+int session_send_msg( transport_session* session, transport_message* msg );
+
+// ---------------------------------------------------------------------------------
+// Returns 1 if this session is connected to the jabber server. 0 otherwise
+// ---------------------------------------------------------------------------------
+int session_connected( transport_session* );
+
+// ------------------------------------------------------------------
+// Deallocates session memory
+// ------------------------------------------------------------------
+int session_free( transport_session* session );
+
+// ------------------------------------------------------------------
+// Connects to the Jabber server.  Waits at most connect_timeout
+// seconds before failing
+// ------------------------------------------------------------------
+int session_connect( transport_session* session, 
+               const char* username, const char* password, const char* resource, int connect_timeout );
+
+int session_disconnect( transport_session* session );
+
+int reset_session_buffers( transport_session* session );
+
+#endif
diff --git a/include/opensrf/transport_socket.h b/include/opensrf/transport_socket.h
new file mode 100644 (file)
index 0000000..65a83cc
--- /dev/null
@@ -0,0 +1,68 @@
+#include "generic_utils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <errno.h>
+
+//---------------------------------------------------------------
+// WIN32
+//---------------------------------------------------------------
+#ifdef WIN32
+#include <Windows.h>
+#include <Winsock.h>
+#else
+
+//---------------------------------------------------------------
+// Unix headers
+//---------------------------------------------------------------
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#endif
+
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+#ifndef TRANSPORT_SOCKET_H
+#define TRANSPORT_SOCKET_H
+
+/* how many characters we read from the socket at a time */
+#ifdef _ROUTER
+#define BUFSIZE 412
+#else
+#define BUFSIZE 4096
+#endif
+
+/* we maintain the socket information */
+struct transport_socket_struct {
+       int sock_fd;
+       int connected;
+       char* server;
+       int port;
+       void * user_data;
+       /* user_data may be anything.  it's whatever you wish
+               to see showing up in the callback in addition to
+               the acutal character data*/
+       void (*data_received_callback) (void * user_data, char*);
+};
+typedef struct transport_socket_struct transport_socket;
+
+int tcp_connect( transport_socket* obj );
+int tcp_send( transport_socket* obj, const char* data );
+int tcp_disconnect( transport_socket* obj );
+int tcp_wait( transport_socket* obj, int timeout );
+int tcp_connected( transport_socket* obj );
+
+/* utility methods */
+int set_fl( int fd, int flags );
+int clr_fl( int fd, int flags );
+
+
+#endif
diff --git a/src/javascript/JSON.js b/src/javascript/JSON.js
new file mode 100644 (file)
index 0000000..03ec070
--- /dev/null
@@ -0,0 +1,89 @@
+// in case we run on an implimentation that doesn't have "undefined";
+var undefined;
+
+function Cast (obj, class_constructor) {
+       try {
+               if (eval(class_constructor + '.prototype["class_name"]')) {
+                       var template = eval("new " + class_constructor + "()");
+                       // copy the methods over to 'obj'
+                       for (var m in obj) {
+                               if (typeof(obj[m]) != 'undefined') {
+                                       template[m] = obj[m];
+                               }
+                       }
+                       obj = template;
+               }
+       } catch( E ) {
+               obj['class_name'] = function () { return class_constructor };
+               //dump( super_dump(E) + "\n");
+       } finally {
+               return obj;
+       }
+}
+
+function JSON2js (json) {
+       json = json.replace( /\/\*--\s*S\w*?\s*?\s+\w+\s*--\*\//g, 'Cast(');
+       json = json.replace( /\/\*--\s*E\w*?\s*?\s+(\w+)\s*--\*\//g, ', "$1")');
+       var obj;
+       if (json != '') {
+               eval( 'obj = ' + json );
+       }
+       obj.toString = function () { return js2JSON(this) };
+       return obj;
+}
+
+function js2JSON(arg) {
+var i, o, u, v;
+
+       switch (typeof arg) {
+               case 'object':
+                       if (arg) {
+                               if (arg.constructor == Array) {
+                                       o = '';
+                                       for (i = 0; i < arg.length; ++i) {
+                                               v = js2JSON(arg[i]);
+                                               if (o) {
+                                                       o += ',';
+                                               }
+                                               if (v !== u) {
+                                                       o += v;
+                                               } else {
+                                                       o += 'null,';
+                                               }
+                                       }
+                                       return '[' + o + ']';
+                               } else if (typeof arg.toString != 'undefined') {
+                                       o = '';
+                                       for (i in arg) {
+                                               v = js2JSON(arg[i]);
+                                               if (v !== u) {
+                                                       if (o) {
+                                                               o += ',';
+                                                       }
+                                                       o += js2JSON(i) + ':' + v;
+                                               }
+                                       }
+                                       var obj_start = '{';
+                                       var obj_end = '}';
+                                       try {
+                                               if ( arg.class_name() ) {
+                                                       obj_start = '/*--S ' + arg.class_name() + '--*/{';
+                                                       obj_end   = '}/*--E ' + arg.class_name() + '--*/';
+                                               }
+                                       } catch( E ) {}
+                                       o = obj_start + o + obj_end;
+                                       return o;
+                               } else {
+                                       return;
+                               }
+                       }
+                       return 'null';
+               case 'unknown':
+               case 'undefined':
+               case 'function':
+                       return u;
+               case 'string':
+               default:
+                       return '"' + String(arg).replace(/(["\\])/g, '\\$1') + '"';
+       }
+}
diff --git a/src/javascript/md5.js b/src/javascript/md5.js
new file mode 100644 (file)
index 0000000..46d2aab
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for more info.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
+var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
+var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
+function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
+function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
+function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
+function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
+function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function md5_vm_test()
+{
+  return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
+}
+
+/*
+ * Calculate the MD5 of an array of little-endian words, and a bit length
+ */
+function core_md5(x, len)
+{
+  /* append padding */
+  x[len >> 5] |= 0x80 << ((len) % 32);
+  x[(((len + 64) >>> 9) << 4) + 14] = len;
+
+  var a =  1732584193;
+  var b = -271733879;
+  var c = -1732584194;
+  var d =  271733878;
+
+  for(var i = 0; i < x.length; i += 16)
+  {
+    var olda = a;
+    var oldb = b;
+    var oldc = c;
+    var oldd = d;
+
+    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
+    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
+    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
+    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
+    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
+    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
+    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
+    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
+    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
+    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
+    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
+    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
+    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
+    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
+    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
+    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);
+
+    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
+    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
+    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
+    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
+    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
+    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
+    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
+    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
+    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
+    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
+    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
+    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
+    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
+    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
+    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
+    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
+
+    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
+    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
+    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
+    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
+    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
+    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
+    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
+    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
+    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
+    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
+    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
+    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
+    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
+    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
+    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
+    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
+
+    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
+    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
+    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
+    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
+    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
+    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
+    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
+    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
+    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
+    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
+    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
+    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
+    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
+    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
+    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
+    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
+
+    a = safe_add(a, olda);
+    b = safe_add(b, oldb);
+    c = safe_add(c, oldc);
+    d = safe_add(d, oldd);
+  }
+  return Array(a, b, c, d);
+
+}
+
+/*
+ * These functions implement the four basic operations the algorithm uses.
+ */
+function md5_cmn(q, a, b, x, s, t)
+{
+  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
+}
+function md5_ff(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
+}
+function md5_gg(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
+}
+function md5_hh(a, b, c, d, x, s, t)
+{
+  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
+}
+function md5_ii(a, b, c, d, x, s, t)
+{
+  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
+}
+
+/*
+ * Calculate the HMAC-MD5, of a key and some data
+ */
+function core_hmac_md5(key, data)
+{
+  var bkey = str2binl(key);
+  if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);
+
+  var ipad = Array(16), opad = Array(16);
+  for(var i = 0; i < 16; i++)
+  {
+    ipad[i] = bkey[i] ^ 0x36363636;
+    opad[i] = bkey[i] ^ 0x5C5C5C5C;
+  }
+
+  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
+  return core_md5(opad.concat(hash), 512 + 128);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+  return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+  return (num << cnt) | (num >>> (32 - cnt));
+}
+
+/*
+ * Convert a string to an array of little-endian words
+ * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
+ */
+function str2binl(str)
+{
+  var bin = Array();
+  var mask = (1 << chrsz) - 1;
+  for(var i = 0; i < str.length * chrsz; i += chrsz)
+    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
+  return bin;
+}
+
+/*
+ * Convert an array of little-endian words to a string
+ */
+function binl2str(bin)
+{
+  var str = "";
+  var mask = (1 << chrsz) - 1;
+  for(var i = 0; i < bin.length * 32; i += chrsz)
+    str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
+  return str;
+}
+
+/*
+ * Convert an array of little-endian words to a hex string.
+ */
+function binl2hex(binarray)
+{
+  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+  var str = "";
+  for(var i = 0; i < binarray.length * 4; i++)
+  {
+    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
+           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
+  }
+  return str;
+}
+
+/*
+ * Convert an array of little-endian words to a base-64 string
+ */
+function binl2b64(binarray)
+{
+  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  var str = "";
+  for(var i = 0; i < binarray.length * 4; i += 3)
+  {
+    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
+                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
+                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
+    for(var j = 0; j < 4; j++)
+    {
+      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
+      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
+    }
+  }
+  return str;
+}
diff --git a/src/javascript/opensrf_app_session.js b/src/javascript/opensrf_app_session.js
new file mode 100644 (file)
index 0000000..892a958
--- /dev/null
@@ -0,0 +1,509 @@
+/** @file oils_app_session.js
+  * @brief AppRequest and AppSession.
+  * The AppSession class does most of the communication work and the AppRequest 
+  * contains the top level client API.
+  */
+
+/** The default wait time when a client calls recv. It
+  * may be overrided by passing in a value to the recv method
+  */
+AppRequest.DEF_RECV_TIMEOUT = 10000;
+
+/** Provide a pre-built AppSession object and the payload of the REQUEST
+  * message you wish to send
+  */
+function AppRequest( session, payload ) {
+
+       /** Our controling session */
+       this.session = session;
+
+       /** This requests message thread trace */
+       this.thread_trace = null;
+
+       /** This request REQUEST payload */
+       this.payload = payload;
+
+       /** True if this request has completed is request cycle */
+       this.is_complete = false;
+
+       /** Stores responses received from requests */
+       this.recv_queue = new Array();
+}
+
+/** returns true if this AppRequest has completed its request cycle.  */
+AppRequest.prototype.complete = function() {
+       if( this.is_complete ) { return true; }
+       this.session.queue_wait(0);
+       return this.is_complete;
+}
+
+/** When called on an AppRequest object, its status will be
+  * set to complete regardless of its previous status
+  */
+AppRequest.prototype.set_complete = function() {
+       this.is_complete = true;
+}
+
+/** Returns the current thread trace */
+AppRequest.prototype.get_thread_trace = function() {
+       return this.thread_trace;
+}
+
+/** Pushes some payload onto the recv queue */
+AppRequest.prototype.push_queue = function( payload ) {
+       this.recv_queue.push( payload );
+}
+
+/** Returns the current payload of this request */
+AppRequest.prototype.get_payload = function() {
+       return this.payload;
+}
+
+/** Removes this request from the our AppSession's request bucket 
+  * Call this method when you are finished with a particular request 
+  */
+AppRequest.prototype.finish = function() {
+       this.session.remove_request( this );
+}
+
+
+/** Retrieves the current thread trace from the associated AppSession object,
+  * increments that session's thread trace, sets this AppRequest's thread trace
+  * to the new value.  The request is then sent.
+  */
+AppRequest.prototype.make_request = function() {
+       var tt = this.session.get_thread_trace();
+       this.session.set_thread_trace( ++tt );
+       this.thread_trace = tt;
+       this.session.add_request( this );
+       this.session.send( oilsMessage.REQUEST, tt, this.payload );
+}
+
+/** Checks the receive queue for message payloads.  If any are found, the first 
+  * is returned.  Otherwise, this method will wait at most timeout seconds for
+  * a message to appear in the receive queue.  Once it arrives it is returned.
+  * If no messages arrive in the timeout provided, null is returned.
+
+  * NOTE: timeout is in * milliseconds *
+  */
+
+AppRequest.prototype.recv = function( /*int*/ timeout ) {
+
+
+       if( this.recv_queue.length > 0 ) {
+               return this.recv_queue.shift();
+       }
+
+       //if( this.complete ) { return null; }
+
+       if( timeout == null ) {
+               timeout = AppRequest.DEF_RECV_TIMEOUT;
+       }
+
+       while( timeout > 0 ) {
+
+               var start = new Date().getTime();
+               this.session.queue_wait( timeout );
+
+               if( this.recv_queue.length > 0 ) {
+                       return this.recv_queue.shift();
+               }
+
+               // shortcircuit the call if we're already complete
+               if( this.complete() ) { return null; }
+
+               new Logger().debug( "AppRequest looping in recv " 
+                               + this.get_thread_trace() + " with timeout " + timeout, Logger.DEBUG );
+
+               var end = new Date().getTime();
+               timeout -= ( end - start );
+       }
+
+       return null;
+}
+
+/** Resend this AppRequest's REQUEST message, useful for recovering
+ * from disconnects, etc.
+ */
+AppRequest.prototype.resend = function() {
+
+       new Logger().debug( "Resending msg with thread trace: " 
+                       + this.get_thread_trace(), Logger.DEBUG );
+       this.session.send( oilsMessage.REQUEST, this.get_thread_trace(), this.payload );
+}
+
+
+
+       
+// -----------------------------------------------------------------------------
+// -----------------------------------------------------------------------------
+// AppSession code
+// -----------------------------------------------------------------------------
+
+/** Global cach of AppSession objects */
+AppSession.session_cache = new Array();
+
+// -----------------------------------------------------------------------------
+// Session states
+// -----------------------------------------------------------------------------
+/** True when we're attempting to connect to a remte service */
+AppSession.CONNECTING  = 0; 
+/** True when we have successfully connected to a remote service */
+AppSession.CONNECTED           = 1;
+/** True when we have been disconnected from a remote service */
+AppSession.DISCONNECTED = 2;
+/** The current default method protocol */
+AppSession.PROTOCOL            = 1;
+
+/** Our connection with the outside world */
+AppSession.transport_handle = null;
+
+
+/** Returns the session with the given session id */
+AppSession.find_session = function(session_id) {
+       return AppSession.session_cache[session_id];
+}
+
+/** Adds the given session to the global cache */
+AppSession.push_session = function(session) {
+       AppSession.session_cache[session.get_session_id()] = session;
+}
+
+/** Deletes the session with the given session id from the global cache */
+AppSession.delete_session = function(session_id) {
+       AppSession.session_cache[session_id] = null;
+}
+
+/** Builds a new session.
+  * @param remote_service The remote service we want to make REQUEST's of
+  */
+function AppSession( username, password, remote_service ) {
+
+
+       /** Our logger object */
+       this.logger = new Logger();
+
+       random_num = Math.random() + "";
+       random_num.replace( '.', '' );
+
+       /** Our session id */
+       this.session_id = new Date().getTime() + "" + random_num;
+
+       this.auth = new userAuth( username, password );
+
+       /** Our AppRequest queue */
+       this.request_queue = new Array();
+
+       /** Our connectivity state */
+       this.state = AppSession.DISCONNECTED;
+
+       var config = new Config();
+       
+       /** The remote ID of the service we are communicating with as retrieved 
+         * from the config file
+        */
+       this.orig_remote_id = config.get_value( "remote_service/" + remote_service );
+       if( ! this.orig_remote_id ) { 
+               throw new oils_ex_config( "No remote service id for: " + remote_service );
+       }
+
+       /** The current remote ID of the service we are communicating with */
+       this.remote_id = this.orig_remote_id;
+
+       /** Our current request threadtrace, which is incremented with each 
+         * newly sent AppRequest */
+       this.thread_trace = 0;
+
+       /** Our queue of AppRequest objects */
+       this.req_queue = new Array();
+
+       /** Our queue of AppRequests that are awaiting a resend of their payload */
+       this.resend_queue = new Array();
+
+       // Build the transport_handle if if doesn't already exist
+       if( AppSession.transport_handle == null ) {
+               this.build_transport();
+       }
+
+       AppSession.push_session( this );
+
+}
+
+/** The transport implementation is loaded from the config file and magically
+  * eval'ed into an object.  All connection settings come from the client 
+  * config.
+  * * This should only be called by the AppSession constructor and only when
+  * the transport_handle is null.
+  */
+AppSession.prototype.build_transport = function() {
+
+       var config = new Config();
+       var transport_impl = config.get_value( "transport/transport_impl" );
+       if( ! transport_impl ) {
+               throw new oils_ex_config( "No transport implementation defined in config file" );
+       }
+
+       var username    = config.get_value( "transport/username" );
+       var password    = config.get_value( "transport/password" );
+       var this_host   = config.get_value( "system/hostname" );
+       var resource    = this_host + "_" + new Date().getTime();
+       var server              = config.get_value( "transport/primary" );
+       var port                        = config.get_value( "transport/port" );
+       var tim                 = config.get_value( "transport/connect_timeout" );
+       var timeout             = tim * 1000;
+
+       var eval_string = 
+               "AppSession.transport_handle = new " + transport_impl + "( username, password, resource );";
+
+       eval( eval_string );
+       
+       if( AppSession.transport_handle == null ) {
+               throw new oils_ex_config( "Transport implementation defined in config file is not valid" );
+       }
+
+       if( !AppSession.transport_handle.connect( server, port, timeout ) ) {
+               throw new oils_ex_transport( "Connection attempt to remote service timed out" );
+       }
+
+       if( ! AppSession.transport_handle.connected() ) {
+               throw new oils_ex_transport( "AppSession is unable to connect to the remote service" );
+       }
+}
+
+
+/** Adds the given AppRequest object to this AppSession's request queue */
+AppSession.prototype.add_request = function( req_obj ) {
+       new Logger().debug( "Adding AppRequest: " + req_obj.get_thread_trace(), Logger.DEBUG );
+       this.req_queue[req_obj.get_thread_trace()] = req_obj;
+}
+
+/** Removes the AppRequest object from this AppSession's request queue */
+AppSession.prototype.remove_request = function( req_obj ) {
+       this.req_queue[req_obj.get_thread_trace()] = null;
+}
+
+/** Returns the AppRequest with the given thread_trace */
+AppSession.prototype.get_request = function( thread_trace ) {
+       return this.req_queue[thread_trace];
+}
+
+
+/** Returns this AppSession's session id */
+AppSession.prototype.get_session_id = function() { 
+       return this.session_id; 
+}
+
+/** Resets the remote_id for the transport to the original remote_id retrieved
+  * from the config file
+  */
+AppSession.prototype.reset_remote = function() { 
+       this.remote_id = this.orig_remote_id; 
+}
+
+/** Returns the current message thread trace */
+AppSession.prototype.get_thread_trace = function() {
+       return this.thread_trace;
+}
+
+/** Sets the current thread trace */
+AppSession.prototype.set_thread_trace = function( tt ) {
+       this.thread_trace = tt;
+}
+
+/** Returns the state that this session is in (i.e. CONNECTED) */
+AppSession.prototype.get_state = function() {
+       return this.state;
+}
+
+/** Sets the session state.  The state should be one of the predefined 
+  * session AppSession session states.
+  */
+AppSession.prototype.set_state = function(state) {
+       this.state = state;
+}
+
+/** Returns the current remote_id for this session */
+AppSession.prototype.get_remote_id = function() {
+       return this.remote_id;
+}
+
+/** Sets the current remote_id for this session */
+AppSession.prototype.set_remote_id = function( id ) {
+       this.remote_id = id;
+}
+
+/** Pushes an AppRequest object onto the resend queue */
+AppSession.prototype.push_resend = function( app_request ) {
+       this.resend_queue.push( app_request );
+}
+
+/** Destroys the current session.  This will disconnect from the
+  * remote service, remove all AppRequests from the request
+  * queue, and finally remove this session from the global cache
+  */
+AppSession.prototype.destroy = function() {
+
+       new Logger().debug( "Destroying AppSession: " + this.get_session_id(), Logger.DEBUG );
+
+       // disconnect from the remote service
+       if( this.get_state() != AppSession.DISCONNECTED ) {
+               this.disconnect();
+       }
+       // remove us from the global cache
+       AppSession.delete_session( this.get_session_id() );
+
+       // Remove app request references
+       for( var index in this.req_queue ) {
+               this.req_queue[index] = null;
+       }
+}
+
+/** This forces a resend of all AppRequests that are currently 
+  * in the resend queue
+  */
+AppSession.prototype.flush_resend = function() {
+
+       if( this.resend_queue.length > 0 ) {
+               new Logger().debug( "Resending " 
+                       + this.resend_queue.length + " messages", Logger.INFO );
+       }
+
+       var req = this.resend_queue.shift();
+
+       while( req != null ) {
+               req.resend();
+               req = this.resend_queue.shift();
+       }
+}
+
+/** This method tracks down the AppRequest with the given thread_trace and 
+  * pushes the payload into that AppRequest's recieve queue.
+  */
+AppSession.prototype.push_queue = function( dom_payload, thread_trace ) {
+
+       var req = this.get_request( thread_trace );
+       if( ! req ) {
+               new Logger().debug( "No AppRequest exists for TT: " + thread_trace, Logger.ERROR );
+               return;
+       }
+       req.push_queue( dom_payload );
+}
+
+
+/** Connect to the remote service.  The connect timeout is read from the config.
+  * This method returns null if the connection fails.  It returns a reference
+  * to this AppSession object otherwise.
+  */
+AppSession.prototype.connect = function() {
+
+       if( this.get_state() == AppSession.CONNECTED ) { 
+               return this;
+       }
+
+       var config = new Config();
+       var rem = config.get_value( "transport/connect_timeout" );
+       if( ! rem ) {
+               throw new oils_ex_config( "Unable to retreive timeout value from config" );
+       }
+
+       var remaining = rem * 1000; // milliseconds
+
+       this.reset_remote();
+       this.set_state( AppSession.CONNECTING );
+       this.send( oilsMessage.CONNECT, 0, "" );
+
+       new Logger().debug( "CONNECTING with timeout: " + remaining, Logger.DEBUG );
+
+       while( this.get_state() != AppSession.CONNECTED && remaining > 0 ) {
+
+               var starttime = new Date().getTime();
+               this.queue_wait( remaining );
+               var endtime = new Date().getTime();
+               remaining -= (endtime - starttime);
+       }
+
+       if( ! this.get_state() == AppSession.CONNECTED ) {
+               return null;
+       }
+
+       return this;
+}
+
+/** Disconnects from the remote service */
+AppSession.prototype.disconnect = function() {
+
+       if( this.get_state() == AppSession.DISCONNECTED ) {
+               return;
+       }
+
+       this.send( oilsMessage.DISCONNECT, this.get_thread_trace(), "" );
+       this.set_state( AppSession.DISCONNECTED );
+       this.reset_remote();
+}
+
+
+/** Builds a new message with the given type and thread_trace.  If the message
+  * is a REQUEST, then the payload is added as well.
+  * This method will verify that the session is in the CONNECTED state before
+  * sending any REQUEST's by attempting to do a connect.
+  *
+  * Note: msg_type and thread_trace must be provided.
+  */
+AppSession.prototype.send = function( msg_type, thread_trace, payload ) {
+
+       if( msg_type == null || thread_trace == null ) {
+               throw new oils_ex_args( "Not enough arguments provided to AppSession.send method" );
+       }
+
+       // go ahead and make sure there's nothing new to process
+       this.queue_wait(0);
+
+       var msg;
+       if( msg_type == oilsMessage.CONNECT ) {
+               msg = new oilsMessage( msg_type, AppSession.PROTOCOL, this.auth );
+       } else {
+               msg = new oilsMessage( msg_type, AppSession.PROTOCOL );
+       }
+
+       msg.setThreadTrace( thread_trace );
+
+       if( msg_type == oilsMessage.REQUEST ) {
+               if( ! payload ) {
+                       throw new oils_ex_args( "No payload provided for REQUEST message in AppSession.send" );
+               }
+               msg.add( payload );
+       }
+
+
+       // Make sure we're connected
+       if( (msg_type != oilsMessage.DISCONNECT) && (msg_type != oilsMessage.CONNECT) &&
+                       (this.get_state() != AppSession.CONNECTED) ) {
+               if( ! this.connect() ) {
+                       throw new oils_ex_session( this.get_session_id() + " | Unable to connect to remote service after redirect" );
+               }
+       }
+
+       this.logger.debug( "AppSession sending tt: " 
+                       + thread_trace + " to " + this.get_remote_id() 
+                       + " " +  msg_type , Logger.INFO );
+
+       AppSession.transport_handle.send( this.get_remote_id(), this.get_session_id(), msg.toString(true) );
+
+}
+
+
+/** Waits up to 'timeout' milliseconds for some data to arrive.
+  * Any data that arrives will be process according to its
+  * payload and message type.  This method will return after
+  * any data has arrived.
+  * @param timeout How many milliseconds to wait or data to arrive
+  */
+AppSession.prototype.queue_wait = function( timeout ) {
+       this.flush_resend(); // necessary if running parallel sessions 
+       new Logger().debug( "In queue_wait " + timeout, Logger.DEBUG );
+       var tran_msg = AppSession.transport_handle.process_msg( timeout );
+       this.flush_resend();
+}
+
+
+
diff --git a/src/javascript/opensrf_config.js b/src/javascript/opensrf_config.js
new file mode 100644 (file)
index 0000000..1beef71
--- /dev/null
@@ -0,0 +1,281 @@
+/** @file oils_config.js
+  * Config code and Logger code
+  * The config module is simple.  It parses an xml config file and 
+  * the values from the file are accessed via XPATH queries.
+  */
+
+/** Searches up from Mozilla's chrome directory for the client 
+  * config file and returns the file object
+  */
+function get_config_file() {
+
+       var dirService = Components.classes["@mozilla.org/file/directory_service;1"].
+               getService( Components.interfaces.nsIProperties );
+
+       chromeDir = dirService.get( "AChrom",  Components.interfaces.nsIFile );
+       chromeDir.append("evergreen");
+       chromeDir.append("content");
+       chromeDir.append("conf");
+       chromeDir.append("client_config.xml");
+       return chromeDir;
+
+}
+
+
+/** Constructor. You may provide an optional path to the config file. **/
+function Config( config_file ) {
+
+       if( Config.config != null ) { return Config.config; }
+
+       config_file = get_config_file();
+
+       // ------------------------------------------------------------------
+       // Grab the data from the config file
+       // ------------------------------------------------------------------
+       var data = "";
+       var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
+                       .createInstance(Components.interfaces.nsIFileInputStream);
+
+       var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
+                       .createInstance(Components.interfaces.nsIScriptableInputStream);
+
+       fstream.init(config_file, 1, 0, false);
+       sstream.init(fstream);
+       data += sstream.read(-1);
+
+       sstream.close();
+       fstream.close();
+       
+       
+
+       var DOMParser = new Components.Constructor(
+               "@mozilla.org/xmlextras/domparser;1", "nsIDOMParser" );
+
+       this.config_doc = new DOMParser().parseFromString( data, "text/xml" );
+
+       Config.config = this;
+
+}
+
+/** Returns the value stored in the config file found with the
+  * given xpath expression
+  * E.g.  config.get_value( "/oils_config/dirs/log_dir" );
+  * Note, the 'oils_config' node is the base of the xpath expression,
+  * so something like this will also work:
+  * config.get_value( "dirs/log_dir" );
+  */
+Config.prototype.get_value = function( xpath_query ) {
+
+       var evaluator = Components.classes["@mozilla.org/dom/xpath-evaluator;1"].
+               createInstance( Components.interfaces.nsIDOMXPathEvaluator );
+
+       var xpath_obj = evaluator.evaluate( xpath_query, this.config_doc.documentElement, null, 0, null );
+       if( ! xpath_obj ) { return null; }
+
+       var node = xpath_obj.iterateNext();
+       if( node == null ) {
+               throw new oils_ex_config( "No config option matching " + xpath_query );
+       }
+
+       return node.firstChild.nodeValue;
+}      
+
+
+
+
+
+
+// ------------------------------------------------------------------
+// Logger code.
+// ------------------------------------------------------------------
+/** The global logging object */
+Logger.logger          = null;
+/** No logging level */
+Logger.NONE                    = 0;
+/** Error log level */
+Logger.ERROR           = 1;
+/** Info log level */
+Logger.INFO                    = 2;
+/* Debug log level */
+Logger.DEBUG           = 3;
+
+/** There exists a single logger object that all components share.
+  * Calling var logger = new Logger() will return the same one
+  * with each call.  This is so we only need one file handle for
+  * each type of log file.
+  */
+function Logger() {
+
+       if( Logger.logger != null ) { return Logger.logger }
+
+       var config = new Config();
+       this.log_level = config.get_value( "system/log_level" );
+
+       this.stdout_log = config.get_value( "system/stdout_log" );
+
+       if( ! this.stdout_log || this.stdout_log < 0 || this.stdout_log > 2 ) {
+               throw new oils_ex_config( "stdout_log setting is invalid: " + this.stdout_log + 
+                               ". Should be 0, 1, or 2." );
+       }
+
+       // ------------------------------------------------------------------
+       // Load up all of the log files
+       // ------------------------------------------------------------------
+       var transport_file = config.get_value( "logs/transport" );
+       if( transport_file == null ) {
+               throw new oils_ex_config( "Unable to load transport log file: 'logs/transport'" );
+       }
+
+       var debug_file = config.get_value( "logs/debug" );
+       if( debug_file == null ) {
+               throw new oils_ex_config( "Unable to load debug log file: 'logs/debug'" );
+       }
+       
+       var error_file = config.get_value( "logs/error" );
+       if( error_file == null ) {
+               throw new oils_ex_config( "Unable to load debug log file: 'logs/error'" );
+       }
+
+
+       // ------------------------------------------------------------------
+       // Build the file objects
+       // ------------------------------------------------------------------
+       var transport_file_obj = Logger.get_log_file( transport_file );
+
+       var debug_file_obj = Logger.get_log_file( debug_file );
+
+       var error_file_obj = Logger.get_log_file( error_file );
+
+
+       // ------------------------------------------------------------------
+       // Build all of the file stream objects
+       // ------------------------------------------------------------------
+       this.transport_stream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+                       .createInstance(Components.interfaces.nsIFileOutputStream);
+
+       this.debug_stream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+                       .createInstance(Components.interfaces.nsIFileOutputStream);
+
+       this.error_stream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+                       .createInstance(Components.interfaces.nsIFileOutputStream);
+
+       // ------------------------------------------------------------------
+       // Init all of the streams
+       // use 0x02 | 0x10 to open file for appending.
+       // ------------------------------------------------------------------
+       this.transport_stream.init(transport_file_obj,  0x02 | 0x10 | 0x08, 0664, 0 ); 
+       this.debug_stream.init( debug_file_obj,                 0x02 | 0x10 | 0x08, 0664, 0 ); 
+       this.error_stream.init( error_file_obj,                 0x02 | 0x10 | 0x08, 0664, 0 ); 
+
+       Logger.logger = this;
+
+}
+
+/** Internal.  Returns a XPCOM nsIFile object for the log file we're interested in */
+Logger.get_log_file = function( log_name ) {
+
+       var dirService = Components.classes["@mozilla.org/file/directory_service;1"].
+               getService( Components.interfaces.nsIProperties );
+
+       logFile = dirService.get( "AChrom",  Components.interfaces.nsIFile );
+       logFile.append("evergreen");
+       logFile.append("content");
+       logFile.append("log");
+       logFile.append( log_name );
+
+       if( ! logFile.exists() ) {
+               logFile.create( 0, 0640 );
+       }
+
+       return logFile;
+}
+
+
+
+/** Internal. Builds a log message complete with data, etc. */
+Logger.prototype.build_string = function( message, level ) {
+
+       if( ! (message && level) ) { return null; }
+
+       var lev = "INFO";
+       if( level == Logger.ERROR ) { lev = "ERROR"; }
+       if( level == Logger.DEBUG ) { lev = "DEBUG"; }
+
+       var date                = new Date();
+       var year                = date.getYear();
+       year += 1900;
+
+       var month       = ""+date.getMonth();
+       if(month.length==1) {month="0"+month;}
+       var day         = ""+date.getDate();
+       if(day.length==1) {day="0"+day;}
+       var hour                = ""+date.getHours();
+       if(hour.length== 1){hour="0"+hour;}
+       var min         = ""+date.getMinutes();
+       if(min.length==1){min="0"+min;}
+       var sec         = ""+date.getSeconds();
+       if(sec.length==1){sec="0"+sec;}
+       var mil         = ""+date.getMilliseconds();
+       if(mil.length==1){sec="0"+sec;}
+
+       var date_string = year + "-" + month + "-" + day + " " + 
+               hour + ":" + min + ":" + sec + "." + mil;
+
+       var str_array = message.split('\n');
+       var ret_array = new Array();
+       for( var i in str_array ) {
+               ret_str = "[" + date_string +  "] " + lev + " " + str_array[i] + "\n";
+               ret_array.push( ret_str );
+       }
+
+       var line = "-------------------------\n";
+       ret_array.unshift( line );
+
+       return ret_array;
+}
+
+/** Internal. Does the actual writing */
+Logger.prototype._log = function( data, stream, level ) {
+
+       if( ! data ) { return; }
+       if( ! stream ) { 
+               throw oils_ex_logger( "No file stream open for log message: " + data ); 
+       }
+       if( ! level ) { level = Logger.DEBUG; }
+
+       if( level > this.log_level ) { return; }
+       var str_array = this.build_string( data, level );
+       if( ! str_array ) { return; }
+
+       for( var i in str_array ) {
+               if( this.stdout_log > 0 ) { dump( str_array[i] ); }
+               if( this.stdout_log < 2 ) { stream.write( str_array[i], str_array[i].length ); }
+       }
+
+       // write errors to the error log if they were destined for anywhere else
+       if( level == Logger.ERROR && stream != this.error_stream ) {
+               for( var i in str_array ) {
+                       if( this.stdout_log > 0 ) { dump( str_array[i] ); }
+                       if( this.stdout_log < 2 ) { this.error_stream.write( str_array[i], str_array[i].length ); }
+               }
+       }
+}
+       
+
+
+/** Writes the message to the error log */
+Logger.prototype.error = function( message, level ) {
+       this._log( message, this.error_stream, level );
+}
+
+
+/** Writes to the debug log */
+Logger.prototype.debug = function( message, level ) {
+       this._log( message, this.debug_stream, level );
+}
+
+
+/** Writes to the transport log */
+Logger.prototype.transport = function( message, level ) {
+       this._log( message, this.transport_stream, level );
+}
diff --git a/src/javascript/opensrf_dom_element.js b/src/javascript/opensrf_dom_element.js
new file mode 100644 (file)
index 0000000..2e419e3
--- /dev/null
@@ -0,0 +1,263 @@
+/** @file oils_dom_element.js
+  * -----------------------------------------------------------------------------
+  * This file holds all of the core OILS DOM elements.
+  *  -----------------------------------------------------------------------------
+
+  * -----------------------------------------------------------------------------
+  * Make sure you load md5.js into  your script/html/etc. before loading this 
+  * file
+  * -----------------------------------------------------------------------------
+  * 
+  * -----------------------------------------------------------------------------
+  * 1. Use these if you are running via xpcshell, but not if you are running
+  *    directly from mozilla.
+  * 2. BTW. XMLSerializer doesn't like being run outside of mozilla, so
+  *            toString() fails.
+  * 
+  * var DOMParser = new Components.Constructor(
+  *            "@mozilla.org/xmlextras/domparser;1", "nsIDOMParser" );
+  * 
+  *    var XMLSerializer = new Components.Constructor(
+  *            "@mozilla.org/xmlextras/xmlserializer;1", "nsIDOMSerializer" );
+  *  -----------------------------------------------------------------------------
+  * 
+  * 
+  */
+
+/**
+  *  -----------------------------------------------------------------------------
+  *  DOM - Top level generic class
+  *  -----------------------------------------------------------------------------
+  */
+
+function DOM() { 
+       this.doc = null; 
+       this.root = null;
+       this.element = null;
+}
+
+/** Returns a string representation of the XML doc.  If full_bool
+  * is true, it will return the entire doc and not just the relevant
+  * portions (i.e. our object's element).
+  */
+DOM.prototype.toString = function(full_bool) {
+       if( full_bool ) {
+               return new XMLSerializer().serializeToString(this.doc);
+       } else {
+               return new XMLSerializer().serializeToString(this.element);
+       }
+}
+
+/** Initializes a document and sets this.doc, this.root and this.element */
+DOM.prototype.init = function( elementName ) {
+       
+       this.doc = new DOMParser().parseFromString( 
+               "<?xml version='1.0' encoding='UTF-8'?><oils:root xmlns:oils='http://open-ils.org/namespaces/oils_v1'></oils:root>", "text/xml" );
+       this.root = this.doc.documentElement;
+       this.element = this.doc.createElement( "oils:" + elementName );
+       return this.element;
+}
+
+/** Good for creating objects from raw xml.  This builds a new doc and inserts
+  * the node provided.  So you can build a dummy object, then call replaceNode  
+  * with the new xml to build an object from XML on the fly.
+  */
+DOM.prototype._replaceNode = function( name, new_element ) {
+
+       var node = this.doc.importNode( new_element, true );
+
+       if( node.nodeName != "oils:" + name ) {
+               throw new oils_ex_dom( "Invalid Tag to " + name + "::replaceNode()" );
+       }
+
+       this.doc = new DOMParser().parseFromString( 
+               "<?xml version='1.0' encoding='UTF-8'?><oils:root xmlns:oils='http://open-ils.org/namespaces/oils_v1'></oils:root>", "text/xml" );
+       this.root = this.doc.documentElement;
+
+       this.root.appendChild( node );
+       this.element = this.root.firstChild;
+       return this;
+}
+
+
+
+// -----------------------------------------------------------------------------
+// domainObjectAttr
+// -----------------------------------------------------------------------------
+
+domainObjectAttr.prototype     = new DOM();
+domainObjectAttr.prototype.constructor = domainObjectAttr;
+domainObjectAttr.baseClass     = DOM.prototype.constructor;
+
+/** A domainObjectAttr is a single XML node with 'name' and 'value' attributes */
+function domainObjectAttr( name, value ) {
+
+       var node = this.init( "domainObjectAttr" );
+       if( ! name && value ) { return; }
+       node.setAttribute( "name", name );
+       node.setAttribute( "value", value );
+       this.root.appendChild( node );
+}
+
+
+/** Returns the attribute name */
+domainObjectAttr.prototype.getName = function() {
+       return this.element.getAttribute("name"); 
+}
+
+/** Returns the attribut value. */
+domainObjectAttr.prototype. getValue = function() {
+       return this.element.getAttribute("value"); 
+}
+
+/** Sets the attribute value. */
+domainObjectAttr.prototype. setValue = function( value ) {
+       return this.element.setAttribute("value", value ); 
+}
+
+/** Pass in the this.element component of a domainObjectAttr and this will
+  * replace the old element with the new one 
+  */
+domainObjectAttr.prototype.replaceNode = function( domainObjectAttr_element ) {
+       return this._replaceNode( "domainObjectAttr", domainObjectAttr_element );
+}
+
+// -----------------------------------------------------------------------------
+// userAuth
+// -----------------------------------------------------------------------------
+
+
+userAuth.prototype = new DOM();
+userAuth.prototype.constructor = userAuth;
+userAuth.baseClass = DOM.prototype.constructor;
+
+function userAuth( username, secret ) {
+
+       var node = this.init( "userAuth" );
+       if( !( username && secret) ) { return; }
+       node.setAttribute( "username", username );
+
+       //There is no way to specify the hash seed with the 
+       //md5 utility provided
+       var hash = hex_md5( secret );
+       node.setAttribute( "secret", hash );
+       node.setAttribute( "hashseed", "" ); 
+
+       this.root.appendChild( node );
+}
+
+userAuth.prototype.getUsername = function() {
+       return this.element.getAttribute( "username" );
+}
+
+userAuth.prototype.getSecret = function() {
+       return this.element.getAttribute( "secret" );
+}
+
+userAuth.prototype.getHashseed = function() {
+       return this.element.getAttribute( "hashseed" );
+}
+
+userAuth.prototype.replaceNode = function( userAuth_element ) {
+       return this._replaceNode( "userAuth", userAuth_element );
+}
+
+
+// -----------------------------------------------------------------------------
+// domainObject
+// -----------------------------------------------------------------------------
+
+domainObject.prototype = new DOM(); 
+domainObject.baseClass = DOM.prototype.constructor;
+domainObject.prototype.constructor = domainObject;
+
+/** Holds the XML for a DomainObject (see oils_domain_object.js or the DomainObject class) */
+function domainObject( name ) {
+       this._init_domainObject( name ); 
+}
+
+/** Initializes the domainObject XML node */
+domainObject.prototype._init_domainObject = function( name ) {
+
+       var node = this.init( "domainObject" );
+
+       if( ! name ) { return; }
+       node.setAttribute( "name", name );
+       this.root.appendChild( node ); 
+
+}
+
+/** Pass in any DOM Element or DomainObject and this method will as the
+  * new object as the next child of the root (domainObject) node 
+  */
+domainObject.prototype.add = function( new_DOM ) {
+
+       this.element.appendChild( 
+                       new_DOM.element.cloneNode( true ) );
+}
+
+/** Removes the original domainObject XML node and replaces it with the
+  * one provided.  This is useful for building new domainObject's from
+  * raw xml.  Just create a new one, then call replaceNode with the new XML.
+  */
+domainObject.prototype.replaceNode = function( domainObject_element ) {
+       return this._replaceNode( "domainObject", domainObject_element );
+}
+
+
+
+// -----------------------------------------------------------------------------
+// Param 
+// -----------------------------------------------------------------------------
+
+
+param.prototype = new DOM();
+param.baseClass = DOM.prototype.constructor;
+param.prototype.constructor = param;
+
+function param( value ) {
+       var node = this.init( "param" );
+       node.appendChild( this.doc.createTextNode( value ) );
+       this.root.appendChild( node );
+}
+
+param.prototype.getValue = function() {
+       return this.element.textContent;
+}
+
+param.prototype.replaceNode = function( param_element ) {
+       return this._replaceNode( "param", param_element );
+}
+
+
+// -----------------------------------------------------------------------------
+// domainObjectCollection
+// -----------------------------------------------------------------------------
+
+domainObjectCollection.prototype = new DOM();
+domainObjectCollection.prototype.constructor = 
+       domainObjectCollection;
+domainObjectCollection.baseClass = DOM.prototype.constructor;
+
+function domainObjectCollection( name ) {
+       var node = this.init( "domainObjectCollection" );       
+       this.root.appendChild( node );
+       if(name) { this._initCollection( name ); }
+}
+
+domainObjectCollection.prototype._initCollection = function( name ) {
+       if( name ) {
+               this.element.setAttribute( "name", name );
+       }
+}
+
+domainObjectCollection.prototype.add = function( new_domainObject ) {
+       this.element.appendChild( 
+                       new_domainObject.element.cloneNode( true ) );
+}
+
+domainObjectCollection.prototype.replaceNode = function( new_node ) {
+       return this._replaceNode( "domainObjectCollection", new_node );
+}
+
+
diff --git a/src/javascript/opensrf_domain_object.js b/src/javascript/opensrf_domain_object.js
new file mode 100644 (file)
index 0000000..edbd247
--- /dev/null
@@ -0,0 +1,609 @@
+// -----------------------------------------------------------------------------
+// This houses all of the domain object code.
+// -----------------------------------------------------------------------------
+
+
+
+
+// -----------------------------------------------------------------------------
+// DomainObject 
+
+DomainObject.prototype                                 = new domainObject();
+DomainObject.prototype.constructor     = DomainObject;
+DomainObject.prototype.baseClass               = domainObject.prototype.constructor;
+
+/** Top level DomainObject class.  This most provides convience methods
+  * and a shared superclass
+  */
+function DomainObject( name ) {
+       if( name ) { this._init_domainObject( name ); }
+}
+
+/** Returns the actual element of the given domainObjectAttr. */
+DomainObject.prototype._findAttr = function ( name ) {
+       
+       var nodes = this.element.childNodes;
+
+       if( ! nodes || nodes.length < 1 ) {
+               throw new oils_ex_dom( "Invalid xml object in _findAttr: " + this.toString() );
+       }
+
+       var i=0;
+       var node = nodes.item(i);
+
+       while( node != null ) {
+
+               if( node.nodeName == "oils:domainObjectAttr" &&
+                               node.getAttribute("name") == name ) {
+                       return node;
+               }
+
+               node = nodes.item(++i);
+       }
+
+       return null;
+}
+
+
+
+
+/** Returns the value stored in the given attribute */
+DomainObject.prototype.getAttr = function ( name ) {
+
+       var node = this._findAttr( name );
+       if( node ) { return node.getAttribute( "value" ); }
+       else { 
+               throw new oils_ex_dom( "getAttr(); Getting nonexistent attribute: " + name ); 
+       }
+}
+
+/** Updates the value held by the given attribute */
+DomainObject.prototype.setAttr = function ( name, value ) {
+
+       var node = this._findAttr( name );
+       if( node ) {
+               node.setAttribute( "value", value );
+       } else { 
+               throw new oils_ex_dom( "setAttr(); Setting nonexistent attribute: " + name ); 
+       }
+}
+
+/** This takes a raw DOM Element node and creates the DomainObject that the node
+  * embodies and returns the object.  All new DomainObjects should be added to
+  * this list if they require this type of dynamic functionality.
+  * NOTE Much of this will be deprecated as move to a more JSON-centric wire protocol
+  */
+DomainObject.newFromNode = function( node ) {
+
+       switch( node.getAttribute("name") ) {
+
+               case "oilsMethod":
+                       return new oilsMethod().replaceNode( node );
+               
+               case "oilsMessage":
+                       return new oilsMessage().replaceNode( node );
+
+               case "oilsResponse":
+                       return new oilsResponse().replaceNode( node );
+
+               case "oilsResult":
+                       return new oilsResult().replaceNode( node );
+
+               case "oilsConnectStatus":
+                       return new oilsConnectStatus().replaceNode( node );
+
+               case "oilsException":
+                       return new oilsException().replaceNode( node );
+
+               case "oilsMethodException":
+                       return new oilsMethodException().replaceNode( node );
+
+               case "oilsScalar":
+                       return new oilsScalar().replaceNode( node );
+
+               case "oilsPair":
+                       return new oilsPair().replaceNode( node );
+
+               case "oilsArray":
+                       return new oilsArray().replaceNode( node );
+
+               case "oilsHash":
+                       return new oilsHash().replaceNode( node );
+
+
+       }
+
+}
+
+
+
+// -----------------------------------------------------------------------------
+// oilsMethod
+
+oilsMethod.prototype = new DomainObject();
+oilsMethod.prototype.constructor = oilsMethod;
+oilsMethod.prototype.baseClass = DomainObject.prototype.constructor;
+
+/**
+  * oilsMethod Constructor
+  * 
+  * @param method_name The name of the method your are sending
+  * to the remote server
+  * @param params_array A Javascript array of the params (any
+  * Javascript data type)
+  */
+
+function oilsMethod( method_name, params_array ) {
+       this._init_domainObject( "oilsMethod" );
+       this.add( new domainObjectAttr( "method", method_name ) );
+
+       if( params_array ) {
+               var params = this.doc.createElement( "oils:params" );
+               this.element.appendChild( params ); 
+               params.appendChild( this.doc.createTextNode( 
+                               js2JSON( params_array ) ) ); 
+       }
+}
+
+
+/** Locates the params node of this method */
+oilsMethod.prototype._getParamsNode = function() {
+
+       var nodes = this.element.childNodes;
+       if( ! nodes || nodes.length < 1 ) { return null; }
+       var x=0;
+       var node = nodes.item(x);
+       while( node != null ) {
+               if( node.nodeName == "oils:params" ) {
+                       return node;
+               }
+               node = nodes.item(++x);
+       }
+
+       return null;
+}
+
+
+/** Returns an array of param objects  */
+oilsMethod.prototype.getParams = function() {
+       var node = this._getParamsNode();
+       if(node != null ) { return JSON2js( node->textContent ); }
+}
+
+
+
+// -----------------------------------------------------------------------------
+// oilsMessage
+
+// -----------------------------------------------------------------------------
+// oilsMessage message types
+// -----------------------------------------------------------------------------
+/** CONNECT Message type */
+oilsMessage.CONNECT            = 'CONNECT';
+/** DISCONNECT Message type */
+oilsMessage.DISCONNECT = 'DISCONNECT';
+/** STATUS Message type */
+oilsMessage.STATUS             = 'STATUS';
+/** REQUEST Message type */
+oilsMessage.REQUEST            = 'REQUEST';
+/** RESULT Message type */
+oilsMessage.RESULT             = 'RESULT';
+
+
+oilsMessage.prototype = new DomainObject();
+oilsMessage.prototype.constructor = oilsMessage;
+oilsMessage.prototype.baseClass = DomainObject.prototype.constructor;
+
+/** Core XML object for message passing */
+function oilsMessage( type, protocol, user_auth ) {
+
+       if( !( type && protocol) ) { 
+               type = oilsMessage.CONNECT; 
+               protocol = "1";
+       }
+
+       if( ! ( type == oilsMessage.CONNECT             ||
+                               type == oilsMessage.DISCONNECT  ||
+                               type == oilsMessage.STATUS                      ||
+                               type == oilsMessage.REQUEST             ||
+                               type == oilsMessage.RESULT      ) ) {
+               throw new oils_ex_message( "Attempt to create oilsMessage with incorrect type: " + type );
+       }
+
+       this._init_domainObject( "oilsMessage" );
+
+       this.add( new domainObjectAttr( "type", type ) );
+       this.add( new domainObjectAttr( "threadTrace", 0 ) );
+       this.add( new domainObjectAttr( "protocol", protocol ) );       
+
+       if( user_auth ) { this.add( user_auth ); }
+
+}
+
+/** Builds a new oilsMessage from raw xml.
+  * Checks are taken to make sure the xml is supposed to be an oilsMessage.  
+  * If not, an oils_ex_dom exception is throw.
+  */
+oilsMessage.newFromXML = function( xml ) {
+
+       try {
+
+               if( ! xml || xml == "" ) { throw 1; }
+
+               var doc = new DOMParser().parseFromString( xml, "text/xml" );
+               if( ! doc ) { throw 1; }
+
+               var root = doc.documentElement;
+               if( ! root ) { throw 1; }
+
+               // -----------------------------------------------------------------------------
+               // There are two options here.  One is that we were provided the full message
+               // xml (i.e. contains the <oils:root> tag.  The other option is that we were
+               // provided just the message xml portion (top level element is the 
+               // <oils:domainObject>
+               // -----------------------------------------------------------------------------
+               
+               var element;
+               if( root.nodeName == "oils:root"  ) { 
+
+                       element = root.firstChild;
+                       if( ! element ) { throw 1; }
+
+               } else { 
+
+                       if( root.nodeName == "oils:domainObject" ) { 
+                               element = root;
+
+                       } else { throw 1; }
+
+               }
+
+
+               if( element.nodeName != "oils:domainObject" ) { throw 1; } 
+               if( element.getAttribute( "name" ) != "oilsMessage" ) { throw 1; }
+
+               return new oilsMessage().replaceNode( element );
+
+       } catch( E ) {
+
+               if( E && E.message ) {
+                       throw new oils_ex_dom( "Bogus XML for creating oilsMessage: " + E.message + "\n" + xml );
+
+               } else {
+                       throw new oils_ex_dom( "Bogus XML for creating oilsMessage:\n" + xml );
+               }
+       }
+
+       return msg;
+}
+
+
+
+/** Adds a copy of the given DomainObject to the message */
+oilsMessage.prototype.addPayload = function( new_DomainObject ) {
+       this.add( new_DomainObject );
+}
+
+
+/** Returns the top level DomainObject contained in this message
+  *
+  * Note:  The object retuturned will be the actual object, not just a 
+  * generic DomainObject - e.g. oilsException.
+  */
+oilsMessage.prototype.getPayload = function() {
+       
+       var nodes = this.element.childNodes;
+       var x=0;
+       var node = nodes.item(0);
+
+       while( node != null ) {
+
+               if( node.nodeName == "oils:domainObject" ) {
+
+                       new Logger().debug( "Building oilsMessage payload from\n" + 
+                               new XMLSerializer().serializeToString( node ), Logger.DEBUG );
+                       return DomainObject.newFromNode( node );
+               }
+               node = nodes.item(++x);
+       }
+
+       return null;
+}
+
+oilsMessage.prototype.getUserAuth = function() {
+
+       var nodes = this.element.getElementsByTagName( "oils:userAuth" );
+       if( ! nodes ) { return null; }
+
+       var auth_elem = nodes.item(0); // should only be one
+       if ( ! auth_elem ) { return null; }
+
+       return new userAuth().replaceNode( auth_elem );
+
+}
+
+/** Returns the type of the message */
+oilsMessage.prototype.getType = function() {
+       return this.getAttr( "type" );
+}
+
+/** Returns the message protocol */
+oilsMessage.prototype.getProtocol = function() {
+       return this.getAttr( "protocol" );
+}
+
+/** Returns the threadTrace */
+oilsMessage.prototype.getThreadTrace = function() {
+       return this.getAttr( "threadTrace" );
+}
+
+/** Sets the thread trace - MUST be an integer */
+oilsMessage.prototype.setThreadTrace = function( trace ) {
+       this.setAttr( "threadTrace", trace );
+}
+
+/** Increments the thread trace by 1 */
+oilsMessage.prototype.updateThreadTrace = function() {
+       var tt = this.getThreadTrace();
+       return this.setThreadTrace( ++tt );
+}
+
+
+
+
+
+// -----------------------------------------------------------------------------
+// oilsResponse
+
+
+// -----------------------------------------------------------------------------
+// Response codes
+// -----------------------------------------------------------------------------
+
+/**
+  * Below are the various response statuses
+  */
+oilsResponse.STATUS_CONTINUE                                           = 100
+
+oilsResponse.STATUS_OK                                                         = 200
+oilsResponse.STATUS_ACCEPTED                                           = 202
+oilsResponse.STATUS_COMPLETE                                           = 205
+
+oilsResponse.STATUS_REDIRECTED                                 = 307
+
+oilsResponse.STATUS_BADREQUEST                                 = 400
+oilsResponse.STATUS_UNAUTHORIZED                                       = 401
+oilsResponse.STATUS_FORBIDDEN                                          = 403
+oilsResponse.STATUS_NOTFOUND                                           = 404
+oilsResponse.STATUS_NOTALLOWED                                 = 405
+oilsResponse.STATUS_TIMEOUT                                            = 408
+oilsResponse.STATUS_EXPFAILED                                          = 417
+oilsResponse.STATUS_INTERNALSERVERERROR                = 500
+oilsResponse.STATUS_NOTIMPLEMENTED                             = 501
+oilsResponse.STATUS_VERSIONNOTSUPPORTED                = 505
+
+
+oilsResponse.prototype = new DomainObject();
+oilsResponse.prototype.constructor = oilsResponse;
+oilsResponse.prototype.baseClass = DomainObject.prototype.constructor;
+
+/** Constructor.  status is the text describing the message status and
+  * statusCode must correspond to one of the oilsResponse status code numbers
+  */
+function oilsResponse( name, status, statusCode ) {
+       if( name && status && statusCode ) {
+               this._initResponse( name, status, statusCode );
+       }
+}
+
+/** Initializes the reponse XML */
+oilsResponse.prototype._initResponse = function( name, status, statusCode ) {
+
+       this._init_domainObject( name );
+       this.add( new domainObjectAttr( "status", status ));
+       this.add( new domainObjectAttr( "statusCode", statusCode ) );
+}
+
+
+
+/** Returns the status of the response */
+oilsResponse.prototype.getStatus = function() {
+       return this.getAttr( "status" );
+}
+
+/** Returns the statusCode of the response */
+oilsResponse.prototype.getStatusCode = function() {
+       return this.getAttr("statusCode");
+}
+
+
+oilsConnectStatus.prototype = new oilsResponse();
+oilsConnectStatus.prototype.constructor = oilsConnectStatus;
+oilsConnectStatus.prototype.baseClass = oilsResponse.prototype.constructor;
+
+/** Constructor.  These give us info on our connection attempts **/
+function oilsConnectStatus( status, statusCode ) {
+       this._initResponse( "oilsConnectStatus", status, statusCode );
+}
+
+
+oilsResult.prototype = new oilsResponse();
+oilsResult.prototype.constructor = oilsResult;
+oilsResult.prototype.baseClass = oilsResponse.prototype.constructor;
+
+
+/** Constructor.  These usually carry REQUEST responses */
+function oilsResult( status, statusCode ) {
+       if( status && statusCode ) {
+               this._initResponse( "oilsResult", status, statusCode );
+       }
+}
+
+/** Returns the top level DomainObject within this result message.
+  * Note:  The object retuturned will be the actual object, not just a 
+  * generic DomainObject - e.g. oilsException.
+  */
+oilsResult.prototype.getContent = function() {
+
+       var nodes = this.element.childNodes;
+       if( ! nodes || nodes.length < 1 ) {
+               throw new oils_ex_dom("Content node for oilsResult is invalid\n" + this.toString() );
+       }
+       var x = 0;
+       var node = nodes.item(x);
+       while( node != null ) {
+
+               if( node.nodeName == "oils:domainObject"                                        ||
+                               node.nodeName == "oils:domainObjectCollection" ) {
+
+                       return DomainObject.newFromNode( node );
+               }
+               node = nodes.item(++x);
+       }
+
+       throw new oils_ex_dom("Content node for oilsResult is invalid\n" + this.toString() );
+}
+
+
+oilsException.prototype = new oilsResponse();
+oilsException.prototype.constructor = oilsException;
+oilsException.prototype.baseClass = oilsResponse.prototype.constructor;
+
+/** Top level exception */
+function oilsException( status, statusCode ) {
+       if( status && statusCode ) {
+               this._initResponse( "oilsException", status, statusCode );
+       }
+}
+
+
+oilsMethodException.prototype = new oilsException();
+oilsMethodException.prototype.constructor = oilsMethodException;
+oilsMethodException.prototype.baseClass = oilsException.prototype.constructor;
+
+/** Method exception */
+function oilsMethodException( status, statusCode ) {
+       if( status && statusCode ) {
+               this._initResponse( "oilsMethodException", status, statusCode );
+       }
+}
+
+
+// -----------------------------------------------------------------------------
+// oilsScalar
+
+
+oilsScalar.prototype = new DomainObject();
+oilsScalar.prototype.constructor = oilsScalar;
+oilsScalar.prototype.baseClass = DomainObject.prototype.constructor;
+
+/** Contains a single JSON item as its text content */
+function oilsScalar( value ) {
+       this._init_domainObject( "oilsScalar" );
+       this.element.appendChild( this.doc.createTextNode( value ) );
+}
+
+/** Returns the contained item as a Javascript object */
+oilsScalar.prototype.getValue = function() {
+       return JSON2js( this.element.textContent );
+}
+
+
+// -----------------------------------------------------------------------------
+// oilsPair
+
+
+oilsPair.prototype = new DomainObject()
+oilsPair.prototype.constructor = oilsPair;
+oilsPair.prototype.baseClass = DomainObject.prototype.constructor;
+
+function oilsPair( key, value ) {
+
+       this._init_domainObject( "oilsPair" );
+       this.element.appendChild( this.doc.createTextNode( value ) );
+       this.element.setAttribute( "key", key );
+}
+
+oilsPair.prototype.getKey = function() {
+       return this.element.getAttribute( "key" );
+}
+
+oilsPair.prototype.getValue = function() {
+       return this.element.textContent;
+}
+
+
+
+// -----------------------------------------------------------------------------
+// oilsArray - is a domainObjectCollection that stores a list of domainObject's
+// or domainObjectCollections.
+// -----------------------------------------------------------------------------
+
+oilsArray.prototype = new domainObjectCollection()
+oilsArray.prototype.constructor = oilsArray;
+oilsArray.prototype.baseClass = domainObjectCollection.prototype.constructor;
+
+function oilsArray( obj_list ) {
+       this._initCollection( "oilsArray" );
+       if(obj_list) { this.initArray( obj_list ); }
+}
+
+// -----------------------------------------------------------------------------
+// Adds the array of objects to the array, these should be actual objects, not
+// DOM nodes/elements, etc.
+// -----------------------------------------------------------------------------
+oilsArray.prototype.initArray = function( obj_list ) {
+       if( obj_array != null && obj_array.length > 0 ) {
+               for( var i= 0; i!= obj_array.length; i++ ) {
+                       this.add( obj_array[i] );
+               }
+       }
+}
+
+// -----------------------------------------------------------------------------
+// Returns an array of DomainObjects or domainObjectCollections.  The objects
+// returned will already be 'cast' into the correct object. i.e. you will not
+// receieve an array of DomainObjects, but instead an array of oilsScalar's or
+// an array of oilsHashe's, etc.
+// -----------------------------------------------------------------------------
+oilsArray.prototype.getObjects = function() {
+
+       var obj_list = new Array();
+       var nodes = this.element.childNodes;
+       var i=0;
+       var node = nodes.item(i);
+
+       while( node != null ) {
+
+               if( node.nodeName == "oils:domainObject"                                        ||
+                               node.nodeName == "oils:domainObjectCollection" ) {
+                       obj_list[i++] = DomainObject.newFromNode( node );
+               }
+               node = nodes.item(i);
+       }
+
+       return obj_list;
+}
+
+
+// -----------------------------------------------------------------------------
+// oilsHash - an oilsHash is an oilsArray except it only stores oilsPair's
+// -----------------------------------------------------------------------------
+
+oilsHash.prototype = new oilsArray();
+oilsHash.prototype.constructor = oilsHash;
+oilsHash.prototype.baseClass = oilsArray.prototype.constructor;
+
+function oilsHash( pair_array ) {
+       this._initCollection("oilsHash");
+       if(pair_array) { this.initArray( pair_array ); }
+}
+
+// -----------------------------------------------------------------------------
+// Returns the array of oilsPairs objects that this hash contains
+// -----------------------------------------------------------------------------
+oilsHash.prototype.getPairs = function() {
+       return this.getObjects();
+}
+
+
diff --git a/src/javascript/opensrf_jabber_transport.js b/src/javascript/opensrf_jabber_transport.js
new file mode 100644 (file)
index 0000000..fbd6dac
--- /dev/null
@@ -0,0 +1,418 @@
+// ------------------------------------------------------------------
+//             Houses the jabber transport code
+//
+// 1. jabber_connection - high level jabber component
+// 2. jabber_message - message class
+// 3. jabber_socket - socket handling code (shouldn't have to 
+//             use this class directly)
+//
+// Requires oils_utils.js
+// ------------------------------------------------------------------
+
+
+
+
+
+// ------------------------------------------------------------------
+// JABBER_CONNECTION
+// High level transport code
+
+// ------------------------------------------------------------------
+// Constructor
+// ------------------------------------------------------------------
+jabber_connection.prototype = new transport_connection();
+jabber_connection.prototype.constructor = jabber_connection;
+jabber_connection.baseClass = transport_connection.prototype.constructor;
+
+/** Initializes a jabber_connection object */
+function jabber_connection( username, password, resource ) {
+
+       this.username           = username;
+       this.password           = password;
+       this.resource           = resource;
+       this.socket                     = new jabber_socket();
+
+       this.host                       = "";
+
+}
+
+/** Connects to the Jabber server.  'timeout' is the connect timeout
+  * in milliseconds 
+ */
+jabber_connection.prototype.connect = function( host, port, timeout ) {
+       this.host = host;
+       return this.socket.connect( 
+                       this.username, this.password, this.resource, host, port, timeout );
+}
+
+/** Sends a message to 'recipient' with the provided message 
+  * thread and body 
+  */
+jabber_connection.prototype.send = function( recipient, thread, body ) {
+       var jid = this.username+"@"+this.host+"/"+this.resource;
+       var msg = new jabber_message( jid, recipient, thread, body );
+       return this.socket.tcp_send( msg.to_string() );
+}
+
+/** This method will wait at most 'timeout' milliseconds
+  * for a Jabber message to arrive.  If one arrives
+  * it is returned to the caller, other it returns null
+  */
+jabber_connection.prototype.recv = function( timeout ) {
+       return this.socket.recv( timeout );
+}
+
+/** Disconnects from the jabber server */
+jabber_connection.prototype.disconnect = function() {
+       return this.socket.disconnect();
+}
+
+/** Returns true if we are currently connected to the 
+  * Jabber server
+  */
+jabber_connection.prototype.connected = function() {
+       return this.socket.connected();
+}
+
+
+
+// ------------------------------------------------------------------
+// JABBER_MESSAGE
+// High level message handling code
+       
+
+jabber_message.prototype = new transport_message();
+jabber_message.prototype.constructor = jabber_message;
+jabber_message.prototype.baseClass = transport_message.prototype.constructor;
+
+/** Builds a jabber_message object */
+function jabber_message( sender, recipient, thread, body ) {
+
+       if( sender == null || recipient == null || recipient.length < 1 ) { return; }
+
+       this.doc = new DOMParser().parseFromString("<message></message>", "text/xml");
+       this.root = this.doc.documentElement;
+       this.root.setAttribute( "from", sender );
+       this.root.setAttribute( "to", recipient );
+
+       var body_node = this.doc.createElement("body");
+       body_node.appendChild( this.doc.createTextNode( body ) );
+
+       var thread_node = this.doc.createElement("thread");
+       thread_node.appendChild( this.doc.createTextNode( thread ) );
+
+       this.root.appendChild( body_node );
+       this.root.appendChild( thread_node );
+
+}
+
+/** Builds a new message from raw xml.
+  * If the message is a Jabber error message, then msg.is_error_msg
+  * is set to true;
+  */
+jabber_message.prototype.from_xml = function( xml ) {
+       var msg = new jabber_message();
+       msg.doc = new DOMParser().parseFromString( xml, "text/xml" );
+       msg.root = msg.doc.documentElement;
+
+       if( msg.root.getAttribute( "type" ) == "error" ) {
+               msg.is_error_msg = true;
+       } else {
+               this.is_error_msg = false;
+       }
+
+       return msg;
+}
+
+/** Returns the 'from' field of the message */
+jabber_message.prototype.get_sender = function() {
+       return this.root.getAttribute( "from" );
+}
+
+/** Returns the jabber thread */
+jabber_message.prototype.get_thread = function() {
+       var nodes = this.root.getElementsByTagName( "thread" );
+       var thread_node = nodes.item(0);
+       return thread_node.firstChild.nodeValue;
+}
+
+/** Returns the message body */
+jabber_message.prototype.get_body = function() {
+       var nodes = this.root.getElementsByTagName( "body" );
+       var body_node = nodes.item(0);
+       new Logger().transport( "Get Body returning:\n" + body_node.textContent, Logger.DEBUG );
+       return body_node.textContent;
+}
+       
+/** Returns the message as a whole as an XML string */
+jabber_message.prototype.to_string = function() {
+   return new XMLSerializer().serializeToString(this.root);
+}
+
+
+
+
+// ------------------------------------------------------------------
+// TRANSPORT_SOCKET
+
+/** Initializes a new jabber_socket object */
+function jabber_socket() {
+
+       this.is_connected       = false;
+       this.outstream          = "";
+       this.instream           = "";
+       this.buffer                     = "";
+       this.socket                     = "";
+
+}
+
+/** Connects to the jabber server */
+jabber_socket.prototype.connect = 
+       function( username, password, resource,  host, port, timeout ) {
+
+       var starttime = new Date().getTime();
+
+       // there has to be at least some kind of timeout
+       if( ! timeout || timeout < 100 ) { timeout = 1000; }
+
+       try {
+
+               this.xpcom_init( host, port );
+               this.tcp_send( "<stream:stream to='"+host
+                               +"' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>" );
+
+               if( !this.tcp_recv( timeout ) ) {  throw 1; }
+
+       } catch( E ) {
+               throw new oils_ex_transport( "Could not open a socket to the transport server\n" 
+                               + "Server: " + host + " Port: " + port  );
+       }
+
+       // Send the auth packet
+       this.tcp_send( "<iq id='123456789' type='set'><query xmlns='jabber:iq:auth'><username>" 
+                       + username + "</username><password>" + password + 
+                       "</password><resource>" + resource + "</resource></query></iq>" );
+
+       var cur = new Date().getTime();
+       var remaining = timeout - ( cur - starttime );
+       this.tcp_recv( remaining );
+
+       if( ! this.connected() ) {
+               throw new oils_ex_transport( "Connection to transport server timed out" );
+       }
+
+       return true;
+
+
+}
+
+
+/** Sets up all of the xpcom components */
+jabber_socket.prototype.xpcom_init = function( host, port ) {
+
+       var transportService =
+               Components.classes["@mozilla.org/network/socket-transport-service;1"]
+               .getService(Components.interfaces.nsISocketTransportService);
+
+       this.transport = transportService.createTransport( null, 0, host, port, null);
+
+       // ------------------------------------------------------------------
+       // Build the stream objects
+       // ------------------------------------------------------------------
+       this.outstream = this.transport.openOutputStream(0,0,0);
+       
+       var stream = this.transport.openInputStream(0,0,0);
+
+       this.instream = Components.classes["@mozilla.org/scriptableinputstream;1"]
+                       .createInstance(Components.interfaces.nsIScriptableInputStream);
+
+       this.instream.init(stream);
+
+}
+
+/** Send data to the TCP pipe */
+jabber_socket.prototype.tcp_send = function( data ) {
+       new Logger().transport( "Sending Data: \n" + data, Logger.INFO );
+       this.outstream.write(data,data.length);
+}
+
+
+/** Accepts data coming directly from the socket.  If we're not
+  * connected, we pass it off to procecc_connect().  Otherwise,
+  * this method adds the data to the local buffer.
+  */
+jabber_socket.prototype.process_data = function( data ) {
+
+       new Logger().transport( "Received TCP data: " + data, Logger.DEBUG );
+
+       if( ! this.connected() ) {
+               this.process_connect( data );
+               return;
+       } 
+
+       this.buffer += data;
+
+}
+
+/** Processes connect data to verify we are logged in correctly */
+jabber_socket.prototype.process_connect = function( data ) {
+
+       var reg = /type=["\']result["\']/;
+       var err = /error/;
+
+       if( reg.exec( data ) ) {
+               this.is_connected = true;
+       } else {
+               if( err.exec( data ) ) {
+                       //throw new oils_ex_transport( "Server returned: \n" + data );
+                       throw new oils_ex_jabber_auth( "Server returned: \n" + data );
+                       // Throw exception, return something...
+               }
+       }
+}
+
+/** Waits up to at most 'timeout' milliseconds for data to arrive 
+  * in the TCP buffer.  If there is at least one byte of data 
+  * in the buffer, then all of the data that is in the buffer is sent 
+  * to the process_data method for processing and the method returns.  
+  */
+jabber_socket.prototype.tcp_recv = function( timeout ) {
+
+       var count = this.instream.available();
+       var did_receive = false;
+
+       // ------------------------------------------------------------------
+       // If there is any data in the tcp buffer, process it and return
+       // ------------------------------------------------------------------
+       if( count > 0 ) { 
+
+               did_receive = true;
+               while( count > 0 ) { 
+                       this.process_data( this.instream.read( count ) );
+                       count = this.instream.available();
+               }
+
+       } else { 
+
+               // ------------------------------------------------------------------
+               // Do the timeout dance
+               // ------------------------------------------------------------------
+
+               // ------------------------------------------------------------------
+               // If there is no data in the buffer, wait up to timeout seconds
+               // for some data to arrive.  Once it arrives, suck it all out
+               // and send it on for processing
+               // ------------------------------------------------------------------
+
+               var now, then;
+               now = new Date().getTime();
+               then = now;
+
+               // ------------------------------------------------------------------
+               // Loop and poll for data every 50 ms.
+               // ------------------------------------------------------------------
+               while( ((now-then) <= timeout) && count <= 0 ) { 
+                       sleep(50);
+                       count = this.instream.available();
+                       now = new Date().getTime();
+               }
+
+               // ------------------------------------------------------------------
+               // if we finally get some data, process it.
+               // ------------------------------------------------------------------
+               if( count > 0 ) {
+
+                       did_receive = true;
+                       while( count > 0 ) { // pull in all of the data there is
+                               this.process_data( this.instream.read( count ) );
+                               count = this.instream.available();
+                       }
+               }
+       }
+
+       return did_receive;
+
+}
+
+/** If a message is already sitting in the queue, it is returned.  
+  * If not, this method waits till at most 'timeout' milliseconds 
+  * for a full jabber message to arrive and then returns that.
+  * If none ever arrives, returns null.
+  */
+jabber_socket.prototype.recv = function( timeout ) {
+
+       var now, then;
+       now = new Date().getTime();
+       then = now;
+
+       while( ((now-then) <= timeout) ) {
+               
+               if( this.buffer.length == 0 ) {
+                       if( ! this.tcp_recv( timeout ) ) {
+                               return null;
+                       }
+               }
+
+               //new Logger().transport( "\n\nTCP Buffer Before: \n" + this.buffer, Logger.DEBUG );
+
+               var buf = this.buffer;
+               this.buffer = "";
+
+               buf = buf.replace( /\n/g, '' ); // remove pesky newlines
+
+               var reg = /<message.*?>.*?<\/message>/;
+               var iqr = /<iq.*?>.*?<\/iq>/;
+               var out = reg.exec(buf);
+
+               if( out ) { 
+
+                       var msg_xml = out[0];
+                       this.buffer = buf.substring( msg_xml.length, buf.length );
+                       new Logger().transport( "Building Jabber message\n\n" + msg_xml, Logger.DEBUG );
+                       var jab_msg = new jabber_message().from_xml( msg_xml );
+                       if( jab_msg.is_error_msg ) {
+                               new Logger().transport( "Received Jabber error message \n\n" + msg_xml, Logger.ERROR );
+                       } 
+
+                       return jab_msg;
+
+
+               } else { 
+
+                       out = iqr.exec(buf);
+
+                       if( out ) {
+                               var msg_xml = out[0];
+                               this.buffer = buf.substring( msg_xml.length, buf.length );
+                               process_iq_data( msg_xml );
+                               return;
+
+                       }
+               }
+
+               now = new Date().getTime();
+       }
+
+       return null;
+}
+
+jabber_socket.prototype.process_iq_data = function( data ) {
+       new Logger().transport( "IQ Packet received... Not Implemented\n" + data, Logger.ERROR );
+}
+
+/** Disconnects from the jabber server and closes down shop */
+jabber_socket.prototype.disconnect = function() {
+       this.tcp_send( "</stream:stream>" );
+       this.instream.close();
+       this.outstream.close();
+}
+
+/** True if connected */
+jabber_socket.prototype.connected = function() {
+       return this.is_connected;
+}
+
+
+
+
+
diff --git a/src/javascript/opensrf_msg_stack.js b/src/javascript/opensrf_msg_stack.js
new file mode 100644 (file)
index 0000000..51d5fd8
--- /dev/null
@@ -0,0 +1,203 @@
+// -----------------------------------------------------------------------------
+// Message stack code.
+// -----------------------------------------------------------------------------
+
+
+
+// -----------------------------------------------------------------------------
+// These just have to be defined for the 'static' methods to work
+// -----------------------------------------------------------------------------
+function Transport() {}
+function Message() {}
+function Application() {}
+
+/** Transport handler.  
+  * Takes a transport_message as parameter
+  * Parses the incoming message data and builds one or more oilsMessage objects
+  * from the XML.  Each message is passed in turn to the Message.handler
+  * method.
+  */
+Transport.handler = function( msg ) {
+
+       if( msg.is_error_msg ) {
+               throw new oils_ex_session( "Receved error message from jabber server for recipient: " + msg.get_sender() );
+               return;
+       }
+
+       var remote_id   = msg.get_sender();
+       var session_id = msg.get_thread();
+       var body                        = msg.get_body();
+
+       var session             = AppSession.find_session( session_id );
+
+       if( ! session ) {
+               new Logger().debug( "No AppSession found with id: " + session_id );
+               return;
+       }
+
+       session.set_remote_id( remote_id );
+
+       var nodelist; // oilsMessage nodes
+
+
+       // -----------------------------------------------------------------------------
+       // Parse the incoming XML
+       // -----------------------------------------------------------------------------
+       try {
+
+               var doc = new DOMParser().parseFromString( body, "text/xml" );
+               nodelist = doc.documentElement.getElementsByTagName( "oils:domainObject" );
+
+               if( ! nodelist || nodelist.length < 1 ) {
+                       nodelist = doc.documentElement.getElementsByTagName( "domainObject" );
+                       if( ! nodelist || nodelist.length < 1 ) { throw 1; }
+               }
+
+       } catch( E ) {
+
+               var str = "Error parsing incoming message document";
+
+               if( E ) { throw new oils_ex_dom( str + "\n" + E.message + "\n" + body ); 
+               } else { throw new oils_ex_dom( str + "\n" + body ); }
+
+       }
+       
+       // -----------------------------------------------------------------------------
+       // Pass the messages up the chain.
+       // -----------------------------------------------------------------------------
+       try {
+
+               var i = 0;
+               var node = nodelist.item(i); // a single oilsMessage
+
+               while( node != null ) {
+
+                       if( node.getAttribute("name") != "oilsMessage" ) {
+                               node = nodelist.item(++i);
+                               continue;
+                       }
+                               
+                       var oils_msg = new oilsMessage().replaceNode( node );  
+
+
+                       // -----------------------------------------------------------------------------
+                       // this is really inefficient compared to the above line of code,
+                       // however, this resolves some namespace oddities in DOMParser - 
+                       // namely, DOMParser puts dummy namesapaces in "a0" when, I'm assuming, it 
+                       // can't find the definition for the namesapace included.
+                       // -----------------------------------------------------------------------------
+                       //      var oils_msg = oilsMessage.newFromXML( new XMLSerializer().serializeToString( node ) );
+
+                       new Logger().transport( "Transport passing up:\n" + oils_msg.toString(true), Logger.INFO );
+
+                       // Pass the message off to the message layer
+                       Message.handler( session, oils_msg );
+                       node = nodelist.item(++i);
+               }
+
+       } catch( E ) {
+
+               var str = "Processing Error";
+
+               if( E ) { throw new oils_ex_session( str + "\n" + E.message + "\n" + body ); } 
+               else { throw new oils_ex_session( str + "\n" + body ); }
+       }
+}
+
+/** Checks to see what type of message has arrived.  If it is a 'STATUS' message,
+  * the appropriate transport layer actions are taken.  Otherwise (RESULT), the
+  * message is passed to the Application.handler method.
+  */
+Message.handler = function( session, msg ) {
+
+       var msg_type                                    = msg.getType();
+       var domain_object_payload       = msg.getPayload();
+       var tt                                                  = msg.getThreadTrace();
+
+       var code = domain_object_payload.getStatusCode();
+
+       new Logger().debug( "Message.handler received " + msg_type + " from " +
+                       session.get_remote_id() + " with thread_trace " + tt + " and status " + code, Logger.INFO );
+       new Logger().debug( "Message.handler received:\n" + domain_object_payload.toString(), Logger.DEBUG );
+
+       if( msg_type == oilsMessage.STATUS ) {
+
+               switch( code ) {
+
+                       case  oilsResponse.STATUS_OK + "": {
+                               session.set_state( AppSession.CONNECTED );
+                               new Logger().debug( " * Connected Successfully: " + tt, Logger.INFO );
+                               return;
+                       }
+
+                       case oilsResponse.STATUS_TIMEOUT + "": {
+                               return Message.reset_session( session, tt, "Disconnected because of timeout" );
+                       }
+
+                       case oilsResponse.STATUS_REDIRECTED + "": {
+                               return Message.reset_session( session, tt, "Disconnected because of redirect" );
+                       }
+
+                       case oilsResponse.STATUS_EXPFAILED + "": {
+                               return Message.reset_session( session, tt, "Disconnected because of mangled session" );
+                       }
+
+                       case oilsResponse.STATUS_NOTALLOWED + "": {
+                               new Logger().debug( "Method Not Allowed", Logger.ERROR );
+                               session.destroy();
+                               break; // we want the exception to be thrown below
+                       }
+
+                       case oilsResponse.STATUS_CONTINUE +"": {
+                               return;
+                       }
+
+                       case oilsResponse.STATUS_COMPLETE + "": {
+                               var req = session.get_request(tt);
+                               if( req ) { req.set_complete(); }
+                               new Logger().debug( " * Request completed: " + tt, Logger.INFO );
+                               return;
+                       }
+
+                       default: { break; } 
+               }
+
+       }
+
+       // throw any exceptions received from the server
+       if( domain_object_payload instanceof oilsException ) {
+               throw new oils_ex_session( domain_object_payload.getStatus() );
+       }
+
+       new Logger().debug( "Message Layer passing up:\n" + domain_object_payload.toString(), Logger.DEBUG );
+
+       Application.handler( session, domain_object_payload, tt );
+
+}
+
+/** Utility method for cleaning up a session.  Sets state to disconnected.
+  * resets the remoted_id, pushes the current app_request onto the resend
+  * queue. Logs a message.
+  */
+Message.reset_session = function( session, thread_trace, message ) {
+       session.set_state( AppSession.DISCONNECTED );
+       session.reset_remote();
+       var req = session.get_request( thread_trace );
+       if( req && !req.complete ) { session.push_resend( req ); }
+       new Logger().debug( " * " + message + " : " + thread_trace, Logger.INFO );
+}
+
+
+/** Pushes all incoming messages onto the session message queue. **/
+Application.handler = function( session, domain_object_payload, thread_trace ) {
+
+       new Logger().debug( "Application Pushing onto queue: " 
+                       + thread_trace + "\n" + domain_object_payload.toString(), Logger.DEBUG );
+
+       session.push_queue( domain_object_payload, thread_trace );
+}
+
+
+       
+
+       
diff --git a/src/javascript/opensrf_transport.js b/src/javascript/opensrf_transport.js
new file mode 100644 (file)
index 0000000..a0d846d
--- /dev/null
@@ -0,0 +1,64 @@
+/** @file oils_transport.js
+  * Houses the top level transport 'abstract' classes
+  * You can think of this like a header file which provides the 
+  * interface that any transport code must implement
+  */
+
+
+// ------------------------------------------------------------------
+// TRANSPORT_CONNECTION
+
+/** Constructor */
+function transport_connection( username, password, resource ) { }
+
+/** Connects to the transport host */
+transport_connection.prototype.connect = function(  host, /*int*/ port, /*int*/ timeout ) {}
+
+/** Sends a new message to recipient, with session_id and body */
+transport_connection.prototype.send = function( recipient, session_id, body ) {}
+
+
+/** Returns a transport_message.  This function will return 
+  * immediately if there is a message available.  Otherwise, it will
+  * wait at most 'timeout' seconds for one to arrive.  Returns
+  * null if a message does not arrivae in time.
+
+  * 'timeout' is specified in milliseconds
+  */
+transport_connection.prototype.recv = function( /*int*/ timeout ) {}
+
+/** This method calls recv and then passes the contents on to the
+  * message processing stack.
+  */
+transport_connection.prototype.process_msg = function( /*int*/ timeout ) {
+       var msg = this.recv( timeout );
+       if( msg ) { Transport.handler( msg ); }
+}
+
+/** Disconnects from the transpot host */
+transport_connection.prototype.disconnect = function() {}
+
+/** Returns true if this connection instance is currently connected
+  * to the transport host.
+  */
+transport_connection.prototype.connected = function() {}
+
+
+
+// ------------------------------------------------------------------
+// TRANSPORT_MESSAGE
+       
+
+/** Constructor */
+function transport_message( sender, recipient, session_id, body ) {}
+
+/** Returns the sender of the message */
+transport_message.prototype.get_sender = function() {}
+
+/** Returns the session id */
+transport_message.prototype.get_session = function() {}
+
+/** Returns the message body */
+transport_message.prototype.get_body = function() {}
+       
+
diff --git a/src/javascript/opensrf_utils.js b/src/javascript/opensrf_utils.js
new file mode 100644 (file)
index 0000000..d348fd5
--- /dev/null
@@ -0,0 +1,117 @@
+// ------------------------------------------------------------------
+// Houses utility functions
+// ------------------------------------------------------------------
+
+/** Prints to console.  If alert_bool = true, displays a popup as well */
+function _debug( message, alert_bool ) {
+
+       dump( "\n" + new Date() + "\n--------------------------------\n" + 
+                       message + "\n-----------------------------------\n" );
+       if( alert_bool == true ) { alert( message ) };
+
+}
+
+
+/** Provides millisec sleep times, enjoy... */
+function sleep(gap){ 
+
+       var threadService = Components.classes["@mozilla.org/thread;1"].
+               getService(Components.interfaces.nsIThread);
+
+       var th = threadService.currentThread;
+       th.sleep(gap);
+}
+
+
+
+/** Top level exception classe */
+function oils_ex() {}
+
+/** Initializes an exception */
+oils_ex.prototype.init_ex = function( name, message ) {
+       if( !(name && message) ) { return; }
+       this.name = name;
+       this.message = name + " : " + message;
+       new Logger().debug( "***\n" + this.message + "\n***", Logger.ERROR );
+}
+
+/** Returns a string representation of an exception */
+oils_ex.prototype.toString = function() {
+       return this.message;
+}
+
+
+oils_ex_transport.prototype = new oils_ex();
+oils_ex_transport.prototype.constructor = oils_ex_transport;
+oils_ex_transport.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown when the transport connection has problems*/
+function oils_ex_transport( message ) {
+       this.init_ex( "Transport Exception", message );
+}
+
+
+oils_ex_transport_auth.prototype = new oils_ex_transport(); 
+oils_ex_transport_auth.prototype.constructor = oils_ex_transport_auth;
+oils_ex_transport_auth.baseClass = oils_ex_transport.prototype.constructor;
+
+/** Thrown when the initial transport connection fails */
+function oils_ex_transport_auth( message ) {
+       this.init_ex( "Transport Authentication Error", message );
+}
+
+oils_ex_dom.prototype = new oils_ex(); 
+oils_ex_dom.prototype.constructor = oils_ex_dom;
+oils_ex_dom.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown when there is an XML problem */
+function oils_ex_dom( message ) {
+       this.init_ex( "DOM Error", message );
+}
+
+oils_ex_message.prototype = new oils_ex();
+oils_ex_message.prototype.constructor = oils_ex_message;
+oils_ex_message.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown when there is a message problem */
+function oils_ex_message( message ) {
+       this.init_ex( "OILS Message Layer Error", message ) ;
+}
+
+oils_ex_config.prototype = new oils_ex();
+oils_ex_config.prototype.constructor = oils_ex_config;
+oils_ex_config.prototype.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown when values cannot be retrieved from the config file */
+function oils_ex_config( message ) {
+       this.init_ex( "OILS Config Exception", message );
+}
+
+oils_ex_logger.prototype = new oils_ex();
+oils_ex_logger.prototype.constructor = oils_ex_logger;
+oils_ex_logger.prototype.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown where there are logging problems */
+function oils_ex_logger( message ) {
+       this.init_ex( "OILS Logger Exception", message );
+}
+
+
+oils_ex_args.prototype = new oils_ex();
+oils_ex_args.prototype.constructor = oils_ex_args;
+oils_ex_args.prototype.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown when when a method does not receive all of the required arguments */
+function oils_ex_args( message ) {
+       this.init_ex( "Method Argument Exception", message );
+}
+
+
+oils_ex_session.prototype = new oils_ex();
+oils_ex_session.prototype.constructor = oils_ex_session;
+oils_ex_session.prototype.baseClass = oils_ex.prototype.constructor;
+
+/** Thrown where there is a session processing problem */
+function oils_ex_session( message ) {
+       this.init_ex( "Session Exception", message );
+}
diff --git a/src/libtransport/Makefile b/src/libtransport/Makefile
new file mode 100644 (file)
index 0000000..9a5c0b5
--- /dev/null
@@ -0,0 +1,31 @@
+# set this shell variable prior to calling make to run with malloc_check enabled
+#MALLOC_CHECK_=1 # XXX debug only
+
+CC = gcc
+LIB_DIR=../../lib
+CC_OPTS = -Wall -O2 -I /usr/include/libxml2 -I /usr/include/libxml2/libxml -I ../../include -I ../../../../cc/libxml2-2.6.16
+LD_OPTS = -lxml2
+EXE_LD_OPTS = -L $(LIB_DIR) -lxml2 -ltransport 
+LIB_SOURCES = generic_utils.c transport_socket.c transport_session.c transport_message.c transport_client.c
+
+TARGETS=generic_utils.o transport_socket.o transport_message.o transport_session.o transport_client.o 
+
+all: router basic_client
+
+basic_client: lib
+       $(CC) $(CC_OPTS) $(EXE_LD_OPTS) basic_client.c -o $@
+
+# --- Libs -----------------------------------------------
+       
+lib: 
+       $(CC) -c $(CC_OPTS)     $(LIB_SOURCES)  
+       $(CC) -shared -W1 $(LD_OPTS) $(TARGETS) -o $(LIB_DIR)/libtransport.so
+
+
+# The router is compiled as a static binary because of some 
+# necessary #defines that would break the library
+router: 
+       $(CC) $(LD_OPTS) -D_ROUTER $(CC_OPTS)   $(LIB_SOURCES) transport_router.c -o $@ 
+
+clean:
+       /bin/rm -f *.o ../../lib/libtransport.so router basic_client
diff --git a/src/libtransport/basic_client.c b/src/libtransport/basic_client.c
new file mode 100644 (file)
index 0000000..61cb9e6
--- /dev/null
@@ -0,0 +1,78 @@
+#include "transport_client.h"
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+/**
+  * Simple jabber client
+  */
+
+
+
+
+/* connects and registers with the router */
+int main( int argc, char** argv ) {
+
+       if( argc < 5 ) {
+               fatal_handler( "Usage: %s <username> <host> <resource> <recipient> \n", argv[0] );
+               return 99;
+       }
+
+       transport_message* send;
+       transport_client* client = client_init( argv[2], 5222 );
+
+       // try to connect, allow 15 second connect timeout 
+       if( client_connect( client, argv[1], "asdfjkjk", argv[3], 15 ) ) 
+               info_handler("Connected...\n");
+        else  
+               fatal_handler( "NOT Connected...\n" ); 
+       
+       if( fork() ) {
+
+               fprintf(stderr, "Listener: %d\n", getpid() );   
+               char buf[300];
+               memset(buf, 0, 300);
+               printf("=> ");
+
+               while( fgets( buf, 299, stdin) ) {
+
+                       // remove newline
+                       buf[strlen(buf)-1] = '\0';
+
+                       if( strcmp(buf, "exit")==0) { 
+                               client_free( client );  
+                               break; 
+                       }
+
+                       send = message_init( buf, "", "123454321", argv[4], NULL );
+                       client_send_message( client, send );
+                       message_free( send );
+                       printf("\n=> ");
+                       memset(buf, 0, 300);
+               }
+               return 0;
+
+       } else {
+
+               fprintf(stderr, "Sender: %d\n", getpid() );     
+
+               transport_message* recv;
+               while( (recv=client_recv( client, -1)) ) {
+                       if( recv->is_error )
+                               fprintf( stderr, "\nReceived Error\t: ------------------\nFrom:\t\t"
+                                       "%s\nRouterFrom:\t%s\nBody:\t\t%s\nType %s\nCode %d\n=> ", recv->sender, recv->router_from, recv->body, recv->error_type, recv->error_code );
+                       else
+                               fprintf( stderr, "\nReceived\t: ------------------\nFrom:\t\t"
+                                       "%s\nRouterFrom:\t%s\nBody:\t\t%s\n=> ", recv->sender, recv->router_from, recv->body );
+
+                       message_free( recv );
+               }
+
+       }
+       return 0;
+
+}
+
+
+
+
diff --git a/src/libtransport/generic_utils.c b/src/libtransport/generic_utils.c
new file mode 100644 (file)
index 0000000..ba0b284
--- /dev/null
@@ -0,0 +1,392 @@
+#include "generic_utils.h"
+#include <stdio.h>
+#include "pthread.h"
+
+int _init_log();
+
+int balance = 0;
+
+#define LOG_ERROR 1
+#define LOG_WARNING 2
+#define LOG_INFO 3
+
+void get_timestamp( char buf_25chars[]) {
+       time_t epoch = time(NULL);      
+       char* localtime = strdup( ctime( &epoch ) );
+       strcpy( buf_25chars, localtime );
+       buf_25chars[ strlen(localtime)-1] = '\0'; // remove newline
+       free(localtime);
+}
+
+
+inline void* safe_malloc( int size ) {
+       void* ptr = (void*) malloc( size );
+       if( ptr == NULL ) 
+               fatal_handler("safe_malloc(): Out of Memory" );
+       memset( ptr, 0, size );
+       return ptr;
+}
+
+// ---------------------------------------------------------------------------------
+// Here we define how we want to handle various error levels.
+// ---------------------------------------------------------------------------------
+
+
+static FILE* log_file = NULL;
+static int log_level = -1;
+pthread_mutex_t mutex;
+
+void log_free() { if( log_file != NULL ) fclose(log_file ); }
+
+void fatal_handler( char* msg, ... ) {
+               
+       char buf[25];
+       memset( buf, 0, 25 );
+       get_timestamp( buf );
+       pid_t  pid = getpid();
+       va_list args;
+
+       if( _init_log() ) {
+
+               if( log_level < LOG_ERROR )
+                       return;
+
+               pthread_mutex_lock( &(mutex) );
+               fprintf( log_file, "[%s %d] [%s] ", buf, pid, "ERR " );
+       
+               va_start(args, msg);
+               vfprintf(log_file, msg, args);
+               va_end(args);
+       
+               fprintf(log_file, "\n");
+               fflush( log_file );
+               pthread_mutex_unlock( &(mutex) );
+
+       }
+       
+       /* also log to stderr  for ERRORS*/
+       fprintf( stderr, "[%s %d] [%s] ", buf, pid, "ERR " );
+       va_start(args, msg);
+       vfprintf(stderr, msg, args);
+       va_end(args);
+       fprintf( stderr, "\n" );
+
+       exit(99);
+}
+
+void warning_handler( char* msg, ... ) {
+
+       char buf[25];
+       memset( buf, 0, 25 );
+       get_timestamp( buf );
+       pid_t  pid = getpid();
+       va_list args;
+       
+       if( _init_log() ) {
+
+               if( log_level < LOG_WARNING )
+                       return;
+
+               pthread_mutex_lock( &(mutex) );
+               fprintf( log_file, "[%s %d] [%s] ", buf, pid, "WARN" );
+       
+               va_start(args, msg);
+               vfprintf(log_file, msg, args);
+               va_end(args);
+       
+               fprintf(log_file, "\n");
+               fflush( log_file );
+               pthread_mutex_unlock( &(mutex) );
+
+       } else {
+
+               fprintf( stderr, "[%s %d] [%s] ", buf, pid, "WARN" );
+               va_start(args, msg);
+               vfprintf(stderr, msg, args);
+               va_end(args);
+               fprintf( stderr, "\n" );
+       }
+
+}
+
+void info_handler( char* msg, ... ) {
+
+       char buf[25];
+       memset( buf, 0, 25 );
+       get_timestamp( buf );
+       pid_t  pid = getpid();
+       va_list args;
+
+       if( _init_log() ) {
+
+               if( log_level < LOG_INFO )
+                       return;
+               pthread_mutex_lock( &(mutex) );
+               fprintf( log_file, "[%s %d] [%s] ", buf, pid, "INFO" );
+
+               va_start(args, msg);
+               vfprintf(log_file, msg, args);
+               va_end(args);
+       
+               fprintf(log_file, "\n");
+               fflush( log_file );
+               pthread_mutex_unlock( &(mutex) );
+
+       } else {
+
+               fprintf( stderr, "[%s %d] [%s] ", buf, pid, "INFO" );
+               va_start(args, msg);
+               vfprintf(stderr, msg, args);
+               va_end(args);
+               fprintf( stderr, "\n" );
+               fflush(stderr);
+
+       }
+}
+
+
+int _init_log() {
+
+       if( log_level != -1 )
+               return 1;
+
+
+       pthread_mutex_init( &(mutex), NULL );
+
+       /* load the log level setting if we haven't already */
+
+       if( conf_reader == NULL ) {
+               return 0;
+               //fprintf( stderr, "No config file specified" );
+       }
+
+       char* log_level_str = config_value( "//router/log/level");
+       if( log_level_str == NULL ) {
+       //      fprintf( stderr, "No log level specified" );
+               return 0;
+       }
+       log_level = atoi(log_level_str);
+       free(log_level_str);
+
+       /* see if we have a log file yet */
+       char* f = config_value("//router/log/file");
+
+       if( f == NULL ) {
+               // fprintf( stderr, "No log file specified" );
+               return 0;
+       }
+
+       log_file = fopen( f, "a" );
+       if( log_file == NULL ) {
+               fprintf( stderr, "Unable to open log file %s for appending\n", f );
+               return 0;
+       }
+       free(f);
+       return 1;
+                               
+}
+
+
+
+// ---------------------------------------------------------------------------------
+// Flesh out a ubiqitous growing string buffer
+// ---------------------------------------------------------------------------------
+
+growing_buffer* buffer_init(int num_initial_bytes) {
+
+       if( num_initial_bytes > BUFFER_MAX_SIZE ) {
+               return NULL;
+       }
+
+
+       size_t len = sizeof(growing_buffer);
+
+       growing_buffer* gb = (growing_buffer*) safe_malloc(len);
+
+       gb->n_used = 0;/* nothing stored so far */
+       gb->size = num_initial_bytes;
+       gb->buf = (char *) safe_malloc(gb->size + 1);
+
+       return gb;
+}
+
+int buffer_add(growing_buffer* gb, char* data) {
+
+
+       if( ! gb || ! data  ) { return 0; }
+       int data_len = strlen( data );
+
+       if( data_len == 0 ) { return 0; }
+       int total_len = data_len + gb->n_used;
+
+       while( total_len >= gb->size ) {
+               gb->size *= 2;
+       }
+
+       if( gb->size > BUFFER_MAX_SIZE ) {
+               warning_handler( "Buffer reached MAX_SIZE of %d", BUFFER_MAX_SIZE );
+               buffer_free( gb );
+               return 0;
+       }
+
+       char* new_data = (char*) safe_malloc( gb->size );
+
+       strcpy( new_data, gb->buf );
+       free( gb->buf );
+       gb->buf = new_data;
+
+       strcat( gb->buf, data );
+       gb->n_used = total_len;
+       return total_len;
+}
+
+
+int buffer_reset( growing_buffer *gb){
+       if( gb == NULL ) { return 0; }
+       if( gb->buf == NULL ) { return 0; }
+       memset( gb->buf, 0, gb->size );
+       gb->n_used = 0;
+       return 1;
+}
+
+int buffer_free( growing_buffer* gb ) {
+       if( gb == NULL ) 
+               return 0;
+       free( gb->buf );
+       free( gb );
+       return 1;
+}
+
+char* buffer_data( growing_buffer *gb) {
+       return strdup( gb->buf );
+}
+
+
+
+
+
+// ---------------------------------------------------------------------------------
+// Config module
+// ---------------------------------------------------------------------------------
+
+
+// ---------------------------------------------------------------------------------
+// Allocate and build the conf_reader.  This only has to happen once in a given
+// system.  Repeated calls are ignored.
+// ---------------------------------------------------------------------------------
+void config_reader_init( char* config_file ) {
+       if( conf_reader == NULL ) {
+
+               if( config_file == NULL || strlen(config_file) == 0 ) {
+                       fatal_handler( "config_reader_init(): No config file specified" );
+                       return;
+               }
+
+               size_t len = sizeof( config_reader );
+               conf_reader = (config_reader*) safe_malloc( len );
+
+               conf_reader->config_doc = xmlParseFile( config_file ); 
+               conf_reader->xpathCx = xmlXPathNewContext( conf_reader->config_doc );
+               if( conf_reader->xpathCx == NULL ) {
+                       fatal_handler( "config_reader_init(): Unable to create xpath context");
+                       return;
+               }
+       }
+}
+
+char* config_value( const char* xp_query, ... ) {
+
+       if( conf_reader == NULL || xp_query == NULL ) {
+               fatal_handler( "config_value(): NULL param(s)" );
+               return NULL;
+       }
+
+       int slen = strlen(xp_query) + 512;/* this is unsafe ... */
+       char xpath_query[ slen ]; 
+       memset( xpath_query, 0, slen );
+       va_list va_args;
+       va_start(va_args, xp_query);
+       vsprintf(xpath_query, xp_query, va_args);
+       va_end(va_args);
+
+
+       char* val;
+       int len = strlen(xpath_query) + 100;
+       char alert_buffer[len];
+       memset( alert_buffer, 0, len );
+
+       // build the xpath object
+       xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression( BAD_CAST xpath_query, conf_reader->xpathCx );
+
+       if( xpathObj == NULL ) {
+               sprintf( alert_buffer, "Could not build xpath object: %s", xpath_query );
+               fatal_handler( alert_buffer );
+               return NULL;
+       }
+
+
+       if( xpathObj->type == XPATH_NODESET ) {
+
+               // ----------------------------------------------------------------------------
+               // Grab nodeset from xpath query, then first node, then first text node and 
+               // finaly the text node's value
+               // ----------------------------------------------------------------------------
+               xmlNodeSet* node_list = xpathObj->nodesetval;
+               if( node_list == NULL ) {
+                       sprintf( alert_buffer, "Could not build xpath object: %s", xpath_query );
+                       warning_handler(alert_buffer);
+                       return NULL;
+               }
+
+               if( node_list->nodeNr == 0 ) {
+                       sprintf( alert_buffer, "Config XPATH query  returned 0 results: %s", xpath_query );
+                       warning_handler(alert_buffer);
+                       return NULL;
+               }
+
+
+               xmlNodePtr element_node = *(node_list)->nodeTab;
+               if( element_node == NULL ) {
+                       sprintf( alert_buffer, "Config XPATH query  returned 0 results: %s", xpath_query );
+                       warning_handler(alert_buffer);
+                       return NULL;
+               }
+
+               xmlNodePtr text_node = element_node->children;
+               if( text_node == NULL ) {
+                       sprintf( alert_buffer, "Config variable has no value: %s", xpath_query );
+                       warning_handler(alert_buffer);
+                       return NULL;
+               }
+
+               val = text_node->content;
+               if( val == NULL ) {
+                       sprintf( alert_buffer, "Config variable has no value: %s", xpath_query );
+                       warning_handler(alert_buffer);
+                       return NULL;
+               }
+
+
+       } else { 
+               sprintf( alert_buffer, "Xpath evaluation failed: %s", xpath_query );
+               warning_handler(alert_buffer);
+               return NULL;
+       }
+
+       char* value = strdup(val);
+       if( value == NULL ) { fatal_handler( "config_value(): Out of Memory!" ); }
+
+       // Free XPATH structures
+       if( xpathObj != NULL ) xmlXPathFreeObject( xpathObj );
+
+       return value;
+}
+
+
+void config_reader_free() {
+       if( conf_reader == NULL ) { return; }
+       xmlXPathFreeContext( conf_reader->xpathCx );
+       xmlFreeDoc( conf_reader->config_doc );
+       free( conf_reader );
+       conf_reader = NULL;
+}
diff --git a/src/libtransport/transport_client.c b/src/libtransport/transport_client.c
new file mode 100644 (file)
index 0000000..40b2c36
--- /dev/null
@@ -0,0 +1,212 @@
+#include "transport_client.h"
+
+
+//int main( int argc, char** argv );
+
+/*
+int main( int argc, char** argv ) {
+
+       transport_message* recv;
+       transport_message* send;
+
+       transport_client* client = client_init( "spacely.georgialibraries.org", 5222 );
+
+       // try to connect, allow 15 second connect timeout 
+       if( client_connect( client, "admin", "asdfjkjk", "system", 15 ) ) {
+               printf("Connected...\n");
+       } else { 
+               printf( "NOT Connected...\n" ); exit(99); 
+       }
+
+       while( (recv = client_recv( client, -1 )) ) {
+
+               if( recv->body ) {
+                       int len = strlen(recv->body);
+                       char buf[len + 20];
+                       memset( buf, 0, len + 20); 
+                       sprintf( buf, "Echoing...%s", recv->body );
+                       send = message_init( buf, "Echoing Stuff", "12345", recv->sender, "" );
+               } else {
+                       send = message_init( " * ECHOING * ", "Echoing Stuff", "12345", recv->sender, "" );
+               }
+
+               if( send == NULL ) { printf("something's wrong"); }
+               client_send_message( client, send );
+                               
+               message_free( send );
+               message_free( recv );
+       }
+
+       printf( "ended recv loop\n" );
+
+       return 0;
+
+}
+*/
+
+
+transport_client* client_init( char* server, int port ) {
+
+       if(server == NULL) return NULL;
+
+       /* build and clear the client object */
+       size_t c_size = sizeof( transport_client);
+       transport_client* client = (transport_client*) safe_malloc( c_size );
+
+       /* build and clear the message list */
+       size_t l_size = sizeof( transport_message_list );
+       client->m_list = (transport_message_list*) safe_malloc( l_size );
+
+       client->m_list->type = MESSAGE_LIST_HEAD;
+       client->session = init_transport( server, port, client );
+
+
+       if(client->session == NULL) {
+               fatal_handler( "client_init(): Out of Memory"); 
+               return NULL;
+       }
+       client->session->message_callback = client_message_handler;
+
+       return client;
+}
+
+int client_connect( transport_client* client, 
+               char* username, char* password, char* resource, int connect_timeout ) {
+       if(client == NULL) return 0; 
+       return session_connect( client->session, username, password, resource, connect_timeout );
+}
+
+int client_disconnect( transport_client* client ) {
+       if( client == NULL ) { return 0; }
+       return session_disconnect( client->session );
+}
+
+int client_connected( transport_client* client ) {
+       if(client == NULL) return 0;
+       return client->session->state_machine->connected;
+}
+
+int client_send_message( transport_client* client, transport_message* msg ) {
+       if(client == NULL) return 0;
+       return session_send_msg( client->session, msg );
+}
+
+
+transport_message* client_recv( transport_client* client, int timeout ) {
+       if( client == NULL ) { return NULL; }
+
+       transport_message_node* node;
+       transport_message* msg;
+
+
+       /* see if there are any message in the messages queue */
+       if( client->m_list->next != NULL ) {
+               /* pop off the first one... */
+               node = client->m_list->next;
+               client->m_list->next = node->next;
+               msg = node->message;
+               free( node );
+               return msg;
+       }
+
+       if( timeout == -1 ) {  /* wait potentially forever for data to arrive */
+
+               while( client->m_list->next == NULL ) {
+                       if( ! session_wait( client->session, -1 ) ) {
+                               return NULL;
+                       }
+               }
+
+       } else { /* wait at most timeout seconds */
+
+       
+               /* if not, loop up to 'timeout' seconds waiting for data to arrive */
+               time_t start = time(NULL);      
+               time_t remaining = (time_t) timeout;
+
+               int counter = 0;
+
+               int wait_ret;
+               while( client->m_list->next == NULL && remaining >= 0 ) {
+
+                       if( ! (wait_ret= session_wait( client->session, remaining)) ) 
+                               return NULL;
+
+                       ++counter;
+
+#ifdef _ROUTER
+                       // session_wait returns -1 if there is no more data and we're a router
+                       if( remaining == 0 && wait_ret == -1 ) {
+                               break;
+                       }
+#else
+                       if( remaining == 0 ) // or infinite loop
+                               break;
+#endif
+
+                       remaining -= (int) (time(NULL) - start);
+               }
+
+               info_handler("It took %d reads to grab this messag", counter);
+       }
+
+       /* again, see if there are any messages in the message queue */
+       if( client->m_list->next != NULL ) {
+               /* pop off the first one... */
+               node = client->m_list->next;
+               client->m_list->next = node->next;
+               msg = node->message;
+               free( node );
+               return msg;
+       } else {
+               return NULL;
+       }
+}
+
+/* throw the message into the message queue */
+void client_message_handler( void* client, transport_message* msg ){
+
+       if(client == NULL) return;
+       if(msg == NULL) return; 
+
+       transport_client* cli = (transport_client*) client;
+
+       size_t len = sizeof(transport_message_node);
+       transport_message_node* node = 
+               (transport_message_node*) safe_malloc(len);
+       node->type = MESSAGE_LIST_ITEM;
+       node->message = msg;
+
+
+       /* find the last node and put this onto the end */
+       transport_message_node* tail = cli->m_list;
+       transport_message_node* current = tail->next;
+
+       while( current != NULL ) {
+               tail = current;
+               current = current->next;
+       }
+       tail->next = node;
+}
+
+
+int client_free( transport_client* client ){
+       if(client == NULL) return 0; 
+
+       session_free( client->session );
+       transport_message_node* current = client->m_list->next;
+       transport_message_node* next;
+
+       /* deallocate the list of messages */
+       while( current != NULL ) {
+               next = current->next;
+               message_free( current->message );
+               free(current);
+               current = next;
+       }
+
+       free( client->m_list );
+       free( client );
+       return 1;
+}
+
diff --git a/src/libtransport/transport_message.c b/src/libtransport/transport_message.c
new file mode 100644 (file)
index 0000000..78ebb1b
--- /dev/null
@@ -0,0 +1,230 @@
+#include "transport_message.h"
+
+
+// ---------------------------------------------------------------------------------
+// Allocates and initializes a new transport_message
+// ---------------------------------------------------------------------------------
+transport_message* message_init( char* body, 
+               char* subject, char* thread, char* recipient, char* sender ) {
+
+       transport_message* msg = 
+               (transport_message*) safe_malloc( sizeof(transport_message) );
+
+       if( body                                        == NULL ) { body                        = ""; }
+       if( thread                              == NULL ) { thread              = ""; }
+       if( subject                             == NULL ) { subject             = ""; }
+       if( sender                              == NULL ) { sender              = ""; }
+       if( recipient                   ==      NULL ) { recipient      = ""; }
+
+       msg->body                               = strdup(body);
+       msg->thread                             = strdup(thread);
+       msg->subject                    = strdup(subject);
+       msg->recipient                  = strdup(recipient);
+       msg->sender                             = strdup(sender);
+
+       if(     msg->body               == NULL || msg->thread                          == NULL ||
+                       msg->subject    == NULL || msg->recipient                       == NULL ||
+                       msg->sender             == NULL ) {
+
+               fatal_handler( "message_init(): Out of Memory" );
+               return NULL;
+       }
+
+       return msg;
+}
+
+void message_set_router_info( transport_message* msg, char* router_from,
+               char* router_to, char* router_class, char* router_command, int broadcast_enabled ) {
+
+       msg->router_from                = strdup(router_from);
+       msg->router_to                  = strdup(router_to);
+       msg->router_class               = strdup(router_class);
+       msg->router_command     = strdup(router_command);
+       msg->broadcast = broadcast_enabled;
+
+       if( msg->router_from == NULL || msg->router_to == NULL ||
+                       msg->router_class == NULL || msg->router_command == NULL ) 
+               fatal_handler( "message_set_router_info(): Out of Memory" );
+
+       return;
+}
+
+
+
+/* encodes the message for traversal */
+int message_prepare_xml( transport_message* msg ) {
+       if( msg->msg_xml != NULL ) { return 1; }
+       msg->msg_xml = message_to_xml( msg );
+       return 1;
+}
+
+
+// ---------------------------------------------------------------------------------
+//
+// ---------------------------------------------------------------------------------
+int message_free( transport_message* msg ){
+       if( msg == NULL ) { return 0; }
+
+       free(msg->body); 
+       free(msg->thread);
+       free(msg->subject);
+       free(msg->recipient);
+       free(msg->sender);
+       free(msg->router_from);
+       free(msg->router_to);
+       free(msg->router_class);
+       free(msg->router_command);
+       if( msg->error_type != NULL ) free(msg->error_type);
+       if( msg->msg_xml != NULL ) free(msg->msg_xml);
+       free(msg);
+       return 1;
+}
+       
+// ---------------------------------------------------------------------------------
+// Allocates a char* holding the XML representation of this jabber message
+// ---------------------------------------------------------------------------------
+char* message_to_xml( const transport_message* msg ) {
+
+       int                     bufsize;
+       xmlChar*                xmlbuf;
+       char*                   encoded_body;
+
+       xmlNodePtr      message_node;
+       xmlNodePtr      body_node;
+       xmlNodePtr      thread_node;
+       xmlNodePtr      subject_node;
+       xmlNodePtr      error_node;
+       
+       xmlDocPtr       doc;
+
+       xmlKeepBlanksDefault(0);
+
+       if( ! msg ) { 
+               warning_handler( "Passing NULL message to message_to_xml()"); 
+               return 0; 
+       }
+
+       doc = xmlReadDoc( BAD_CAST "<message/>", NULL, NULL, XML_PARSE_NSCLEAN );
+       message_node = xmlDocGetRootElement(doc);
+
+       if( msg->is_error ) {
+               error_node = xmlNewChild(message_node, NULL, BAD_CAST "error" , NULL );
+               xmlAddChild( message_node, error_node );
+               xmlNewProp( error_node, BAD_CAST "type", BAD_CAST msg->error_type );
+               char code_buf[16];
+               memset( code_buf, 0, 16);
+               sprintf(code_buf, "%d", msg->error_code );
+               xmlNewProp( error_node, BAD_CAST "code", BAD_CAST code_buf  );
+       }
+       
+
+       /* set from and to */
+       xmlNewProp( message_node, BAD_CAST "to", BAD_CAST msg->recipient );
+       xmlNewProp( message_node, BAD_CAST "from", BAD_CAST msg->sender );
+       xmlNewProp( message_node, BAD_CAST "router_from", BAD_CAST msg->router_from );
+       xmlNewProp( message_node, BAD_CAST "router_to", BAD_CAST msg->router_to );
+       xmlNewProp( message_node, BAD_CAST "router_class", BAD_CAST msg->router_class );
+       xmlNewProp( message_node, BAD_CAST "router_command", BAD_CAST msg->router_command );
+
+       if( msg->broadcast )
+               xmlNewProp( message_node, BAD_CAST "broadcast", BAD_CAST "1" );
+
+       /* Now add nodes where appropriate */
+       char* body                              = msg->body;
+       char* subject                   = msg->subject;
+       char* thread                    = msg->thread; 
+
+       if( thread && strlen(thread) > 0 ) {
+               thread_node = xmlNewChild(message_node, NULL, (xmlChar*) "thread", (xmlChar*) thread );
+               xmlAddChild( message_node, thread_node ); 
+       }
+
+       if( subject && strlen(subject) > 0 ) {
+               subject_node = xmlNewChild(message_node, NULL, (xmlChar*) "subject", (xmlChar*) subject );
+               xmlAddChild( message_node, subject_node ); 
+       }
+
+       if( body && strlen(body) > 0 ) {
+               body_node = xmlNewChild(message_node, NULL, (xmlChar*) "body", (xmlChar*) body );
+               xmlAddChild( message_node, body_node ); 
+       }
+
+
+       xmlDocDumpFormatMemory( doc, &xmlbuf, &bufsize, 0 );
+       encoded_body = strdup( (char*) xmlbuf );
+
+       if( encoded_body == NULL ) 
+               fatal_handler("message_to_xml(): Out of Memory");
+
+       xmlFree(xmlbuf);
+       xmlFreeDoc( doc );
+       xmlCleanupParser();
+
+
+       /*** remove the XML declaration */
+
+       int len = strlen(encoded_body);
+       char tmp[len];
+       memset( tmp, 0, len );
+       int i;
+       int found_at = 0;
+
+       /* when we reach the first >, take everything after it */
+       for( i = 0; i!= len; i++ ) {
+               if( encoded_body[i] == 62) { /* ascii > */
+
+                       /* found_at holds the starting index of the rest of the doc*/
+                       found_at = i + 1; 
+                       break;
+               }
+       }
+
+       if( found_at ) {
+               /* move the shortened doc into the tmp buffer */
+               strncpy( tmp, encoded_body + found_at, len - found_at );
+               /* move the tmp buffer back into the allocated space */
+               memset( encoded_body, 0, len );
+               strcpy( encoded_body, tmp );
+       }
+
+       return encoded_body;
+}
+
+
+
+void jid_get_username( const char* jid, char buf[] ) {
+
+       if( jid == NULL ) { return; }
+
+       /* find the @ and return whatever is in front of it */
+       int len = strlen( jid );
+       int i;
+       for( i = 0; i != len; i++ ) {
+               if( jid[i] == 64 ) { /*ascii @*/
+                       strncpy( buf, jid, i );
+                       return;
+               }
+       }
+}
+
+
+void jid_get_resource( const char* jid, char buf[])  {
+       if( jid == NULL ) { return; }
+       int len = strlen( jid );
+       int i;
+       for( i = 0; i!= len; i++ ) {
+               if( jid[i] == 47 ) { /* ascii / */
+                       strncpy( buf, jid + i + 1, len - (i+1) );
+               }
+       }
+}
+
+void set_msg_error( transport_message* msg, char* type, int err_code ) {
+
+       if( type != NULL && strlen( type ) > 0 ) {
+               msg->error_type = safe_malloc( strlen(type)+1); 
+               strcpy( msg->error_type, type );
+               msg->error_code = err_code;
+       }
+       msg->is_error = 1;
+}
diff --git a/src/libtransport/transport_session.c b/src/libtransport/transport_session.c
new file mode 100644 (file)
index 0000000..71b0768
--- /dev/null
@@ -0,0 +1,507 @@
+#include "transport_session.h"
+
+
+
+// ---------------------------------------------------------------------------------
+// returns a built and allocated transport_session object.
+// This codes does no network activity, only memory initilization
+// ---------------------------------------------------------------------------------
+transport_session* init_transport(  char* server, int port, void* user_data ) {
+
+       /* create the session struct */
+       transport_session* session = 
+               (transport_session*) safe_malloc( sizeof(transport_session) );
+
+       session->user_data = user_data;
+
+       /* initialize the data buffers */
+       session->body_buffer                    = buffer_init( JABBER_BODY_BUFSIZE );
+       session->subject_buffer         = buffer_init( JABBER_SUBJECT_BUFSIZE );
+       session->thread_buffer          = buffer_init( JABBER_THREAD_BUFSIZE );
+       session->from_buffer                    = buffer_init( JABBER_JID_BUFSIZE );
+       session->status_buffer          = buffer_init( JABBER_STATUS_BUFSIZE );
+       session->recipient_buffer       = buffer_init( JABBER_JID_BUFSIZE );
+       session->message_error_type = buffer_init( JABBER_JID_BUFSIZE );
+
+       /* for OILS extensions */
+       session->router_to_buffer               = buffer_init( JABBER_JID_BUFSIZE );
+       session->router_from_buffer     = buffer_init( JABBER_JID_BUFSIZE );
+       session->router_class_buffer    = buffer_init( JABBER_JID_BUFSIZE );
+       session->router_command_buffer  = buffer_init( JABBER_JID_BUFSIZE );
+
+
+
+       if(     session->body_buffer            == NULL || session->subject_buffer       == NULL        ||
+                       session->thread_buffer  == NULL || session->from_buffer          == NULL        ||
+                       session->status_buffer  == NULL || session->recipient_buffer == NULL ||
+                       session->router_to_buffer       == NULL || session->router_from_buffer   == NULL ||
+                       session->router_class_buffer == NULL || session->router_command_buffer == NULL ) { 
+
+               fatal_handler( "init_transport(): buffer_init returned NULL" );
+               return 0;
+       }
+
+
+       /* initialize the jabber state machine */
+       session->state_machine = (jabber_machine*) safe_malloc( sizeof(jabber_machine) );
+
+       /* initialize the sax push parser */
+       session->parser_ctxt = xmlCreatePushParserCtxt(SAXHandler, session, "", 0, NULL);
+
+       /* initialize the transport_socket structure */
+       session->sock_obj = (transport_socket*) safe_malloc( sizeof(transport_socket) );
+
+       //int serv_size = strlen( server );
+       session->sock_obj->server = server;
+       session->sock_obj->port = port;
+       session->sock_obj->data_received_callback = &grab_incoming;
+
+       /* this will be handed back to us in callbacks */
+       session->sock_obj->user_data = session; 
+
+       return session;
+}
+
+/* XXX FREE THE BUFFERS */
+int session_free( transport_session* session ) {
+       if( ! session ) { return 0; }
+
+       if( session->sock_obj ) 
+               free( session->sock_obj );
+
+       if( session->state_machine ) free( session->state_machine );
+       if( session->parser_ctxt) {
+               xmlCleanupCharEncodingHandlers();
+               xmlFreeDoc( session->parser_ctxt->myDoc );
+               xmlFreeParserCtxt(session->parser_ctxt);
+       }
+       xmlCleanupParser();
+
+       buffer_free(session->body_buffer);
+       buffer_free(session->subject_buffer);
+       buffer_free(session->thread_buffer);
+       buffer_free(session->from_buffer);
+       buffer_free(session->recipient_buffer);
+       buffer_free(session->status_buffer);
+       buffer_free(session->message_error_type);
+       buffer_free(session->router_to_buffer);
+       buffer_free(session->router_from_buffer);
+       buffer_free(session->router_class_buffer);
+       buffer_free(session->router_command_buffer);
+
+
+       free( session );
+       return 1;
+}
+
+
+int session_wait( transport_session* session, int timeout ) {
+       if( ! session || ! session->sock_obj ) {
+               return 0;
+       }
+       int ret =  tcp_wait( session->sock_obj, timeout );
+       if( ! ret ) {
+               session->state_machine->connected = 0;
+       }
+       return ret;
+}
+
+int session_send_msg( 
+               transport_session* session, transport_message* msg ) {
+
+       if( ! session ) { return 0; }
+
+       if( ! session->state_machine->connected ) {
+               warning_handler("State machine is not connected in send_msg()");
+               return 0;
+       }
+
+       message_prepare_xml( msg );
+       tcp_send( session->sock_obj, msg->msg_xml );
+
+       return 1;
+
+}
+
+
+/* connects to server and connects to jabber */
+int session_connect( transport_session* session, 
+               const char* username, const char* password, const char* resource, int connect_timeout ) {
+
+       int size1 = 0;
+       int size2 = 0;
+
+       if( ! session ) { 
+               warning_handler( "session is null in connect" );
+               return 0; 
+       }
+
+
+       /*
+       session->state_machine->connected = 
+               tcp_connected( session->sock_obj );
+
+       if( session->state_machine->connected ) {
+               return 1;
+       }
+       */
+
+
+       char* server = session->sock_obj->server;
+
+       if( ! session->sock_obj ) {
+               return 0;
+       }
+
+       if( ! session->sock_obj->connected ) {
+               if( ! tcp_connect( session->sock_obj ))
+                       return 0;
+       }
+
+       /* the first Jabber connect stanza */
+       size1 = 100 + strlen( server );
+       char stanza1[ size1 ]; 
+       memset( stanza1, 0, size1 );
+       sprintf( stanza1, 
+                       "<stream:stream to='%s' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>",
+                       server );
+       
+       /* the second jabber connect stanza including login info*/
+       /* currently, we only support plain text login */
+       size2 = 150 + strlen( username ) + strlen(password) + strlen(resource);
+       char stanza2[ size2 ];
+       memset( stanza2, 0, size2 );
+
+       sprintf( stanza2, 
+                       "<iq id='123456789' type='set'><query xmlns='jabber:iq:auth'><username>%s</username><password>%s</password><resource>%s</resource></query></iq>",
+                       username, password, resource );
+
+       /* send the first stanze */
+       session->state_machine->connecting = CONNECTING_1;
+       if( ! tcp_send( session->sock_obj, stanza1 ) ) {
+               warning_handler("error sending");
+               return 0;
+       }
+
+       /* wait for reply */
+       tcp_wait( session->sock_obj, connect_timeout ); /* make the timeout smarter XXX */
+
+       /* server acknowledges our existence, now see if we can login */
+       if( session->state_machine->connecting == CONNECTING_2 ) {
+               if( ! tcp_send( session->sock_obj, stanza2 )  ) {
+                       warning_handler("error sending");
+                       return 0;
+               }
+       }
+
+       /* wait for reply */
+       tcp_wait( session->sock_obj, connect_timeout );
+
+
+       if( session->state_machine->connected ) {
+               /* yar! */
+               return 1;
+       }
+
+       return 0;
+}
+
+// ---------------------------------------------------------------------------------
+// TCP data callback. Shove the data into the push parser.
+// ---------------------------------------------------------------------------------
+void grab_incoming( void * session, char* data ) {
+       transport_session* ses = (transport_session*) session;
+       if( ! ses ) { return; }
+       xmlParseChunk(ses->parser_ctxt, data, strlen(data), 0);
+}
+
+
+void startElementHandler(
+       void *session, const xmlChar *name, const xmlChar **atts) {
+
+       transport_session* ses = (transport_session*) session;
+       if( ! ses ) { return; }
+
+       
+       if( strcmp( name, "message" ) == 0 ) {
+               ses->state_machine->in_message = 1;
+               buffer_add( ses->from_buffer, get_xml_attr( atts, "from" ) );
+               buffer_add( ses->recipient_buffer, get_xml_attr( atts, "to" ) );
+               buffer_add( ses->router_from_buffer, get_xml_attr( atts, "router_from" ) );
+               buffer_add( ses->router_to_buffer, get_xml_attr( atts, "router_to" ) );
+               buffer_add( ses->router_class_buffer, get_xml_attr( atts, "router_class" ) );
+               buffer_add( ses->router_command_buffer, get_xml_attr( atts, "router_command" ) );
+               char* broadcast = get_xml_attr( atts, "broadcast" );
+               if( broadcast )
+                       ses->router_broadcast = atoi( broadcast );
+
+               return;
+       }
+
+       if( ses->state_machine->in_message ) {
+
+               if( strcmp( name, "body" ) == 0 ) {
+                       ses->state_machine->in_message_body = 1;
+                       return;
+               }
+       
+               if( strcmp( name, "subject" ) == 0 ) {
+                       ses->state_machine->in_subject = 1;
+                       return;
+               }
+       
+               if( strcmp( name, "thread" ) == 0 ) {
+                       ses->state_machine->in_thread = 1;
+                       return;
+               }
+
+       }
+
+       if( strcmp( name, "presence" ) == 0 ) {
+               ses->state_machine->in_presence = 1;
+               buffer_add( ses->from_buffer, get_xml_attr( atts, "from" ) );
+               buffer_add( ses->recipient_buffer, get_xml_attr( atts, "to" ) );
+               return;
+       }
+
+       if( strcmp( name, "status" ) == 0 ) {
+               ses->state_machine->in_status = 1;
+               return;
+       }
+
+
+       if( strcmp( name, "stream:error" ) == 0 ) {
+               ses->state_machine->in_error = 1;
+               warning_handler( "Received <stream:error> message from Jabber server" );
+               return;
+       }
+
+
+       /* first server response from a connect attempt */
+       if( strcmp( name, "stream:stream" ) == 0 ) {
+               if( ses->state_machine->connecting == CONNECTING_1 ) {
+                       ses->state_machine->connecting = CONNECTING_2;
+               }
+       }
+
+       if( strcmp( name, "error" ) == 0 ) {
+               ses->state_machine->in_message_error = 1;
+               buffer_add( ses->message_error_type, get_xml_attr( atts, "type" ) );
+               ses->message_error_code = atoi( get_xml_attr( atts, "code" ) );
+               warning_handler( "Received <error> message" );
+               return;
+       }
+
+       if( strcmp( name, "iq" ) == 0 ) {
+               ses->state_machine->in_iq = 1;
+
+               if( strcmp( get_xml_attr(atts, "type"), "result") == 0 
+                               && ses->state_machine->connecting == CONNECTING_2 ) {
+                       ses->state_machine->connected = 1;
+                       ses->state_machine->connecting = 0;
+                       return;
+               }
+
+               if( strcmp( get_xml_attr(atts, "type"), "error") == 0 ) {
+                       warning_handler( "Error connecting to jabber" );
+                       return;
+               }
+       }
+}
+
+char* get_xml_attr( const xmlChar** atts, char* attr_name ) {
+       int i;
+       if (atts != NULL) {
+               for(i = 0;(atts[i] != NULL);i++) {
+                       if( strcmp( atts[i++], attr_name ) == 0 ) {
+                               if( atts[i] != NULL ) {
+                                       return (char*) atts[i];
+                               }
+                       }
+               }
+       }
+       return NULL;
+}
+
+
+// ------------------------------------------------------------------
+// See which tags are ending
+// ------------------------------------------------------------------
+void endElementHandler( void *session, const xmlChar *name) {
+       transport_session* ses = (transport_session*) session;
+       if( ! ses ) { return; }
+
+       if( strcmp( name, "message" ) == 0 ) {
+
+               /* pass off the message info the callback */
+               if( ses->message_callback ) {
+
+                       /* here it's ok to pass in the raw buffers because
+                               message_init allocates new space for the chars 
+                               passed in */
+                       transport_message* msg =  message_init( 
+                               ses->body_buffer->buf, 
+                               ses->subject_buffer->buf,
+                               ses->thread_buffer->buf, 
+                               ses->recipient_buffer->buf, 
+                               ses->from_buffer->buf );
+
+                       message_set_router_info( msg, 
+                               ses->router_from_buffer->buf, 
+                               ses->router_to_buffer->buf, 
+                               ses->router_class_buffer->buf,
+                               ses->router_command_buffer->buf,
+                               ses->router_broadcast );
+
+                       if( ses->message_error_type->n_used > 0 ) {
+                               set_msg_error( msg, ses->message_error_type->buf, ses->message_error_code );
+                       }
+
+                       if( msg == NULL ) { return; }
+                       ses->message_callback( ses->user_data, msg );
+               }
+
+               ses->state_machine->in_message = 0;
+               reset_session_buffers( session );
+               return;
+       }
+       
+       if( strcmp( name, "body" ) == 0 ) {
+               ses->state_machine->in_message_body = 0;
+               return;
+       }
+
+       if( strcmp( name, "subject" ) == 0 ) {
+               ses->state_machine->in_subject = 0;
+               return;
+       }
+
+       if( strcmp( name, "thread" ) == 0 ) {
+               ses->state_machine->in_thread = 0;
+               return;
+       }
+       
+       if( strcmp( name, "iq" ) == 0 ) {
+               ses->state_machine->in_iq = 0;
+               if( ses->message_error_code > 0 ) {
+                       warning_handler( "Error in IQ packet: code %d",  ses->message_error_code );
+                       warning_handler( "Error 401 means not authorized" );
+               }
+               reset_session_buffers( session );
+               return;
+       }
+
+       if( strcmp( name, "presence" ) == 0 ) {
+               ses->state_machine->in_presence = 0;
+               /*
+               if( ses->presence_callback ) {
+                       // call the callback with the status, etc.
+               }
+               */
+               reset_session_buffers( session );
+               return;
+       }
+
+       if( strcmp( name, "status" ) == 0 ) {
+               ses->state_machine->in_status = 0;
+               return;
+       }
+
+       if( strcmp( name, "error" ) == 0 ) {
+               ses->state_machine->in_message_error = 0;
+               return;
+       }
+
+       if( strcmp( name, "error:error" ) == 0 ) {
+               ses->state_machine->in_error = 0;
+               return;
+       }
+}
+
+int reset_session_buffers( transport_session* ses ) {
+       buffer_reset( ses->body_buffer );
+       buffer_reset( ses->subject_buffer );
+       buffer_reset( ses->thread_buffer );
+       buffer_reset( ses->from_buffer );
+       buffer_reset( ses->recipient_buffer );
+       buffer_reset( ses->router_from_buffer );
+       buffer_reset( ses->router_to_buffer );
+       buffer_reset( ses->router_class_buffer );
+       buffer_reset( ses->router_command_buffer );
+       buffer_reset( ses->message_error_type );
+
+       return 1;
+}
+
+// ------------------------------------------------------------------
+// takes data out of the body of the message and pushes it into
+// the appropriate buffer
+// ------------------------------------------------------------------
+void characterHandler(
+               void *session, const xmlChar *ch, int len) {
+
+       char data[len+1];
+       memset( data, 0, len+1 );
+       strncpy( data, (char*) ch, len );
+       data[len] = 0;
+
+       //printf( "Handling characters: %s\n", data );
+       transport_session* ses = (transport_session*) session;
+       if( ! ses ) { return; }
+
+       /* set the various message parts */
+       if( ses->state_machine->in_message ) {
+
+               if( ses->state_machine->in_message_body ) {
+                       buffer_add( ses->body_buffer, data );
+               }
+
+               if( ses->state_machine->in_subject ) {
+                       buffer_add( ses->subject_buffer, data );
+               }
+
+               if( ses->state_machine->in_thread ) {
+                       buffer_add( ses->thread_buffer, data );
+               }
+       }
+
+       /* set the presence status */
+       if( ses->state_machine->in_presence && ses->state_machine->in_status ) {
+               buffer_add( ses->status_buffer, data );
+       }
+
+       if( ses->state_machine->in_error ) {
+               /* for now... */
+               warning_handler( "ERROR Xml fragment: %s\n", ch );
+       }
+
+}
+
+/* XXX change to warning handlers */
+void  parseWarningHandler( void *session, const char* msg, ... ) {
+
+       va_list args;
+       va_start(args, msg);
+       fprintf(stdout, "WARNING");
+       vfprintf(stdout, msg, args);
+       va_end(args);
+       fprintf(stderr, "XML WARNING: %s\n", msg ); 
+}
+
+void  parseErrorHandler( void *session, const char* msg, ... ){
+
+       va_list args;
+       va_start(args, msg);
+       fprintf(stdout, "ERROR");
+       vfprintf(stdout, msg, args);
+       va_end(args);
+       fprintf(stderr, "XML ERROR: %s\n", msg ); 
+
+}
+
+int session_disconnect( transport_session* session ) {
+       if( session == NULL ) { return 0; }
+       tcp_send( session->sock_obj, "</stream:stream>");
+       return tcp_disconnect( session->sock_obj );
+}
+
diff --git a/src/libtransport/transport_socket.c b/src/libtransport/transport_socket.c
new file mode 100644 (file)
index 0000000..6a9446c
--- /dev/null
@@ -0,0 +1,289 @@
+#include "transport_socket.h"
+
+
+/*
+int main( char* argc, char** argv ) {
+
+       transport_socket sock_obj;
+       sock_obj.port = 5222;
+       sock_obj.server = "10.0.0.4";
+       sock_obj.data_received_callback = &print_stuff;
+
+       printf("connecting...\n");
+       if( (tcp_connect( &sock_obj )) < 0 ) {
+               printf( "error connecting" );
+       }
+
+       printf("sending...\n");
+       if( tcp_send( &sock_obj, "<stream>\n" ) < 0 ) {
+               printf( "error sending" );
+       }
+       
+       printf("waiting...\n");
+       if( tcp_wait( &sock_obj, 15 ) < 0 ) {
+               printf( "error receiving" );
+       }
+
+       printf("disconnecting...\n");
+       tcp_disconnect( &sock_obj );
+
+}
+*/
+
+
+// returns the socket fd, -1 on error
+int tcp_connect( transport_socket* sock_obj ){
+
+       if( sock_obj == NULL ) {
+               fatal_handler( "connect(): null sock_obj" );
+               return -1;
+       }
+       struct sockaddr_in remoteAddr, localAddr;
+       struct hostent *hptr;
+       int sock_fd;
+
+       #ifdef WIN32
+       WSADATA data;
+       char bfr;
+       if( WSAStartup(MAKEWORD(1,1), &data) ) {
+               fatal_handler( "somethin's broke with windows socket startup" );
+               return -1;
+       }
+       #endif
+
+
+
+       // ------------------------------------------------------------------
+       // Create the socket
+       // ------------------------------------------------------------------
+       if( (sock_fd = socket( AF_INET, SOCK_STREAM, 0 )) < 0 ) {
+               fatal_handler( "tcp_connect(): Cannot create socket" );
+               return -1;
+       }
+
+       // ------------------------------------------------------------------
+       // Get the hostname
+       // ------------------------------------------------------------------
+       if( (hptr = gethostbyname( sock_obj->server ) ) == NULL ) {
+               fatal_handler( "tcp_connect(): Unknown Host" );
+               return -1;
+       }
+
+       // ------------------------------------------------------------------
+       // Construct server info struct
+       // ------------------------------------------------------------------
+       memset( &remoteAddr, 0, sizeof(remoteAddr));
+       remoteAddr.sin_family = AF_INET;
+       remoteAddr.sin_port = htons( sock_obj->port );
+       memcpy( (char*) &remoteAddr.sin_addr.s_addr,
+                       hptr->h_addr_list[0], hptr->h_length );
+
+       // ------------------------------------------------------------------
+       // Construct local info struct
+       // ------------------------------------------------------------------
+       memset( &localAddr, 0, sizeof( localAddr ) );
+       localAddr.sin_family = AF_INET;
+       localAddr.sin_addr.s_addr = htonl( INADDR_ANY );
+       localAddr.sin_port = htons(0);
+
+       // ------------------------------------------------------------------
+       // Bind to a local port
+       // ------------------------------------------------------------------
+       if( bind( sock_fd, (struct sockaddr *) &localAddr, sizeof( localAddr ) ) < 0 ) {
+               fatal_handler( "tcp_connect(): Cannot bind to local port" );
+               return -1;
+       }
+
+       // ------------------------------------------------------------------
+       // Connect to server
+       // ------------------------------------------------------------------
+       if( connect( sock_fd, (struct sockaddr*) &remoteAddr, sizeof( struct sockaddr_in ) ) < 0 ) {
+               fatal_handler( "tcp_connect(): Cannot connect to server %s", sock_obj->server );
+               return -1;
+       }
+
+       sock_obj->sock_fd = sock_fd;
+       sock_obj->connected = 1;
+       return sock_fd;
+
+}
+
+
+int tcp_send( transport_socket* sock_obj, const char* data ){
+
+       if( sock_obj == NULL ) {
+               fatal_handler( "tcp_send(): null sock_obj" );
+               return 0;
+       }
+
+       //fprintf( stderr, "TCP Sending: \n%s\n", data );
+
+       // ------------------------------------------------------------------
+       // Send the data down the TCP pipe
+       // ------------------------------------------------------------------
+       if( send( sock_obj->sock_fd, data, strlen(data), 0 ) < 0 ) {
+               fatal_handler( "tcp_send(): Error sending data" );
+               return 0;
+       }
+       return 1;
+}
+
+
+int tcp_disconnect( transport_socket* sock_obj ){
+
+       if( sock_obj == NULL ) {
+               fatal_handler( "tcp_disconnect(): null sock_obj" );
+               return -1;
+       }
+
+       if( close( sock_obj->sock_fd ) == -1 ) {
+
+               // ------------------------------------------------------------------
+               // Not really worth throwing an exception for... should be logged.
+               // ------------------------------------------------------------------
+               warning_handler( "tcp_disconnect(): Error closing socket" );
+               return -1;
+       } 
+
+       return 0;
+}
+
+// ------------------------------------------------------------------
+// And now for the gory C socket code.
+// Returns 0 on failure, 1 otherwise
+// ------------------------------------------------------------------
+int tcp_wait( transport_socket* sock_obj, int timeout ){
+
+       if( sock_obj == NULL ) {
+               fatal_handler( "tcp_wait(): null sock_obj" );
+               return 0;
+       }
+
+       int n = 0; 
+       int retval = 0;
+       char buf[BUFSIZE];
+       int sock_fd = sock_obj->sock_fd;
+
+
+       fd_set read_set;
+
+       FD_ZERO( &read_set );
+       FD_SET( sock_fd, &read_set );
+
+       // ------------------------------------------------------------------
+       // Build the timeval struct
+       // ------------------------------------------------------------------
+       struct timeval tv;
+       tv.tv_sec = timeout;
+       tv.tv_usec = 0;
+
+       if( timeout == -1 ) {  
+
+               // ------------------------------------------------------------------
+               // If timeout is -1, there is no timeout passed to the call to select
+               // ------------------------------------------------------------------
+               if( (retval = select( sock_fd + 1 , &read_set, NULL, NULL, NULL)) == -1 ) {
+                       warning_handler( "Call to select failed" );
+                       return 0;
+               }
+
+       } else if( timeout != 0 ) { /* timeout of 0 means don't block */
+
+               if( (retval = select( sock_fd + 1 , &read_set, NULL, NULL, &tv)) == -1 ) {
+                       warning_handler( "Call to select failed" );
+                       return 0;
+               }
+       }
+
+       memset( &buf, 0, BUFSIZE );
+
+       if( set_fl( sock_fd, O_NONBLOCK ) < 0 ) 
+               return 0;
+
+#ifdef _ROUTER // just read one buffer full of data
+
+       n = recv(sock_fd, buf, BUFSIZE-1, 0);
+       sock_obj->data_received_callback( sock_obj->user_data, buf );
+       if( n == 0 )
+               n = -1;
+
+#else // read everything we can
+
+       while( (n = recv(sock_fd, buf, BUFSIZE-1, 0) ) > 0 ) {
+               //printf("\nReceived:  %s\n", buf);
+               sock_obj->data_received_callback( sock_obj->user_data, buf );
+               memset( &buf, 0, BUFSIZE );
+       }
+
+#endif
+
+       if( clr_fl( sock_fd, O_NONBLOCK ) < 0 ) {
+               return 0;
+       }
+
+       if( n < 0 ) { 
+               if( errno != EAGAIN ) { 
+                       warning_handler( " * Error reading socket with errno %d", errno );
+                       return 0;
+               }
+       }
+
+#ifdef _ROUTER
+       return n;
+#else
+       return 1;
+#endif
+
+}
+
+int set_fl( int fd, int flags ) {
+       
+       int val;
+
+       if( (val = fcntl( fd, F_GETFL, 0) ) < 0 ) {
+               fatal_handler("fcntl F_GETFL error");
+               return -1;
+       }
+
+       val |= flags;
+
+       if( fcntl( fd, F_SETFL, val ) < 0 ) {
+               fatal_handler( "fcntl F_SETFL error" );
+               return -1;
+       }
+       return 0;
+}
+       
+int clr_fl( int fd, int flags ) {
+       
+       int val;
+
+       if( (val = fcntl( fd, F_GETFL, 0) ) < 0 ) {
+               fatal_handler("fcntl F_GETFL error" );
+               return -1;
+       }
+
+       val &= ~flags;
+
+       if( fcntl( fd, F_SETFL, val ) < 0 ) {
+               fatal_handler( "fcntl F_SETFL error" );
+               return -1;
+       }
+       return 0;
+}
+       
+
+/*
+int tcp_connected( transport_socket* obj ) {
+
+       int ret;
+       if( ! obj->sock_fd ) { return 0; }
+
+       ret = read( obj->sock_fd  , NULL,0 );
+       if( ret <= 0 ) {
+               return 0;
+       }
+       return 1;
+}
+*/
+
diff --git a/src/patch/README b/src/patch/README
new file mode 100644 (file)
index 0000000..3865013
--- /dev/null
@@ -0,0 +1,8 @@
+Patch                                          Source                          Location                                                                                Purpose
+------------------------------------------------------------------------------------------------------
+
+mod_offline.c                  jabberd-2.0s4           jabberd-2.0s4/sm/mod_offline.c          Disables offline storage 
+------------------------------------------------------------------------------------------------------
+
+nad.c                                          jabberd-2.0s4           jabberd-2.0s4/util/nad.c                                Fixes segfault in a branch of code that we don't use 
+
diff --git a/src/patch/mod_offline.c b/src/patch/mod_offline.c
new file mode 100644 (file)
index 0000000..4234cd7
--- /dev/null
@@ -0,0 +1,266 @@
+/*
+ * jabberd - Jabber Open Source Server
+ * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney,
+ *                    Ryan Eatmon, Robert Norris
+ *
+ * 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, MA02111-1307USA
+ */
+
+
+/** ! ! !  Patched version to disable offline storage for jabberd-2.0s4 ! ! ! */
+
+#include "sm.h"
+
+/** @file sm/mod_offline.c
+  * @brief offline storage
+  * @author Robert Norris
+  * $Date$
+  * $Revision$
+  */
+
+typedef struct _mod_offline_st {
+    int dropmessages;
+    int dropsubscriptions;
+} *mod_offline_t;
+
+static mod_ret_t _offline_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt) {
+    st_ret_t ret;
+    os_t os;
+    os_object_t o;
+    os_type_t ot;
+    nad_t nad;
+    pkt_t queued;
+    int ns, elem, attr;
+    char cttl[15], cstamp[18];
+    time_t ttl, stamp;
+
+    /* if they're becoming available for the first time */
+    if(pkt->type == pkt_PRESENCE && pkt->to == NULL && sess->user->top == NULL) {
+
+        ret = storage_get(pkt->sm->st, "queue", jid_user(sess->jid), NULL, &os);
+        if(ret != st_SUCCESS) {
+            log_debug(ZONE, "storage_get returned %d", ret);
+            return mod_PASS;
+        }
+        
+        if(os_iter_first(os))
+            do {
+                o = os_iter_object(os);
+
+                if(os_object_get(o, "xml", (void **) &nad, &ot)) {
+                    queued = pkt_new(pkt->sm, nad_copy(nad));
+                    if(queued == NULL) {
+                        log_debug(ZONE, "invalid queued packet, not delivering");
+                    } else {
+                        /* check expiry as necessary */
+                        if((ns = nad_find_scoped_namespace(queued->nad, uri_EXPIRE, NULL)) >= 0 &&
+                           (elem = nad_find_elem(queued->nad, 1, ns, "x", 1)) >= 0 &&
+                           (attr = nad_find_attr(queued->nad, elem, -1, "seconds", NULL)) >= 0) {
+                            snprintf(cttl, 15, "%.*s", NAD_AVAL_L(queued->nad, attr), NAD_AVAL(queued->nad, attr));
+                            ttl = atoi(cttl);
+
+                            /* it should have a x:delay stamp, because we stamp everything we store */
+                            if((ns = nad_find_scoped_namespace(queued->nad, uri_DELAY, NULL)) >= 0 &&
+                               (elem = nad_find_elem(queued->nad, 1, ns, "x", 1)) >= 0 &&
+                               (attr = nad_find_attr(queued->nad, elem, -1, "stamp", NULL)) >= 0) {
+                                snprintf(cstamp, 18, "%.*s", NAD_AVAL_L(queued->nad, attr), NAD_AVAL(queued->nad, attr));
+                                stamp = datetime_in(cstamp);
+
+                                if(stamp + ttl <= time(NULL)) {
+                                    log_debug(ZONE, "queued packet has expired, dropping");
+                                    pkt_free(queued);
+                                    continue;
+                                }
+                            }
+                        }
+
+                        log_debug(ZONE, "delivering queued packet to %s", jid_full(sess->jid));
+                        pkt_sess(queued, sess);
+                    }
+                }
+            } while(os_iter_next(os));
+
+        os_free(os);
+
+        /* drop the spool */
+        storage_delete(pkt->sm->st, "queue", jid_user(sess->jid), NULL);
+    }
+
+    /* pass it so that other modules and mod_presence can get it */
+    return mod_PASS;
+}
+
+static mod_ret_t _offline_pkt_user(mod_instance_t mi, user_t user, pkt_t pkt) {
+    mod_offline_t offline = (mod_offline_t) mi->mod->private;
+    int ns, elem, attr;
+    os_t os;
+    os_object_t o;
+    pkt_t event;
+
+    /* send messages and s10ns to the top session */
+    if(user->top != NULL && (pkt->type & pkt_MESSAGE || pkt->type & pkt_S10N)) {
+        pkt_sess(pkt, user->top);
+        return mod_HANDLED;
+    }
+
+    /* save messages and s10ns for later */
+    if((pkt->type & pkt_MESSAGE && !offline->dropmessages) ||
+       (pkt->type & pkt_S10N && !offline->dropsubscriptions)) {
+        log_debug(ZONE, "saving message for later");
+
+        pkt_delay(pkt, time(NULL), user->sm->id);
+
+        /* new object */
+        os = os_new();
+        o = os_object_new(os);
+
+        os_object_put(o, "xml", pkt->nad, os_type_NAD);
+
+        /* store it */
+        switch(storage_put(user->sm->st, "queue", jid_user(user->jid), os)) {
+            case st_FAILED:
+                os_free(os);
+                return -stanza_err_INTERNAL_SERVER_ERROR;
+
+            case st_NOTIMPL:
+                os_free(os);
+                return -stanza_err_SERVICE_UNAVAILABLE;     /* xmpp-im 9.5#4 */
+
+            default:
+                os_free(os);
+
+                /* send offline events if they asked for it */
+                if((ns = nad_find_scoped_namespace(pkt->nad, uri_EVENT, NULL)) >= 0 &&
+                   (elem = nad_find_elem(pkt->nad, 1, ns, "x", 1)) >= 0 &&
+                   nad_find_elem(pkt->nad, elem, ns, "offline", 1) >= 0) {
+
+                    event = pkt_create(user->sm, "message", NULL, jid_full(pkt->from), jid_full(pkt->to));
+
+                    attr = nad_find_attr(pkt->nad, 1, -1, "type", NULL);
+                    if(attr >= 0)
+                        nad_set_attr(event->nad, 1, -1, "type", NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr));
+
+                    ns = nad_add_namespace(event->nad, uri_EVENT, NULL);
+                    nad_append_elem(event->nad, ns, "x", 2);
+                    nad_append_elem(event->nad, ns, "offline", 3);
+
+                    nad_append_elem(event->nad, ns, "id", 3);
+                    attr = nad_find_attr(pkt->nad, 1, -1, "id", NULL);
+                    if(attr >= 0)
+                        nad_append_cdata(event->nad, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr), 4);
+
+                    pkt_router(event);
+                }
+
+                pkt_free(pkt);
+                return mod_HANDLED;
+        }
+    }
+
+    return mod_PASS;
+}
+
+static void _offline_user_delete(mod_instance_t mi, jid_t jid) {
+    os_t os;
+    os_object_t o;
+    os_type_t ot;
+    nad_t nad;
+    pkt_t queued;
+    int ns, elem, attr;
+    char cttl[15], cstamp[18];
+    time_t ttl, stamp;
+
+    log_debug(ZONE, "deleting queue for %s", jid_user(jid));
+
+    /* bounce the queue */
+    if(storage_get(mi->mod->mm->sm->st, "queue", jid_user(jid), NULL, &os) == st_SUCCESS) {
+        if(os_iter_first(os))
+            do {
+                o = os_iter_object(os);
+
+                if(os_object_get(o, "xml", (void **) &nad, &ot)) {
+                    queued = pkt_new(mi->mod->mm->sm, nad);
+                    if(queued == NULL) {
+                        log_debug(ZONE, "invalid queued packet, not delivering");
+                    } else {
+                        /* check expiry as necessary */
+                        if((ns = nad_find_scoped_namespace(queued->nad, uri_EXPIRE, NULL)) >= 0 &&
+                           (elem = nad_find_elem(queued->nad, 1, ns, "x", 1)) >= 0 &&
+                           (attr = nad_find_attr(queued->nad, elem, -1, "seconds", NULL)) >= 0) {
+                            snprintf(cttl, 15, "%.*s", NAD_AVAL_L(queued->nad, attr), NAD_AVAL(queued->nad, attr));
+                            ttl = atoi(cttl);
+
+                            /* it should have a x:delay stamp, because we stamp everything we store */
+                            if((ns = nad_find_scoped_namespace(queued->nad, uri_DELAY, NULL)) >= 0 &&
+                               (elem = nad_find_elem(queued->nad, 1, ns, "x", 1)) >= 0 &&
+                               (attr = nad_find_attr(queued->nad, elem, -1, "stamp", NULL)) >= 0) {
+                                snprintf(cstamp, 18, "%.*s", NAD_AVAL_L(queued->nad, attr), NAD_AVAL(queued->nad, attr));
+                                stamp = datetime_in(cstamp);
+
+                                if(stamp + ttl <= time(NULL)) {
+                                    log_debug(ZONE, "queued packet has expired, dropping");
+                                    pkt_free(queued);
+                                    continue;
+                                }
+                            }
+                        }
+
+                        log_debug(ZONE, "bouncing queued packet from %s", jid_full(queued->from));
+                        pkt_router(pkt_error(queued, stanza_err_ITEM_NOT_FOUND));
+                    }
+                }
+            } while(os_iter_next(os));
+
+        os_free(os);
+    }
+    
+    storage_delete(mi->sm->st, "queue", jid_user(jid), NULL);
+}
+
+static void _offline_free(module_t mod) {
+    mod_offline_t offline = (mod_offline_t) mod->private;
+
+    free(offline);
+}
+
+int offline_init(mod_instance_t mi, char *arg) {
+    module_t mod = mi->mod;
+    char *configval;
+    mod_offline_t offline;
+    int dropmessages = 0;
+    int dropsubscriptions = 0;
+
+    if(mod->init) return 0;
+
+    configval = config_get_one(mod->mm->sm->config, "offline.dropmessages", 0);
+    if (configval != NULL)
+        dropmessages = 1;
+    configval = config_get_one(mod->mm->sm->config, "offline.dropsubscriptions", 0);
+    if (configval != NULL)
+        dropsubscriptions = 1;
+
+    offline = (mod_offline_t) malloc(sizeof(struct _mod_offline_st));
+    offline->dropmessages = dropmessages;
+    offline->dropsubscriptions = dropsubscriptions;
+
+    mod->private = offline;
+
+    mod->in_sess = _offline_in_sess;
+    mod->pkt_user = _offline_pkt_user;
+    mod->user_delete = _offline_user_delete;
+    mod->free = _offline_free;
+
+    return 0;
+}
diff --git a/src/patch/nad.c b/src/patch/nad.c
new file mode 100644 (file)
index 0000000..d9914c4
--- /dev/null
@@ -0,0 +1,1155 @@
+/*
+ * jabberd - Jabber Open Source Server
+ * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney,
+ *                    Ryan Eatmon, Robert Norris
+ *
+ * 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, MA02111-1307USA
+ */
+
+
+/** ! ! ! Patched version to fix segfault issue for jabberd-2.0s4 ! ! ! */
+
+/**
+ * !!! Things to do (after 2.0)
+ *
+ * - make nad_find_scoped_namespace() take an element index, and only search
+ *   the scope on that element (currently, it searchs all elements from
+ *   end to start, which isn't really correct, though it works in most cases
+ *
+ * - new functions:
+ *     * insert one nad (or part thereof) into another nad
+ *     * clear a part of a nad (like xmlnode_hide)
+ *
+ * - audit use of depth array and parent (see j2 bug #792)
+ */
+
+#include "util.h"
+
+#ifdef HAVE_EXPAT
+#include "expat/expat.h"
+#endif
+
+/* define NAD_DEBUG to get pointer tracking - great for weird bugs that you can't reproduce */
+#ifdef NAD_DEBUG
+
+static xht _nad_alloc_tracked = NULL;
+static xht _nad_free_tracked = NULL;
+
+static void _nad_ptr_check(const char *func, nad_t nad) {
+    char loc[24];
+    snprintf(loc, sizeof(loc), "%x", (int) nad);
+
+    if(xhash_get(_nad_alloc_tracked, loc) == NULL) {
+        fprintf(stderr, ">>> NAD OP %s: 0x%x not allocated!\n", func, (int) nad);
+        abort();
+    }
+
+    if(xhash_get(_nad_free_tracked, loc) != NULL) {
+        fprintf(stderr, ">>> NAD OP %s: 0x%x previously freed!\n", func, (int) nad);
+        abort();
+    }
+
+    fprintf(stderr, ">>> NAD OP %s: 0x%x\n", func, (int) nad);
+}
+#else
+#define _nad_ptr_check(func,nad)
+#endif
+
+#define BLOCKSIZE 1024
+
+/** internal: do and return the math and ensure it gets realloc'd */
+int _nad_realloc(void **oblocks, int len)
+{
+    void *nblocks;
+    int nlen;
+
+    /* round up to standard block sizes */
+    nlen = (((len-1)/BLOCKSIZE)+1)*BLOCKSIZE;
+
+    /* keep trying till we get it */
+    while((nblocks = realloc(*oblocks, nlen)) == NULL) sleep(1);
+    *oblocks = nblocks;
+    return nlen;
+}
+
+/** this is the safety check used to make sure there's always enough mem */
+#define NAD_SAFE(blocks, size, len) if((size) > len) len = _nad_realloc((void**)&(blocks),(size));
+
+/** internal: append some cdata and return the index to it */
+int _nad_cdata(nad_t nad, const char *cdata, int len)
+{
+    NAD_SAFE(nad->cdata, nad->ccur + len, nad->clen);
+
+    memcpy(nad->cdata + nad->ccur, cdata, len);
+    nad->ccur += len;
+    return nad->ccur - len;
+}
+
+/** internal: create a new attr on any given elem */
+int _nad_attr(nad_t nad, int elem, int ns, const char *name, const char *val, int vallen)
+{
+    int attr;
+
+    /* make sure there's mem for us */
+    NAD_SAFE(nad->attrs, (nad->acur + 1) * sizeof(struct nad_attr_st), nad->alen);
+
+    attr = nad->acur;
+    nad->acur++;
+    nad->attrs[attr].next = nad->elems[elem].attr;
+    nad->elems[elem].attr = attr;
+    nad->attrs[attr].lname = strlen(name);
+    nad->attrs[attr].iname = _nad_cdata(nad,name,nad->attrs[attr].lname);
+    if(vallen > 0)
+        nad->attrs[attr].lval = vallen;
+    else
+        nad->attrs[attr].lval = strlen(val);
+    nad->attrs[attr].ival = _nad_cdata(nad,val,nad->attrs[attr].lval);    
+    nad->attrs[attr].my_ns = ns;
+
+    return attr;
+}
+
+/** create a new cache, simple pointer to a list of nads */
+nad_cache_t nad_cache_new(void)
+{
+    nad_cache_t cache;
+    while((cache = malloc(sizeof(nad_cache_t))) == NULL) sleep(1);
+    *cache = NULL;
+
+#ifdef NAD_DEBUG
+    if(_nad_alloc_tracked == NULL) _nad_alloc_tracked = xhash_new(501);
+    if(_nad_free_tracked == NULL) _nad_free_tracked = xhash_new(501);
+#endif
+    
+    return cache;
+}
+
+
+/** free the cache and any nads in it */
+void nad_cache_free(nad_cache_t cache)
+{
+    nad_t cur;
+    while((cur = *cache) != NULL)
+    {
+        *cache = cur->next;
+        free(cur->elems);
+        free(cur->attrs);
+        free(cur->nss);
+        free(cur->cdata);
+        free(cur->depths);
+        free(cur);
+    }
+    free(cache);
+}
+
+/** get the next nad from the cache, or create some */
+nad_t nad_new(nad_cache_t cache)
+{
+    nad_t nad;
+
+#ifndef NAD_DEBUG
+    /* If cache==NULL, then this NAD is not in a cache */
+
+    if ((cache!=NULL) && (*cache != NULL))
+    {
+        nad = *cache;
+        *cache = nad->next;
+        nad->ccur = nad->ecur = nad->acur = nad->ncur = 0;
+        nad->scope = -1;
+        nad->cache = cache;
+        nad->next = NULL;
+        return nad;
+    }
+#endif
+
+    while((nad = malloc(sizeof(struct nad_st))) == NULL) sleep(1);
+    memset(nad,0,sizeof(struct nad_st));
+
+    nad->scope = -1;
+    nad->cache = cache;
+
+#ifdef NAD_DEBUG
+    {
+    char loc[24];
+    snprintf(loc, sizeof(loc), "%x", (int) nad);
+    xhash_put(_nad_alloc_tracked, pstrdup(xhash_pool(_nad_alloc_tracked), loc), (void *) 1);
+    }
+    _nad_ptr_check(__func__, nad);
+#endif
+
+    return nad;
+}
+
+nad_t nad_copy(nad_t nad)
+{
+    nad_t copy;
+
+    _nad_ptr_check(__func__, nad);
+
+    if(nad == NULL) return NULL;
+
+    /* create a new nad not participating in a cache */
+    copy = nad_new(NULL);
+
+    /* if it's not large enough, make bigger */
+    NAD_SAFE(copy->elems, nad->elen, copy->elen);
+    NAD_SAFE(copy->attrs, nad->alen, copy->alen);
+    NAD_SAFE(copy->nss, nad->nlen, copy->nlen);
+    NAD_SAFE(copy->cdata, nad->clen, copy->clen);
+
+    /* copy all data */
+    memcpy(copy->elems, nad->elems, nad->elen);
+    memcpy(copy->attrs, nad->attrs, nad->alen);
+    memcpy(copy->nss, nad->nss, nad->nlen);
+    memcpy(copy->cdata, nad->cdata, nad->clen);
+
+    /* sync data */
+    copy->ecur = nad->ecur;
+    copy->acur = nad->acur;
+    copy->ncur = nad->ncur;
+    copy->ccur = nad->ccur;
+
+    copy->scope = nad->scope;
+
+    return copy;
+}
+
+/** free nad, or plug nad back in the cache */
+void nad_free(nad_t nad)
+{
+    if(nad == NULL) return;
+
+#ifdef NAD_DEBUG
+    _nad_ptr_check(__func__, nad);
+    {
+    char loc[24];
+    snprintf(loc, sizeof(loc), "%x", (int) nad);
+    xhash_zap(_nad_alloc_tracked, loc);
+    xhash_put(_nad_free_tracked, pstrdup(xhash_pool(_nad_free_tracked), loc), (void *) nad);
+    }
+#else
+    /* If nad->cache != NULL, then put back into cache, otherwise this nad is not in a cache */
+
+    if (nad->cache != NULL) {
+       nad->next = *(nad->cache);
+       *(nad->cache) = nad;
+       return;
+    } 
+#endif
+
+    /* Free nad */
+    free(nad->elems);
+    free(nad->attrs);
+    free(nad->cdata);
+    free(nad->nss);
+    free(nad->depths);
+}
+
+/** locate the next elem at a given depth with an optional matching name */
+int nad_find_elem(nad_t nad, int elem, int ns, const char *name, int depth)
+{
+    int my_ns;
+    int lname = 0;
+
+    _nad_ptr_check(__func__, nad);
+
+    /* make sure there are valid args */
+    if(elem >= nad->ecur || name == NULL) return -1;
+
+    /* set up args for searching */
+    depth = nad->elems[elem].depth + depth;
+    if(name != NULL) lname = strlen(name);
+
+    /* search */
+    for(elem++;elem < nad->ecur;elem++)
+    {
+        /* if we hit one with a depth less than ours, then we don't have the
+         * same parent anymore, bail */
+        if(nad->elems[elem].depth < depth)
+            return -1;
+
+        if(nad->elems[elem].depth == depth && (lname <= 0 || (lname == nad->elems[elem].lname && strncmp(name,nad->cdata + nad->elems[elem].iname, lname) == 0)) &&
+          (ns < 0 || ((my_ns = nad->elems[elem].my_ns) >= 0 && NAD_NURI_L(nad, ns) == NAD_NURI_L(nad, my_ns) && strncmp(NAD_NURI(nad, ns), NAD_NURI(nad, my_ns), NAD_NURI_L(nad, ns)) == 0)))
+            return elem;
+    }
+
+    return -1;
+}
+
+/** get a matching attr on this elem, both name and optional val */
+int nad_find_attr(nad_t nad, int elem, int ns, const char *name, const char *val)
+{
+    int attr, my_ns;
+    int lname, lval = 0;
+
+    _nad_ptr_check(__func__, nad);
+
+    /* make sure there are valid args */
+    if(elem >= nad->ecur || name == NULL) return -1;
+
+    attr = nad->elems[elem].attr;
+    lname = strlen(name);
+    if(val != NULL) lval = strlen(val);
+
+    while(attr >= 0)
+    {
+        /* hefty, match name and if a val, also match that */
+        if(lname == nad->attrs[attr].lname && strncmp(name,nad->cdata + nad->attrs[attr].iname, lname) == 0 && 
+          (lval <= 0 || (lval == nad->attrs[attr].lval && strncmp(val,nad->cdata + nad->attrs[attr].ival, lval) == 0)) &&
+          (ns < 0 || ((my_ns = nad->attrs[attr].my_ns) >= 0 && NAD_NURI_L(nad, ns) == NAD_NURI_L(nad, my_ns) && strncmp(NAD_NURI(nad, ns), NAD_NURI(nad, my_ns), NAD_NURI_L(nad, ns)) == 0)))
+            return attr;
+        attr = nad->attrs[attr].next;
+    }
+    return -1;
+}
+
+/** get a matching ns on this elem, both uri and optional prefix */
+int nad_find_namespace(nad_t nad, int elem, const char *uri, const char *prefix)
+{
+    int check, ns;
+
+    _nad_ptr_check(__func__, nad);
+
+    if(uri == NULL)
+        return -1;
+
+    /* work backwards through our parents, looking for our namespace on each one.
+     * if we find it, link it. if not, the namespace is undeclared - for now, just drop it */
+    check = elem;
+    while(check >= 0)
+    {
+        ns = nad->elems[check].ns;
+        while(ns >= 0)
+        {
+            if(strlen(uri) == NAD_NURI_L(nad, ns) && strncmp(uri, NAD_NURI(nad, ns), NAD_NURI_L(nad, ns)) == 0 && (prefix == NULL || (nad->nss[ns].iprefix >= 0 && strlen(prefix) == NAD_NPREFIX_L(nad, ns) && strncmp(prefix, NAD_NPREFIX(nad, ns), NAD_NPREFIX_L(nad, ns)) == 0)))
+                return ns;
+            ns = nad->nss[ns].next;
+        }
+        check = nad->elems[check].parent;
+    }
+
+    return -1;
+}
+
+/** find a namespace in scope */
+int nad_find_scoped_namespace(nad_t nad, const char *uri, const char *prefix)
+{
+    int ns;
+
+    _nad_ptr_check(__func__, nad);
+
+    if(uri == NULL)
+        return -1;
+
+    for(ns = 0; ns < nad->ncur; ns++)
+    {
+        if(strlen(uri) == NAD_NURI_L(nad, ns) && strncmp(uri, NAD_NURI(nad, ns), NAD_NURI_L(nad, ns)) == 0 &&
+           (prefix == NULL ||
+             (nad->nss[ns].iprefix >= 0 &&
+              strlen(prefix) == NAD_NPREFIX_L(nad, ns) && strncmp(prefix, NAD_NPREFIX(nad, ns), NAD_NPREFIX_L(nad, ns)) == 0)))
+            return ns;
+    }
+
+    return -1;
+}
+
+/** create, update, or zap any matching attr on this elem */
+void nad_set_attr(nad_t nad, int elem, int ns, const char *name, const char *val, int vallen)
+{
+    int attr;
+
+    _nad_ptr_check(__func__, nad);
+
+    /* find one to replace first */
+    if((attr = nad_find_attr(nad, elem, ns, name, NULL)) < 0)
+    {
+        /* only create new if there's a value to store */
+        if(val != NULL)
+            _nad_attr(nad, elem, ns, name, val, vallen);
+        return;
+    }
+
+    /* got matching, update value or zap */
+    if(val == NULL)
+    {
+        nad->attrs[attr].lval = nad->attrs[attr].lname = 0;
+    }else{
+        if(vallen > 0)
+            nad->attrs[attr].lval = vallen;
+        else
+            nad->attrs[attr].lval = strlen(val);
+        nad->attrs[attr].ival = _nad_cdata(nad,val,nad->attrs[attr].lval);
+    }
+
+}
+
+/** shove in a new child elem after the given one */
+int nad_insert_elem(nad_t nad, int parent, int ns, const char *name, const char *cdata)
+{
+    int elem = parent + 1;
+
+    _nad_ptr_check(__func__, nad);
+
+    NAD_SAFE(nad->elems, (nad->ecur + 1) * sizeof(struct nad_elem_st), nad->elen);
+
+    /* relocate all the rest of the elems (unless we're at the end already) */
+    if(nad->ecur != elem)
+        memmove(&nad->elems[elem + 1], &nad->elems[elem], (nad->ecur - elem) * sizeof(struct nad_elem_st));
+    nad->ecur++;
+
+    /* set up req'd parts of new elem */
+    nad->elems[elem].parent = parent;
+    nad->elems[elem].lname = strlen(name);
+    nad->elems[elem].iname = _nad_cdata(nad,name,nad->elems[elem].lname);
+    nad->elems[elem].attr = -1;
+    nad->elems[elem].ns = nad->scope; nad->scope = -1;
+    nad->elems[elem].itail = nad->elems[elem].ltail = 0;
+    nad->elems[elem].my_ns = ns;
+
+    /* add cdata if given */
+    if(cdata != NULL)
+    {
+        nad->elems[elem].lcdata = strlen(cdata);
+        nad->elems[elem].icdata = _nad_cdata(nad,cdata,nad->elems[elem].lcdata);
+    }else{
+        nad->elems[elem].icdata = nad->elems[elem].lcdata = 0;
+    }
+
+    /* parent/child */
+    nad->elems[elem].depth = nad->elems[parent].depth + 1;
+
+    return elem;
+}
+
+/** wrap an element with another element */
+void nad_wrap_elem(nad_t nad, int elem, int ns, const char *name)
+{
+    int cur;
+
+    _nad_ptr_check(__func__, nad);
+
+    if(elem >= nad->ecur) return;
+
+    NAD_SAFE(nad->elems, (nad->ecur + 1) * sizeof(struct nad_elem_st), nad->elen);
+
+    /* relocate all the rest of the elems after us */
+    memmove(&nad->elems[elem + 1], &nad->elems[elem], (nad->ecur - elem) * sizeof(struct nad_elem_st));
+    nad->ecur++;
+
+    /* set up req'd parts of new elem */
+    nad->elems[elem].lname = strlen(name);
+    nad->elems[elem].iname = _nad_cdata(nad,name,nad->elems[elem].lname);
+    nad->elems[elem].attr = -1;
+    nad->elems[elem].ns = nad->scope; nad->scope = -1;
+    nad->elems[elem].itail = nad->elems[elem].ltail = 0;
+    nad->elems[elem].icdata = nad->elems[elem].lcdata = 0;
+    nad->elems[elem].my_ns = ns;
+
+    /* raise the bar on all the children */
+    nad->elems[elem+1].depth++;
+    for(cur = elem + 2; cur < nad->ecur && nad->elems[cur].depth > nad->elems[elem].depth; cur++) nad->elems[cur].depth++;
+
+    /* relink the parents */
+    nad->elems[elem].parent = nad->elems[elem + 1].parent;
+    nad->elems[elem + 1].parent = elem;
+}
+
+/** create a new elem on the list */
+int nad_append_elem(nad_t nad, int ns, const char *name, int depth)
+{
+    int elem;
+
+    _nad_ptr_check(__func__, nad);
+
+    /* make sure there's mem for us */
+    NAD_SAFE(nad->elems, (nad->ecur + 1) * sizeof(struct nad_elem_st), nad->elen);
+
+    elem = nad->ecur;
+    nad->ecur++;
+    nad->elems[elem].lname = strlen(name);
+    nad->elems[elem].iname = _nad_cdata(nad,name,nad->elems[elem].lname);
+    nad->elems[elem].icdata = nad->elems[elem].lcdata = 0;
+    nad->elems[elem].itail = nad->elems[elem].ltail = 0;
+    nad->elems[elem].attr = -1;
+    nad->elems[elem].ns = nad->scope; nad->scope = -1;
+    nad->elems[elem].depth = depth;
+    nad->elems[elem].my_ns = ns;
+
+    /* make sure there's mem in the depth array, then track us */
+    NAD_SAFE(nad->depths, (depth + 1) * sizeof(int), nad->dlen);
+    nad->depths[depth] = elem;
+
+    /* our parent is the previous guy in the depth array */
+    if(depth <= 0)
+        nad->elems[elem].parent = -1;
+    else
+        nad->elems[elem].parent = nad->depths[depth - 1];
+
+    return elem;
+}
+
+/** attach new attr to the last elem */
+int nad_append_attr(nad_t nad, int ns, const char *name, const char *val)
+{
+    _nad_ptr_check(__func__, nad);
+
+    return _nad_attr(nad, nad->ecur - 1, ns, name, val, 0);
+}
+
+/** append new cdata to the last elem */
+void nad_append_cdata(nad_t nad, const char *cdata, int len, int depth)
+{
+    int elem = nad->ecur - 1;
+
+    _nad_ptr_check(__func__, nad);
+
+    /* make sure this cdata is the child of the last elem to append */
+    if(nad->elems[elem].depth == depth - 1)
+    {
+        if(nad->elems[elem].icdata == 0)
+            nad->elems[elem].icdata = nad->ccur;
+        _nad_cdata(nad,cdata,len);
+        nad->elems[elem].lcdata += len;
+        return;
+    }
+
+    /* otherwise, pin the cdata on the tail of the last element at this depth */
+    elem = nad->depths[depth];
+    if(nad->elems[elem].itail == 0)
+        nad->elems[elem].itail = nad->ccur;
+    _nad_cdata(nad,cdata,len);
+    nad->elems[elem].ltail += len;
+}
+
+/** bring a new namespace into scope */
+int nad_add_namespace(nad_t nad, const char *uri, const char *prefix)
+{
+    int ns;
+
+    _nad_ptr_check(__func__, nad);
+
+    /* only add it if its not already in scope */
+    ns = nad_find_scoped_namespace(nad, uri, NULL);
+    if(ns >= 0)
+        return ns;
+
+    /* make sure there's mem for us */
+    NAD_SAFE(nad->nss, (nad->ncur + 1) * sizeof(struct nad_ns_st), nad->nlen);
+
+    ns = nad->ncur;
+    nad->ncur++;
+    nad->nss[ns].next = nad->scope;
+    nad->scope = ns;
+
+    nad->nss[ns].luri = strlen(uri);
+    nad->nss[ns].iuri = _nad_cdata(nad, uri, nad->nss[ns].luri);
+    if(prefix != NULL)
+    {
+        nad->nss[ns].lprefix = strlen(prefix);
+        nad->nss[ns].iprefix = _nad_cdata(nad, prefix, nad->nss[ns].lprefix);    
+    }
+    else
+        nad->nss[ns].iprefix = -1;
+
+    return ns;
+}
+
+/** declare a namespace on an already-existing element */
+int nad_append_namespace(nad_t nad, int elem, const char *uri, const char *prefix) {
+    int ns;
+
+    _nad_ptr_check(__func__, nad);
+
+    /* see if its already scoped on this element */
+    ns = nad_find_namespace(nad, elem, uri, NULL);
+    if(ns >= 0)
+        return ns;
+
+    /* make some room */
+    NAD_SAFE(nad->nss, (nad->ncur + 1) * sizeof(struct nad_ns_st), nad->nlen);
+
+    ns = nad->ncur;
+    nad->ncur++;
+    nad->nss[ns].next = nad->elems[elem].ns;
+    nad->elems[elem].ns = ns;
+
+    nad->nss[ns].luri = strlen(uri);
+    nad->nss[ns].iuri = _nad_cdata(nad, uri, nad->nss[ns].luri);
+    if(prefix != NULL)
+    {
+        nad->nss[ns].lprefix = strlen(prefix);
+        nad->nss[ns].iprefix = _nad_cdata(nad, prefix, nad->nss[ns].lprefix);    
+    }
+    else
+        nad->nss[ns].iprefix = -1;
+
+    return ns;
+}
+
+void _nad_escape(nad_t nad, int data, int len, int flag)
+{
+    char *c;
+    int ic;
+
+    if(len <= 0) return;
+
+    /* first, if told, find and escape ' */
+    while(flag >= 3 && (c = memchr(nad->cdata + data,'\'',len)) != NULL)
+    {
+        /* get offset */
+        ic = c - nad->cdata;
+
+        /* cute, eh?  handle other data before this normally */
+        _nad_escape(nad, data, ic - data, 2);
+
+        /* ensure enough space, and add our escaped &apos; */
+        NAD_SAFE(nad->cdata, nad->ccur + 6, nad->clen);
+        memcpy(nad->cdata + nad->ccur, "&apos;", 6);
+        nad->ccur += 6;
+
+        /* just update and loop for more */
+        len -= (ic+1) - data;
+        data = ic+1;
+    }
+
+    /* next look for < */
+    while(flag >= 2 && (c = memchr(nad->cdata + data,'<',len)) != NULL)
+    {
+        ic = c - nad->cdata;
+        _nad_escape(nad, data, ic - data, 1);
+
+        /* ensure enough space, and add our escaped &lt; */
+        NAD_SAFE(nad->cdata, nad->ccur + 4, nad->clen);
+        memcpy(nad->cdata + nad->ccur, "&lt;", 4);
+        nad->ccur += 4;
+
+        /* just update and loop for more */
+        len -= (ic+1) - data;
+        data = ic+1;
+    }
+
+    /* check for ]]>, we need to escape the > */
+       /* WE DID THIS  (add the '0' as the first test to the while loop 
+               because the loops dies 3 lines in... (and we don't reall
+               need this)) ...
+        */
+    while( 0 && flag >= 1 && (c = memchr(nad->cdata + data, '>', len)) != NULL)
+    {
+        ic = c - nad->cdata;
+
+        _nad_escape(nad, data, ic - data, 0);
+
+        /* check for the sequence */
+
+        if( c >= nad->cdata + 2 && c[-1] == ']' && c[-2] == ']')
+        {
+            /* ensure enough space, and add our escaped &gt; */
+            NAD_SAFE(nad->cdata, nad->ccur + 4, nad->clen);
+            memcpy(nad->cdata + nad->ccur, "&gt;", 4);
+            nad->ccur += 4;
+        }
+
+        /* otherwise, just plug the > in as-is */
+        else
+        {
+            NAD_SAFE(nad->cdata, nad->ccur + 1, nad->clen);
+            *(nad->cdata + nad->ccur) = '>';
+            nad->ccur++;
+        }
+
+        /* just update and loop for more */
+        len -= (ic+1) - data;
+        data = ic+1;
+    }
+
+    /* if & is found, escape it */
+    while((c = memchr(nad->cdata + data,'&',len)) != NULL)
+    {
+        ic = c - nad->cdata;
+
+        /* ensure enough space */
+        NAD_SAFE(nad->cdata, nad->ccur + 5 + (ic - data), nad->clen);
+
+        /* handle normal data */
+        memcpy(nad->cdata + nad->ccur, nad->cdata + data, (ic - data));
+        nad->ccur += (ic - data);
+
+        /* append escaped &lt; */
+        memcpy(nad->cdata + nad->ccur, "&amp;", 5);
+        nad->ccur += 5;
+
+        /* just update and loop for more */
+        len -= (ic+1) - data;
+        data = ic+1;
+    }
+
+    /* nothing exciting, just append normal cdata */
+    NAD_SAFE(nad->cdata, nad->ccur + len, nad->clen);
+    memcpy(nad->cdata + nad->ccur, nad->cdata + data, len);
+    nad->ccur += len;
+}
+
+/** internal recursive printing function */
+int _nad_lp0(nad_t nad, int elem)
+{
+    int attr;
+    int ndepth;
+    int ns;
+
+    /* there's a lot of code in here, but don't let that scare you, it's just duplication in order to be a bit more efficient cpu-wise */
+
+    /* this whole thing is in a big loop for processing siblings */
+    while(elem != nad->ecur)
+    {
+
+    /* make enough space for the opening element */
+    ns = nad->elems[elem].my_ns;
+    if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+    {
+        NAD_SAFE(nad->cdata, nad->ccur + nad->elems[elem].lname + nad->nss[ns].lprefix + 2, nad->clen);
+    } else {
+        NAD_SAFE(nad->cdata, nad->ccur + nad->elems[elem].lname + 1, nad->clen);
+    }
+
+    /* opening tag */
+    *(nad->cdata + nad->ccur++) = '<';
+
+    /* add the prefix if necessary */
+    if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+    {
+        memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix);
+        nad->ccur += nad->nss[ns].lprefix;
+        *(nad->cdata + nad->ccur++) = ':';
+    }
+    
+    /* copy in the name */
+    memcpy(nad->cdata + nad->ccur, nad->cdata + nad->elems[elem].iname, nad->elems[elem].lname);
+    nad->ccur += nad->elems[elem].lname;
+
+    /* add the namespaces */
+    for(ns = nad->elems[elem].ns; ns >= 0; ns = nad->nss[ns].next)
+    {
+        /* never explicitly declare the implicit xml namespace */
+        if(nad->nss[ns].luri == strlen(uri_XML) && strncmp(uri_XML, nad->cdata + nad->nss[ns].iuri, nad->nss[ns].luri) == 0)
+            continue;
+
+        /* make space */
+        if(nad->nss[ns].iprefix >= 0)
+        {
+            NAD_SAFE(nad->cdata, nad->ccur + nad->nss[ns].luri + nad->nss[ns].lprefix + 10, nad->clen);
+        } else {
+            NAD_SAFE(nad->cdata, nad->ccur + nad->nss[ns].luri + 9, nad->clen);
+        }
+
+        /* start */
+        memcpy(nad->cdata + nad->ccur, " xmlns", 6);
+        nad->ccur += 6;
+
+        /* prefix if necessary */
+        if(nad->nss[ns].iprefix >= 0)
+        {
+            *(nad->cdata + nad->ccur++) = ':';
+            memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix);
+            nad->ccur += nad->nss[ns].lprefix;
+        }
+
+        *(nad->cdata + nad->ccur++) = '=';
+        *(nad->cdata + nad->ccur++) = '\'';
+
+        /* uri */
+        memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iuri, nad->nss[ns].luri);
+        nad->ccur += nad->nss[ns].luri;
+
+        *(nad->cdata + nad->ccur++) = '\'';
+    }
+
+    for(attr = nad->elems[elem].attr; attr >= 0; attr = nad->attrs[attr].next)
+    {
+        if(nad->attrs[attr].lname <= 0) continue;
+
+        /* make enough space for the wrapper part */
+        ns = nad->attrs[attr].my_ns;
+        if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+        {
+            NAD_SAFE(nad->cdata, nad->ccur + nad->attrs[attr].lname + nad->nss[ns].lprefix + 4, nad->clen);
+        } else {
+            NAD_SAFE(nad->cdata, nad->ccur + nad->attrs[attr].lname + 3, nad->clen);
+        }
+
+        *(nad->cdata + nad->ccur++) = ' ';
+
+        /* add the prefix if necessary */
+        if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+        {
+            memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix);
+            nad->ccur += nad->nss[ns].lprefix;
+            *(nad->cdata + nad->ccur++) = ':';
+        }
+    
+        /* copy in the name parts */
+        memcpy(nad->cdata + nad->ccur, nad->cdata + nad->attrs[attr].iname, nad->attrs[attr].lname);
+        nad->ccur += nad->attrs[attr].lname;
+        *(nad->cdata + nad->ccur++) = '=';
+        *(nad->cdata + nad->ccur++) = '\'';
+
+        /* copy in the escaped value */
+        _nad_escape(nad, nad->attrs[attr].ival, nad->attrs[attr].lval, 3);
+
+        /* make enough space for the closing quote and add it */
+        NAD_SAFE(nad->cdata, nad->ccur + 1, nad->clen);
+        *(nad->cdata + nad->ccur++) = '\'';
+    }
+
+    /* figure out what's next */
+    if(elem+1 == nad->ecur)
+        ndepth = -1;
+    else
+        ndepth = nad->elems[elem+1].depth;
+
+    /* handle based on if there are children, update nelem after done */
+    if(ndepth <= nad->elems[elem].depth)
+    {
+        /* make sure there's enough for what we could need */
+        NAD_SAFE(nad->cdata, nad->ccur + 2, nad->clen);
+        if(nad->elems[elem].lcdata == 0)
+        {
+            memcpy(nad->cdata + nad->ccur, "/>", 2);
+            nad->ccur += 2;
+        }else{
+            *(nad->cdata + nad->ccur++) = '>';
+
+            /* copy in escaped cdata */
+            _nad_escape(nad, nad->elems[elem].icdata, nad->elems[elem].lcdata,2);
+
+            /* make room */
+            ns = nad->elems[elem].my_ns;
+            if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+            {
+                NAD_SAFE(nad->cdata, nad->ccur + 4 + nad->elems[elem].lname + nad->nss[ns].lprefix, nad->clen);
+            } else {
+                NAD_SAFE(nad->cdata, nad->ccur + 3 + nad->elems[elem].lname, nad->clen);
+            }
+
+            /* close tag */
+            memcpy(nad->cdata + nad->ccur, "</", 2);
+            nad->ccur += 2;
+    
+            /* add the prefix if necessary */
+            if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+            {
+                memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix);
+                nad->ccur += nad->nss[ns].lprefix;
+                *(nad->cdata + nad->ccur++) = ':';
+            }
+    
+            memcpy(nad->cdata + nad->ccur, nad->cdata + nad->elems[elem].iname, nad->elems[elem].lname);
+            nad->ccur += nad->elems[elem].lname;
+            *(nad->cdata + nad->ccur++) = '>';
+        }
+
+        /* always try to append the tail */
+        _nad_escape(nad, nad->elems[elem].itail, nad->elems[elem].ltail,2);
+
+        /* if no siblings either, bail */
+        if(ndepth < nad->elems[elem].depth)
+            return elem+1;
+
+        /* next sibling */
+        elem++;
+    }else{
+        int nelem;
+        /* process any children */
+
+        /* close ourself and append any cdata first */
+        NAD_SAFE(nad->cdata, nad->ccur + 1, nad->clen);
+        *(nad->cdata + nad->ccur++) = '>';
+        _nad_escape(nad, nad->elems[elem].icdata, nad->elems[elem].lcdata,2);
+
+        /* process children */
+        nelem = _nad_lp0(nad,elem+1);
+
+        /* close and tail up */
+        ns = nad->elems[elem].my_ns;
+        if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+        {
+            NAD_SAFE(nad->cdata, nad->ccur + 4 + nad->elems[elem].lname + nad->nss[ns].lprefix, nad->clen);
+        } else {
+            NAD_SAFE(nad->cdata, nad->ccur + 3 + nad->elems[elem].lname, nad->clen);
+        }
+        memcpy(nad->cdata + nad->ccur, "</", 2);
+        nad->ccur += 2;
+        if(ns >= 0 && nad->nss[ns].iprefix >= 0)
+        {
+            memcpy(nad->cdata + nad->ccur, nad->cdata + nad->nss[ns].iprefix, nad->nss[ns].lprefix);
+            nad->ccur += nad->nss[ns].lprefix;
+            *(nad->cdata + nad->ccur++) = ':';
+        }
+        memcpy(nad->cdata + nad->ccur, nad->cdata + nad->elems[elem].iname, nad->elems[elem].lname);
+        nad->ccur += nad->elems[elem].lname;
+        *(nad->cdata + nad->ccur++) = '>';
+        _nad_escape(nad, nad->elems[elem].itail, nad->elems[elem].ltail,2);
+
+        /* if the next element is not our sibling, we're done */
+        if(nelem < nad->ecur && nad->elems[nelem].depth < nad->elems[elem].depth)
+            return nelem;
+
+        /* for next sibling in while loop */
+        elem = nelem;
+    }
+
+    /* here's the end of that big while loop */
+    }
+
+    return elem;
+}
+
+void nad_print(nad_t nad, int elem, char **xml, int *len)
+{
+    int ixml = nad->ccur;
+
+    _nad_ptr_check(__func__, nad);
+
+    _nad_lp0(nad,elem);
+    *len = nad->ccur - ixml;
+    *xml = nad->cdata + ixml;
+}
+
+/**
+ * nads serialize to a buffer of this form:
+ *
+ * [buflen][ecur][acur][ncur][ccur][elems][attrs][nss][cdata]
+ *
+ * nothing is done with endianness or word length, so the nad must be
+ * serialized and deserialized on the same platform
+ *
+ * buflen is not actually used by deserialize(), but is provided as a
+ * convenience to the application so it knows how many bytes to read before
+ * passing them in to deserialize()
+ *
+ * the depths array is not stored, so after deserialization
+ * nad_append_elem() and nad_append_cdata() will not work. this is rarely
+ * a problem
+ */
+
+void nad_serialize(nad_t nad, char **buf, int *len) {
+    char *pos;
+
+    _nad_ptr_check(__func__, nad);
+
+    *len = sizeof(int) * 5 + /* 4 ints in nad_t, plus one for len */
+           sizeof(struct nad_elem_st) * nad->ecur +
+           sizeof(struct nad_attr_st) * nad->acur +
+           sizeof(struct nad_ns_st) * nad->ncur +
+           sizeof(char) * nad->ccur;
+
+    *buf = (char *) malloc(*len);
+    pos = *buf;
+
+    * (int *) pos = *len;       pos += sizeof(int);
+    * (int *) pos = nad->ecur;  pos += sizeof(int);
+    * (int *) pos = nad->acur;  pos += sizeof(int);
+    * (int *) pos = nad->ncur;  pos += sizeof(int);
+    * (int *) pos = nad->ccur;  pos += sizeof(int);
+
+    memcpy(pos, nad->elems, sizeof(struct nad_elem_st) * nad->ecur);    pos += sizeof(struct nad_elem_st) * nad->ecur;
+    memcpy(pos, nad->attrs, sizeof(struct nad_attr_st) * nad->acur);    pos += sizeof(struct nad_attr_st) * nad->acur;
+    memcpy(pos, nad->nss, sizeof(struct nad_ns_st) * nad->ncur);        pos += sizeof(struct nad_ns_st) * nad->ncur;
+    memcpy(pos, nad->cdata, sizeof(char) * nad->ccur);
+}
+
+nad_t nad_deserialize(nad_cache_t cache, const char *buf) {
+    nad_t nad = nad_new(cache);
+    const char *pos = buf + sizeof(int);  /* skip len */
+
+    _nad_ptr_check(__func__, nad);
+
+    nad->ecur = * (int *) pos; pos += sizeof(int);
+    nad->acur = * (int *) pos; pos += sizeof(int);
+    nad->ncur = * (int *) pos; pos += sizeof(int);
+    nad->ccur = * (int *) pos; pos += sizeof(int);
+    nad->elen = nad->ecur;
+    nad->alen = nad->acur;
+    nad->nlen = nad->ncur;
+    nad->clen = nad->ccur;
+
+    if(nad->ecur > 0)
+    {
+        nad->elems = (struct nad_elem_st *) malloc(sizeof(struct nad_elem_st) * nad->ecur);
+        memcpy(nad->elems, pos, sizeof(struct nad_elem_st) * nad->ecur);
+        pos += sizeof(struct nad_elem_st) * nad->ecur;
+    }
+
+    if(nad->acur > 0)
+    {
+        nad->attrs = (struct nad_attr_st *) malloc(sizeof(struct nad_attr_st) * nad->acur);
+        memcpy(nad->attrs, pos, sizeof(struct nad_attr_st) * nad->acur);
+        pos += sizeof(struct nad_attr_st) * nad->acur;
+    }
+
+    if(nad->ncur > 0)
+    {
+        nad->nss = (struct nad_ns_st *) malloc(sizeof(struct nad_ns_st) * nad->ncur);
+        memcpy(nad->nss, pos, sizeof(struct nad_ns_st) * nad->ncur);
+        pos += sizeof(struct nad_ns_st) * nad->ncur;
+    }
+
+    if(nad->ccur > 0)
+    {
+        nad->cdata = (char *) malloc(sizeof(char) * nad->ccur);
+        memcpy(nad->cdata, pos, sizeof(char) * nad->ccur);
+    }
+
+    return nad;
+}
+
+#ifdef HAVE_EXPAT
+
+/** parse a buffer into a nad */
+
+struct build_data {
+    nad_t               nad;
+    int                 depth;
+};
+
+static void _nad_parse_element_start(void *arg, const char *name, const char **atts) {
+    struct build_data *bd = (struct build_data *) arg;
+    char buf[1024];
+    char *uri, *elem, *prefix;
+    const char **attr;
+    int ns;
+
+    /* make a copy */
+    strncpy(buf, name, 1024);
+    buf[1023] = '\0';
+
+    /* expat gives us:
+         prefixed namespaced elem: uri|elem|prefix
+          default namespaced elem: uri|elem
+               un-namespaced elem: elem
+     */
+
+    /* extract all the bits */
+    uri = buf;
+    elem = strchr(uri, '|');
+    if(elem != NULL) {
+        *elem = '\0';
+        elem++;
+        prefix = strchr(elem, '|');
+        if(prefix != NULL) {
+            *prefix = '\0';
+            prefix++;
+        }
+        ns = nad_add_namespace(bd->nad, uri, prefix);
+    } else {
+        /* un-namespaced, just take it as-is */
+        uri = NULL;
+        elem = buf;
+        prefix = NULL;
+        ns = -1;
+    }
+
+    /* add it */
+    nad_append_elem(bd->nad, ns, elem, bd->depth);
+
+    /* now the attributes, one at a time */
+    attr = atts;
+    while(attr[0] != NULL) {
+
+        /* make a copy */
+        strncpy(buf, attr[0], 1024);
+        buf[1023] = '\0';
+
+        /* extract all the bits */
+        uri = buf;
+        elem = strchr(uri, '|');
+        if(elem != NULL) {
+            *elem = '\0';
+            elem++;
+            prefix = strchr(elem, '|');
+            if(prefix != NULL) {
+                *prefix = '\0';
+                prefix++;
+            }
+            ns = nad_add_namespace(bd->nad, uri, prefix);
+        } else {
+            /* un-namespaced, just take it as-is */
+            uri = NULL;
+            elem = buf;
+            prefix = NULL;
+            ns = -1;
+        }
+
+        /* add it */
+        nad_append_attr(bd->nad, ns, elem, (char *) attr[1]);
+
+        attr += 2;
+    }
+
+    bd->depth++;
+}
+
+static void _nad_parse_element_end(void *arg, const char *name) {
+    struct build_data *bd = (struct build_data *) arg;
+
+    bd->depth--;
+}
+
+static void _nad_parse_cdata(void *arg, const char *str, int len) {
+    struct build_data *bd = (struct build_data *) arg;
+
+    /* go */
+    nad_append_cdata(bd->nad, (char *) str, len, bd->depth);
+}
+
+static void _nad_parse_namespace_start(void *arg, const char *prefix, const char *uri) {
+    struct build_data *bd = (struct build_data *) arg;
+
+    nad_add_namespace(bd->nad, (char *) uri, (char *) prefix);
+}
+
+nad_t nad_parse(nad_cache_t cache, const char *buf, int len) {
+    struct build_data bd;
+    XML_Parser p;
+
+    if(len == 0)
+        len = strlen(buf);
+
+    p = XML_ParserCreateNS(NULL, '|');
+    if(p == NULL)
+        return NULL;
+
+    bd.nad = nad_new(cache);
+    bd.depth = 0;
+
+    XML_SetUserData(p, (void *) &bd);
+    XML_SetElementHandler(p, _nad_parse_element_start, _nad_parse_element_end);
+    XML_SetCharacterDataHandler(p, _nad_parse_cdata);
+    XML_SetStartNamespaceDeclHandler(p, _nad_parse_namespace_start);
+
+    if(!XML_Parse(p, buf, len, 1)) {
+        XML_ParserFree(p);
+        nad_free(bd.nad);
+        return NULL;
+    }
+
+    XML_ParserFree(p);
+
+    if(bd.depth != 0)
+        return NULL;
+
+    return bd.nad;
+}
+
+#endif
diff --git a/src/perlmods/JSON.pm b/src/perlmods/JSON.pm
new file mode 100644 (file)
index 0000000..18454b5
--- /dev/null
@@ -0,0 +1,235 @@
+package JSON::number;
+sub new {
+       my $class = shift;
+       my $x = shift || $class;
+       return bless \$x => __PACKAGE__;
+}
+use overload ( '""' => \&toString );
+use overload ( '0+' => sub { ${$_[0]} } );
+
+sub toString { defined($_[1]) ? ${$_[1]} : ${$_[0]} }
+
+package JSON::bool::true;
+sub new { return bless {} => __PACKAGE__ }
+use overload ( '""' => \&toString );
+use overload ( 'bool' => sub { 1 } );
+use overload ( '0+' => sub { 1 } );
+
+sub toString { 'true' }
+
+package JSON::bool::false;
+sub new { return bless {} => __PACKAGE__ }
+use overload ( '""' => \&toString );
+use overload ( 'bool' => sub { 0 } );
+use overload ( '0+' => sub { 0 } );
+
+sub toString { 'false' }
+
+package JSON;
+use vars qw/%_class_map/;
+
+sub register_class_hint {
+       my $class = shift;
+       my %args = @_;
+
+       $_class_map{$args{hint}} = \%args;
+       $_class_map{$args{name}} = \%args;
+}
+
+sub JSON2perl {
+       my ($class, $json) = @_;
+       $json ||= $class;
+
+       $json =~ s/\/\/.+$//gmo; # remove C++ comments
+       $json =~ s/(?<!\\)\$/\\\$/gmo; # fixup $ for later
+       $json =~ s/(?<!\\)\@/\\\@/gmo; # fixup @ for later
+
+       my @casts;
+       my $casting_depth = 0;
+       my $current_cast;
+       my $output = '';
+       while ($json =~ s/^\s* ( 
+                                  {                            | # start object
+                                  \[                           | # start array
+                                  -?\d+\.?\d*                  | # number literal
+                                  "(?:(?:\\[\"])|[^\"])+"      | # string literal
+                                  (?:\/\*.+?\*\/)              | # C comment
+                                  true                         | # bool true
+                                  false                        | # bool false
+                                  null                         | # undef()
+                                  :                            | # object key-value sep
+                                  ,                            | # list sep
+                                  \]                           | # array end
+                                  }                              # object end
+                               )
+                        \s*//sox) {
+               my $element = $1;
+
+               if ($element eq 'null') {
+                       $output .= ' undef() ';
+                       next;
+               } elsif ($element =~ /^\/\*--\s*S\w*?\s+(\w+)\s*--\*\/$/) {
+                       my $hint = $1;
+                       if (exists $_class_map{$hint}) {
+                               $casts[$casting_depth] = $hint;
+                               $output .= ' bless(';
+                       }
+                       next;
+               } elsif ($element =~ /^\/\*/) {
+                       next;
+               } elsif ($element =~ /^\d/) {
+                       $output .= "do { JSON::number::new($element) }";
+                       next;
+               } elsif ($element eq '{' or $element eq '[') {
+                       $casting_depth++;
+               } elsif ($element eq '}' or $element eq ']') {
+                       $casting_depth--;
+                       my $hint = $casts[$casting_depth];
+                       $casts[$casting_depth] = undef;
+                       if (defined $hint and exists $_class_map{$hint}) {
+                               $output .= $element . ',"'. $_class_map{$hint}{name} . '")';
+                               next;
+                       }
+               } elsif ($element eq ':') {
+                       $output .= ' => ';
+                       next;
+               } elsif ($element eq 'true') {
+                       $output .= 'bless( {}, "JSON::bool::true")';
+                       next;
+               } elsif ($element eq 'false') {
+                       $output .= 'bless( {}, "JSON::bool::false")';
+                       next;
+               }
+               
+               $output .= $element;
+       }
+
+       return eval $output;
+}
+
+sub perl2JSON {
+       my ($class, $perl) = @_;
+       $perl ||= $class;
+
+       my $output = '';
+       if (!defined($perl)) {
+               $output = 'null';
+       } elsif (ref($perl) and ref($perl) =~ /^JSON/) {
+               $output .= $perl;
+       } elsif ( ref($perl) && exists($_class_map{ref($perl)}) ) {
+               $output .= '/*--S '.$_class_map{ref($perl)}{hint}.'--*/';
+               if (lc($_class_map{ref($perl)}{type}) eq 'hash') {
+                       my %hash =  %$perl;
+                       $output .= perl2JSON(\%hash);
+               } elsif (lc($_class_map{ref($perl)}{type}) eq 'array') {
+                       my @array =  @$perl;
+                       $output .= perl2JSON(\@array);
+               }
+               $output .= '/*--E '.$_class_map{ref($perl)}{hint}.'--*/';
+       } elsif (ref($perl) and ref($perl) =~ /HASH/) {
+               $output .= '{';
+               my $c = 0;
+               for my $key (sort keys %$perl) {
+                       $output .= ',' if ($c); 
+                       
+                       $output .= perl2JSON($key).':'.perl2JSON($$perl{$key});
+                       $c++;
+               }
+               $output .= '}';
+       } elsif (ref($perl) and ref($perl) =~ /ARRAY/) {
+               $output .= '[';
+               my $c = 0;
+               for my $part (@$perl) {
+                       $output .= ',' if ($c); 
+                       
+                       $output .= perl2JSON($part);
+                       $c++;
+               }
+               $output .= ']';
+       } else {
+               $perl =~ s/\\/\\\\/sgo;
+               $perl =~ s/"/\\"/sgo;
+               $perl =~ s/\t/\\t/sgo;
+               $perl =~ s/\f/\\f/sgo;
+               $perl =~ s/\r/\\r/sgo;
+               $perl =~ s/\n/\\n/sgo;
+               $output = '"'.$perl.'"';
+       }
+
+       return $output;
+}
+
+my $depth = 0;
+sub perl2prettyJSON {
+       my ($class, $perl, $nospace) = @_;
+       $perl ||= $class;
+
+       my $output = '';
+       if (!defined($perl)) {
+               $output = 'null';
+       } elsif (ref($perl) and ref($perl) =~ /^JSON/) {
+               $output .= $perl;
+       } elsif ( ref($perl) && exists($_class_map{ref($perl)}) ) {
+               $depth++;
+               $output .= "\n";
+               $output .= "   "x$depth;
+               $output .= '/*--S '.$_class_map{ref($perl)}{hint}."--*/ ";
+               if (lc($_class_map{ref($perl)}{type}) eq 'hash') {
+                       my %hash =  %$perl;
+                       $output .= perl2prettyJSON(\%hash,undef,1);
+               } elsif (lc($_class_map{ref($perl)}{type}) eq 'array') {
+                       my @array =  @$perl;
+                       $output .= perl2prettyJSON(\@array,undef,1);
+               }
+               #$output .= "   "x$depth;
+               $output .= ' /*--E '.$_class_map{ref($perl)}{hint}.'--*/';
+               $depth--;
+       } elsif (ref($perl) and ref($perl) =~ /HASH/) {
+               #$depth++;
+               $output .= "   "x$depth unless ($nospace);
+               $output .= "{\n";
+               my $c = 0;
+               $depth++;
+               for my $key (sort keys %$perl) {
+                       $output .= ",\n" if ($c); 
+                       
+                       $output .= perl2prettyJSON($key)." : ".perl2prettyJSON($$perl{$key}, undef, 1);
+                       $c++;
+               }
+               $depth--;
+               $output .= "\n";
+               $output .= "   "x$depth;
+               $output .= '}';
+               #$depth--;
+       } elsif (ref($perl) and ref($perl) =~ /ARRAY/) {
+               #$depth++;
+               $output .= "   "x$depth unless ($nospace);
+               $output .= "[\n";
+               my $c = 0;
+               $depth++;
+               for my $part (@$perl) {
+                       $output .= ",\n" if ($c); 
+                       
+                       $output .= perl2prettyJSON($part);
+                       $c++;
+               }
+               $depth--;
+               $output .= "\n";
+               $output .= "   "x$depth;
+               $output .= "]";
+               #$depth--;
+       } else {
+               $perl =~ s/\\/\\\\/sgo;
+               $perl =~ s/"/\\"/sgo;
+               $perl =~ s/\t/\\t/sgo;
+               $perl =~ s/\f/\\f/sgo;
+               $perl =~ s/\r/\\r/sgo;
+               $perl =~ s/\n/\\n/sgo;
+               $output .= "   "x$depth unless($nospace);
+               $output .= '"'.$perl.'"';
+       }
+
+       return $output;
+}
+
+1;
diff --git a/src/perlmods/OpenSRF.pm b/src/perlmods/OpenSRF.pm
new file mode 100644 (file)
index 0000000..18d157a
--- /dev/null
@@ -0,0 +1,75 @@
+package OpenILS;
+use strict;
+use Error;
+use vars qw/$VERSION $AUTOLOAD/;
+$VERSION = do { my @r=(q$Revision$=~/\d+/g); sprintf "%d."."%02d"x$#r,@r };
+
+=head1 OpenILS
+
+=cut
+
+=head2 Overview
+
+ Top level class for OpenILS perl modules.
+
+=cut
+
+# Exception base classes
+#use Exception::Class
+#      ( OpenILSException => { fields => [ 'errno' ] });
+#push @Exception::Class::ISA, 'Error';
+
+=head3 AUTOLOAD()
+
+ Traps methods calls for methods that have not been defined so they
+ don't propogate up the class hierarchy.
+
+=cut
+sub AUTOLOAD {
+       my $self = shift;
+       my $type = ref($self) || $self;
+       my $name = $AUTOLOAD;
+       my $otype = ref $self;
+       
+       my ($package, $filename, $line) = caller;
+       my ($package1, $filename1, $line1) = caller(1);
+       my ($package2, $filename2, $line2) = caller(2);
+       my ($package3, $filename3, $line3) = caller(3);
+       my ($package4, $filename4, $line4) = caller(4);
+       my ($package5, $filename5, $line5) = caller(5);
+       $name =~ s/.*://;   # strip fully-qualified portion
+       warn <<"        WARN";
+****
+** ${name}() isn't there.  Please create me somewhere (like in $type)!
+** Error at $package ($filename), line $line
+** Call Stack (5 deep):
+**     $package1 ($filename1), line $line1
+**     $package2 ($filename2), line $line2
+**     $package3 ($filename3), line $line3
+**     $package4 ($filename4), line $line4
+**     $package5 ($filename5), line $line5
+** Object type was $otype
+****
+       WARN
+}
+
+
+
+=head3 alert_abstract()
+
+ This method is called by abstract methods to ensure that
+ the process dies when an undefined abstract method is called
+
+=cut
+sub alert_abstract() {
+       my $c = shift;
+       my $class = ref( $c ) || $c;
+       my ($file, $line, $method) = (caller(1))[1..3];
+       die " * Call to abstract method $method at $file, line $line";
+}
+
+sub class {
+       return scalar(caller);
+}
+
+1;
diff --git a/src/perlmods/OpenSRF/AppSession.pm b/src/perlmods/OpenSRF/AppSession.pm
new file mode 100644 (file)
index 0000000..0fa3d45
--- /dev/null
@@ -0,0 +1,827 @@
+package OpenSRF::AppSession;
+use OpenSRF::DOM;
+use OpenSRF::DOM::Element::userAuth;
+use OpenSRF::DomainObject::oilsMessage;
+use OpenSRF::DomainObject::oilsMethod;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::Transport::PeerHandle;
+use OpenSRF::Utils::Config;
+use OpenSRF::Utils::Logger qw(:level);
+use OpenSRF::EX;
+use OpenSRF;
+use Exporter;
+use base qw/Exporter OpenSRF/;
+use Time::HiRes qw( time usleep );
+use warnings;
+use strict;
+
+our @EXPORT_OK = qw/CONNECTING INIT_CONNECTED CONNECTED DISCONNECTED CLIENT SERVER/;
+our %EXPORT_TAGS = ( state => [ qw/CONNECTING INIT_CONNECTED CONNECTED DISCONNECTED/ ],
+                endpoint => [ qw/CLIENT SERVER/ ],
+);
+
+my $logger = "OpenSRF::Utils::Logger";
+
+our %_CACHE;
+our @_CLIENT_CACHE;
+our @_RESEND_QUEUE;
+
+sub kill_client_session_cache {
+       for my $session ( @_CLIENT_CACHE ) {
+               $session->kill_me;
+       }
+}
+
+sub CONNECTING { return 3 };
+sub INIT_CONNECTED { return 4 };
+sub CONNECTED { return 1 };
+sub DISCONNECTED { return 2 };
+
+sub CLIENT { return 2 };
+sub SERVER { return 1 };
+
+sub find {
+       return undef unless (defined $_[1]);
+       return $_CACHE{$_[1]} if (exists($_CACHE{$_[1]}));
+}
+
+sub find_client {
+       my( $self, $app ) = @_;
+       $logger->debug( "Client Cache contains: " .scalar(@_CLIENT_CACHE), INTERNAL );
+       my ($client) = grep { $_->[0] eq $app and $_->[1] == 1 } @_CLIENT_CACHE;
+       $client->[1] = 0;
+       return $client->[2];
+}
+
+sub transport_connected {
+       my $self = shift;
+       if( ! exists $self->{peer_handle} || 
+                                       ! $self->{peer_handle} ) {
+               return 0;
+       }
+       return $self->{peer_handle}->tcp_connected();
+}
+
+# ----------------------------------------------------------------------------
+# Clears the transport buffers
+# call this if you are not through with the sesssion, but you want 
+# to have a clean slate.  You shouldn't have to call this if
+# you are correctly 'recv'ing all of the data from a request.
+# however, if you don't want all of the data, this will
+# slough off any excess
+#  * * Note: This will delete data for all sessions using this transport
+# handle.  For example, all client sessions use the same handle.
+# ----------------------------------------------------------------------------
+sub buffer_reset {
+
+       my $self = shift;
+       if( ! exists $self->{peer_handle} || ! $self->{peer_handle} ) { 
+               return 0;
+       }
+       $self->{peer_handle}->buffer_reset();
+}
+
+
+sub client_cache {
+       my $self = shift;
+       push @_CLIENT_CACHE, [ $self->app, 1, $self ];
+}
+
+# when any incoming data is received, this method is called.
+sub server_build {
+       my $class = shift;
+       $class = ref($class) || $class;
+
+       my $sess_id = shift;
+       my $remote_id = shift;
+       my $service = shift;
+
+       return undef unless ($sess_id and $remote_id and $service);
+
+       my $conf = OpenSRF::Utils::Config->current;
+       
+       if( ! $conf ) { 
+               OpenSRF::EX::Config->throw( "No suitable config found" ); 
+       }
+
+       if ( my $thingy = $class->find($sess_id) ) {
+               $thingy->remote_id( $remote_id );
+               $logger->debug( "AppSession returning existing session $sess_id", DEBUG );
+               return $thingy;
+       } else {
+               $logger->debug( "AppSession building new server session $sess_id", DEBUG );
+       }
+
+       if( $service eq "client" ) {
+               #throw OpenSRF::EX::PANIC ("Attempting to build a client session as a server" .
+               #       " Session ID [$sess_id], remote_id [$remote_id]");
+               $logger->debug("Attempting to build a client session as ".
+                               "a server Session ID [$sess_id], remote_id [$remote_id]", ERROR );
+       }
+
+
+       my $max_requests = $conf->$service->max_requests;
+       $logger->debug( "Max Requests for $service is $max_requests", INTERNAL );# if $max_requests;
+
+       $logger->transport( "AppSession creating new session: $sess_id", INTERNAL );
+
+       my $self = bless { recv_queue  => [],
+                          request_queue  => [],
+                          requests  => 0,
+                          endpoint    => SERVER,
+                          state       => CONNECTING, 
+                          session_id  => $sess_id,
+                          remote_id    => $remote_id,
+                               peer_handle => OpenSRF::Transport::PeerHandle->retrieve($service),
+                               max_requests => $max_requests,
+                               session_threadTrace => 0,
+                               service => $service,
+                        } => $class;
+
+       return $_CACHE{$sess_id} = $self;
+}
+
+sub service { return shift()->{service}; }
+
+sub continue_request {
+       my $self = shift;
+       $self->{'requests'}++;
+       return $self->{'requests'} <= $self->{'max_requests'} ? 1 : 0;
+}
+
+sub last_sent_payload {
+       my( $self, $payload ) = @_;
+       if( $payload ) {
+               return $self->{'last_sent_payload'} = $payload;
+       }
+       return $self->{'last_sent_payload'};
+}
+
+sub last_sent_type {
+       my( $self, $type ) = @_;
+       if( $type ) {
+               return $self->{'last_sent_type'} = $type;
+       }
+       return $self->{'last_sent_type'};
+}
+
+# When we're a client and we want to connect to a remote service
+# create( $app, username => $user, secret => $passwd );
+#    OR
+# create( $app, sysname => $user, secret => $shared_secret );
+sub create {
+       my $class = shift;
+       $class = ref($class) || $class;
+
+       my $app = shift;
+       my %auth_args = @_;
+
+
+       unless ( $app &&
+                exists($auth_args{secret}) &&
+                ( exists($auth_args{username}) ||
+                  exists($auth_args{sysname}) ) ) {
+               throw OpenSRF::EX::User ( 'Insufficient authentication information for session creation');
+       }
+
+       if( my $thingy = OpenSRF::AppSession->find_client( $app ) ) {
+                       $logger->debug( "AppSession returning existing client session for $app", DEBUG );
+                       return $thingy;
+       } else {
+               $logger->debug( "AppSession creating new client session for $app", DEBUG );
+       }
+
+
+       
+       my $auth = OpenSRF::DOM::Element::userAuth->new( %auth_args );
+
+       my $conf = OpenSRF::Utils::Config->current;
+
+       if( ! $conf ) { 
+               OpenSRF::EX::Config->throw( "No suitable config found" ); 
+       }
+
+       my $sess_id = time . rand( $$ );
+       while ( $class->find($sess_id) ) {
+               $sess_id = time . rand( $$ );
+       }
+
+       my $r_id = $conf->$app->transport_target ||
+                       die("No remote id for $app!");
+
+       my $self = bless { app_name    => $app,
+                          client_auth => $auth,
+                          #recv_queue  => [],
+                          request_queue  => [],
+                          endpoint    => CLIENT,
+                          state       => DISCONNECTED,#since we're init'ing
+                          session_id  => $sess_id,
+                          remote_id   => $r_id,
+                          orig_remote_id   => $r_id,
+                               # peer_handle => OpenSRF::Transport::PeerHandle->retrieve($app),
+                               peer_handle => OpenSRF::Transport::PeerHandle->retrieve("client"),
+                               session_threadTrace => 0,
+                        } => $class;
+
+       $self->client_cache();
+       $_CACHE{$sess_id} = $self;
+       return $self->find_client( $app );
+}
+
+sub app {
+       return shift()->{app_name};
+}
+
+sub reset {
+       my $self = shift;
+       $self->remote_id($$self{orig_remote_id});
+}
+
+# 'connect' can be used as a constructor if called as a class method,
+# or used to connect a session that has disconnectd if called against
+# an existing session that seems to be disconnected, or was just built
+# using 'create' above.
+
+# connect( $app, username => $user, secret => $passwd );
+#    OR
+# connect( $app, sysname => $user, secret => $shared_secret );
+
+# --- Returns undef if the connect attempt times out.
+# --- Returns the OpenSRF::EX object if one is returned by the server
+# --- Returns self if connected
+sub connect {
+       my $self = shift;
+       my $class = ref($self) || $self;
+
+       return $self if ( ref( $self ) and  $self->state && $self->state == CONNECTED  );
+
+       my $app = shift;
+
+       $self = $class->create($app, @_) if (!ref($self));
+       return undef unless ($self);
+
+
+       $self->reset;
+       $self->state(CONNECTING);
+       $self->send('CONNECT', "");
+
+       my $time_remaining = OpenSRF::Utils::Config->current->client->connect_timeout;
+
+       while ( $self->state != CONNECTED  and $time_remaining > 0 ) {
+               my $starttime = time;
+               $self->queue_wait($time_remaining);
+               my $endtime = time;
+               $time_remaining -= ($endtime - $starttime);
+       }
+
+       return undef unless($self->state == CONNECTED);
+
+       return $self;
+}
+
+sub finish {
+       my $self = shift;
+       #$self->disconnect if ($self->endpoint == CLIENT);
+       for my $ses ( @_CLIENT_CACHE ) {
+               if ($ses->[2]->session_id eq $self->session_id) {
+                       $ses->[1] = 1;
+               }
+       }
+}
+
+sub kill_me {
+       my $self = shift;
+       if( ! $self->session_id ) { return 0; }
+       $self->disconnect;
+       $logger->transport( "AppSession killing self: " . $self->session_id(), DEBUG );
+       my @a;
+       for my $ses ( @_CLIENT_CACHE ) {
+               push @a, $ses 
+                       if ($ses->[2]->session_id ne $self->session_id);
+       }
+       @_CLIENT_CACHE = @a;
+       delete $_CACHE{$self->session_id};
+       delete($$self{$_}) for (keys %$self);
+}
+
+sub disconnect {
+       my $self = shift;
+       unless( $self->state == DISCONNECTED ) {
+               $self->send('DISCONNECT', "") if ($self->endpoint == CLIENT);;
+               $self->state( DISCONNECTED ); 
+       }
+       $self->reset;
+}
+
+sub request {
+       my $self = shift;
+       my $meth = shift;
+
+       my $method;
+       if (!ref $meth) {
+               $method = new OpenSRF::DomainObject::oilsMethod ( method => $meth );
+       } else {
+               $method = $meth;
+       }
+       
+       $method->params( @_ );
+
+       $self->send('REQUEST',$method);
+}
+
+
+sub send {
+       my $self = shift;
+       my @payload_list = @_; # this is a Domain Object
+
+       $logger->debug( "In send", INTERNAL );
+       
+       my $tT;
+
+       if( @payload_list % 2 ) { $tT = pop @payload_list; }
+
+       if( ! @payload_list ) {
+               $logger->debug( "payload_list param is incomplete in AppSession::send()", ERROR );
+               return undef; 
+       }
+
+       my $doc = OpenSRF::DOM->createDocument();
+
+       $logger->debug( "In send2", INTERNAL );
+
+       my $disconnect = 0;
+       my $connecting = 0;
+
+       while( @payload_list ) {
+
+               my ($msg_type, $payload) = ( shift(@payload_list), shift(@payload_list) ); 
+
+               if ($msg_type eq 'DISCONNECT' ) {
+                       $disconnect++;
+                       if( $self->state == DISCONNECTED) {
+                               next;
+                       }
+               }
+
+               if( $msg_type eq "CONNECT" ) { $connecting++; }
+
+
+               if( $payload ) {
+                       $logger->debug( "Payload is ".$payload->toString, INTERNAL );
+               }
+       
+       
+               my $msg = OpenSRF::DomainObject::oilsMessage->new();
+               $logger->debug( "AppSession after creating oilsMessage $msg", INTERNAL );
+               
+               $msg->type($msg_type);
+               $logger->debug( "AppSession after adding type" . $msg->toString(), INTERNAL );
+       
+               $msg->userAuth($self->client_auth) if ($self->endpoint == CLIENT && $msg_type eq 'CONNECT');
+       
+               no warnings;
+               $msg->threadTrace( $tT || int($self->session_threadTrace) || int($self->last_threadTrace) );
+               use warnings;
+       
+               if ($msg->type eq 'REQUEST') {
+                       if ( !defined($tT) || $self->last_threadTrace != $tT ) {
+                               $msg->update_threadTrace;
+                               $self->session_threadTrace( $msg->threadTrace );
+                               $tT = $self->session_threadTrace;
+                               OpenSRF::AppRequest->new($self, $payload);
+                       }
+               }
+       
+               $msg->protocol(1);
+               $msg->payload($payload) if $payload;
+       
+               $doc->documentElement->appendChild( $msg );
+
+       
+               $logger->debug( "AppSession sending ".$msg->type." to ".$self->remote_id.
+                       " with threadTrace [".$msg->threadTrace."]", INFO );
+
+       }
+       
+       if ($self->endpoint == CLIENT and ! $disconnect) {
+               $self->queue_wait(0);
+               unless ($self->state == CONNECTED || ($self->state == CONNECTING && $connecting )) {
+                       my $v = $self->connect();
+                       if( ! $v ) {
+                               $logger->debug( "Unable to connect to remote service in AppSession::send()", ERROR );
+                               return undef;
+                       }
+                       if( $v and $v->class->isa( "OpenSRF::EX" ) ) {
+                               return $v;
+                       }
+               }
+       } 
+
+       $logger->debug( "AppSession sending doc: " . $doc->toString(), INTERNAL );
+
+
+       $self->{peer_handle}->send( 
+                                       to     => $self->remote_id,
+                                  thread => $self->session_id,
+                                  body   => $doc->toString );
+
+       return $self->app_request( $tT );
+}
+
+sub app_request {
+       my $self = shift;
+       my $tT = shift;
+       
+       return undef unless (defined $tT);
+       my ($req) = grep { $_->threadTrace == $tT } @{ $self->{request_queue} };
+
+       return $req;
+}
+
+sub remove_app_request {
+       my $self = shift;
+       my $req = shift;
+       
+       my @list = grep { $_->threadTrace != $req->threadTrace } @{ $self->{request_queue} };
+
+       $self->{request_queue} = \@list;
+}
+
+sub endpoint {
+       return $_[0]->{endpoint};
+}
+
+
+sub session_id {
+       my $self = shift;
+       return $self->{session_id};
+}
+
+sub push_queue {
+       my $self = shift;
+       my $resp = shift;
+       my $req = $self->app_request($resp->[1]);
+       return $req->push_queue( $resp->[0] ) if ($req);
+       push @{ $self->{recv_queue} }, $resp->[0];
+}
+
+sub last_threadTrace {
+       my $self = shift;
+       my $new_last_threadTrace = shift;
+
+       my $old_last_threadTrace = $self->{last_threadTrace};
+       if (defined $new_last_threadTrace) {
+               $self->{last_threadTrace} = $new_last_threadTrace;
+               return $new_last_threadTrace unless ($old_last_threadTrace);
+       }
+
+       return $old_last_threadTrace;
+}
+
+sub session_threadTrace {
+       my $self = shift;
+       my $new_last_threadTrace = shift;
+
+       my $old_last_threadTrace = $self->{session_threadTrace};
+       if (defined $new_last_threadTrace) {
+               $self->{session_threadTrace} = $new_last_threadTrace;
+               return $new_last_threadTrace unless ($old_last_threadTrace);
+       }
+
+       return $old_last_threadTrace;
+}
+
+sub last_message_type {
+       my $self = shift;
+       my $new_last_message_type = shift;
+
+       my $old_last_message_type = $self->{last_message_type};
+       if (defined $new_last_message_type) {
+               $self->{last_message_type} = $new_last_message_type;
+               return $new_last_message_type unless ($old_last_message_type);
+       }
+
+       return $old_last_message_type;
+}
+
+sub last_message_protocol {
+       my $self = shift;
+       my $new_last_message_protocol = shift;
+
+       my $old_last_message_protocol = $self->{last_message_protocol};
+       if (defined $new_last_message_protocol) {
+               $self->{last_message_protocol} = $new_last_message_protocol;
+               return $new_last_message_protocol unless ($old_last_message_protocol);
+       }
+
+       return $old_last_message_protocol;
+}
+
+sub remote_id {
+       my $self = shift;
+       my $new_remote_id = shift;
+
+       my $old_remote_id = $self->{remote_id};
+       if (defined $new_remote_id) {
+               $self->{remote_id} = $new_remote_id;
+               return $new_remote_id unless ($old_remote_id);
+       }
+
+       return $old_remote_id;
+}
+
+sub client_auth {
+       my $self = shift;
+       my $new_ua = shift;
+
+       my $old_ua = $self->{client_auth};
+       if (defined $new_ua) {
+               $self->{client_auth} = $new_ua;
+               return $new_ua unless ($old_ua);
+       }
+
+       return $old_ua->cloneNode(1);
+}
+
+sub state {
+       my $self = shift;
+       my $new_state = shift;
+
+       my $old_state = $self->{state};
+       if (defined $new_state) {
+               $self->{state} = $new_state;
+               return $new_state unless ($old_state);
+       }
+
+       return $old_state;
+}
+
+sub DESTROY {
+       my $self = shift;
+       delete $$self{$_} for keys %$self;
+       return undef;
+}
+
+sub recv {
+       my $self = shift;
+       my @proto_args = @_;
+       my %args;
+
+       if ( @proto_args ) {
+               if ( !(@proto_args % 2) ) {
+                       %args = @proto_args;
+               } elsif (@proto_args == 1) {
+                       %args = ( timeout => @proto_args );
+               }
+       }
+
+       #$logger->debug( ref($self). " recv_queue before wait: " . $self->_print_queue(), INTERNAL );
+
+       if( exists( $args{timeout} ) ) {
+               $args{timeout} = int($args{timeout});
+       } else {
+               $args{timeout} = 10;
+       }
+
+       #$args{timeout} = 0 if ($self->complete);
+
+       $logger->debug( ref($self) ."->recv with timeout " . $args{timeout}, INTERNAL );
+
+       $args{count} ||= 1;
+
+       my $avail = @{ $self->{recv_queue} };
+       my $time_remaining = $args{timeout};
+
+       while ( $avail < $args{count} and $time_remaining > 0 ) {
+               last if $self->complete;
+               my $starttime = time;
+               $self->queue_wait($time_remaining);
+               my $endtime = time;
+               $time_remaining -= ($endtime - $starttime);
+               $avail = @{ $self->{recv_queue} };
+       }
+
+       #$logger->debug( ref($self)." queue after wait: " . $self->_print_queue(), INTERNAL );
+               
+       my @list;
+       while ( my $msg = shift @{ $self->{recv_queue} } ) {
+               push @list, $msg;
+               last if (scalar(@list) >= $args{count});
+       }
+
+#      $self->{recv_queue} = [@unlist, @{ $self->{recv_queue} }];
+       $logger->debug( "Number of matched responses: " . @list, DEBUG );
+
+       $self->queue_wait(0); # check for statuses
+       
+       return $list[0] unless (wantarray);
+       return @list;
+}
+
+sub push_resend {
+       my $self = shift;
+       push @OpenSRF::AppSession::_RESEND_QUEUE, @_;
+}
+
+sub flush_resend {
+       my $self = shift;
+       $logger->debug( "Resending..." . @_RESEND_QUEUE, DEBUG );
+       while ( my $req = shift @OpenSRF::AppSession::_RESEND_QUEUE ) {
+               $req->resend;
+       }
+}
+
+
+sub queue_wait {
+       my $self = shift;
+       if( ! $self->{peer_handle} ) { return 0; }
+       my $timeout = shift || 0;
+       $logger->debug( "Calling queue_wait($timeout)" , DEBUG );
+       $logger->debug( "Timestamp before process($timeout) : " . $logger->format_time(), INTERNAL );
+       my $o = $self->{peer_handle}->process($timeout);
+       $logger->debug( "Timestamp after  process($timeout) : " . $logger->format_time(), INTERNAL );
+       $self->flush_resend;
+       return $o;
+}
+
+sub _print_queue {
+       my( $self ) = @_;
+       my $string = "";
+       foreach my $msg ( @{$self->{recv_queue}} ) {
+               $string = $string . $msg->toString(1) . "\n";
+       }
+       return $string;
+}
+
+sub status {
+       my $self = shift;
+       $self->send( 'STATUS', @_ );
+}
+
+#-------------------------------------------------------------------------------
+
+package OpenSRF::AppRequest;
+use base qw/OpenSRF::AppSession/;
+use OpenSRF::Utils::Logger qw/:level/;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+
+sub new {
+       my $class = shift;
+       $class = ref($class) || $class;
+
+       my $session = shift;
+       my $threadTrace = $session->session_threadTrace || $session->last_threadTrace;
+       my $payload = shift;
+       
+       my $self = {    session     => $session,
+                       threadTrace => $threadTrace,
+                       payload     => $payload,
+                       complete    => 0,
+                       recv_queue  => [],
+       };
+
+       bless $self => $class;
+
+       push @{ $self->session->{request_queue} }, $self;
+
+       return $self;
+}
+
+sub queue_size {
+       my $size = @{$_[0]->{recv_queue}};
+       return $size;
+}
+       
+sub send {
+       shift()->session->send(@_);
+}
+
+sub finish {
+       my $self = shift;
+       $self->session->remove_app_request($self);
+       delete($$self{$_}) for (keys %$self);
+}
+
+sub session {
+       return shift()->{session};
+}
+
+sub complete {
+       my $self = shift;
+       my $complete = shift;
+       return $self->{complete} if ($self->{complete});
+       if (defined $complete) {
+               $self->{complete} = $complete;
+       } else {
+               $self->session->queue_wait(0);
+       }
+       return $self->{complete};
+}
+
+sub wait_complete {
+       my $self = shift;
+       my $timeout = shift || 1;
+       my $time_remaining = $timeout;
+
+       while ( ! $self->complete  and $time_remaining > 0 ) {
+               my $starttime = time;
+               $self->queue_wait($time_remaining);
+               my $endtime = time;
+               $time_remaining -= ($endtime - $starttime);
+       }
+
+       return $self->complete;
+}
+
+sub threadTrace {
+       return shift()->{threadTrace};
+}
+
+sub push_queue {
+       my $self = shift;
+       my $resp = shift;
+       push @{ $self->{recv_queue} }, $resp;
+       OpenSRF::Utils::Logger->debug( "AppRequest pushed ".$resp->toString(), INTERNAL );
+}
+
+sub queue_wait {
+       my $self = shift;
+       OpenSRF::Utils::Logger->debug( "Calling queue_wait(@_)", DEBUG );
+       return $self->session->queue_wait(@_)
+}
+
+sub payload { return shift()->{payload}; }
+
+sub resend {
+       my $self = shift;
+       OpenSRF::Utils::Logger->debug(
+               "I'm resending the request for threadTrace ". $self->threadTrace, DEBUG);
+       OpenSRF::Utils::Logger->debug($self->payload->toString,INTERNAL);
+       return $self->session->send('REQUEST', $self->payload, $self->threadTrace );
+}
+
+sub status {
+       my $self = shift;
+       my $msg = shift;
+       $self->session->send( 'STATUS',$msg, $self->threadTrace );
+}
+
+sub respond {
+       my $self = shift;
+       my $msg = shift;
+
+       my $response;
+       if (!ref($msg) || ($msg->can('getAttribute') && $msg->getAttribute('name') !~ /oilsResult/)) {
+               $response = new OpenSRF::DomainObject::oilsResult;
+               $response->content($msg);
+       } else {
+               $response = $msg;
+       }
+
+       $self->session->send('RESULT', $response, $self->threadTrace);
+}
+
+sub respond_complete {
+       my $self = shift;
+       my $msg = shift;
+
+       my $response;
+       if (!ref($msg) || ($msg->can('getAttribute') && $msg->getAttribute('name') !~ /oilsResult/)) {
+               $response = new OpenSRF::DomainObject::oilsResult;
+               $response->content($msg);
+       } else {
+               $response = $msg;
+       }
+
+       my $stat = OpenSRF::DomainObject::oilsConnectStatus->new(
+               statusCode => STATUS_COMPLETE(),
+               status => 'Request Complete' );
+
+       $self->session->send( 'RESULT' => $response, 'STATUS' => $stat, $self->threadTrace);
+
+}
+
+
+1;
+
+
+__END__
+
+[client]
+interval:connect_timeout = 2 seconds
+
+[servers]
+subsection:StorageServer = Storage_config
+
+[Storage_config]
+#transport = xmlrpc
+#transport_target = http://open-ils.org/RPC2/services/Storage
+
+transport = jabber
+transport_target = Storage@open-ils.org/SERVICE_RECEIVER
+method_class = OpenSRF::App::Storage::PGStore;
+
+
+
+
+
diff --git a/src/perlmods/OpenSRF/Application.pm b/src/perlmods/OpenSRF/Application.pm
new file mode 100644 (file)
index 0000000..13f80dc
--- /dev/null
@@ -0,0 +1,222 @@
+package OpenSRF::Application;
+use base qw/OpenSRF/;
+use OpenSRF::AppSession;
+use OpenSRF::DomainObject::oilsMethod;
+use OpenSRF::DomainObject::oilsResponse qw/:status/;
+use OpenSRF::Utils::Logger qw/:level/;
+use Time::HiRes qw/time/;
+use vars qw/$_app $log/;
+use OpenSRF::EX qw/:try/;
+use strict;
+use warnings;
+
+$log = 'OpenSRF::Utils::Logger';
+
+our $in_request = 0;
+our @pending_requests;
+
+sub application_implementation {
+       my $self = shift;
+       my $app = shift;
+
+       if (defined $app) {
+               $_app = $app;
+               eval "use $_app;";
+               if( $@ ) {
+                       $log->error( "Error loading application_implementation: $app -> $@", ERROR);
+               }
+
+       }
+
+       return $_app;
+}
+
+sub handler {
+       my ($self, $session, $app_msg) = @_;
+
+       $log->debug( "In Application::handler()", DEBUG );
+
+       my $app = $self->application_implementation;
+
+       if( $app ) {
+               $log->debug( "Application is $app", DEBUG);
+       }
+       $log->debug( "Message is ".$app_msg->toString(1), INTERNAL);
+
+
+       if ($session->last_message_type eq 'REQUEST') {
+               $log->debug( "We got a REQUEST: ". $app_msg->method, INFO );
+
+               my $method_name = $app_msg->method;
+               $log->debug( " * Looking up $method_name inside $app", DEBUG);
+
+               my $method_proto = $session->last_message_protocol;
+               $log->debug( " * Method API Level [$method_proto]", DEBUG);
+
+               my $coderef = $app->method_lookup( $method_name, $method_proto );
+
+               unless ($coderef) {
+                       $session->status( OpenSRF::DomainObject::oilsMethodException->new() );
+                       return 1;
+               }
+
+               #if ( $session->client_auth->username || $session->client_auth->userid ) {
+               #       unless ( $coderef->is_action ) {
+               #               $session->status(
+               #                       OpenSRF::DomainObject::oilsMethodException->new(
+               #                                       statusCode => STATUS_NOTALLOWED(),
+               #                                       status => "User cannot use [$method_name]" ) );
+               #               return 1;
+               #       }
+               #}
+                       
+
+               $log->debug( " (we got coderef $coderef", DEBUG);
+
+               unless ($session->continue_request) {
+                       $session->status(
+                               OpenSRF::DomainObject::oilsConnectStatus->new(
+                                               statusCode => STATUS_REDIRECTED(),
+                                               status => 'Disconnect on max requests' ) );
+                       $session->kill_me;
+                       return 1;
+               }
+
+               if (ref $coderef) {
+                       my @args = $app_msg->params;
+                       my $appreq = OpenSRF::AppRequest->new( $session );
+
+                       $log->debug( "in_request = $in_request : [" . $appreq->threadTrace."]", DEBUG );
+                       if( $in_request ) {
+                               $log->debug( "Pushing onto pending requests: " . $appreq->threadTrace, DEBUG );
+                               push @pending_requests, [ $appreq, \@args, $coderef ]; 
+                               return 1;
+                       }
+
+
+                       $in_request++;
+
+                       $log->debug( "Executing coderef for {$method_name -> ".join(', ', @args)."}", INTERNAL );
+
+                       my $resp;
+                       try {
+                               my $start = time();
+                               $resp = $coderef->run( $appreq, @args); 
+                               my $time = sprintf '%.3f', time() - $start;
+                               $log->debug( "Method duration for {$method_name -> ".join(', ', @args)."}:  ". $time, DEBUG );
+                               if( ref( $resp ) ) {
+                                       $log->debug( "Calling respond_complete: ". $resp->toString(), INTERNAL );
+                                       $appreq->respond_complete( $resp );
+                               } else {
+                                       $appreq->status( OpenSRF::DomainObject::oilsConnectStatus->new(
+                                                               statusCode => STATUS_COMPLETE(),
+                                                               status => 'Request Complete' ) );
+                               }
+                       } catch Error with {
+                               my $e = shift;
+                               $e = $e->{-text} || $e->message if (ref $e);
+                               my $sess_id = $session->session_id;
+                               $session->status(
+                                       OpenSRF::DomainObject::oilsMethodException->new(
+                                                       statusCode      => STATUS_INTERNALSERVERERROR(),
+                                                       status          => " *** Call to [$method_name] failed for session ".
+                                                                          "[$sess_id], thread trace [".$appreq->threadTrace."]:\n".$e
+                                       )
+                               );
+                       };
+
+
+
+                       # ----------------------------------------------
+
+
+                       # XXX may need this later
+                       # $_->[1] = 1 for (@OpenSRF::AppSession::_CLIENT_CACHE);
+
+                       $in_request--;
+
+                       $log->debug( "Pending Requests: " . scalar(@pending_requests), INTERNAL );
+
+                       # cycle through queued requests
+                       while( my $aref = shift @pending_requests ) {
+                               $in_request++;
+                               my $resp;
+                               try {
+                                       my $start = time;
+                                       my $response = $aref->[2]->run( $aref->[0], @{$aref->[1]} );
+                                       my $time = sprintf '%.3f', time - $start;
+                                       $log->debug( "Method duration for {[".$aref->[2]->name." -> ".join(', ',@{$aref->[1]}).'}:  '.$time, DEBUG );
+
+&n