Sat Apr 26 2014 22:02:52

Asterisk developer's documentation


http_websocket.h File Reference

Support for WebSocket connections within the Asterisk HTTP server. More...

Include dependency graph for http_websocket.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Typedefs

typedef void(* ast_websocket_callback )(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers)
 Callback for when a new connection for a sub-protocol is established.

Enumerations

enum  ast_websocket_opcode {
  AST_WEBSOCKET_OPCODE_TEXT = 0x1, AST_WEBSOCKET_OPCODE_BINARY = 0x2, AST_WEBSOCKET_OPCODE_PING = 0x9, AST_WEBSOCKET_OPCODE_PONG = 0xA,
  AST_WEBSOCKET_OPCODE_CLOSE = 0x8, AST_WEBSOCKET_OPCODE_CONTINUATION = 0x0
}
 WebSocket operation codes. More...

Functions

int ast_websocket_add_protocol (const char *name, ast_websocket_callback callback)
 Add a sub-protocol handler to the server.
int ast_websocket_close (struct ast_websocket *session, uint16_t reason)
 Close a WebSocket session by sending a message with the CLOSE opcode and an optional code.
int ast_websocket_fd (struct ast_websocket *session)
 Get the file descriptor for a WebSocket session.
int ast_websocket_is_secure (struct ast_websocket *session)
 Get whether the WebSocket session is using a secure transport or not.
int ast_websocket_read (struct ast_websocket *session, char **payload, uint64_t *payload_len, enum ast_websocket_opcode *opcode, int *fragmented)
 Read a WebSocket frame and handle it.
void ast_websocket_reconstruct_disable (struct ast_websocket *session)
 Disable multi-frame reconstruction.
void ast_websocket_reconstruct_enable (struct ast_websocket *session, size_t bytes)
 Enable multi-frame reconstruction up to a certain number of bytes.
void ast_websocket_ref (struct ast_websocket *session)
 Increase the reference count for a WebSocket session.
struct ast_sockaddrast_websocket_remote_address (struct ast_websocket *session)
 Get the remote address for a WebSocket connected session.
int ast_websocket_remove_protocol (const char *name, ast_websocket_callback callback)
 Remove a sub-protocol handler from the server.
int ast_websocket_set_nonblock (struct ast_websocket *session)
 Set the socket of a WebSocket session to be non-blocking.
void ast_websocket_unref (struct ast_websocket *session)
 Decrease the reference count for a WebSocket session.
int ast_websocket_write (struct ast_websocket *session, enum ast_websocket_opcode opcode, char *payload, uint64_t actual_length)
 Construct and transmit a WebSocket frame.

Detailed Description

Support for WebSocket connections within the Asterisk HTTP server.

Author:
Joshua Colp <jcolp@digium.com>

Definition in file http_websocket.h.


Typedef Documentation

typedef void(* ast_websocket_callback)(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers)

Callback for when a new connection for a sub-protocol is established.

Parameters:
sessionA WebSocket session structure
parametersParameters extracted from the request URI
headersHeaders included in the request
Note:
Once called the ownership of the session is transferred to the sub-protocol handler. It is responsible for closing and cleaning up.

Definition at line 58 of file http_websocket.h.


Enumeration Type Documentation

WebSocket operation codes.

Enumerator:
AST_WEBSOCKET_OPCODE_TEXT 

Text frame

AST_WEBSOCKET_OPCODE_BINARY 

Binary frame

AST_WEBSOCKET_OPCODE_PING 

Request that the other side respond with a pong

AST_WEBSOCKET_OPCODE_PONG 

Response to a ping

AST_WEBSOCKET_OPCODE_CLOSE 

Connection is being closed

AST_WEBSOCKET_OPCODE_CONTINUATION 

Continuation of a previous frame

Definition at line 33 of file http_websocket.h.

                          {
   AST_WEBSOCKET_OPCODE_TEXT = 0x1,         /*!< Text frame */
   AST_WEBSOCKET_OPCODE_BINARY = 0x2,       /*!< Binary frame */
   AST_WEBSOCKET_OPCODE_PING = 0x9,         /*!< Request that the other side respond with a pong */
   AST_WEBSOCKET_OPCODE_PONG = 0xA,         /*!< Response to a ping */
   AST_WEBSOCKET_OPCODE_CLOSE = 0x8,        /*!< Connection is being closed */
   AST_WEBSOCKET_OPCODE_CONTINUATION = 0x0, /*!< Continuation of a previous frame */
};

