Refactor and rewrite.

Perl script is removed in favour of C. Stale documentation and initfiles
are removed for now also.

C interface works, but still needs work (for example, mDNS is missing) -
see TODO.
This commit is contained in:
James Laird
2013-04-01 18:57:08 +11:00
parent 98a86371d0
commit a2fb5d219d
26 changed files with 1767 additions and 3524 deletions
-4
View File
@@ -1,4 +0,0 @@
/usr/ports/security/p5-Crypt-RSA
/usr/ports/security/p5-Crypt-OpenSSL-RSA
net/p5-Net-SDP
/usr/ports/net/p5-IO-Socket-INET6
-106
View File
@@ -1,106 +0,0 @@
Type `make` to build the packet decoder, `hairtunes`.
You need the following installed:
* openssl
* libao
* Perl 5.10 or higher
* Linux: avahi (or, for embedded systems, howl)
* Windows/Mac OS X: Bonjour
Perl modules (install from CPAN if needed e.g. `perl -MCPAN -e 'install X'`):
* HTTP::Request
* HTTP::Message
* Crypt::OpenSSL::RSA
* IO::Socket::INET6
* Net::SDP
## Debian/Ubuntu:
apt-get install build-essential libssl-dev libcrypt-openssl-rsa-perl libao-dev libio-socket-inet6-perl libwww-perl avahi-utils pkg-config
make
perl shairport.pl
## Redhat/Fedora:
yum install openssl-devel libao libao-devel perl-Crypt-OpenSSL-RSA perl-IO-Socket-INET6 perl-libwww-perl avahi-tools
make
perl shairport.pl
## Gentoo/Funtoo
echo "media-libs/libao alsa" >> /etc/portage/package.use
echo "net-dns/avahi mdnsresponder-compat" >> /etc/portage/package.use
echo "dev-perl/HTTP-Message ~x86" >> /etc/portage/package.keywords
echo "dev-perl/HTTP-Date ~x86" >> /etc/portage/package.keywords
echo "dev-perl/Encode-Locale ~x86" >> /etc/portage/package.keywords
echo "dev-perl/LWP-MediaTypes ~x86" >> /etc/portage/package.keywords
emerge libao avahi dev-perl/HTTP-Message dev-perl/Crypt-OpenSSL-RSA dev-perl/IO-Socket-INET6
rc-update add dbus default
rc-update add avahi-daemon default
/etc/init.d/dbus start
/etc/init.d/avahi-daemon start
make
perl shairport.pl
## OpenBSD
pkg_add -r gmake libao avahi p5-libwww p5-Crypt-OpenSSL-RSA p5-IO-Socket-INET6
gmake
perl shairport.pl
## FreeBSD
pkg_add -r gmake libao avahi p5-libwww p5-Crypt-OpenSSL-RSA p5-Net-SDP p5-IO-Socket-INET6
gmake
# ipv6 does not work for me, but maybe works for you? might be due to not
# having local ipv6 working dns names?
# force ipv4 on FreeBSD with '-4' on the command line.
perl shairport.pl -4
## Mac OS X:
* install XCode
* install [Homebrew](https://github.com/mxcl/homebrew) or [MacPorts](http://www.macports.org/)
* type:
$ export ARCHFLAGS="-arch x86_64" # (replace x86_64 by your arch)
$ brew install pkg-config libao # for [Homebrew](https://github.com/mxcl/homebrew)
$ port install pkgconfig libao # for [MacPorts](http://www.macports.org/)
$ make
$ perl -MCPAN -e 'install Crypt::OpenSSL::RSA' # (may require sudo)
$ perl -MCPAN -e 'install IO::Socket::INET6' # (may require sudo)
$ perl shairport.pl
Users of OS X 10.5 and below will need to install a newer Perl (via `port`/`brew`).
### How to run as a daemon on Mac 10.6
$ cp hairtunes shairport.pl /usr/local/bin
$ vi /usr/local/bin/shairport.pl # change the path of hairtunes from ./hairtunes to /usr/local/bin/hairtunes
$ mkdir -p ~/Library/LaunchAgents
$ cp org.mafipulation.shairport.plist ~/Library/LaunchAgents/
$ launchctl load org.mafipulation.shairport.plist
$ launchctl unload org.mafipulation.shairport.plist # (to remove)
## Windows
* Download and install Cygwin.
* During setup, select
* openssl-devel
* openssl
* libao
* libao-devel
* gcc4
* make
* pkg-config
* perl
* Install [Bonjour for Windows](http://support.apple.com/kb/DL999)
* Launch the Cygwin Bash shell
* type:
$ make
$ perl -MCPAN -e 'install Crypt::OpenSSL::RSA'
$ perl -MCPAN -e 'install IO::Socket::INET6'
$ perl shairport.pl
+4 -38
View File
@@ -1,40 +1,6 @@
SRCS := shairport.c rtsp.c common.c rtp.c player.c alac.c $(wildcard audio_*.c)
MY_CFLAGS= $(shell pkg-config --cflags ao)
MY_LDFLAGS= $(shell pkg-config --libs ao)
ifeq ($(shell uname),FreeBSD)
MY_LDFLAGS+= -lssl
else
MY_CFLAGS+= $(shell pkg-config --cflags openssl)
MY_LDFLAGS+= $(shell pkg-config --libs openssl)
endif
CFLAGS:=-O2 -Wall $(MY_CFLAGS)
LDFLAGS:=-lm -lpthread $(MY_LDFLAGS)
OBJS=socketlib.o shairport.o alac.o hairtunes.o
all: hairtunes shairport
hairtunes: hairtunes.c alac.o
$(CC) $(CFLAGS) -DHAIRTUNES_STANDALONE hairtunes.c alac.o -o $@ $(LDFLAGS)
shairport: $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o $@ $(LDFLAGS)
clean:
-@rm -rf hairtunes shairport $(OBJS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
prefix=/usr/local
install: hairtunes shairport
install -D -m 0755 hairtunes $(DESTDIR)$(prefix)/bin/hairtunes
install -D -m 0755 shairport.pl $(DESTDIR)$(prefix)/bin/shairport.pl
install -D -m 0755 shairport $(DESTDIR)$(prefix)/bin/shairport
.PHONY: all clean install
.SILENT: clean
LIBS := -lcrypto -lm -lao -lpthread
shairport: $(SRCS)
gcc -ggdb -Wall $(SRCS) $(LIBS) -o shairport
+13
View File
@@ -0,0 +1,13 @@
- make BUFFER_FRAMES adjustable
- use getopt
- copyright updates
- signal handlers
- pluggable audio drivers
- audio usage
- finish ao
- makefile
- documentation
- PKGBUILD, initscript, systemd, launchd plist
- mDNS
- volume control
- tidy debug printing
+6 -6
View File
@@ -55,7 +55,7 @@ static const int host_bigendian = 0;
struct {signed int x:24;} se_struct_24;
#define SignExtend24(val) (se_struct_24.x = val)
void allocate_buffers(alac_file *alac)
void alac_allocate_buffers(alac_file *alac)
{
alac->predicterror_buffer_a = malloc(alac->setinfo_max_samples_per_frame * 4);
alac->predicterror_buffer_b = malloc(alac->setinfo_max_samples_per_frame * 4);
@@ -110,7 +110,7 @@ void alac_set_info(alac_file *alac, char *inputbuffer)
if (!host_bigendian)
_Swap32(alac->setinfo_8a_rate);
allocate_buffers(alac);
alac_allocate_buffers(alac);
}
@@ -687,9 +687,9 @@ static void deinterlace_24(int32_t *buffer_a, int32_t *buffer_b,
}
void decode_frame(alac_file *alac,
unsigned char *inbuffer,
void *outbuffer, int *outputsize)
void alac_decode_frame(alac_file *alac,
unsigned char *inbuffer,
void *outbuffer, int *outputsize)
{
int channels;
int32_t outputsamples = alac->setinfo_max_samples_per_frame;
@@ -1104,7 +1104,7 @@ void decode_frame(alac_file *alac,
}
}
alac_file *create_alac(int samplesize, int numchannels)
alac_file *alac_create(int samplesize, int numchannels)
{
alac_file *newfile = malloc(sizeof(alac_file));
+7 -5
View File
@@ -1,14 +1,16 @@
#ifndef __ALAC__DECOMP_H
#define __ALAC__DECOMP_H
#include <stdint.h>
typedef struct alac_file alac_file;
alac_file *create_alac(int samplesize, int numchannels);
void decode_frame(alac_file *alac,
unsigned char *inbuffer,
void *outbuffer, int *outputsize);
alac_file *alac_create(int samplesize, int numchannels);
void alac_decode_frame(alac_file *alac,
unsigned char *inbuffer,
void *outbuffer, int *outputsize);
void alac_set_info(alac_file *alac, char *inputbuffer);
void allocate_buffers(alac_file *alac);
void alac_allocate_buffers(alac_file *alac);
struct alac_file
{
+22
View File
@@ -0,0 +1,22 @@
#ifndef _AUDIO_H
#define _AUDIO_H
typedef struct {
// start of program
int (*init)(int argc, char **argv);
// at end of program
void (*deinit)(void);
void (*start)(int sample_rate);
// block of samples
void (*play)(short buf[], int samples);
void (*stop)(void);
// may be NULL, in which case soft volume is applied
void (*volume)(double vol);
} audio_ops;
extern audio_ops audio_dummy;
extern audio_ops audio_ao;
#endif //_AUDIO_H
+53
View File
@@ -0,0 +1,53 @@
#include <stdio.h>
#include <unistd.h>
#include <memory.h>
#include <ao/ao.h>
#include "common.h"
#include "audio.h"
ao_device *dev = NULL;
static int init(int argc, char **argv) {
printf("ao: init\n");
ao_initialize();
ao_sample_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.bits = 16;
fmt.rate = 44100;
fmt.channels = 2;
fmt.byte_format = AO_FMT_NATIVE;
int driver = ao_default_driver_id();
dev = ao_open_live(driver, &fmt, NULL);
return dev ? 0 : 1;
}
static void deinit(void) {
if (dev)
ao_close(dev);
}
static void start(int sample_rate) {
printf("ao: start\n");
if (sample_rate != 44100)
die("unexpceted sample rate!\n");
}
static void play(short buf[], int samples) {
ao_play(dev, (char*)buf, samples*4);
}
static void stop(void) {
}
audio_ops audio_ao = {
.init = &init,
.deinit = &deinit,
.start = &start,
.stop = &stop,
.play = &play,
.volume = NULL
};
+52
View File
@@ -0,0 +1,52 @@
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include "audio.h"
int Fs;
long long starttime, samples_played;
static int dummy_init(int argc, char **argv) {
return 0;
}
static void dummy_deinit(void) {
}
static void dummy_start(int sample_rate) {
Fs = sample_rate;
starttime = 0;
samples_played = 0;
printf("dummy audio output started at Fs=%d Hz\n", sample_rate);
}
static void dummy_play(short buf[], int samples) {
struct timeval tv;
// this is all a bit expensive but it's long-term stable.
gettimeofday(&tv, NULL);
long long nowtime = tv.tv_usec + 1e6*tv.tv_sec;
if (!starttime)
starttime = nowtime;
samples_played += samples;
long long finishtime = starttime + samples_played * 1e6 / Fs;
usleep(finishtime - nowtime);
}
static void dummy_stop(void) {
printf("dummy audio stopped\n");
}
audio_ops audio_dummy = {
.init = &dummy_init,
.deinit = &dummy_deinit,
.start = &dummy_start,
.stop = &dummy_stop,
.play = &dummy_play,
.volume = NULL
};
+143
View File
@@ -0,0 +1,143 @@
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <memory.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include "common.h"
shairport_cfg config;
int debuglev = 1;
void die(char *format, ...) {
fprintf(stderr, "FATAL: ");
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
exit(1);
}
void warn(char *format, ...) {
fprintf(stderr, "WARNING: ");
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
void debug(char *format, ...) {
if (!debuglev)
return;
fprintf(stderr, "DEBUG: ");
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
char *base64_enc(uint8_t *input, int length) {
int ret;
BIO *bmem, *b64;
BUF_MEM *bptr;
b64 = BIO_new(BIO_f_base64());
bmem = BIO_new(BIO_s_mem());
b64 = BIO_push(b64, bmem);
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
BIO_write(b64, input, length);
ret = BIO_flush(b64);
BIO_get_mem_ptr(b64, &bptr);
char *buf = (char *)malloc(bptr->length);
if (bptr->length) {
memcpy(buf, bptr->data, bptr->length-1);
buf[bptr->length-1] = 0;
}
BIO_free_all(bmem);
return buf;
}
uint8_t *base64_dec(char *input, int *outlen) {
int ret;
BIO *bmem, *b64;
int inlen = strlen(input);
b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bmem = BIO_new(BIO_s_mem());
b64 = BIO_push(b64, bmem);
// Apple cut the padding off their challenges; restore it
BIO_write(bmem, input, inlen);
while (inlen++ & 3)
BIO_write(bmem, "=", 1);
ret = BIO_flush(bmem);
int bufsize = strlen(input)*3/4 + 1;
uint8_t *buf = malloc(bufsize);
int nread;
nread = BIO_read(b64, buf, bufsize);
BIO_free_all(bmem);
*outlen = nread;
return buf;
}
static char super_secret_key[] =
"-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\n"
"wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\n"
"wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\n"
"/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\n"
"UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\n"
"BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\n"
"LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\n"
"NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\n"
"lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\n"
"aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\n"
"a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\n"
"oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\n"
"oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\n"
"k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\n"
"AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\n"
"cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\n"
"54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\n"
"17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\n"
"1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\n"
"LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\n"
"2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n"
"-----END RSA PRIVATE KEY-----";
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
static RSA *rsa = NULL;
if (!rsa) {
BIO *bmem = BIO_new_mem_buf(super_secret_key, -1);
rsa = PEM_read_bio_RSAPrivateKey(bmem, NULL, NULL, NULL);
BIO_free(bmem);
}
uint8_t *out = malloc(RSA_size(rsa));
switch (mode) {
case RSA_MODE_AUTH:
*outlen = RSA_private_encrypt(inlen, input, out, rsa,
RSA_PKCS1_PADDING);
break;
case RSA_MODE_KEY:
*outlen = RSA_private_decrypt(inlen, input, out, rsa,
RSA_PKCS1_OAEP_PADDING);
break;
default:
die("bad rsa mode");
}
return out;
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef _COMMON_H
#define _COMMON_H
#include <openssl/rsa.h>
#include <stdint.h>
#include "audio.h"
typedef struct {
char *password;
char hw_addr[6];
int port;
audio_ops *output;
int buffer_start_fill;
} shairport_cfg;
void die(char *format, ...);
void warn(char *format, ...);
void debug(char *format, ...);
uint8_t *base64_dec(char *input, int *outlen);
char *base64_enc(uint8_t *input, int length);
#define RSA_MODE_AUTH (0)
#define RSA_MODE_KEY (1)
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode);
extern shairport_cfg config;
#endif // _COMMON_H
-964
View File
@@ -1,964 +0,0 @@
/*
* HairTunes - RAOP packet handler and slave-clocked replay engine
* Copyright (c) James Laird 2011
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <openssl/aes.h>
#include <math.h>
#include <sys/stat.h>
#include "hairtunes.h"
#include <sys/signal.h>
#include <fcntl.h>
#include <ao/ao.h>
#ifdef FANCY_RESAMPLING
#include <samplerate.h>
#endif
#include <assert.h>
static int debug = 0;
#include "alac.h"
// and how full it needs to be to begin (must be <BUFFER_FRAMES)
#define START_FILL 282
#define MAX_PACKET 2048
typedef unsigned short seq_t;
// global options (constant after init)
static unsigned char aeskey[16], aesiv[16];
static AES_KEY aes;
static char *rtphost = 0;
static int dataport = 0, controlport = 0, timingport = 0;
static int fmtp[32];
static int sampling_rate;
static int frame_size;
static int buffer_start_fill;
static char *libao_driver = NULL;
static char *libao_devicename = NULL;
static char *libao_deviceid = NULL; // ao_options expects "char*"
// FIFO name and file handle
static char *pipename = NULL;
static int pipe_handle = -1;
#define FRAME_BYTES (4*frame_size)
// maximal resampling shift - conservative
#define OUTFRAME_BYTES (4*(frame_size+3))
static alac_file *decoder_info;
#ifdef FANCY_RESAMPLING
static int fancy_resampling = 1;
static SRC_STATE *src;
#endif
static int init_rtp(void);
static void init_buffer(void);
static int init_output(void);
static void rtp_request_resend(seq_t first, seq_t last);
static void ab_resync(void);
// interthread variables
// stdin->decoder
static double volume = 1.0;
static int fix_volume = 0x10000;
static pthread_mutex_t vol_mutex = PTHREAD_MUTEX_INITIALIZER;
typedef struct audio_buffer_entry { // decoded audio packets
int ready;
signed short *data;
} abuf_t;
static abuf_t audio_buffer[BUFFER_FRAMES];
#define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES)
// mutex-protected variables
static seq_t ab_read, ab_write;
static int ab_buffering = 1, ab_synced = 0;
static pthread_mutex_t ab_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t ab_buffer_ready = PTHREAD_COND_INITIALIZER;
static void die(char *why) {
fprintf(stderr, "FATAL: %s\n", why);
exit(1);
}
#ifdef HAIRTUNES_STANDALONE
static int hex2bin(unsigned char *buf, char *hex) {
int i, j;
if (strlen(hex) != 0x20)
return 1;
for (i=0; i<0x10; i++) {
if (!sscanf(hex, "%2X", &j))
return 1;
hex += 2;
*buf++ = j;
}
return 0;
}
#endif
static int init_decoder(void) {
alac_file *alac;
frame_size = fmtp[1]; // stereo samples
sampling_rate = fmtp[11];
int sample_size = fmtp[3];
if (sample_size != 16)
die("only 16-bit samples supported!");
alac = create_alac(sample_size, 2);
if (!alac)
return 1;
decoder_info = alac;
alac->setinfo_max_samples_per_frame = frame_size;
alac->setinfo_7a = fmtp[2];
alac->setinfo_sample_size = sample_size;
alac->setinfo_rice_historymult = fmtp[4];
alac->setinfo_rice_initialhistory = fmtp[5];
alac->setinfo_rice_kmodifier = fmtp[6];
alac->setinfo_7f = fmtp[7];
alac->setinfo_80 = fmtp[8];
alac->setinfo_82 = fmtp[9];
alac->setinfo_86 = fmtp[10];
alac->setinfo_8a_rate = fmtp[11];
allocate_buffers(alac);
return 0;
}
int hairtunes_init(char *pAeskey, char *pAesiv, char *fmtpstr, int pCtrlPort, int pTimingPort,
int pDataPort, char *pRtpHost, char*pPipeName, char *pLibaoDriver, char *pLibaoDeviceName, char *pLibaoDeviceId,
int bufStartFill)
{
if(pAeskey != NULL)
memcpy(aeskey, pAeskey, sizeof(aeskey));
if(pAesiv != NULL)
memcpy(aesiv, pAesiv, sizeof(aesiv));
if(pRtpHost != NULL)
rtphost = pRtpHost;
if(pPipeName != NULL)
pipename = pPipeName;
if(pLibaoDriver != NULL)
libao_driver = pLibaoDriver;
if(pLibaoDeviceName != NULL)
libao_devicename = pLibaoDeviceName;
if(pLibaoDeviceId != NULL)
libao_deviceid = pLibaoDeviceId;
controlport = pCtrlPort;
timingport = pTimingPort;
dataport = pDataPort;
if(bufStartFill < 0)
bufStartFill = START_FILL;
buffer_start_fill = bufStartFill;
AES_set_decrypt_key(aeskey, 128, &aes);
memset(fmtp, 0, sizeof(fmtp));
int i = 0;
char *arg;
while ( (arg = strsep(&fmtpstr, " \t")) )
fmtp[i++] = atoi(arg);
init_decoder();
init_buffer();
init_rtp(); // open a UDP listen port and start a listener; decode into ring buffer
fflush(stdout);
init_output(); // resample and output from ring buffer
char line[128];
int in_line = 0;
int n;
double f;
while (fgets(line + in_line, sizeof(line) - in_line, stdin)) {
n = strlen(line);
if (line[n-1] != '\n') {
in_line = strlen(line) - 1;
if (n == sizeof(line)-1)
in_line = 0;
continue;
}
if (sscanf(line, "vol: %lf\n", &f)) {
assert(f<=0);
if (debug)
fprintf(stderr, "VOL: %lf\n", f);
pthread_mutex_lock(&vol_mutex);
volume = pow(10.0,0.05*f);
fix_volume = 65536.0 * volume;
pthread_mutex_unlock(&vol_mutex);
continue;
}
if (!strcmp(line, "exit\n")) {
exit(0);
}
if (!strcmp(line, "flush\n")) {
pthread_mutex_lock(&ab_mutex);
ab_resync();
pthread_mutex_unlock(&ab_mutex);
if (debug)
fprintf(stderr, "FLUSH\n");
}
}
fprintf(stderr, "bye!\n");
fflush(stderr);
return EXIT_SUCCESS;
}
#ifdef AF_INET6
static int ipv4_only = 0;
#else
static int ipv4_only = 1;
#endif
#ifdef HAIRTUNES_STANDALONE
int main(int argc, char **argv) {
char *hexaeskey = 0, *hexaesiv = 0;
char *fmtpstr = 0;
char *arg;
assert(RAND_MAX >= 0x10000); // XXX move this to compile time
while ( (arg = *++argv) ) {
if (!strcasecmp(arg, "iv")) {
hexaesiv = *++argv;
argc--;
} else
if (!strcasecmp(arg, "ipv4_only")) {
ipv4_only = 1;
} else
if (!strcasecmp(arg, "key")) {
hexaeskey = *++argv;
argc--;
} else
if (!strcasecmp(arg, "fmtp")) {
fmtpstr = *++argv;
} else
if (!strcasecmp(arg, "cport")) {
controlport = atoi(*++argv);
} else
if (!strcasecmp(arg, "tport")) {
timingport = atoi(*++argv);
} else
if (!strcasecmp(arg, "dport")) {
dataport = atoi(*++argv);
} else
if (!strcasecmp(arg, "host")) {
rtphost = *++argv;
} else
if (!strcasecmp(arg, "pipe")) {
if (libao_driver || libao_devicename || libao_deviceid ) {
die("Option 'pipe' may not be combined with 'ao_driver', 'ao_devicename' or 'ao_deviceid'");
}
pipename = *++argv;
} else
if (!strcasecmp(arg, "ao_driver")) {
if (pipename) {
die("Option 'ao_driver' may not be combined with 'pipe'");
}
libao_driver = *++argv;
} else
if (!strcasecmp(arg, "ao_devicename")) {
if (pipename || libao_deviceid ) {
die("Option 'ao_devicename' may not be combined with 'pipe' or 'ao_deviceid'");
}
libao_devicename = *++argv;
} else
if (!strcasecmp(arg, "ao_deviceid")) {
if (pipename || libao_devicename) {
die("Option 'ao_deviceid' may not be combined with 'pipe' or 'ao_devicename'");
}
libao_deviceid = *++argv;
}
#ifdef FANCY_RESAMPLING
else
if (!strcasecmp(arg, "resamp")) {
fancy_resampling = atoi(*++argv);
}
#endif
}
if (!hexaeskey || !hexaesiv)
die("Must supply AES key and IV!");
if (hex2bin(aesiv, hexaesiv))
die("can't understand IV");
if (hex2bin(aeskey, hexaeskey))
die("can't understand key");
return hairtunes_init(NULL, NULL, fmtpstr, controlport, timingport, dataport,
NULL, NULL, NULL, NULL, NULL, START_FILL);
}
#endif
static void init_buffer(void) {
int i;
for (i=0; i<BUFFER_FRAMES; i++)
audio_buffer[i].data = malloc(OUTFRAME_BYTES);
ab_resync();
}
static void ab_resync(void) {
int i;
for (i=0; i<BUFFER_FRAMES; i++)
audio_buffer[i].ready = 0;
ab_synced = 0;
ab_buffering = 1;
}
// the sequence numbers will wrap pretty often.
// this returns true if the second arg is after the first
static inline int seq_order(seq_t a, seq_t b) {
signed short d = b - a;
return d > 0;
}
static void alac_decode(short *dest, char *buf, int len) {
unsigned char packet[MAX_PACKET];
assert(len<=MAX_PACKET);
unsigned char iv[16];
int aeslen = len & ~0xf;
memcpy(iv, aesiv, sizeof(iv));
AES_cbc_encrypt((unsigned char*)buf, packet, aeslen, &aes, iv, AES_DECRYPT);
memcpy(packet+aeslen, buf+aeslen, len-aeslen);
int outsize;
decode_frame(decoder_info, packet, dest, &outsize);
assert(outsize == FRAME_BYTES);
}
static void buffer_put_packet(seq_t seqno, char *data, int len) {
abuf_t *abuf = 0;
short buf_fill;
pthread_mutex_lock(&ab_mutex);
if (!ab_synced) {
ab_write = seqno;
ab_read = seqno-1;
ab_synced = 1;
}
if (seqno == ab_write+1) { // expected packet
abuf = audio_buffer + BUFIDX(seqno);
ab_write = seqno;
} else if (seq_order(ab_write, seqno)) { // newer than expected
rtp_request_resend(ab_write+1, seqno-1);
abuf = audio_buffer + BUFIDX(seqno);
ab_write = seqno;
} else if (seq_order(ab_read, seqno)) { // late but not yet played
abuf = audio_buffer + BUFIDX(seqno);
} else { // too late.
fprintf(stderr, "\nlate packet %04X (%04X:%04X)\n", seqno, ab_read, ab_write);
}
buf_fill = ab_write - ab_read;
pthread_mutex_unlock(&ab_mutex);
if (abuf) {
alac_decode(abuf->data, data, len);
abuf->ready = 1;
}
pthread_mutex_lock(&ab_mutex);
if (ab_buffering && buf_fill >= buffer_start_fill) {
ab_buffering = 0;
pthread_cond_signal(&ab_buffer_ready);
}
pthread_mutex_unlock(&ab_mutex);
}
static int rtp_sockets[2]; // data, control
#ifdef AF_INET6
static struct sockaddr_in6 rtp_client6;
#endif
static struct sockaddr_in rtp_client4;
struct sockaddr * get_rtp_client_sockaddr(void) {
#ifdef AF_INET6
if (!ipv4_only) {
return ((struct sockaddr*)&rtp_client6);
}
#endif
return ((struct sockaddr*)&rtp_client4);
}
socklen_t get_rtp_sockaddr_len(void) {
#ifdef AF_INET6
if (!ipv4_only) {
return (sizeof(&rtp_client6));
}
#endif
return (sizeof(rtp_client4));
}
static void *rtp_thread_func(void *arg) {
socklen_t si_len;
char packet[MAX_PACKET];
char *pktp;
seq_t seqno;
ssize_t plen;
int sock = rtp_sockets[0], csock = rtp_sockets[1];
int readsock;
char type;
struct sockaddr *rtp_client;
rtp_client = get_rtp_client_sockaddr();
si_len = get_rtp_sockaddr_len();
fd_set fds;
FD_ZERO(&fds);
FD_SET(sock, &fds);
FD_SET(csock, &fds);
while (select(csock>sock ? csock+1 : sock+1, &fds, 0, 0, 0)!=-1) {
if (FD_ISSET(sock, &fds)) {
readsock = sock;
} else {
readsock = csock;
}
FD_SET(sock, &fds);
FD_SET(csock, &fds);
plen = recvfrom(readsock, packet, sizeof(packet), 0, rtp_client, &si_len);
if (plen < 0)
continue;
assert(plen<=MAX_PACKET);
type = packet[1] & ~0x80;
if (type == 0x60 || type == 0x56) { // audio data / resend
pktp = packet;
if (type==0x56) {
pktp += 4;
plen -= 4;
}
seqno = ntohs(*(unsigned short *)(pktp+2));
// adjust pointer and length
pktp += 12;
plen -= 12;
// check if packet contains enough content to be reasonable
if (plen >= 16) {
buffer_put_packet(seqno, pktp, plen);
} else {
// resync?
if (type == 0x56 && seqno == 0) {
fprintf(stderr, "Suspected resync request packet received. Initiating resync.\n");
pthread_mutex_lock(&ab_mutex);
ab_resync();
pthread_mutex_unlock(&ab_mutex);
}
}
}
}
return 0;
}
static void rtp_request_resend(seq_t first, seq_t last) {
if (seq_order(last, first))
return;
fprintf(stderr, "requesting resend on %d packets (port %d)\n", last-first+1, controlport);
char req[8]; // *not* a standard RTCP NACK
req[0] = 0x80;
req[1] = 0x55|0x80; // Apple 'resend'
*(unsigned short *)(req+2) = htons(1); // our seqnum
*(unsigned short *)(req+4) = htons(first); // missed seqnum
*(unsigned short *)(req+6) = htons(last-first+1); // count
struct sockaddr *rtp_client = get_rtp_client_sockaddr();
if (ipv4_only) {
((struct sockaddr_in *)rtp_client)->sin_port = htons(controlport);
}
#ifdef AF_INET6
else {
((struct sockaddr_in6 *)rtp_client)->sin6_port = htons(controlport);
}
#endif
sendto(rtp_sockets[1], req, sizeof(req), 0, rtp_client, get_rtp_sockaddr_len());
}
static int init_rtp(void) {
struct sockaddr_in si;
int type = AF_INET;
struct sockaddr* si_p = (struct sockaddr*)&si;
socklen_t si_len = sizeof(si);
unsigned short *sin_port = &si.sin_port;
memset(&si, 0, sizeof(si));
#ifdef AF_INET6
struct sockaddr_in6 si6;
if (!ipv4_only) {
type = AF_INET6;
si_p = (struct sockaddr*)&si6;
si_len = sizeof(si6);
sin_port = &si6.sin6_port;
memset(&si6, 0, sizeof(si6));
}
#endif
si.sin_family = AF_INET;
#ifdef SIN_LEN
si.sin_len = sizeof(si);
#endif
si.sin_addr.s_addr = htonl(INADDR_ANY);
#ifdef AF_INET6
if (!ipv4_only) {
si6.sin6_family = AF_INET6;
#ifdef SIN6_LEN
si6.sin6_len = sizeof(si);
#endif
si6.sin6_addr = in6addr_any;
si6.sin6_flowinfo = 0;
}
#endif
int sock = -1, csock = -1; // data and control (we treat the streams the same here)
unsigned short port = 6000;
while(1) {
if(sock < 0)
sock = socket(type, SOCK_DGRAM, IPPROTO_UDP);
#ifdef AF_INET6
if(sock==-1 && type == AF_INET6) {
// try fallback to IPv4
type = AF_INET;
si_p = (struct sockaddr*)&si;
si_len = sizeof(si);
sin_port = &si.sin_port;
continue;
}
#endif
if (sock==-1)
die("Can't create data socket!");
if(csock < 0)
csock = socket(type, SOCK_DGRAM, IPPROTO_UDP);
if (csock==-1)
die("Can't create control socket!");
*sin_port = htons(port);
int bind1 = bind(sock, si_p, si_len);
*sin_port = htons(port + 1);
int bind2 = bind(csock, si_p, si_len);
if(bind1 != -1 && bind2 != -1) break;
if(bind1 != -1) { close(sock); sock = -1; }
if(bind2 != -1) { close(csock); csock = -1; }
port += 3;
}
printf("port: %d\n", port); // let our handler know where we end up listening
printf("cport: %d\n", port+1);
pthread_t rtp_thread;
rtp_sockets[0] = sock;
rtp_sockets[1] = csock;
pthread_create(&rtp_thread, NULL, rtp_thread_func, (void *)rtp_sockets);
return port;
}
static short lcg_rand(void) {
static unsigned long lcg_prev = 12345;
lcg_prev = lcg_prev * 69069 + 3;
return lcg_prev & 0xffff;
}
static inline short dithered_vol(short sample) {
static short rand_a, rand_b;
long out;
out = (long)sample * fix_volume;
if (fix_volume < 0x10000) {
rand_b = rand_a;
rand_a = lcg_rand();
out += rand_a;
out -= rand_b;
}
return out>>16;
}
typedef struct {
double hist[2];
double a[2];
double b[3];
} biquad_t;
static void biquad_init(biquad_t *bq, double a[], double b[]) {
bq->hist[0] = bq->hist[1] = 0.0;
memcpy(bq->a, a, 2*sizeof(double));
memcpy(bq->b, b, 3*sizeof(double));
}
static void biquad_lpf(biquad_t *bq, double freq, double Q) {
double w0 = 2.0 * M_PI * freq * frame_size / (double)sampling_rate;
double alpha = sin(w0)/(2.0*Q);
double a_0 = 1.0 + alpha;
double b[3], a[2];
b[0] = (1.0-cos(w0))/(2.0*a_0);
b[1] = (1.0-cos(w0))/a_0;
b[2] = b[0];
a[0] = -2.0*cos(w0)/a_0;
a[1] = (1-alpha)/a_0;
biquad_init(bq, a, b);
}
static double biquad_filt(biquad_t *bq, double in) {
double w = in - bq->a[0]*bq->hist[0] - bq->a[1]*bq->hist[1];
double out = bq->b[1]*bq->hist[0] + bq->b[2]*bq->hist[1] + bq->b[0]*w;
bq->hist[1] = bq->hist[0];
bq->hist[0] = w;
return out;
}
static double bf_playback_rate = 1.0;
static double bf_est_drift = 0.0; // local clock is slower by
static biquad_t bf_drift_lpf;
static double bf_est_err = 0.0, bf_last_err;
static biquad_t bf_err_lpf, bf_err_deriv_lpf;
static double desired_fill;
static int fill_count;
static void bf_est_reset(short fill) {
biquad_lpf(&bf_drift_lpf, 1.0/180.0, 0.3);
biquad_lpf(&bf_err_lpf, 1.0/10.0, 0.25);
biquad_lpf(&bf_err_deriv_lpf, 1.0/2.0, 0.2);
fill_count = 0;
bf_playback_rate = 1.0;
bf_est_err = bf_last_err = 0;
desired_fill = fill_count = 0;
}
static void bf_est_update(short fill) {
if (fill_count < 1000) {
desired_fill += (double)fill/1000.0;
fill_count++;
return;
}
#define CONTROL_A (1e-4)
#define CONTROL_B (1e-1)
double buf_delta = fill - desired_fill;
bf_est_err = biquad_filt(&bf_err_lpf, buf_delta);
double err_deriv = biquad_filt(&bf_err_deriv_lpf, bf_est_err - bf_last_err);
double adj_error = CONTROL_A * bf_est_err;
bf_est_drift = biquad_filt(&bf_drift_lpf, CONTROL_B*(adj_error + err_deriv) + bf_est_drift);
if (debug)
fprintf(stderr, "bf %d err %f drift %f desiring %f ed %f estd %f\n",
fill, bf_est_err, bf_est_drift, desired_fill, err_deriv, err_deriv + adj_error);
bf_playback_rate = 1.0 + adj_error + bf_est_drift;
bf_last_err = bf_est_err;
}
// get the next frame, when available. return 0 if underrun/stream reset.
static short *buffer_get_frame(void) {
short buf_fill;
seq_t read;
abuf_t *abuf = 0;
unsigned short next;
int i;
pthread_mutex_lock(&ab_mutex);
buf_fill = ab_write - ab_read;
if (buf_fill < 1 || !ab_synced || ab_buffering) { // init or underrun. stop and wait
if (ab_synced)
fprintf(stderr, "\nunderrun.\n");
ab_buffering = 1;
pthread_cond_wait(&ab_buffer_ready, &ab_mutex);
ab_read++;
buf_fill = ab_write - ab_read;
bf_est_reset(buf_fill);
pthread_mutex_unlock(&ab_mutex);
return 0;
}
if (buf_fill >= BUFFER_FRAMES) { // overrunning! uh-oh. restart at a sane distance
fprintf(stderr, "\noverrun.\n");
ab_read = ab_write - buffer_start_fill;
}
read = ab_read;
ab_read++;
buf_fill = ab_write - ab_read;
bf_est_update(buf_fill);
// check if t+16, t+32, t+64, t+128, ... (START_FILL / 2)
// packets have arrived... last-chance resend
if (!ab_buffering) {
for (i = 16; i < (START_FILL / 2); i = (i * 2)) {
next = ab_read + i;
abuf = audio_buffer + BUFIDX(next);
if (!abuf->ready) {
rtp_request_resend(next, next);
}
}
}
abuf_t *curframe = audio_buffer + BUFIDX(read);
if (!curframe->ready) {
fprintf(stderr, "\nmissing frame.\n");
memset(curframe->data, 0, FRAME_BYTES);
}
curframe->ready = 0;
pthread_mutex_unlock(&ab_mutex);
return curframe->data;
}
static int stuff_buffer(double playback_rate, short *inptr, short *outptr) {
int i;
int stuffsamp = frame_size;
int stuff = 0;
double p_stuff;
p_stuff = 1.0 - pow(1.0 - fabs(playback_rate-1.0), frame_size);
if (rand() < p_stuff * RAND_MAX) {
stuff = playback_rate > 1.0 ? -1 : 1;
stuffsamp = rand() % (frame_size - 1);
}
pthread_mutex_lock(&vol_mutex);
for (i=0; i<stuffsamp; i++) { // the whole frame, if no stuffing
*outptr++ = dithered_vol(*inptr++);
*outptr++ = dithered_vol(*inptr++);
};
if (stuff) {
if (stuff==1) {
if (debug)
fprintf(stderr, "+++++++++\n");
// interpolate one sample
*outptr++ = dithered_vol(((long)inptr[-2] + (long)inptr[0]) >> 1);
*outptr++ = dithered_vol(((long)inptr[-1] + (long)inptr[1]) >> 1);
} else if (stuff==-1) {
if (debug)
fprintf(stderr, "---------\n");
inptr++;
inptr++;
}
for (i=stuffsamp; i<frame_size + stuff; i++) {
*outptr++ = dithered_vol(*inptr++);
*outptr++ = dithered_vol(*inptr++);
}
}
pthread_mutex_unlock(&vol_mutex);
return frame_size + stuff;
}
static void *audio_thread_func(void *arg) {
ao_device* dev = arg;
int play_samples;
signed short buf_fill __attribute__((unused));
signed short *inbuf, *outbuf, *silence;
outbuf = malloc(OUTFRAME_BYTES);
silence = malloc(OUTFRAME_BYTES);
int i;
for (i=0; i<OUTFRAME_BYTES/2; i++) {
silence[i] = 0;
}
#ifdef FANCY_RESAMPLING
float *frame, *outframe;
SRC_DATA srcdat;
if (fancy_resampling) {
frame = malloc(frame_size*2*sizeof(float));
outframe = malloc(2*frame_size*2*sizeof(float));
srcdat.data_in = frame;
srcdat.data_out = outframe;
srcdat.input_frames = FRAME_BYTES;
srcdat.output_frames = 2*FRAME_BYTES;
srcdat.src_ratio = 1.0;
srcdat.end_of_input = 0;
}
#endif
while (1) {
if (ab_buffering) {
inbuf = silence;
} else {
do {
inbuf = buffer_get_frame();
} while (!inbuf);
}
#ifdef FANCY_RESAMPLING
if (fancy_resampling) {
int i;
pthread_mutex_lock(&vol_mutex);
for (i=0; i<2*FRAME_BYTES; i++) {
frame[i] = (float)inbuf[i] / 32768.0;
frame[i] *= volume;
}
pthread_mutex_unlock(&vol_mutex);
srcdat.src_ratio = bf_playback_rate;
src_process(src, &srcdat);
assert(srcdat.input_frames_used == FRAME_BYTES);
src_float_to_short_array(outframe, outbuf, FRAME_BYTES*2);
play_samples = srcdat.output_frames_gen;
} else
#endif
play_samples = stuff_buffer(bf_playback_rate, inbuf, outbuf);
if (pipename) {
if (pipe_handle == -1) {
// attempt to open pipe - block if there are no readers
pipe_handle = open(pipename, O_WRONLY);
}
// only write if pipe open (there's a reader)
if (pipe_handle != -1) {
if (write(pipe_handle, outbuf, play_samples*4) == -1) {
// write failed - do anything here?
// SIGPIPE is handled elsewhere...
}
}
} else {
ao_play(dev, (char *)outbuf, play_samples*4);
}
}
return 0;
}
#define NUM_CHANNELS 2
static void handle_broken_fifo() {
close(pipe_handle);
pipe_handle = -1;
}
static void init_pipe(const char* pipe) {
// make the FIFO and catch the broken pipe signal
mknod(pipe, S_IFIFO | 0644, 0);
signal(SIGPIPE, handle_broken_fifo);
}
static void* init_ao(void) {
ao_initialize();
int driver;
if (libao_driver) {
// if a libao driver is specified on the command line, use that
driver = ao_driver_id(libao_driver);
if (driver == -1) {
die("Could not find requested ao driver");
}
} else {
// otherwise choose the default
driver = ao_default_driver_id();
}
ao_sample_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.bits = 16;
fmt.rate = sampling_rate;
fmt.channels = NUM_CHANNELS;
fmt.byte_format = AO_FMT_NATIVE;
ao_option *ao_opts = NULL;
if(libao_deviceid) {
ao_append_option(&ao_opts, "id", libao_deviceid);
} else if(libao_devicename){
ao_append_option(&ao_opts, "dev", libao_devicename);
// Old libao versions (for example, 0.8.8) only support
// "dsp" instead of "dev".
ao_append_option(&ao_opts, "dsp", libao_devicename);
}
ao_device *dev = ao_open_live(driver, &fmt, ao_opts);
if (dev == NULL) {
die("Could not open ao device");
}
return dev;
}
static int init_output(void) {
void* arg = 0;
if (pipename) {
init_pipe(pipename);
} else {
arg = init_ao();
}
#ifdef FANCY_RESAMPLING
int err;
if (fancy_resampling)
src = src_new(SRC_SINC_MEDIUM_QUALITY, 2, &err);
else
src = 0;
#endif
pthread_t audio_thread;
pthread_create(&audio_thread, NULL, audio_thread_func, arg);
return 0;
}
-11
View File
@@ -1,11 +0,0 @@
#ifndef _HAIRTUNES_H_
#define _HAIRTUNES_H_
int hairtunes_init(char *pAeskey, char *pAesiv, char *fmtpstr, int pCtrlPort, int pTimingPort,
int pDataPort, char *pRtpHost, char*pPipeName, char *pLibaoDriver, char *pLibaoDeviceName, char *pLibaoDeviceId,
int bufStartFill);
// default buffer size
// needs to be a power of 2 because of the way BUFIDX(seqno) works
#define BUFFER_FRAMES 512
#endif
-16
View File
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.mafipulation.shairport</string>
<key>RunAtLoad</key>
<true/>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/shairport.pl</string>
<string>--apname</string>
<string>RJ</string>
</array>
</dict>
</plist>
+478
View File
@@ -0,0 +1,478 @@
// slave-clocked stream player
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <pthread.h>
#include <openssl/aes.h>
#include <math.h>
#include <sys/stat.h>
#include <sys/signal.h>
#include <assert.h>
#include <fcntl.h>
#include "common.h"
#include "player.h"
#include "rtp.h"
#ifdef FANCY_RESAMPLING
#include <samplerate.h>
#endif
#include "alac.h"
// parameters from the source
static unsigned char *aesiv;
static AES_KEY aes;
static int sampling_rate, frame_size;
#define FRAME_BYTES (4*frame_size)
// maximal resampling shift - conservative
#define OUTFRAME_BYTES (4*(frame_size+3))
static pthread_t player_thread;
static int please_stop;
static alac_file *decoder_info;
#ifdef FANCY_RESAMPLING
static int fancy_resampling = 1;
static SRC_STATE *src;
#endif
// interthread variables
static double volume = 1.0;
static int fix_volume = 0x10000;
static pthread_mutex_t vol_mutex = PTHREAD_MUTEX_INITIALIZER;
// default buffer size
// needs to be a power of 2 because of the way BUFIDX(seqno) works
#define BUFFER_FRAMES 512
#define START_FILL 250
#define MAX_PACKET 2048
typedef struct audio_buffer_entry { // decoded audio packets
int ready;
signed short *data;
} abuf_t;
static abuf_t audio_buffer[BUFFER_FRAMES];
#define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES)
// mutex-protected variables
static seq_t ab_read, ab_write;
static int ab_buffering = 1, ab_synced = 0;
static pthread_mutex_t ab_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t ab_buffer_ready = PTHREAD_COND_INITIALIZER;
static void ab_resync(void) {
int i;
for (i=0; i<BUFFER_FRAMES; i++)
audio_buffer[i].ready = 0;
ab_synced = 0;
ab_buffering = 1;
}
// the sequence numbers will wrap pretty often.
// this returns true if the second arg is after the first
static inline int seq_order(seq_t a, seq_t b) {
signed short d = b - a;
return d > 0;
}
static void alac_decode(short *dest, uint8_t *buf, int len) {
unsigned char packet[MAX_PACKET];
assert(len<=MAX_PACKET);
unsigned char iv[16];
int aeslen = len & ~0xf;
memcpy(iv, aesiv, sizeof(iv));
AES_cbc_encrypt(buf, packet, aeslen, &aes, iv, AES_DECRYPT);
memcpy(packet+aeslen, buf+aeslen, len-aeslen);
int outsize;
alac_decode_frame(decoder_info, packet, dest, &outsize);
assert(outsize == FRAME_BYTES);
}
static int init_decoder(int32_t fmtp[12]) {
alac_file *alac;
frame_size = fmtp[1]; // stereo samples
sampling_rate = fmtp[11];
int sample_size = fmtp[3];
if (sample_size != 16)
die("only 16-bit samples supported!");
alac = alac_create(sample_size, 2);
if (!alac)
return 1;
decoder_info = alac;
alac->setinfo_max_samples_per_frame = frame_size;
alac->setinfo_7a = fmtp[2];
alac->setinfo_sample_size = sample_size;
alac->setinfo_rice_historymult = fmtp[4];
alac->setinfo_rice_initialhistory = fmtp[5];
alac->setinfo_rice_kmodifier = fmtp[6];
alac->setinfo_7f = fmtp[7];
alac->setinfo_80 = fmtp[8];
alac->setinfo_82 = fmtp[9];
alac->setinfo_86 = fmtp[10];
alac->setinfo_8a_rate = fmtp[11];
alac_allocate_buffers(alac);
return 0;
}
#ifdef FANCY_RESAMPLING
static int init_src(void) {
int err;
if (fancy_resampling)
src = src_new(SRC_SINC_MEDIUM_QUALITY, 2, &err);
else
src = 0;
return err;
}
#endif
static void init_buffer(void) {
int i;
for (i=0; i<BUFFER_FRAMES; i++)
audio_buffer[i].data = malloc(OUTFRAME_BYTES);
ab_resync();
}
void player_put_packet(seq_t seqno, uint8_t *data, int len) {
abuf_t *abuf = 0;
short buf_fill;
pthread_mutex_lock(&ab_mutex);
if (!ab_synced) {
ab_write = seqno;
ab_read = seqno-1;
ab_synced = 1;
}
if (seqno == ab_write+1) { // expected packet
abuf = audio_buffer + BUFIDX(seqno);
ab_write = seqno;
} else if (seq_order(ab_write, seqno)) { // newer than expected
rtp_request_resend(ab_write+1, seqno-1);
abuf = audio_buffer + BUFIDX(seqno);
ab_write = seqno;
} else if (seq_order(ab_read, seqno)) { // late but not yet played
abuf = audio_buffer + BUFIDX(seqno);
} else { // too late.
warn("late packet %04X (%04X:%04X)\n", seqno, ab_read, ab_write);
}
buf_fill = ab_write - ab_read;
pthread_mutex_unlock(&ab_mutex);
if (abuf) {
alac_decode(abuf->data, data, len);
abuf->ready = 1;
}
pthread_mutex_lock(&ab_mutex);
if (ab_buffering && buf_fill >= config.buffer_start_fill) {
debug("buffering over. starting play\n");
ab_buffering = 0;
pthread_cond_signal(&ab_buffer_ready);
}
pthread_mutex_unlock(&ab_mutex);
}
static short lcg_rand(void) {
static unsigned long lcg_prev = 12345;
lcg_prev = lcg_prev * 69069 + 3;
return lcg_prev & 0xffff;
}
static inline short dithered_vol(short sample) {
static short rand_a, rand_b;
long out;
out = (long)sample * fix_volume;
if (fix_volume < 0x10000) {
rand_b = rand_a;
rand_a = lcg_rand();
out += rand_a;
out -= rand_b;
}
return out>>16;
}
typedef struct {
double hist[2];
double a[2];
double b[3];
} biquad_t;
static void biquad_init(biquad_t *bq, double a[], double b[]) {
bq->hist[0] = bq->hist[1] = 0.0;
memcpy(bq->a, a, 2*sizeof(double));
memcpy(bq->b, b, 3*sizeof(double));
}
static void biquad_lpf(biquad_t *bq, double freq, double Q) {
double w0 = 2.0 * M_PI * freq * frame_size / (double)sampling_rate;
double alpha = sin(w0)/(2.0*Q);
double a_0 = 1.0 + alpha;
double b[3], a[2];
b[0] = (1.0-cos(w0))/(2.0*a_0);
b[1] = (1.0-cos(w0))/a_0;
b[2] = b[0];
a[0] = -2.0*cos(w0)/a_0;
a[1] = (1-alpha)/a_0;
biquad_init(bq, a, b);
}
static double biquad_filt(biquad_t *bq, double in) {
double w = in - bq->a[0]*bq->hist[0] - bq->a[1]*bq->hist[1];
double out = bq->b[1]*bq->hist[0] + bq->b[2]*bq->hist[1] + bq->b[0]*w;
bq->hist[1] = bq->hist[0];
bq->hist[0] = w;
return out;
}
static double bf_playback_rate = 1.0;
static double bf_est_drift = 0.0; // local clock is slower by
static biquad_t bf_drift_lpf;
static double bf_est_err = 0.0, bf_last_err;
static biquad_t bf_err_lpf, bf_err_deriv_lpf;
static double desired_fill;
static int fill_count;
static void bf_est_reset(short fill) {
biquad_lpf(&bf_drift_lpf, 1.0/180.0, 0.3);
biquad_lpf(&bf_err_lpf, 1.0/10.0, 0.25);
biquad_lpf(&bf_err_deriv_lpf, 1.0/2.0, 0.2);
fill_count = 0;
bf_playback_rate = 1.0;
bf_est_err = bf_last_err = 0;
desired_fill = fill_count = 0;
}
static void bf_est_update(short fill) {
if (fill_count < 1000) {
desired_fill += (double)fill/1000.0;
fill_count++;
return;
}
#define CONTROL_A (1e-4)
#define CONTROL_B (1e-1)
double buf_delta = fill - desired_fill;
bf_est_err = biquad_filt(&bf_err_lpf, buf_delta);
double err_deriv = biquad_filt(&bf_err_deriv_lpf, bf_est_err - bf_last_err);
double adj_error = CONTROL_A * bf_est_err;
bf_est_drift = biquad_filt(&bf_drift_lpf, CONTROL_B*(adj_error + err_deriv) + bf_est_drift);
debug("bf %d err %f drift %f desiring %f ed %f estd %f\n",
fill, bf_est_err, bf_est_drift, desired_fill, err_deriv, err_deriv + adj_error);
bf_playback_rate = 1.0 + adj_error + bf_est_drift;
bf_last_err = bf_est_err;
}
// get the next frame, when available. return 0 if underrun/stream reset.
static short *buffer_get_frame(void) {
short buf_fill;
seq_t read;
abuf_t *abuf = 0;
unsigned short next;
int i;
pthread_mutex_lock(&ab_mutex);
buf_fill = ab_write - ab_read;
if (buf_fill < 1 || !ab_synced || ab_buffering) { // init or underrun. stop and wait
if (ab_synced)
warn("underrun.\n");
ab_buffering = 1;
pthread_cond_wait(&ab_buffer_ready, &ab_mutex);
ab_read++;
buf_fill = ab_write - ab_read;
bf_est_reset(buf_fill);
pthread_mutex_unlock(&ab_mutex);
return 0;
}
if (buf_fill >= BUFFER_FRAMES) { // overrunning! uh-oh. restart at a sane distance
warn("overrun.\n");
ab_read = ab_write - config.buffer_start_fill;
}
read = ab_read;
ab_read++;
buf_fill = ab_write - ab_read;
bf_est_update(buf_fill);
// check if t+16, t+32, t+64, t+128, ... (START_FILL / 2)
// packets have arrived... last-chance resend
if (!ab_buffering) {
for (i = 16; i < (START_FILL / 2); i = (i * 2)) {
next = ab_read + i;
abuf = audio_buffer + BUFIDX(next);
if (!abuf->ready) {
rtp_request_resend(next, next);
}
}
}
abuf_t *curframe = audio_buffer + BUFIDX(read);
if (!curframe->ready) {
warn("missing frame.\n");
memset(curframe->data, 0, FRAME_BYTES);
}
curframe->ready = 0;
pthread_mutex_unlock(&ab_mutex);
return curframe->data;
}
static int stuff_buffer(double playback_rate, short *inptr, short *outptr) {
int i;
int stuffsamp = frame_size;
int stuff = 0;
double p_stuff;
p_stuff = 1.0 - pow(1.0 - fabs(playback_rate-1.0), frame_size);
if (rand() < p_stuff * RAND_MAX) {
stuff = playback_rate > 1.0 ? -1 : 1;
stuffsamp = rand() % (frame_size - 1);
}
pthread_mutex_lock(&vol_mutex);
for (i=0; i<stuffsamp; i++) { // the whole frame, if no stuffing
*outptr++ = dithered_vol(*inptr++);
*outptr++ = dithered_vol(*inptr++);
};
if (stuff) {
if (stuff==1) {
debug("+++++++++\n");
// interpolate one sample
*outptr++ = dithered_vol(((long)inptr[-2] + (long)inptr[0]) >> 1);
*outptr++ = dithered_vol(((long)inptr[-1] + (long)inptr[1]) >> 1);
} else if (stuff==-1) {
debug("---------\n");
inptr++;
inptr++;
}
for (i=stuffsamp; i<frame_size + stuff; i++) {
*outptr++ = dithered_vol(*inptr++);
*outptr++ = dithered_vol(*inptr++);
}
}
pthread_mutex_unlock(&vol_mutex);
return frame_size + stuff;
}
static void *player_thread_func(void *arg) {
int play_samples;
signed short *inbuf, *outbuf, *silence;
outbuf = malloc(OUTFRAME_BYTES);
silence = malloc(OUTFRAME_BYTES);
memset(silence, 0, OUTFRAME_BYTES);
#ifdef FANCY_RESAMPLING
float *frame, *outframe;
SRC_DATA srcdat;
if (fancy_resampling) {
frame = malloc(frame_size*2*sizeof(float));
outframe = malloc(2*frame_size*2*sizeof(float));
srcdat.data_in = frame;
srcdat.data_out = outframe;
srcdat.input_frames = FRAME_BYTES;
srcdat.output_frames = 2*FRAME_BYTES;
srcdat.src_ratio = 1.0;
srcdat.end_of_input = 0;
}
#endif
while (!please_stop) {
do {
inbuf = ab_buffering ? silence : buffer_get_frame();
} while (!inbuf);
#ifdef FANCY_RESAMPLING
if (fancy_resampling) {
int i;
pthread_mutex_lock(&vol_mutex);
for (i=0; i<2*FRAME_BYTES; i++) {
frame[i] = (float)inbuf[i] / 32768.0;
frame[i] *= volume;
}
pthread_mutex_unlock(&vol_mutex);
srcdat.src_ratio = bf_playback_rate;
src_process(src, &srcdat);
assert(srcdat.input_frames_used == FRAME_BYTES);
src_float_to_short_array(outframe, outbuf, FRAME_BYTES*2);
play_samples = srcdat.output_frames_gen;
} else
#endif
play_samples = stuff_buffer(bf_playback_rate, inbuf, outbuf);
config.output->play(outbuf, play_samples);
}
return 0;
}
// takes the volume as specified by the airplay protocol
void player_volume(double f) {
double linear_volume = pow(10.0, 0.05*f);
if (config.output->volume) {
config.output->volume(linear_volume);
} else {
pthread_mutex_lock(&vol_mutex);
volume = linear_volume;
fix_volume = 65536.0 * volume;
pthread_mutex_unlock(&vol_mutex);
}
}
void player_flush(void) {
pthread_mutex_lock(&ab_mutex);
ab_resync();
pthread_mutex_unlock(&ab_mutex);
}
int player_play(stream_cfg *stream) {
AES_set_decrypt_key(stream->aeskey, 128, &aes);
aesiv = stream->aesiv;
init_decoder(stream->fmtp);
init_buffer();
#ifdef FANCY_RESAMPLING
init_src();
#endif
please_stop = 0;
config.output->start(sampling_rate);
pthread_create(&player_thread, NULL, player_thread_func, NULL);
return 0;
}
void player_stop(void) {
please_stop = 1;
pthread_join(player_thread, NULL);
config.output->stop();
}
+22
View File
@@ -0,0 +1,22 @@
#ifndef _PLAYER_H
#define _PLAYER_H
#include "audio.h"
typedef struct {
uint8_t aesiv[16], aeskey[16];
int32_t fmtp[12];
} stream_cfg;
typedef unsigned short seq_t;
int player_play(stream_cfg *cfg);
void player_stop(void);
void player_volume(double f);
void player_flush(void);
void player_resync(void);
void player_put_packet(seq_t seqno, uint8_t *data, int len);
#endif //_PLAYER_H
+157
View File
@@ -0,0 +1,157 @@
// only one RTP session can be active at a time.
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#include <memory.h>
#include <sys/socket.h>
#include <netdb.h>
#include "common.h"
#include "player.h"
static int running = 0;
static int please_shutdown;
static struct sockaddr rtp_client;
static int sock;
static pthread_t rtp_thread;
static void *rtp_receiver(void *arg) {
// we inherit the signal mask (SIGUSR1)
uint8_t packet[2048], *pktp;
ssize_t nread;
while (1) {
if (please_shutdown)
break;
nread = recv(sock, packet, sizeof(packet), 0);
if (nread < 0)
break;
ssize_t plen = nread;
uint8_t type = packet[1] & ~0x80;
if (type == 0x60 || type == 0x56) { // audio data / resend
pktp = packet;
if (type==0x56) {
pktp += 4;
plen -= 4;
}
seq_t seqno = ntohs(*(unsigned short *)(pktp+2));
pktp += 12;
plen -= 12;
// check if packet contains enough content to be reasonable
if (plen >= 16) {
player_put_packet(seqno, pktp, plen);
continue;
}
if (type == 0x56 && seqno == 0) {
debug("Suspected resync request packet received.\n");
player_flush();
continue;
}
}
warn("Unknown RTP packet of type 0x%02X length %d\n", type, nread);
}
debug("RTP thread interrupted. terminating.\n");
close(sock);
return NULL;
}
static int bind_port(struct sockaddr *remote) {
struct addrinfo hints, *info;
memset(&hints, 0, sizeof(hints));
hints.ai_family = remote->sa_family;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_PASSIVE;
int ret = getaddrinfo(NULL, "0", &hints, &info);
if (ret < 0)
die("failed to get usable addrinfo?! %s", gai_strerror(ret));
sock = socket(remote->sa_family, SOCK_DGRAM, IPPROTO_UDP);
ret = bind(sock, info->ai_addr, info->ai_addrlen);
if (ret < 0)
die("could not bind a UDP port!");
int sport;
struct sockaddr local;
socklen_t local_len = sizeof(local);
getsockname(sock, &local, &local_len);
#ifdef AF_INET6
if (local.sa_family == AF_INET6) {
struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)&local;
sport = htons(sa6->sin6_port);
} else
#endif
{
struct sockaddr_in *sa = (struct sockaddr_in*)&local;
sport = htons(sa->sin_port);
}
return sport;
}
int rtp_setup(struct sockaddr *remote, int cport, int tport) {
if (running)
die("rtp_setup called with active stream!\n");
debug("rtp_setup: cport=%d tport=%d\n", cport, tport);
// we do our own timing and ignore the timing port.
// an audio perfectionist may wish to learn the protocol.
memcpy(&rtp_client, remote, sizeof(rtp_client));
#ifdef AF_INET6
if (rtp_client.sa_family == AF_INET6) {
struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)&rtp_client;
sa6->sin6_port = htons(cport);
} else
#endif
{
struct sockaddr_in *sa = (struct sockaddr_in*)&rtp_client;
sa->sin_port = htons(cport);
}
int sport = bind_port(remote);
debug("rtp listening on port %d\n", sport);
please_shutdown = 0;
pthread_create(&rtp_thread, NULL, &rtp_receiver, NULL);
running = 1;
return sport;
}
void rtp_shutdown(void) {
if (!running)
die("rtp_shutdown called without active stream!\n");
please_shutdown = 1;
pthread_kill(rtp_thread, SIGUSR1);
void *retval;
pthread_join(rtp_thread, &retval);
running = 0;
}
void rtp_request_resend(seq_t first, seq_t last) {
if (!running)
die("rtp_request_resend called without active stream!\n");
warn("requesting resend on %d packets\n", last-first+1);
char req[8]; // *not* a standard RTCP NACK
req[0] = 0x80;
req[1] = 0x55|0x80; // Apple 'resend'
*(unsigned short *)(req+2) = htons(1); // our seqnum
*(unsigned short *)(req+4) = htons(first); // missed seqnum
*(unsigned short *)(req+6) = htons(last-first+1); // count
sendto(sock, req, sizeof(req), 0, &rtp_client, sizeof(rtp_client));
}
+10
View File
@@ -0,0 +1,10 @@
#ifndef _RTP_H
#define _RTP_H
#include <sys/socket.h>
int rtp_setup(struct sockaddr *remote, int controlport, int timingport);
void rtp_shutdown(void);
void rtp_request_resend(seq_t first, seq_t last);
#endif // _RTP_H
+723
View File
@@ -0,0 +1,723 @@
#include <memory.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <signal.h>
#include <netdb.h>
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <openssl/md5.h>
#include "common.h"
#include "player.h"
#include "rtp.h"
// only one thread is allowed to use the player at once.
// it monitors the request variable (at least when interrupted)
static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER;
static int please_shutdown = 0;
static pthread_t playing_thread = 0;
typedef struct {
stream_cfg stream;
struct sockaddr remote;
} rtsp_conn_info;
static inline int rtsp_playing(void) {
return playing_thread == pthread_self();
}
static void rtsp_take_player(void) {
if (pthread_mutex_trylock(&playing_mutex)) {
debug("another thread is playing, shutting it down\n");
// XXX minor race condition between please_shutdown and signal delivery
please_shutdown = 1;
pthread_kill(playing_thread, SIGUSR1);
pthread_mutex_lock(&playing_mutex);
}
}
void rtsp_shutdown_stream(void) {
rtsp_take_player();
pthread_mutex_unlock(&playing_mutex);
}
// park a null at the line ending, and return the next line pointer
// accept \r, \n, or \r\n
static char *nextline(char *in, int inbuf) {
char *out = NULL;
while (inbuf) {
if (*in == '\r') {
*in++ = 0;
out = in;
}
if (*in == '\n') {
*in++ = 0;
out = in;
}
if (out)
break;
in++;
inbuf--;
}
return out;
}
typedef struct {
int nheaders;
char *name[16];
char *value[16];
int contentlength;
char *content;
// for requests
char method[16];
// for responses
int respcode;
} rtsp_message;
static rtsp_message * msg_init(void) {
rtsp_message *msg = malloc(sizeof(rtsp_message));
memset(msg, 0, sizeof(rtsp_message));
return msg;
}
static int msg_add_header(rtsp_message *msg, char *name, char *value) {
if (msg->nheaders >= sizeof(msg->name)/sizeof(char*)) {
warn("too many headers?!\n");
return 1;
}
msg->name[msg->nheaders] = strdup(name);
msg->value[msg->nheaders] = strdup(value);
msg->nheaders++;
return 0;
}
static char *msg_get_header(rtsp_message *msg, char *name) {
int i;
for (i=0; i<msg->nheaders; i++)
if (!strcasecmp(msg->name[i], name))
return msg->value[i];
return NULL;
}
static void msg_free(rtsp_message *msg) {
int i;
for (i=0; i<msg->nheaders; i++) {
free(msg->name[i]);
free(msg->value[i]);
}
if (msg->content)
free(msg->content);
free(msg);
}
static int msg_handle_line(rtsp_message **pmsg, char *line) {
rtsp_message *msg = *pmsg;
if (!msg) {
msg = msg_init();
*pmsg = msg;
char *sp, *p;
debug("received request: %s\n", line);
p = strtok_r(line, " ", &sp);
if (!p)
goto fail;
strncpy(msg->method, p, sizeof(msg->method)-1);
p = strtok_r(NULL, " ", &sp);
if (!p)
goto fail;
p = strtok_r(NULL, " ", &sp);
if (!p)
goto fail;
if (strcmp(p, "RTSP/1.0"))
goto fail;
return -1;
}
if (strlen(line)) {
char *p;
p = strstr(line, ": ");
if (!p) {
warn("bad header: >>%s<<\n", line);
goto fail;
}
*p = 0;
p += 2;
msg_add_header(msg, line, p);
debug(" %s: %s\n", line, p);
return -1;
} else {
char *cl = msg_get_header(msg, "Content-Length");
if (cl)
return atoi(cl);
else
return 0;
}
fail:
*pmsg = NULL;
msg_free(msg);
return 0;
}
static rtsp_message * rtsp_read_request(int fd) {
ssize_t buflen = 512;
char *buf = malloc(buflen+1);
rtsp_message *msg = NULL;
ssize_t nread;
ssize_t inbuf = 0;
int msg_size = -1;
while (msg_size < 0) {
if (please_shutdown) {
debug("RTSP shutdown requested\n");
goto shutdown;
}
nread = read(fd, buf+inbuf, buflen - inbuf);
if (!nread) {
debug("RTSP connection closed\n");
goto shutdown;
}
if (nread < 0) {
if (errno==EINTR)
continue;
perror("read failure");
goto shutdown;
}
inbuf += nread;
char *next;
while (msg_size < 0 && (next = nextline(buf, inbuf))) {
msg_size = msg_handle_line(&msg, buf);
if (!msg) {
warn("no RTSP header received\n");
goto shutdown;
}
inbuf -= next-buf;
if (inbuf)
memmove(buf, next, inbuf);
}
}
if (msg_size > buflen) {
buf = realloc(buf, msg_size);
if (!buf) {
warn("too much content");
goto shutdown;
}
buflen = msg_size;
}
while (inbuf < msg_size) {
nread = read(fd, buf+inbuf, msg_size-inbuf);
if (!nread)
goto shutdown;
if (nread==EINTR)
continue;
if (nread < 0) {
perror("read failure");
goto shutdown;
}
inbuf += nread;
}
msg->contentlength = inbuf;
msg->content = buf;
return msg;
shutdown:
free(buf);
if (msg) {
msg_free(msg);
}
return NULL;
}
static void msg_write_response(int fd, rtsp_message *resp) {
char rbuf[30];
int nrbuf;
nrbuf = snprintf(rbuf, sizeof(rbuf),
"RTSP/1.0 %d %s\r\n", resp->respcode,
resp->respcode==200 ? "OK" : "Error");
write(fd, rbuf, nrbuf);
debug("sending response: %s", rbuf);
int i;
for (i=0; i<resp->nheaders; i++) {
debug(" %s: %s\n", resp->name[i], resp->value[i]);
write(fd, resp->name[i], strlen(resp->name[i]));
write(fd, ": ", 2);
write(fd, resp->value[i], strlen(resp->value[i]));
write(fd, "\r\n", 2);
}
write(fd, "\r\n", 2);
}
static void handle_options(rtsp_conn_info *conn,
rtsp_message *req, rtsp_message *resp) {
resp->respcode = 200;
msg_add_header(resp, "Public",
"ANNOUNCE, SETUP, RECORD, "
"PAUSE, FLUSH, TEARDOWN, "
"OPTIONS, GET_PARAMETER, SET_PARAMETER");
}
static void handle_teardown(rtsp_conn_info *conn,
rtsp_message *req, rtsp_message *resp) {
if (!rtsp_playing())
return;
resp->respcode = 200;
msg_add_header(resp, "Connection", "close");
please_shutdown = 1;
}
static void handle_flush(rtsp_conn_info *conn,
rtsp_message *req, rtsp_message *resp) {
if (!rtsp_playing())
return;
player_flush();
resp->respcode = 200;
}
static void handle_setup(rtsp_conn_info *conn,
rtsp_message *req, rtsp_message *resp) {
playing_thread = pthread_self();
int cport, tport;
char *hdr = msg_get_header(req, "Transport");
if (!hdr)
return;
char *p;
p = strstr(hdr, "control_port=");
if (!p)
return;
p = strchr(p, '=') + 1;
cport = atoi(p);
p = strstr(hdr, "timing_port=");
if (!p)
return;
p = strchr(p, '=') + 1;
tport = atoi(p);
rtsp_take_player();
int sport = rtp_setup(&conn->remote, cport, tport);
if (!sport)
return;
player_play(&conn->stream);
char *resphdr = malloc(strlen(hdr) + 20);
strcpy(resphdr, hdr);
sprintf(resphdr + strlen(resphdr), ";server_port=%d", sport);
msg_add_header(resp, "Transport", resphdr);
resp->respcode = 200;
}
static void handle_ignore(rtsp_conn_info *conn,
rtsp_message *req, rtsp_message *resp) {
resp->respcode = 200;
}
static void handle_announce(rtsp_conn_info *conn,
rtsp_message *req, rtsp_message *resp) {
char *paesiv = NULL;
char *prsaaeskey = NULL;
char *pfmtp = NULL;
char *cp = req->content;
int cp_left = req->contentlength;
char *next;
while (cp) {
next = nextline(cp, cp_left);
cp_left -= next-cp;
if (!strncmp(cp, "a=fmtp:", 7))
pfmtp = cp+7;
if (!strncmp(cp, "a=aesiv:", 8))
paesiv = cp+8;
if (!strncmp(cp, "a=rsaaeskey:", 12))
prsaaeskey = cp+12;
cp = next;
}
if (!paesiv || !prsaaeskey || !pfmtp) {
warn("required params missing from announce\n");
return;
}
int len, keylen;
uint8_t *aesiv = base64_dec(paesiv, &len);
if (len != 16) {
warn("client announced aeskey of %d bytes, wanted 16\n", len);
free(aesiv);
return;
}
memcpy(conn->stream.aesiv, aesiv, 16);
free(aesiv);
uint8_t *rsaaeskey = base64_dec(prsaaeskey, &len);
uint8_t *aeskey = rsa_apply(rsaaeskey, len, &keylen, RSA_MODE_KEY);
free(rsaaeskey);
if (keylen != 16) {
warn("client announced rsaaeskey of %d bytes, wanted 16\n", keylen);
free(aeskey);
return;
}
memcpy(conn->stream.aeskey, aeskey, 16);
free(aeskey);
int i;
for (i=0; i<sizeof(conn->stream.fmtp)/sizeof(conn->stream.fmtp[0]); i++)
conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t"));
resp->respcode = 200;
}
static struct method_handler {
char *method;
void (*handler)(rtsp_conn_info *conn, rtsp_message *req,
rtsp_message *resp);
} method_handlers[] = {
{"OPTIONS", handle_options},
{"ANNOUNCE", handle_announce},
{"FLUSH", handle_flush},
{"TEARDOWN", handle_teardown},
{"SETUP", handle_setup},
{"GET_PARAMETER", handle_ignore},
{"SET_PARAMETER", handle_ignore}, // XXX
{"RECORD", handle_ignore},
{NULL, NULL}
};
static void apple_challenge(int fd, rtsp_message *req, rtsp_message *resp) {
char *hdr = msg_get_header(req, "Apple-Challenge");
if (!hdr)
return;
struct sockaddr fdsa;
socklen_t sa_len = sizeof(fdsa);
getsockname(fd, &fdsa, &sa_len);
int chall_len;
uint8_t *chall = base64_dec(hdr, &chall_len);
uint8_t buf[48], *bp = buf;
int i;
memset(buf, 0, sizeof(buf));
if (chall_len > 16) {
warn("oversized Apple-Challenge!\n");
free(chall);
return;
}
memcpy(bp, chall, chall_len);
free(chall);
bp += chall_len;
#ifdef AF_INET6
if (fdsa.sa_family == AF_INET6) {
struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)(&fdsa);
memcpy(bp, sa6->sin6_addr.s6_addr, 16);
bp += 16;
} else
#endif
{
struct sockaddr_in *sa = (struct sockaddr_in*)(&fdsa);
unsigned int ip = sa->sin_addr.s_addr;
for (i=0; i<4; i++) {
*bp++ = ip & 0xff;
ip >>= 8;
}
}
for (i=0; i<6; i++)
*bp++ = config.hw_addr[i];
int buflen, resplen;
buflen = bp-buf;
if (buflen < 0x20)
buflen = 0x20;
uint8_t *challresp = rsa_apply(buf, buflen, &resplen, RSA_MODE_AUTH);
char *encoded = base64_enc(challresp, resplen);
// strip the padding.
char *padding = strchr(encoded, '=');
if (padding)
*padding = 0;
msg_add_header(resp, "Apple-Response", encoded);
free(challresp);
free(encoded);
}
static char *make_nonce(void) {
uint8_t random[8];
int fd = open("/dev/random", O_RDONLY);
if (fd < 0)
die("could not open /dev/random!");
read(fd, random, sizeof(random));
close(fd);
return base64_enc(random, 8);
}
static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) {
if (!config.password)
return 0;
if (!*nonce) {
*nonce = make_nonce();
goto authenticate;
}
char *hdr = msg_get_header(req, "Authorization");
if (!hdr || strncmp(hdr, "Digest ", 7))
goto authenticate;
char *realm = strstr(hdr, "realm=\"");
char *username = strstr(hdr, "username=\"");
char *response = strstr(hdr, "response=\"");
char *uri = strstr(hdr, "uri=\"");
if (!realm || !username || !response || !uri)
goto authenticate;
char *quote;
realm = strchr(realm, '"') + 1;
if (!(quote = strchr(realm, '"')))
goto authenticate;
*quote = 0;
username = strchr(username, '"') + 1;
if (!(quote = strchr(username, '"')))
goto authenticate;
*quote = 0;
response = strchr(response, '"') + 1;
if (!(quote = strchr(response, '"')))
goto authenticate;
*quote = 0;
uri = strchr(uri, '"') + 1;
if (!(quote = strchr(uri, '"')))
goto authenticate;
*quote = 0;
uint8_t digest_urp[16], digest_mu[16], digest_total[16];
MD5_CTX ctx;
MD5_Init(&ctx);
MD5_Update(&ctx, username, strlen(username));
MD5_Update(&ctx, ":", 1);
MD5_Update(&ctx, realm, strlen(realm));
MD5_Update(&ctx, ":", 1);
MD5_Update(&ctx, config.password, strlen(config.password));
MD5_Final(digest_urp, &ctx);
MD5_Init(&ctx);
MD5_Update(&ctx, req->method, strlen(req->method));
MD5_Update(&ctx, ":", 1);
MD5_Update(&ctx, uri, strlen(uri));
MD5_Final(digest_mu, &ctx);
int i;
char buf[33];
for (i=0; i<16; i++)
sprintf(buf + 2*i, "%02X", digest_urp[i]);
MD5_Init(&ctx);
MD5_Update(&ctx, buf, 32);
MD5_Update(&ctx, ":", 1);
MD5_Update(&ctx, *nonce, strlen(*nonce));
MD5_Update(&ctx, ":", 1);
for (i=0; i<16; i++)
sprintf(buf + 2*i, "%02X", digest_mu[i]);
MD5_Update(&ctx, buf, 32);
MD5_Final(digest_total, &ctx);
for (i=0; i<16; i++)
sprintf(buf + 2*i, "%02X", digest_total[i]);
if (!strcmp(response, buf))
return 0;
warn("auth failed\n");
authenticate:
resp->respcode = 401;
int hdrlen = strlen(*nonce) + 40;
char *authhdr = malloc(hdrlen);
snprintf(authhdr, hdrlen, "Digest realm=\"taco\", nonce=\"%s\"", *nonce);
msg_add_header(resp, "WWW-Authenticate", authhdr);
free(authhdr);
return 1;
}
static void *rtsp_conversation_thread_func(void *vfd) {
// SIGUSR1 is used to interrupt this thread if blocked for read
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
pthread_sigmask(SIG_UNBLOCK, &set, NULL);
rtsp_conn_info conn;
memset(&conn, 0, sizeof(conn));
int fd = *(int*)vfd;
socklen_t slen = sizeof(conn.remote);
fd = accept(fd, &conn.remote, &slen);
if (fd < 0) {
perror("failed to accept connection");
goto shutdown;
}
rtsp_message *req, *resp;
char *hdr, *auth_nonce = NULL;
while ((req = rtsp_read_request(fd))) {
resp = msg_init();
resp->respcode = 400;
apple_challenge(fd, req, resp);
hdr = msg_get_header(req, "CSeq");
if (hdr)
msg_add_header(resp, "CSeq", hdr);
msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog");
if (rtsp_auth(&auth_nonce, req, resp))
goto respond;
struct method_handler *mh;
for (mh=method_handlers; mh->method; mh++) {
if (!strcmp(mh->method, req->method)) {
mh->handler(&conn, req, resp);
break;
}
}
respond:
msg_write_response(fd, resp);
msg_free(req);
msg_free(resp);
}
shutdown:
if (fd > 0)
close(fd);
if (rtsp_playing()) {
rtp_shutdown();
player_stop();
please_shutdown = 0;
pthread_mutex_unlock(&playing_mutex);
}
if (auth_nonce)
free(auth_nonce);
return NULL;
}
void rtsp_listen_loop(void) {
struct addrinfo hints, *info, *p;
char portstr[6];
int sockfd[2];
int nsock = 0;
int ret;
memset(sockfd, 0, sizeof(sockfd));
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
snprintf(portstr, 6, "%d", config.port);
ret = getaddrinfo(NULL, portstr, &hints, &info);
if (ret) {
die("getaddrinfo failed: %s\n", gai_strerror(ret));
}
for (p=info; p; p=p->ai_next) {
int fd = socket(p->ai_family, p->ai_socktype, IPPROTO_TCP);
int yes = 1;
ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
#ifdef AF_INET6
// some systems don't support v4 access on v6 sockets, but some do.
// since we need to account for two sockets we might as well always.
if (p->ai_family == AF_INET6)
ret |= setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes));
#endif
if (!ret)
ret = bind(fd, p->ai_addr, p->ai_addrlen);
if (ret) {
perror("could not bind a listen socket");
continue;
}
listen(fd, 5);
sockfd[nsock++] = fd;
}
freeaddrinfo(info);
if (!nsock)
die("could not bind any listen sockets!");
int maxfd = sockfd[0];
if (sockfd[1]>maxfd)
maxfd = sockfd[1];
fd_set fds;
FD_ZERO(&fds);
FD_SET(sockfd[0], &fds);
if (sockfd[1])
FD_SET(sockfd[1], &fds);
printf("Listening for connections.\n");
int acceptfd;
while (select(maxfd+1, &fds, 0, 0, 0) >= 0) {
if (FD_ISSET(sockfd[0], &fds))
acceptfd = sockfd[0];
if (FD_ISSET(sockfd[1], &fds) && sockfd[1])
acceptfd = sockfd[1];
// for now, we do not track these and let them die of natural causes.
// XXX: this leaks threads; they need to be culled with pthread_tryjoin_np.
// XXX: acceptfd could change before the thread is up. which should never happen, but still.
pthread_t rtsp_conversation_thread;
pthread_create(&rtsp_conversation_thread, NULL, rtsp_conversation_thread_func, &acceptfd);
FD_SET(sockfd[0], &fds);
if (sockfd[1])
FD_SET(sockfd[1], &fds);
}
perror("select");
die("fell out of the RTSP select loop\n");
}
+7
View File
@@ -0,0 +1,7 @@
#ifndef _RTSP_H
#define _RTSP_H
void rtsp_listen_loop(void);
void rtsp_shutdown_stream(void);
#endif // _RTSP_H
+41 -1171
View File
File diff suppressed because it is too large Load Diff
-57
View File
@@ -1,57 +0,0 @@
#ifndef __SHAIRPORT_H__
#define __SHAIRPORT_H__
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "socketlib.h"
#include <regex.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <regex.h>
#define HWID_SIZE 6
#define SHAIRPORT_LOG 1
#define LOG_INFO 1
#define LOG_DEBUG 5
#define LOG_DEBUG_V 6
#define LOG_DEBUG_VV 7
struct shairbuffer
{
char *data;
int current;
int maxsize;
int marker;
};
struct keyring
{
char *aeskey;
char *aesiv;
char *fmt;
};
struct comms
{
int in[2];
int out[2];
};
struct connection
{
struct shairbuffer recv;
struct shairbuffer resp;
struct keyring *keys; // Does not point to malloc'd memory.
struct comms *hairtunes;
int clientSocket;
char *password;
};
void sim(int pLevel, char *pValue1, char *pValue2);
#endif
-67
View File
@@ -1,67 +0,0 @@
#!/bin/bash
#
# This starts and stops shairport
#
### BEGIN INIT INFO
# Provides: shairport
# Required-Start: $network
# Required-Stop:
# Short-Description: shairport - Airtunes emulator!
# Description: Airtunes emulator!
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
### END INIT INFO
# Source function library.
. /lib/lsb/init-functions
NAME=ShairPort
DAEMON="/usr/local/bin/shairport.pl"
PIDFILE=/var/run/$NAME.pid
DAEMON_ARGS="-w $PIDFILE -a $NAME"
[ -x $binary ] || exit 0
RETVAL=0
start() {
echo -n "Starting shairport: "
start-stop-daemon --start --quiet --pidfile "$PIDFILE" \
--exec "$DAEMON" -b --oknodo -- $DAEMON_ARGS
log_end_msg $?
}
stop() {
echo -n "Shutting down shairport: "
start-stop-daemon --stop --quiet --pidfile "$PIDFILE" \
--retry 1 --oknodo
log_end_msg $?
}
restart() {
stop
sleep 1
start
}
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status shairport
;;
restart)
restart
;;
*)
echo "Usage: $0 {start|stop|status|restart}"
;;
esac
exit 0
-763
View File
@@ -1,763 +0,0 @@
#!/usr/bin/env perl
# ShairPort - Airtunes compatible server
# Copyright (c) 2011 James Laird
# All rights reserved.
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
use strict;
use warnings;
use 5.10.0;
# For given() { when() { } ... }
use feature ":5.10";
use Getopt::Long;
use FindBin;
use File::Basename;
use IO::Select;
use IO::Socket;
use MIME::Base64;
use HTTP::Request;
use HTTP::Response;
use URI::Escape;
use IPC::Open2;
use Crypt::OpenSSL::RSA;
use Digest::MD5 qw(md5 md5_hex);
use POSIX qw(:sys_wait_h setsid);
eval "use IO::Socket::INET6;";
use Net::SDP;
my $shairportversion = "0.05";
my $apname = "ShairPort $$ on " . `hostname`;
my $port = 5002;
# password - required to connect
# for no password, set:
my $password = '';
# output to a pipe?
my $pipepath;
# detach
my $daemon;
# ao options
my $libao_driver;
my $libao_devicename;
my $libao_deviceid;
# suppose hairtunes is under same directory
my $hairtunes_cli = $FindBin::Bin . '/hairtunes';
# Integrate with Squeezebox Server
my $squeeze;
# SBS CLI port
my $cliport;
# SB target
my $mac;
# SB volume
my $volume;
# custom play and stop program
my $play_prog;
my $stop_prog;
# output debugging information
my $verbose;
# where to write PID
my $writepid;
# show help
my $help;
my $ipv4_only;
unless (-x $hairtunes_cli) {
say "Can't find the 'hairtunes' decoder binary, you need to build this before using ShairPort.";
say "Read the INSTALL instructions!";
exit(1);
}
GetOptions("a|apname=s" => \$apname,
"p|password=s" => \$password,
"o|server_port=s" => \$port,
"i|pipe=s" => \$pipepath,
"d" => \$daemon,
"ao_driver=s" => \$libao_driver,
"ao_devicename=s" => \$libao_devicename,
"ao_deviceid=s" => \$libao_deviceid,
"v|verbose" => \$verbose,
"4" => \$ipv4_only,
"w|writepid=s" => \$writepid,
"s|squeezebox" => \$squeeze,
"c|cliport=s" => \$cliport,
"m|mac=s" => \$mac,
"play_prog=s" => \$play_prog,
"stop_prog=s" => \$stop_prog,
"l|volume=s" => \$volume,
"h|help" => \$help);
sub usage {
print "ShairPort version $shairportversion - Airport Express emulator\n".
"Usage:\n".
basename($0) . " [OPTION...]\n".
"\n".
"Options:\n".
" -a, --apname=AirPort Sets AirPort name\n".
" -p, --password=secret Sets password\n",
" -o, --server_port=5002 Sets Port for Avahi/dns-sd/howl\n",
" -i, --pipe=pipepath Sets the path to a named pipe for output\n",
" --ao_driver=driver Sets the ao driver (optional)\n",
" --ao_devicename=devicename Sets the ao device name (optional)\n",
" --ao_deviceid=id Sets the ao device id (optional)\n",
" -s --squeezebox Enables local Squeezebox Server integration\n",
" -c --cliport=port Sets the SBS CLI port\n",
" -m --mac=address Sets the SB target device\n",
" -l --volume=level Sets the SB volume level (in %)\n",
" --play_prog=cmdline Program to start on 1st connection\n",
" --stop_prog=cmdline Program to start on last disconnection\n",
" -d Daemon mode\n",
" -w --writepid=path Write PID to this location\n",
" -v --verbose Print debugging messages\n",
" -h, --help This help\n",
"\n";
exit;
}
if (defined($help) && $help == 1) { usage(); }
# ensure that $verbose is set, one way or another...
if (defined($verbose) && $verbose) {
$verbose = 1;
} else {
$verbose = 0;
}
$cliport = ( (defined( $cliport ) && $cliport && $cliport =~ m/^[0-9]+/ && $cliport > 1023 && $cliport < 65536 ) ? $cliport : 9090 );
$volume = ( (defined( $volume ) && $volume && $volume =~ m/^[0-9]+/ && $volume >= 0 && $volume <= 101 ) ? $volume : undef );
if (defined($squeeze) && $squeeze) {
my $players;
my @details;
my $response;
my $socket = IO::Socket::INET -> new (
PeerAddr => "127.0.0.1"
, PeerPort => $cliport
, Proto => 'tcp'
, Timeout => 1
);
if( !( $socket ) ) {
print "ERROR: Could not create socket to interface with SqueezeBox Server on port $cliport: $!\n";
print "WARN: Disabling Squeezebox Server integration\n";
undef $squeeze;
}
if( $squeeze ) {
print $socket "player count ?\n";
$response = <$socket>;
if( !( defined( $response ) ) ) {
print "ERROR: Could not communicate with SqueezeBox Server on port $cliport\n";
print "WARN: Disabling Squeezebox Server integration\n";
undef $squeeze;
} else {
( $players ) = ( $response =~ m/^player count ([0-9]+)$/ );
print $socket "players 0 $players\n";
$response = <$socket>;
@details = split( /playerindex%3A/ , $response );
close( $socket );
shift( @details );
for( my $n = 0; $n <= scalar( @details ); $n++ ) {
if( defined( $details[ $n ] ) ) {
my $address = $details[ $n ];
$address =~ s/^.*playerid%3A([[:xdigit:]%]+)\s.*$/$1/;
$address =~ s/%3A/:/g;
chomp $address;
$details[ $n ] = $address;
}
}
print "Discovered players: $players\n" if $verbose;
print "Player MAC addresses:\n" if $verbose;
foreach my $address (@details) {
print "\t$address\n" if( ( defined( $address ) && $address ) && $verbose );
}
if( defined( $mac ) && $mac ) {
chomp $mac;
if( !( grep { lc( $_ ) eq lc( $mac ) } @details ) ) {
print "ERROR: Invalid or non-present MAC specified.\n\n";
print "Please select a target MAC address from:\n";
foreach my $address (@details) {
print "\t$address\n" if( defined( $address ) && $address );
}
exit(1);
}
} else {
if( 1 == $players ) {
$mac = $details[ 0 ];
print "WARN: No Squeezebox player specified, using $mac.\n";
} else {
print "ERROR: No Squeezebox player specified, please select a target MAC address with the '--mac' option with a value from:\n";
foreach my $address (@details) {
print "\t$address\n" if( defined( $address ) && $address );
}
exit(1);
}
}
$mac = uri_escape( $mac ) if( defined( $mac ) && $mac );
}
}
};
chomp $apname;
my @hw_addr = +(map(ord, split(//, md5($apname))))[0..5];
sub POPE {
print "Broken pipe\n" if $verbose;
$SIG{PIPE} = \&POPE;
}
$SIG{PIPE} = \&POPE;
our $avahi_publish;
our $squeezebox_setup;
sub REAP {
my $pid = waitpid( -1, WNOHANG );
given( $pid ) {
when( $avahi_publish ) {
die( "avahi daemon terminated or 'avahi-publish-service' binary not found" );
}
when( $squeezebox_setup ) {
print( "Squeezebox configuration routine completed\n" ) if $verbose;
}
}
print("Child exited\n") if $verbose;
$SIG{CHLD} = \&REAP;
};
$SIG{CHLD} = \&REAP;
my %conns;
$SIG{TERM} = $SIG{INT} = sub {
print basename($0) . " killed\n";
map { eval { kill $_->{decoder_pid} } } keys %conns;
kill 9, $avahi_publish if $avahi_publish;
# Clean up any running squeezebox_setup processes...
my $child;
do {
$child = waitpid( -1, WNOHANG );
} while $child > 0;
exit 0;
};
$SIG{__DIE__} = sub {
map { eval { kill $_->{decoder_pid} } } keys %conns;
kill 9, $avahi_publish if $avahi_publish;
# Clean up any running squeezebox_setup processes...
my $child;
do {
$child = waitpid( -1, WNOHANG );
} while $child > 0;
};
$avahi_publish = fork();
my $pw_clause = (length $password) ? "pw=true" : "pw=false";
if ($avahi_publish==0) {
{ exec 'avahi-publish-service',
join('', map { sprintf "%02X", $_ } @hw_addr) . "\@$apname",
"_raop._tcp",
$port,
"tp=UDP","sm=false","sv=false","ek=1","et=0,1","cn=0,1","ch=2","ss=16","sr=44100",$pw_clause,"vn=3","txtvers=1"; };
{ exec 'dns-sd', '-R',
join('', map { sprintf "%02X", $_ } @hw_addr) . "\@$apname",
"_raop._tcp",
".",
$port,
"tp=UDP","sm=false","sv=false","ek=1","et=0,1","cn=0,1","ch=2","ss=16","sr=44100",$pw_clause,"vn=3","txtvers=1"; };
{ exec 'mDNSPublish',
join('', map { sprintf "%02X", $_ } @hw_addr) . "\@$apname",
"_raop._tcp",
$port,
"tp=UDP","sm=false","sv=false","ek=1","et=0,1","cn=0,1","ch=2","ss=16","sr=44100",$pw_clause,"vn=3","txtvers=1"; };
die "could not run avahi-publish-service nor dns-sd nor mDNSPublish";
}
my $airport_pem = join '', <DATA>;
my $rsa = Crypt::OpenSSL::RSA->new_private_key($airport_pem) || die "RSA private key import failed";
my $listen;
{
if (!defined($ipv4_only)) {
eval {
local $SIG{__DIE__};
$listen = new IO::Socket::INET6(Listen => 1,
Domain => AF_INET6,
LocalPort => $port,
ReuseAddr => 1,
Proto => 'tcp');
};
if ($@) {
print "**************************************\n",
"* IO::Socket::INET6 not present! *\n",
"* Install this if iTunes won't play. *\n",
"**************************************\n\n";
}
}
$listen ||= new IO::Socket::INET(Listen => 1,
LocalPort => $port,
ReuseAddr => 1,
Proto => 'tcp');
}
die "Can't listen on port " . $port . ": $!" unless $listen;
sub ip6bin {
my $ip = shift;
$ip =~ /((.*)::)?(.+)/;
my @left = split /:/, $2;
my @right = split /:/, $3;
my @mid;
my $pad = 8 - ($#left + $#right + 2);
if ($pad > 0) {
@mid = (0) x $pad;
}
pack('S>*', map { hex } (@left, @mid, @right));
}
my $sel = new IO::Select($listen);
if ($daemon) {
chdir "/" or die "Could not chdir to '/': $!";
umask 0;
open STDIN, "/dev/null" or die "Could not redirect /dev/null to STDIN(0): $!";
open STDOUT, ">/dev/null" or die "Could not redirect STDOUT(1) to /dev/null: $!";
defined( my $pid = fork() ) or die "Could not fork: $!";
exit 0 if $pid;
setsid() or die "Could not start new session: $!";
open STDERR, ">&STDOUT" or die "Could not dup STDOUT(1)";
}
if (defined($writepid) && $writepid) {
open PID, ">$writepid" or die "Could not create PID file '$writepid': $!";
print PID $$;
close PID;
}
print "Listening...\n" if $verbose;
sub performSqueezeboxSetup {
$squeezebox_setup = fork();
if( 0 == $squeezebox_setup ) {
my $items;
my @favourites;
my @ids;
my $index;
my $response;
my $findFavourites = sub {
my ( $socket ) = @_;
print $socket "favorites items\n";
$response = <$socket>;
$response =~ s/^\s*favorites\s+items\s+count%3A([0-9]+)\s*$/$1/;
$items = $response;
print "Found $items favourites...\n" if $verbose;
print $socket "favorites items 0 $items want_url%3A1\n";
$response = <$socket>;
undef( @favourites );
@favourites = split( /id%3A/ , $response );
@ids = split( /id%3A/ , $response );
shift( @favourites );
shift( @ids );
for( my $n = 0; $n <= scalar( @favourites ); $n++ ) {
if( defined( $favourites[ $n ] ) ) {
my $url = $favourites[ $n ];
$url =~ s/^.*url%3A([^ ]+)\s.*$/$1/;
$url = uri_unescape( $url );
chomp $url;
print "\tFavourite with URL '$url' " if $verbose;
$favourites[ $n ] = $url;
}
if( defined( $ids[ $n ] ) ) {
my $id = $ids[ $n ];
$id =~ s/^([^ ]+)\s.*$/$1/;
chomp $id;
my @components = split( /\./, $id );
shift ( @components );
$id = join( '.', @components );
print "and ID '$id'\n" if $verbose;
$ids[ $n ] = $id;
}
}
};
my $socket = IO::Socket::INET -> new (
PeerAddr => "127.0.0.1"
, PeerPort => ( (defined( $cliport ) and $cliport ) ? $cliport : 9090 )
, Proto => 'tcp'
, Timeout => 1
) or die "Could not create socket: $!";
&$findFavourites( $socket );
print "Favourites URLs:\n" if $verbose;
foreach my $url (@favourites) {
print "\t$url\n" if( ( defined( $url ) && $url ) && $verbose );
}
my $okay = 1;
if( !( grep { $_ =~ m/^wavin:/ } @favourites ) ) {
$okay = 0;
print "INFO: AirPlay 'wavin' Favourite does not exist - creating... ";
print $socket "favorites add url%3Awavin%3Aairplay title%3AAirPlay\n";
$response = <$socket>;
if( $response =~ m/\scount%3A1/ ) {
print "done\nINFO: AirPlay 'wavin' favourite successfully created.\n";
$okay = 1;
} else {
print "failed\nWARN: Could not create AirPlay favourite\n";
print " Server response was $response\n";
}
if( $okay ) {
&$findFavourites( $socket );
print "Updated Favourites URLs:\n" if $verbose;
foreach my $url (@favourites) {
print "\t$url\n" if( ( defined( $url ) && $url ) && $verbose );
}
if( !( grep { $_ =~ m/^wavin:/ } @favourites ) ) {
print "WARN: Cloud not identify AirPlay Favourite, even after creating it - disabling SqueezeBox integration\n";
$squeeze = 0;
$okay = 0;
}
}
}
if( $okay ) {
for ( my $n = 0 ; !( defined( $index ) ) && $n <= scalar( @favourites ) ; $n++ ) {
if( $favourites[ $n ] =~ m/^wavin:/ ) {
$index = $n;
print "Found favourite '" . $favourites[ $index ] . "' with ID '" . $ids[ $index ] . "' at position $index.\n" if $verbose;
}
}
print "Turning on player (if off)... " if $verbose;
print $socket "$mac power 1\n";
$response = <$socket>;
print "$response\n" if $verbose;
print "Stopping player (if playing)... " if $verbose;
print $socket "$mac stop\n";
$response = <$socket>;
print "$response\n" if $verbose;
print "Unmuting player (if muted)... " if $verbose;
print $socket "$mac mixer muting 0\n";
$response = <$socket>;
print "$response\n" if $verbose;
if( defined( $volume ) ) {
print "Setting player volume to $volume... " if $verbose;
print $socket "$mac mixer volume $volume\n";
$response = <$socket>;
print "$response\n" if $verbose;
}
print "Showing message... " if $verbose;
print $socket "$mac show line2%3AStarting%20AirPlay duration%3A5 brightness%3ApowerOn font%3Ahuge\n";
$response = <$socket>;
print "$response\n" if $verbose;
if( defined( $index ) ) {
print "Playing favourite... " if $verbose;
my $id = uri_escape( $ids[ $index ] );
print $socket "$mac favorites playlist play item_id%3A$id\n";
} else {
print "Resuming play... " if $verbose;
print $socket "$mac play\n";
}
$response = <$socket>;
print "$response\n" if $verbose;
}
close( $socket );
exit(0);
}
};
while (1) {
printf "about to select\n" if $verbose;
my @waiting = $sel->can_read;
foreach my $fh (@waiting) {
if ($fh==$listen) {
my $new = $listen->accept;
printf "New connection from %s\n", $new->peerhost if $verbose;
$sel->add($new);
$new->blocking(0);
$conns{$new} = {fh => $fh};
if (defined($squeeze) && $squeeze) {
&performSqueezeboxSetup();
}
# the 2nd connection is a player connection
if (defined($play_prog) && $sel->count() == 2) {
printf "play prog: $play_prog\n" if ($verbose);
system($play_prog);
}
} else {
if (eof($fh)) {
print "Closed: $fh\n" if $verbose;
$sel->remove($fh);
close $fh;
# Prevent warnings when decoder_pid isn't defined
# (e.g. client connected, but playback not started)
if (defined($conns{$fh}{decoder_pid})) {
eval { kill $conns{$fh}{decoder_pid} };
}
delete $conns{$fh};
# 1 connection means no connection
if (defined($stop_prog) && $sel->count() == 1) {
system($stop_prog);
}
next;
}
if (exists $conns{$fh}) {
conn_handle_data($fh);
}
}
}
}
exit(1); # Unreachable
sub conn_handle_data {
my $fh = shift;
my $conn = $conns{$fh};
printf "handle data 1\n" if ($verbose);
if ($conn->{req_need}) {
if (length($conn->{data}) >= $conn->{req_need}) {
$conn->{req}->content(substr($conn->{data}, 0, $conn->{req_need}, ''));
conn_handle_request($fh, $conn);
}
undef $conn->{req_need};
return;
}
read $fh, my $data, 4096;
$conn->{data} .= $data;
if ($conn->{data} =~ /(\r\n\r\n|\n\n|\r\r)/) {
my $req_data = substr($conn->{data}, 0, $+[0], '');
$conn->{req} = HTTP::Request->parse($req_data);
printf "REQ: %s\n", $conn->{req}->method if $verbose;
conn_handle_request($fh, $conn);
conn_handle_data($fh) if length($conn->{data});
}
}
sub digest_ok {
my ($req, $conn) = @_;
my $authz = $req->header('Authorization');
return 0 unless $authz =~ s/^Digest\s+//i;
return 0 unless length $conn->{nonce};
my @authz = split /,\s*/, $authz;
my %authz = map { /(.+)="(.+)"/; ($1, $2) } @authz;
# not a standard digest - uses capital hex digits, in conflict with the RFC
my $digest = uc md5_hex (
uc(md5_hex($authz{username} . ':' . $authz{realm} . ':' . $password))
. ':' . $authz{nonce} . ':' .
uc(md5_hex($req->method . ':' . $authz{uri}))
);
return $digest eq $authz{response};
}
sub conn_handle_request {
my ($fh, $conn) = @_;
my $req = $conn->{req};;
my $clen = $req->header('content-length') // 0;
if ($clen > 0 && !length($req->content)) {
$conn->{req_need} = $clen;
return; # need more!
}
my $resp = HTTP::Response->new(200);
$resp->request($req);
$resp->protocol($req->protocol);
$resp->header('CSeq', $req->header('CSeq'));
$resp->header('Audio-Jack-Status', 'connected; type=analog');
if (my $chall = $req->header('Apple-Challenge')) {
my $data = decode_base64($chall);
my $ip = $fh->sockhost;
if ($ip =~ /((\d+\.){3}\d+)$/) { # IPv4
$data .= join '', map { chr } split(/\./, $1);
} else {
$data .= ip6bin($ip);
}
$data .= join '', map { chr } @hw_addr;
$data .= chr(0) x (0x20-length($data));
$rsa->use_pkcs1_padding; # this isn't hashed before signing
my $signature = encode_base64 $rsa->private_encrypt($data), '';
$signature =~ s/=*$//;
$resp->header('Apple-Response', $signature);
}
if (length $password) {
if (!digest_ok($req, $conn)) {
my $nonce = md5_hex(map { rand } 1..20);
$conn->{nonce} = $nonce;
$resp->header('WWW-Authenticate', "Digest realm=\"$apname\", nonce=\"$nonce\"");
$resp->code(401);
$req->method('DENIED');
}
}
for ($req->method) {
/^OPTIONS$/ && do {
$resp->header('Public', 'ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER');
last;
};
/^ANNOUNCE$/ && do {
my $sdp = Net::SDP->new($req->content);
my $audio = $sdp->media_desc_of_type('audio');
print $audio->as_string();
print $audio->attribute('aesiv');
die("no AESIV") unless my $aesiv = decode_base64($audio->attribute('aesiv'));
die("no AESKEY") unless my $rsaaeskey = decode_base64($audio->attribute('rsaaeskey'));
$rsa->use_pkcs1_oaep_padding;
my $aeskey = $rsa->decrypt($rsaaeskey) || die "RSA decrypt failed";
$conn->{aesiv} = $aesiv;
$conn->{aeskey} = $aeskey;
$conn->{fmtp} = $audio->attribute('fmtp');
last;
};
/^SETUP$/ && do {
my $transport = $req->header('Transport');
$transport =~ s/;control_port=(\d+)//;
my $cport = $1;
$transport =~ s/;timing_port=(\d+)//;
my $tport = $1;
$transport =~ s/;server_port=(\d+)//;
my $dport = $1;
$resp->header('Session', 'DEADBEEF');
my %dec_args = (
iv => unpack('H*', $conn->{aesiv}),
key => unpack('H*', $conn->{aeskey}),
fmtp => $conn->{fmtp},
cport => $cport,
tport => $tport,
dport => $dport,
# host => 'unused',
);
$dec_args{pipe} = $pipepath if defined $pipepath;
$dec_args{ao_driver} = $libao_driver if defined $libao_driver;
$dec_args{ao_devicename} = $libao_devicename if defined $libao_devicename;
$dec_args{ao_deviceid} = $libao_deviceid if defined $libao_deviceid;
my $dec = '"' . $hairtunes_cli . '"' . join(' ', '', map { sprintf "%s '%s'", $_, $dec_args{$_} } keys(%dec_args));
if ($ipv4_only) {
$dec .= " ipv4_only";
}
print "decode command: $dec\n" if ($verbose);
my $decoder = open2(my $dec_out, my $dec_in, $dec);
$conn->{decoder_pid} = $decoder;
$conn->{decoder_fh} = $dec_in;
my $portdesc = <$dec_out>;
die("Expected port number from decoder; got $portdesc") unless $portdesc =~ /^port: (\d+)/;
my $port = $1;
print "launched decoder: $decoder on port: $port\n" if $verbose;
$resp->header('Transport', $req->header('Transport') . ";server_port=$port");
last;
};
/^RECORD$/ && last;
/^FLUSH$/ && do {
my $dfh = $conn->{decoder_fh};
print $dfh "flush\n";
last;
};
/^TEARDOWN$/ && do {
$resp->header('Connection', 'close');
close $conn->{decoder_fh};
last;
};
/^SET_PARAMETER$/ && do {
my @lines = split /[\r\n]+/, $req->content;
printf("SET_PARAMETER req: " . $req->content . "\n") if ($verbose);
my %content = map { /^(\S+): (.+)/; (lc $1, $2) } @lines;
my $cfh = $conn->{decoder_fh};
if (exists $content{volume}) {
printf("sending-> vol: %f\n", $content{volume}) if ($verbose);
printf $cfh "vol: %f\n", $content{volume};
} else {
printf("unable to perform content for req: " . $req->content . "\n") if ($verbose);
}
last;
};
/^GET_PARAMETER$/ && last;
/^DENIED$/ && last;
die("Unknown method: $_");
}
printf("%s", $resp->as_string("\r\n")) if ($verbose);
print $fh $resp->as_string("\r\n");
$fh->flush;
}
__DATA__
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt
wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U
wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf
/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/
UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW
BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa
LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5
NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm
lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz
aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu
a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM
oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z
oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+
k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL
AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA
cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf
54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov
17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc
1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI
LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ
2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=
-----END RSA PRIVATE KEY-----
-277
View File
@@ -1,277 +0,0 @@
/*
* Socket Library - Common socket functions, based on stevens networking book.
* Copyright (c) M. Andrew Webster 2011
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include "socketlib.h"
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
int common_setup(struct addrinfo *pAddrInfo)
{
int tSock;
//printAddrs(pAddrInfo);
tSock = socket(pAddrInfo->ai_family, pAddrInfo->ai_socktype, 0);
#ifdef AF_INET6
if((tSock==-1) && (pAddrInfo->ai_family == AF_INET6) && (errno == EAFNOSUPPORT))
{
//Fallback to ipv4
perror("Failed to create ipv6 socket. Trying ipv4");
pAddrInfo->ai_family = AF_INET;
tSock = socket(pAddrInfo->ai_family, pAddrInfo->ai_socktype, 0);
}
#endif
return tSock;
}
int setup_client(struct addrinfo *server_host)
{
int tSockDesc = -1;
int tIdx = 0;
while(tIdx++ < RETRY_COUNT)
{
tSockDesc = common_setup(server_host);
if (tSockDesc < 0 && tIdx >= RETRY_COUNT)
{
perror("Error: Could not create socket");
return ERROR;
}
if (connect(tSockDesc, server_host->ai_addr, server_host->ai_addrlen) >= 0)
{
return tSockDesc;
}
else
{
close(tSockDesc);
perror("Error: Could not connect to server");
struct timeval tRes;
delay(RETRY_DELAY, &tRes);
}
}
printf("%d Retry attempts exceeded\n", RETRY_COUNT);
return ERROR;
}
int getAddr(char *pHostname, char *pService, int pFamily, int pSockType, struct addrinfo **pAddrInfo)
{
struct addrinfo hints;
int tError = 0;
memset(&hints, 0 , sizeof(hints));
hints.ai_family = pFamily;
hints.ai_socktype = pSockType;
if(pHostname == NULL)
{
hints.ai_flags = AI_PASSIVE;
}
tError = getaddrinfo(pHostname, pService, &hints, pAddrInfo);
if(tError != 0)
{
printf("Error getting address info\n");
}
return tError;
}
int setup_server(struct addrinfo *server_addr)
{
int tSock = common_setup(server_addr);
if (tSock < 0)
{
perror("Error: Could not create server socket");
return ERROR;
}
int tEnable = 1;
setsockopt(tSock, SOL_SOCKET, SO_REUSEADDR, &tEnable, sizeof (tEnable));
if (bind(tSock, server_addr->ai_addr, server_addr->ai_addrlen) < 0)
{
close(tSock);
perror("Error: Could not bind socket");
return ERROR;
}
if (listen(tSock, 5) < 0)
{
close(tSock);
perror("Error: Unable to listen on server socket");
return ERROR;
}
return tSock;
}
int acceptClient(int pSock, struct addrinfo *server_addr)
{
int tAccept = accept(pSock, server_addr->ai_addr, &server_addr->ai_addrlen);
// close the listen socket. Not expecting any more clients.
if (tAccept < 0)
{
perror("Error: Unable to accept connection to server socket");
return ERROR;
}
else
{
//printf("..Accepted on socket: %d\n", tAccept);
}
return tAccept;
}
int setupListenServer(struct addrinfo **pAddrInfo, int pPort)
{
char tService[SERVLEN];
sprintf(tService, "%d", pPort); // copies port to string
int tFamily = AF_INET;
#ifdef AF_INET6
//printf("Listening on IPv6 Socket\n");
tFamily = AF_INET6;
#else
//printf("Listening on IPv4 Socket");
#endif
if(getAddr(NULL, tService, tFamily, SOCK_STREAM, pAddrInfo))
{
return ERROR; // getAddr prints out error message
}
int tSocketDescriptor = setup_server(*pAddrInfo);
char tAddr[INET6_ADDRSTRLEN];
socklen_t tSize = INET6_ADDRSTRLEN;
inet_ntop((*pAddrInfo)->ai_family, (*pAddrInfo)->ai_addr, tAddr, tSize);
//printf("Size is: %d\n", tSize);
return tSocketDescriptor;
}
void delay(long pMillisecs, struct timeval *pRes)
{
pRes->tv_sec = pMillisecs / 1000;
pRes->tv_usec = (pMillisecs - (pRes->tv_sec * 1000)) * 1000;
select(0,NULL,NULL,NULL,pRes);
}
static int getCorrectedEncodeSize(int pSize)
{
if(pSize % 4 == 0)
{
return pSize;
}
else if((pSize + 1) % 4 == 0)
{
return pSize+1;
}
else if((pSize + 2) % 4 == 0)
{
return pSize+2;
}
else
{
// Invalid encoded data, no other cases are possible.
printf("Unrecoverable error....base64 values are incorrectly encoded\n");
return pSize;
}
}
// From http://www.ioncannon.net/programming/34/howto-base64-encode-with-cc-and-openssl/
//int main(int argc, char **argv)
//{
// char *output = decode_base64("WU9ZTyEA\n\0", strlen("WU9ZTyEA\n\0"));
// printf("Unbase64: *%s*\n", output);
// free(output);/
//}
char *decode_base64(unsigned char *pInput, int pLength, int *pActualLength)
{
// Needs All NO_NL flags for proper RSA AES KEY Descrypt
BIO *b64, *bmem;
unsigned char *input = pInput;
int length = getCorrectedEncodeSize(pLength);
if(pLength != length)
{
input = malloc(length * sizeof(unsigned char));
memset(input, 0, length);
memcpy(input, pInput, pLength);
memset(input+pLength, '=', length-pLength);
printf("Fixed value: [%.*s]\n", length, input);
}
char *buffer = (char *)malloc(length);
memset(buffer, 0, length);
b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bmem = BIO_new_mem_buf(input, length);
BIO_set_flags(bmem, BIO_FLAGS_BASE64_NO_NL);
bmem = BIO_push(b64, bmem);
BIO_set_flags(bmem, BIO_FLAGS_BASE64_NO_NL);
*pActualLength = BIO_read(bmem, buffer, length);
BIO_free_all(bmem);
if(pLength != length)
{
free(input);
}
return buffer;
}
char *encode_base64(unsigned char *input, int length)
{
BIO *bmem, *b64;
BUF_MEM *bptr;
b64 = BIO_new(BIO_f_base64());
// This enables/disables nls
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bmem = BIO_new(BIO_s_mem());
b64 = BIO_push(b64, bmem);
BIO_write(b64, input, length);
(void)BIO_flush(b64);
BIO_get_mem_ptr(b64, &bptr);
char *buff = (char *)malloc(bptr->length);
memcpy(buff, bptr->data, bptr->length-1);
buff[bptr->length-1] = 0;
BIO_free_all(b64);
return buff;
}
-39
View File
@@ -1,39 +0,0 @@
/* Name: M. Andrew Webster
* Based on stevens networking book
* Description: function prototypes for socket library
*/
#ifndef _SOCKETLIB_H
#define _SOCKETLIB_H
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#define MAX_SIZE 8192
#define PORT 5002
#define RETRY_COUNT 7
#define SERVLEN 80
#define RETRY_DELAY 1000
#define ERROR -1
#define DEFAULT_IP "127.0.0.1"
#define DEFAULT_UNIX "/unix"
int setup_client(struct addrinfo *server_info);
int setup_server(struct addrinfo *server_address);
int setupListenServer(struct addrinfo **pAddrInfo, int pPort);
int acceptClient(int pSock, struct addrinfo *server_addr);
void delay(long pMillisecs, struct timeval *pRes);
int getAddr(char *pHostname, char *pService, int pFamily, int pSockType, struct addrinfo **pAddrInfo);
// All calls to decode and encode need to be freed
char *decode_base64(unsigned char *input, int length, int *tActualLength);
// All calls to decode and encode need to be freed
char *encode_base64(unsigned char *input, int length);
#endif