res_pjsip_session: properly handle SDP from a forked call with early media
[asterisk/asterisk.git] / res / ari / ari_websockets.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * David M. Lee, II <dlee@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 #include "asterisk.h"
20
21 #include "asterisk/ari.h"
22 #include "asterisk/astobj2.h"
23 #include "asterisk/http_websocket.h"
24 #include "asterisk/stasis_app.h"
25 #include "internal.h"
26
27 /*! \file
28  *
29  * \brief WebSocket support for RESTful API's.
30  * \author David M. Lee, II <dlee@digium.com>
31  */
32
33 struct ast_ari_websocket_session {
34         struct ast_websocket *ws_session;
35         int (*validator)(struct ast_json *);
36 };
37
38 static void websocket_session_dtor(void *obj)
39 {
40         struct ast_ari_websocket_session *session = obj;
41
42         ast_websocket_unref(session->ws_session);
43         session->ws_session = NULL;
44 }
45
46 /*!
47  * \brief Validator that always succeeds.
48  */
49 static int null_validator(struct ast_json *json)
50 {
51         return 1;
52 }
53
54 struct ast_ari_websocket_session *ast_ari_websocket_session_create(
55         struct ast_websocket *ws_session, int (*validator)(struct ast_json *))
56 {
57         RAII_VAR(struct ast_ari_websocket_session *, session, NULL, ao2_cleanup);
58         RAII_VAR(struct ast_ari_conf *, config, ast_ari_config_get(), ao2_cleanup);
59
60         if (ws_session == NULL) {
61                 return NULL;
62         }
63
64         if (config == NULL || config->general == NULL) {
65                 return NULL;
66         }
67
68         if (validator == NULL) {
69                 validator = null_validator;
70         }
71
72         if (ast_websocket_set_nonblock(ws_session) != 0) {
73                 ast_log(LOG_ERROR,
74                         "ARI web socket failed to set nonblock; closing: %s\n",
75                         strerror(errno));
76                 return NULL;
77         }
78
79         if (ast_websocket_set_timeout(ws_session, config->general->write_timeout)) {
80                 ast_log(LOG_WARNING, "Failed to set write timeout %d on ARI web socket\n",
81                         config->general->write_timeout);
82         }
83
84         session = ao2_alloc(sizeof(*session), websocket_session_dtor);
85         if (!session) {
86                 return NULL;
87         }
88
89         ao2_ref(ws_session, +1);
90         session->ws_session = ws_session;
91         session->validator = validator;
92
93         ao2_ref(session, +1);
94         return session;
95 }
96
97 struct ast_json *ast_ari_websocket_session_read(
98         struct ast_ari_websocket_session *session)
99 {
100         RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
101
102         if (ast_websocket_fd(session->ws_session) < 0) {
103                 return NULL;
104         }
105
106         while (!message) {
107                 int res;
108                 char *payload;
109                 uint64_t payload_len;
110                 enum ast_websocket_opcode opcode;
111                 int fragmented;
112
113                 res = ast_wait_for_input(
114                         ast_websocket_fd(session->ws_session), -1);
115
116                 if (res <= 0) {
117                         ast_log(LOG_WARNING, "WebSocket poll error: %s\n",
118                                 strerror(errno));
119                         return NULL;
120                 }
121
122                 res = ast_websocket_read(session->ws_session, &payload,
123                         &payload_len, &opcode, &fragmented);
124
125                 if (res != 0) {
126                         ast_log(LOG_WARNING, "WebSocket read error: %s\n",
127                                 strerror(errno));
128                         return NULL;
129                 }
130
131                 switch (opcode) {
132                 case AST_WEBSOCKET_OPCODE_CLOSE:
133                         ast_debug(1, "WebSocket closed\n");
134                         return NULL;
135                 case AST_WEBSOCKET_OPCODE_TEXT:
136                         message = ast_json_load_buf(payload, payload_len, NULL);
137                         if (message == NULL) {
138                                 ast_log(LOG_WARNING,
139                                         "WebSocket input failed to parse\n");
140                         }
141
142                         break;
143                 default:
144                         /* Ignore all other message types */
145                         break;
146                 }
147         }
148
149         return ast_json_ref(message);
150 }
151
152 #define VALIDATION_FAILED                               \
153         "{"                                             \
154         "  \"error\": \"InvalidMessage\","              \
155         "  \"message\": \"Message validation failed\""  \
156         "}"
157
158 int ast_ari_websocket_session_write(struct ast_ari_websocket_session *session,
159         struct ast_json *message)
160 {
161         RAII_VAR(char *, str, NULL, ast_json_free);
162
163 #ifdef AST_DEVMODE
164         if (!session->validator(message)) {
165                 ast_log(LOG_ERROR, "Outgoing message failed validation\n");
166                 return ast_websocket_write_string(session->ws_session, VALIDATION_FAILED);
167         }
168 #endif
169
170         str = ast_json_dump_string_format(message, ast_ari_json_format());
171
172         if (str == NULL) {
173                 ast_log(LOG_ERROR, "Failed to encode JSON object\n");
174                 return -1;
175         }
176
177         if (ast_websocket_write_string(session->ws_session, str)) {
178                 ast_log(LOG_NOTICE, "Problem occurred during websocket write to %s, websocket closed\n",
179                         ast_sockaddr_stringify(ast_ari_websocket_session_get_remote_addr(session)));
180                 return -1;
181         }
182         return 0;
183 }
184
185 struct ast_sockaddr *ast_ari_websocket_session_get_remote_addr(
186         struct ast_ari_websocket_session *session)
187 {
188         return ast_websocket_remote_address(session->ws_session);
189 }
190
191 void ari_handle_websocket(struct ast_websocket_server *ws_server,
192         struct ast_tcptls_session_instance *ser, const char *uri,
193         enum ast_http_method method, struct ast_variable *get_params,
194         struct ast_variable *headers)
195 {
196         struct ast_http_uri fake_urih = {
197                 .data = ws_server,
198         };
199         ast_websocket_uri_cb(ser, &fake_urih, uri, method, get_params,
200                 headers);
201 }
202
203 const char *ast_ari_websocket_session_id(
204         const struct ast_ari_websocket_session *session)
205 {
206         return ast_websocket_session_id(session->ws_session);
207 }