Function Documentation

int ast_websocket_add_protocol ( const char *  name,
ast_websocket_callback  callback 
)

Add a sub-protocol handler to the server.

Parameters:
nameName of the sub-protocol to register
callbackCallback called when a new connection requesting the sub-protocol is established
Return values:
0success
-1if sub-protocol handler could not be registered

Definition at line 131 of file res_http_websocket.c.

References ao2_alloc, ao2_find, ao2_link_flags, ao2_lock, ao2_ref, ao2_unlock, ast_strdup, ast_verb, websocket_protocol::callback, websocket_protocol::name, OBJ_KEY, OBJ_NOLOCK, and protocol_destroy_fn().

Referenced by load_module().

{
   struct websocket_protocol *protocol;

   ao2_lock(protocols);

   /* Ensure a second protocol handler is not registered for the same protocol */
   if ((protocol = ao2_find(protocols, name, OBJ_KEY | OBJ_NOLOCK))) {
      ao2_ref(protocol, -1);
      ao2_unlock(protocols);
      return -1;
   }

   if (!(protocol = ao2_alloc(sizeof(*protocol), protocol_destroy_fn))) {
      ao2_unlock(protocols);
      return -1;
   }

   if (!(protocol->name = ast_strdup(name))) {
      ao2_ref(protocol, -1);
      ao2_unlock(protocols);
      return -1;
   }

   protocol->callback = callback;

   ao2_link_flags(protocols, protocol, OBJ_NOLOCK);
   ao2_unlock(protocols);
   ao2_ref(protocol, -1);

   ast_verb(2, "WebSocket registered sub-protocol '%s'\n", name);

   return 0;
}
int ast_websocket_close ( struct ast_websocket session,
uint16_t  reason 
)

Close a WebSocket session by sending a message with the CLOSE opcode and an optional code.

Parameters:
sessionPointer to the WebSocket session
reasonReason code for closing the session as defined in the RFC
Return values:
0if successfully written
-1if error occurred

Definition at line 188 of file res_http_websocket.c.

References AST_WEBSOCKET_OPCODE_CLOSE, ast_websocket::closing, ast_websocket::f, and put_unaligned_uint16().

Referenced by ast_websocket_read().

{
   char frame[4] = { 0, }; /* The header is 2 bytes and the reason code takes up another 2 bytes */

   frame[0] = AST_WEBSOCKET_OPCODE_CLOSE | 0x80;
   frame[1] = 2; /* The reason code is always 2 bytes */

   /* If no reason has been specified assume 1000 which is normal closure */
   put_unaligned_uint16(&frame[2], htons(reason ? reason : 1000));

   session->closing = 1;

   return (fwrite(frame, 1, 4, session->f) == 4) ? 0 : -1;
}
int ast_websocket_fd ( struct ast_websocket session)

Get the file descriptor for a WebSocket session.

Return values:
filedescriptor
Note:
You must *not* directly read from or write to this file descriptor. It should only be used for polling.

Definition at line 268 of file res_http_websocket.c.

References ast_websocket::closing, and ast_websocket::fd.

Referenced by sip_prepare_socket(), sip_websocket_callback(), and websocket_echo_callback().

{
   return session->closing ? -1 : session->fd;
}
int ast_websocket_is_secure ( struct ast_websocket session)

Get whether the WebSocket session is using a secure transport or not.

Return values:
0if unsecure
1if secure

Definition at line 278 of file res_http_websocket.c.

References ast_websocket::secure.

Referenced by sip_websocket_callback().

{
   return session->secure;
}
int ast_websocket_read ( struct ast_websocket session,
char **  payload,
uint64_t *  payload_len,
enum ast_websocket_opcode opcode,
int *  fragmented 
)

Read a WebSocket frame and handle it.

