1 #include <opensrf/transport_client.h>
4 @file transport_client.c
5 @brief Collection of routines for sending and receiving single messages over Jabber.
7 These functions form an API built on top of the transport_session API. They serve
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.
13 static void client_message_handler( void* client, transport_message* msg );
15 //int main( int argc, char** argv );
18 int main( int argc, char** argv ) {
20 transport_message* recv;
21 transport_message* send;
23 transport_client* client = client_init( "spacely.georgialibraries.org", 5222 );
25 // try to connect, allow 15 second connect timeout
26 if( client_connect( client, "admin", "asdfjkjk", "system", 15 ) ) {
27 printf("Connected...\n");
29 printf( "NOT Connected...\n" ); exit(99);
32 while( (recv = client_recv( client, -1 )) ) {
35 int len = strlen(recv->body);
37 osrf_clearbuf( buf, 0, sizeof(buf));
38 sprintf( buf, "Echoing...%s", recv->body );
39 send = message_init( buf, "Echoing Stuff", "12345", recv->sender, "" );
41 send = message_init( " * ECHOING * ", "Echoing Stuff", "12345", recv->sender, "" );
44 if( send == NULL ) { printf("something's wrong"); }
45 client_send_message( client, send );
51 printf( "ended recv loop\n" );
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.
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
71 The calling code is responsible for freeing the transport_client by calling client_free().
73 transport_client* client_init( const char* server, int port, const char* unix_path, int component ) {
75 if(server == NULL) return NULL;
77 /* build and clear the client object */
78 transport_client* client = safe_malloc( sizeof( transport_client) );
80 /* start with an empty message queue */
81 client->msg_q_head = NULL;
82 client->msg_q_tail = NULL;
84 /* build the session */
85 client->session = init_transport( server, port, unix_path, client, component );
87 client->session->message_callback = client_message_handler;
89 client->host = strdup(server);
90 client->xmpp_id = NULL;
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.
106 Besides opening the Jabber session, create a Jabber ID for future use.
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.
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.
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.
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 ) {
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 );
131 // Open a transport_session
132 return session_connect( client->session, username,
133 password, resource, connect_timeout, auth_type );
137 @brief Disconnect from the Jabber session.
138 @param client Pointer to the transport_client.
139 @return 0 in all cases.
141 If there are any messages still in the queue, they stay there; i.e. we don't free them here.
143 int client_disconnect( transport_client* client ) {
144 if( client == NULL ) { return 0; }
145 return session_disconnect( client->session );
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.
153 int client_connected( const transport_client* client ) {
154 if(client == NULL) return 0;
155 return session_connected( client->session );
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.
164 Translate the transport_message into XML and send it to Jabber, using the previously
165 stored Jabber ID for the sender.
167 int client_send_message( transport_client* client, transport_message* msg ) {
168 if( client == NULL || client->error )
172 msg->sender = strdup(client->xmpp_id);
173 return session_send_msg( client->session, msg );
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.
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
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,
190 The calling code is responsible for freeing the transport_message by calling message_free().
192 transport_message* client_recv( transport_client* client, int timeout ) {
193 if( client == NULL ) { return NULL; }
195 int error = 0; /* boolean */
197 if( NULL == client->msg_q_head ) {
199 // No message available on the queue? Try to get a fresh one.
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.
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.
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.
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.
215 // Likewise we could time out while still receiving the second or subsequent message,
216 // return the first message, and resume receiving messages later.
218 if( timeout == -1 ) { /* wait potentially forever for data to arrive */
222 if( (x = session_wait( client->session, -1 )) ) {
223 osrfLogDebug(OSRF_LOG_MARK, "session_wait returned failure code %d\n", x);
227 } while( client->msg_q_head == NULL );
229 } else { /* loop up to 'timeout' seconds waiting for data to arrive */
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 */
234 time_t start = time(NULL);
235 time_t remaining = (time_t) timeout;
239 if( (wait_ret = session_wait( client->session, (int) remaining)) ) {
241 osrfLogDebug(OSRF_LOG_MARK,
242 "session_wait returned failure code %d: setting error=1\n", wait_ret);
246 remaining -= time(NULL) - start;
247 } while( NULL == client->msg_q_head && remaining > 0 );
251 transport_message* msg = NULL;
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;
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.
272 Add a newly arrived input message to the tail of the queue.
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.
279 static void client_message_handler( void* client, transport_message* msg ){
281 if(client == NULL) return;
282 if(msg == NULL) return;
284 transport_client* cli = (transport_client*) client;
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;
290 cli->msg_q_tail->next = msg;
291 cli->msg_q_tail = msg;
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.
302 int client_free( transport_client* client ) {
305 session_free( client->session );
306 client->session = NULL;
307 return client_discard( client );
311 @brief Free a transport_client's resources, but without disconnecting.
312 @param client Pointer to the transport_client to be freed.
313 @return 1 if successful, or 0 if not. The only error condition is if @a client is NULL.
315 A child process may call this in order to free the resources associated with the parent's
316 transport_client, but without disconnecting from Jabber, since disconnecting would
317 disconnect the parent as well.
319 int client_discard( transport_client* client ) {
323 transport_message* current = client->msg_q_head;
324 transport_message* next;
326 /* deallocate the list of messages */
327 while( current != NULL ) {
328 next = current->next;
329 message_free( current );
334 free(client->xmpp_id);
339 int client_sock_fd( transport_client* client )
344 return client->session->sock_id;