683f00e19e1c1a79f08ddf18a4db53e068bb9e6e
[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 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
22
23 #include "asterisk/ari.h"
24 #include "asterisk/astobj2.h"
25 #include "asterisk/http_websocket.h"
26 #include "internal.h"
27
28 /*! \file
29  *
30  * \brief WebSocket support for RESTful API's.
31  * \author David M. Lee, II <dlee@digium.com>
32  */
33
34 struct ast_ari_websocket_session {
35         struct ast_websocket *ws_session;
36         int (*validator)(struct ast_json *);
37 };
38
39 static void websocket_session_dtor(void *obj)
40 {
41         struct ast_ari_websocket_session *session = obj;
42
43         ast_websocket_unref(session->ws_session);
44         session->ws_session = NULL;
45 }
46
47 /*!
48  * \brief Validator that always succeeds.
49  */
50 static int null_validator(struct ast_json *json)
51 {
52         return 1;
53 }
54
55 struct ast_ari_websocket_session *ast_ari_websocket_session_create(
56         struct ast_websocket *ws_session, int (*validator)(struct ast_json *))
57 {
58         RAII_VAR(struct ast_ari_websocket_session *, session, NULL, ao2_cleanup);
59         RAII_VAR(struct ast_ari_conf *, config, ast_ari_config_get(), ao2_cleanup);
60
61         if (ws_session == NULL) {
62                 return NULL;
63         }
64
65         if (config == NULL || config->general == NULL) {
66                 return NULL;
67         }
68
69         if (validator == NULL) {
70                 validator = null_validator;
71         }
72
73         if (ast_websocket_set_nonblock(ws_session) != 0) {
74                 ast_log(LOG_ERROR,
75                         "ARI web socket failed to set nonblock; closing: %s\n",
76                         strerror(errno));
77                 return NULL;
78         }
79
80         if (ast_websocket_set_timeout(ws_session, config->general->write_timeout)) {
81                 ast_log(LOG_WARNING, "Failed to set write timeout %d on ARI web socket\n",
82                         config->general->write_timeout);
83         }
84
85         session = ao2_alloc(sizeof(*session), websocket_session_dtor);
86         if (!session) {
87                 return NULL;
88         }
89
90         ao2_ref(ws_session, +1);
91         session->ws_session = ws_session;
92         session->validator = validator;
93
94         ao2_ref(session, +1);
95         return session;
96 }
97
98 struct ast_json *ast_ari_websocket_session_read(
99         struct ast_ari_websocket_session *session)
100 {
101         RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
102
103         if (ast_websocket_fd(session->ws_session) < 0) {
104                 return NULL;
105         }
106
107
108         if (ast_websocket_fd(session->ws_session) <= 0) {
109                 return NULL;
110         }
111
112
113         while (!message) {
114                 int res;
115                 char *payload;
116                 uint64_t payload_len;
117                 enum ast_websocket_opcode opcode;
118                 int fragmented;
119
120                 res = ast_wait_for_input(
121                         ast_websocket_fd(session->ws_session), -1);
122
123                 if (res <= 0) {
124                         ast_log(LOG_WARNING, "WebSocket poll error: %s\n",
125                                 strerror(errno));
126                         return NULL;
127                 }
128
129                 res = ast_websocket_read(session->ws_session, &payload,
130                         &payload_len, &opcode, &fragmented);
131
132                 if (res != 0) {
133                         ast_log(LOG_WARNING, "WebSocket read error: %s\n",
134                                 strerror(errno));
135                         return NULL;
136                 }
137
138                 switch (opcode) {
139                 case AST_WEBSOCKET_OPCODE_CLOSE:
140                         ast_debug(1, "WebSocket closed\n");
141                         return NULL;
142                 case AST_WEBSOCKET_OPCODE_TEXT:
143                         message = ast_json_load_buf(payload, payload_len, NULL);
144                         if (message == NULL) {
145                                 ast_log(LOG_WARNING,
146                                         "WebSocket input failed to parse\n");
147                         }
148                         break;
149                 default:
150                         /* Ignore all other message types */
151                         break;
152                 }
153         }
154
155         return ast_json_ref(message);
156 }
157
158 #define VALIDATION_FAILED                               \
159         "{"                                             \
160         "  \"error\": \"InvalidMessage\","              \
161         "  \"message\": \"Message validation failed\""  \
162         "}" 
163
164 int ast_ari_websocket_session_write(struct ast_ari_websocket_session *session,
165         struct ast_json *message)
166 {
167         RAII_VAR(char *, str, NULL, ast_json_free);
168
169 #ifdef AST_DEVMODE
170         if (!session->validator(message)) {
171                 ast_log(LOG_ERROR, "Outgoing message failed validation\n");
172                 return ast_websocket_write(session->ws_session,
173                         AST_WEBSOCKET_OPCODE_TEXT, VALIDATION_FAILED,
174                         strlen(VALIDATION_FAILED));
175         }
176 #endif
177
178         str = ast_json_dump_string_format(message, ast_ari_json_format());
179
180         if (str == NULL) {
181                 ast_log(LOG_ERROR, "Failed to encode JSON object\n");
182                 return -1;
183         }
184
185         ast_debug(3, "Examining ARI event: \n%s\n", str);
186         if (ast_websocket_write(session->ws_session,
187                                 AST_WEBSOCKET_OPCODE_TEXT, str, strlen(str))) {
188                 ast_log(LOG_NOTICE, "Problem occurred during websocket write, websocket closed\n");
189                 return -1;
190         }
191         return 0;
192 }
193
194 void ari_handle_websocket(struct ast_websocket_server *ws_server,
195         struct ast_tcptls_session_instance *ser, const char *uri,
196         enum ast_http_method method, struct ast_variable *get_params,
197         struct ast_variable *headers)
198 {
199         struct ast_http_uri fake_urih = {
200                 .data = ws_server,
201         };
202         ast_websocket_uri_cb(ser, &fake_urih, uri, method, get_params,
203                 headers);
204 }