]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/libopensrf/transport_client.c
Changed the signal handling.
[OpenSRF.git] / src / libopensrf / transport_client.c
1 #include <opensrf/transport_client.h>
2
3 /**
4         @file transport_client.c
5         @brief Collection of routines for sending and receiving single messages over Jabber.
6
7         These functions form an API built on top of the transport_session API.  They serve
8         two main purposes:
9         - They remember a Jabber ID to use when sending messages.
10         - They maintain a queue of input messages that the calling code can get one at a time.
11 */
12
13 static void client_message_handler( void* client, transport_message* msg );
14
15 //int main( int argc, char** argv );
16
17 /*
18 int main( int argc, char** argv ) {
19
20         transport_message* recv;
21         transport_message* send;
22
23         transport_client* client = client_init( "spacely.georgialibraries.org", 5222 );
24
25         // try to connect, allow 15 second connect timeout
26         if( client_connect( client, "admin", "asdfjkjk", "system", 15 ) ) {
27                 printf("Connected...\n");
28         } else {
29                 printf( "NOT Connected...\n" ); exit(99);
30         }
31
32         while( (recv = client_recv( client, -1 )) ) {
33
34                 if( recv->body ) {
35                         int len = strlen(recv->body);
36                         char buf[len + 20];
37                         osrf_clearbuf( buf, 0, sizeof(buf));
38                         sprintf( buf, "Echoing...%s", recv->body );
39                         send = message_init( buf, "Echoing Stuff", "12345", recv->sender, "" );
40                 } else {
41                         send = message_init( " * ECHOING * ", "Echoing Stuff", "12345", recv->sender, "" );
42                 }
43
44                 if( send == NULL ) { printf("something's wrong"); }
45                 client_send_message( client, send );
46
47                 message_free( send );
48                 message_free( recv );
49         }
50
51         printf( "ended recv loop\n" );
52
53         return 0;
54
55 }
56 */
57
58
59 /**
60         @brief Allocate and initialize a transport_client.
61         @param server Hostname or IP address where the Jabber server resides.
62         @param port Port used for connecting to Jabber (0 if using UNIX domain socket).
63         @param unix_path Name of Jabber's socket in file system (if using UNIX domain socket).
64         @param component Boolean; true if we're a Jabber component.
65         @return A pointer to a newly created transport_client.
66
67         Create a transport_client with a transport_session and an empty message queue (but don't open a connection yet).
68         Install a callback function in the transport_session to enqueue incoming messages.
69
70         The calling code is responsible for freeing the transport_client by calling client_free().
71 */
72 transport_client* client_init( const char* server, int port, const char* unix_path, int component ) {
73
74         if(server == NULL) return NULL;
75
76         /* build and clear the client object */
77         transport_client* client = safe_malloc( sizeof( transport_client) );
78
79         /* start with an empty message queue */
80         client->msg_q_head = NULL;
81         client->msg_q_tail = NULL;
82
83         /* build the session */
84         client->session = init_transport( server, port, unix_path, client, component );
85
86         client->session->message_callback = client_message_handler;
87         client->error = 0;
88         client->host = strdup(server);
89         client->xmpp_id = NULL;
90
91         return client;
92 }
93
94
95 /**
96         @brief Open a Jabber session for a transport_client.
97         @param client Pointer to the transport_client.
98         @param username Jabber user name.
99         @param password Password for the Jabber logon.
100         @param resource Resource name for the Jabber logon.
101         @param connect_timeout How many seconds to wait for the connection to open.
102         @param auth_type An enum: either AUTH_PLAIN or AUTH_DIGEST (see notes).
103         @return 1 if successful, or 0 upon error.
104
105         Besides opening the Jabber session, create a Jabber ID for future use.
106
107         If @a connect_timeout is -1, wait indefinitely for the Jabber server to respond.  If
108         @a connect_timeout is zero, don't wait at all.  If @a timeout is positive, wait that
109         number of seconds before timing out.  If @a connect_timeout has a negative value other
110         than -1, the results are not well defined.
111
112         The value of @a connect_timeout applies to each of two stages in the logon procedure.
113         Hence the logon may take up to twice the amount of time indicated.
114
115         If we connect as a Jabber component, we send the password as an SHA1 hash.  Otherwise
116         we look at the @a auth_type.  If it's AUTH_PLAIN, we send the password as plaintext; if
117         it's AUTH_DIGEST, we send it as a hash.
118  */
119 int client_connect( transport_client* client,
120                 const char* username, const char* password, const char* resource,
121                 int connect_timeout, enum TRANSPORT_AUTH_TYPE  auth_type ) {
122         if( client == NULL )
123                 return 0;
124
125         // Create and store a Jabber ID
126         if( client->xmpp_id )
127                 free( client->xmpp_id );
128         client->xmpp_id = va_list_to_string( "%s@%s/%s", username, client->host, resource );
129
130         // Open a transport_session
131         return session_connect( client->session, username,
132                         password, resource, connect_timeout, auth_type );
133 }
134
135 /**
136         @brief Disconnect from the Jabber session.
137         @param client Pointer to the transport_client.
138         @return 0 in all cases.
139
140         If there are any messages still in the queue, they stay there; i.e. we don't free them here.
141 */
142 int client_disconnect( transport_client* client ) {
143         if( client == NULL ) { return 0; }
144         return session_disconnect( client->session );
145 }
146
147 /**
148         @brief Report whether a transport_client is connected.
149         @param client Pointer to the transport_client.
150         @return Boolean: 1 if connected, or 0 if not.
151 */
152 int client_connected( const transport_client* client ) {
153         if(client == NULL) return 0;
154         return session_connected( client->session );
155 }
156
157 /**
158         @brief Send a transport message to the current destination.
159         @param client Pointer to a transport_client.
160         @param msg Pointer to the transport_message to be sent.
161         @return 0 if successful, or -1 if not.
162
163         Translate the transport_message into XML and send it to Jabber, using the previously
164         stored Jabber ID for the sender.
165 */
166 int client_send_message( transport_client* client, transport_message* msg ) {
167         if( client == NULL || client->error )
168                 return -1;
169         if( msg->sender )
170                 free( msg->sender );
171         msg->sender = strdup(client->xmpp_id);
172         return session_send_msg( client->session, msg );
173 }
174
175 /**
176         @brief Fetch an input message, if one is available.
177         @param client Pointer to a transport_client.
178         @param timeout How long to wait for a message to arrive, in seconds (see remarks).
179         @return A pointer to a transport_message if successful, or NULL if not.
180
181         If there is a message already in the queue, return it immediately.  Otherwise read any
182         available messages from the transport_session (subject to a timeout), and return the
183         first one.
184
185         If the value of @a timeout is -1, then there is no time limit -- wait indefinitely until a
186         message arrives (or we error out for other reasons).  If the value of @a timeout is zero,
187         don't wait at all.
188
189         The calling code is responsible for freeing the transport_message by calling message_free().
190 */
191 transport_message* client_recv( transport_client* client, int timeout ) {
192         if( client == NULL ) { return NULL; }
193
194         int error = 0;  /* boolean */
195
196         if( NULL == client->msg_q_head ) {
197
198                 // No message available on the queue?  Try to get a fresh one.
199
200                 // When we call session_wait(), it reads a socket for new messages.  When it finds
201                 // one, it enqueues it by calling the callback function client_message_handler(),
202                 // which we installed in the transport_session when we created the transport_client.
203
204                 // Since a single call to session_wait() may not result in the receipt of a complete
205                 // message. we call it repeatedly until we get either a message or an error.
206
207                 // Alternatively, a single call to session_wait() may result in the receipt of
208                 // multiple messages.  That's why we have to enqueue them.
209
210                 // The timeout applies to the receipt of a complete message.  For a sufficiently
211                 // short timeout, a sufficiently long message, and a sufficiently slow connection,
212                 // we could timeout on the first message even though we're still receiving data.
213                 
214                 // Likewise we could time out while still receiving the second or subsequent message,
215                 // return the first message, and resume receiving messages later.
216
217                 if( timeout == -1 ) {  /* wait potentially forever for data to arrive */
218
219                         int x;
220                         do {
221                                 if( (x = session_wait( client->session, -1 )) ) {
222                                         osrfLogDebug(OSRF_LOG_MARK, "session_wait returned failure code %d\n", x);
223                                         error = 1;
224                                         break;
225                                 }
226                         } while( client->msg_q_head == NULL );
227
228                 } else {    /* loop up to 'timeout' seconds waiting for data to arrive  */
229
230                         /* This loop assumes that a time_t is denominated in seconds -- not */
231                         /* guaranteed by Standard C, but a fair bet for Linux or UNIX       */
232
233                         time_t start = time(NULL);
234                         time_t remaining = (time_t) timeout;
235
236                         int wait_ret;
237                         do {
238                                 if( (wait_ret = session_wait( client->session, (int) remaining)) ) {
239                                         error = 1;
240                                         osrfLogDebug(OSRF_LOG_MARK,
241                                                 "session_wait returned failure code %d: setting error=1\n", wait_ret);
242                                         break;
243                                 }
244
245                                 remaining -= time(NULL) - start;
246                         } while( NULL == client->msg_q_head && remaining > 0 );
247                 }
248         }
249
250         transport_message* msg = NULL;
251
252         if( error )
253                 client->error = 1;
254         else if( client->msg_q_head != NULL ) {
255                 /* got message(s); dequeue the oldest one */
256                 msg = client->msg_q_head;
257                 client->msg_q_head = msg->next;
258                 msg->next = NULL;  /* shouldn't be necessary; nullify for good hygiene */
259                 if( NULL == client->msg_q_head )
260                         client->msg_q_tail = NULL;
261         }
262
263         return msg;
264 }
265
266 /**
267         @brief Enqueue a newly received transport_message.
268         @param client A pointer to a transport_client, cast to a void pointer.
269         @param msg A new transport message.
270
271         Add a newly arrived input message to the tail of the queue.
272
273         This is a callback function.  The transport_session parses the XML coming in through a
274         socket, accumulating various bits and pieces.  When it sees the end of a message stanza,
275         it packages the bits and pieces into a transport_message that it passes to this function,
276         which enqueues the message for processing.
277 */
278 static void client_message_handler( void* client, transport_message* msg ){
279
280         if(client == NULL) return;
281         if(msg == NULL) return;
282
283         transport_client* cli = (transport_client*) client;
284
285         /* add the new message to the tail of the queue */
286         if( NULL == cli->msg_q_head )
287                 cli->msg_q_tail = cli->msg_q_head = msg;
288         else {
289                 cli->msg_q_tail->next = msg;
290                 cli->msg_q_tail = msg;
291         }
292         msg->next = NULL;
293 }
294
295
296 /**
297         @brief Free a transport_client, along with all resources it owns.
298         @param client Pointer to the transport_client to be freed.
299         @return 1 if successful, or 0 if not.  The only error condition is if @a client is NULL.
300 */
301 int client_free( transport_client* client ){
302         if(client == NULL) return 0;
303
304         session_free( client->session );
305         transport_message* current = client->msg_q_head;
306         transport_message* next;
307
308         /* deallocate the list of messages */
309         while( current != NULL ) {
310                 next = current->next;
311                 message_free( current );
312                 current = next;
313         }
314
315         free(client->host);
316         free(client->xmpp_id);
317         free( client );
318         return 1;
319 }
320