Parameters:
sessionPointer to the WebSocket session
payloadPointer to a char* which will be populated with a pointer to the payload if present
payload_lenPointer to a uint64_t which will be populated with the length of the payload if present
opcodePointer to an enum which will be populated with the opcode of the frame
fragmentedPointer to an int which is set to 1 if payload is fragmented and 0 if not
Return values:
-1on error
0on success
Note:
Once an AST_WEBSOCKET_OPCODE_CLOSE opcode is received the socket will be closed

Definition at line 364 of file res_http_websocket.c.

References ast_websocket::address, ast_log(), ast_realloc, ast_sockaddr_stringify(), ast_verb, ast_websocket_close(), AST_WEBSOCKET_OPCODE_BINARY, AST_WEBSOCKET_OPCODE_CLOSE, AST_WEBSOCKET_OPCODE_CONTINUATION, AST_WEBSOCKET_OPCODE_PING, AST_WEBSOCKET_OPCODE_PONG, AST_WEBSOCKET_OPCODE_TEXT, ast_websocket_write(), ast_websocket::closing, ast_websocket::f, frame_size, get_unaligned_uint16(), get_unaligned_uint64(), LOG_WARNING, MAXIMUM_FRAME_SIZE, MIN_WS_HDR_SZ, ast_websocket::opcode, ast_websocket::payload, ast_websocket::payload_len, ast_websocket::reconstruct, and ws_safe_read().

Referenced by sip_websocket_callback(), and websocket_echo_callback().

