1 /* -*- c-basic-offset: 8; -*- */
2 /* proto_roaraudio.c: RoarAudio protocol support.
3 * $Id$
4 *
5 * Copyright (C) 2015-2019 Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the Free
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21
22 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
25
26 #ifdef HAVE_INTTYPES_H
27 # include <inttypes.h>
28 #endif
29
30 /* for htonl(). */
31 #ifdef HAVE_ARPA_INET_H
32 # include <arpa/inet.h>
33 #endif
34
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38
39 #include <shout/shout.h>
40 #include "shout_private.h"
41
42 typedef enum {
43 STATE_IDENT = 0,
44 STATE_AUTH,
45 STATE_NEW_STREAM,
46 STATE_EXEC
47 } shout_roar_protocol_state_t;
48
49 typedef enum {
50 CMD_IDENTIFY = 1,
51 CMD_AUTH = 2,
52 CMD_NEW_STREAM = 3,
53 CMD_EXEC_STREAM = 5,
54 CMD_OK = 254
55 } shout_roar_command_t;
56
57 #define STREAM_NONE ((uint16_t)0xFFFF)
58 #define HEADER_SIZE 10
59
command_send(shout_t * self,shout_connection_t * connection,shout_roar_command_t command,uint16_t stream,const void * data,size_t datalen)60 static int command_send(shout_t *self,
61 shout_connection_t *connection,
62 shout_roar_command_t command,
63 uint16_t stream,
64 const void *data,
65 size_t datalen)
66 {
67 uint8_t header[HEADER_SIZE];
68
69 if (!self)
70 return SHOUTERR_INSANE;
71
72 if (datalen > 65535)
73 return SHOUTERR_INSANE;
74
75 if (datalen && !data)
76 return SHOUTERR_INSANE;
77
78 /* version.
79 * While version 2 is already on it's way we still go for version 0
80 * as it will work well for us and is defined as part of the core
81 * every RoarAudio server MUST implement.
82 */
83 header[0] = 0;
84 /* command ID. */
85 header[1] = command;
86 /* stream ID. First upper then lower byte. */
87 header[2] = (stream & 0xFF00) >> 8;
88 header[3] = (stream & 0x00FF);
89 /* now 4 bytes of stream position.
90 * This implementation doesn't need this so we
91 * set it to all zeros.
92 */
93 header[4] = 0;
94 header[5] = 0;
95 header[6] = 0;
96 header[7] = 0;
97 /* Now body ("data") size. First upper then lower byte. */
98 header[8] = (datalen & 0xFF00) >> 8;
99 header[9] = (datalen & 0x00FF);
100
101 shout_queue_data(&connection->wqueue, header, HEADER_SIZE);
102 if (datalen)
103 shout_queue_data(&connection->wqueue, data, datalen);
104
105 return SHOUTERR_SUCCESS;
106 }
107
shout_create_roaraudio_request_ident(shout_t * self,shout_connection_t * connection)108 static int shout_create_roaraudio_request_ident(shout_t *self, shout_connection_t *connection)
109 {
110 int ret;
111 size_t datalen;
112 uint8_t *data;
113 const char *agent;
114 uint32_t pid = getpid();
115
116 /* We implement version 1 IDENTIFY header.
117 * It has the following structure:
118 * byte 0: version (1).
119 * byte 1-4: PID in big endian.
120 * byte 5-end: client name.
121 */
122
123 agent = shout_get_agent(self);
124 if (!agent)
125 return SHOUTERR_INSANE;
126
127 datalen = 5 + strlen(agent);
128 data = malloc(datalen);
129 if (!data)
130 return SHOUTERR_MALLOC;
131
132 /* version number (1). */
133 data[0] = 1;
134 /* PID */
135 data[1] = (pid & 0xFF000000UL) >> 24;
136 data[2] = (pid & 0x00FF0000UL) >> 16;
137 data[3] = (pid & 0x0000FF00UL) >> 8;
138 data[4] = (pid & 0x000000FFUL) >> 0;
139 /* agent name */
140 memcpy(data + 5, agent, datalen - 5);
141
142 ret = command_send(self, connection, CMD_IDENTIFY, STREAM_NONE, data, datalen);
143
144 free(data);
145
146 return ret;
147 }
148
shout_create_roaraudio_request_auth(shout_t * self,shout_connection_t * connection)149 static int shout_create_roaraudio_request_auth(shout_t *self, shout_connection_t *connection)
150 {
151 /* Now we send an AUTH command to the server.
152 * We currently only implement the NONE type.
153 * NONE type is assumed by the server if
154 * we send an empty body.
155 */
156 return command_send(self, connection, CMD_AUTH, STREAM_NONE, NULL, 0);
157 }
158
shout_create_roaraudio_request_new_stream(shout_t * self,shout_connection_t * connection)159 static int shout_create_roaraudio_request_new_stream(shout_t *self, shout_connection_t *connection)
160 {
161 uint32_t data[6];
162
163 /* We implement 24 byte NEW_STREAM structure (version 0).
164 * It has the following structure:
165 * byte 0- 3: stream direction [0].
166 * byte 4- 7: Rel Pos ID (here: -1=NONE)
167 * byte 8-11: Sample Rate[1].
168 * byte 12-15: Bits per Sample[1].
169 * byte 16-19: Number of Channels[1].
170 * byte 20-23: Codec ID[2].
171 *
172 * The following asumptions are based on us only supporting
173 * Ogg-based for now.
174 * [0] = We currently only suport playback of waveform signals (1).
175 * See https://bts.keep-cool.org/wiki/Specs/DirValues
176 * [1] = Server should detect automagically. defaulting to: 44100/16/2.
177 * [2] = Ogg/Vorbis = 0x0010, Ogg/Speex = 0x0012, Ogg/FLAC = 0x0014,
178 * Ogg/CELT = 0x0016, Ogg/GENERAL (unknown logical streams) = 0x0015.
179 * See https://bts.keep-cool.org/wiki/Specs/CodecsValues
180 */
181
182 data[0] = htonl(1);
183 data[1] = htonl((uint32_t)-1);
184 data[2] = htonl(44100);
185 data[3] = htonl(32);
186 data[4] = htonl(2);
187 data[5] = htonl(0x0010); /* we assume Ogg/Vorbis for now. */
188
189 return command_send(self, connection, CMD_NEW_STREAM, STREAM_NONE, data, 24);
190 }
191
shout_create_roaraudio_request_exec(shout_t * self,shout_connection_t * connection)192 static int shout_create_roaraudio_request_exec(shout_t *self, shout_connection_t *connection)
193 {
194 /* Last an EXEC_STREAM command should be sent to open
195 * an IO channel for the new stream.
196 * If successful the control socket will be used for data
197 * after that. This very much like with SOURCE requests.
198 * so no hard deal to intigrate.
199 */
200 return command_send(self, connection, CMD_EXEC_STREAM, connection->protocol_extra.si, NULL, 0);
201 }
202
shout_create_roaraudio_request(shout_t * self,shout_connection_t * connection)203 static shout_connection_return_state_t shout_create_roaraudio_request(shout_t *self, shout_connection_t *connection)
204 {
205 int ret;
206
207 switch ((shout_roar_protocol_state_t)connection->current_protocol_state) {
208 case STATE_IDENT:
209 ret = shout_create_roaraudio_request_ident(self, connection);
210 break;
211 case STATE_AUTH:
212 ret = shout_create_roaraudio_request_auth(self, connection);
213 break;
214 case STATE_NEW_STREAM:
215 ret = shout_create_roaraudio_request_new_stream(self, connection);
216 break;
217 case STATE_EXEC:
218 ret = shout_create_roaraudio_request_exec(self, connection);
219 break;
220 default:
221 ret = SHOUTERR_INSANE;
222 break;
223 }
224
225 shout_connection_set_error(connection, ret);
226 return ret == SHOUTERR_SUCCESS ? SHOUT_RS_DONE : SHOUT_RS_ERROR;
227 }
228
shout_get_roaraudio_response(shout_t * self,shout_connection_t * connection)229 static shout_connection_return_state_t shout_get_roaraudio_response(shout_t *self, shout_connection_t *connection)
230 {
231 shout_buf_t *queue;
232 size_t total_len = 0;
233 uint8_t header[HEADER_SIZE];
234
235 if (!connection->rqueue.len) {
236 shout_connection_set_error(connection, SHOUTERR_SOCKET);
237 return SHOUT_RS_ERROR;
238 }
239
240 for (queue = connection->rqueue.head; queue; queue = queue->next) {
241 if (total_len < 10)
242 memcpy(header + total_len, queue->data, queue->len > (HEADER_SIZE - total_len) ? (HEADER_SIZE - total_len) : queue->len);
243 total_len += queue->len;
244 }
245
246 /* the header alone has 10 bytes. */
247 if (total_len < HEADER_SIZE)
248 return SHOUT_RS_NOTNOW;
249
250 /* ok. we got a header.
251 * Now find the body length ("data length") bytes
252 * and see if they are both zero.
253 * If not the server sent us extra infos we currently
254 * not support.
255 */
256
257 if (header[8] || header[9]) {
258 shout_connection_set_error(connection, SHOUTERR_UNSUPPORTED);
259 return SHOUT_RS_ERROR;
260 }
261
262 /* Hey, we got a response. */
263 return SHOUT_RS_DONE;
264 }
265
shout_parse_roaraudio_response(shout_t * self,shout_connection_t * connection)266 static shout_connection_return_state_t shout_parse_roaraudio_response(shout_t *self, shout_connection_t *connection)
267 {
268 char *data = NULL;
269 uint8_t header[HEADER_SIZE];
270
271 /* ok, this is the most hacky function in here as we do not
272 * use a well designed and universal parser for the responses.
273 * Yet there is little need for it.
274 * We just need to check if we got an CMD_OK and
275 * pull out the stream ID in case of STATE_NEW_STREAM.
276 * "data length" is already checked by shout_get_roaraudio_response().
277 */
278
279 if (shout_queue_collect(connection->rqueue.head, &data) != HEADER_SIZE) {
280 free(data);
281 shout_connection_set_error(connection, SHOUTERR_INSANE);
282 return SHOUT_RS_ERROR;
283 }
284 shout_queue_free(&connection->rqueue);
285 memcpy(header, data, HEADER_SIZE);
286 free(data);
287
288 /* check version */
289 if (header[0] != 0) {
290 shout_connection_set_error(connection, SHOUTERR_UNSUPPORTED);
291 return SHOUT_RS_ERROR;
292 }
293
294 /* have we got a positive response? */
295 if (header[1] != CMD_OK) {
296 shout_connection_set_error(connection, SHOUTERR_NOLOGIN);
297 return SHOUT_RS_ERROR;
298 }
299
300 switch ((shout_roar_protocol_state_t)connection->current_protocol_state) {
301 case STATE_IDENT:
302 connection->current_protocol_state = STATE_AUTH;
303 connection->server_caps |= LIBSHOUT_CAP_GOTCAPS;
304 break;
305
306 case STATE_AUTH:
307 connection->current_protocol_state = STATE_NEW_STREAM;
308 break;
309
310 case STATE_NEW_STREAM:
311 connection->protocol_extra.si = (((unsigned int)header[2]) << 8) | (unsigned int)header[3];
312 connection->current_protocol_state = STATE_EXEC;
313 break;
314
315 case STATE_EXEC:
316 /* ok. everything worked. Continue normally! */
317 connection->current_message_state = SHOUT_MSGSTATE_SENDING1;
318 connection->target_message_state = SHOUT_MSGSTATE_WAITING1;
319 return SHOUT_RS_DONE;
320 break;
321
322 default:
323 shout_connection_set_error(connection, SHOUTERR_INSANE);
324 return SHOUT_RS_ERROR;
325 break;
326 }
327
328 connection->current_message_state = SHOUT_MSGSTATE_CREATING0;
329 return SHOUT_RS_DONE;
330 }
331
332 static const shout_protocol_impl_t shout_roaraudio_impl_real = {
333 .msg_create = shout_create_roaraudio_request,
334 .msg_get = shout_get_roaraudio_response,
335 .msg_parse = shout_parse_roaraudio_response
336 };
337 const shout_protocol_impl_t *shout_roaraudio_impl = &shout_roaraudio_impl_real;
338