PageRenderTime 92ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/src/libopensrf/transport_client.c

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