{
   char buf[MAXIMUM_FRAME_SIZE] = "";
   int fin = 0;
   int mask_present = 0;
   char *mask = NULL, *new_payload = NULL;
   size_t options_len = 0, frame_size = 0;

   *payload = NULL;
   *payload_len = 0;
   *fragmented = 0;

   if (ws_safe_read(session, &buf[0], MIN_WS_HDR_SZ, opcode)) {
      return 0;
   }
   frame_size += MIN_WS_HDR_SZ;

   /* ok, now we have the first 2 bytes, so we know some flags, opcode and payload length (or whether payload length extension will be required) */
   *opcode = buf[0] & 0xf;
   *payload_len = buf[1] & 0x7f;
   if (*opcode == AST_WEBSOCKET_OPCODE_TEXT || *opcode == AST_WEBSOCKET_OPCODE_BINARY || *opcode == AST_WEBSOCKET_OPCODE_CONTINUATION ||
       *opcode == AST_WEBSOCKET_OPCODE_PING || *opcode == AST_WEBSOCKET_OPCODE_PONG) {
      fin = (buf[0] >> 7) & 1;
      mask_present = (buf[1] >> 7) & 1;

      /* Based on the mask flag and payload length, determine how much more we need to read before start parsing the rest of the header */
      options_len += mask_present ? 4 : 0;
      options_len += (*payload_len == 126) ? 2 : (*payload_len == 127) ? 8 : 0;
      if (options_len) {
         /* read the rest of the header options */
         if (ws_safe_read(session, &buf[frame_size], options_len, opcode)) {
            return 0;
         }
         frame_size += options_len;
      }

      if (*payload_len == 126) {
         /* Grab the 2-byte payload length  */
         *payload_len = ntohs(get_unaligned_uint16(&buf[2]));
         mask = &buf[4];
      } else if (*payload_len == 127) {
         /* Grab the 8-byte payload length  */
         *payload_len = ntohl(get_unaligned_uint64(&buf[2]));
         mask = &buf[10];
      } else {
         /* Just set the mask after the small 2-byte header */
         mask = &buf[2];
      }

      /* Now read the rest of the payload */
      *payload = &buf[frame_size]; /* payload will start here, at the end of the options, if any */
      frame_size = frame_size + (*payload_len); /* final frame size is header + optional headers + payload data */
      if (frame_size > MAXIMUM_FRAME_SIZE) {
         ast_log(LOG_WARNING, "Cannot fit huge websocket frame of %zd bytes\n", frame_size);
         /* The frame won't fit :-( */
         ast_websocket_close(session, 1009);
         return -1;
      }

      if (ws_safe_read(session, (*payload), (*payload_len), opcode)) {
         return 0;
      }

      /* If a mask is present unmask the payload */
      if (mask_present) {
         unsigned int pos;
         for (pos = 0; pos < *payload_len; pos++) {
            (*payload)[pos] ^= mask[pos % 4];
         }
      }

      if (!(new_payload = ast_realloc(session->payload, (session->payload_len + *payload_len)))) {
         ast_log(LOG_WARNING, "Failed allocation: %p, %zd, %"PRIu64"\n",
            session->payload, session->payload_len, *payload_len);
         *payload_len = 0;
         ast_websocket_close(session, 1009);
         return 0;
      }

      /* Per the RFC for PING we need to send back an opcode with the application data as received */
      if (*opcode == AST_WEBSOCKET_OPCODE_PING) {
         ast_websocket_write(session, AST_WEBSOCKET_OPCODE_PONG, *payload, *payload_len);
      }

      session->payload = new_payload;
      memcpy((session->payload + session->payload_len), (*payload), (*payload_len));
      session->payload_len += *payload_len;

      if (!fin && session->reconstruct && (session->payload_len < session->reconstruct)) {
         /* If this is not a final message we need to defer returning it until later */
         if (*opcode != AST_WEBSOCKET_OPCODE_CONTINUATION) {
            session->opcode = *opcode;
         }
         *opcode = AST_WEBSOCKET_OPCODE_CONTINUATION;
         *payload_len = 0;
         *payload = NULL;
      } else {
         if (*opcode == AST_WEBSOCKET_OPCODE_CONTINUATION) {
            if (!fin) {
               /* If this was not actually the final message tell the user it is fragmented so they can deal with it accordingly */
               *fragmented = 1;
            } else {
               /* Final frame in multi-frame so push up the actual opcode */
               *opcode = session->opcode;
            }
         }
         *payload_len = session->payload_len;
         *payload = session->payload;
         session->payload_len = 0;
      }
   } else if (*opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
      /* Make the payload available so the user can look at the reason code if they so desire */
      if ((*payload_len) && (new_payload = ast_realloc(session->payload, *payload_len))) {
         if (ws_safe_read(session, &buf[frame_size], (*payload_len), opcode)) {
            return 0;
         }
         session->payload = new_payload;
         memcpy(session->payload, &buf[frame_size], *payload_len);
         *payload = session->payload;
         frame_size += (*payload_len);
      }

      if (!session->closing) {
         ast_websocket_close(session, 0);
      }

      fclose(session->f);
      session->f = NULL;
      ast_verb(2, "WebSocket connection from '%s' closed\n", ast_sockaddr_stringify(&session->address));
   } else {
      ast_log(LOG_WARNING, "WebSocket unknown opcode %d\n", *opcode);
      /* We received an opcode that we don't understand, the RFC states that 1003 is for a type of data that can't be accepted... opcodes
       * fit that, I think. */
      ast_websocket_close(session, 1003);
   }

   return 0;
}

Disable multi-frame reconstruction.

Parameters:
sessionPointer to the WebSocket session
Note:
If reconstruction is disabled each message that is part of a multi-frame message will be sent up to the user when ast_websocket_read is called.

Definition at line 253 of file res_http_websocket.c.

References ast_websocket::reconstruct.

{
   session->reconstruct = 0;
}
void ast_websocket_reconstruct_enable ( struct ast_websocket session,
size_t  bytes 
)

Enable multi-frame reconstruction up to a certain number of bytes.

Parameters:
sessionPointer to the WebSocket session
bytesIf a reconstructed payload exceeds the specified number of bytes the payload will be returned and upon reception of the next multi-frame a new reconstructed payload will begin.

Definition at line 248 of file res_http_websocket.c.

References MAXIMUM_RECONSTRUCTION_CEILING, MIN, and ast_websocket::reconstruct.

void ast_websocket_ref ( struct ast_websocket session)

Increase the reference count for a WebSocket session.

Parameters:
sessionPointer to the WebSocket session

Definition at line 258 of file res_http_websocket.c.

References ao2_ref.

Referenced by copy_socket_data().

{
   ao2_ref(session, +1);
}
struct ast_sockaddr* ast_websocket_remote_address ( struct ast_websocket session) [read]

