New function: client_sock_fd(). It returns the socket fd used
[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 Domain name 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
68         open a connection yet).  Install a callback function in the transport_session to enqueue
69         incoming messages.
70
71         The calling code is responsible for freeing the transport_client by calling client_free().
72 */
73 transport_client* client_init( const char* server, int port, const char* unix_path, int component ) {
74
75         if(server == NULL) return NULL;
76
77         /* build and clear the client object */
78         transport_client* client = safe_malloc( sizeof( transport_client) );
79
80         /* start with an empty message queue */
81         client->msg_q_head = NULL;
82         client->msg_q_tail = NULL;
83
84         /* build the session */
85         client->session = init_transport( server, port, unix_path, client, component );
86
87         client->session->message_callback = client_message_handler;
88         client->error = 0;
89         client->host = strdup(server);
90         client->xmpp_id = NULL;
91
92         return client;
93 }
94
95
96 /**
97         @brief Open a Jabber session for a transport_client.
98         @param client Pointer to the transport_client.
99         @param username Jabber user name.
100         @param password Password for the Jabber logon.
101         @param resource Resource name for the Jabber logon.
102         @param connect_timeout How many seconds to wait for the connection to open.
103         @param auth_type An enum: either AUTH_PLAIN or AUTH_DIGEST (see notes).
104         @return 1 if successful, or 0 upon error.
105
106         Besides opening the Jabber session, create a Jabber ID for future use.
107
108         If @a connect_timeout is -1, wait indefinitely for the Jabber server to respond.  If
109         @a connect_timeout is zero, don't wait at all.  If @a timeout is positive, wait that
110         number of seconds before timing out.  If @a connect_timeout has a negative value other
111         than -1, the results are not well defined.
112
113         The value of @a connect_timeout applies to each of two stages in the logon procedure.
114         Hence the logon may take up to twice the amount of time indicated.
115
116         If we connect as a Jabber component, we send the password as an SHA1 hash.  Otherwise
117         we look at the @a auth_type.  If it's AUTH_PLAIN, we send the password as plaintext; if
118         it's AUTH_DIGEST, we send it as a hash.
119  */
120 int client_connect( transport_client* client,
121                 const char* username, const char* password, const char* resource,
122                 int connect_timeout, enum TRANSPORT_AUTH_TYPE  auth_type ) {
123         if( client == NULL )
124                 return 0;
125
126         // Create and store a Jabber ID
127         if( client->xmpp_id )
128                 free( client->xmpp_id );
129         client->xmpp_id = va_list_to_string( "%s@%s/%s", username, client->host, resource );
130
131         // Open a transport_session
132         return session_connect( client->session, username,
133                         password, resource, connect_timeout, auth_type );
134 }
135
136 /**
137         @brief Disconnect from the Jabber session.
138         @param client Pointer to the transport_client.
139         @return 0 in all cases.
140
141         If there are any messages still in the queue, they stay there; i.e. we don't free them here.
142 */
143 int client_disconnect( transport_client* client ) {
144         if( client == NULL ) { return 0; }
145         return session_disconnect( client->session );
146 }
147
148 /**
149         @brief Report whether a transport_client is connected.
150         @param client Pointer to the transport_client.
151         @return Boolean: 1 if connected, or 0 if not.
152 */
153 int client_connected( const transport_client* client ) {
154         if(client == NULL) return 0;
155         return session_connected( client->session );
156 }
157
158 /**
159         @brief Send a transport message to the current destination.
160         @param client Pointer to a transport_client.
161         @param msg Pointer to the transport_message to be sent.
162         @return 0 if successful, or -1 if not.
163
164         Translate the transport_message into XML and send it to Jabber, using the previously
165         stored Jabber ID for the sender.
166 */
167 int client_send_message( transport_client* client, transport_message* msg ) {
168         if( client == NULL || client->error )
169                 return -1;
170         if( msg->sender )
171                 free( msg->sender );
172         msg->sender = strdup(client->xmpp_id);
173         return session_send_msg( client->session, msg );
174 }
175
176 /**
177         @brief Fetch an input message, if one is available.
178         @param client Pointer to a transport_client.
179         @param timeout How long to wait for a message to arrive, in seconds (see remarks).
180         @return A pointer to a transport_message if successful, or NULL if not.
181
182         If there is a message already in the queue, return it immediately.  Otherwise read any
183         available messages from the transport_session (subject to a timeout), and return the
184         first one.
185
186         If the value of @a timeout is -1, then there is no time limit -- wait indefinitely until a
187         message arrives (or we error out for other reasons).  If the value of @a timeout is zero,
188         don't wait at all.
189
190         The calling code is responsible for freeing the transport_message by calling message_free().
191 */
192 transport_message* client_recv( transport_client* client, int timeout ) {
193         if( client == NULL ) { return NULL; }
194
195         int error = 0;  /* boolean */
196
197         if( NULL == client->msg_q_head ) {
198
199                 // No message available on the queue?  Try to get a fresh one.
200
201                 // When we call session_wait(), it reads a socket for new messages.  When it finds
202                 // one, it enqueues it by calling the callback function client_message_handler(),
203                 // which we installed in the transport_session when we created the transport_client.
204
205                 // Since a single call to session_wait() may not result in the receipt of a complete
206                 // message. we call it repeatedly until we get either a message or an error.
207
208                 // Alternatively, a single call to session_wait() may result in the receipt of
209                 // multiple messages.  That's why we have to enqueue them.
210
211                 // The timeout applies to the receipt of a complete message.  For a sufficiently
212                 // short timeout, a sufficiently long message, and a sufficiently slow connection,
213                 // we could timeout on the first message even though we're still receiving data.
214                 
215                 // Likewise we could time out while still receiving the second or subsequent message,
216                 // return the first message, and resume receiving messages later.
217
218                 if( timeout == -1 ) {  /* wait potentially forever for data to arrive */
219
220                         int x;
221                         do {
222                                 if( (x = session_wait( client->session, -1 )) ) {
223                                         osrfLogDebug(OSRF_LOG_MARK, "session_wait returned failure code %d\n", x);
224                                         error = 1;
225                                         break;
226                                 }
227                         } while( client->msg_q_head == NULL );
228
229                 } else {    /* loop up to 'timeout' seconds waiting for data to arrive  */
230
231                         /* This loop assumes that a time_t is denominated in seconds -- not */
232                         /* guaranteed by Standard C, but a fair bet for Linux or UNIX       */
233
234                         time_t start = time(NULL);
235                         time_t remaining = (time_t) timeout;
236
237                         int wait_ret;
238                         do {
239                                 if( (wait_ret = session_wait( client->session, (int) remaining)) ) {
240                                         error = 1;
241                                         osrfLogDebug(OSRF_LOG_MARK,
242                                                 "session_wait returned failure code %d: setting error=1\n", wait_ret);
243                                         break;
244                                 }
245
246                                 remaining -= time(NULL) - start;
247                         } while( NULL == client->msg_q_head && remaining > 0 );
248                 }
249         }
250
251         transport_message* msg = NULL;
252
253         if( error )
254                 client->error = 1;
255         else if( client->msg_q_head != NULL ) {
256                 /* got message(s); dequeue the oldest one */
257                 msg = client->msg_q_head;
258                 client->msg_q_head = msg->next;
259                 msg->next = NULL;  /* shouldn't be necessary; nullify for good hygiene */
260                 if( NULL == client->msg_q_head )
261                         client->msg_q_tail = NULL;
262         }
263
264         return msg;
265 }
266
267 /**
268         @brief Enqueue a newly received transport_message.
269         @param client A pointer to a transport_client, cast to a void pointer.
270         @param msg A new transport message.
271
272         Add a newly arrived input message to the tail of the queue.
273
274         This is a callback function.  The transport_session parses the XML coming in through a
275         socket, accumulating various bits and pieces.  When it sees the end of a message stanza,
276         it packages the bits and pieces into a transport_message that it passes to this function,
277         which enqueues the message for processing.
278 */
279 static void client_message_handler( void* client, transport_message* msg ){
280
281         if(client == NULL) return;
282         if(msg == NULL) return;
283
284         transport_client* cli = (transport_client*) client;
285
286         /* add the new message to the tail of the queue */
287         if( NULL == cli->msg_q_head )
288                 cli->msg_q_tail = cli->msg_q_head = msg;
289         else {
290                 cli->msg_q_tail->next = msg;
291                 cli->msg_q_tail = msg;
292         }
293         msg->next = NULL;
294 }
295
296
297 /**
298         @brief Free a transport_client, along with all resources it owns.
299         @param client Pointer to the transport_client to be freed.
300         @return 1 if successful, or 0 if not.  The only error condition is if @a client is NULL.
301 */
302 int client_free( transport_client* client ){
303         if(client == NULL) return 0;
304
305         session_free( client->session );
306         transport_message* current = client->msg_q_head;
307         transport_message* next;
308
309         /* deallocate the list of messages */
310         while( current != NULL ) {
311                 next = current->next;
312                 message_free( current );
313                 current = next;
314         }
315
316         free(client->host);
317         free(client->xmpp_id);
318         free( client );
319         return 1;
320 }
321
322 int client_sock_fd( transport_client* client )
323 {
324         if( !client )
325                 return 0;
326         else
327                 return client->session->sock_id;
328 }