From 8715121755ad1aa9c083dd70781a63ced13359c2 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 3 Aug 2008 22:46:21 +0100 Subject: Modularise the RAOP stuff that requires OpenSSL and make it optional at compile time --- configure.ac | 41 +++ src/Makefile.am | 35 ++- src/modules/raop/base64.c | 126 +++++++++ src/modules/raop/base64.h | 34 +++ src/modules/raop/raop_client.c | 561 +++++++++++++++++++++++++++++++++++++++++ src/modules/raop/raop_client.h | 46 ++++ src/modules/rtp/base64.c | 126 --------- src/modules/rtp/base64.h | 34 --- src/modules/rtp/raop_client.c | 561 ----------------------------------------- src/modules/rtp/raop_client.h | 46 ---- 10 files changed, 835 insertions(+), 775 deletions(-) create mode 100644 src/modules/raop/base64.c create mode 100644 src/modules/raop/base64.h create mode 100644 src/modules/raop/raop_client.c create mode 100644 src/modules/raop/raop_client.h delete mode 100644 src/modules/rtp/base64.c delete mode 100644 src/modules/rtp/base64.h delete mode 100644 src/modules/rtp/raop_client.c delete mode 100644 src/modules/rtp/raop_client.h diff --git a/configure.ac b/configure.ac index 2b91a006..2a040763 100644 --- a/configure.ac +++ b/configure.ac @@ -1000,6 +1000,41 @@ AC_SUBST(POLKIT_LIBS) AC_SUBST(HAVE_POLKIT) AM_CONDITIONAL([HAVE_POLKIT], [test "x$HAVE_POLKIT" = x1]) +#### OpenSSL support (optional) #### + +AC_ARG_ENABLE([openssl], + AC_HELP_STRING([--disable-openssl], [Disable OpenSSL support (used for Airtunes/RAOP)]), + [ + case "${enableval}" in + yes) openssl=yes ;; + no) openssl=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-openssl) ;; + esac + ], + [openssl=auto]) + +if test "x${openssl}" != xno ; then + + PKG_CHECK_MODULES(OPENSSL, [ openssl > 0.9 ], + [ + HAVE_OPENSSL=1 + AC_DEFINE([HAVE_OPENSSL], 1, [Have OpenSSL]) + ], + [ + HAVE_OPENSSL=0 + if test "x$openssl" = xyes ; then + AC_MSG_ERROR([*** OpenSSL support not found]) + fi + ]) +else + HAVE_OPENSSL=0 +fi + +AC_SUBST(OPENSSL_CFLAGS) +AC_SUBST(OPENSSL_LIBS) +AC_SUBST(HAVE_OPENSSL) +AM_CONDITIONAL([HAVE_OPENSSL], [test "x$HAVE_OPENSSL" = x1]) + ### Build and Install man pages ### AC_ARG_ENABLE(manpages, AS_HELP_STRING([--disable-manpages],[Disable building and installation of man pages]), @@ -1201,6 +1236,11 @@ if test "x${HAVE_POLKIT}" = "x1" ; then ENABLE_POLKIT=yes fi +ENABLE_OPENSSL=no +if test "x${HAVE_OPENSSL}" = "x1" ; then + ENABLE_OPENSSL=yes +fi + ENABLE_PER_USER_ESOUND_SOCKET=no if test "x$per_user_esound_socket" = "x1" ; then ENABLE_PER_USER_ESOUND_SOCKET=yes @@ -1232,6 +1272,7 @@ echo " Enable TCP Wrappers: ${ENABLE_TCPWRAP} Enable libsamplerate: ${ENABLE_LIBSAMPLERATE} Enable PolicyKit: ${ENABLE_POLKIT} + Enable OpenSSL (for Airtunes): ${ENABLE_OPENSSL} System User: ${PA_SYSTEM_USER} System Group: ${PA_SYSTEM_GROUP} Realtime Group: ${PA_REALTIME_GROUP} diff --git a/src/Makefile.am b/src/Makefile.am index 4ffdfd93..f216b71b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -62,6 +62,10 @@ AM_CFLAGS += -DPA_MACHINE_ID=\"$(localstatedir)/lib/dbus/machine-id\" # This cool debug trap works on i386/gcc only AM_CFLAGS += '-DDEBUG_TRAP=__asm__("int $$3")' +if HAVE_OPENSSL +AM_CFLAGS += -I$(top_builddir)/src/modules/raop +endif + AM_LIBADD = $(PTHREAD_LIBS) $(INTLLIBS) AM_LDADD = $(PTHREAD_LIBS) $(INTLLIBS) @@ -89,6 +93,7 @@ PA_THREAD_OBJS = \ pulsecore/semaphore-posix.c pulsecore/semaphore.h endif + ################################### # Extra files # ################################### @@ -1009,11 +1014,16 @@ librtp_la_SOURCES = \ modules/rtp/sdp.c modules/rtp/sdp.h \ modules/rtp/sap.c modules/rtp/sap.h \ modules/rtp/rtsp_client.c modules/rtp/rtsp_client.h \ - modules/rtp/raop_client.c modules/rtp/raop_client.h \ - modules/rtp/headerlist.c modules/rtp/headerlist.h \ - modules/rtp/base64.c modules/rtp/base64.h + modules/rtp/headerlist.c modules/rtp/headerlist.h librtp_la_LDFLAGS = -avoid-version -librtp_la_LIBADD = $(AM_LIBADD) libsocket-util.la libiochannel.la libsocket-client.la libioline.la libpulsecore.la -lssl +librtp_la_LIBADD = $(AM_LIBADD) libsocket-util.la libiochannel.la libsocket-client.la libioline.la libpulsecore.la + +libraop_la_SOURCES = \ + modules/raop/raop_client.c modules/raop/raop_client.h \ + modules/raop/base64.c modules/raop/base64.h +libraop_la_CFLAGS = $(AM_CFLAGS) $(OPENSSL_CFLAGS) +libraop_la_LDFLAGS = -avoid-version +libraop_la_LIBADD = $(AM_LIBADD) $(OPENSSL_LIBS) libsocket-util.la libiochannel.la libsocket-client.la libioline.la libpulsecore.la librtp.la # X11 @@ -1060,7 +1070,6 @@ modlibexec_LTLIBRARIES += \ module-remap-sink.la \ module-ladspa-sink.la \ module-esound-sink.la \ - module-raop-sink.la \ module-tunnel-sink.la \ module-tunnel-source.la \ module-position-event-sounds.la @@ -1127,8 +1136,7 @@ endif if HAVE_AVAHI modlibexec_LTLIBRARIES += \ module-zeroconf-publish.la \ - module-zeroconf-discover.la \ - module-raop-discover.la + module-zeroconf-discover.la endif if HAVE_LIRC @@ -1186,6 +1194,17 @@ pulselibexec_PROGRAMS += \ proximity-helper endif +if HAVE_OPENSSL +modlibexec_LTLIBRARIES += \ + libraop.la \ + module-raop-sink.la +if HAVE_AVAHI +modlibexec_LTLIBRARIES += \ + module-raop-discover.la +endif +endif + + # These are generated by a M4 script SYMDEF_FILES = \ @@ -1609,7 +1628,7 @@ module_bluetooth_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) # Apple Airtunes/RAOP module_raop_sink_la_SOURCES = modules/module-raop-sink.c module_raop_sink_la_LDFLAGS = -module -avoid-version -module_raop_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la librtp.la +module_raop_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la librtp.la libraop.la module_raop_discover_la_SOURCES = modules/module-raop-discover.c module_raop_discover_la_LDFLAGS = -module -avoid-version diff --git a/src/modules/raop/base64.c b/src/modules/raop/base64.c new file mode 100644 index 00000000..8918def8 --- /dev/null +++ b/src/modules/raop/base64.c @@ -0,0 +1,126 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* + This file was originally inspired by a file developed by + Kungliga Tekniska H�gskolan +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include + +#include "base64.h" + +static const char base64_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static int pos(char c) +{ + if (c >= 'A' && c <= 'Z') return c - 'A' + 0; + if (c >= 'a' && c <= 'z') return c - 'a' + 26; + if (c >= '0' && c <= '9') return c - '0' + 52; + if (c == '+') return 62; + if (c == '/') return 63; +} + +int pa_base64_encode(const void *data, int size, char **str) +{ + char *s, *p; + int i; + int c; + const unsigned char *q; + + p = s = pa_xnew(char, size * 4 / 3 + 4); + q = (const unsigned char *) data; + i = 0; + for (i = 0; i < size;) { + c = q[i++]; + c *= 256; + if (i < size) + c += q[i]; + i++; + c *= 256; + if (i < size) + c += q[i]; + i++; + p[0] = base64_chars[(c & 0x00fc0000) >> 18]; + p[1] = base64_chars[(c & 0x0003f000) >> 12]; + p[2] = base64_chars[(c & 0x00000fc0) >> 6]; + p[3] = base64_chars[(c & 0x0000003f) >> 0]; + if (i > size) + p[3] = '='; + if (i > size + 1) + p[2] = '='; + p += 4; + } + *p = 0; + *str = s; + return strlen(s); +} + +#define DECODE_ERROR 0xffffffff + +static unsigned int token_decode(const char *token) +{ + int i; + unsigned int val = 0; + int marker = 0; + if (strlen(token) < 4) + return DECODE_ERROR; + for (i = 0; i < 4; i++) { + val *= 64; + if (token[i] == '=') + marker++; + else if (marker > 0) + return DECODE_ERROR; + else + val += pos(token[i]); + } + if (marker > 2) + return DECODE_ERROR; + return (marker << 24) | val; +} + +int pa_base64_decode(const char *str, void *data) +{ + const char *p; + unsigned char *q; + + q = data; + for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4) { + unsigned int val = token_decode(p); + unsigned int marker = (val >> 24) & 0xff; + if (val == DECODE_ERROR) + return -1; + *q++ = (val >> 16) & 0xff; + if (marker < 2) + *q++ = (val >> 8) & 0xff; + if (marker < 1) + *q++ = val & 0xff; + } + return q - (unsigned char *) data; +} diff --git a/src/modules/raop/base64.h b/src/modules/raop/base64.h new file mode 100644 index 00000000..dac0e707 --- /dev/null +++ b/src/modules/raop/base64.h @@ -0,0 +1,34 @@ +#ifndef foobase64hfoo +#define foobase64hfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + Copyright Kungliga Tekniska Høgskolan + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* + This file was originally inspired by a file developed by + Kungliga Tekniska Høgskolan +*/ + +int pa_base64_encode(const void *data, int size, char **str); +int pa_base64_decode(const char *str, void *data); + +#endif diff --git a/src/modules/raop/raop_client.c b/src/modules/raop/raop_client.c new file mode 100644 index 00000000..4627545e --- /dev/null +++ b/src/modules/raop/raop_client.c @@ -0,0 +1,561 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_FILIO_H +#include +#endif + +/* TODO: Replace OpenSSL with NSS */ +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "raop_client.h" +#include "rtsp_client.h" +#include "base64.h" + +#define AES_CHUNKSIZE 16 + +#define JACK_STATUS_DISCONNECTED 0 +#define JACK_STATUS_CONNECTED 1 + +#define JACK_TYPE_ANALOG 0 +#define JACK_TYPE_DIGITAL 1 + +#define VOLUME_DEF -30 +#define VOLUME_MIN -144 +#define VOLUME_MAX 0 + + +struct pa_raop_client { + pa_core *core; + char *host; + char *sid; + pa_rtsp_client *rtsp; + + uint8_t jack_type; + uint8_t jack_status; + + /* Encryption Related bits */ + AES_KEY aes; + uint8_t aes_iv[AES_CHUNKSIZE]; /* initialization vector for aes-cbc */ + uint8_t aes_nv[AES_CHUNKSIZE]; /* next vector for aes-cbc */ + uint8_t aes_key[AES_CHUNKSIZE]; /* key for aes-cbc */ + + pa_socket_client *sc; + int fd; + + uint16_t seq; + uint32_t rtptime; + + pa_raop_client_cb_t callback; + void* userdata; + pa_raop_client_closed_cb_t closed_callback; + void* closed_userdata; +}; + +/** + * Function to write bits into a buffer. + * @param buffer Handle to the buffer. It will be incremented if new data requires it. + * @param bit_pos A pointer to a position buffer to keep track the current write location (0 for MSB, 7 for LSB) + * @param size A pointer to the byte size currently written. This allows the calling function to do simple buffer overflow checks + * @param data The data to write + * @param data_bit_len The number of bits from data to write + */ +static inline void bit_writer(uint8_t **buffer, uint8_t *bit_pos, int *size, uint8_t data, uint8_t data_bit_len) { + int bits_left, bit_overflow; + uint8_t bit_data; + + if (!data_bit_len) + return; + + /* If bit pos is zero, we will definatly use at least one bit from the current byte so size increments. */ + if (!*bit_pos) + *size += 1; + + /* Calc the number of bits left in the current byte of buffer */ + bits_left = 7 - *bit_pos + 1; + /* Calc the overflow of bits in relation to how much space we have left... */ + bit_overflow = bits_left - data_bit_len; + if (bit_overflow >= 0) { + /* We can fit the new data in our current byte */ + /* As we write from MSB->LSB we need to left shift by the overflow amount */ + bit_data = data << bit_overflow; + if (*bit_pos) + **buffer |= bit_data; + else + **buffer = bit_data; + /* If our data fits exactly into the current byte, we need to increment our pointer */ + if (0 == bit_overflow) { + /* Do not increment size as it will be incremeneted on next call as bit_pos is zero */ + *buffer += 1; + *bit_pos = 0; + } else { + *bit_pos += data_bit_len; + } + } else { + /* bit_overflow is negative, there for we will need a new byte from our buffer */ + /* Firstly fill up what's left in the current byte */ + bit_data = data >> -bit_overflow; + **buffer |= bit_data; + /* Increment our buffer pointer and size counter*/ + *buffer += 1; + *size += 1; + **buffer = data << (8 + bit_overflow); + *bit_pos = -bit_overflow; + } +} + +static int rsa_encrypt(uint8_t *text, int len, uint8_t *res) { + const char n[] = + "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" + "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR" + "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB" + "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ" + "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh" + "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="; + const char e[] = "AQAB"; + uint8_t modules[256]; + uint8_t exponent[8]; + int size; + RSA *rsa; + + rsa = RSA_new(); + size = pa_base64_decode(n, modules); + rsa->n = BN_bin2bn(modules, size, NULL); + size = pa_base64_decode(e, exponent); + rsa->e = BN_bin2bn(exponent, size, NULL); + + size = RSA_public_encrypt(len, text, res, rsa, RSA_PKCS1_OAEP_PADDING); + RSA_free(rsa); + return size; +} + +static int aes_encrypt(pa_raop_client* c, uint8_t *data, int size) +{ + uint8_t *buf; + int i=0, j; + + pa_assert(c); + + memcpy(c->aes_nv, c->aes_iv, AES_CHUNKSIZE); + while (i+AES_CHUNKSIZE <= size) { + buf = data + i; + for (j=0; jaes_nv[j]; + + AES_encrypt(buf, buf, &c->aes); + memcpy(c->aes_nv, buf, AES_CHUNKSIZE); + i += AES_CHUNKSIZE; + } + return i; +} + +static inline void rtrimchar(char *str, char rc) +{ + char *sp = str + strlen(str) - 1; + while (sp >= str && *sp == rc) { + *sp = '\0'; + sp -= 1; + } +} + +static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { + pa_raop_client *c = userdata; + + pa_assert(sc); + pa_assert(c); + pa_assert(c->sc == sc); + pa_assert(c->fd < 0); + pa_assert(c->callback); + + pa_socket_client_unref(c->sc); + c->sc = NULL; + + if (!io) { + pa_log("Connection failed: %s", pa_cstrerror(errno)); + return; + } + + c->fd = pa_iochannel_get_send_fd(io); + + pa_iochannel_set_noclose(io, TRUE); + pa_iochannel_socket_set_sndbuf(io, 1024); + pa_iochannel_free(io); + + pa_make_tcp_socket_low_delay(c->fd); + + pa_log_debug("Connection established"); + c->callback(c->fd, c->userdata); +} + +static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* headers, void *userdata) +{ + pa_raop_client* c = userdata; + pa_assert(c); + pa_assert(rtsp); + pa_assert(rtsp == c->rtsp); + + switch (state) { + case STATE_CONNECT: { + int i; + uint8_t rsakey[512]; + char *key, *iv, *sac, *sdp; + uint16_t rand_data; + const char *ip; + char *url; + + pa_log_debug("RAOP: CONNECTED"); + ip = pa_rtsp_localip(c->rtsp); + /* First of all set the url properly */ + url = pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid); + pa_rtsp_set_url(c->rtsp, url); + pa_xfree(url); + + /* Now encrypt our aes_public key to send to the device */ + i = rsa_encrypt(c->aes_key, AES_CHUNKSIZE, rsakey); + pa_base64_encode(rsakey, i, &key); + rtrimchar(key, '='); + pa_base64_encode(c->aes_iv, AES_CHUNKSIZE, &iv); + rtrimchar(iv, '='); + + pa_random(&rand_data, sizeof(rand_data)); + pa_base64_encode(&rand_data, AES_CHUNKSIZE, &sac); + rtrimchar(sac, '='); + pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac); + sdp = pa_sprintf_malloc( + "v=0\r\n" + "o=iTunes %s 0 IN IP4 %s\r\n" + "s=iTunes\r\n" + "c=IN IP4 %s\r\n" + "t=0 0\r\n" + "m=audio 0 RTP/AVP 96\r\n" + "a=rtpmap:96 AppleLossless\r\n" + "a=fmtp:96 4096 0 16 40 10 14 2 255 0 0 44100\r\n" + "a=rsaaeskey:%s\r\n" + "a=aesiv:%s\r\n", + c->sid, ip, c->host, key, iv); + pa_rtsp_announce(c->rtsp, sdp); + pa_xfree(key); + pa_xfree(iv); + pa_xfree(sac); + pa_xfree(sdp); + break; + } + + case STATE_ANNOUNCE: + pa_log_debug("RAOP: ANNOUNCED"); + pa_rtsp_remove_header(c->rtsp, "Apple-Challenge"); + pa_rtsp_setup(c->rtsp); + break; + + case STATE_SETUP: { + char *aj = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status")); + pa_log_debug("RAOP: SETUP"); + if (aj) { + char *token, *pc; + char delimiters[] = ";"; + const char* token_state = NULL; + c->jack_type = JACK_TYPE_ANALOG; + c->jack_status = JACK_STATUS_DISCONNECTED; + + while ((token = pa_split(aj, delimiters, &token_state))) { + if ((pc = strstr(token, "="))) { + *pc = 0; + if (!strcmp(token, "type") && !strcmp(pc+1, "digital")) { + c->jack_type = JACK_TYPE_DIGITAL; + } + } else { + if (!strcmp(token,"connected")) + c->jack_status = JACK_STATUS_CONNECTED; + } + pa_xfree(token); + } + pa_xfree(aj); + } else { + pa_log_warn("Audio Jack Status missing"); + } + pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime); + break; + } + + case STATE_RECORD: { + uint32_t port = pa_rtsp_serverport(c->rtsp); + pa_log_debug("RAOP: RECORDED"); + + if (!(c->sc = pa_socket_client_new_string(c->core->mainloop, c->host, port))) { + pa_log("failed to connect to server '%s:%d'", c->host, port); + return; + } + pa_socket_client_set_callback(c->sc, on_connection, c); + break; + } + + case STATE_FLUSH: + pa_log_debug("RAOP: FLUSHED"); + break; + + case STATE_TEARDOWN: + case STATE_SET_PARAMETER: + pa_log_debug("RAOP: SET_PARAMETER"); + break; + case STATE_DISCONNECTED: + pa_assert(c->closed_callback); + pa_assert(c->rtsp); + + pa_log_debug("RTSP control channel closed"); + pa_rtsp_client_free(c->rtsp); + c->rtsp = NULL; + if (c->fd > 0) { + /* We do not close the fd, we leave it to the closed callback to do that */ + c->fd = -1; + } + if (c->sc) { + pa_socket_client_unref(c->sc); + c->sc = NULL; + } + pa_xfree(c->sid); + c->sid = NULL; + c->closed_callback(c->closed_userdata); + break; + } +} + +pa_raop_client* pa_raop_client_new(pa_core *core, const char* host) +{ + pa_raop_client* c = pa_xnew0(pa_raop_client, 1); + + pa_assert(core); + pa_assert(host); + + c->core = core; + c->fd = -1; + c->host = pa_xstrdup(host); + + if (pa_raop_connect(c)) { + pa_raop_client_free(c); + return NULL; + } + return c; +} + + +void pa_raop_client_free(pa_raop_client* c) +{ + pa_assert(c); + + if (c->rtsp) + pa_rtsp_client_free(c->rtsp); + pa_xfree(c->host); + pa_xfree(c); +} + + +int pa_raop_connect(pa_raop_client* c) +{ + char *sci; + struct { + uint32_t a; + uint32_t b; + uint32_t c; + } rand_data; + + pa_assert(c); + + if (c->rtsp) { + pa_log_debug("Connection already in progress"); + return 0; + } + + c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, 5000, "iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); + + /* Initialise the AES encryption system */ + pa_random(c->aes_iv, sizeof(c->aes_iv)); + pa_random(c->aes_key, sizeof(c->aes_key)); + memcpy(c->aes_nv, c->aes_iv, sizeof(c->aes_nv)); + AES_set_encrypt_key(c->aes_key, 128, &c->aes); + + /* Generate random instance id */ + pa_random(&rand_data, sizeof(rand_data)); + c->sid = pa_sprintf_malloc("%u", rand_data.a); + sci = pa_sprintf_malloc("%08x%08x",rand_data.b, rand_data.c); + pa_rtsp_add_header(c->rtsp, "Client-Instance", sci); + pa_xfree(sci); + pa_rtsp_set_callback(c->rtsp, rtsp_cb, c); + return pa_rtsp_connect(c->rtsp); +} + + +int pa_raop_flush(pa_raop_client* c) +{ + pa_assert(c); + + pa_rtsp_flush(c->rtsp, c->seq, c->rtptime); + return 0; +} + + +int pa_raop_client_set_volume(pa_raop_client* c, pa_volume_t volume) +{ + int rv; + double db; + char *param; + + pa_assert(c); + + db = pa_sw_volume_to_dB(volume); + if (db < VOLUME_MIN) + db = VOLUME_MIN; + else if (db > VOLUME_MAX) + db = VOLUME_MAX; + + param = pa_sprintf_malloc("volume: %0.6f\r\n", db); + + /* We just hit and hope, cannot wait for the callback */ + rv = pa_rtsp_setparameter(c->rtsp, param); + pa_xfree(param); + return rv; +} + + +int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded) +{ + uint16_t len; + size_t bufmax; + uint8_t *bp, bpos; + uint8_t *ibp, *maxibp; + int size; + uint8_t *b, *p; + uint32_t bsize; + size_t length; + static uint8_t header[] = { + 0x24, 0x00, 0x00, 0x00, + 0xF0, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + int header_size = sizeof(header); + + pa_assert(c); + pa_assert(c->fd > 0); + pa_assert(raw); + pa_assert(raw->memblock); + pa_assert(raw->length > 0); + pa_assert(encoded); + + /* We have to send 4 byte chunks */ + bsize = (int)(raw->length / 4); + length = bsize * 4; + + /* Leave 16 bytes extra to allow for the ALAC header which is about 55 bits */ + bufmax = length + header_size + 16; + pa_memchunk_reset(encoded); + encoded->memblock = pa_memblock_new(c->core->mempool, bufmax); + b = pa_memblock_acquire(encoded->memblock); + memcpy(b, header, header_size); + + /* Now write the actual samples */ + bp = b + header_size; + size = bpos = 0; + bit_writer(&bp,&bpos,&size,1,3); /* channel=1, stereo */ + bit_writer(&bp,&bpos,&size,0,4); /* unknown */ + bit_writer(&bp,&bpos,&size,0,8); /* unknown */ + bit_writer(&bp,&bpos,&size,0,4); /* unknown */ + bit_writer(&bp,&bpos,&size,1,1); /* hassize */ + bit_writer(&bp,&bpos,&size,0,2); /* unused */ + bit_writer(&bp,&bpos,&size,1,1); /* is-not-compressed */ + + /* size of data, integer, big endian */ + bit_writer(&bp,&bpos,&size,(bsize>>24)&0xff,8); + bit_writer(&bp,&bpos,&size,(bsize>>16)&0xff,8); + bit_writer(&bp,&bpos,&size,(bsize>>8)&0xff,8); + bit_writer(&bp,&bpos,&size,(bsize)&0xff,8); + + ibp = p = pa_memblock_acquire(raw->memblock); + maxibp = p + raw->length - 4; + while (ibp <= maxibp) { + /* Byte swap stereo data */ + bit_writer(&bp,&bpos,&size,*(ibp+1),8); + bit_writer(&bp,&bpos,&size,*(ibp+0),8); + bit_writer(&bp,&bpos,&size,*(ibp+3),8); + bit_writer(&bp,&bpos,&size,*(ibp+2),8); + ibp += 4; + raw->index += 4; + raw->length -= 4; + } + pa_memblock_release(raw->memblock); + encoded->length = header_size + size; + + /* store the lenght (endian swapped: make this better) */ + len = size + header_size - 4; + *(b + 2) = len >> 8; + *(b + 3) = len & 0xff; + + /* encrypt our data */ + aes_encrypt(c, (b + header_size), size); + + /* We're done with the chunk */ + pa_memblock_release(encoded->memblock); + + return 0; +} + + +void pa_raop_client_set_callback(pa_raop_client* c, pa_raop_client_cb_t callback, void *userdata) +{ + pa_assert(c); + + c->callback = callback; + c->userdata = userdata; +} + +void pa_raop_client_set_closed_callback(pa_raop_client* c, pa_raop_client_closed_cb_t callback, void *userdata) +{ + pa_assert(c); + + c->closed_callback = callback; + c->closed_userdata = userdata; +} diff --git a/src/modules/raop/raop_client.h b/src/modules/raop/raop_client.h new file mode 100644 index 00000000..ec3136a7 --- /dev/null +++ b/src/modules/raop/raop_client.h @@ -0,0 +1,46 @@ +#ifndef fooraopclientfoo +#define fooraopclientfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include +#include +#include + +typedef struct pa_raop_client pa_raop_client; + +pa_raop_client* pa_raop_client_new(pa_core *core, const char* host); +void pa_raop_client_free(pa_raop_client* c); + +int pa_raop_connect(pa_raop_client* c); +int pa_raop_flush(pa_raop_client* c); + +int pa_raop_client_set_volume(pa_raop_client* c, pa_volume_t volume); +int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded); + +typedef void (*pa_raop_client_cb_t)(int fd, void *userdata); +void pa_raop_client_set_callback(pa_raop_client* c, pa_raop_client_cb_t callback, void *userdata); + +typedef void (*pa_raop_client_closed_cb_t)(void *userdata); +void pa_raop_client_set_closed_callback(pa_raop_client* c, pa_raop_client_closed_cb_t callback, void *userdata); + +#endif diff --git a/src/modules/rtp/base64.c b/src/modules/rtp/base64.c deleted file mode 100644 index 8918def8..00000000 --- a/src/modules/rtp/base64.c +++ /dev/null @@ -1,126 +0,0 @@ -/*** - This file is part of PulseAudio. - - Copyright 2008 Colin Guthrie - - PulseAudio is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published - by the Free Software Foundation; either version 2 of the License, - or (at your option) any later version. - - PulseAudio is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with PulseAudio; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - -/* - This file was originally inspired by a file developed by - Kungliga Tekniska H�gskolan -*/ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include - -#include - -#include "base64.h" - -static const char base64_chars[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -static int pos(char c) -{ - if (c >= 'A' && c <= 'Z') return c - 'A' + 0; - if (c >= 'a' && c <= 'z') return c - 'a' + 26; - if (c >= '0' && c <= '9') return c - '0' + 52; - if (c == '+') return 62; - if (c == '/') return 63; -} - -int pa_base64_encode(const void *data, int size, char **str) -{ - char *s, *p; - int i; - int c; - const unsigned char *q; - - p = s = pa_xnew(char, size * 4 / 3 + 4); - q = (const unsigned char *) data; - i = 0; - for (i = 0; i < size;) { - c = q[i++]; - c *= 256; - if (i < size) - c += q[i]; - i++; - c *= 256; - if (i < size) - c += q[i]; - i++; - p[0] = base64_chars[(c & 0x00fc0000) >> 18]; - p[1] = base64_chars[(c & 0x0003f000) >> 12]; - p[2] = base64_chars[(c & 0x00000fc0) >> 6]; - p[3] = base64_chars[(c & 0x0000003f) >> 0]; - if (i > size) - p[3] = '='; - if (i > size + 1) - p[2] = '='; - p += 4; - } - *p = 0; - *str = s; - return strlen(s); -} - -#define DECODE_ERROR 0xffffffff - -static unsigned int token_decode(const char *token) -{ - int i; - unsigned int val = 0; - int marker = 0; - if (strlen(token) < 4) - return DECODE_ERROR; - for (i = 0; i < 4; i++) { - val *= 64; - if (token[i] == '=') - marker++; - else if (marker > 0) - return DECODE_ERROR; - else - val += pos(token[i]); - } - if (marker > 2) - return DECODE_ERROR; - return (marker << 24) | val; -} - -int pa_base64_decode(const char *str, void *data) -{ - const char *p; - unsigned char *q; - - q = data; - for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4) { - unsigned int val = token_decode(p); - unsigned int marker = (val >> 24) & 0xff; - if (val == DECODE_ERROR) - return -1; - *q++ = (val >> 16) & 0xff; - if (marker < 2) - *q++ = (val >> 8) & 0xff; - if (marker < 1) - *q++ = val & 0xff; - } - return q - (unsigned char *) data; -} diff --git a/src/modules/rtp/base64.h b/src/modules/rtp/base64.h deleted file mode 100644 index dac0e707..00000000 --- a/src/modules/rtp/base64.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef foobase64hfoo -#define foobase64hfoo - -/*** - This file is part of PulseAudio. - - Copyright 2008 Colin Guthrie - Copyright Kungliga Tekniska Høgskolan - - PulseAudio is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published - by the Free Software Foundation; either version 2 of the License, - or (at your option) any later version. - - PulseAudio is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with PulseAudio; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - -/* - This file was originally inspired by a file developed by - Kungliga Tekniska Høgskolan -*/ - -int pa_base64_encode(const void *data, int size, char **str); -int pa_base64_decode(const char *str, void *data); - -#endif diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c deleted file mode 100644 index 4627545e..00000000 --- a/src/modules/rtp/raop_client.c +++ /dev/null @@ -1,561 +0,0 @@ -/*** - This file is part of PulseAudio. - - Copyright 2008 Colin Guthrie - - PulseAudio is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published - by the Free Software Foundation; either version 2 of the License, - or (at your option) any later version. - - PulseAudio is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with PulseAudio; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_SYS_FILIO_H -#include -#endif - -/* TODO: Replace OpenSSL with NSS */ -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "raop_client.h" -#include "rtsp_client.h" -#include "base64.h" - -#define AES_CHUNKSIZE 16 - -#define JACK_STATUS_DISCONNECTED 0 -#define JACK_STATUS_CONNECTED 1 - -#define JACK_TYPE_ANALOG 0 -#define JACK_TYPE_DIGITAL 1 - -#define VOLUME_DEF -30 -#define VOLUME_MIN -144 -#define VOLUME_MAX 0 - - -struct pa_raop_client { - pa_core *core; - char *host; - char *sid; - pa_rtsp_client *rtsp; - - uint8_t jack_type; - uint8_t jack_status; - - /* Encryption Related bits */ - AES_KEY aes; - uint8_t aes_iv[AES_CHUNKSIZE]; /* initialization vector for aes-cbc */ - uint8_t aes_nv[AES_CHUNKSIZE]; /* next vector for aes-cbc */ - uint8_t aes_key[AES_CHUNKSIZE]; /* key for aes-cbc */ - - pa_socket_client *sc; - int fd; - - uint16_t seq; - uint32_t rtptime; - - pa_raop_client_cb_t callback; - void* userdata; - pa_raop_client_closed_cb_t closed_callback; - void* closed_userdata; -}; - -/** - * Function to write bits into a buffer. - * @param buffer Handle to the buffer. It will be incremented if new data requires it. - * @param bit_pos A pointer to a position buffer to keep track the current write location (0 for MSB, 7 for LSB) - * @param size A pointer to the byte size currently written. This allows the calling function to do simple buffer overflow checks - * @param data The data to write - * @param data_bit_len The number of bits from data to write - */ -static inline void bit_writer(uint8_t **buffer, uint8_t *bit_pos, int *size, uint8_t data, uint8_t data_bit_len) { - int bits_left, bit_overflow; - uint8_t bit_data; - - if (!data_bit_len) - return; - - /* If bit pos is zero, we will definatly use at least one bit from the current byte so size increments. */ - if (!*bit_pos) - *size += 1; - - /* Calc the number of bits left in the current byte of buffer */ - bits_left = 7 - *bit_pos + 1; - /* Calc the overflow of bits in relation to how much space we have left... */ - bit_overflow = bits_left - data_bit_len; - if (bit_overflow >= 0) { - /* We can fit the new data in our current byte */ - /* As we write from MSB->LSB we need to left shift by the overflow amount */ - bit_data = data << bit_overflow; - if (*bit_pos) - **buffer |= bit_data; - else - **buffer = bit_data; - /* If our data fits exactly into the current byte, we need to increment our pointer */ - if (0 == bit_overflow) { - /* Do not increment size as it will be incremeneted on next call as bit_pos is zero */ - *buffer += 1; - *bit_pos = 0; - } else { - *bit_pos += data_bit_len; - } - } else { - /* bit_overflow is negative, there for we will need a new byte from our buffer */ - /* Firstly fill up what's left in the current byte */ - bit_data = data >> -bit_overflow; - **buffer |= bit_data; - /* Increment our buffer pointer and size counter*/ - *buffer += 1; - *size += 1; - **buffer = data << (8 + bit_overflow); - *bit_pos = -bit_overflow; - } -} - -static int rsa_encrypt(uint8_t *text, int len, uint8_t *res) { - const char n[] = - "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" - "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR" - "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB" - "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ" - "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh" - "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="; - const char e[] = "AQAB"; - uint8_t modules[256]; - uint8_t exponent[8]; - int size; - RSA *rsa; - - rsa = RSA_new(); - size = pa_base64_decode(n, modules); - rsa->n = BN_bin2bn(modules, size, NULL); - size = pa_base64_decode(e, exponent); - rsa->e = BN_bin2bn(exponent, size, NULL); - - size = RSA_public_encrypt(len, text, res, rsa, RSA_PKCS1_OAEP_PADDING); - RSA_free(rsa); - return size; -} - -static int aes_encrypt(pa_raop_client* c, uint8_t *data, int size) -{ - uint8_t *buf; - int i=0, j; - - pa_assert(c); - - memcpy(c->aes_nv, c->aes_iv, AES_CHUNKSIZE); - while (i+AES_CHUNKSIZE <= size) { - buf = data + i; - for (j=0; jaes_nv[j]; - - AES_encrypt(buf, buf, &c->aes); - memcpy(c->aes_nv, buf, AES_CHUNKSIZE); - i += AES_CHUNKSIZE; - } - return i; -} - -static inline void rtrimchar(char *str, char rc) -{ - char *sp = str + strlen(str) - 1; - while (sp >= str && *sp == rc) { - *sp = '\0'; - sp -= 1; - } -} - -static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { - pa_raop_client *c = userdata; - - pa_assert(sc); - pa_assert(c); - pa_assert(c->sc == sc); - pa_assert(c->fd < 0); - pa_assert(c->callback); - - pa_socket_client_unref(c->sc); - c->sc = NULL; - - if (!io) { - pa_log("Connection failed: %s", pa_cstrerror(errno)); - return; - } - - c->fd = pa_iochannel_get_send_fd(io); - - pa_iochannel_set_noclose(io, TRUE); - pa_iochannel_socket_set_sndbuf(io, 1024); - pa_iochannel_free(io); - - pa_make_tcp_socket_low_delay(c->fd); - - pa_log_debug("Connection established"); - c->callback(c->fd, c->userdata); -} - -static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* headers, void *userdata) -{ - pa_raop_client* c = userdata; - pa_assert(c); - pa_assert(rtsp); - pa_assert(rtsp == c->rtsp); - - switch (state) { - case STATE_CONNECT: { - int i; - uint8_t rsakey[512]; - char *key, *iv, *sac, *sdp; - uint16_t rand_data; - const char *ip; - char *url; - - pa_log_debug("RAOP: CONNECTED"); - ip = pa_rtsp_localip(c->rtsp); - /* First of all set the url properly */ - url = pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid); - pa_rtsp_set_url(c->rtsp, url); - pa_xfree(url); - - /* Now encrypt our aes_public key to send to the device */ - i = rsa_encrypt(c->aes_key, AES_CHUNKSIZE, rsakey); - pa_base64_encode(rsakey, i, &key); - rtrimchar(key, '='); - pa_base64_encode(c->aes_iv, AES_CHUNKSIZE, &iv); - rtrimchar(iv, '='); - - pa_random(&rand_data, sizeof(rand_data)); - pa_base64_encode(&rand_data, AES_CHUNKSIZE, &sac); - rtrimchar(sac, '='); - pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac); - sdp = pa_sprintf_malloc( - "v=0\r\n" - "o=iTunes %s 0 IN IP4 %s\r\n" - "s=iTunes\r\n" - "c=IN IP4 %s\r\n" - "t=0 0\r\n" - "m=audio 0 RTP/AVP 96\r\n" - "a=rtpmap:96 AppleLossless\r\n" - "a=fmtp:96 4096 0 16 40 10 14 2 255 0 0 44100\r\n" - "a=rsaaeskey:%s\r\n" - "a=aesiv:%s\r\n", - c->sid, ip, c->host, key, iv); - pa_rtsp_announce(c->rtsp, sdp); - pa_xfree(key); - pa_xfree(iv); - pa_xfree(sac); - pa_xfree(sdp); - break; - } - - case STATE_ANNOUNCE: - pa_log_debug("RAOP: ANNOUNCED"); - pa_rtsp_remove_header(c->rtsp, "Apple-Challenge"); - pa_rtsp_setup(c->rtsp); - break; - - case STATE_SETUP: { - char *aj = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status")); - pa_log_debug("RAOP: SETUP"); - if (aj) { - char *token, *pc; - char delimiters[] = ";"; - const char* token_state = NULL; - c->jack_type = JACK_TYPE_ANALOG; - c->jack_status = JACK_STATUS_DISCONNECTED; - - while ((token = pa_split(aj, delimiters, &token_state))) { - if ((pc = strstr(token, "="))) { - *pc = 0; - if (!strcmp(token, "type") && !strcmp(pc+1, "digital")) { - c->jack_type = JACK_TYPE_DIGITAL; - } - } else { - if (!strcmp(token,"connected")) - c->jack_status = JACK_STATUS_CONNECTED; - } - pa_xfree(token); - } - pa_xfree(aj); - } else { - pa_log_warn("Audio Jack Status missing"); - } - pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime); - break; - } - - case STATE_RECORD: { - uint32_t port = pa_rtsp_serverport(c->rtsp); - pa_log_debug("RAOP: RECORDED"); - - if (!(c->sc = pa_socket_client_new_string(c->core->mainloop, c->host, port))) { - pa_log("failed to connect to server '%s:%d'", c->host, port); - return; - } - pa_socket_client_set_callback(c->sc, on_connection, c); - break; - } - - case STATE_FLUSH: - pa_log_debug("RAOP: FLUSHED"); - break; - - case STATE_TEARDOWN: - case STATE_SET_PARAMETER: - pa_log_debug("RAOP: SET_PARAMETER"); - break; - case STATE_DISCONNECTED: - pa_assert(c->closed_callback); - pa_assert(c->rtsp); - - pa_log_debug("RTSP control channel closed"); - pa_rtsp_client_free(c->rtsp); - c->rtsp = NULL; - if (c->fd > 0) { - /* We do not close the fd, we leave it to the closed callback to do that */ - c->fd = -1; - } - if (c->sc) { - pa_socket_client_unref(c->sc); - c->sc = NULL; - } - pa_xfree(c->sid); - c->sid = NULL; - c->closed_callback(c->closed_userdata); - break; - } -} - -pa_raop_client* pa_raop_client_new(pa_core *core, const char* host) -{ - pa_raop_client* c = pa_xnew0(pa_raop_client, 1); - - pa_assert(core); - pa_assert(host); - - c->core = core; - c->fd = -1; - c->host = pa_xstrdup(host); - - if (pa_raop_connect(c)) { - pa_raop_client_free(c); - return NULL; - } - return c; -} - - -void pa_raop_client_free(pa_raop_client* c) -{ - pa_assert(c); - - if (c->rtsp) - pa_rtsp_client_free(c->rtsp); - pa_xfree(c->host); - pa_xfree(c); -} - - -int pa_raop_connect(pa_raop_client* c) -{ - char *sci; - struct { - uint32_t a; - uint32_t b; - uint32_t c; - } rand_data; - - pa_assert(c); - - if (c->rtsp) { - pa_log_debug("Connection already in progress"); - return 0; - } - - c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, 5000, "iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); - - /* Initialise the AES encryption system */ - pa_random(c->aes_iv, sizeof(c->aes_iv)); - pa_random(c->aes_key, sizeof(c->aes_key)); - memcpy(c->aes_nv, c->aes_iv, sizeof(c->aes_nv)); - AES_set_encrypt_key(c->aes_key, 128, &c->aes); - - /* Generate random instance id */ - pa_random(&rand_data, sizeof(rand_data)); - c->sid = pa_sprintf_malloc("%u", rand_data.a); - sci = pa_sprintf_malloc("%08x%08x",rand_data.b, rand_data.c); - pa_rtsp_add_header(c->rtsp, "Client-Instance", sci); - pa_xfree(sci); - pa_rtsp_set_callback(c->rtsp, rtsp_cb, c); - return pa_rtsp_connect(c->rtsp); -} - - -int pa_raop_flush(pa_raop_client* c) -{ - pa_assert(c); - - pa_rtsp_flush(c->rtsp, c->seq, c->rtptime); - return 0; -} - - -int pa_raop_client_set_volume(pa_raop_client* c, pa_volume_t volume) -{ - int rv; - double db; - char *param; - - pa_assert(c); - - db = pa_sw_volume_to_dB(volume); - if (db < VOLUME_MIN) - db = VOLUME_MIN; - else if (db > VOLUME_MAX) - db = VOLUME_MAX; - - param = pa_sprintf_malloc("volume: %0.6f\r\n", db); - - /* We just hit and hope, cannot wait for the callback */ - rv = pa_rtsp_setparameter(c->rtsp, param); - pa_xfree(param); - return rv; -} - - -int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded) -{ - uint16_t len; - size_t bufmax; - uint8_t *bp, bpos; - uint8_t *ibp, *maxibp; - int size; - uint8_t *b, *p; - uint32_t bsize; - size_t length; - static uint8_t header[] = { - 0x24, 0x00, 0x00, 0x00, - 0xF0, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - }; - int header_size = sizeof(header); - - pa_assert(c); - pa_assert(c->fd > 0); - pa_assert(raw); - pa_assert(raw->memblock); - pa_assert(raw->length > 0); - pa_assert(encoded); - - /* We have to send 4 byte chunks */ - bsize = (int)(raw->length / 4); - length = bsize * 4; - - /* Leave 16 bytes extra to allow for the ALAC header which is about 55 bits */ - bufmax = length + header_size + 16; - pa_memchunk_reset(encoded); - encoded->memblock = pa_memblock_new(c->core->mempool, bufmax); - b = pa_memblock_acquire(encoded->memblock); - memcpy(b, header, header_size); - - /* Now write the actual samples */ - bp = b + header_size; - size = bpos = 0; - bit_writer(&bp,&bpos,&size,1,3); /* channel=1, stereo */ - bit_writer(&bp,&bpos,&size,0,4); /* unknown */ - bit_writer(&bp,&bpos,&size,0,8); /* unknown */ - bit_writer(&bp,&bpos,&size,0,4); /* unknown */ - bit_writer(&bp,&bpos,&size,1,1); /* hassize */ - bit_writer(&bp,&bpos,&size,0,2); /* unused */ - bit_writer(&bp,&bpos,&size,1,1); /* is-not-compressed */ - - /* size of data, integer, big endian */ - bit_writer(&bp,&bpos,&size,(bsize>>24)&0xff,8); - bit_writer(&bp,&bpos,&size,(bsize>>16)&0xff,8); - bit_writer(&bp,&bpos,&size,(bsize>>8)&0xff,8); - bit_writer(&bp,&bpos,&size,(bsize)&0xff,8); - - ibp = p = pa_memblock_acquire(raw->memblock); - maxibp = p + raw->length - 4; - while (ibp <= maxibp) { - /* Byte swap stereo data */ - bit_writer(&bp,&bpos,&size,*(ibp+1),8); - bit_writer(&bp,&bpos,&size,*(ibp+0),8); - bit_writer(&bp,&bpos,&size,*(ibp+3),8); - bit_writer(&bp,&bpos,&size,*(ibp+2),8); - ibp += 4; - raw->index += 4; - raw->length -= 4; - } - pa_memblock_release(raw->memblock); - encoded->length = header_size + size; - - /* store the lenght (endian swapped: make this better) */ - len = size + header_size - 4; - *(b + 2) = len >> 8; - *(b + 3) = len & 0xff; - - /* encrypt our data */ - aes_encrypt(c, (b + header_size), size); - - /* We're done with the chunk */ - pa_memblock_release(encoded->memblock); - - return 0; -} - - -void pa_raop_client_set_callback(pa_raop_client* c, pa_raop_client_cb_t callback, void *userdata) -{ - pa_assert(c); - - c->callback = callback; - c->userdata = userdata; -} - -void pa_raop_client_set_closed_callback(pa_raop_client* c, pa_raop_client_closed_cb_t callback, void *userdata) -{ - pa_assert(c); - - c->closed_callback = callback; - c->closed_userdata = userdata; -} diff --git a/src/modules/rtp/raop_client.h b/src/modules/rtp/raop_client.h deleted file mode 100644 index ec3136a7..00000000 --- a/src/modules/rtp/raop_client.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef fooraopclientfoo -#define fooraopclientfoo - -/*** - This file is part of PulseAudio. - - Copyright 2008 Colin Guthrie - - PulseAudio is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published - by the Free Software Foundation; either version 2 of the License, - or (at your option) any later version. - - PulseAudio is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with PulseAudio; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - -#include -#include -#include - -typedef struct pa_raop_client pa_raop_client; - -pa_raop_client* pa_raop_client_new(pa_core *core, const char* host); -void pa_raop_client_free(pa_raop_client* c); - -int pa_raop_connect(pa_raop_client* c); -int pa_raop_flush(pa_raop_client* c); - -int pa_raop_client_set_volume(pa_raop_client* c, pa_volume_t volume); -int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded); - -typedef void (*pa_raop_client_cb_t)(int fd, void *userdata); -void pa_raop_client_set_callback(pa_raop_client* c, pa_raop_client_cb_t callback, void *userdata); - -typedef void (*pa_raop_client_closed_cb_t)(void *userdata); -void pa_raop_client_set_closed_callback(pa_raop_client* c, pa_raop_client_closed_cb_t callback, void *userdata); - -#endif -- cgit