Get the remote address for a WebSocket connected session.

Return values:
ast_sockaddrRemote address

Definition at line 273 of file res_http_websocket.c.

References ast_websocket::address.

Referenced by sip_websocket_callback().

{
   return &session->address;
}
int ast_websocket_remove_protocol ( const char *  name,
ast_websocket_callback  callback 
)

Remove a sub-protocol handler from the server.

Parameters:
nameName of the sub-protocol to unregister
callbackCallback that was previously registered with the sub-protocol
Return values:
0success
-1if sub-protocol was not found or if callback did not match

Definition at line 166 of file res_http_websocket.c.

References ao2_find, ao2_ref, ao2_unlink, ast_verb, websocket_protocol::callback, and OBJ_KEY.

Referenced by unload_module().

{
   struct websocket_protocol *protocol;

   if (!(protocol = ao2_find(protocols, name, OBJ_KEY))) {
      return -1;
   }

   if (protocol->callback != callback) {
      ao2_ref(protocol, -1);
      return -1;
   }

   ao2_unlink(protocols, protocol);
   ao2_ref(protocol, -1);

   ast_verb(2, "WebSocket unregistered sub-protocol '%s'\n", name);

   return 0;
}
int ast_websocket_set_nonblock ( struct ast_websocket session)

Set the socket of a WebSocket session to be non-blocking.

Return values:
0on success
-1on failure

Definition at line 283 of file res_http_websocket.c.

References ast_websocket::fd.

Referenced by sip_websocket_callback().

{
   int flags;

   if ((flags = fcntl(session->fd, F_GETFL)) == -1) {
      return -1;
   }

   flags |= O_NONBLOCK;

   if ((flags = fcntl(session->fd, F_SETFL, flags)) == -1) {
      return -1;
   }

   return 0;
}
void ast_websocket_unref ( struct ast_websocket session)

Decrease the reference count for a WebSocket session.

Parameters:
sessionPointer to the WebSocket session

Definition at line 263 of file res_http_websocket.c.

References ao2_ref.

Referenced by __sip_destroy(), copy_socket_data(), expire_register(), parse_moved_contact(), set_socket_transport(), sip_destroy_peer(), sip_websocket_callback(), and websocket_echo_callback().

{
   ao2_ref(session, -1);
}
int ast_websocket_write ( struct ast_websocket session,
enum ast_websocket_opcode  opcode,
char *  payload,
uint64_t  actual_length 
)

Construct and transmit a WebSocket frame.

Parameters:
sessionPointer to the WebSocket session
opcodeWebSocket operation code to place in the frame
payloadOptional pointer to a payload to add to the frame
actual_lengthLength of the payload (0 if no payload)
Return values:
0if successfully written
-1if error occurred

Definition at line 205 of file res_http_websocket.c.

References ast_alloca, ast_websocket::f, put_unaligned_uint16(), and put_unaligned_uint64().

Referenced by __sip_xmit(), ast_websocket_read(), and websocket_echo_callback().

{
   size_t header_size = 2; /* The minimum size of a websocket frame is 2 bytes */
   char *frame;
   uint64_t length = 0;

   if (actual_length < 126) {
      length = actual_length;
   } else if (actual_length < (1 << 16)) {
      length = 126;
      /* We need an additional 2 bytes to store the extended length */
      header_size += 2;
   } else {
      length = 127;
      /* We need an additional 8 bytes to store the really really extended length */
      header_size += 8;
   }

   frame = ast_alloca(header_size);
   memset(frame, 0, sizeof(*frame));

   frame[0] = opcode | 0x80;
   frame[1] = length;

   /* Use the additional available bytes to store the length */
   if (length == 126) {
      put_unaligned_uint16(&frame[2], htons(actual_length));
   } else if (length == 127) {
      put_unaligned_uint64(&frame[2], htonl(actual_length));
   }

   if (fwrite(frame, 1, header_size, session->f) != header_size) {
      return -1;
   }

   if (fwrite(payload, 1, actual_length, session->f) != actual_length) {
      return -1;
   }
   fflush(session->f);

   return 0;
}