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