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