LP#1709710: Make chunk sizing smart about XML quoting
[OpenSRF.git] / src / libopensrf / osrf_app_session.c
1 /**
2         @file osrf_app_session.c
3         @brief Implementation of osrfAppSession.
4 */
5
6 #include <time.h>
7 #include "opensrf/osrf_app_session.h"
8 #include "opensrf/osrf_stack.h"
9
10 static char* current_ingress = NULL;
11
12 struct osrf_app_request_struct {
13         /** The controlling session. */
14         struct osrf_app_session_struct* session;
15
16         /** Request id.  It is the same as the thread_trace of the REQUEST message
17                 for which it was created.
18         */
19         int request_id;
20         /** True if we have received a 'request complete' message from our request. */
21         int complete;
22         /** The original REQUEST message payload. */
23         osrfMessage* payload;
24         /** Linked list of responses to the request. */
25         osrfMessage* result;
26
27     /** Buffer used to collect partial response messages */
28     growing_buffer* part_response_buffer;
29
30         /** Boolean; if true, then a call that is waiting on a response will reset the
31         timeout and set this variable back to false. */
32         int reset_timeout;
33         /** Linkage pointers for a linked list.  We maintain a hash table of pending requests,
34             and each slot of the hash table is a doubly linked list. */
35         osrfAppRequest* next;
36         osrfAppRequest* prev;
37 };
38
39 static inline unsigned int request_id_hash( int req_id );
40 static osrfAppRequest* find_app_request( const osrfAppSession* session, int req_id );
41 static void add_app_request( osrfAppSession* session, osrfAppRequest* req );
42
43 /* Send the given message */
44 static int _osrf_app_session_send( osrfAppSession*, osrfMessage* msg );
45
46 static int osrfAppSessionMakeLocaleRequest(
47                 osrfAppSession* session, const jsonObject* params, const char* method_name,
48                 int protocol, osrfStringArray* param_strings, char* locale );
49
50 /** @brief The global session cache.
51
52         Key: session_id.  Data: osrfAppSession.
53 */
54 static osrfHash* osrfAppSessionCache = NULL;
55
56 // --------------------------------------------------------------------------
57 // Request API
58 // --------------------------------------------------------------------------
59
60 /**
61         @brief Create a new osrfAppRequest.
62         @param session Pointer to the osrfAppSession that will own the new osrfAppRequest.
63         @param msg Pointer to the osrfMessage representing the request.
64         @return Pointer to the new osrfAppRequest.
65
66         The calling code is responsible for freeing the osrfAppRequest by calling
67         _osrf_app_request_free().
68 */
69 static osrfAppRequest* _osrf_app_request_init(
70                 osrfAppSession* session, osrfMessage* msg ) {
71
72         osrfAppRequest* req = safe_malloc(sizeof(osrfAppRequest));
73
74         req->session        = session;
75         req->request_id     = msg->thread_trace;
76         req->complete       = 0;
77         req->payload        = msg;
78         req->result         = NULL;
79         req->reset_timeout  = 0;
80         req->next           = NULL;
81         req->prev           = NULL;
82         req->part_response_buffer = NULL;
83
84         return req;
85 }
86
87
88 /**
89         @brief Free an osrfAppRequest and everything it owns.
90         @param req Pointer to an osrfAppRequest.
91 */
92 static void _osrf_app_request_free( osrfAppRequest * req ) {
93         if( req ) {
94                 if( req->payload )
95                         osrfMessageFree( req->payload );
96
97                 /* Free the messages in the result queue */
98                 osrfMessage* next_msg;
99                 while( req->result ) {
100                         next_msg = req->result->next;
101                         osrfMessageFree( req->result );
102                         req->result = next_msg;
103                 }
104
105         if (req->part_response_buffer)
106             buffer_free(req->part_response_buffer);
107
108                 free( req );
109         }
110 }
111
112 /**
113         @brief Append a new message to the list of responses to a request.
114         @param req Pointer to the osrfAppRequest for the original REQUEST message.
115         @param result Pointer to an osrfMessage received in response to the request.
116
117         For each osrfAppRequest we maintain a linked list of response messages, and traverse
118         it to find the end.
119 */
120 static void _osrf_app_request_push_queue( osrfAppRequest* req, osrfMessage* result ){
121         if(req == NULL || result == NULL)
122                 return;
123
124     if (result->status_code == OSRF_STATUS_PARTIAL) {
125         osrfLogDebug(OSRF_LOG_MARK, "received partial message response");
126
127         if (!req->part_response_buffer) {
128             // assume the max_chunk_size of the server matches ours for
129             // buffer initialization,  since the setting will usually be 
130             // a site-wide value.
131                 req->part_response_buffer = buffer_init(OSRF_MSG_CHUNK_SIZE + 1);
132         }
133
134         const char* partial = jsonObjectGetString(result->_result_content);
135
136         if (partial != NULL) {
137             osrfLogDebug(OSRF_LOG_MARK, 
138                 "adding %d bytes to response buffer", strlen(partial));
139         
140             // add the partial contents of the message to the buffer
141             buffer_add(req->part_response_buffer, partial);
142         }
143
144         // all done.  req and result are freed by the caller
145         return;
146
147     } else if (result->status_code == OSRF_STATUS_NOCONTENT) {
148         if (req->part_response_buffer && req->part_response_buffer->n_used) {
149
150             // part_response_buffer contains a stitched-together JSON string
151             osrfLogDebug(OSRF_LOG_MARK, 
152                 "partial response complete, parsing %d bytes", 
153                 req->part_response_buffer->n_used);
154
155             // coerce the partial-complete response into a standard RESULT.
156             osrf_message_set_status_info(result, NULL, "OK", OSRF_STATUS_OK);
157
158             // use the stitched-together JSON string as the result conten
159             osrf_message_set_result_content(
160                 result, req->part_response_buffer->buf);
161
162             // free string, keep the buffer
163             buffer_reset(req->part_response_buffer); 
164
165         } else {
166             osrfLogDebug(OSRF_LOG_MARK, 
167                 "Received OSRF_STATUS_NOCONTENT with no preceeding content");
168             return;
169         }
170     }
171
172         osrfLogDebug( OSRF_LOG_MARK, "App Session pushing request [%d] onto request queue",
173                         result->thread_trace );
174
175         if(req->result == NULL) {
176                 req->result = result;   // Add the first node
177
178         } else {
179
180                 // Find the last node in the list, and append the new node to it
181                 osrfMessage* ptr = req->result;
182                 osrfMessage* ptr2 = req->result->next;
183                 while( ptr2 ) {
184                         ptr = ptr2;
185                         ptr2 = ptr2->next;
186                 }
187                 ptr->next = result;
188         }
189 }
190
191 /**
192         @brief Remove an osrfAppRequest (identified by request_id) from an osrfAppSession.
193         @param session Pointer to the osrfAppSession that owns the osrfAppRequest.
194         @param req_id request_id of the osrfAppRequest to be removed.
195 */
196 void osrf_app_session_request_finish( osrfAppSession* session, int req_id ) {
197
198         if( session ) {
199                 // Search the hash table for the request in question
200                 unsigned int index = request_id_hash( req_id );
201                 osrfAppRequest* old_req = session->request_hash[ index ];
202                 while( old_req ) {
203                         if( old_req->request_id == req_id )
204                                 break;
205                         else
206                                 old_req = old_req->next;
207                 }
208
209                 if( old_req ) {
210                         // Remove the request from the doubly linked list
211                         if( old_req->prev )
212                                 old_req->prev->next = old_req->next;
213                         else
214                                 session->request_hash[ index ] = old_req->next;
215
216                         if( old_req->next )
217                                 old_req->next->prev = old_req->prev;
218
219                         _osrf_app_request_free( old_req );
220                 }
221         }
222 }
223
224 /**
225         @brief Derive a hash key from a request id.
226         @param req_id The request id.
227         @return The corresponding hash key; an index into request_hash[].
228
229         If OSRF_REQUEST_HASH_SIZE is a power of two, then this calculation should
230         reduce to a binary AND.
231 */
232 static inline unsigned int request_id_hash( int req_id ) {
233         return ((unsigned int) req_id ) % OSRF_REQUEST_HASH_SIZE;
234 }
235
236 /**
237         @brief Search for an osrfAppRequest in the hash table, given a request id.
238         @param session Pointer to the relevant osrfAppSession.
239         @param req_id The request_id of the osrfAppRequest being sought.
240         @return A pointer to the osrfAppRequest if found, or NULL if not.
241 */
242 static osrfAppRequest* find_app_request( const osrfAppSession* session, int req_id ) {
243
244         osrfAppRequest* req = session->request_hash[ request_id_hash( req_id) ];
245         while( req ) {
246                 if( req->request_id == req_id )
247                         break;
248                 else
249                         req = req->next;
250         }
251
252         return req;
253 }
254
255 /**
256         @brief Add an osrfAppRequest to the hash table of a given osrfAppSession.
257         @param session Pointer to the session to which the request belongs.
258         @param req Pointer to the osrfAppRequest to be stored.
259
260         Find the right spot in the hash table; then add the request to the linked list at that
261         spot.  We just add it to the head of the list, without trying to maintain any particular
262         ordering.
263 */
264 static void add_app_request( osrfAppSession* session, osrfAppRequest* req ) {
265         if( session && req ) {
266                 unsigned int index = request_id_hash( req->request_id );
267                 req->next = session->request_hash[ index ];
268                 req->prev = NULL;
269                 session->request_hash[ index ] = req;
270         }
271 }
272
273 /**
274         @brief Request a reset of the timeout period for a request.
275         @param session Pointer to the relevant osrfAppSession.
276         @param req_id Request ID of the request whose timeout is to be reset.
277
278         This happens when a client receives a STATUS message with a status code
279         OSRF_STATUS_CONTINUE; in effect the server is asking for more time.
280
281         The request to be reset is identified by the combination of session and request id.
282 */
283 void osrf_app_session_request_reset_timeout( osrfAppSession* session, int req_id ) {
284         if(session == NULL)
285                 return;
286         osrfLogDebug( OSRF_LOG_MARK, "Resetting request timeout %d", req_id );
287         osrfAppRequest* req = find_app_request( session, req_id );
288         if( req )
289                 req->reset_timeout = 1;
290 }
291
292 /**
293         @brief Fetch the next response message to a given previous request, subject to a timeout.
294         @param req Pointer to the osrfAppRequest representing the request.
295         @param timeout Maxmimum time to wait, in seconds.
296
297         @return Pointer to the next osrfMessage for this request, if one is available, or if it
298         becomes available before the end of the timeout; otherwise NULL;
299
300         If there is already a message available in the input queue for this request, dequeue and
301         return it immediately.  Otherwise wait up to timeout seconds until you either get an
302         input message for the specified request, run out of time, or encounter an error.
303
304         If the only message we receive for this request is a STATUS message with a status code
305         OSRF_STATUS_COMPLETE, then return NULL.  That means that the server has nothing further
306         to send in response to this request.
307
308         You may also receive other messages for other requests, and other sessions.  These other
309         messages will be wholly or partially processed behind the scenes while you wait for the
310         one you want.
311 */
312 static osrfMessage* _osrf_app_request_recv( osrfAppRequest* req, int timeout ) {
313
314         if(req == NULL) return NULL;
315
316         if( req->result != NULL ) {
317                 /* Dequeue the next message in the list */
318                 osrfMessage* tmp_msg = req->result;
319                 req->result = req->result->next;
320                 return tmp_msg;
321         }
322
323         time_t start = time(NULL);
324         time_t remaining = (time_t) timeout;
325
326         // Wait repeatedly for input messages until you either receive one for the request
327         // you're interested in, run out of time, or encounter an error.
328         // Wait repeatedly because you may also receive messages for other requests, or for
329         // other sessions, and process them behind the scenes. These are not the messages
330         // you're looking for.
331         while( remaining >= 0 ) {
332                 /* tell the session to wait for stuff */
333                 osrfLogDebug( OSRF_LOG_MARK,  "In app_request receive with remaining time [%d]",
334                                 (int) remaining );
335
336
337                 osrf_app_session_queue_wait( req->session, 0, NULL );
338                 if(req->session->transport_error) {
339                         osrfLogError(OSRF_LOG_MARK, "Transport error in recv()");
340                         return NULL;
341                 }
342
343                 if( req->result != NULL ) { /* if we received any results for this request */
344                         /* dequeue the first message in the list */
345                         osrfLogDebug( OSRF_LOG_MARK, "app_request_recv received a message, returning it" );
346                         osrfMessage* ret_msg = req->result;
347                         req->result = ret_msg->next;
348                         if (ret_msg->sender_locale)
349                                 osrf_app_session_set_locale(req->session, ret_msg->sender_locale);
350
351                         return ret_msg;
352                 }
353
354                 if( req->complete )
355                         return NULL;
356
357                 osrf_app_session_queue_wait( req->session, (int) remaining, NULL );
358
359                 if(req->session->transport_error) {
360                         osrfLogError(OSRF_LOG_MARK, "Transport error in recv()");
361                         return NULL;
362                 }
363
364                 if( req->result != NULL ) { /* if we received any results for this request */
365                         /* dequeue the first message in the list */
366                         osrfLogDebug( OSRF_LOG_MARK,  "app_request_recv received a message, returning it");
367                         osrfMessage* ret_msg = req->result;
368                         req->result = ret_msg->next;
369                         if (ret_msg->sender_locale)
370                                 osrf_app_session_set_locale(req->session, ret_msg->sender_locale);
371
372                         return ret_msg;
373                 }
374
375                 if( req->complete )
376                         return NULL;
377
378                 // Determine how much time is left
379                 if(req->reset_timeout) {
380                         // We got a reprieve.  This happens when a client receives a STATUS message
381                         // with a status code OSRF_STATUS_CONTINUE.  We restart the timer from the
382                         // beginning -- but only once.  We reset reset_timeout to zero. so that a
383                         // second attempted reprieve will allow, at most, only one more second.
384                         remaining = (time_t) timeout;
385                         req->reset_timeout = 0;
386                         osrfLogDebug( OSRF_LOG_MARK, "Received a timeout reset");
387                 } else {
388                         remaining -= (int) (time(NULL) - start);
389                 }
390         }
391
392         // Timeout exhausted; no messages for the request in question
393         char* paramString = jsonObjectToJSON(req->payload->_params);
394         osrfLogInfo( OSRF_LOG_MARK, "Returning NULL from app_request_recv after timeout: %s %s",
395                 req->payload->method_name, paramString);
396         free(paramString);
397
398         return NULL;
399 }
400
401 // --------------------------------------------------------------------------
402 // Session API
403 // --------------------------------------------------------------------------
404
405 /**
406         @brief Install a copy of a locale string in a specified session.
407         @param session Pointer to the osrfAppSession in which the locale is to be installed.
408         @param locale The locale string to be copied and installed.
409         @return A pointer to the installed copy of the locale string.
410 */
411 char* osrf_app_session_set_locale( osrfAppSession* session, const char* locale ) {
412         if (!session || !locale)
413                 return NULL;
414
415         if(session->session_locale) {
416                 if( strlen(session->session_locale) >= strlen(locale) ) {
417                         /* There's room available; just copy */
418                         strcpy(session->session_locale, locale);
419                 } else {
420                         free(session->session_locale);
421                         session->session_locale = strdup( locale );
422                 }
423         } else {
424                 session->session_locale = strdup( locale );
425         }
426
427         return session->session_locale;
428 }
429
430 /**
431         @brief Install a copy of a TZ string in a specified session.
432         @param session Pointer to the osrfAppSession in which the TZ is to be installed.
433         @param TZ The TZ string to be copied and installed.
434         @return A pointer to the installed copy of the TZ string.
435 */
436 char* osrf_app_session_set_tz( osrfAppSession* session, const char* tz ) {
437         if (!session || !tz)
438                 return NULL;
439
440         if(session->session_tz) {
441                 if( strlen(session->session_tz) >= strlen(tz) ) {
442                         /* There's room available; just copy */
443                         strcpy(session->session_tz, tz);
444                 } else {
445                         free(session->session_tz);
446                         session->session_tz = strdup( tz );
447                 }
448         } else {
449                 session->session_tz = strdup( tz );
450         }
451
452         return session->session_tz;
453 }
454
455 /**
456         @brief Install a copy of a ingress string as the new default.
457         @param session Pointer to the new strdup'ed default_ingress
458         @param ingress The ingress string to be copied and installed.
459 */
460 char* osrfAppSessionSetIngress(const char* ingress) {
461         if (!ingress) return NULL;
462     if(current_ingress) 
463         free(current_ingress);
464     return current_ingress = strdup(ingress);
465 }
466
467 /**
468     @brief Returns the current ingress value
469     @return A pointer to the installed copy of the ingress string 
470 */
471 const char* osrfAppSessionGetIngress() {
472     return current_ingress;
473 }
474
475 /**
476         @brief Find the osrfAppSession for a given session id.
477         @param session_id The session id to look for.
478         @return Pointer to the corresponding osrfAppSession if found, or NULL if not.
479
480         Search the global session cache for the specified session id.
481 */
482 osrfAppSession* osrf_app_session_find_session( const char* session_id ) {
483         if(session_id)
484                 return osrfHashGet( osrfAppSessionCache, session_id );
485         return NULL;
486 }
487
488
489 /**
490         @brief Add a session to the global session cache, keyed by session id.
491         @param session Pointer to the osrfAppSession to be added.
492
493         If a cache doesn't exist yet, create one.  It's an osrfHash using session ids for the
494         key and osrfAppSessions for the data.
495 */
496 static void _osrf_app_session_push_session( osrfAppSession* session ) {
497         if( session ) {
498                 if( osrfAppSessionCache == NULL )
499                         osrfAppSessionCache = osrfNewHash();
500                 if( osrfHashGet( osrfAppSessionCache, session->session_id ) )
501                         return;   // A session with this id is already in the cache.  Shouldn't happen.
502                 osrfHashSet( osrfAppSessionCache, session, session->session_id );
503         }
504 }
505
506 /**
507         @brief Create an osrfAppSession for a client.
508         @param remote_service Name of the service to which to connect
509         @return Pointer to the new osrfAppSession if successful, or NULL upon error.
510
511         Allocate memory for an osrfAppSession, and initialize it as follows:
512
513         - For talking with Jabber, grab an existing transport_client.  It must have been
514         already set up by a prior call to osrfSystemBootstrapClientResc().
515         - Build a Jabber ID for addressing the service.
516         - Build a session ID based on a fine-grained timestamp and a process ID.  This ID is
517         intended to be unique across the system, but uniqueness is not strictly guaranteed.
518         - Initialize various other bits and scraps.
519         - Add the session to the global session cache.
520
521         Do @em not connect to the service at this point.
522 */
523 osrfAppSession* osrfAppSessionClientInit( const char* remote_service ) {
524
525         if (!remote_service) {
526                 osrfLogWarning( OSRF_LOG_MARK, "No remote service specified in osrfAppSessionClientInit");
527                 return NULL;
528         }
529
530         osrfAppSession* session = safe_malloc(sizeof(osrfAppSession));
531
532         // Grab an existing transport_client for talking with Jabber
533         session->transport_handle = osrfSystemGetTransportClient();
534         if( session->transport_handle == NULL ) {
535                 osrfLogWarning( OSRF_LOG_MARK, "No transport client for service 'client'");
536                 free( session );
537                 return NULL;
538         }
539
540         // Get a list of domain names from the config settings;
541         // ignore all but the first one in the list.
542         osrfStringArray* arr = osrfNewStringArray(8);
543         osrfConfigGetValueList(NULL, arr, "/domain");
544         const char* domain = osrfStringArrayGetString(arr, 0);
545         if (!domain) {
546                 osrfLogWarning( OSRF_LOG_MARK, "No domains specified in the OpenSRF config file");
547                 free( session );
548                 osrfStringArrayFree(arr);
549                 return NULL;
550         }
551
552         // Get a router name from the config settings.
553         char* router_name = osrfConfigGetValue(NULL, "/router_name");
554         if (!router_name) {
555                 osrfLogWarning( OSRF_LOG_MARK, "No router name specified in the OpenSRF config file");
556                 free( session );
557                 osrfStringArrayFree(arr);
558                 return NULL;
559         }
560
561         char target_buf[512];
562         target_buf[ 0 ] = '\0';
563
564         // Using the router name, domain, and service name,
565         // build a Jabber ID for addressing the service.
566         int len = snprintf( target_buf, sizeof(target_buf), "%s@%s/%s",
567                         router_name ? router_name : "(null)",
568                         domain ? domain : "(null)",
569                         remote_service ? remote_service : "(null)" );
570         osrfStringArrayFree(arr);
571         free(router_name);
572
573         if( len >= sizeof( target_buf ) ) {
574                 osrfLogWarning( OSRF_LOG_MARK, "Buffer overflow for remote_id");
575                 free( session );
576                 return NULL;
577         }
578
579         session->remote_id = strdup(target_buf);
580         session->orig_remote_id = strdup(session->remote_id);
581         session->remote_service = strdup(remote_service);
582         session->session_locale = NULL;
583         session->session_tz = NULL;
584         session->transport_error = 0;
585         session->panic = 0;
586         session->outbuf = NULL;   // Not used by client
587
588         #ifdef ASSUME_STATELESS
589         session->stateless = 1;
590         osrfLogDebug( OSRF_LOG_MARK, "%s session is stateless", remote_service );
591         #else
592         session->stateless = 0;
593         osrfLogDebug( OSRF_LOG_MARK, "%s session is NOT stateless", remote_service );
594         #endif
595
596         /* build a chunky, random session id */
597         char id[256];
598
599         snprintf(id, sizeof(id), "%f.%d%ld", get_timestamp_millis(), (int)time(NULL), (long) getpid());
600         session->session_id = strdup(id);
601         osrfLogDebug( OSRF_LOG_MARK,  "Building a new client session with id [%s] [%s]",
602                         session->remote_service, session->session_id );
603
604         session->thread_trace = 0;
605         session->state = OSRF_SESSION_DISCONNECTED;
606         session->type = OSRF_SESSION_CLIENT;
607
608         session->userData = NULL;
609         session->userDataFree = NULL;
610
611         // Initialize the hash table
612         int i;
613         for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i )
614                 session->request_hash[ i ] = NULL;
615
616         _osrf_app_session_push_session( session );
617         return session;
618 }
619
620 /**
621         @brief Create an osrfAppSession for a server.
622         @param session_id The session ID.  In practice this comes from the thread member of
623         the transport message from the client.
624         @param our_app The name of the service being provided.
625         @param remote_id Jabber ID of the client.
626         @return Pointer to the newly created osrfAppSession if successful, or NULL upon failure.
627
628         If there is already a session with the specified id, report an error.  Otherwise:
629
630         - Allocate memory for an osrfAppSession.
631         - For talking with Jabber, grab an existing transport_client.  It should have been
632         already set up by a prior call to osrfSystemBootstrapClientResc().
633         - Install a copy of the @a our_app string as remote_service.
634         - Install copies of the @a remote_id string as remote_id and orig_remote_id.
635         - Initialize various other bits and scraps.
636         - Add the session to the global session cache.
637
638         Do @em not respond to the client at this point.
639 */
640 osrfAppSession* osrf_app_server_session_init(
641                 const char* session_id, const char* our_app, const char* remote_id ) {
642
643         osrfLogDebug( OSRF_LOG_MARK, "Initing server session with session id %s, service %s,"
644                         " and remote_id %s", session_id, our_app, remote_id );
645
646         osrfAppSession* session = osrf_app_session_find_session( session_id );
647         if(session) {
648                 osrfLogWarning( OSRF_LOG_MARK, "App session already exists for session id %s",
649                                 session_id );
650                 return NULL;
651         }
652
653         session = safe_malloc(sizeof(osrfAppSession));
654
655         // Grab an existing transport_client for talking with Jabber
656         session->transport_handle = osrfSystemGetTransportClient();
657         if( session->transport_handle == NULL ) {
658                 osrfLogWarning( OSRF_LOG_MARK, "No transport client for service '%s'", our_app );
659                 free(session);
660                 return NULL;
661         }
662
663         // Decide from a config setting whether the session is stateless or not.  However
664         // this determination is pointless because it will immediately be overruled according
665         // to the compile-time macro ASSUME_STATELESS.
666         int stateless = 0;
667         char* statel = osrf_settings_host_value("/apps/%s/stateless", our_app );
668         if( statel )
669                 stateless = atoi( statel );
670         free(statel);
671
672         session->remote_id = strdup(remote_id);
673         session->orig_remote_id = strdup(remote_id);
674         session->session_id = strdup(session_id);
675         session->remote_service = strdup(our_app);
676         session->stateless = stateless;
677
678         #ifdef ASSUME_STATELESS
679         session->stateless = 1;
680         #else
681         session->stateless = 0;
682         #endif
683
684         session->thread_trace = 0;
685         session->state = OSRF_SESSION_DISCONNECTED;
686         session->type = OSRF_SESSION_SERVER;
687         session->session_locale = NULL;
688         session->session_tz = NULL;
689
690         session->userData = NULL;
691         session->userDataFree = NULL;
692         session->transport_error = 0;
693
694         // Initialize the hash table
695         int i;
696         for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i )
697                 session->request_hash[ i ] = NULL;
698
699         session->panic = 0;
700         session->outbuf = buffer_init( 4096 );
701
702         _osrf_app_session_push_session( session );
703         return session;
704 }
705
706 /**
707         @brief Create a REQUEST message, send it, and save it for future reference.
708         @param session Pointer to the current session, which has the addressing information.
709         @param params One way of specifying the parameters for the method.
710         @param method_name The name of the method to be called.
711         @param protocol Protocol.
712         @param param_strings Another way of specifying the parameters for the method.
713         @return The request ID of the resulting REQUEST message, or -1 upon error.
714
715         DEPRECATED.  Use osrfAppSessionSendRequest() instead.  It is identical except that it
716         doesn't use the param_strings argument, which is redundant, confusing, and unused.
717
718         If @a params is non-NULL, use it to specify the parameters to the method.  Otherwise
719         use @a param_strings.
720
721         If @a params points to a JSON_ARRAY, then pass each element of the array as a separate
722         parameter.  If @a params points to any other kind of jsonObject, pass it as a single
723         parameter.
724
725         If @a params is NULL, and @a param_strings is not NULL, then each pointer in the
726         osrfStringArray must point to a JSON string encoding a parameter.  Pass them.
727
728         At this writing, all calls to this function use @a params to pass parameters, rather than
729         @a param_strings.
730
731         This function is a thin wrapper for osrfAppSessionMakeLocaleRequest().
732 */
733 int osrfAppSessionMakeRequest(
734                 osrfAppSession* session, const jsonObject* params,
735                 const char* method_name, int protocol, osrfStringArray* param_strings ) {
736
737         osrfLogWarning( OSRF_LOG_MARK, "Function osrfAppSessionMakeRequest() is deprecated; "
738                         "call osrfAppSessionSendRequest() instead" );
739         return osrfAppSessionMakeLocaleRequest( session, params,
740                         method_name, protocol, param_strings, NULL );
741 }
742
743 /**
744         @brief Create a REQUEST message, send it, and save it for future reference.
745         @param session Pointer to the current session, which has the addressing information.
746         @param params One way of specifying the parameters for the method.
747         @param method_name The name of the method to be called.
748         @param protocol Protocol.
749         @return The request ID of the resulting REQUEST message, or -1 upon error.
750
751         If @a params points to a JSON_ARRAY, then pass each element of the array as a separate
752         parameter.  If @a params points to any other kind of jsonObject, pass it as a single
753         parameter.
754
755         This function is a thin wrapper for osrfAppSessionMakeLocaleRequest().
756 */
757 int osrfAppSessionSendRequest( osrfAppSession* session, const jsonObject* params,
758                 const char* method_name, int protocol ) {
759
760         return osrfAppSessionMakeLocaleRequest( session, params,
761                  method_name, protocol, NULL, NULL );
762 }
763
764 /**
765         @brief Create a REQUEST message, send it, and save it for future reference.
766         @param session Pointer to the current session, which has the addressing information.
767         @param params One way of specifying the parameters for the method.
768         @param method_name The name of the method to be called.
769         @param protocol Protocol.
770         @param param_strings Another way of specifying the parameters for the method.
771         @param locale Pointer to a locale string.
772         @return The request ID of the resulting REQUEST message, or -1 upon error.
773
774         See the discussion of osrfAppSessionSendRequest(), which at this writing is the only
775         place that calls this function, except for the similar but deprecated function
776         osrfAppSessionMakeRequest().
777
778         At this writing, the @a param_strings and @a locale parameters are always NULL.
779 */
780 static int osrfAppSessionMakeLocaleRequest(
781                 osrfAppSession* session, const jsonObject* params, const char* method_name,
782                 int protocol, osrfStringArray* param_strings, char* locale ) {
783
784         if(session == NULL) return -1;
785
786         osrfLogMkXid();
787
788         osrfMessage* req_msg = osrf_message_init( REQUEST, ++(session->thread_trace), protocol );
789         osrf_message_set_method(req_msg, method_name);
790
791         if (locale) {
792                 osrf_message_set_locale(req_msg, locale);
793         } else if (session->session_locale) {
794                 osrf_message_set_locale(req_msg, session->session_locale);
795         }
796
797         osrf_message_set_tz(req_msg, session->session_tz);
798
799         if (!current_ingress)
800                 osrfAppSessionSetIngress("opensrf");
801         osrfMessageSetIngress(req_msg, current_ingress);
802
803         if(params) {
804                 osrf_message_set_params(req_msg, params);
805
806         } else {
807
808                 if(param_strings) {
809                         int i;
810                         for(i = 0; i!= param_strings->size ; i++ ) {
811                                 osrf_message_add_param(req_msg,
812                                         osrfStringArrayGetString(param_strings,i));
813                         }
814                 }
815         }
816
817         osrfAppRequest* req = _osrf_app_request_init( session, req_msg );
818         if(_osrf_app_session_send( session, req_msg ) ) {
819                 osrfLogWarning( OSRF_LOG_MARK,  "Error sending request message [%d]",
820                                 session->thread_trace );
821                 _osrf_app_request_free(req);
822                 return -1;
823         }
824
825         osrfLogDebug( OSRF_LOG_MARK,  "Pushing [%d] onto request queue for session [%s] [%s]",
826                         req->request_id, session->remote_service, session->session_id );
827         add_app_request( session, req );
828         return req->request_id;
829 }
830
831 /**
832         @brief Mark an osrfAppRequest (identified by session and ID) as complete.
833         @param session Pointer to the osrfAppSession that owns the request.
834         @param request_id Request ID of the osrfAppRequest.
835 */
836 void osrf_app_session_set_complete( osrfAppSession* session, int request_id ) {
837         if(session == NULL)
838                 return;
839
840         osrfAppRequest* req = find_app_request( session, request_id );
841         if(req)
842                 req->complete = 1;
843 }
844
845 /**
846         @brief Determine whether a osrfAppRequest, identified by session and ID, is complete.
847         @param session Pointer to the osrfAppSession that owns the request.
848         @param request_id Request ID of the osrfAppRequest.
849         @return Non-zero if the request is complete; zero if it isn't, or if it can't be found.
850 */
851 int osrf_app_session_request_complete( const osrfAppSession* session, int request_id ) {
852         if(session == NULL)
853                 return 0;
854
855         osrfAppRequest* req = find_app_request( session, request_id );
856         if(req)
857                 return req->complete;
858
859         return 0;
860 }
861
862 /**
863         @brief Reset the remote ID of a session to its original remote ID.
864         @param session Pointer to the osrfAppSession to be reset.
865 */
866 void osrf_app_session_reset_remote( osrfAppSession* session ){
867         if( session==NULL )
868                 return;
869
870         osrfLogDebug( OSRF_LOG_MARK,  "App Session [%s] [%s] resetting remote id to %s",
871                         session->remote_service, session->session_id, session->orig_remote_id );
872
873         osrf_app_session_set_remote( session, session->orig_remote_id );
874 }
875
876 /**
877         @brief Set a session's remote ID to a specified value.
878         @param session Pointer to the osrfAppSession whose remote ID is to be set.
879         @param remote_id Pointer to the new remote id.
880 */
881 void osrf_app_session_set_remote( osrfAppSession* session, const char* remote_id ) {
882         if( session == NULL || remote_id == NULL )
883                 return;
884
885         if( session->remote_id ) {
886                 if( strlen(session->remote_id) >= strlen(remote_id) ) {
887                         // There's enough room; just copy it
888                         strcpy(session->remote_id, remote_id);
889                 } else {
890                         free(session->remote_id );
891                         session->remote_id = strdup( remote_id );
892                 }
893         } else
894                 session->remote_id = strdup( remote_id );
895 }
896
897 /**
898         @brief Append an osrfMessage to the list of responses to an osrfAppRequest.
899         @param session Pointer to the osrfAppSession that owns the request.
900         @param msg Pointer to the osrfMessage to be added.
901
902         The thread_trace member of the osrfMessage is the request_id of the osrfAppRequest.
903         Find the corresponding request in the session and append the osrfMessage to its list.
904 */
905 void osrf_app_session_push_queue( osrfAppSession* session, osrfMessage* msg ) {
906         if( session && msg ) {
907                 osrfAppRequest* req = find_app_request( session, msg->thread_trace );
908                 if( req )
909                         _osrf_app_request_push_queue( req, msg );
910         }
911 }
912
913 /**
914         @brief Connect to the remote service.
915         @param session Pointer to the osrfAppSession for the service.
916         @return 1 if successful, or 0 if not.
917
918         If already connected, exit immediately, reporting success.  Otherwise, build a CONNECT
919         message and send it to the service.  Wait for up to five seconds for an acknowledgement.
920
921         The timeout value is currently hard-coded.  Perhaps it should be configurable.
922 */
923 int osrfAppSessionConnect( osrfAppSession* session ) {
924
925         if(session == NULL)
926                 return 0;
927
928         if(session->state == OSRF_SESSION_CONNECTED) {
929                 return 1;
930         }
931
932         int timeout = 5; /* XXX CONFIG VALUE */
933
934         osrfLogDebug( OSRF_LOG_MARK,  "AppSession connecting to %s", session->remote_id );
935
936         /* defaulting to protocol 1 for now */
937         osrfMessage* con_msg = osrf_message_init( CONNECT, session->thread_trace, 1 );
938
939         // Address this message to the router
940         osrf_app_session_reset_remote( session );
941         session->state = OSRF_SESSION_CONNECTING;
942         int ret = _osrf_app_session_send( session, con_msg );
943         osrfMessageFree(con_msg);
944         if(ret)
945                 return 0;
946
947         time_t start = time(NULL);
948         time_t remaining = (time_t) timeout;
949
950         // Wait for the acknowledgement.  We look for it repeatedly because, under the covers,
951         // we may receive and process messages other than the one we're looking for.
952         while( session->state != OSRF_SESSION_CONNECTED && remaining >= 0 ) {
953                 osrf_app_session_queue_wait( session, remaining, NULL );
954                 if(session->transport_error) {
955                         osrfLogError(OSRF_LOG_MARK, "cannot communicate with %s", session->remote_service);
956                         return 0;
957                 }
958                 remaining -= (int) (time(NULL) - start);
959         }
960
961         if(session->state == OSRF_SESSION_CONNECTED)
962                 osrfLogDebug( OSRF_LOG_MARK, " * Connected Successfully to %s", session->remote_service );
963
964         if(session->state != OSRF_SESSION_CONNECTED)
965                 return 0;
966
967         return 1;
968 }
969
970 /**
971         @brief Disconnect from the remote service.  No response is expected.
972         @param session Pointer to the osrfAppSession to be disconnected.
973         @return 1 in all cases.
974
975         If we're already disconnected, return immediately without doing anything.  Likewise if
976         we have a stateless session and we're in the process of connecting.  Otherwise, send a
977         DISCONNECT message to the service.
978 */
979 int osrf_app_session_disconnect( osrfAppSession* session){
980         if(session == NULL)
981                 return 1;
982
983         if(session->state == OSRF_SESSION_DISCONNECTED)
984                 return 1;
985
986         if(session->stateless && session->state != OSRF_SESSION_CONNECTED) {
987                 osrfLogDebug( OSRF_LOG_MARK,
988                                 "Exiting disconnect on stateless session %s",
989                                 session->session_id);
990                 return 1;
991         }
992
993         osrfLogDebug(OSRF_LOG_MARK,  "AppSession disconnecting from %s", session->remote_id );
994
995         osrfMessage* dis_msg = osrf_message_init( DISCONNECT, session->thread_trace, 1 );
996         _osrf_app_session_send( session, dis_msg );
997         session->state = OSRF_SESSION_DISCONNECTED;
998
999         osrfMessageFree( dis_msg );
1000         osrf_app_session_reset_remote( session );
1001         return 1;
1002 }
1003
1004 /**
1005         @brief Resend a request message, as specified by session and request id.
1006         @param session Pointer to the osrfAppSession.
1007         @param req_id Request ID for the request to be resent.
1008         @return Zero if successful, or if the specified request cannot be found; 1 if the
1009         request is already complete, or if the attempt to resend the message fails.
1010
1011         The choice of return codes may seem seem capricious, but at this writing nothing
1012         pays any attention to the return code anyway.
1013 */
1014 int osrf_app_session_request_resend( osrfAppSession* session, int req_id ) {
1015         osrfAppRequest* req = find_app_request( session, req_id );
1016
1017         int rc;
1018         if(req == NULL) {
1019                 rc = 0;
1020         } else if(!req->complete) {
1021                 osrfLogDebug( OSRF_LOG_MARK, "Resending request [%d]", req->request_id );
1022                 rc = _osrf_app_session_send( req->session, req->payload );
1023         } else {
1024                 rc = 1;
1025         }
1026
1027         return rc;
1028 }
1029
1030 /**
1031         @brief Send one or more osrfMessages to the remote service or client.
1032         @param session Pointer to the osrfAppSession responsible for sending the message(s).
1033         @param msgs Pointer to an array of pointers to osrfMessages.
1034         @param size How many messages to send.
1035         @return 0 upon success, or -1 upon failure.
1036 */
1037 static int osrfAppSessionSendBatch( osrfAppSession* session, osrfMessage* msgs[], int size ) {
1038
1039         if( !(session && msgs && size > 0) ) return -1;
1040         int retval = 0;
1041
1042         osrfMessage* msg = msgs[0];
1043
1044         if(msg) {
1045
1046                 // First grab and process any input messages, for any app session.  This gives us
1047                 // a chance to see any CONNECT or DISCONNECT messages that may have arrived.  We
1048                 // may also see some unrelated messages, but we have to process those sooner or
1049                 // later anyway, so we might as well do it now.
1050                 osrf_app_session_queue_wait( session, 0, NULL );
1051
1052                 if(session->state != OSRF_SESSION_CONNECTED)  {
1053
1054                         if(session->stateless) { /* stateless session always send to the root listener */
1055                                 osrf_app_session_reset_remote(session);
1056
1057                         } else {
1058
1059                                 /* do an auto-connect if necessary */
1060                                 if( ! session->stateless &&
1061                                         (msg->m_type != CONNECT) &&
1062                                         (msg->m_type != DISCONNECT) &&
1063                                         (session->state != OSRF_SESSION_CONNECTED) ) {
1064
1065                                         if(!osrfAppSessionConnect( session ))
1066                                                 return -1;
1067                                 }
1068                         }
1069                 }
1070         }
1071
1072         // Translate the collection of osrfMessages into a JSON array
1073         char* string = osrfMessageSerializeBatch(msgs, size);
1074
1075         // Send the JSON as the payload of a transport_message
1076         if( string ) {
1077                 retval = osrfSendTransportPayload( session, string );
1078                 free(string);
1079         }
1080
1081         return retval;
1082 }
1083
1084 /**
1085         @brief Split a given string into one or more transport result messages and send it
1086         @param session Pointer to the osrfAppSession responsible for sending the message(s).
1087         @param request_id Request ID of the osrfAppRequest.
1088         @param payload A string to be sent via Jabber.
1089         @param payload_size length of payload
1090         @param chunk_size chunk_size to use
1091
1092         @return 0 upon success, or -1 upon failure.
1093 */
1094 int osrfSendChunkedResult(
1095         osrfAppSession* session, int request_id, const char* payload,
1096         size_t payload_size, size_t chunk_size ) {
1097
1098         // chunking payload
1099         int i;
1100         for (i = 0; i < payload_size; i += chunk_size) {
1101                 osrfMessage* msg = osrf_message_init(RESULT, request_id, 1);
1102                 osrf_message_set_status_info(msg,
1103                         "osrfResultPartial",
1104                         "Partial Response",
1105                         OSRF_STATUS_PARTIAL
1106                 );
1107
1108                 // see how long this chunk is.  If this is the last
1109                 // chunk, it will likely be less than chunk_size
1110                 int partial_size = strlen(&payload[i]);
1111                 if (partial_size > chunk_size)
1112                         partial_size = chunk_size;
1113
1114                 // substr(data, i, partial_size)
1115                 char partial_buf[partial_size + 1];
1116                 memcpy(partial_buf, &payload[i], partial_size);
1117                 partial_buf[partial_size] = '\0';
1118
1119                 // package the partial chunk as a JSON string object
1120                 jsonObject*  partial_obj = jsonNewObject(partial_buf);
1121                 osrf_message_set_result(msg, partial_obj);
1122                 jsonObjectFree(partial_obj);
1123
1124                 // package the osrf message within an array then
1125                 // serialize to json for delivery
1126                 jsonObject* arr = jsonNewObject(NULL);
1127
1128                 // msg json freed when arr is freed
1129                 jsonObjectPush(arr, osrfMessageToJSON(msg));
1130                 char* json = jsonObjectToJSON(arr);
1131
1132                 osrfSendTransportPayload(session, json);
1133                 osrfMessageFree(msg);
1134                 jsonObjectFree(arr);
1135                 free(json);
1136         }
1137
1138         // all chunks sent; send the final partial-complete msg
1139         osrfMessage* msg = osrf_message_init(RESULT, request_id, 1);
1140         osrf_message_set_status_info(msg,
1141                 "osrfResultPartialComplete",
1142                 "Partial Response Finalized",
1143                 OSRF_STATUS_NOCONTENT
1144         );
1145
1146         jsonObject* arr = jsonNewObject(NULL);
1147         jsonObjectPush(arr, osrfMessageToJSON(msg));
1148         char* json = jsonObjectToJSON(arr);
1149         osrfSendTransportPayload(session, json);
1150         osrfMessageFree(msg);
1151         jsonObjectFree(arr);
1152         free(json);
1153
1154         return 0;
1155 }
1156
1157 /**
1158         @brief Wrap a given string in a transport message and send it.
1159         @param session Pointer to the osrfAppSession responsible for sending the message(s).
1160         @param payload A string to be sent via Jabber.
1161         @return 0 upon success, or -1 upon failure.
1162
1163         In practice the payload is normally a JSON string, but this function assumes nothing
1164         about it.
1165 */
1166 int osrfSendTransportPayload( osrfAppSession* session, const char* payload ) {
1167         transport_message* t_msg = message_init(
1168                 payload, "", session->session_id, session->remote_id, NULL );
1169         message_set_osrf_xid( t_msg, osrfLogGetXid() );
1170
1171         int retval = client_send_message( session->transport_handle, t_msg );
1172         if( retval ) {
1173                 osrfLogError( OSRF_LOG_MARK, "client_send_message failed, exit()ing immediately" );
1174                 exit(99);
1175         }
1176
1177         osrfLogInfo(OSRF_LOG_MARK, "[%s] sent %d bytes of data to %s",
1178                 session->remote_service, strlen( payload ), t_msg->recipient );
1179
1180         osrfLogDebug( OSRF_LOG_MARK, "Sent: %s", payload );
1181
1182         message_free( t_msg );
1183         return retval;
1184 }
1185
1186 /**
1187         @brief Send a single osrfMessage to the remote service or client.
1188         @param session Pointer to the osrfAppSession.
1189         @param msg Pointer to the osrfMessage to be sent.
1190         @return zero upon success, or 1 upon failure.
1191
1192         A thin wrapper.  Create an array of one element, and pass it to osrfAppSessionSendBatch().
1193 */
1194 static int _osrf_app_session_send( osrfAppSession* session, osrfMessage* msg ){
1195         if( !(session && msg) )
1196                 return 1;
1197         osrfMessage* a[1];
1198         a[0] = msg;
1199         return  - osrfAppSessionSendBatch( session, a, 1 );
1200 }
1201
1202
1203 /**
1204         @brief Wait for any input messages to arrive, and process them as needed.
1205         @param session Pointer to the osrfAppSession whose transport_session we will use.
1206         @param timeout How many seconds to wait for the first input message.
1207         @param recvd Pointer to an boolean int.  If you receive at least one message, set the boolean
1208         to true; otherwise set it to false.
1209         @return 0 upon success (even if a timeout occurs), or -1 upon failure.
1210
1211         A thin wrapper for osrf_stack_process().  The timeout applies only to the first
1212         message; process subsequent messages if they are available, but don't wait for them.
1213
1214         The first parameter identifies an osrfApp session, but all we really use it for is to
1215         get a pointer to the transport_session.  Typically, a given process opens only a single
1216         transport_session (to talk to the Jabber server), and all app sessions in that process
1217         use the same transport_session.
1218
1219         Hence this function indiscriminately waits for input messages for all osrfAppSessions
1220         tied to the same Jabber session, not just the one specified.
1221
1222         Dispatch each message to the appropriate processing routine, depending on its type
1223         and contents, and on whether we're acting as a client or as a server for that message.
1224         For example, a response to a request may be appended to the input queue of the
1225         relevant request.  A server session receiving a REQUEST message may execute the
1226         requested method.  And so forth.
1227 */
1228 int osrf_app_session_queue_wait( osrfAppSession* session, int timeout, int* recvd ){
1229         if(session == NULL) return 0;
1230         osrfLogDebug(OSRF_LOG_MARK, "AppSession in queue_wait with timeout %d", timeout );
1231         return osrf_stack_process(session->transport_handle, timeout, recvd);
1232 }
1233
1234 /**
1235         @brief Shut down and destroy an osrfAppSession.
1236         @param session Pointer to the osrfAppSession to be destroyed.
1237
1238         If this is a client session, send a DISCONNECT message.
1239
1240         Remove the session from the global session cache.
1241
1242         Free all associated resources, including any pending osrfAppRequests.
1243 */
1244 void osrfAppSessionFree( osrfAppSession* session ){
1245         if(session == NULL) return;
1246
1247         /* Disconnect */
1248
1249         osrfLogDebug(OSRF_LOG_MARK,  "AppSession [%s] [%s] destroying self and deleting requests",
1250                         session->remote_service, session->session_id );
1251         /* disconnect if we're a client */
1252         if(session->type == OSRF_SESSION_CLIENT
1253                         && session->state != OSRF_SESSION_DISCONNECTED ) {
1254                 osrfMessage* dis_msg = osrf_message_init( DISCONNECT, session->thread_trace, 1 );
1255                 _osrf_app_session_send( session, dis_msg );
1256                 osrfMessageFree(dis_msg);
1257         }
1258
1259         /* Remove self from the global session cache */
1260
1261         osrfHashRemove( osrfAppSessionCache, session->session_id );
1262
1263         /* Free the memory */
1264
1265         if( session->userDataFree && session->userData )
1266                 session->userDataFree(session->userData);
1267
1268         if(session->session_locale)
1269                 free(session->session_locale);
1270
1271         if(session->session_tz)
1272                 free(session->session_tz);
1273
1274         free(session->remote_id);
1275         free(session->orig_remote_id);
1276         free(session->session_id);
1277         free(session->remote_service);
1278
1279         // Free the request hash
1280         int i;
1281         for( i = 0; i < OSRF_REQUEST_HASH_SIZE; ++i ) {
1282                 osrfAppRequest* app = session->request_hash[ i ];
1283                 while( app ) {
1284                         osrfAppRequest* next = app->next;
1285                         _osrf_app_request_free( app );
1286                         app = next;
1287                 }
1288         }
1289
1290         if( session->outbuf )
1291                 buffer_free( session->outbuf );
1292
1293         free(session);
1294 }
1295
1296 /**
1297         @brief Wait for a response to a given request, subject to a timeout.
1298         @param session Pointer to the osrfAppSession that owns the request.
1299         @param req_id Request ID for the request.
1300         @param timeout How many seconds to wait.
1301         @return A pointer to the received osrfMessage if one arrives; otherwise NULL.
1302
1303         A thin wrapper.  Given a session and a request ID, look up the corresponding request
1304         and pass it to _osrf_app_request_recv().
1305 */
1306 osrfMessage* osrfAppSessionRequestRecv(
1307                 osrfAppSession* session, int req_id, int timeout ) {
1308         if(req_id < 0 || session == NULL)
1309                 return NULL;
1310         osrfAppRequest* req = find_app_request( session, req_id );
1311         return _osrf_app_request_recv( req, timeout );
1312 }
1313
1314 /**
1315         @brief In response to a specified request, send a payload of data to a client.
1316         @param ses Pointer to the osrfAppSession that owns the request.
1317         @param requestId Request ID of the osrfAppRequest.
1318         @param data Pointer to a jsonObject containing the data payload.
1319         @return 0 upon success, or -1 upon failure.
1320
1321         Translate the jsonObject to a JSON string, and send it wrapped in a RESULT message.
1322
1323         The only failure detected is if either of the two pointer parameters is NULL.
1324 */
1325 int osrfAppRequestRespond( osrfAppSession* ses, int requestId, const jsonObject* data ) {
1326         if( !ses || ! data )
1327                 return -1;
1328
1329         osrfMessage* msg = osrf_message_init( RESULT, requestId, 1 );
1330         osrf_message_set_status_info( msg, NULL, "OK", OSRF_STATUS_OK );
1331         char* json = jsonObjectToJSON( data );
1332
1333         osrf_message_set_result_content( msg, json );
1334         _osrf_app_session_send( ses, msg );
1335
1336         free(json);
1337         osrfMessageFree( msg );
1338
1339         return 0;
1340 }
1341
1342
1343 /**
1344         @brief Send one or two messages to a client in response to a specified request.
1345         @param ses Pointer to the osrfAppSession that owns the request.
1346         @param requestId Request ID of the osrfAppRequest.
1347         @param data Pointer to a jsonObject containing the data payload.
1348         @return  Zero in all cases.
1349
1350         If the @a data parameter is not NULL, translate the jsonObject into a JSON string, and
1351         incorporate that string into a RESULT message as as the payload .  Also build a STATUS
1352         message indicating that the response is complete.  Send both messages bundled together
1353         in the same transport_message.
1354
1355         If the @a data parameter is NULL, send only a STATUS message indicating that the response
1356         is complete.
1357 */
1358 int osrfAppRequestRespondComplete(
1359                 osrfAppSession* ses, int requestId, const jsonObject* data ) {
1360
1361         osrfMessage* status = osrf_message_init( STATUS, requestId, 1);
1362         osrf_message_set_status_info( status, "osrfConnectStatus", "Request Complete",
1363                         OSRF_STATUS_COMPLETE );
1364
1365         if (data) {
1366                 char* json = jsonObjectToJSON(data);
1367                 size_t raw_size = strlen(json);
1368                 size_t extra_size = osrfXmlEscapingLength(json);
1369                 size_t data_size = raw_size + extra_size;
1370                 size_t chunk_size = OSRF_MSG_CHUNK_SIZE;
1371
1372                 if (data_size > chunk_size) // calculate an escape-scaled chunk size
1373                         chunk_size = ((double)raw_size / (double)data_size) * (double)chunk_size;
1374
1375                 if (chunk_size > 0 && chunk_size < raw_size) {
1376                         // chunking -- response message exceeds max message size.
1377                         // break it up into chunks for partial delivery
1378
1379                         osrfSendChunkedResult(ses, requestId, json, raw_size, chunk_size);
1380                         osrfAppSessionSendBatch( ses, &status, 1 );
1381
1382                 } else {
1383                         // message doesn't need to be chunked
1384                         osrfMessage* payload = osrf_message_init( RESULT, requestId, 1 );
1385                         osrf_message_set_status_info( payload, NULL, "OK", OSRF_STATUS_OK );
1386
1387                         osrf_message_set_result_content( payload, json );
1388
1389                         osrfMessage* ms[2];
1390                         ms[0] = payload;
1391                         ms[1] = status;
1392
1393                         osrfAppSessionSendBatch( ses, ms, 2 );
1394
1395                         osrfMessageFree( payload );
1396                 }
1397
1398                 free(json);
1399
1400         } else {
1401                 osrfAppSessionSendBatch( ses, &status, 1 );
1402         }
1403
1404         osrfMessageFree( status );
1405
1406         return 0;
1407 }
1408
1409 /**
1410         @brief Send a STATUS message, for a specified request, back to the client.
1411         @param ses Pointer to the osrfAppSession connected to the client.
1412         @param type A numeric code denoting the status.
1413         @param name A string naming the status.
1414         @param reqId The request ID of the request.
1415         @param message A brief message describing the status.
1416         @return 0 upon success, or -1 upon failure.
1417
1418         The only detected failure is when the @a ses parameter is NULL.
1419 */
1420 int osrfAppSessionStatus( osrfAppSession* ses, int type,
1421                 const char* name, int reqId, const char* message ) {
1422
1423         if(ses) {
1424                 osrfMessage* msg = osrf_message_init( STATUS, reqId, 1);
1425                 osrf_message_set_status_info( msg, name, message, type );
1426                 _osrf_app_session_send( ses, msg );
1427                 osrfMessageFree( msg );
1428                 return 0;
1429         } else
1430                 return -1;
1431 }
1432
1433 /**
1434         @brief Free the global session cache.
1435
1436         Note that the osrfHash that implements the global session cache does @em not have a
1437         callback function installed for freeing its cargo.  As a result, any remaining
1438         osrfAppSessions are leaked, along with all the osrfAppRequests and osrfMessages they
1439         own.
1440 */
1441 void osrfAppSessionCleanup( void ) {
1442         osrfHashFree(osrfAppSessionCache);
1443         osrfAppSessionCache = NULL;
1444 }
1445
1446 /**
1447         @brief Arrange for immediate termination of the process.
1448         @param ses Pointer to the current osrfAppSession.
1449
1450         Typical use case: a server drone loses its database connection, thereby becoming useless.
1451         It terminates so that it will not receive further requests, being unable to service them.
1452 */
1453 void osrfAppSessionPanic( osrfAppSession* ses ) {
1454         if( ses )
1455                 ses->panic = 1;
1456 }