From 2340bcd3ce772d058bbeddcadd7a8301f7b18747 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Juho=20V=C3=A4h=C3=A4-Herttua?= Date: Fri, 9 Mar 2012 14:10:51 +0200 Subject: [PATCH] Initial commit to the repository --- .gitignore | 7 + AirTV-Qt/AirTV.icns | Bin 0 -> 153031 bytes AirTV-Qt/AirTV.ico | Bin 0 -> 17897 bytes AirTV-Qt/AirTV.pro | 104 + AirTV-Qt/AirTV.qrc | 5 + AirTV-Qt/AirTV.rc | 1 + AirTV-Qt/audiooutput.cpp | 184 ++ AirTV-Qt/audiooutput.h | 51 + AirTV-Qt/images/airtv.svg | 98 + AirTV-Qt/main.cpp | 30 + AirTV-Qt/mainapplication.cpp | 46 + AirTV-Qt/mainapplication.h | 36 + AirTV-Qt/mainwindow.ui | 24 + AirTV-Qt/qtsingleapplication/INSTALL.TXT | 254 +++ AirTV-Qt/qtsingleapplication/README.TXT | 33 + .../qtsingleapplication/buildlib/buildlib.pro | 13 + AirTV-Qt/qtsingleapplication/common.pri | 6 + AirTV-Qt/qtsingleapplication/configure | 25 + AirTV-Qt/qtsingleapplication/configure.bat | 39 + .../qtsingleapplication/doc/html/classic.css | 284 +++ .../doc/html/images/qt-logo.png | Bin 0 -> 4075 bytes .../qtsingleapplication/doc/html/index.html | 48 + .../qtsingleapplication-example-loader.html | 177 ++ .../qtsingleapplication-example-trivial.html | 103 + .../doc/html/qtsingleapplication-members.html | 235 +++ .../html/qtsingleapplication-obsolete.html | 31 + .../doc/html/qtsingleapplication.dcf | 40 + .../doc/html/qtsingleapplication.html | 162 ++ .../doc/html/qtsingleapplication.index | 90 + .../doc/html/qtsingleapplication.qhp | 53 + ...singlecoreapplication-example-console.html | 120 ++ .../html/qtsinglecoreapplication-members.html | 126 ++ .../doc/html/qtsinglecoreapplication.html | 98 + .../doc/images/qt-logo.png | Bin 0 -> 4075 bytes AirTV-Qt/qtsingleapplication/doc/index.qdoc | 47 + .../examples/console/console.pro | 5 + .../examples/console/console.qdoc | 64 + .../examples/console/main.cpp | 88 + .../qtsingleapplication/examples/examples.pro | 4 + .../examples/loader/file1.qsl | 1 + .../examples/loader/file2.qsl | 1 + .../examples/loader/loader.pro | 5 + .../examples/loader/loader.qdoc | 80 + .../examples/loader/main.cpp | 151 ++ .../examples/trivial/main.cpp | 77 + .../examples/trivial/trivial.pro | 5 + .../examples/trivial/trivial.qdoc | 75 + .../qtsingleapplication.pro | 5 + AirTV-Qt/qtsingleapplication/src/QtLockedFile | 1 + .../src/QtSingleApplication | 1 + .../qtsingleapplication/src/qtlocalpeer.cpp | 199 ++ .../qtsingleapplication/src/qtlocalpeer.h | 76 + .../qtsingleapplication/src/qtlockedfile.cpp | 192 ++ .../qtsingleapplication/src/qtlockedfile.h | 96 + .../src/qtlockedfile_unix.cpp | 114 ++ .../src/qtlockedfile_win.cpp | 206 ++ .../src/qtsingleapplication.cpp | 344 ++++ .../src/qtsingleapplication.h | 102 + .../src/qtsingleapplication.pri | 16 + .../src/qtsinglecoreapplication.cpp | 148 ++ .../src/qtsinglecoreapplication.h | 70 + .../src/qtsinglecoreapplication.pri | 10 + AirTV-Qt/raopcallbackhandler.cpp | 45 + AirTV-Qt/raopcallbackhandler.h | 27 + AirTV-Qt/raopservice.cpp | 145 ++ AirTV-Qt/raopservice.h | 36 + Makefile | 12 + README | 1 + airport.key | 23 + include/dnssd.h | 27 + include/raop.h | 30 + src/alac/alac.c | 1172 +++++++++++ src/alac/alac.h | 13 + src/alac/stdint_win.h | 14 + src/base64.c | 268 +++ src/base64.h | 15 + src/compat.h | 24 + src/crypto/aes.c | 457 +++++ src/crypto/bigint.c | 1512 ++++++++++++++ src/crypto/bigint.h | 99 + src/crypto/bigint_impl.h | 131 ++ src/crypto/config.h | 6 + src/crypto/crypto.h | 146 ++ src/crypto/hmac.c | 105 + src/crypto/md5.c | 294 +++ src/crypto/os_port.h | 160 ++ src/crypto/rc4.c | 92 + src/crypto/sha1.c | 249 +++ src/dnssd.c | 285 +++ src/dnssd.m | 164 ++ src/dnssdint.h | 19 + src/global.h | 10 + src/http_parser.c | 1820 +++++++++++++++++ src/http_parser.h | 290 +++ src/http_request.c | 238 +++ src/http_request.h | 22 + src/http_response.c | 133 ++ src/http_response.h | 15 + src/httpd.c | 339 +++ src/httpd.h | 27 + src/logger.c | 90 + src/logger.h | 28 + src/memalign.h | 39 + src/netutils.c | 139 ++ src/netutils.h | 10 + src/raop.c | 340 +++ src/raop_buffer.c | 380 ++++ src/raop_buffer.h | 35 + src/raop_rtp.c | 521 +++++ src/raop_rtp.h | 23 + src/rsakey.c | 371 ++++ src/rsakey.h | 25 + src/rsapem.c | 148 ++ src/rsapem.h | 10 + src/sdp.c | 228 +++ src/sdp.h | 22 + src/sockets.h | 35 + src/threads.h | 46 + src/utils.c | 130 ++ src/utils.h | 9 + test/dnssd_test.c | 48 + test/dnssd_test.m | 32 + test/main.c | 213 ++ test/shairport.c | 75 + 124 files changed, 16168 insertions(+) create mode 100644 .gitignore create mode 100644 AirTV-Qt/AirTV.icns create mode 100644 AirTV-Qt/AirTV.ico create mode 100644 AirTV-Qt/AirTV.pro create mode 100644 AirTV-Qt/AirTV.qrc create mode 100644 AirTV-Qt/AirTV.rc create mode 100644 AirTV-Qt/audiooutput.cpp create mode 100644 AirTV-Qt/audiooutput.h create mode 100644 AirTV-Qt/images/airtv.svg create mode 100644 AirTV-Qt/main.cpp create mode 100644 AirTV-Qt/mainapplication.cpp create mode 100644 AirTV-Qt/mainapplication.h create mode 100644 AirTV-Qt/mainwindow.ui create mode 100644 AirTV-Qt/qtsingleapplication/INSTALL.TXT create mode 100644 AirTV-Qt/qtsingleapplication/README.TXT create mode 100644 AirTV-Qt/qtsingleapplication/buildlib/buildlib.pro create mode 100644 AirTV-Qt/qtsingleapplication/common.pri create mode 100755 AirTV-Qt/qtsingleapplication/configure create mode 100644 AirTV-Qt/qtsingleapplication/configure.bat create mode 100644 AirTV-Qt/qtsingleapplication/doc/html/classic.css create mode 100644 AirTV-Qt/qtsingleapplication/doc/html/images/qt-logo.png create mode 100644 AirTV-Qt/qtsingleapplication/doc/html/index.html create mode 100644 AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication-example-loader.html create mode 100644 AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication-example-trivial.html create mode 100644 AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication-members.html create mode 100644 AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication-obsolete.html create mode 100644 AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication.dcf create mode 100644 AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication.html create mode 100644 AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication.index create mode 100644 AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication.qhp create mode 100644 AirTV-Qt/qtsingleapplication/doc/html/qtsinglecoreapplication-example-console.html create mode 100644 AirTV-Qt/qtsingleapplication/doc/html/qtsinglecoreapplication-members.html create mode 100644 AirTV-Qt/qtsingleapplication/doc/html/qtsinglecoreapplication.html create mode 100644 AirTV-Qt/qtsingleapplication/doc/images/qt-logo.png create mode 100644 AirTV-Qt/qtsingleapplication/doc/index.qdoc create mode 100644 AirTV-Qt/qtsingleapplication/examples/console/console.pro create mode 100644 AirTV-Qt/qtsingleapplication/examples/console/console.qdoc create mode 100644 AirTV-Qt/qtsingleapplication/examples/console/main.cpp create mode 100644 AirTV-Qt/qtsingleapplication/examples/examples.pro create mode 100644 AirTV-Qt/qtsingleapplication/examples/loader/file1.qsl create mode 100644 AirTV-Qt/qtsingleapplication/examples/loader/file2.qsl create mode 100644 AirTV-Qt/qtsingleapplication/examples/loader/loader.pro create mode 100644 AirTV-Qt/qtsingleapplication/examples/loader/loader.qdoc create mode 100644 AirTV-Qt/qtsingleapplication/examples/loader/main.cpp create mode 100644 AirTV-Qt/qtsingleapplication/examples/trivial/main.cpp create mode 100644 AirTV-Qt/qtsingleapplication/examples/trivial/trivial.pro create mode 100644 AirTV-Qt/qtsingleapplication/examples/trivial/trivial.qdoc create mode 100644 AirTV-Qt/qtsingleapplication/qtsingleapplication.pro create mode 100644 AirTV-Qt/qtsingleapplication/src/QtLockedFile create mode 100644 AirTV-Qt/qtsingleapplication/src/QtSingleApplication create mode 100644 AirTV-Qt/qtsingleapplication/src/qtlocalpeer.cpp create mode 100644 AirTV-Qt/qtsingleapplication/src/qtlocalpeer.h create mode 100644 AirTV-Qt/qtsingleapplication/src/qtlockedfile.cpp create mode 100644 AirTV-Qt/qtsingleapplication/src/qtlockedfile.h create mode 100644 AirTV-Qt/qtsingleapplication/src/qtlockedfile_unix.cpp create mode 100644 AirTV-Qt/qtsingleapplication/src/qtlockedfile_win.cpp create mode 100644 AirTV-Qt/qtsingleapplication/src/qtsingleapplication.cpp create mode 100644 AirTV-Qt/qtsingleapplication/src/qtsingleapplication.h create mode 100644 AirTV-Qt/qtsingleapplication/src/qtsingleapplication.pri create mode 100644 AirTV-Qt/qtsingleapplication/src/qtsinglecoreapplication.cpp create mode 100644 AirTV-Qt/qtsingleapplication/src/qtsinglecoreapplication.h create mode 100644 AirTV-Qt/qtsingleapplication/src/qtsinglecoreapplication.pri create mode 100644 AirTV-Qt/raopcallbackhandler.cpp create mode 100644 AirTV-Qt/raopcallbackhandler.h create mode 100644 AirTV-Qt/raopservice.cpp create mode 100644 AirTV-Qt/raopservice.h create mode 100644 Makefile create mode 100644 README create mode 100644 airport.key create mode 100644 include/dnssd.h create mode 100644 include/raop.h create mode 100644 src/alac/alac.c create mode 100644 src/alac/alac.h create mode 100644 src/alac/stdint_win.h create mode 100644 src/base64.c create mode 100644 src/base64.h create mode 100644 src/compat.h create mode 100644 src/crypto/aes.c create mode 100644 src/crypto/bigint.c create mode 100644 src/crypto/bigint.h create mode 100644 src/crypto/bigint_impl.h create mode 100644 src/crypto/config.h create mode 100644 src/crypto/crypto.h create mode 100644 src/crypto/hmac.c create mode 100644 src/crypto/md5.c create mode 100644 src/crypto/os_port.h create mode 100644 src/crypto/rc4.c create mode 100644 src/crypto/sha1.c create mode 100644 src/dnssd.c create mode 100644 src/dnssd.m create mode 100644 src/dnssdint.h create mode 100644 src/global.h create mode 100644 src/http_parser.c create mode 100644 src/http_parser.h create mode 100644 src/http_request.c create mode 100644 src/http_request.h create mode 100644 src/http_response.c create mode 100644 src/http_response.h create mode 100644 src/httpd.c create mode 100644 src/httpd.h create mode 100644 src/logger.c create mode 100644 src/logger.h create mode 100644 src/memalign.h create mode 100644 src/netutils.c create mode 100644 src/netutils.h create mode 100644 src/raop.c create mode 100644 src/raop_buffer.c create mode 100644 src/raop_buffer.h create mode 100644 src/raop_rtp.c create mode 100644 src/raop_rtp.h create mode 100644 src/rsakey.c create mode 100644 src/rsakey.h create mode 100644 src/rsapem.c create mode 100644 src/rsapem.h create mode 100644 src/sdp.c create mode 100644 src/sdp.h create mode 100644 src/sockets.h create mode 100644 src/threads.h create mode 100644 src/utils.c create mode 100644 src/utils.h create mode 100644 test/dnssd_test.c create mode 100644 test/dnssd_test.m create mode 100644 test/main.c create mode 100644 test/shairport.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c9c2803 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*-build-* +*.user +*.o +*.DS_Store +*.swp +project.xcworkspace +xcuserdata diff --git a/AirTV-Qt/AirTV.icns b/AirTV-Qt/AirTV.icns new file mode 100644 index 0000000000000000000000000000000000000000..060ba2d78ecc77dc604b03612361cc8bb99d1fe4 GIT binary patch literal 153031 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 literal 0 HcmV?d00001 diff --git a/AirTV-Qt/AirTV.ico b/AirTV-Qt/AirTV.ico new file mode 100644 index 0000000000000000000000000000000000000000..7505011f439e6c70240d9b180207e1407fdaeb4d GIT binary patch literal 17897 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 WheP*YD1jFM0000 + + images/airtv.svg + + diff --git a/AirTV-Qt/AirTV.rc b/AirTV-Qt/AirTV.rc new file mode 100644 index 0000000..bfbdddf --- /dev/null +++ b/AirTV-Qt/AirTV.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "AirTV.ico" diff --git a/AirTV-Qt/audiooutput.cpp b/AirTV-Qt/audiooutput.cpp new file mode 100644 index 0000000..e87c60d --- /dev/null +++ b/AirTV-Qt/audiooutput.cpp @@ -0,0 +1,184 @@ +#include "audiooutput.h" + +#include +#include +#include + +#define BUFFER_SIZE (64*1024) + +AudioOutput::AudioOutput(QObject *parent) : + QIODevice(parent), + m_initialized(false), + m_output(0), + m_volume(0.0f) +{ +} + +bool AudioOutput::init(int bits, int channels, int samplerate) +{ + if (m_initialized) { + return false; + } + if (bits != 16) { + return false; + } + + m_format.setSampleSize(bits); + m_format.setChannels(channels); + m_format.setFrequency(samplerate); + m_format.setCodec("audio/pcm"); + m_format.setByteOrder(QAudioFormat::LittleEndian); + m_format.setSampleType(QAudioFormat::SignedInt); + + m_initialized = setDevice(QAudioDeviceInfo::defaultOutputDevice()); + return m_initialized; +} + +bool AudioOutput::setDevice(QAudioDeviceInfo deviceInfo) +{ + if (!deviceInfo.isFormatSupported(m_format)) { + qDebug() << "Format not supported!"; + return false; + } + m_deviceInfo = deviceInfo; + this->reinit(); + return true; +} + +void AudioOutput::reinit() +{ + bool running = false; + if (m_output && m_output->state() != QAudio::StoppedState) { + running = true; + } + this->stop(); + + // Reinitialize audio output + delete m_output; + m_output = 0; + m_output = new QAudioOutput(m_deviceInfo, m_format, this); + + // Set constant values to new audio output + connect(m_output, SIGNAL(notify()), SLOT(notified())); + connect(m_output, SIGNAL(stateChanged(QAudio::State)), SLOT(stateChanged(QAudio::State))); + if (running) { + this->start(); + } +} + +void AudioOutput::start() +{ + if (m_output == 0 || m_output->state() != QAudio::StoppedState) { + return; + } + this->open(QIODevice::ReadOnly); + m_buffer.clear(); + m_output->start(this); + m_output->suspend(); +} + +void AudioOutput::setVolume(float volume) +{ + m_volume = volume; +} + +void AudioOutput::output(const char *data, int datalen) +{ + if (m_output && m_output->state() != QAudio::StoppedState) { + // Append input data to the end of buffer + m_buffer.append(data, datalen); + + // Check if our buffer has grown too large + if (m_buffer.length() > 2*BUFFER_SIZE) { + // There could be a better way to handle this + this->flush(); + } + + // If audio is suspended and buffer is full, resume + if (m_output->state() == QAudio::SuspendedState) { + if (m_buffer.length() >= BUFFER_SIZE) { + qDebug() << "Resuming..."; + m_output->resume(); + } + } + } +} + +void AudioOutput::flush() +{ + // Flushing buffers is a bit tricky... + // Don't modify this unless you're sure + this->stop(); + m_output->reset(); + this->start(); +} + +void AudioOutput::stop() +{ + if (m_output && m_output->state() != QAudio::StoppedState) { + // Stop audio output + m_output->stop(); + m_buffer.clear(); + this->close(); + } +} + +static void apply_s16le_volume(float volume, uchar *data, int datalen) +{ + int samples = datalen/2; + float mult = pow(10.0,0.05*volume); + + for (int i=0; i(data+i*2)*mult; + qToLittleEndian(val, data+i*2); + } +} + +qint64 AudioOutput::readData(char *data, qint64 maxlen) +{ + // Calculate output length, always full samples + int outlen = qMin(m_buffer.length(), (int)maxlen); + if (outlen%2 != 0) { + outlen += 1; + } + + memcpy(data, m_buffer.data(), outlen); + apply_s16le_volume(m_volume, (uchar *)data, outlen); + m_buffer.remove(0, outlen); + return outlen; +} + +qint64 AudioOutput::writeData(const char *data, qint64 len) +{ + Q_UNUSED(data); + Q_UNUSED(len); + + return 0; +} + +qint64 AudioOutput::bytesAvailable() const +{ + return m_buffer.length() + QIODevice::bytesAvailable(); +} + +bool AudioOutput::isSequential() const +{ + return true; +} + +void AudioOutput::notified() +{ +} + +void AudioOutput::stateChanged(QAudio::State state) +{ + // Start buffering again in case of underrun... + // Required on Windows, otherwise it stalls idle + if (state == QAudio::IdleState && m_output->error() == QAudio::UnderrunError) { + // This check is required, because Mac OS X underruns often + if (m_buffer.length() < BUFFER_SIZE) { + m_output->suspend(); + } + } + qWarning() << "state = " << state; +} diff --git a/AirTV-Qt/audiooutput.h b/AirTV-Qt/audiooutput.h new file mode 100644 index 0000000..70b67bb --- /dev/null +++ b/AirTV-Qt/audiooutput.h @@ -0,0 +1,51 @@ +#ifndef AUDIOOUTPUT_H +#define AUDIOOUTPUT_H + +#include +#include +#include +#include +#include +#include +#include + +class AudioOutput : public QIODevice +{ + Q_OBJECT +public: + explicit AudioOutput(QObject *parent = 0); + bool init(int bits, int channels, int samplerate); + bool setDevice(QAudioDeviceInfo deviceInfo); + + void start(); + void setVolume(float volume); + void output(const char *data, int datalen); + void flush(); + void stop(); + + qint64 readData(char *data, qint64 maxlen); + qint64 writeData(const char *data, qint64 len); + qint64 bytesAvailable() const; + bool isSequential() const; + +private: + void reinit(); + +private: + bool m_initialized; + QByteArray m_buffer; + QAudioFormat m_format; + QAudioDeviceInfo m_deviceInfo; + QAudioOutput* m_output; + float m_volume; + +signals: + +public slots: + +private slots: + void notified(); + void stateChanged(QAudio::State state); +}; + +#endif // AUDIOOUTPUT_H diff --git a/AirTV-Qt/images/airtv.svg b/AirTV-Qt/images/airtv.svg new file mode 100644 index 0000000..978b181 --- /dev/null +++ b/AirTV-Qt/images/airtv.svg @@ -0,0 +1,98 @@ + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AirTV-Qt/main.cpp b/AirTV-Qt/main.cpp new file mode 100644 index 0000000..0eaf1ce --- /dev/null +++ b/AirTV-Qt/main.cpp @@ -0,0 +1,30 @@ +#include +#include + +#include "mainapplication.h" +#include "videowidget.h" +#include "raopservice.h" + +int main(int argc, char *argv[]) +{ + QtSingleApplication a(argc, argv); + if (a.isRunning()) { + return 0; + } + a.setApplicationName("AirTV"); + + if (!QSystemTrayIcon::isSystemTrayAvailable()) { + QMessageBox::critical(0, QObject::tr("Systray"), + QObject::tr("I couldn't detect any system tray " + "on this system.")); + return 1; + } + QApplication::setQuitOnLastWindowClosed(false); + + MainApplication m; + QObject::connect(&m, SIGNAL(quitRequested()), &a, SLOT(quit())); + QObject::connect(&a, SIGNAL(aboutToQuit()), &m, SLOT(aboutToQuit())); + m.start(); + + return a.exec(); +} diff --git a/AirTV-Qt/mainapplication.cpp b/AirTV-Qt/mainapplication.cpp new file mode 100644 index 0000000..477f782 --- /dev/null +++ b/AirTV-Qt/mainapplication.cpp @@ -0,0 +1,46 @@ +#include "mainapplication.h" + +#include + +MainApplication::MainApplication(QObject *parent) : + QObject(parent) +{ + raopService = new RaopService(0); + trayIconMenu = new QMenu(0); + + // Initialize the service + raopService->init(); + + quitAction = new QAction(tr("&Quit"), trayIconMenu); + connect(quitAction, SIGNAL(triggered()), this, SIGNAL(quitRequested())); + trayIconMenu->addAction(quitAction); + + // Construct the actual system tray icon + trayIcon = new QSystemTrayIcon(this); + trayIcon->setContextMenu(trayIconMenu); + trayIcon->setIcon(QIcon(":icons/airtv.svg")); +} + +MainApplication::~MainApplication() +{ + trayIcon->setContextMenu(0); + delete trayIconMenu; + delete raopService; +} + +void MainApplication::start() +{ + raopService->start(); + trayIcon->show(); +} + +void MainApplication::stop() +{ + raopService->stop(); + trayIcon->hide(); +} + +void MainApplication::aboutToQuit() +{ + this->stop(); +} diff --git a/AirTV-Qt/mainapplication.h b/AirTV-Qt/mainapplication.h new file mode 100644 index 0000000..eccdfb2 --- /dev/null +++ b/AirTV-Qt/mainapplication.h @@ -0,0 +1,36 @@ +#ifndef MAINAPPLICATION_H +#define MAINAPPLICATION_H + +#include +#include +#include +#include + +#include "raopservice.h" + +class MainApplication : public QObject +{ + Q_OBJECT +public: + explicit MainApplication(QObject *parent = 0); + ~MainApplication(); + + void start(); + void stop(); + +private: + RaopService *raopService; + + QSystemTrayIcon *trayIcon; + QMenu *trayIconMenu; + QAction *quitAction; + +signals: + void quitRequested(); + +public slots: + void aboutToQuit(); + +}; + +#endif // MAINAPPLICATION_H diff --git a/AirTV-Qt/mainwindow.ui b/AirTV-Qt/mainwindow.ui new file mode 100644 index 0000000..6050363 --- /dev/null +++ b/AirTV-Qt/mainwindow.ui @@ -0,0 +1,24 @@ + + MainWindow + + + + 0 + 0 + 400 + 300 + + + + MainWindow + + + + + + + + + + + diff --git a/AirTV-Qt/qtsingleapplication/INSTALL.TXT b/AirTV-Qt/qtsingleapplication/INSTALL.TXT new file mode 100644 index 0000000..bbb74a9 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/INSTALL.TXT @@ -0,0 +1,254 @@ +INSTALLATION INSTRUCTIONS + +These instructions refer to the package you are installing as +some-package.tar.gz or some-package.zip. The .zip file is intended for use +on Windows. + +The directory you choose for the installation will be referred to as +your-install-dir. + +Note to Qt Visual Studio Integration users: In the instructions below, +instead of building from command line with nmake, you can use the menu +command 'Qt->Open Solution from .pro file' on the .pro files in the +example and plugin directories, and then build from within Visual +Studio. + +Unpacking and installation +-------------------------- + +1. Unpacking the archive (if you have not done so already). + + On Unix and Mac OS X (in a terminal window): + + cd your-install-dir + gunzip some-package.tar.gz + tar xvf some-package.tar + + This creates the subdirectory some-package containing the files. + + On Windows: + + Unpack the .zip archive by right-clicking it in explorer and + choosing "Extract All...". If your version of Windows does not + have zip support, you can use the infozip tools available + from www.info-zip.org. + + If you are using the infozip tools (in a command prompt window): + cd your-install-dir + unzip some-package.zip + +2. Configuring the package. + + The configure script is called "configure" on unix/mac and + "configure.bat" on Windows. It should be run from a command line + after cd'ing to the package directory. + + You can choose whether you want to use the component by including + its source code directly into your project, or build the component + as a dynamic shared library (DLL) that is loaded into the + application at run-time. The latter may be preferable for + technical or licensing (LGPL) reasons. If you want to build a DLL, + run the configure script with the argument "-library". Also see + the note about usage below. + + (Components that are Qt plugins, e.g. styles and image formats, + are by default built as a plugin DLL.) + + The configure script will prompt you in some cases for further + information. Answer these questions and carefully read the license text + before accepting the license conditions. The package cannot be used if + you do not accept the license conditions. + +3. Building the component and examples (when required). + + If a DLL is to be built, or if you would like to build the + examples, next give the commands + + qmake + make [or nmake if your are using Microsoft Visual C++] + + The example program(s) can be found in the directory called + "examples" or "example". + + Components that are Qt plugins, e.g. styles and image formats, are + ready to be used as soon as they are built, so the rest of this + installation instruction can be skipped. + +4. Building the Qt Designer plugin (optional). + + Some of the widget components are provided with plugins for Qt + Designer. To build and install the plugin, cd into the + some-package/plugin directory and give the commands + + qmake + make [or nmake if your are using Microsoft Visual C++] + + Restart Qt Designer to make it load the new widget plugin. + + Note: If you are using the built-in Qt Designer from the Qt Visual + Studio Integration, you will need to manually copy the plugin DLL + file, i.e. copy + %QTDIR%\plugins\designer\some-component.dll + to the Qt Visual Studio Integration plugin path, typically: + C:\Program Files\Trolltech\Qt VS Integration\plugins + + Note: If you for some reason are using a Qt Designer that is built + in debug mode, you will need to build the plugin in debug mode + also. Edit the file plugin.pro in the plugin directory, changing + 'release' to 'debug' in the CONFIG line, before running qmake. + + + +Solutions components are intended to be used directly from the package +directory during development, so there is no 'make install' procedure. + + +Using a component in your project +--------------------------------- + +To use this component in your project, add the following line to the +project's .pro file (or do the equivalent in your IDE): + + include(your-install-dir/some-package/src/some-package.pri) + +This adds the package's sources and headers to the SOURCES and HEADERS +project variables respectively (or, if the component has been +configured as a DLL, it adds that library to the LIBS variable), and +updates INCLUDEPATH to contain the package's src +directory. Additionally, the .pri file may include some dependencies +needed by the package. + +To include a header file from the package in your sources, you can now +simply use: + + #include + +or alternatively, in pre-Qt 4 style: + + #include + +Refer to the documentation to see the classes and headers this +components provides. + + + +Install documentation (optional) +-------------------------------- + +The HTML documentation for the package's classes is located in the +your-install-dir/some-package/doc/html/index.html. You can open this +file and read the documentation with any web browser. + +To install the documentation into Qt Assistant (for Qt version 4.4 and +later): + +1. In Assistant, open the Edit->Preferences dialog and choose the + Documentation tab. Click the Add... button and select the file + your-install-dir/some-package/doc/html/some-package.qch + +For Qt versions prior to 4.4, do instead the following: + +1. The directory your-install-dir/some-package/doc/html contains a + file called some-package.dcf. Execute the following commands in a + shell, command prompt or terminal window: + + cd your-install-dir/some-package/doc/html/ + assistant -addContentFile some-package.dcf + +The next time you start Qt Assistant, you can access the package's +documentation. + + +Removing the documentation from assistant +----------------------------------------- + +If you have installed the documentation into Qt Assistant, and want to uninstall it, do as follows, for Qt version 4.4 and later: + +1. In Assistant, open the Edit->Preferences dialog and choose the + Documentation tab. In the list of Registered Documentation, select + the item com.nokia.qtsolutions.some-package_version, and click + the Remove button. + +For Qt versions prior to 4.4, do instead the following: + +1. The directory your-install-dir/some-package/doc/html contains a + file called some-package.dcf. Execute the following commands in a + shell, command prompt or terminal window: + + cd your-install-dir/some-package/doc/html/ + assistant -removeContentFile some-package.dcf + + + +Using the component as a DLL +---------------------------- + +1. Normal components + + The shared library (DLL) is built and placed in the + some-package/lib directory. It is intended to be used directly + from there during development. When appropriate, both debug and + release versions are built, since the run-time linker will in some + cases refuse to load a debug-built DLL into a release-built + application or vice versa. + + The following steps are taken by default to help the dynamic + linker to locate the DLL at run-time (during development): + + Unix: The some-package.pri file will add linker instructions to + add the some-package/lib directory to the rpath of the + executable. (When distributing, or if your system does not support + rpath, you can copy the shared library to another place that is + searched by the dynamic linker, e.g. the "lib" directory of your + Qt installation.) + + Mac: The full path to the library is hardcoded into the library + itself, from where it is copied into the executable at link time, + and ready by the dynamic linker at run-time. (When distributing, + you will want to edit these hardcoded paths in the same way as for + the Qt DLLs. Refer to the document "Deploying an Application on + Mac OS X" in the Qt Reference Documentation.) + + Windows: the .dll file(s) are copied into the "bin" directory of + your Qt installation. The Qt installation will already have set up + that directory to be searched by the dynamic linker. + + +2. Plugins + + For Qt Solutions plugins (e.g. image formats), both debug and + release versions of the plugin are built by default when + appropriate, since in some cases the release Qt library will not + load a debug plugin, and vice versa. The plugins are automatically + copied into the plugins directory of your Qt installation when + built, so no further setup is required. + + Plugins may also be built statically, i.e. as a library that will be + linked into your application executable, and so will not need to + be redistributed as a separate plugin DLL to end users. Static + building is required if Qt itself is built statically. To do it, + just add "static" to the CONFIG variable in the plugin/plugin.pro + file before building. Refer to the "Static Plugins" section in the + chapter "How to Create Qt Plugins" for explanation of how to use a + static plugin in your application. The source code of the example + program(s) will also typically contain the relevant instructions + as comments. + + + +Uninstalling +------------ + + The following command will remove any fils that have been + automatically placed outside the package directory itself during + installation and building + + make distclean [or nmake if your are using Microsoft Visual C++] + + If Qt Assistant documentation or Qt Designer plugins have been + installed, they can be uninstalled manually, ref. above. + + +Enjoy! :) + +- The Qt Solutions Team. diff --git a/AirTV-Qt/qtsingleapplication/README.TXT b/AirTV-Qt/qtsingleapplication/README.TXT new file mode 100644 index 0000000..06abb09 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/README.TXT @@ -0,0 +1,33 @@ +Qt Solutions Component: Single Application + +The QtSingleApplication component provides support for +applications that can be only started once per user. + + + +Version history: + +2.0: - Version 1.3 ported to Qt 4. + +2.1: - Fix compilation problem on Mac. + +2.2: - Really fix the Mac compilation problem. + - Mac: fix crash due to wrong object releasing. + - Mac: Fix memory leak. + +2.3: - Windows: Force creation of internal widget to make it work + with Qt 4.2. + +2.4: - Fix the system for automatic window raising on message + reception. NOTE: minor API change. + +2.5: - Mac: Fix isRunning() to work and report correctly. + +2.6: - - initialize() is now obsolete, no longer necessary to call + it + - - Fixed race condition where multiple instances migth be started + - - QtSingleCoreApplication variant provided for non-GUI (console) + usage + - Complete reimplementation. Visible changes: + - LGPL release. + diff --git a/AirTV-Qt/qtsingleapplication/buildlib/buildlib.pro b/AirTV-Qt/qtsingleapplication/buildlib/buildlib.pro new file mode 100644 index 0000000..37dddcd --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/buildlib/buildlib.pro @@ -0,0 +1,13 @@ +TEMPLATE=lib +CONFIG += qt dll qtsingleapplication-buildlib +mac:CONFIG += absolute_library_soname +win32|mac:!wince*:!win32-msvc:!macx-xcode:CONFIG += debug_and_release build_all +include(../src/qtsingleapplication.pri) +TARGET = $$QTSINGLEAPPLICATION_LIBNAME +DESTDIR = $$QTSINGLEAPPLICATION_LIBDIR +win32 { + DLLDESTDIR = $$[QT_INSTALL_BINS] + QMAKE_DISTCLEAN += $$[QT_INSTALL_BINS]\\$${QTSINGLEAPPLICATION_LIBNAME}.dll +} +target.path = $$DESTDIR +INSTALLS += target diff --git a/AirTV-Qt/qtsingleapplication/common.pri b/AirTV-Qt/qtsingleapplication/common.pri new file mode 100644 index 0000000..8f5fc80 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/common.pri @@ -0,0 +1,6 @@ +infile(config.pri, SOLUTIONS_LIBRARY, yes): CONFIG += qtsingleapplication-uselib +TEMPLATE += fakelib +QTSINGLEAPPLICATION_LIBNAME = $$qtLibraryTarget(QtSolutions_SingleApplication-head) +TEMPLATE -= fakelib +QTSINGLEAPPLICATION_LIBDIR = $$PWD/lib +unix:qtsingleapplication-uselib:!qtsingleapplication-buildlib:QMAKE_RPATHDIR += $$QTSINGLEAPPLICATION_LIBDIR diff --git a/AirTV-Qt/qtsingleapplication/configure b/AirTV-Qt/qtsingleapplication/configure new file mode 100755 index 0000000..3c4edff --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/configure @@ -0,0 +1,25 @@ +#!/bin/sh + +if [ "x$1" != "x" -a "x$1" != "x-library" ]; then + echo "Usage: $0 [-library]" + echo + echo "-library: Build the component as a dynamic library (DLL). Default is to" + echo " include the component source code directly in the application." + echo + exit 0 +fi + +rm -f config.pri +if [ "x$1" = "x-library" ]; then + echo "Configuring to build this component as a dynamic library." + echo "SOLUTIONS_LIBRARY = yes" > config.pri +fi + +echo +echo "This component is now configured." +echo +echo "To build the component library (if requested) and example(s)," +echo "run qmake and your make command." +echo +echo "To remove or reconfigure, run make distclean." +echo diff --git a/AirTV-Qt/qtsingleapplication/configure.bat b/AirTV-Qt/qtsingleapplication/configure.bat new file mode 100644 index 0000000..72c1b83 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/configure.bat @@ -0,0 +1,39 @@ +@echo off + +rem +rem "Main" +rem + +if not "%1"=="" ( + if not "%1"=="-library" ( + call :PrintUsage + goto EOF + ) +) + +if exist config.pri. del config.pri +if "%1"=="-library" ( + echo Configuring to build this component as a dynamic library. + echo SOLUTIONS_LIBRARY = yes > config.pri +) + +echo . +echo This component is now configured. +echo . +echo To build the component library (if requested) and example(s), +echo run qmake and your make or nmake command. +echo . +echo To remove or reconfigure, run make (nmake) distclean. +echo . + +:PrintUsage +echo Usage: configure.bat [-library] +echo . +echo -library: Build the component as a dynamic library (DLL). Default is to +echo include the component source directly in the application. +echo A DLL may be preferable for technical or licensing (LGPL) reasons. +echo . +goto EOF + + +:EOF diff --git a/AirTV-Qt/qtsingleapplication/doc/html/classic.css b/AirTV-Qt/qtsingleapplication/doc/html/classic.css new file mode 100644 index 0000000..b8cae8e --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/doc/html/classic.css @@ -0,0 +1,284 @@ +BODY,H1,H2,H3,H4,H5,H6,P,CENTER,TD,TH,UL,DL,DIV { + font-family: Arial, Geneva, Helvetica, sans-serif; +} +H1 { + text-align: center; + font-size: 160%; +} +H2 { + font-size: 120%; +} +H3 { + font-size: 100%; +} + +h3.fn,span.fn +{ + background-color: #eee; + border-width: 1px; + border-style: solid; + border-color: #ddd; + font-weight: bold; + padding: 6px 0px 6px 10px; + margin: 42px 0px 0px 0px; +} + +hr { + border: 0; + color: #a0a0a0; + background-color: #ccc; + height: 1px; + width: 100%; + text-align: left; + margin: 34px 0px 34px 0px; +} + +table.valuelist { + border-width: 1px 1px 1px 1px; + border-style: solid; + border-color: #dddddd; + border-collapse: collapse; + background-color: #f0f0f0; +} + +table.indextable { + border-width: 1px 1px 1px 1px; + border-style: solid; + border-collapse: collapse; + background-color: #f0f0f0; + border-color:#555; + font-size: 100%; +} + +table td.largeindex { + border-width: 1px 1px 1px 1px; + border-collapse: collapse; + background-color: #f0f0f0; + border-color:#555; + font-size: 120%; +} + +table.valuelist th { + border-width: 1px 1px 1px 2px; + padding: 4px; + border-style: solid; + border-color: #666; + color:white; + background-color:#666; +} + +th.titleheader { + border-width: 1px 0px 1px 0px; + padding: 2px; + border-style: solid; + border-color: #666; + color:white; + background-color:#555; + background-image:url('images/gradient.png')}; + background-repeat: repeat-x; + font-size: 100%; +} + + +th.largeheader { + border-width: 1px 0px 1px 0px; + padding: 4px; + border-style: solid; + border-color: #444; + color:white; + background-color:#555555; + font-size: 120%; +} + +p { + + margin-left: 4px; + margin-top: 8px; + margin-bottom: 8px; +} + +a:link +{ + color: #0046ad; + text-decoration: none +} + +a:visited +{ + color: #672967; + text-decoration: none +} + +a.obsolete +{ + color: #661100; + text-decoration: none +} + +a.compat +{ + color: #661100; + text-decoration: none +} + +a.obsolete:visited +{ + color: #995500; + text-decoration: none +} + +a.compat:visited +{ + color: #995500; + text-decoration: none +} + +body +{ + background: #ffffff; + color: black +} + +table.generic, table.annotated +{ + border-width: 1px; + border-color:#bbb; + border-style:solid; + border-collapse:collapse; +} + +table td.memItemLeft { + width: 180px; + padding: 2px 0px 0px 8px; + margin: 4px; + border-width: 1px; + border-color: #E0E0E0; + border-style: none; + font-size: 100%; + white-space: nowrap +} + +table td.memItemRight { + padding: 2px 8px 0px 8px; + margin: 4px; + border-width: 1px; + border-color: #E0E0E0; + border-style: none; + font-size: 100%; +} + +table tr.odd { + background: #f0f0f0; + color: black; +} + +table tr.even { + background: #e4e4e4; + color: black; +} + +table.annotated th { + padding: 3px; + text-align: left +} + +table.annotated td { + padding: 3px; +} + +table tr pre +{ + padding-top: 0px; + padding-bottom: 0px; + padding-left: 0px; + padding-right: 0px; + border: none; + background: none +} + +tr.qt-style +{ + background: #96E066; + color: black +} + +body pre +{ + padding: 0.2em; + border: #e7e7e7 1px solid; + background: #f1f1f1; + color: black +} + +table tr.qt-code pre +{ + padding: 0.2em; + border: #e7e7e7 1px solid; + background: #f1f1f1; + color: black +} + +span.preprocessor, span.preprocessor a +{ + color: darkblue; +} + +span.comment +{ + color: darkred; + font-style: italic +} + +span.string,span.char +{ + color: darkgreen; +} + +.title +{ + text-align: center +} + +.subtitle +{ + font-size: 0.8em +} + +.small-subtitle +{ + font-size: 0.65em +} + +.qmlitem { + padding: 0; +} + +.qmlname { + white-space: nowrap; +} + +.qmltype { + text-align: center; + font-size: 160%; +} + +.qmlproto { + background-color: #eee; + border-width: 1px; + border-style: solid; + border-color: #ddd; + font-weight: bold; + padding: 6px 10px 6px 10px; + margin: 42px 0px 0px 0px; +} + +.qmlreadonly { + float: right; + color: red +} + +.qmldoc { +} + +*.qmlitem p { +} diff --git a/AirTV-Qt/qtsingleapplication/doc/html/images/qt-logo.png b/AirTV-Qt/qtsingleapplication/doc/html/images/qt-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..794162f5af58e7b38beebf472842fc9703ede19b GIT binary patch literal 4075 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 literal 0 HcmV?d00001 diff --git a/AirTV-Qt/qtsingleapplication/doc/html/index.html b/AirTV-Qt/qtsingleapplication/doc/html/index.html new file mode 100644 index 0000000..af9dab1 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/doc/html/index.html @@ -0,0 +1,48 @@ + + + + + + Single Application + + + + + + + +
  Home

Single Application
+

+
+

Description

+

The QtSingleApplication component provides support for applications that can be only started once per user.

+

For some applications it is useful or even critical that they are started only once by any user. Future attempts to start the application should activate any already running instance, and possibly perform requested actions, e.g. loading a file, in that instance.

+

The QtSingleApplication class provides an interface to detect a running instance, and to send command strings to that instance. For console (non-GUI) applications, the QtSingleCoreApplication variant is provided, which avoids dependency on QtGui.

+ +

Classes

+ + +

Examples

+ + +

Tested platforms

+
    +
  • Qt 4.4, 4.5 / Windows XP / MSVC.NET 2005
  • +
  • Qt 4.4, 4.5 / Linux / gcc
  • +
  • Qt 4.4, 4.5 / MacOS X 10.5 / gcc
  • +
+


+ + + + +
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
+ diff --git a/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication-example-loader.html b/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication-example-loader.html new file mode 100644 index 0000000..c305ae5 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication-example-loader.html @@ -0,0 +1,177 @@ + + + + + + Loading Documents + + + + + + + +
  Home

Loading Documents
+

+

The application in this example loads or prints the documents passed as commandline parameters to further instances of this application.

+
 /****************************************************************************
+ **
+ ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+ ** All rights reserved.
+ **
+ ** Contact: Nokia Corporation (qt-info@nokia.com)
+ **
+ ** This file is part of a Qt Solutions component.
+ **
+ ** You may use this file under the terms of the BSD license as follows:
+ **
+ ** "Redistribution and use in source and binary forms, with or without
+ ** modification, are permitted provided that the following conditions are
+ ** met:
+ **   * Redistributions of source code must retain the above copyright
+ **     notice, this list of conditions and the following disclaimer.
+ **   * Redistributions in binary form must reproduce the above copyright
+ **     notice, this list of conditions and the following disclaimer in
+ **     the documentation and/or other materials provided with the
+ **     distribution.
+ **   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+ **     the names of its contributors may be used to endorse or promote
+ **     products derived from this software without specific prior written
+ **     permission.
+ **
+ ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+ **
+ ****************************************************************************/
+
+ #include <qtsingleapplication.h>
+ #include <QtCore/QFile>
+ #include <QtGui/QMainWindow>
+ #include <QtGui/QPrinter>
+ #include <QtGui/QPainter>
+ #include <QtGui/QTextEdit>
+ #include <QtGui/QMdiArea>
+ #include <QtCore/QTextStream>
+
+ class MainWindow : public QMainWindow
+ {
+     Q_OBJECT
+ public:
+     MainWindow();
+
+ public slots:
+     void handleMessage(const QString& message);
+
+ signals:
+     void needToShow();
+
+ private:
+     QMdiArea *workspace;
+ };
+

The user interface in this application is a QMainWindow subclass with a QMdiArea as the central widget. It implements a slot handleMessage() that will be connected to the messageReceived() signal of the QtSingleApplication class.

+
 MainWindow::MainWindow()
+ {
+     workspace = new QMdiArea(this);
+
+     setCentralWidget(workspace);
+ }
+

The MainWindow constructor creates a minimal user interface.

+
 void MainWindow::handleMessage(const QString& message)
+ {
+     enum Action {
+         Nothing,
+         Open,
+         Print
+     } action;
+
+     action = Nothing;
+     QString filename = message;
+     if (message.toLower().startsWith("/print ")) {
+         filename = filename.mid(7);
+         action = Print;
+     } else if (!message.isEmpty()) {
+         action = Open;
+     }
+     if (action == Nothing) {
+         emit needToShow();
+         return;
+     }
+
+     QFile file(filename);
+     QString contents;
+     if (file.open(QIODevice::ReadOnly))
+         contents = file.readAll();
+     else
+         contents = "[[Error: Could not load file " + filename + "]]";
+
+     QTextEdit *view = new QTextEdit;
+     view->setPlainText(contents);
+
+     switch(action) {
+

The handleMessage() slot interprets the message passed in as a filename that can be prepended with /print to indicate that the file should just be printed rather than loaded.

+
     case Print:
+         {
+             QPrinter printer;
+             view->print(&printer);
+             delete view;
+         }
+         break;
+
+     case Open:
+         {
+             workspace->addSubWindow(view);
+             view->setWindowTitle(message);
+             view->show();
+             emit needToShow();
+         }
+         break;
+     default:
+         break;
+     };
+ }
+

Loading the file will also activate the window.

+
 #include "main.moc"
+
+ int main(int argc, char **argv)
+ {
+     QtSingleApplication instance("File loader QtSingleApplication example", argc, argv);
+     QString message;
+     for (int a = 1; a < argc; ++a) {
+         message += argv[a];
+         if (a < argc-1)
+             message += " ";
+     }
+
+     if (instance.sendMessage(message))
+         return 0;
+

The main entry point function creates a QtSingleApplication object, and creates a message to send to a running instance of the application. If the message was sent successfully the process exits immediately.

+
     MainWindow mw;
+     mw.handleMessage(message);
+     mw.show();
+
+     QObject::connect(&instance, SIGNAL(messageReceived(const QString&)),
+                      &mw, SLOT(handleMessage(const QString&)));
+
+     instance.setActivationWindow(&mw, false);
+     QObject::connect(&mw, SIGNAL(needToShow()), &instance, SLOT(activateWindow()));
+
+     return instance.exec();
+ }
+

If the message could not be sent the application starts up. Note that false is passed to the call to setActivationWindow() to prevent automatic activation for every message received, e.g. when the application should just print a file. Instead, the message handling function determines whether activation is requested, and signals that by emitting the needToShow() signal. This is then simply connected directly to QtSingleApplication's activateWindow() slot.

+


+ + + + +
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
+ diff --git a/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication-example-trivial.html b/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication-example-trivial.html new file mode 100644 index 0000000..b3a1910 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication-example-trivial.html @@ -0,0 +1,103 @@ + + + + + + A Trivial Example + + + + + + + +
  Home

A Trivial Example
+

+

The application in this example has a log-view that displays messages sent by further instances of the same application.

+

The example demonstrates the use of the QtSingleApplication class to detect and communicate with a running instance of the application using the sendMessage() API. The messageReceived() signal is used to display received messages in a QTextEdit log.

+
 /****************************************************************************
+ **
+ ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+ ** All rights reserved.
+ **
+ ** Contact: Nokia Corporation (qt-info@nokia.com)
+ **
+ ** This file is part of a Qt Solutions component.
+ **
+ ** You may use this file under the terms of the BSD license as follows:
+ **
+ ** "Redistribution and use in source and binary forms, with or without
+ ** modification, are permitted provided that the following conditions are
+ ** met:
+ **   * Redistributions of source code must retain the above copyright
+ **     notice, this list of conditions and the following disclaimer.
+ **   * Redistributions in binary form must reproduce the above copyright
+ **     notice, this list of conditions and the following disclaimer in
+ **     the documentation and/or other materials provided with the
+ **     distribution.
+ **   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+ **     the names of its contributors may be used to endorse or promote
+ **     products derived from this software without specific prior written
+ **     permission.
+ **
+ ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+ **
+ ****************************************************************************/
+
+ #include <qtsingleapplication.h>
+ #include <QtGui/QTextEdit>
+
+ class TextEdit : public QTextEdit
+ {
+     Q_OBJECT
+ public:
+     TextEdit(QWidget *parent = 0)
+         : QTextEdit(parent)
+     {}
+ public slots:
+     void append(const QString &str)
+     {
+         QTextEdit::append(str);
+     }
+ };
+
+ #include "main.moc"
+
+ int main(int argc, char **argv)
+ {
+     QtSingleApplication instance(argc, argv);
+

The example has only the main entry point function. A QtSingleApplication object is created immediately.

+
     if (instance.sendMessage("Wake up!"))
+         return 0;
+

If another instance of this application is already running, sendMessage() will succeed, and this instance just exits immediately.

+
     TextEdit logview;
+     logview.setReadOnly(true);
+     logview.show();
+

Otherwise the instance continues as normal and creates the user interface.

+
     instance.setActivationWindow(&logview);
+
+     QObject::connect(&instance, SIGNAL(messageReceived(const QString&)),
+                      &logview, SLOT(append(const QString&)));
+
+     return instance.exec();
+

The logview object is also set as the application's activation window. Every time a message is received, the window will be raised and activated automatically.

+

The messageReceived() signal is also connected to the QTextEdit's append() slot. Every message received from further instances of this application will be displayed in the log.

+

Finally the event loop is entered.

+


+ + + + +
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
+ diff --git a/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication-members.html b/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication-members.html new file mode 100644 index 0000000..c995ce3 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication-members.html @@ -0,0 +1,235 @@ + + + + + + List of All Members for QtSingleApplication + + + + + + + +
  Home

List of All Members for QtSingleApplication

+

This is the complete list of members for QtSingleApplication, including inherited members.

+

+ +
+

+


+ + + + +
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
+ diff --git a/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication-obsolete.html b/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication-obsolete.html new file mode 100644 index 0000000..0d07dfa --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication-obsolete.html @@ -0,0 +1,31 @@ + + + + + + Obsolete Members for QtSingleApplication + + + + + + + +
  Home

Obsolete Members for QtSingleApplication

+

The following class members are obsolete. They are provided to keep old source code working. We strongly advise against using them in new code.

+

+

Public Functions

+ + +
void initialize ( bool dummy = true )   (obsolete)
+
+

Member Function Documentation

+

void QtSingleApplication::initialize ( bool dummy = true )

+


+ + + + +
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
+ diff --git a/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication.dcf b/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication.dcf new file mode 100644 index 0000000..d81f87f --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication.dcf @@ -0,0 +1,40 @@ + + +
+
+ QtSingleApplication + activateWindow + activationWindow + id + isRunning + messageReceived + sendMessage + setActivationWindow +
+
+
+
+ QtSingleCoreApplication + id + isRunning + messageReceived + sendMessage +
+
+
+
+
+ A non-GUI example +
+
+ A Trivial Example +
+
+ Loading Documents +
+
+ Single Application +
+
+
+ diff --git a/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication.html b/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication.html new file mode 100644 index 0000000..2754a3b --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication.html @@ -0,0 +1,162 @@ + + + + + + QtSingleApplication Class Reference + + + + + + + +
  Home

QtSingleApplication Class Reference

+

The QtSingleApplication class provides an API to detect and communicate with running instances of an application. More...

+
 #include <QtSingleApplication>

Inherits QApplication.

+ +
+ +

Public Functions

+ + + + + + + + + + + +
QtSingleApplication ( int & argc, char ** argv, bool GUIenabled = true )
QtSingleApplication ( const QString & appId, int & argc, char ** argv )
QtSingleApplication ( int & argc, char ** argv, Type type )
QtSingleApplication ( Display * dpy, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )
QtSingleApplication ( Display * dpy, int & argc, char ** argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )
QtSingleApplication ( Display * dpy, const QString & appId, int argc, char ** argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )
QWidget * activationWindow () const
QString id () const
bool isRunning ()
void setActivationWindow ( QWidget * aw, bool activateOnMessage = true )
+ +
+ +

Public Slots

+ + + +
void activateWindow ()
bool sendMessage ( const QString & message, int timeout = 5000 )
+ +
+ +

Signals

+ + +
void messageReceived ( const QString & message )
+ +

Additional Inherited Members

+ + +
+

Detailed Description

+

The QtSingleApplication class provides an API to detect and communicate with running instances of an application.

+

This class allows you to create applications where only one instance should be running at a time. I.e., if the user tries to launch another instance, the already running instance will be activated instead. Another usecase is a client-server system, where the first started instance will assume the role of server, and the later instances will act as clients of that server.

+

By default, the full path of the executable file is used to determine whether two processes are instances of the same application. You can also provide an explicit identifier string that will be compared instead.

+

The application should create the QtSingleApplication object early in the startup phase, and call isRunning() to find out if another instance of this application is already running. If isRunning() returns false, it means that no other instance is running, and this instance has assumed the role as the running instance. In this case, the application should continue with the initialization of the application user interface before entering the event loop with exec(), as normal.

+

The messageReceived() signal will be emitted when the running application receives messages from another instance of the same application. When a message is received it might be helpful to the user to raise the application so that it becomes visible. To facilitate this, QtSingleApplication provides the setActivationWindow() function and the activateWindow() slot.

+

If isRunning() returns true, another instance is already running. It may be alerted to the fact that another instance has started by using the sendMessage() function. Also data such as startup parameters (e.g. the name of the file the user wanted this new instance to open) can be passed to the running instance with this function. Then, the application should terminate (or enter client mode).

+

If isRunning() returns true, but sendMessage() fails, that is an indication that the running instance is frozen.

+

Here's an example that shows how to convert an existing application to use QtSingleApplication. It is very simple and does not make use of all QtSingleApplication's functionality (see the examples for that).

+
 // Original
+ int main(int argc, char **argv)
+ {
+     QApplication app(argc, argv);
+
+     MyMainWidget mmw;
+     mmw.show();
+     return app.exec();
+ }
+
+ // Single instance
+ int main(int argc, char **argv)
+ {
+     QtSingleApplication app(argc, argv);
+
+     if (app.isRunning())
+         return !app.sendMessage(someDataString);
+
+     MyMainWidget mmw;
+     app.setActivationWindow(&mmw);
+     mmw.show();
+     return app.exec();
+ }
+

Once this QtSingleApplication instance is destroyed (normally when the process exits or crashes), when the user next attempts to run the application this instance will not, of course, be encountered. The next instance to call isRunning() or sendMessage() will assume the role as the new running instance.

+

For console (non-GUI) applications, QtSingleCoreApplication may be used instead of this class, to avoid the dependency on the QtGui library.

+

See also QtSingleCoreApplication.

+
+

Member Function Documentation

+

QtSingleApplication::QtSingleApplication ( int & argc, char ** argv, bool GUIenabled = true )

+

Creates a QtSingleApplication object. The application identifier will be QCoreApplication::applicationFilePath(). argc, argv, and GUIenabled are passed on to the QAppliation constructor.

+

If you are creating a console application (i.e. setting GUIenabled to false), you may consider using QtSingleCoreApplication instead.

+

QtSingleApplication::QtSingleApplication ( const QString & appId, int & argc, char ** argv )

+

Creates a QtSingleApplication object with the application identifier appId. argc and argv are passed on to the QAppliation constructor.

+

QtSingleApplication::QtSingleApplication ( int & argc, char ** argv, Type type )

+

Creates a QtSingleApplication object. The application identifier will be QCoreApplication::applicationFilePath(). argc, argv, and type are passed on to the QAppliation constructor.

+

QtSingleApplication::QtSingleApplication ( Display * dpy, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )

+

Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be QCoreApplication::applicationFilePath(). dpy, visual, and cmap are passed on to the QApplication constructor.

+

QtSingleApplication::QtSingleApplication ( Display * dpy, int & argc, char ** argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )

+

Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be QCoreApplication::applicationFilePath(). dpy, argc, argv, visual, and cmap are passed on to the QApplication constructor.

+

QtSingleApplication::QtSingleApplication ( Display * dpy, const QString & appId, int argc, char ** argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )

+

Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be appId. dpy, argc, argv, visual, and cmap are passed on to the QApplication constructor.

+

void QtSingleApplication::activateWindow ()   [slot]

+

De-minimizes, raises, and activates this application's activation window. This function does nothing if no activation window has been set.

+

This is a convenience function to show the user that this application instance has been activated when he has tried to start another instance.

+

This function should typically be called in response to the messageReceived() signal. By default, that will happen automatically, if an activation window has been set.

+

See also setActivationWindow(), messageReceived(), and initialize().

+

QWidget * QtSingleApplication::activationWindow () const

+

Returns the applications activation window if one has been set by calling setActivationWindow(), otherwise returns 0.

+

See also setActivationWindow().

+

QString QtSingleApplication::id () const

+

Returns the application identifier. Two processes with the same identifier will be regarded as instances of the same application.

+

bool QtSingleApplication::isRunning ()

+

Returns true if another instance of this application is running; otherwise false.

+

This function does not find instances of this application that are being run by a different user (on Windows: that are running in another session).

+

See also sendMessage().

+

void QtSingleApplication::messageReceived ( const QString & message )   [signal]

+

This signal is emitted when the current instance receives a message from another instance of this application.

+

See also sendMessage(), setActivationWindow(), and activateWindow().

+

bool QtSingleApplication::sendMessage ( const QString & message, int timeout = 5000 )   [slot]

+

Tries to send the text message to the currently running instance. The QtSingleApplication object in the running instance will emit the messageReceived() signal when it receives the message.

+

This function returns true if the message has been sent to, and processed by, the current instance. If there is no instance currently running, or if the running instance fails to process the message within timeout milliseconds, this function return false.

+

See also isRunning() and messageReceived().

+

void QtSingleApplication::setActivationWindow ( QWidget * aw, bool activateOnMessage = true )

+

Sets the activation window of this application to aw. The activation window is the widget that will be activated by activateWindow(). This is typically the application's main window.

+

If activateOnMessage is true (the default), the window will be activated automatically every time a message is received, just prior to the messageReceived() signal being emitted.

+

See also activationWindow(), activateWindow(), and messageReceived().

+


+ + + + +
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
+ diff --git a/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication.index b/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication.index new file mode 100644 index 0000000..56052c2 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication.index @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication.qhp b/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication.qhp new file mode 100644 index 0000000..ff42d9d --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/doc/html/qtsingleapplication.qhp @@ -0,0 +1,53 @@ + + + com.nokia.qtsolutions.qtsingleapplication_head + qdoc + + qt + solutions + qtsingleapplication + + + qt + solutions + qtsingleapplication + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + qtsingleapplication.html + index.html + qtsingleapplication-example-trivial.html + qtsinglecoreapplication.html + qtsingleapplication-example-loader.html + qtsinglecoreapplication-example-console.html + classic.css + images/qt-logo.png + + + diff --git a/AirTV-Qt/qtsingleapplication/doc/html/qtsinglecoreapplication-example-console.html b/AirTV-Qt/qtsingleapplication/doc/html/qtsinglecoreapplication-example-console.html new file mode 100644 index 0000000..5c4f642 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/doc/html/qtsinglecoreapplication-example-console.html @@ -0,0 +1,120 @@ + + + + + + A non-GUI example + + + + + + + +
  Home

A non-GUI example
+

+

This example shows how to use the single-application functionality in a console application. It does not require the QtGui library at all.

+

The only differences from the GUI application usage demonstrated in the other examples are:

+

1) The .pro file should include qtsinglecoreapplication.pri instead of qtsingleapplication.pri

+

2) The class name is QtSingleCoreApplication instead of QtSingleApplication.

+

3) No calls are made regarding window activation, for obvious reasons.

+

console.pro:

+
 TEMPLATE   = app
+ CONFIG    += console
+ SOURCES   += main.cpp
+ include(../../src/qtsinglecoreapplication.pri)
+ QT -= gui
+

main.cpp:

+
 /****************************************************************************
+ **
+ ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+ ** All rights reserved.
+ **
+ ** Contact: Nokia Corporation (qt-info@nokia.com)
+ **
+ ** This file is part of a Qt Solutions component.
+ **
+ ** You may use this file under the terms of the BSD license as follows:
+ **
+ ** "Redistribution and use in source and binary forms, with or without
+ ** modification, are permitted provided that the following conditions are
+ ** met:
+ **   * Redistributions of source code must retain the above copyright
+ **     notice, this list of conditions and the following disclaimer.
+ **   * Redistributions in binary form must reproduce the above copyright
+ **     notice, this list of conditions and the following disclaimer in
+ **     the documentation and/or other materials provided with the
+ **     distribution.
+ **   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+ **     the names of its contributors may be used to endorse or promote
+ **     products derived from this software without specific prior written
+ **     permission.
+ **
+ ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+ **
+ ****************************************************************************/
+
+ #include "qtsinglecoreapplication.h"
+ #include <QtCore/QDebug>
+
+ void report(const QString& msg)
+ {
+     qDebug("[%i] %s", (int)QCoreApplication::applicationPid(), qPrintable(msg));
+ }
+
+ class MainClass : public QObject
+ {
+     Q_OBJECT
+ public:
+     MainClass()
+         : QObject()
+         {}
+
+ public slots:
+     void handleMessage(const QString& message)
+         {
+             report( "Message received: \"" + message + "\"");
+         }
+ };
+
+ int main(int argc, char **argv)
+ {
+     report("Starting up");
+
+     QtSingleCoreApplication app(argc, argv);
+
+     if (app.isRunning()) {
+         QString msg(QString("Hi master, I am %1.").arg(QCoreApplication::applicationPid()));
+         bool sentok = app.sendMessage(msg, 2000);
+         QString rep("Another instance is running, so I will exit.");
+         rep += sentok ? " Message sent ok." : " Message sending failed; the other instance may be frozen.";
+         report(rep);
+         return 0;
+     } else {
+         report("No other instance is running; so I will.");
+         MainClass mainObj;
+         QObject::connect(&app, SIGNAL(messageReceived(const QString&)),
+                          &mainObj, SLOT(handleMessage(const QString&)));
+         return app.exec();
+     }
+ }
+
+ #include "main.moc"
+


+ + + + +
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
+ diff --git a/AirTV-Qt/qtsingleapplication/doc/html/qtsinglecoreapplication-members.html b/AirTV-Qt/qtsingleapplication/doc/html/qtsinglecoreapplication-members.html new file mode 100644 index 0000000..69fb858 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/doc/html/qtsinglecoreapplication-members.html @@ -0,0 +1,126 @@ + + + + + + List of All Members for QtSingleCoreApplication + + + + + + + +
  Home

List of All Members for QtSingleCoreApplication

+

This is the complete list of members for QtSingleCoreApplication, including inherited members.

+

+ +
+

+


+ + + + +
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
+ diff --git a/AirTV-Qt/qtsingleapplication/doc/html/qtsinglecoreapplication.html b/AirTV-Qt/qtsingleapplication/doc/html/qtsinglecoreapplication.html new file mode 100644 index 0000000..a20cf2f --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/doc/html/qtsinglecoreapplication.html @@ -0,0 +1,98 @@ + + + + + + QtSingleCoreApplication Class Reference + + + + + + + +
  Home

QtSingleCoreApplication Class Reference

+

A variant of the QtSingleApplication class for non-GUI applications. More...

+
 #include <QtSingleCoreApplication>

Inherits QCoreApplication.

+ +
+ +

Public Functions

+ + + + + +
QtSingleCoreApplication ( int & argc, char ** argv )
QtSingleCoreApplication ( const QString & appId, int & argc, char ** argv )
QString id () const
bool isRunning ()
+ +
+ +

Public Slots

+ + +
bool sendMessage ( const QString & message, int timeout = 5000 )
+ +
+ +

Signals

+ + +
void messageReceived ( const QString & message )
+ +

Additional Inherited Members

+ + +
+

Detailed Description

+

A variant of the QtSingleApplication class for non-GUI applications.

+

This class is a variant of QtSingleApplication suited for use in console (non-GUI) applications. It is an extension of QCoreApplication (instead of QApplication). It does not require the QtGui library.

+

The API and usage is identical to QtSingleApplication, except that functions relating to the "activation window" are not present, for obvious reasons. Please refer to the QtSingleApplication documentation for explanation of the usage.

+

A QtSingleCoreApplication instance can communicate to a QtSingleApplication instance if they share the same application id. Hence, this class can be used to create a light-weight command-line tool that sends commands to a GUI application.

+

See also QtSingleApplication.

+
+

Member Function Documentation

+

QtSingleCoreApplication::QtSingleCoreApplication ( int & argc, char ** argv )

+

Creates a QtSingleCoreApplication object. The application identifier will be QCoreApplication::applicationFilePath(). argc and argv are passed on to the QCoreAppliation constructor.

+

QtSingleCoreApplication::QtSingleCoreApplication ( const QString & appId, int & argc, char ** argv )

+

Creates a QtSingleCoreApplication object with the application identifier appId. argc and argv are passed on to the QCoreAppliation constructor.

+

QString QtSingleCoreApplication::id () const

+

Returns the application identifier. Two processes with the same identifier will be regarded as instances of the same application.

+

bool QtSingleCoreApplication::isRunning ()

+

Returns true if another instance of this application is running; otherwise false.

+

This function does not find instances of this application that are being run by a different user (on Windows: that are running in another session).

+

See also sendMessage().

+

void QtSingleCoreApplication::messageReceived ( const QString & message )   [signal]

+

This signal is emitted when the current instance receives a message from another instance of this application.

+

See also sendMessage().

+

bool QtSingleCoreApplication::sendMessage ( const QString & message, int timeout = 5000 )   [slot]

+

Tries to send the text message to the currently running instance. The QtSingleCoreApplication object in the running instance will emit the messageReceived() signal when it receives the message.

+

This function returns true if the message has been sent to, and processed by, the current instance. If there is no instance currently running, or if the running instance fails to process the message within timeout milliseconds, this function return false.

+

See also isRunning() and messageReceived().

+


+ + + + +
Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies)Trademarks
Qt Solutions
+ diff --git a/AirTV-Qt/qtsingleapplication/doc/images/qt-logo.png b/AirTV-Qt/qtsingleapplication/doc/images/qt-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..794162f5af58e7b38beebf472842fc9703ede19b GIT binary patch literal 4075 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 literal 0 HcmV?d00001 diff --git a/AirTV-Qt/qtsingleapplication/doc/index.qdoc b/AirTV-Qt/qtsingleapplication/doc/index.qdoc new file mode 100644 index 0000000..9c3308d --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/doc/index.qdoc @@ -0,0 +1,47 @@ +/*! + \page index.html + \title Single Application + + \section1 Description + + The QtSingleApplication component provides support + for applications that can be only started once per user. + + + + For some applications it is useful or even critical that they are started + only once by any user. Future attempts to start the application should + activate any already running instance, and possibly perform requested + actions, e.g. loading a file, in that instance. + + The QtSingleApplication class provides an interface to detect a running + instance, and to send command strings to that instance. + For console (non-GUI) applications, the QtSingleCoreApplication variant is provided, which avoids dependency on QtGui. + + + + + \section1 Classes + \list + \i QtSingleApplication \i QtSingleCoreApplication\endlist + + \section1 Examples + \list + \i \link qtsingleapplication-example-trivial.html A Trivial Example \endlink \i \link qtsingleapplication-example-loader.html Loading Documents \endlink \i \link qtsinglecoreapplication-example-console.html A Non-GUI Example \endlink \endlist + + + + + + + \section1 Tested platforms + \list + \i Qt 4.4, 4.5 / Windows XP / MSVC.NET 2005 + \i Qt 4.4, 4.5 / Linux / gcc + \i Qt 4.4, 4.5 / MacOS X 10.5 / gcc + \endlist + + + + + */ \ No newline at end of file diff --git a/AirTV-Qt/qtsingleapplication/examples/console/console.pro b/AirTV-Qt/qtsingleapplication/examples/console/console.pro new file mode 100644 index 0000000..e0390e2 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/examples/console/console.pro @@ -0,0 +1,5 @@ +TEMPLATE = app +CONFIG += console +SOURCES += main.cpp +include(../../src/qtsinglecoreapplication.pri) +QT -= gui diff --git a/AirTV-Qt/qtsingleapplication/examples/console/console.qdoc b/AirTV-Qt/qtsingleapplication/examples/console/console.qdoc new file mode 100644 index 0000000..7bef07c --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/examples/console/console.qdoc @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + +/*! \page qtsinglecoreapplication-example-console.html + \title A non-GUI example + + This example shows how to use the single-application functionality + in a console application. It does not require the \c QtGui library + at all. + + The only differences from the GUI application usage demonstrated + in the other examples are: + + 1) The \c.pro file should include \c qtsinglecoreapplication.pri + instead of \c qtsingleapplication.pri + + 2) The class name is \c QtSingleCoreApplication instead of \c + QtSingleApplication. + + 3) No calls are made regarding window activation, for obvious reasons. + + console.pro: + \quotefile console/console.pro + + main.cpp: + \quotefile console/main.cpp + +*/ diff --git a/AirTV-Qt/qtsingleapplication/examples/console/main.cpp b/AirTV-Qt/qtsingleapplication/examples/console/main.cpp new file mode 100644 index 0000000..de4816a --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/examples/console/main.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + + +#include "qtsinglecoreapplication.h" +#include + + +void report(const QString& msg) +{ + qDebug("[%i] %s", (int)QCoreApplication::applicationPid(), qPrintable(msg)); +} + +class MainClass : public QObject +{ + Q_OBJECT +public: + MainClass() + : QObject() + {} + +public slots: + void handleMessage(const QString& message) + { + report( "Message received: \"" + message + "\""); + } +}; + +int main(int argc, char **argv) +{ + report("Starting up"); + + QtSingleCoreApplication app(argc, argv); + + if (app.isRunning()) { + QString msg(QString("Hi master, I am %1.").arg(QCoreApplication::applicationPid())); + bool sentok = app.sendMessage(msg, 2000); + QString rep("Another instance is running, so I will exit."); + rep += sentok ? " Message sent ok." : " Message sending failed; the other instance may be frozen."; + report(rep); + return 0; + } else { + report("No other instance is running; so I will."); + MainClass mainObj; + QObject::connect(&app, SIGNAL(messageReceived(const QString&)), + &mainObj, SLOT(handleMessage(const QString&))); + return app.exec(); + } +} + + +#include "main.moc" diff --git a/AirTV-Qt/qtsingleapplication/examples/examples.pro b/AirTV-Qt/qtsingleapplication/examples/examples.pro new file mode 100644 index 0000000..36b8fd3 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/examples/examples.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs +SUBDIRS = trivial \ + loader \ + console diff --git a/AirTV-Qt/qtsingleapplication/examples/loader/file1.qsl b/AirTV-Qt/qtsingleapplication/examples/loader/file1.qsl new file mode 100644 index 0000000..50fcd26 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/examples/loader/file1.qsl @@ -0,0 +1 @@ +File 1 diff --git a/AirTV-Qt/qtsingleapplication/examples/loader/file2.qsl b/AirTV-Qt/qtsingleapplication/examples/loader/file2.qsl new file mode 100644 index 0000000..4475433 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/examples/loader/file2.qsl @@ -0,0 +1 @@ +File 2 diff --git a/AirTV-Qt/qtsingleapplication/examples/loader/loader.pro b/AirTV-Qt/qtsingleapplication/examples/loader/loader.pro new file mode 100644 index 0000000..673497a --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/examples/loader/loader.pro @@ -0,0 +1,5 @@ +TEMPLATE = app + +include(../../src/qtsingleapplication.pri) + +SOURCES += main.cpp diff --git a/AirTV-Qt/qtsingleapplication/examples/loader/loader.qdoc b/AirTV-Qt/qtsingleapplication/examples/loader/loader.qdoc new file mode 100644 index 0000000..2ae59e3 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/examples/loader/loader.qdoc @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + +/*! \page qtsingleapplication-example-loader.html + \title Loading Documents + + The application in this example loads or prints the documents + passed as commandline parameters to further instances of this + application. + + \quotefromfile loader/main.cpp + \printuntil }; + The user interface in this application is a QMainWindow subclass + with a QMdiArea as the central widget. It implements a slot + \c handleMessage() that will be connected to the messageReceived() + signal of the QtSingleApplication class. + + \printuntil } + The MainWindow constructor creates a minimal user interface. + + \printto case Print: + The handleMessage() slot interprets the message passed in as a + filename that can be prepended with \e /print to indicate that + the file should just be printed rather than loaded. + + \printto #include + Loading the file will also activate the window. + + \printto mw + The \c main entry point function creates a QtSingleApplication + object, and creates a message to send to a running instance + of the application. If the message was sent successfully the + process exits immediately. + + \printuntil } + If the message could not be sent the application starts up. Note + that \c false is passed to the call to setActivationWindow() to + prevent automatic activation for every message received, e.g. when + the application should just print a file. Instead, the message + handling function determines whether activation is requested, and + signals that by emitting the needToShow() signal. This is then + simply connected directly to QtSingleApplication's + activateWindow() slot. +*/ diff --git a/AirTV-Qt/qtsingleapplication/examples/loader/main.cpp b/AirTV-Qt/qtsingleapplication/examples/loader/main.cpp new file mode 100644 index 0000000..7bc5686 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/examples/loader/main.cpp @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +class MainWindow : public QMainWindow +{ + Q_OBJECT +public: + MainWindow(); + +public slots: + void handleMessage(const QString& message); + +signals: + void needToShow(); + +private: + QMdiArea *workspace; +}; + +MainWindow::MainWindow() +{ + workspace = new QMdiArea(this); + + setCentralWidget(workspace); +} + +void MainWindow::handleMessage(const QString& message) +{ + enum Action { + Nothing, + Open, + Print + } action; + + action = Nothing; + QString filename = message; + if (message.toLower().startsWith("/print ")) { + filename = filename.mid(7); + action = Print; + } else if (!message.isEmpty()) { + action = Open; + } + if (action == Nothing) { + emit needToShow(); + return; + } + + QFile file(filename); + QString contents; + if (file.open(QIODevice::ReadOnly)) + contents = file.readAll(); + else + contents = "[[Error: Could not load file " + filename + "]]"; + + QTextEdit *view = new QTextEdit; + view->setPlainText(contents); + + switch(action) { + case Print: + { + QPrinter printer; + view->print(&printer); + delete view; + } + break; + + case Open: + { + workspace->addSubWindow(view); + view->setWindowTitle(message); + view->show(); + emit needToShow(); + } + break; + default: + break; + }; +} + +#include "main.moc" + +int main(int argc, char **argv) +{ + QtSingleApplication instance("File loader QtSingleApplication example", argc, argv); + QString message; + for (int a = 1; a < argc; ++a) { + message += argv[a]; + if (a < argc-1) + message += " "; + } + + if (instance.sendMessage(message)) + return 0; + + MainWindow mw; + mw.handleMessage(message); + mw.show(); + + QObject::connect(&instance, SIGNAL(messageReceived(const QString&)), + &mw, SLOT(handleMessage(const QString&))); + + instance.setActivationWindow(&mw, false); + QObject::connect(&mw, SIGNAL(needToShow()), &instance, SLOT(activateWindow())); + + return instance.exec(); +} diff --git a/AirTV-Qt/qtsingleapplication/examples/trivial/main.cpp b/AirTV-Qt/qtsingleapplication/examples/trivial/main.cpp new file mode 100644 index 0000000..8552fe3 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/examples/trivial/main.cpp @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + +#include +#include + +class TextEdit : public QTextEdit +{ + Q_OBJECT +public: + TextEdit(QWidget *parent = 0) + : QTextEdit(parent) + {} +public slots: + void append(const QString &str) + { + QTextEdit::append(str); + } +}; + +#include "main.moc" + + + +int main(int argc, char **argv) +{ + QtSingleApplication instance(argc, argv); + if (instance.sendMessage("Wake up!")) + return 0; + + TextEdit logview; + logview.setReadOnly(true); + logview.show(); + + instance.setActivationWindow(&logview); + + QObject::connect(&instance, SIGNAL(messageReceived(const QString&)), + &logview, SLOT(append(const QString&))); + + return instance.exec(); +} diff --git a/AirTV-Qt/qtsingleapplication/examples/trivial/trivial.pro b/AirTV-Qt/qtsingleapplication/examples/trivial/trivial.pro new file mode 100644 index 0000000..673497a --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/examples/trivial/trivial.pro @@ -0,0 +1,5 @@ +TEMPLATE = app + +include(../../src/qtsingleapplication.pri) + +SOURCES += main.cpp diff --git a/AirTV-Qt/qtsingleapplication/examples/trivial/trivial.qdoc b/AirTV-Qt/qtsingleapplication/examples/trivial/trivial.qdoc new file mode 100644 index 0000000..3185199 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/examples/trivial/trivial.qdoc @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + +/*! \page qtsingleapplication-example-trivial.html + \title A Trivial Example + + The application in this example has a log-view that displays + messages sent by further instances of the same application. + + The example demonstrates the use of the QtSingleApplication + class to detect and communicate with a running instance of + the application using the sendMessage() API. The messageReceived() + signal is used to display received messages in a QTextEdit log. + + \quotefromfile trivial/main.cpp + \printuntil instance + The example has only the \c main entry point function. + A QtSingleApplication object is created immediately. + + \printuntil return + If another instance of this application is already running, + sendMessage() will succeed, and this instance just exits + immediately. + + \printuntil show() + Otherwise the instance continues as normal and creates the + user interface. + + \printuntil return instance.exec(); + The \c logview object is also set as the application's activation + window. Every time a message is received, the window will be raised + and activated automatically. + + The messageReceived() signal is also connected to the QTextEdit's + append() slot. Every message received from further instances of + this application will be displayed in the log. + + Finally the event loop is entered. +*/ diff --git a/AirTV-Qt/qtsingleapplication/qtsingleapplication.pro b/AirTV-Qt/qtsingleapplication/qtsingleapplication.pro new file mode 100644 index 0000000..07257c5 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/qtsingleapplication.pro @@ -0,0 +1,5 @@ +TEMPLATE=subdirs +CONFIG += ordered +include(common.pri) +qtsingleapplication-uselib:SUBDIRS=buildlib +SUBDIRS+=examples diff --git a/AirTV-Qt/qtsingleapplication/src/QtLockedFile b/AirTV-Qt/qtsingleapplication/src/QtLockedFile new file mode 100644 index 0000000..16b48ba --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/src/QtLockedFile @@ -0,0 +1 @@ +#include "qtlockedfile.h" diff --git a/AirTV-Qt/qtsingleapplication/src/QtSingleApplication b/AirTV-Qt/qtsingleapplication/src/QtSingleApplication new file mode 100644 index 0000000..d111bf7 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/src/QtSingleApplication @@ -0,0 +1 @@ +#include "qtsingleapplication.h" diff --git a/AirTV-Qt/qtsingleapplication/src/qtlocalpeer.cpp b/AirTV-Qt/qtsingleapplication/src/qtlocalpeer.cpp new file mode 100644 index 0000000..382d182 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/src/qtlocalpeer.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + + +#include "qtlocalpeer.h" +#include +#include + +#if defined(Q_OS_WIN) +#include +#include +typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); +static PProcessIdToSessionId pProcessIdToSessionId = 0; +#endif +#if defined(Q_OS_UNIX) +#include +#endif + +namespace QtLP_Private { +#include "qtlockedfile.cpp" +#if defined(Q_OS_WIN) +#include "qtlockedfile_win.cpp" +#else +#include "qtlockedfile_unix.cpp" +#endif +} + +const char* QtLocalPeer::ack = "ack"; + +QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId) + : QObject(parent), id(appId) +{ + QString prefix = id; + if (id.isEmpty()) { + id = QCoreApplication::applicationFilePath(); +#if defined(Q_OS_WIN) + id = id.toLower(); +#endif + prefix = id.section(QLatin1Char('/'), -1); + } + prefix.remove(QRegExp("[^a-zA-Z]")); + prefix.truncate(6); + + QByteArray idc = id.toUtf8(); + quint16 idNum = qChecksum(idc.constData(), idc.size()); + socketName = QLatin1String("qtsingleapp-") + prefix + + QLatin1Char('-') + QString::number(idNum, 16); + +#if defined(Q_OS_WIN) + if (!pProcessIdToSessionId) { + QLibrary lib("kernel32"); + pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId"); + } + if (pProcessIdToSessionId) { + DWORD sessionId = 0; + pProcessIdToSessionId(GetCurrentProcessId(), &sessionId); + socketName += QLatin1Char('-') + QString::number(sessionId, 16); + } +#else + socketName += QLatin1Char('-') + QString::number(::getuid(), 16); +#endif + + server = new QLocalServer(this); + QString lockName = QDir(QDir::tempPath()).absolutePath() + + QLatin1Char('/') + socketName + + QLatin1String("-lockfile"); + lockFile.setFileName(lockName); + lockFile.open(QIODevice::ReadWrite); +} + + + +bool QtLocalPeer::isClient() +{ + if (lockFile.isLocked()) + return false; + + if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false)) + return true; + + bool res = server->listen(socketName); +#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0)) + // ### Workaround + if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { + QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName); + res = server->listen(socketName); + } +#endif + if (!res) + qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString())); + QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection())); + return false; +} + + +bool QtLocalPeer::sendMessage(const QString &message, int timeout) +{ + if (!isClient()) + return false; + + QLocalSocket socket; + bool connOk = false; + for(int i = 0; i < 2; i++) { + // Try twice, in case the other instance is just starting up + socket.connectToServer(socketName); + connOk = socket.waitForConnected(timeout/2); + if (connOk || i) + break; + int ms = 250; +#if defined(Q_OS_WIN) + Sleep(DWORD(ms)); +#else + struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; + nanosleep(&ts, NULL); +#endif + } + if (!connOk) + return false; + + QByteArray uMsg(message.toUtf8()); + QDataStream ds(&socket); + ds.writeBytes(uMsg.constData(), uMsg.size()); + bool res = socket.waitForBytesWritten(timeout); + if (res) { + res &= socket.waitForReadyRead(timeout); // wait for ack + if (res) + res &= (socket.read(qstrlen(ack)) == ack); + } + return res; +} + + +void QtLocalPeer::receiveConnection() +{ + QLocalSocket* socket = server->nextPendingConnection(); + if (!socket) + return; + + while (socket->bytesAvailable() < (int)sizeof(quint32)) + socket->waitForReadyRead(); + QDataStream ds(socket); + QByteArray uMsg; + quint32 remaining; + ds >> remaining; + uMsg.resize(remaining); + int got = 0; + char* uMsgBuf = uMsg.data(); + do { + got = ds.readRawData(uMsgBuf, remaining); + remaining -= got; + uMsgBuf += got; + } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); + if (got < 0) { + qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData()); + delete socket; + return; + } + QString message(QString::fromUtf8(uMsg)); + socket->write(ack, qstrlen(ack)); + socket->waitForBytesWritten(1000); + delete socket; + emit messageReceived(message); //### (might take a long time to return) +} diff --git a/AirTV-Qt/qtsingleapplication/src/qtlocalpeer.h b/AirTV-Qt/qtsingleapplication/src/qtlocalpeer.h new file mode 100644 index 0000000..e834b73 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/src/qtlocalpeer.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + +#ifndef QTLOCALPEER_H +#define QTLOCALPEER_H + +#include +#include +#include + +#include "qtlockedfile.h" + +class QtLocalPeer : public QObject +{ + Q_OBJECT + +public: + QtLocalPeer(QObject *parent = 0, const QString &appId = QString()); + bool isClient(); + bool sendMessage(const QString &message, int timeout); + QString applicationId() const + { return id; } + +Q_SIGNALS: + void messageReceived(const QString &message); + +protected Q_SLOTS: + void receiveConnection(); + +protected: + QString id; + QString socketName; + QLocalServer* server; + QtLP_Private::QtLockedFile lockFile; + +private: + static const char* ack; +}; + +#endif // QTLOCALPEER_H diff --git a/AirTV-Qt/qtsingleapplication/src/qtlockedfile.cpp b/AirTV-Qt/qtsingleapplication/src/qtlockedfile.cpp new file mode 100644 index 0000000..3e73ba6 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/src/qtlockedfile.cpp @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + +#include "qtlockedfile.h" + +/*! + \class QtLockedFile + + \brief The QtLockedFile class extends QFile with advisory locking + functions. + + A file may be locked in read or write mode. Multiple instances of + \e QtLockedFile, created in multiple processes running on the same + machine, may have a file locked in read mode. Exactly one instance + may have it locked in write mode. A read and a write lock cannot + exist simultaneously on the same file. + + The file locks are advisory. This means that nothing prevents + another process from manipulating a locked file using QFile or + file system functions offered by the OS. Serialization is only + guaranteed if all processes that access the file use + QLockedFile. Also, while holding a lock on a file, a process + must not open the same file again (through any API), or locks + can be unexpectedly lost. + + The lock provided by an instance of \e QtLockedFile is released + whenever the program terminates. This is true even when the + program crashes and no destructors are called. +*/ + +/*! \enum QtLockedFile::LockMode + + This enum describes the available lock modes. + + \value ReadLock A read lock. + \value WriteLock A write lock. + \value NoLock Neither a read lock nor a write lock. +*/ + +/*! + Constructs an unlocked \e QtLockedFile object. This constructor + behaves in the same way as \e QFile::QFile(). + + \sa QFile::QFile() +*/ +QtLockedFile::QtLockedFile() + : QFile() +{ +#ifdef Q_OS_WIN + wmutex = 0; + rmutex = 0; +#endif + m_lock_mode = NoLock; +} + +/*! + Constructs an unlocked QtLockedFile object with file \a name. This + constructor behaves in the same way as \e QFile::QFile(const + QString&). + + \sa QFile::QFile() +*/ +QtLockedFile::QtLockedFile(const QString &name) + : QFile(name) +{ +#ifdef Q_OS_WIN + wmutex = 0; + rmutex = 0; +#endif + m_lock_mode = NoLock; +} + +/*! + Opens the file in OpenMode \a mode. + + This is identical to QFile::open(), with the one exception that the + Truncate mode flag is disallowed. Truncation would conflict with the + advisory file locking, since the file would be modified before the + write lock is obtained. If truncation is required, use resize(0) + after obtaining the write lock. + + Returns true if successful; otherwise false. + + \sa QFile::open(), QFile::resize() +*/ +bool QtLockedFile::open(OpenMode mode) +{ + if (mode & QIODevice::Truncate) { + qWarning("QtLockedFile::open(): Truncate mode not allowed."); + return false; + } + return QFile::open(mode); +} + +/*! + Returns \e true if this object has a in read or write lock; + otherwise returns \e false. + + \sa lockMode() +*/ +bool QtLockedFile::isLocked() const +{ + return m_lock_mode != NoLock; +} + +/*! + Returns the type of lock currently held by this object, or \e + QtLockedFile::NoLock. + + \sa isLocked() +*/ +QtLockedFile::LockMode QtLockedFile::lockMode() const +{ + return m_lock_mode; +} + +/*! + \fn bool QtLockedFile::lock(LockMode mode, bool block = true) + + Obtains a lock of type \a mode. The file must be opened before it + can be locked. + + If \a block is true, this function will block until the lock is + aquired. If \a block is false, this function returns \e false + immediately if the lock cannot be aquired. + + If this object already has a lock of type \a mode, this function + returns \e true immediately. If this object has a lock of a + different type than \a mode, the lock is first released and then a + new lock is obtained. + + This function returns \e true if, after it executes, the file is + locked by this object, and \e false otherwise. + + \sa unlock(), isLocked(), lockMode() +*/ + +/*! + \fn bool QtLockedFile::unlock() + + Releases a lock. + + If the object has no lock, this function returns immediately. + + This function returns \e true if, after it executes, the file is + not locked by this object, and \e false otherwise. + + \sa lock(), isLocked(), lockMode() +*/ + +/*! + \fn QtLockedFile::~QtLockedFile() + + Destroys the \e QtLockedFile object. If any locks were held, they + are released. +*/ diff --git a/AirTV-Qt/qtsingleapplication/src/qtlockedfile.h b/AirTV-Qt/qtsingleapplication/src/qtlockedfile.h new file mode 100644 index 0000000..07a42bf --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/src/qtlockedfile.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + +#ifndef QTLOCKEDFILE_H +#define QTLOCKEDFILE_H + +#include +#ifdef Q_OS_WIN +#include +#endif + +#if defined(Q_WS_WIN) +# if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT) +# define QT_QTLOCKEDFILE_EXPORT +# elif defined(QT_QTLOCKEDFILE_IMPORT) +# if defined(QT_QTLOCKEDFILE_EXPORT) +# undef QT_QTLOCKEDFILE_EXPORT +# endif +# define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport) +# elif defined(QT_QTLOCKEDFILE_EXPORT) +# undef QT_QTLOCKEDFILE_EXPORT +# define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport) +# endif +#else +# define QT_QTLOCKEDFILE_EXPORT +#endif + +namespace QtLP_Private { + +class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile +{ +public: + enum LockMode { NoLock = 0, ReadLock, WriteLock }; + + QtLockedFile(); + QtLockedFile(const QString &name); + ~QtLockedFile(); + + bool open(OpenMode mode); + + bool lock(LockMode mode, bool block = true); + bool unlock(); + bool isLocked() const; + LockMode lockMode() const; + +private: +#ifdef Q_OS_WIN + Qt::HANDLE wmutex; + Qt::HANDLE rmutex; + QVector rmutexes; + QString mutexname; + + Qt::HANDLE getMutexHandle(int idx, bool doCreate); + bool waitMutex(Qt::HANDLE mutex, bool doBlock); + +#endif + LockMode m_lock_mode; +}; +} +#endif diff --git a/AirTV-Qt/qtsingleapplication/src/qtlockedfile_unix.cpp b/AirTV-Qt/qtsingleapplication/src/qtlockedfile_unix.cpp new file mode 100644 index 0000000..715c7d9 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/src/qtlockedfile_unix.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + +#include +#include +#include +#include + +#include "qtlockedfile.h" + +bool QtLockedFile::lock(LockMode mode, bool block) +{ + if (!isOpen()) { + qWarning("QtLockedFile::lock(): file is not opened"); + return false; + } + + if (mode == NoLock) + return unlock(); + + if (mode == m_lock_mode) + return true; + + if (m_lock_mode != NoLock) + unlock(); + + struct flock fl; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK; + int cmd = block ? F_SETLKW : F_SETLK; + int ret = fcntl(handle(), cmd, &fl); + + if (ret == -1) { + if (errno != EINTR && errno != EAGAIN) + qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); + return false; + } + + + m_lock_mode = mode; + return true; +} + + +bool QtLockedFile::unlock() +{ + if (!isOpen()) { + qWarning("QtLockedFile::unlock(): file is not opened"); + return false; + } + + if (!isLocked()) + return true; + + struct flock fl; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_UNLCK; + int ret = fcntl(handle(), F_SETLKW, &fl); + + if (ret == -1) { + qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); + return false; + } + + m_lock_mode = NoLock; + return true; +} + +QtLockedFile::~QtLockedFile() +{ + if (isOpen()) + unlock(); +} + diff --git a/AirTV-Qt/qtsingleapplication/src/qtlockedfile_win.cpp b/AirTV-Qt/qtsingleapplication/src/qtlockedfile_win.cpp new file mode 100644 index 0000000..4cd2003 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/src/qtlockedfile_win.cpp @@ -0,0 +1,206 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + +#include "qtlockedfile.h" +#include +#include + +#define MUTEX_PREFIX "QtLockedFile mutex " +// Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS +#define MAX_READERS MAXIMUM_WAIT_OBJECTS + +Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate) +{ + if (mutexname.isEmpty()) { + QFileInfo fi(*this); + mutexname = QString::fromLatin1(MUTEX_PREFIX) + + fi.absoluteFilePath().toLower(); + } + QString mname(mutexname); + if (idx >= 0) + mname += QString::number(idx); + + Qt::HANDLE mutex; + if (doCreate) { + QT_WA( { mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); }, + { mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } ); + if (!mutex) { + qErrnoWarning("QtLockedFile::lock(): CreateMutex failed"); + return 0; + } + } + else { + QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); }, + { mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } ); + if (!mutex) { + if (GetLastError() != ERROR_FILE_NOT_FOUND) + qErrnoWarning("QtLockedFile::lock(): OpenMutex failed"); + return 0; + } + } + return mutex; +} + +bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock) +{ + Q_ASSERT(mutex); + DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0); + switch (res) { + case WAIT_OBJECT_0: + case WAIT_ABANDONED: + return true; + break; + case WAIT_TIMEOUT: + break; + default: + qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed"); + } + return false; +} + + + +bool QtLockedFile::lock(LockMode mode, bool block) +{ + if (!isOpen()) { + qWarning("QtLockedFile::lock(): file is not opened"); + return false; + } + + if (mode == NoLock) + return unlock(); + + if (mode == m_lock_mode) + return true; + + if (m_lock_mode != NoLock) + unlock(); + + if (!wmutex && !(wmutex = getMutexHandle(-1, true))) + return false; + + if (!waitMutex(wmutex, block)) + return false; + + if (mode == ReadLock) { + int idx = 0; + for (; idx < MAX_READERS; idx++) { + rmutex = getMutexHandle(idx, false); + if (!rmutex || waitMutex(rmutex, false)) + break; + CloseHandle(rmutex); + } + bool ok = true; + if (idx >= MAX_READERS) { + qWarning("QtLockedFile::lock(): too many readers"); + rmutex = 0; + ok = false; + } + else if (!rmutex) { + rmutex = getMutexHandle(idx, true); + if (!rmutex || !waitMutex(rmutex, false)) + ok = false; + } + if (!ok && rmutex) { + CloseHandle(rmutex); + rmutex = 0; + } + ReleaseMutex(wmutex); + if (!ok) + return false; + } + else { + Q_ASSERT(rmutexes.isEmpty()); + for (int i = 0; i < MAX_READERS; i++) { + Qt::HANDLE mutex = getMutexHandle(i, false); + if (mutex) + rmutexes.append(mutex); + } + if (rmutexes.size()) { + DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(), + TRUE, block ? INFINITE : 0); + if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) { + if (res != WAIT_TIMEOUT) + qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed"); + m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky + unlock(); + return false; + } + } + } + + m_lock_mode = mode; + return true; +} + +bool QtLockedFile::unlock() +{ + if (!isOpen()) { + qWarning("QtLockedFile::unlock(): file is not opened"); + return false; + } + + if (!isLocked()) + return true; + + if (m_lock_mode == ReadLock) { + ReleaseMutex(rmutex); + CloseHandle(rmutex); + rmutex = 0; + } + else { + foreach(Qt::HANDLE mutex, rmutexes) { + ReleaseMutex(mutex); + CloseHandle(mutex); + } + rmutexes.clear(); + ReleaseMutex(wmutex); + } + + m_lock_mode = QtLockedFile::NoLock; + return true; +} + +QtLockedFile::~QtLockedFile() +{ + if (isOpen()) + unlock(); + if (wmutex) + CloseHandle(wmutex); +} diff --git a/AirTV-Qt/qtsingleapplication/src/qtsingleapplication.cpp b/AirTV-Qt/qtsingleapplication/src/qtsingleapplication.cpp new file mode 100644 index 0000000..5a8f1b0 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/src/qtsingleapplication.cpp @@ -0,0 +1,344 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + + +#include "qtsingleapplication.h" +#include "qtlocalpeer.h" +#include + + +/*! + \class QtSingleApplication qtsingleapplication.h + \brief The QtSingleApplication class provides an API to detect and + communicate with running instances of an application. + + This class allows you to create applications where only one + instance should be running at a time. I.e., if the user tries to + launch another instance, the already running instance will be + activated instead. Another usecase is a client-server system, + where the first started instance will assume the role of server, + and the later instances will act as clients of that server. + + By default, the full path of the executable file is used to + determine whether two processes are instances of the same + application. You can also provide an explicit identifier string + that will be compared instead. + + The application should create the QtSingleApplication object early + in the startup phase, and call isRunning() to find out if another + instance of this application is already running. If isRunning() + returns false, it means that no other instance is running, and + this instance has assumed the role as the running instance. In + this case, the application should continue with the initialization + of the application user interface before entering the event loop + with exec(), as normal. + + The messageReceived() signal will be emitted when the running + application receives messages from another instance of the same + application. When a message is received it might be helpful to the + user to raise the application so that it becomes visible. To + facilitate this, QtSingleApplication provides the + setActivationWindow() function and the activateWindow() slot. + + If isRunning() returns true, another instance is already + running. It may be alerted to the fact that another instance has + started by using the sendMessage() function. Also data such as + startup parameters (e.g. the name of the file the user wanted this + new instance to open) can be passed to the running instance with + this function. Then, the application should terminate (or enter + client mode). + + If isRunning() returns true, but sendMessage() fails, that is an + indication that the running instance is frozen. + + Here's an example that shows how to convert an existing + application to use QtSingleApplication. It is very simple and does + not make use of all QtSingleApplication's functionality (see the + examples for that). + + \code + // Original + int main(int argc, char **argv) + { + QApplication app(argc, argv); + + MyMainWidget mmw; + mmw.show(); + return app.exec(); + } + + // Single instance + int main(int argc, char **argv) + { + QtSingleApplication app(argc, argv); + + if (app.isRunning()) + return !app.sendMessage(someDataString); + + MyMainWidget mmw; + app.setActivationWindow(&mmw); + mmw.show(); + return app.exec(); + } + \endcode + + Once this QtSingleApplication instance is destroyed (normally when + the process exits or crashes), when the user next attempts to run the + application this instance will not, of course, be encountered. The + next instance to call isRunning() or sendMessage() will assume the + role as the new running instance. + + For console (non-GUI) applications, QtSingleCoreApplication may be + used instead of this class, to avoid the dependency on the QtGui + library. + + \sa QtSingleCoreApplication +*/ + + +void QtSingleApplication::sysInit(const QString &appId) +{ + actWin = 0; + peer = new QtLocalPeer(this, appId); + connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); +} + + +/*! + Creates a QtSingleApplication object. The application identifier + will be QCoreApplication::applicationFilePath(). \a argc, \a + argv, and \a GUIenabled are passed on to the QAppliation constructor. + + If you are creating a console application (i.e. setting \a + GUIenabled to false), you may consider using + QtSingleCoreApplication instead. +*/ + +QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled) + : QApplication(argc, argv, GUIenabled) +{ + sysInit(); +} + + +/*! + Creates a QtSingleApplication object with the application + identifier \a appId. \a argc and \a argv are passed on to the + QAppliation constructor. +*/ + +QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv) + : QApplication(argc, argv) +{ + sysInit(appId); +} + + +/*! + Creates a QtSingleApplication object. The application identifier + will be QCoreApplication::applicationFilePath(). \a argc, \a + argv, and \a type are passed on to the QAppliation constructor. +*/ +QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type) + : QApplication(argc, argv, type) +{ + sysInit(); +} + + +#if defined(Q_WS_X11) +/*! + Special constructor for X11, ref. the documentation of + QApplication's corresponding constructor. The application identifier + will be QCoreApplication::applicationFilePath(). \a dpy, \a visual, + and \a cmap are passed on to the QApplication constructor. +*/ +QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap) + : QApplication(dpy, visual, cmap) +{ + sysInit(); +} + +/*! + Special constructor for X11, ref. the documentation of + QApplication's corresponding constructor. The application identifier + will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a + argv, \a visual, and \a cmap are passed on to the QApplication + constructor. +*/ +QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) + : QApplication(dpy, argc, argv, visual, cmap) +{ + sysInit(); +} + +/*! + Special constructor for X11, ref. the documentation of + QApplication's corresponding constructor. The application identifier + will be \a appId. \a dpy, \a argc, \a + argv, \a visual, and \a cmap are passed on to the QApplication + constructor. +*/ +QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) + : QApplication(dpy, argc, argv, visual, cmap) +{ + sysInit(appId); +} +#endif + + +/*! + Returns true if another instance of this application is running; + otherwise false. + + This function does not find instances of this application that are + being run by a different user (on Windows: that are running in + another session). + + \sa sendMessage() +*/ + +bool QtSingleApplication::isRunning() +{ + return peer->isClient(); +} + + +/*! + Tries to send the text \a message to the currently running + instance. The QtSingleApplication object in the running instance + will emit the messageReceived() signal when it receives the + message. + + This function returns true if the message has been sent to, and + processed by, the current instance. If there is no instance + currently running, or if the running instance fails to process the + message within \a timeout milliseconds, this function return false. + + \sa isRunning(), messageReceived() +*/ +bool QtSingleApplication::sendMessage(const QString &message, int timeout) +{ + return peer->sendMessage(message, timeout); +} + + +/*! + Returns the application identifier. Two processes with the same + identifier will be regarded as instances of the same application. +*/ +QString QtSingleApplication::id() const +{ + return peer->applicationId(); +} + + +/*! + Sets the activation window of this application to \a aw. The + activation window is the widget that will be activated by + activateWindow(). This is typically the application's main window. + + If \a activateOnMessage is true (the default), the window will be + activated automatically every time a message is received, just prior + to the messageReceived() signal being emitted. + + \sa activateWindow(), messageReceived() +*/ + +void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage) +{ + actWin = aw; + if (activateOnMessage) + connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); + else + disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); +} + + +/*! + Returns the applications activation window if one has been set by + calling setActivationWindow(), otherwise returns 0. + + \sa setActivationWindow() +*/ +QWidget* QtSingleApplication::activationWindow() const +{ + return actWin; +} + + +/*! + De-minimizes, raises, and activates this application's activation window. + This function does nothing if no activation window has been set. + + This is a convenience function to show the user that this + application instance has been activated when he has tried to start + another instance. + + This function should typically be called in response to the + messageReceived() signal. By default, that will happen + automatically, if an activation window has been set. + + \sa setActivationWindow(), messageReceived(), initialize() +*/ +void QtSingleApplication::activateWindow() +{ + if (actWin) { + actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized); + actWin->raise(); + actWin->activateWindow(); + } +} + + +/*! + \fn void QtSingleApplication::messageReceived(const QString& message) + + This signal is emitted when the current instance receives a \a + message from another instance of this application. + + \sa sendMessage(), setActivationWindow(), activateWindow() +*/ + + +/*! + \fn void QtSingleApplication::initialize(bool dummy = true) + + \obsolete +*/ diff --git a/AirTV-Qt/qtsingleapplication/src/qtsingleapplication.h b/AirTV-Qt/qtsingleapplication/src/qtsingleapplication.h new file mode 100644 index 0000000..d1613a4 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/src/qtsingleapplication.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + +#ifndef QTSINGLEAPPLICATION_H +#define QTSINGLEAPPLICATION_H + +#include + +class QtLocalPeer; + +#if defined(Q_WS_WIN) +# if !defined(QT_QTSINGLEAPPLICATION_EXPORT) && !defined(QT_QTSINGLEAPPLICATION_IMPORT) +# define QT_QTSINGLEAPPLICATION_EXPORT +# elif defined(QT_QTSINGLEAPPLICATION_IMPORT) +# if defined(QT_QTSINGLEAPPLICATION_EXPORT) +# undef QT_QTSINGLEAPPLICATION_EXPORT +# endif +# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllimport) +# elif defined(QT_QTSINGLEAPPLICATION_EXPORT) +# undef QT_QTSINGLEAPPLICATION_EXPORT +# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllexport) +# endif +#else +# define QT_QTSINGLEAPPLICATION_EXPORT +#endif + +class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication +{ + Q_OBJECT + +public: + QtSingleApplication(int &argc, char **argv, bool GUIenabled = true); + QtSingleApplication(const QString &id, int &argc, char **argv); + QtSingleApplication(int &argc, char **argv, Type type); +#if defined(Q_WS_X11) + QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); + QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0); + QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); +#endif + + bool isRunning(); + QString id() const; + + void setActivationWindow(QWidget* aw, bool activateOnMessage = true); + QWidget* activationWindow() const; + + // Obsolete: + void initialize(bool dummy = true) + { isRunning(); Q_UNUSED(dummy) } + +public Q_SLOTS: + bool sendMessage(const QString &message, int timeout = 5000); + void activateWindow(); + + +Q_SIGNALS: + void messageReceived(const QString &message); + + +private: + void sysInit(const QString &appId = QString()); + QtLocalPeer *peer; + QWidget *actWin; +}; + +#endif // QTSINGLEAPPLICATION_H diff --git a/AirTV-Qt/qtsingleapplication/src/qtsingleapplication.pri b/AirTV-Qt/qtsingleapplication/src/qtsingleapplication.pri new file mode 100644 index 0000000..5909f04 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/src/qtsingleapplication.pri @@ -0,0 +1,16 @@ +include(../common.pri) +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD +QT *= network + +qtsingleapplication-uselib:!qtsingleapplication-buildlib { + LIBS += -L$$QTSINGLEAPPLICATION_LIBDIR -l$$QTSINGLEAPPLICATION_LIBNAME +} else { + SOURCES += $$PWD/qtsingleapplication.cpp $$PWD/qtlocalpeer.cpp + HEADERS += $$PWD/qtsingleapplication.h $$PWD/qtlocalpeer.h +} + +win32 { + contains(TEMPLATE, lib):contains(CONFIG, shared):DEFINES += QT_QTSINGLEAPPLICATION_EXPORT + else:qtsingleapplication-uselib:DEFINES += QT_QTSINGLEAPPLICATION_IMPORT +} diff --git a/AirTV-Qt/qtsingleapplication/src/qtsinglecoreapplication.cpp b/AirTV-Qt/qtsingleapplication/src/qtsinglecoreapplication.cpp new file mode 100644 index 0000000..cf60771 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/src/qtsinglecoreapplication.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + + +#include "qtsinglecoreapplication.h" +#include "qtlocalpeer.h" + +/*! + \class QtSingleCoreApplication qtsinglecoreapplication.h + \brief A variant of the QtSingleApplication class for non-GUI applications. + + This class is a variant of QtSingleApplication suited for use in + console (non-GUI) applications. It is an extension of + QCoreApplication (instead of QApplication). It does not require + the QtGui library. + + The API and usage is identical to QtSingleApplication, except that + functions relating to the "activation window" are not present, for + obvious reasons. Please refer to the QtSingleApplication + documentation for explanation of the usage. + + A QtSingleCoreApplication instance can communicate to a + QtSingleApplication instance if they share the same application + id. Hence, this class can be used to create a light-weight + command-line tool that sends commands to a GUI application. + + \sa QtSingleApplication +*/ + +/*! + Creates a QtSingleCoreApplication object. The application identifier + will be QCoreApplication::applicationFilePath(). \a argc and \a + argv are passed on to the QCoreAppliation constructor. +*/ + +QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv) + : QCoreApplication(argc, argv) +{ + peer = new QtLocalPeer(this); + connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); +} + + +/*! + Creates a QtSingleCoreApplication object with the application + identifier \a appId. \a argc and \a argv are passed on to the + QCoreAppliation constructor. +*/ +QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv) + : QCoreApplication(argc, argv) +{ + peer = new QtLocalPeer(this, appId); + connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); +} + + +/*! + Returns true if another instance of this application is running; + otherwise false. + + This function does not find instances of this application that are + being run by a different user (on Windows: that are running in + another session). + + \sa sendMessage() +*/ + +bool QtSingleCoreApplication::isRunning() +{ + return peer->isClient(); +} + + +/*! + Tries to send the text \a message to the currently running + instance. The QtSingleCoreApplication object in the running instance + will emit the messageReceived() signal when it receives the + message. + + This function returns true if the message has been sent to, and + processed by, the current instance. If there is no instance + currently running, or if the running instance fails to process the + message within \a timeout milliseconds, this function return false. + + \sa isRunning(), messageReceived() +*/ + +bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout) +{ + return peer->sendMessage(message, timeout); +} + + +/*! + Returns the application identifier. Two processes with the same + identifier will be regarded as instances of the same application. +*/ + +QString QtSingleCoreApplication::id() const +{ + return peer->applicationId(); +} + + +/*! + \fn void QtSingleCoreApplication::messageReceived(const QString& message) + + This signal is emitted when the current instance receives a \a + message from another instance of this application. + + \sa sendMessage() +*/ diff --git a/AirTV-Qt/qtsingleapplication/src/qtsinglecoreapplication.h b/AirTV-Qt/qtsingleapplication/src/qtsinglecoreapplication.h new file mode 100644 index 0000000..7cde4b8 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/src/qtsinglecoreapplication.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of a Qt Solutions component. +** +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +****************************************************************************/ + +#ifndef QTSINGLECOREAPPLICATION_H +#define QTSINGLECOREAPPLICATION_H + +#include + +class QtLocalPeer; + +class QtSingleCoreApplication : public QCoreApplication +{ + Q_OBJECT + +public: + QtSingleCoreApplication(int &argc, char **argv); + QtSingleCoreApplication(const QString &id, int &argc, char **argv); + + bool isRunning(); + QString id() const; + +public Q_SLOTS: + bool sendMessage(const QString &message, int timeout = 5000); + + +Q_SIGNALS: + void messageReceived(const QString &message); + + +private: + QtLocalPeer* peer; +}; + +#endif // QTSINGLECOREAPPLICATION_H diff --git a/AirTV-Qt/qtsingleapplication/src/qtsinglecoreapplication.pri b/AirTV-Qt/qtsingleapplication/src/qtsinglecoreapplication.pri new file mode 100644 index 0000000..d2d6cc3 --- /dev/null +++ b/AirTV-Qt/qtsingleapplication/src/qtsinglecoreapplication.pri @@ -0,0 +1,10 @@ +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD +HEADERS += $$PWD/qtsinglecoreapplication.h $$PWD/qtlocalpeer.h +SOURCES += $$PWD/qtsinglecoreapplication.cpp $$PWD/qtlocalpeer.cpp + +QT *= network + +win32:contains(TEMPLATE, lib):contains(CONFIG, shared) { + DEFINES += QT_QTSINGLECOREAPPLICATION_EXPORT=__declspec(dllexport) +} diff --git a/AirTV-Qt/raopcallbackhandler.cpp b/AirTV-Qt/raopcallbackhandler.cpp new file mode 100644 index 0000000..0852fe8 --- /dev/null +++ b/AirTV-Qt/raopcallbackhandler.cpp @@ -0,0 +1,45 @@ +#include "raopcallbackhandler.h" + +RaopCallbackHandler::RaopCallbackHandler(QObject *parent) : + QObject(parent) +{ +} + +void RaopCallbackHandler::audioInit(void *session, int bits, int channels, int samplerate) +{ + void **retval = (void**)session; + + AudioOutput *audioOutput = new AudioOutput(0); + audioOutput->init(bits, channels, samplerate); + audioOutput->start(); + *retval = audioOutput; + + m_outputList.append(audioOutput); +} + +void RaopCallbackHandler::audioSetVolume(void *session, float volume) +{ + AudioOutput *audioOutput = (AudioOutput*)session; + audioOutput->setVolume(volume); +} + +void RaopCallbackHandler::audioProcess(void *session, void *buffer, int buflen) +{ + AudioOutput *audioOutput = (AudioOutput*)session; + audioOutput->output((const char *)buffer, buflen); +} + +void RaopCallbackHandler::audioFlush(void *session) +{ + AudioOutput *audioOutput = (AudioOutput*)session; + audioOutput->flush(); +} + +void RaopCallbackHandler::audioDestroy(void *session) +{ + AudioOutput *audioOutput = (AudioOutput*)session; + m_outputList.removeAll(audioOutput); + + audioOutput->stop(); + delete audioOutput; +} diff --git a/AirTV-Qt/raopcallbackhandler.h b/AirTV-Qt/raopcallbackhandler.h new file mode 100644 index 0000000..3c253ed --- /dev/null +++ b/AirTV-Qt/raopcallbackhandler.h @@ -0,0 +1,27 @@ +#ifndef RAOPCALLBACKHANDLER_H +#define RAOPCALLBACKHANDLER_H + +#include + +#include "audiooutput.h" + +class RaopCallbackHandler : public QObject +{ + Q_OBJECT +public: + explicit RaopCallbackHandler(QObject *parent = 0); + +private: + QList m_outputList; + +signals: + +public slots: + void audioInit(void *session, int bits, int channels, int samplerate); + void audioSetVolume(void *session, float volume); + void audioProcess(void *session, void *buffer, int buflen); + void audioFlush(void *session); + void audioDestroy(void *session); +}; + +#endif // RAOPCALLBACKHANDLER_H diff --git a/AirTV-Qt/raopservice.cpp b/AirTV-Qt/raopservice.cpp new file mode 100644 index 0000000..79472c1 --- /dev/null +++ b/AirTV-Qt/raopservice.cpp @@ -0,0 +1,145 @@ +#include "raopservice.h" + +#include +#include + +static void +audio_init(void *cls, void **session, int bits, int channels, int samplerate) +{ + QMetaObject::invokeMethod((QObject*)cls, "audioInit", Qt::BlockingQueuedConnection, + Q_ARG(void*, (void*)session), + Q_ARG(int, bits), + Q_ARG(int, channels), + Q_ARG(int, samplerate)); +} + +static void +audio_set_volume(void *cls, void *session, float volume) +{ + QMetaObject::invokeMethod((QObject*)cls, "audioSetVolume", Qt::BlockingQueuedConnection, + Q_ARG(void*, session), + Q_ARG(float, volume)); +} + +static void +audio_process(void *cls, void *session, const void *buffer, int buflen) +{ + QMetaObject::invokeMethod((QObject*)cls, "audioProcess", Qt::BlockingQueuedConnection, + Q_ARG(void*, session), + Q_ARG(void*, (void*)buffer), + Q_ARG(int, buflen)); +} + +static void +audio_flush(void *cls, void *session) +{ + QMetaObject::invokeMethod((QObject*)cls, "audioFlush", Qt::BlockingQueuedConnection, + Q_ARG(void*, session)); +} + +static void +audio_destroy(void *cls, void *session) +{ + QMetaObject::invokeMethod((QObject*)cls, "audioDestroy", Qt::BlockingQueuedConnection, + Q_ARG(void*, session)); +} + +RaopService::RaopService(QObject *parent) : + QObject(parent), + m_dnssd(0), + m_raop(0) +{ + /* This whole hack is required because QAudioOutput + * needs to be created in a QThread, threads created + * outside Qt are not allowed (they have no eventloop) */ + m_handler.moveToThread(&m_thread); +} + +RaopService::~RaopService() +{ + this->stop(); + + dnssd_destroy(m_dnssd); + raop_destroy(m_raop); +} + +bool RaopService::init() +{ + const char hwaddr[] = { 0x48, 0x5d, 0x60, 0x7c, 0xee, 0x22 }; + raop_callbacks_t raop_cbs; + int error; + + raop_cbs.cls = &m_handler; + raop_cbs.audio_init = audio_init; + raop_cbs.audio_set_volume = audio_set_volume; + raop_cbs.audio_process = audio_process; + raop_cbs.audio_flush = audio_flush; + raop_cbs.audio_destroy = audio_destroy; + + QFile file("airport.key"); + if (!file.exists()) { + // This is used when running from Qt Creator on Mac + file.setFileName("../../../../airport.key"); + } + if (!file.exists()) { + // This is used when running from Qt Creator on Windows + file.setFileName("../airport.key"); + } + if (!file.exists()) { + return false; + } + file.open(QIODevice::ReadOnly); + QByteArray array = file.read(file.size()); + array.append('\0'); + + m_raop = raop_init(&raop_cbs, array.data(), hwaddr, sizeof(hwaddr)); + if (!m_raop) { + return false; + } + + m_dnssd = dnssd_init(hwaddr, sizeof(hwaddr), &error); + if (!m_dnssd) { + raop_destroy(m_raop); + m_raop = NULL; + return false; + } + + return true; +} + +bool RaopService::start(const QString & name, quint16 port) +{ + if (!m_raop || !m_dnssd || m_thread.isRunning()) { + return false; + } + + m_thread.start(); + if (raop_start(m_raop, &port) < 0) { + m_thread.quit(); + m_thread.wait(); + return false; + } + if (dnssd_register_raop(m_dnssd, name.toUtf8(), port) < 0) { + raop_stop(m_raop); + m_thread.quit(); + m_thread.wait(); + return false; + } + + return true; +} + +void RaopService::stop() +{ + if (m_dnssd) { + dnssd_unregister_raop(m_dnssd); + } + if (m_raop) { + raop_stop(m_raop); + } + if (m_thread.isRunning()) { + m_thread.quit(); + m_thread.wait(); + } +} + diff --git a/AirTV-Qt/raopservice.h b/AirTV-Qt/raopservice.h new file mode 100644 index 0000000..f82fec4 --- /dev/null +++ b/AirTV-Qt/raopservice.h @@ -0,0 +1,36 @@ +#ifndef RAOPSERVICE_H +#define RAOPSERVICE_H + +#include +#include + +#include "raopcallbackhandler.h" + +#include "dnssd.h" +#include "raop.h" + +class RaopService : public QObject +{ + Q_OBJECT +public: + explicit RaopService(QObject *parent = 0); + ~RaopService(); + + bool init(); + bool start(const QString & name=QString("AirTV"), quint16 port=5000); + void stop(); + +private: + dnssd_t * m_dnssd; + raop_t * m_raop; + + QThread m_thread; + RaopCallbackHandler m_handler; + +signals: + +public slots: + +}; + +#endif // RAOPSERVICE_H diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5a02298 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +CFLAGS:=-g -Wall -Iinclude/ +LDFLAGS:=-lm +LIB_OBJS=src/alac/alac.o src/crypto/aes.o src/crypto/bigint.o src/crypto/hmac.o src/crypto/md5.o src/crypto/rc4.o src/crypto/sha1.o src/sdp.o src/raop_buffer.o src/raop_rtp.o src/http_response.o src/http_request.o src/http_parser.o src/httpd.o src/raop.o src/rsakey.o src/rsapem.o src/dnssd.o src/netutils.o src/utils.o src/base64.o src/logger.o + + +all: shairport + +shairport: test/shairport.o $(LIB_OBJS) + $(CC) $(CFLAGS) test/shairport.o $(LIB_OBJS) -o $@ $(LDFLAGS) + +clean: + rm -f shairport test/*.o $(LIB_OBJS) diff --git a/README b/README new file mode 100644 index 0000000..b3d67cf --- /dev/null +++ b/README @@ -0,0 +1 @@ +Some text needs to be added here. diff --git a/airport.key b/airport.key new file mode 100644 index 0000000..ad041c4 --- /dev/null +++ b/airport.key @@ -0,0 +1,23 @@ +-----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----- diff --git a/include/dnssd.h b/include/dnssd.h new file mode 100644 index 0000000..32f3c98 --- /dev/null +++ b/include/dnssd.h @@ -0,0 +1,27 @@ +#ifndef DNSSD_H +#define DNSSD_H +#ifdef __cplusplus +extern "C" { +#endif + +#define DNSSD_ERROR_NOERROR 0 +#define DNSSD_ERROR_HWADDRLEN 1 +#define DNSSD_ERROR_OUTOFMEM 2 +#define DNSSD_ERROR_LIBNOTFOUND 3 +#define DNSSD_ERROR_PROCNOTFOUND 4 + +typedef struct dnssd_s dnssd_t; +dnssd_t *dnssd_init(const char *hwaddr, int hwaddrlen, int *error); + +int dnssd_register_raop(dnssd_t *dnssd, const char *name, unsigned short port); +int dnssd_register_airplay(dnssd_t *dnssd, const char *name, unsigned short port); + +void dnssd_unregister_raop(dnssd_t *dnssd); +void dnssd_unregister_airplay(dnssd_t *dnssd); + +void dnssd_destroy(dnssd_t *dnssd); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/include/raop.h b/include/raop.h new file mode 100644 index 0000000..604957a --- /dev/null +++ b/include/raop.h @@ -0,0 +1,30 @@ +#ifndef RAOP_H +#define RAOP_H +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct raop_s raop_t; + +struct raop_callbacks_s { + void* cls; + void (*audio_init)(void *cls, void **session, int bits, int channels, int samplerate); + void (*audio_set_volume)(void *cls, void *session, float volume); + void (*audio_process)(void *cls, void *session, const void *buffer, int buflen); + void (*audio_flush)(void *cls, void *session); + void (*audio_destroy)(void *cls, void *session); +}; +typedef struct raop_callbacks_s raop_callbacks_t; + +raop_t *raop_init(raop_callbacks_t *callbacks, const char *pemkey, const char *hwaddr, int hwaddrlen); +raop_t *raop_init_from_keyfile(raop_callbacks_t *callbacks, const char *keyfile, const char *hwaddr, int hwaddrlen); + +int raop_start(raop_t *raop, unsigned short *port); +void raop_stop(raop_t *raop); + +void raop_destroy(raop_t *raop); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/alac/alac.c b/src/alac/alac.c new file mode 100644 index 0000000..9d575ae --- /dev/null +++ b/src/alac/alac.c @@ -0,0 +1,1172 @@ +/* + * ALAC (Apple Lossless Audio Codec) decoder + * Copyright (c) 2005 David Hammerton + * All rights reserved. + * + * This is the actual decoder. + * + * http://crazney.net/programs/itunes/alac.html + * + * 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. + * + */ + +#ifdef __BIG_ENDIAN__ +static const int host_bigendian = 1; +#else +static const int host_bigendian = 0; +#endif + +#include +#include +#include +#ifdef _WIN32 + #include "stdint_win.h" +#else + #include +#endif + +#include "alac.h" + +#define _Swap32(v) do { \ + v = (((v) & 0x000000FF) << 0x18) | \ + (((v) & 0x0000FF00) << 0x08) | \ + (((v) & 0x00FF0000) >> 0x08) | \ + (((v) & 0xFF000000) >> 0x18); } while(0) + +#define _Swap16(v) do { \ + v = (((v) & 0x00FF) << 0x08) | \ + (((v) & 0xFF00) >> 0x08); } while (0) + +struct {signed int x:24;} se_struct_24; +#define SignExtend24(val) (se_struct_24.x = val) + +struct alac_file +{ + unsigned char *input_buffer; + int input_buffer_bitaccumulator; /* used so we can do arbitary + bit reads */ + + int samplesize; + int numchannels; + int bytespersample; + + + /* buffers */ + int32_t *predicterror_buffer_a; + int32_t *predicterror_buffer_b; + + int32_t *outputsamples_buffer_a; + int32_t *outputsamples_buffer_b; + + int32_t *uncompressed_bytes_buffer_a; + int32_t *uncompressed_bytes_buffer_b; + + + + /* stuff from setinfo */ + uint32_t setinfo_max_samples_per_frame; /* 0x1000 = 4096 */ /* max samples per frame? */ + uint8_t setinfo_7a; /* 0x00 */ + uint8_t setinfo_sample_size; /* 0x10 */ + uint8_t setinfo_rice_historymult; /* 0x28 */ + uint8_t setinfo_rice_initialhistory; /* 0x0a */ + uint8_t setinfo_rice_kmodifier; /* 0x0e */ + uint8_t setinfo_7f; /* 0x02 */ + uint16_t setinfo_80; /* 0x00ff */ + uint32_t setinfo_82; /* 0x000020e7 */ /* max sample size?? */ + uint32_t setinfo_86; /* 0x00069fe4 */ /* bit rate (avarge)?? */ + uint32_t setinfo_8a_rate; /* 0x0000ac44 */ + /* end setinfo stuff */ + +}; + + +static void 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); + + alac->outputsamples_buffer_a = malloc(alac->setinfo_max_samples_per_frame * 4); + alac->outputsamples_buffer_b = malloc(alac->setinfo_max_samples_per_frame * 4); + + alac->uncompressed_bytes_buffer_a = malloc(alac->setinfo_max_samples_per_frame * 4); + alac->uncompressed_bytes_buffer_b = malloc(alac->setinfo_max_samples_per_frame * 4); +} + +void alac_set_info(alac_file *alac, char *inputbuffer) +{ + char *ptr = inputbuffer; + ptr += 4; /* size */ + ptr += 4; /* frma */ + ptr += 4; /* alac */ + ptr += 4; /* size */ + ptr += 4; /* alac */ + + ptr += 4; /* 0 ? */ + + alac->setinfo_max_samples_per_frame = *(uint32_t*)ptr; /* buffer size / 2 ? */ + if (!host_bigendian) + _Swap32(alac->setinfo_max_samples_per_frame); + ptr += 4; + alac->setinfo_7a = *(uint8_t*)ptr; + ptr += 1; + alac->setinfo_sample_size = *(uint8_t*)ptr; + ptr += 1; + alac->setinfo_rice_historymult = *(uint8_t*)ptr; + ptr += 1; + alac->setinfo_rice_initialhistory = *(uint8_t*)ptr; + ptr += 1; + alac->setinfo_rice_kmodifier = *(uint8_t*)ptr; + ptr += 1; + alac->setinfo_7f = *(uint8_t*)ptr; + ptr += 1; + alac->setinfo_80 = *(uint16_t*)ptr; + if (!host_bigendian) + _Swap16(alac->setinfo_80); + ptr += 2; + alac->setinfo_82 = *(uint32_t*)ptr; + if (!host_bigendian) + _Swap32(alac->setinfo_82); + ptr += 4; + alac->setinfo_86 = *(uint32_t*)ptr; + if (!host_bigendian) + _Swap32(alac->setinfo_86); + ptr += 4; + alac->setinfo_8a_rate = *(uint32_t*)ptr; + if (!host_bigendian) + _Swap32(alac->setinfo_8a_rate); + ptr += 4; + + allocate_buffers(alac); + +} + +/* stream reading */ + +/* supports reading 1 to 16 bits, in big endian format */ +static uint32_t readbits_16(alac_file *alac, int bits) +{ + uint32_t result; + int new_accumulator; + + result = (alac->input_buffer[0] << 16) | + (alac->input_buffer[1] << 8) | + (alac->input_buffer[2]); + + /* shift left by the number of bits we've already read, + * so that the top 'n' bits of the 24 bits we read will + * be the return bits */ + result = result << alac->input_buffer_bitaccumulator; + + result = result & 0x00ffffff; + + /* and then only want the top 'n' bits from that, where + * n is 'bits' */ + result = result >> (24 - bits); + + new_accumulator = (alac->input_buffer_bitaccumulator + bits); + + /* increase the buffer pointer if we've read over n bytes. */ + alac->input_buffer += (new_accumulator >> 3); + + /* and the remainder goes back into the bit accumulator */ + alac->input_buffer_bitaccumulator = (new_accumulator & 7); + + return result; +} + +/* supports reading 1 to 32 bits, in big endian format */ +static uint32_t readbits(alac_file *alac, int bits) +{ + int32_t result = 0; + + if (bits > 16) + { + bits -= 16; + result = readbits_16(alac, 16) << bits; + } + + result |= readbits_16(alac, bits); + + return result; +} + +/* reads a single bit */ +static int readbit(alac_file *alac) +{ + int result; + int new_accumulator; + + result = alac->input_buffer[0]; + + result = result << alac->input_buffer_bitaccumulator; + + result = result >> 7 & 1; + + new_accumulator = (alac->input_buffer_bitaccumulator + 1); + + alac->input_buffer += (new_accumulator / 8); + + alac->input_buffer_bitaccumulator = (new_accumulator % 8); + + return result; +} + +static void unreadbits(alac_file *alac, int bits) +{ + int new_accumulator = (alac->input_buffer_bitaccumulator - bits); + + alac->input_buffer += (new_accumulator >> 3); + + alac->input_buffer_bitaccumulator = (new_accumulator & 7); + if (alac->input_buffer_bitaccumulator < 0) + alac->input_buffer_bitaccumulator *= -1; +} + +/* various implementations of count_leading_zero: + * the first one is the original one, the simplest and most + * obvious for what it's doing. never use this. + * then there are the asm ones. fill in as necessary + * and finally an unrolled and optimised c version + * to fall back to + */ +#if 0 +/* hideously inefficient. could use a bitmask search, + * alternatively bsr on x86, + */ +static int count_leading_zeros(int32_t input) +{ + int i = 0; + while (!(0x80000000 & input) && i < 32) + { + i++; + input = input << 1; + } + return i; +} +#elif defined(__GNUC__) && (defined(_X86) || defined(__i386) || defined(i386)) +/* for some reason the unrolled version (below) is + * actually faster than this. yay intel! + */ +static int count_leading_zeros(int input) +{ + int output = 0; + if (!input) return 32; + __asm("bsr %1, %0\n" + : "=r" (output) + : "r" (input)); + return (0x1f - output); +} +#elif defined(__GNUC__) +static int count_leading_zeros(int input) +{ + return __builtin_clz(input); +} +#elif defined(_MSC_VER) && defined(_M_IX86) +static int count_leading_zeros(int input) +{ + int output = 0; + if (!input) return 32; + __asm + { + mov eax, input; + mov edx, 0x1f; + bsr ecx, eax; + sub edx, ecx; + mov output, edx; + } + return output; +} +#else +#warning using generic count leading zeroes. You may wish to write one for your CPU / compiler +static int count_leading_zeros(int input) +{ + int output = 0; + int curbyte = 0; + + curbyte = input >> 24; + if (curbyte) goto found; + output += 8; + + curbyte = input >> 16; + if (curbyte & 0xff) goto found; + output += 8; + + curbyte = input >> 8; + if (curbyte & 0xff) goto found; + output += 8; + + curbyte = input; + if (curbyte & 0xff) goto found; + output += 8; + + return output; + +found: + if (!(curbyte & 0xf0)) + { + output += 4; + } + else + curbyte >>= 4; + + if (curbyte & 0x8) + return output; + if (curbyte & 0x4) + return output + 1; + if (curbyte & 0x2) + return output + 2; + if (curbyte & 0x1) + return output + 3; + + /* shouldn't get here: */ + return output + 4; +} +#endif + +#define RICE_THRESHOLD 8 // maximum number of bits for a rice prefix. + +int32_t entropy_decode_value(alac_file* alac, + int readSampleSize, + int k, + int rice_kmodifier_mask) +{ + int32_t x = 0; // decoded value + + // read x, number of 1s before 0 represent the rice value. + while (x <= RICE_THRESHOLD && readbit(alac)) + { + x++; + } + + if (x > RICE_THRESHOLD) + { + // read the number from the bit stream (raw value) + int32_t value; + + value = readbits(alac, readSampleSize); + + // mask value + value &= (((uint32_t)0xffffffff) >> (32 - readSampleSize)); + + x = value; + } + else + { + if (k != 1) + { + int extraBits = readbits(alac, k); + + // x = x * (2^k - 1) + x *= (((1 << k) - 1) & rice_kmodifier_mask); + + if (extraBits > 1) + x += extraBits - 1; + else + unreadbits(alac, 1); + } + } + + return x; +} + +void entropy_rice_decode(alac_file* alac, + int32_t* outputBuffer, + int outputSize, + int readSampleSize, + int rice_initialhistory, + int rice_kmodifier, + int rice_historymult, + int rice_kmodifier_mask) +{ + int outputCount; + int history = rice_initialhistory; + int signModifier = 0; + + for (outputCount = 0; outputCount < outputSize; outputCount++) + { + int32_t decodedValue; + int32_t finalValue; + int32_t k; + + k = 31 - rice_kmodifier - count_leading_zeros((history >> 9) + 3); + + if (k < 0) k += rice_kmodifier; + else k = rice_kmodifier; + + // note: don't use rice_kmodifier_mask here (set mask to 0xFFFFFFFF) + decodedValue = entropy_decode_value(alac, readSampleSize, k, 0xFFFFFFFF); + + decodedValue += signModifier; + finalValue = (decodedValue + 1) / 2; // inc by 1 and shift out sign bit + if (decodedValue & 1) // the sign is stored in the low bit + finalValue *= -1; + + outputBuffer[outputCount] = finalValue; + + signModifier = 0; + + // update history + history += (decodedValue * rice_historymult) + - ((history * rice_historymult) >> 9); + + if (decodedValue > 0xFFFF) + history = 0xFFFF; + + // special case, for compressed blocks of 0 + if ((history < 128) && (outputCount + 1 < outputSize)) + { + int32_t blockSize; + + signModifier = 1; + + k = count_leading_zeros(history) + ((history + 16) / 64) - 24; + + // note: blockSize is always 16bit + blockSize = entropy_decode_value(alac, 16, k, rice_kmodifier_mask); + + // got blockSize 0s + if (blockSize > 0) + { + memset(&outputBuffer[outputCount + 1], 0, blockSize * sizeof(*outputBuffer)); + outputCount += blockSize; + } + + if (blockSize > 0xFFFF) + signModifier = 0; + + history = 0; + } + } +} + +#define SIGN_EXTENDED32(val, bits) ((val << (32 - bits)) >> (32 - bits)) + +#define SIGN_ONLY(v) \ + ((v < 0) ? (-1) : \ + ((v > 0) ? (1) : \ + (0))) + +static void predictor_decompress_fir_adapt(int32_t *error_buffer, + int32_t *buffer_out, + int output_size, + int readsamplesize, + int16_t *predictor_coef_table, + int predictor_coef_num, + int predictor_quantitization) +{ + int i; + + /* first sample always copies */ + *buffer_out = *error_buffer; + + if (!predictor_coef_num) + { + if (output_size <= 1) return; + memcpy(buffer_out+1, error_buffer+1, (output_size-1) * 4); + return; + } + + if (predictor_coef_num == 0x1f) /* 11111 - max value of predictor_coef_num */ + { /* second-best case scenario for fir decompression, + * error describes a small difference from the previous sample only + */ + if (output_size <= 1) return; + for (i = 0; i < output_size - 1; i++) + { + int32_t prev_value; + int32_t error_value; + + prev_value = buffer_out[i]; + error_value = error_buffer[i+1]; + buffer_out[i+1] = SIGN_EXTENDED32((prev_value + error_value), readsamplesize); + } + return; + } + + /* read warm-up samples */ + if (predictor_coef_num > 0) + { + int i; + for (i = 0; i < predictor_coef_num; i++) + { + int32_t val; + + val = buffer_out[i] + error_buffer[i+1]; + + val = SIGN_EXTENDED32(val, readsamplesize); + + buffer_out[i+1] = val; + } + } + +#if 0 + /* 4 and 8 are very common cases (the only ones i've seen). these + * should be unrolled and optimised + */ + if (predictor_coef_num == 4) + { + /* FIXME: optimised general case */ + return; + } + + if (predictor_coef_table == 8) + { + /* FIXME: optimised general case */ + return; + } +#endif + + + /* general case */ + if (predictor_coef_num > 0) + { + for (i = predictor_coef_num + 1; + i < output_size; + i++) + { + int j; + int sum = 0; + int outval; + int error_val = error_buffer[i]; + + for (j = 0; j < predictor_coef_num; j++) + { + sum += (buffer_out[predictor_coef_num-j] - buffer_out[0]) * + predictor_coef_table[j]; + } + + outval = (1 << (predictor_quantitization-1)) + sum; + outval = outval >> predictor_quantitization; + outval = outval + buffer_out[0] + error_val; + outval = SIGN_EXTENDED32(outval, readsamplesize); + + buffer_out[predictor_coef_num+1] = outval; + + if (error_val > 0) + { + int predictor_num = predictor_coef_num - 1; + + while (predictor_num >= 0 && error_val > 0) + { + int val = buffer_out[0] - buffer_out[predictor_coef_num - predictor_num]; + int sign = SIGN_ONLY(val); + + predictor_coef_table[predictor_num] -= sign; + + val *= sign; /* absolute value */ + + error_val -= ((val >> predictor_quantitization) * + (predictor_coef_num - predictor_num)); + + predictor_num--; + } + } + else if (error_val < 0) + { + int predictor_num = predictor_coef_num - 1; + + while (predictor_num >= 0 && error_val < 0) + { + int val = buffer_out[0] - buffer_out[predictor_coef_num - predictor_num]; + int sign = - SIGN_ONLY(val); + + predictor_coef_table[predictor_num] -= sign; + + val *= sign; /* neg value */ + + error_val -= ((val >> predictor_quantitization) * + (predictor_coef_num - predictor_num)); + + predictor_num--; + } + } + + buffer_out++; + } + } +} + +void deinterlace_16(int32_t *buffer_a, int32_t *buffer_b, + int16_t *buffer_out, + int numchannels, int numsamples, + uint8_t interlacing_shift, + uint8_t interlacing_leftweight) +{ + int i; + if (numsamples <= 0) return; + + /* weighted interlacing */ + if (interlacing_leftweight) + { + for (i = 0; i < numsamples; i++) + { + int32_t difference, midright; + int16_t left; + int16_t right; + + midright = buffer_a[i]; + difference = buffer_b[i]; + + + right = midright - ((difference * interlacing_leftweight) >> interlacing_shift); + left = right + difference; + + /* output is always little endian */ + if (host_bigendian) + { + _Swap16(left); + _Swap16(right); + } + + buffer_out[i*numchannels] = left; + buffer_out[i*numchannels + 1] = right; + } + + return; + } + + /* otherwise basic interlacing took place */ + for (i = 0; i < numsamples; i++) + { + int16_t left, right; + + left = buffer_a[i]; + right = buffer_b[i]; + + /* output is always little endian */ + if (host_bigendian) + { + _Swap16(left); + _Swap16(right); + } + + buffer_out[i*numchannels] = left; + buffer_out[i*numchannels + 1] = right; + } +} + +void deinterlace_24(int32_t *buffer_a, int32_t *buffer_b, + int uncompressed_bytes, + int32_t *uncompressed_bytes_buffer_a, int32_t *uncompressed_bytes_buffer_b, + void *buffer_out, + int numchannels, int numsamples, + uint8_t interlacing_shift, + uint8_t interlacing_leftweight) +{ + int i; + if (numsamples <= 0) return; + + /* weighted interlacing */ + if (interlacing_leftweight) + { + for (i = 0; i < numsamples; i++) + { + int32_t difference, midright; + int32_t left; + int32_t right; + + midright = buffer_a[i]; + difference = buffer_b[i]; + + right = midright - ((difference * interlacing_leftweight) >> interlacing_shift); + left = right + difference; + + if (uncompressed_bytes) + { + uint32_t mask = ~(0xFFFFFFFF << (uncompressed_bytes * 8)); + left <<= (uncompressed_bytes * 8); + right <<= (uncompressed_bytes * 8); + + left |= uncompressed_bytes_buffer_a[i] & mask; + right |= uncompressed_bytes_buffer_b[i] & mask; + } + + ((uint8_t*)buffer_out)[i * numchannels * 3] = (left) & 0xFF; + ((uint8_t*)buffer_out)[i * numchannels * 3 + 1] = (left >> 8) & 0xFF; + ((uint8_t*)buffer_out)[i * numchannels * 3 + 2] = (left >> 16) & 0xFF; + + ((uint8_t*)buffer_out)[i * numchannels * 3 + 3] = (right) & 0xFF; + ((uint8_t*)buffer_out)[i * numchannels * 3 + 4] = (right >> 8) & 0xFF; + ((uint8_t*)buffer_out)[i * numchannels * 3 + 5] = (right >> 16) & 0xFF; + } + + return; + } + + /* otherwise basic interlacing took place */ + for (i = 0; i < numsamples; i++) + { + int32_t left, right; + + left = buffer_a[i]; + right = buffer_b[i]; + + if (uncompressed_bytes) + { + uint32_t mask = ~(0xFFFFFFFF << (uncompressed_bytes * 8)); + left <<= (uncompressed_bytes * 8); + right <<= (uncompressed_bytes * 8); + + left |= uncompressed_bytes_buffer_a[i] & mask; + right |= uncompressed_bytes_buffer_b[i] & mask; + } + + ((uint8_t*)buffer_out)[i * numchannels * 3] = (left) & 0xFF; + ((uint8_t*)buffer_out)[i * numchannels * 3 + 1] = (left >> 8) & 0xFF; + ((uint8_t*)buffer_out)[i * numchannels * 3 + 2] = (left >> 16) & 0xFF; + + ((uint8_t*)buffer_out)[i * numchannels * 3 + 3] = (right) & 0xFF; + ((uint8_t*)buffer_out)[i * numchannels * 3 + 4] = (right >> 8) & 0xFF; + ((uint8_t*)buffer_out)[i * numchannels * 3 + 5] = (right >> 16) & 0xFF; + + } + +} + +void decode_frame(alac_file *alac, + unsigned char *inbuffer, + void *outbuffer, int *outputsize) +{ + int channels; + int32_t outputsamples = alac->setinfo_max_samples_per_frame; + + /* setup the stream */ + alac->input_buffer = inbuffer; + alac->input_buffer_bitaccumulator = 0; + + channels = readbits(alac, 3); + + *outputsize = outputsamples * alac->bytespersample; + + switch(channels) + { + case 0: /* 1 channel */ + { + int hassize; + int isnotcompressed; + int readsamplesize; + + int uncompressed_bytes; + int ricemodifier; + + /* 2^result = something to do with output waiting. + * perhaps matters if we read > 1 frame in a pass? + */ + readbits(alac, 4); + + readbits(alac, 12); /* unknown, skip 12 bits */ + + hassize = readbits(alac, 1); /* the output sample size is stored soon */ + + uncompressed_bytes = readbits(alac, 2); /* number of bytes in the (compressed) stream that are not compressed */ + + isnotcompressed = readbits(alac, 1); /* whether the frame is compressed */ + + if (hassize) + { + /* now read the number of samples, + * as a 32bit integer */ + outputsamples = readbits(alac, 32); + *outputsize = outputsamples * alac->bytespersample; + } + + readsamplesize = alac->setinfo_sample_size - (uncompressed_bytes * 8); + + if (!isnotcompressed) + { /* so it is compressed */ + int16_t predictor_coef_table[32]; + int predictor_coef_num; + int prediction_type; + int prediction_quantitization; + int i; + + /* skip 16 bits, not sure what they are. seem to be used in + * two channel case */ + readbits(alac, 8); + readbits(alac, 8); + + prediction_type = readbits(alac, 4); + prediction_quantitization = readbits(alac, 4); + + ricemodifier = readbits(alac, 3); + predictor_coef_num = readbits(alac, 5); + + /* read the predictor table */ + for (i = 0; i < predictor_coef_num; i++) + { + predictor_coef_table[i] = (int16_t)readbits(alac, 16); + } + + if (uncompressed_bytes) + { + int i; + for (i = 0; i < outputsamples; i++) + { + alac->uncompressed_bytes_buffer_a[i] = readbits(alac, uncompressed_bytes * 8); + } + } + + entropy_rice_decode(alac, + alac->predicterror_buffer_a, + outputsamples, + readsamplesize, + alac->setinfo_rice_initialhistory, + alac->setinfo_rice_kmodifier, + ricemodifier * alac->setinfo_rice_historymult / 4, + (1 << alac->setinfo_rice_kmodifier) - 1); + + if (prediction_type == 0) + { /* adaptive fir */ + predictor_decompress_fir_adapt(alac->predicterror_buffer_a, + alac->outputsamples_buffer_a, + outputsamples, + readsamplesize, + predictor_coef_table, + predictor_coef_num, + prediction_quantitization); + } + else + { + fprintf(stderr, "FIXME: unhandled predicition type: %i\n", prediction_type); + /* i think the only other prediction type (or perhaps this is just a + * boolean?) runs adaptive fir twice.. like: + * predictor_decompress_fir_adapt(predictor_error, tempout, ...) + * predictor_decompress_fir_adapt(predictor_error, outputsamples ...) + * little strange.. + */ + } + + } + else + { /* not compressed, easy case */ + if (alac->setinfo_sample_size <= 16) + { + int i; + for (i = 0; i < outputsamples; i++) + { + int32_t audiobits = readbits(alac, alac->setinfo_sample_size); + + audiobits = SIGN_EXTENDED32(audiobits, alac->setinfo_sample_size); + + alac->outputsamples_buffer_a[i] = audiobits; + } + } + else + { + int i; + for (i = 0; i < outputsamples; i++) + { + int32_t audiobits; + + audiobits = readbits(alac, 16); + /* special case of sign extension.. + * as we'll be ORing the low 16bits into this */ + audiobits = audiobits << (alac->setinfo_sample_size - 16); + audiobits |= readbits(alac, alac->setinfo_sample_size - 16); + audiobits = SignExtend24(audiobits); + + alac->outputsamples_buffer_a[i] = audiobits; + } + } + uncompressed_bytes = 0; // always 0 for uncompressed + } + + switch(alac->setinfo_sample_size) + { + case 16: + { + int i; + for (i = 0; i < outputsamples; i++) + { + int16_t sample = alac->outputsamples_buffer_a[i]; + if (host_bigendian) + _Swap16(sample); + ((int16_t*)outbuffer)[i * alac->numchannels] = sample; + } + break; + } + case 24: + { + int i; + for (i = 0; i < outputsamples; i++) + { + int32_t sample = alac->outputsamples_buffer_a[i]; + + if (uncompressed_bytes) + { + uint32_t mask; + sample = sample << (uncompressed_bytes * 8); + mask = ~(0xFFFFFFFF << (uncompressed_bytes * 8)); + sample |= alac->uncompressed_bytes_buffer_a[i] & mask; + } + + ((uint8_t*)outbuffer)[i * alac->numchannels * 3] = (sample) & 0xFF; + ((uint8_t*)outbuffer)[i * alac->numchannels * 3 + 1] = (sample >> 8) & 0xFF; + ((uint8_t*)outbuffer)[i * alac->numchannels * 3 + 2] = (sample >> 16) & 0xFF; + } + break; + } + case 20: + case 32: + fprintf(stderr, "FIXME: unimplemented sample size %i\n", alac->setinfo_sample_size); + break; + default: + break; + } + break; + } + case 1: /* 2 channels */ + { + int hassize; + int isnotcompressed; + int readsamplesize; + + int uncompressed_bytes; + + uint8_t interlacing_shift; + uint8_t interlacing_leftweight; + + /* 2^result = something to do with output waiting. + * perhaps matters if we read > 1 frame in a pass? + */ + readbits(alac, 4); + + readbits(alac, 12); /* unknown, skip 12 bits */ + + hassize = readbits(alac, 1); /* the output sample size is stored soon */ + + uncompressed_bytes = readbits(alac, 2); /* the number of bytes in the (compressed) stream that are not compressed */ + + isnotcompressed = readbits(alac, 1); /* whether the frame is compressed */ + + if (hassize) + { + /* now read the number of samples, + * as a 32bit integer */ + outputsamples = readbits(alac, 32); + *outputsize = outputsamples * alac->bytespersample; + } + + readsamplesize = alac->setinfo_sample_size - (uncompressed_bytes * 8) + 1; + + if (!isnotcompressed) + { /* compressed */ + int16_t predictor_coef_table_a[32]; + int predictor_coef_num_a; + int prediction_type_a; + int prediction_quantitization_a; + int ricemodifier_a; + + int16_t predictor_coef_table_b[32]; + int predictor_coef_num_b; + int prediction_type_b; + int prediction_quantitization_b; + int ricemodifier_b; + + int i; + + interlacing_shift = readbits(alac, 8); + interlacing_leftweight = readbits(alac, 8); + + /******** channel 1 ***********/ + prediction_type_a = readbits(alac, 4); + prediction_quantitization_a = readbits(alac, 4); + + ricemodifier_a = readbits(alac, 3); + predictor_coef_num_a = readbits(alac, 5); + + /* read the predictor table */ + for (i = 0; i < predictor_coef_num_a; i++) + { + predictor_coef_table_a[i] = (int16_t)readbits(alac, 16); + } + + /******** channel 2 *********/ + prediction_type_b = readbits(alac, 4); + prediction_quantitization_b = readbits(alac, 4); + + ricemodifier_b = readbits(alac, 3); + predictor_coef_num_b = readbits(alac, 5); + + /* read the predictor table */ + for (i = 0; i < predictor_coef_num_b; i++) + { + predictor_coef_table_b[i] = (int16_t)readbits(alac, 16); + } + + /*********************/ + if (uncompressed_bytes) + { /* see mono case */ + int i; + for (i = 0; i < outputsamples; i++) + { + alac->uncompressed_bytes_buffer_a[i] = readbits(alac, uncompressed_bytes * 8); + alac->uncompressed_bytes_buffer_b[i] = readbits(alac, uncompressed_bytes * 8); + } + } + + /* channel 1 */ + entropy_rice_decode(alac, + alac->predicterror_buffer_a, + outputsamples, + readsamplesize, + alac->setinfo_rice_initialhistory, + alac->setinfo_rice_kmodifier, + ricemodifier_a * alac->setinfo_rice_historymult / 4, + (1 << alac->setinfo_rice_kmodifier) - 1); + + if (prediction_type_a == 0) + { /* adaptive fir */ + predictor_decompress_fir_adapt(alac->predicterror_buffer_a, + alac->outputsamples_buffer_a, + outputsamples, + readsamplesize, + predictor_coef_table_a, + predictor_coef_num_a, + prediction_quantitization_a); + } + else + { /* see mono case */ + fprintf(stderr, "FIXME: unhandled predicition type: %i\n", prediction_type_a); + } + + /* channel 2 */ + entropy_rice_decode(alac, + alac->predicterror_buffer_b, + outputsamples, + readsamplesize, + alac->setinfo_rice_initialhistory, + alac->setinfo_rice_kmodifier, + ricemodifier_b * alac->setinfo_rice_historymult / 4, + (1 << alac->setinfo_rice_kmodifier) - 1); + + if (prediction_type_b == 0) + { /* adaptive fir */ + predictor_decompress_fir_adapt(alac->predicterror_buffer_b, + alac->outputsamples_buffer_b, + outputsamples, + readsamplesize, + predictor_coef_table_b, + predictor_coef_num_b, + prediction_quantitization_b); + } + else + { + fprintf(stderr, "FIXME: unhandled predicition type: %i\n", prediction_type_b); + } + } + else + { /* not compressed, easy case */ + if (alac->setinfo_sample_size <= 16) + { + int i; + for (i = 0; i < outputsamples; i++) + { + int32_t audiobits_a, audiobits_b; + + audiobits_a = readbits(alac, alac->setinfo_sample_size); + audiobits_b = readbits(alac, alac->setinfo_sample_size); + + audiobits_a = SIGN_EXTENDED32(audiobits_a, alac->setinfo_sample_size); + audiobits_b = SIGN_EXTENDED32(audiobits_b, alac->setinfo_sample_size); + + alac->outputsamples_buffer_a[i] = audiobits_a; + alac->outputsamples_buffer_b[i] = audiobits_b; + } + } + else + { + int i; + for (i = 0; i < outputsamples; i++) + { + int32_t audiobits_a, audiobits_b; + + audiobits_a = readbits(alac, 16); + audiobits_a = audiobits_a << (alac->setinfo_sample_size - 16); + audiobits_a |= readbits(alac, alac->setinfo_sample_size - 16); + audiobits_a = SignExtend24(audiobits_a); + + audiobits_b = readbits(alac, 16); + audiobits_b = audiobits_b << (alac->setinfo_sample_size - 16); + audiobits_b |= readbits(alac, alac->setinfo_sample_size - 16); + audiobits_b = SignExtend24(audiobits_b); + + alac->outputsamples_buffer_a[i] = audiobits_a; + alac->outputsamples_buffer_b[i] = audiobits_b; + } + } + uncompressed_bytes = 0; // always 0 for uncompressed + interlacing_shift = 0; + interlacing_leftweight = 0; + } + + switch(alac->setinfo_sample_size) + { + case 16: + { + deinterlace_16(alac->outputsamples_buffer_a, + alac->outputsamples_buffer_b, + (int16_t*)outbuffer, + alac->numchannels, + outputsamples, + interlacing_shift, + interlacing_leftweight); + break; + } + case 24: + { + deinterlace_24(alac->outputsamples_buffer_a, + alac->outputsamples_buffer_b, + uncompressed_bytes, + alac->uncompressed_bytes_buffer_a, + alac->uncompressed_bytes_buffer_b, + (int16_t*)outbuffer, + alac->numchannels, + outputsamples, + interlacing_shift, + interlacing_leftweight); + break; + } + case 20: + case 32: + fprintf(stderr, "FIXME: unimplemented sample size %i\n", alac->setinfo_sample_size); + break; + default: + break; + } + + break; + } + } +} + +alac_file *create_alac(int samplesize, int numchannels) +{ + alac_file *newfile = malloc(sizeof(alac_file)); + + newfile->samplesize = samplesize; + newfile->numchannels = numchannels; + newfile->bytespersample = (samplesize / 8) * numchannels; + + return newfile; +} + diff --git a/src/alac/alac.h b/src/alac/alac.h new file mode 100644 index 0000000..23dbc52 --- /dev/null +++ b/src/alac/alac.h @@ -0,0 +1,13 @@ +#ifndef __ALAC__DECOMP_H +#define __ALAC__DECOMP_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); +void alac_set_info(alac_file *alac, char *inputbuffer); + +#endif /* __ALAC__DECOMP_H */ + diff --git a/src/alac/stdint_win.h b/src/alac/stdint_win.h new file mode 100644 index 0000000..a580eb7 --- /dev/null +++ b/src/alac/stdint_win.h @@ -0,0 +1,14 @@ + +#ifndef ALAC_STDINT_WIN_H__ +#define ALAC_STDINT_WIN_H__ + +typedef signed char int8_t; +typedef signed short int16_t; +typedef signed int int32_t; +typedef signed __int64 int64_t; +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned __int64 uint64_t; + +#endif // ALAC_STDINT_WIN_H__ \ No newline at end of file diff --git a/src/base64.c b/src/base64.c new file mode 100644 index 0000000..786e445 --- /dev/null +++ b/src/base64.c @@ -0,0 +1,268 @@ +#include +#include +#include + +#include "base64.h" + +#define DEFAULT_CHARLIST "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + +#define BASE64_PADDING 0x40 +#define BASE64_INVALID 0x80 + +struct base64_s { + char charlist[65]; + char charmap[256]; + int charmap_inited; + + int use_padding; + int skip_spaces; +}; + +static base64_t default_base64 = + { .charlist = DEFAULT_CHARLIST, + .use_padding = 1 }; + +static void +initialize_charmap(base64_t *base64) +{ + int i; + + memset(base64->charmap, BASE64_INVALID, sizeof(base64->charmap)); + for (i=0; i<64; i++) { + base64->charmap[(int)base64->charlist[i]] = i; + } + base64->charmap['='] = BASE64_PADDING; + base64->charmap_inited = 1; +} + +base64_t * +base64_init(const char *charlist, int use_padding, int skip_spaces) +{ + base64_t *base64; + int i; + + if (!charlist) { + charlist = DEFAULT_CHARLIST; + } + if (strlen(charlist) != 64) { + return NULL; + } + for (i=0; i<64; i++) { + switch (charlist[i]) { + case '\r': + case '\n': + case '=': + return NULL; + } + } + + base64 = calloc(1, sizeof(base64_t)); + if (!base64) { + return NULL; + } + strncpy(base64->charlist, charlist, sizeof(base64->charlist)-1); + base64->use_padding = use_padding; + base64->skip_spaces = skip_spaces; + + return base64; +} + +void +base64_destroy(base64_t *base64) +{ + free(base64); +} + +int +base64_encoded_length(base64_t *base64, int srclen) +{ + if (!base64) { + base64 = &default_base64; + } + + if (base64->use_padding) { + return ((srclen+2)/3*4)+1; + } else { + int strlen = 0; + switch (srclen % 3) { + case 2: + strlen += 1; + case 1: + strlen += 2; + default: + strlen += srclen/3*4; + break; + } + return strlen+1; + } +} + +int +base64_encode(base64_t *base64, char *dst, const unsigned char *src, int srclen) +{ + int src_idx, dst_idx; + int residue; + + if (!base64) { + base64 = &default_base64; + } + + residue = 0; + for (src_idx=dst_idx=0; src_idxcharlist[(residue>>2)%64]; + residue &= 0x03; + break; + case 1: + dst[dst_idx++] = base64->charlist[residue>>4]; + residue &= 0x0f; + break; + case 2: + dst[dst_idx++] = base64->charlist[residue>>6]; + dst[dst_idx++] = base64->charlist[residue&0x3f]; + residue = 0; + break; + } + residue <<= 8; + } + + /* Add padding */ + if (src_idx%3 == 1) { + dst[dst_idx++] = base64->charlist[residue>>4]; + if (base64->use_padding) { + dst[dst_idx++] = '='; + dst[dst_idx++] = '='; + } + } else if (src_idx%3 == 2) { + dst[dst_idx++] = base64->charlist[residue>>6]; + if (base64->use_padding) { + dst[dst_idx++] = '='; + } + } + dst[dst_idx] = '\0'; + return dst_idx; +} + +int +base64_decode(base64_t *base64, unsigned char **dst, const char *src, int srclen) +{ + char *inbuf; + int inbuflen; + unsigned char *outbuf; + int outbuflen; + char *srcptr; + int index; + + if (!base64) { + base64 = &default_base64; + } + if (!base64->charmap_inited) { + initialize_charmap(base64); + } + + inbuf = malloc(srclen+4); + if (!inbuf) { + return -1; + } + memcpy(inbuf, src, srclen); + inbuf[srclen] = '\0'; + + /* Remove all whitespaces from inbuf */ + if (base64->skip_spaces) { + int i, inbuflen = strlen(inbuf); + for (i=0; iuse_padding) { + if (inbuflen%4 == 1) { + free(inbuf); + return -2; + } + if (inbuflen%4 == 2) { + inbuf[inbuflen] = '='; + inbuf[inbuflen+1] = '='; + inbuf[inbuflen+2] = '\0'; + inbuflen += 2; + } else if (inbuflen%4 == 3) { + inbuf[inbuflen] = '='; + inbuf[inbuflen+1] = '\0'; + inbuflen += 1; + } + } + + /* Make sure data is divisible by 4 */ + if (inbuflen%4 != 0) { + free(inbuf); + return -3; + } + + /* Calculate the output length without padding */ + outbuflen = inbuflen/4*3; + if (inbuflen >= 4 && inbuf[inbuflen-1] == '=') { + outbuflen -= 1; + if (inbuf[inbuflen-2] == '=') { + outbuflen -= 1; + } + } + + /* Allocate buffer for outputting data */ + outbuf = malloc(outbuflen); + if (!outbuf) { + free(inbuf); + return -4; + } + + index = 0; + srcptr = inbuf; + while (*srcptr) { + unsigned char a = base64->charmap[(unsigned char)*(srcptr++)]; + unsigned char b = base64->charmap[(unsigned char)*(srcptr++)]; + unsigned char c = base64->charmap[(unsigned char)*(srcptr++)]; + unsigned char d = base64->charmap[(unsigned char)*(srcptr++)]; + + if (a == BASE64_INVALID || b == BASE64_INVALID || + c == BASE64_INVALID || d == BASE64_INVALID) { + return -5; + } + if (a == BASE64_PADDING || b == BASE64_PADDING) { + return -6; + } + + /* Update the first byte */ + outbuf[index++] = (a << 2) | ((b & 0x30) >> 4); + + /* Update the second byte */ + if (c == BASE64_PADDING) { + break; + } + outbuf[index++] = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); + + /* Update the third byte */ + if (d == BASE64_PADDING) { + break; + } + outbuf[index++] = ((c & 0x03) << 6) | d; + } + if (index != outbuflen) { + free(inbuf); + free(outbuf); + return -7; + } + + free(inbuf); + *dst = outbuf; + return outbuflen; +} diff --git a/src/base64.h b/src/base64.h new file mode 100644 index 0000000..e362108 --- /dev/null +++ b/src/base64.h @@ -0,0 +1,15 @@ +#ifndef BASE64_H +#define BASE64_H + +typedef struct base64_s base64_t; + +base64_t *base64_init(const char *charlist, int use_padding, int skip_spaces); + +int base64_encoded_length(base64_t *base64, int srclen); + +int base64_encode(base64_t *base64, char *dst, const unsigned char *src, int srclen); +int base64_decode(base64_t *base64, unsigned char **dst, const char *src, int srclen); + +void base64_destroy(base64_t *base64); + +#endif diff --git a/src/compat.h b/src/compat.h new file mode 100644 index 0000000..4a48e0f --- /dev/null +++ b/src/compat.h @@ -0,0 +1,24 @@ +#ifndef COMPAT_H +#define COMPAT_H + +#if defined(WIN32) +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include "memalign.h" +#include "sockets.h" +#include "threads.h" + +#endif diff --git a/src/crypto/aes.c b/src/crypto/aes.c new file mode 100644 index 0000000..9b07e27 --- /dev/null +++ b/src/crypto/aes.c @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * AES implementation - this is a small code version. There are much faster + * versions around but they are much larger in size (i.e. they use large + * submix tables). + */ + +#include +#include "os_port.h" +#include "crypto.h" + +/* all commented out in skeleton mode */ +#ifndef CONFIG_SSL_SKELETON_MODE + +#define rot1(x) (((x) << 24) | ((x) >> 8)) +#define rot2(x) (((x) << 16) | ((x) >> 16)) +#define rot3(x) (((x) << 8) | ((x) >> 24)) + +/* + * This cute trick does 4 'mul by two' at once. Stolen from + * Dr B. R. Gladman but I'm sure the u-(u>>7) is + * a standard graphics trick + * The key to this is that we need to xor with 0x1b if the top bit is set. + * a 1xxx xxxx 0xxx 0xxx First we mask the 7bit, + * b 1000 0000 0000 0000 then we shift right by 7 putting the 7bit in 0bit, + * c 0000 0001 0000 0000 we then subtract (c) from (b) + * d 0111 1111 0000 0000 and now we and with our mask + * e 0001 1011 0000 0000 + */ +#define mt 0x80808080 +#define ml 0x7f7f7f7f +#define mh 0xfefefefe +#define mm 0x1b1b1b1b +#define mul2(x,t) ((t)=((x)&mt), \ + ((((x)+(x))&mh)^(((t)-((t)>>7))&mm))) + +#define inv_mix_col(x,f2,f4,f8,f9) (\ + (f2)=mul2(x,f2), \ + (f4)=mul2(f2,f4), \ + (f8)=mul2(f4,f8), \ + (f9)=(x)^(f8), \ + (f8)=((f2)^(f4)^(f8)), \ + (f2)^=(f9), \ + (f4)^=(f9), \ + (f8)^=rot3(f2), \ + (f8)^=rot2(f4), \ + (f8)^rot1(f9)) + +/* + * AES S-box + */ +static const uint8_t aes_sbox[256] = +{ + 0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5, + 0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76, + 0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0, + 0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0, + 0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC, + 0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15, + 0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A, + 0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75, + 0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0, + 0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84, + 0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B, + 0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF, + 0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85, + 0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8, + 0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5, + 0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2, + 0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17, + 0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73, + 0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88, + 0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB, + 0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C, + 0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79, + 0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9, + 0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08, + 0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6, + 0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A, + 0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E, + 0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E, + 0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94, + 0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF, + 0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68, + 0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16, +}; + +/* + * AES is-box + */ +static const uint8_t aes_isbox[256] = +{ + 0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38, + 0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb, + 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87, + 0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb, + 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d, + 0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e, + 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2, + 0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25, + 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16, + 0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92, + 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda, + 0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84, + 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a, + 0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06, + 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02, + 0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b, + 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea, + 0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73, + 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85, + 0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e, + 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89, + 0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b, + 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20, + 0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4, + 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31, + 0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f, + 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d, + 0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef, + 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0, + 0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61, + 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26, + 0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d +}; + +static const unsigned char Rcon[30]= +{ + 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80, + 0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f, + 0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4, + 0xb3,0x7d,0xfa,0xef,0xc5,0x91, +}; + +/* ----- static functions ----- */ +static void AES_encrypt(const AES_CTX *ctx, uint32_t *data); +static void AES_decrypt(const AES_CTX *ctx, uint32_t *data); + +/* Perform doubling in Galois Field GF(2^8) using the irreducible polynomial + x^8+x^4+x^3+x+1 */ +static unsigned char AES_xtime(uint32_t x) +{ + return (x&0x80) ? (x<<1)^0x1b : x<<1; +} + +/** + * Set up AES with the key/iv and cipher size. + */ +void AES_set_key(AES_CTX *ctx, const uint8_t *key, + const uint8_t *iv, AES_MODE mode) +{ + int i, ii; + uint32_t *W, tmp, tmp2; + const unsigned char *ip; + int words; + + switch (mode) + { + case AES_MODE_128: + i = 10; + words = 4; + break; + + case AES_MODE_256: + i = 14; + words = 8; + break; + + default: /* fail silently */ + return; + } + + ctx->rounds = i; + ctx->key_size = words; + W = ctx->ks; + for (i = 0; i < words; i+=2) + { + W[i+0]= ((uint32_t)key[ 0]<<24)| + ((uint32_t)key[ 1]<<16)| + ((uint32_t)key[ 2]<< 8)| + ((uint32_t)key[ 3] ); + W[i+1]= ((uint32_t)key[ 4]<<24)| + ((uint32_t)key[ 5]<<16)| + ((uint32_t)key[ 6]<< 8)| + ((uint32_t)key[ 7] ); + key += 8; + } + + ip = Rcon; + ii = 4 * (ctx->rounds+1); + for (i = words; i> 8)&0xff]<<16; + tmp2|=(uint32_t)aes_sbox[(tmp>>16)&0xff]<<24; + tmp2|=(uint32_t)aes_sbox[(tmp>>24) ]; + tmp=tmp2^(((unsigned int)*ip)<<24); + ip++; + } + + if ((words == 8) && ((i % words) == 4)) + { + tmp2 =(uint32_t)aes_sbox[(tmp )&0xff] ; + tmp2|=(uint32_t)aes_sbox[(tmp>> 8)&0xff]<< 8; + tmp2|=(uint32_t)aes_sbox[(tmp>>16)&0xff]<<16; + tmp2|=(uint32_t)aes_sbox[(tmp>>24) ]<<24; + tmp=tmp2; + } + + W[i]=W[i-words]^tmp; + } + + /* copy the iv across */ + memcpy(ctx->iv, iv, 16); +} + +/** + * Change a key for decryption. + */ +void AES_convert_key(AES_CTX *ctx) +{ + int i; + uint32_t *k,w,t1,t2,t3,t4; + + k = ctx->ks; + k += 4; + + for (i= ctx->rounds*4; i > 4; i--) + { + w= *k; + w = inv_mix_col(w,t1,t2,t3,t4); + *k++ =w; + } +} + +/** + * Encrypt a byte sequence (with a block size 16) using the AES cipher. + */ +void AES_cbc_encrypt(AES_CTX *ctx, const uint8_t *msg, uint8_t *out, int length) +{ + int i; + uint32_t tin[4], tout[4], iv[4]; + + memcpy(iv, ctx->iv, AES_IV_SIZE); + for (i = 0; i < 4; i++) + tout[i] = ntohl(iv[i]); + + for (length -= AES_BLOCKSIZE; length >= 0; length -= AES_BLOCKSIZE) + { + uint32_t msg_32[4]; + uint32_t out_32[4]; + memcpy(msg_32, msg, AES_BLOCKSIZE); + msg += AES_BLOCKSIZE; + + for (i = 0; i < 4; i++) + tin[i] = ntohl(msg_32[i])^tout[i]; + + AES_encrypt(ctx, tin); + + for (i = 0; i < 4; i++) + { + tout[i] = tin[i]; + out_32[i] = htonl(tout[i]); + } + + memcpy(out, out_32, AES_BLOCKSIZE); + out += AES_BLOCKSIZE; + } + + for (i = 0; i < 4; i++) + iv[i] = htonl(tout[i]); + memcpy(ctx->iv, iv, AES_IV_SIZE); +} + +/** + * Decrypt a byte sequence (with a block size 16) using the AES cipher. + */ +void AES_cbc_decrypt(AES_CTX *ctx, const uint8_t *msg, uint8_t *out, int length) +{ + int i; + uint32_t tin[4], xor[4], tout[4], data[4], iv[4]; + + memcpy(iv, ctx->iv, AES_IV_SIZE); + for (i = 0; i < 4; i++) + xor[i] = ntohl(iv[i]); + + for (length -= 16; length >= 0; length -= 16) + { + uint32_t msg_32[4]; + uint32_t out_32[4]; + memcpy(msg_32, msg, AES_BLOCKSIZE); + msg += AES_BLOCKSIZE; + + for (i = 0; i < 4; i++) + { + tin[i] = ntohl(msg_32[i]); + data[i] = tin[i]; + } + + AES_decrypt(ctx, data); + + for (i = 0; i < 4; i++) + { + tout[i] = data[i]^xor[i]; + xor[i] = tin[i]; + out_32[i] = htonl(tout[i]); + } + + memcpy(out, out_32, AES_BLOCKSIZE); + out += AES_BLOCKSIZE; + } + + for (i = 0; i < 4; i++) + iv[i] = htonl(xor[i]); + memcpy(ctx->iv, iv, AES_IV_SIZE); +} + +/** + * Encrypt a single block (16 bytes) of data + */ +static void AES_encrypt(const AES_CTX *ctx, uint32_t *data) +{ + /* To make this code smaller, generate the sbox entries on the fly. + * This will have a really heavy effect upon performance. + */ + uint32_t tmp[4]; + uint32_t tmp1, old_a0, a0, a1, a2, a3, row; + int curr_rnd; + int rounds = ctx->rounds; + const uint32_t *k = ctx->ks; + + /* Pre-round key addition */ + for (row = 0; row < 4; row++) + data[row] ^= *(k++); + + /* Encrypt one block. */ + for (curr_rnd = 0; curr_rnd < rounds; curr_rnd++) + { + /* Perform ByteSub and ShiftRow operations together */ + for (row = 0; row < 4; row++) + { + a0 = (uint32_t)aes_sbox[(data[row%4]>>24)&0xFF]; + a1 = (uint32_t)aes_sbox[(data[(row+1)%4]>>16)&0xFF]; + a2 = (uint32_t)aes_sbox[(data[(row+2)%4]>>8)&0xFF]; + a3 = (uint32_t)aes_sbox[(data[(row+3)%4])&0xFF]; + + /* Perform MixColumn iff not last round */ + if (curr_rnd < (rounds - 1)) + { + tmp1 = a0 ^ a1 ^ a2 ^ a3; + old_a0 = a0; + a0 ^= tmp1 ^ AES_xtime(a0 ^ a1); + a1 ^= tmp1 ^ AES_xtime(a1 ^ a2); + a2 ^= tmp1 ^ AES_xtime(a2 ^ a3); + a3 ^= tmp1 ^ AES_xtime(a3 ^ old_a0); + } + + tmp[row] = ((a0 << 24) | (a1 << 16) | (a2 << 8) | a3); + } + + /* KeyAddition - note that it is vital that this loop is separate from + the MixColumn operation, which must be atomic...*/ + for (row = 0; row < 4; row++) + data[row] = tmp[row] ^ *(k++); + } +} + +/** + * Decrypt a single block (16 bytes) of data + */ +static void AES_decrypt(const AES_CTX *ctx, uint32_t *data) +{ + uint32_t tmp[4]; + uint32_t xt0,xt1,xt2,xt3,xt4,xt5,xt6; + uint32_t a0, a1, a2, a3, row; + int curr_rnd; + int rounds = ctx->rounds; + const uint32_t *k = ctx->ks + ((rounds+1)*4); + + /* pre-round key addition */ + for (row=4; row > 0;row--) + data[row-1] ^= *(--k); + + /* Decrypt one block */ + for (curr_rnd = 0; curr_rnd < rounds; curr_rnd++) + { + /* Perform ByteSub and ShiftRow operations together */ + for (row = 4; row > 0; row--) + { + a0 = aes_isbox[(data[(row+3)%4]>>24)&0xFF]; + a1 = aes_isbox[(data[(row+2)%4]>>16)&0xFF]; + a2 = aes_isbox[(data[(row+1)%4]>>8)&0xFF]; + a3 = aes_isbox[(data[row%4])&0xFF]; + + /* Perform MixColumn iff not last round */ + if (curr_rnd<(rounds-1)) + { + /* The MDS cofefficients (0x09, 0x0B, 0x0D, 0x0E) + are quite large compared to encryption; this + operation slows decryption down noticeably. */ + xt0 = AES_xtime(a0^a1); + xt1 = AES_xtime(a1^a2); + xt2 = AES_xtime(a2^a3); + xt3 = AES_xtime(a3^a0); + xt4 = AES_xtime(xt0^xt1); + xt5 = AES_xtime(xt1^xt2); + xt6 = AES_xtime(xt4^xt5); + + xt0 ^= a1^a2^a3^xt4^xt6; + xt1 ^= a0^a2^a3^xt5^xt6; + xt2 ^= a0^a1^a3^xt4^xt6; + xt3 ^= a0^a1^a2^xt5^xt6; + tmp[row-1] = ((xt0<<24)|(xt1<<16)|(xt2<<8)|xt3); + } + else + tmp[row-1] = ((a0<<24)|(a1<<16)|(a2<<8)|a3); + } + + for (row = 4; row > 0; row--) + data[row-1] = tmp[row-1] ^ *(--k); + } +} + +#endif diff --git a/src/crypto/bigint.c b/src/crypto/bigint.c new file mode 100644 index 0000000..e9ca04c --- /dev/null +++ b/src/crypto/bigint.c @@ -0,0 +1,1512 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @defgroup bigint_api Big Integer API + * @brief The bigint implementation as used by the axTLS project. + * + * The bigint library is for RSA encryption/decryption as well as signing. + * This code tries to minimise use of malloc/free by maintaining a small + * cache. A bigint context may maintain state by being made "permanent". + * It be be later released with a bi_depermanent() and bi_free() call. + * + * It supports the following reduction techniques: + * - Classical + * - Barrett + * - Montgomery + * + * It also implements the following: + * - Karatsuba multiplication + * - Squaring + * - Sliding window exponentiation + * - Chinese Remainder Theorem (implemented in rsa.c). + * + * All the algorithms used are pretty standard, and designed for different + * data bus sizes. Negative numbers are not dealt with at all, so a subtraction + * may need to be tested for negativity. + * + * This library steals some ideas from Jef Poskanzer + * + * and GMP . It gets most of its implementation + * detail from "The Handbook of Applied Cryptography" + * + * @{ + */ + +#include +#include +#include +#include +#include +#include "os_port.h" +#include "bigint.h" + +#define V1 v->comps[v->size-1] /**< v1 for division */ +#define V2 v->comps[v->size-2] /**< v2 for division */ +#define U(j) tmp_u->comps[tmp_u->size-j-1] /**< uj for division */ +#define Q(j) quotient->comps[quotient->size-j-1] /**< qj for division */ + +static bigint *bi_int_multiply(BI_CTX *ctx, bigint *bi, comp i); +static bigint *bi_int_divide(BI_CTX *ctx, bigint *biR, comp denom); +static bigint *alloc(BI_CTX *ctx, int size); +static bigint *trim(bigint *bi); +static void more_comps(bigint *bi, int n); +#if defined(CONFIG_BIGINT_KARATSUBA) || defined(CONFIG_BIGINT_BARRETT) || \ + defined(CONFIG_BIGINT_MONTGOMERY) +static bigint *comp_right_shift(bigint *biR, int num_shifts); +static bigint *comp_left_shift(bigint *biR, int num_shifts); +#endif + +#ifdef CONFIG_BIGINT_CHECK_ON +static void check(const bigint *bi); +#else +#define check(A) /**< disappears in normal production mode */ +#endif + + +/** + * @brief Start a new bigint context. + * @return A bigint context. + */ +BI_CTX *bi_initialize(void) +{ + /* calloc() sets everything to zero */ + BI_CTX *ctx = (BI_CTX *)calloc(1, sizeof(BI_CTX)); + + /* the radix */ + ctx->bi_radix = alloc(ctx, 2); + ctx->bi_radix->comps[0] = 0; + ctx->bi_radix->comps[1] = 1; + bi_permanent(ctx->bi_radix); + return ctx; +} + +/** + * @brief Close the bigint context and free any resources. + * + * Free up any used memory - a check is done if all objects were not + * properly freed. + * @param ctx [in] The bigint session context. + */ +void bi_terminate(BI_CTX *ctx) +{ + bi_depermanent(ctx->bi_radix); + bi_free(ctx, ctx->bi_radix); + + if (ctx->active_count != 0) + { +#ifdef CONFIG_SSL_FULL_MODE + printf("bi_terminate: there were %d un-freed bigints\n", + ctx->active_count); +#endif + abort(); + } + + bi_clear_cache(ctx); + free(ctx); +} + +/** + *@brief Clear the memory cache. + */ +void bi_clear_cache(BI_CTX *ctx) +{ + bigint *p, *pn; + + if (ctx->free_list == NULL) + return; + + for (p = ctx->free_list; p != NULL; p = pn) + { + pn = p->next; + free(p->comps); + free(p); + } + + ctx->free_count = 0; + ctx->free_list = NULL; +} + +/** + * @brief Increment the number of references to this object. + * It does not do a full copy. + * @param bi [in] The bigint to copy. + * @return A reference to the same bigint. + */ +bigint *bi_copy(bigint *bi) +{ + check(bi); + if (bi->refs != PERMANENT) + bi->refs++; + return bi; +} + +/** + * @brief Simply make a bigint object "unfreeable" if bi_free() is called on it. + * + * For this object to be freed, bi_depermanent() must be called. + * @param bi [in] The bigint to be made permanent. + */ +void bi_permanent(bigint *bi) +{ + check(bi); + if (bi->refs != 1) + { +#ifdef CONFIG_SSL_FULL_MODE + printf("bi_permanent: refs was not 1\n"); +#endif + abort(); + } + + bi->refs = PERMANENT; +} + +/** + * @brief Take a permanent object and make it eligible for freedom. + * @param bi [in] The bigint to be made back to temporary. + */ +void bi_depermanent(bigint *bi) +{ + check(bi); + if (bi->refs != PERMANENT) + { +#ifdef CONFIG_SSL_FULL_MODE + printf("bi_depermanent: bigint was not permanent\n"); +#endif + abort(); + } + + bi->refs = 1; +} + +/** + * @brief Free a bigint object so it can be used again. + * + * The memory itself it not actually freed, just tagged as being available + * @param ctx [in] The bigint session context. + * @param bi [in] The bigint to be freed. + */ +void bi_free(BI_CTX *ctx, bigint *bi) +{ + check(bi); + if (bi->refs == PERMANENT) + { + return; + } + + if (--bi->refs > 0) + { + return; + } + + bi->next = ctx->free_list; + ctx->free_list = bi; + ctx->free_count++; + + if (--ctx->active_count < 0) + { +#ifdef CONFIG_SSL_FULL_MODE + printf("bi_free: active_count went negative " + "- double-freed bigint?\n"); +#endif + abort(); + } +} + +/** + * @brief Convert an (unsigned) integer into a bigint. + * @param ctx [in] The bigint session context. + * @param i [in] The (unsigned) integer to be converted. + * + */ +bigint *int_to_bi(BI_CTX *ctx, comp i) +{ + bigint *biR = alloc(ctx, 1); + biR->comps[0] = i; + return biR; +} + +/** + * @brief Do a full copy of the bigint object. + * @param ctx [in] The bigint session context. + * @param bi [in] The bigint object to be copied. + */ +bigint *bi_clone(BI_CTX *ctx, const bigint *bi) +{ + bigint *biR = alloc(ctx, bi->size); + check(bi); + memcpy(biR->comps, bi->comps, bi->size*COMP_BYTE_SIZE); + return biR; +} + +/** + * @brief Perform an addition operation between two bigints. + * @param ctx [in] The bigint session context. + * @param bia [in] A bigint. + * @param bib [in] Another bigint. + * @return The result of the addition. + */ +bigint *bi_add(BI_CTX *ctx, bigint *bia, bigint *bib) +{ + int n; + comp carry = 0; + comp *pa, *pb; + + check(bia); + check(bib); + + n = max(bia->size, bib->size); + more_comps(bia, n+1); + more_comps(bib, n); + pa = bia->comps; + pb = bib->comps; + + do + { + comp sl, rl, cy1; + sl = *pa + *pb++; + rl = sl + carry; + cy1 = sl < *pa; + carry = cy1 | (rl < sl); + *pa++ = rl; + } while (--n != 0); + + *pa = carry; /* do overflow */ + bi_free(ctx, bib); + return trim(bia); +} + +/** + * @brief Perform a subtraction operation between two bigints. + * @param ctx [in] The bigint session context. + * @param bia [in] A bigint. + * @param bib [in] Another bigint. + * @param is_negative [out] If defined, indicates that the result was negative. + * is_negative may be null. + * @return The result of the subtraction. The result is always positive. + */ +bigint *bi_subtract(BI_CTX *ctx, + bigint *bia, bigint *bib, int *is_negative) +{ + int n = bia->size; + comp *pa, *pb, carry = 0; + + check(bia); + check(bib); + + more_comps(bib, n); + pa = bia->comps; + pb = bib->comps; + + do + { + comp sl, rl, cy1; + sl = *pa - *pb++; + rl = sl - carry; + cy1 = sl > *pa; + carry = cy1 | (rl > sl); + *pa++ = rl; + } while (--n != 0); + + if (is_negative) /* indicate a negative result */ + { + *is_negative = carry; + } + + bi_free(ctx, trim(bib)); /* put bib back to the way it was */ + return trim(bia); +} + +/** + * Perform a multiply between a bigint an an (unsigned) integer + */ +static bigint *bi_int_multiply(BI_CTX *ctx, bigint *bia, comp b) +{ + int j = 0, n = bia->size; + bigint *biR = alloc(ctx, n + 1); + comp carry = 0; + comp *r = biR->comps; + comp *a = bia->comps; + + check(bia); + + /* clear things to start with */ + memset(r, 0, ((n+1)*COMP_BYTE_SIZE)); + + do + { + long_comp tmp = *r + (long_comp)a[j]*b + carry; + *r++ = (comp)tmp; /* downsize */ + carry = (comp)(tmp >> COMP_BIT_SIZE); + } while (++j < n); + + *r = carry; + bi_free(ctx, bia); + return trim(biR); +} + +/** + * @brief Does both division and modulo calculations. + * + * Used extensively when doing classical reduction. + * @param ctx [in] The bigint session context. + * @param u [in] A bigint which is the numerator. + * @param v [in] Either the denominator or the modulus depending on the mode. + * @param is_mod [n] Determines if this is a normal division (0) or a reduction + * (1). + * @return The result of the division/reduction. + */ +bigint *bi_divide(BI_CTX *ctx, bigint *u, bigint *v, int is_mod) +{ + int n = v->size, m = u->size-n; + int j = 0, orig_u_size = u->size; + uint8_t mod_offset = ctx->mod_offset; + comp d; + bigint *quotient, *tmp_u; + comp q_dash; + + check(u); + check(v); + + /* if doing reduction and we are < mod, then return mod */ + if (is_mod && bi_compare(v, u) > 0) + { + bi_free(ctx, v); + return u; + } + + quotient = alloc(ctx, m+1); + tmp_u = alloc(ctx, n+1); + v = trim(v); /* make sure we have no leading 0's */ + d = (comp)((long_comp)COMP_RADIX/(V1+1)); + + /* clear things to start with */ + memset(quotient->comps, 0, ((quotient->size)*COMP_BYTE_SIZE)); + + /* normalise */ + if (d > 1) + { + u = bi_int_multiply(ctx, u, d); + + if (is_mod) + { + v = ctx->bi_normalised_mod[mod_offset]; + } + else + { + v = bi_int_multiply(ctx, v, d); + } + } + + if (orig_u_size == u->size) /* new digit position u0 */ + { + more_comps(u, orig_u_size + 1); + } + + do + { + /* get a temporary short version of u */ + memcpy(tmp_u->comps, &u->comps[u->size-n-1-j], (n+1)*COMP_BYTE_SIZE); + + /* calculate q' */ + if (U(0) == V1) + { + q_dash = COMP_RADIX-1; + } + else + { + q_dash = (comp)(((long_comp)U(0)*COMP_RADIX + U(1))/V1); + + if (v->size > 1 && V2) + { + /* we are implementing the following: + if (V2*q_dash > (((U(0)*COMP_RADIX + U(1) - + q_dash*V1)*COMP_RADIX) + U(2))) ... */ + comp inner = (comp)((long_comp)COMP_RADIX*U(0) + U(1) - + (long_comp)q_dash*V1); + if ((long_comp)V2*q_dash > (long_comp)inner*COMP_RADIX + U(2)) + { + q_dash--; + } + } + } + + /* multiply and subtract */ + if (q_dash) + { + int is_negative; + tmp_u = bi_subtract(ctx, tmp_u, + bi_int_multiply(ctx, bi_copy(v), q_dash), &is_negative); + more_comps(tmp_u, n+1); + + Q(j) = q_dash; + + /* add back */ + if (is_negative) + { + Q(j)--; + tmp_u = bi_add(ctx, tmp_u, bi_copy(v)); + + /* lop off the carry */ + tmp_u->size--; + v->size--; + } + } + else + { + Q(j) = 0; + } + + /* copy back to u */ + memcpy(&u->comps[u->size-n-1-j], tmp_u->comps, (n+1)*COMP_BYTE_SIZE); + } while (++j <= m); + + bi_free(ctx, tmp_u); + bi_free(ctx, v); + + if (is_mod) /* get the remainder */ + { + bi_free(ctx, quotient); + return bi_int_divide(ctx, trim(u), d); + } + else /* get the quotient */ + { + bi_free(ctx, u); + return trim(quotient); + } +} + +/* + * Perform an integer divide on a bigint. + */ +static bigint *bi_int_divide(BI_CTX *ctx, bigint *biR, comp denom) +{ + int i = biR->size - 1; + long_comp r = 0; + + check(biR); + + do + { + r = (r<comps[i]; + biR->comps[i] = (comp)(r / denom); + r %= denom; + } while (--i >= 0); + + return trim(biR); +} + +#ifdef CONFIG_BIGINT_MONTGOMERY +/** + * There is a need for the value of integer N' such that B^-1(B-1)-N^-1N'=1, + * where B^-1(B-1) mod N=1. Actually, only the least significant part of + * N' is needed, hence the definition N0'=N' mod b. We reproduce below the + * simple algorithm from an article by Dusse and Kaliski to efficiently + * find N0' from N0 and b */ +static comp modular_inverse(bigint *bim) +{ + int i; + comp t = 1; + comp two_2_i_minus_1 = 2; /* 2^(i-1) */ + long_comp two_2_i = 4; /* 2^i */ + comp N = bim->comps[0]; + + for (i = 2; i <= COMP_BIT_SIZE; i++) + { + if ((long_comp)N*t%two_2_i >= two_2_i_minus_1) + { + t += two_2_i_minus_1; + } + + two_2_i_minus_1 <<= 1; + two_2_i <<= 1; + } + + return (comp)(COMP_RADIX-t); +} +#endif + +#if defined(CONFIG_BIGINT_KARATSUBA) || defined(CONFIG_BIGINT_BARRETT) || \ + defined(CONFIG_BIGINT_MONTGOMERY) +/** + * Take each component and shift down (in terms of components) + */ +static bigint *comp_right_shift(bigint *biR, int num_shifts) +{ + int i = biR->size-num_shifts; + comp *x = biR->comps; + comp *y = &biR->comps[num_shifts]; + + check(biR); + + if (i <= 0) /* have we completely right shifted? */ + { + biR->comps[0] = 0; /* return 0 */ + biR->size = 1; + return biR; + } + + do + { + *x++ = *y++; + } while (--i > 0); + + biR->size -= num_shifts; + return biR; +} + +/** + * Take each component and shift it up (in terms of components) + */ +static bigint *comp_left_shift(bigint *biR, int num_shifts) +{ + int i = biR->size-1; + comp *x, *y; + + check(biR); + + if (num_shifts <= 0) + { + return biR; + } + + more_comps(biR, biR->size + num_shifts); + + x = &biR->comps[i+num_shifts]; + y = &biR->comps[i]; + + do + { + *x-- = *y--; + } while (i--); + + memset(biR->comps, 0, num_shifts*COMP_BYTE_SIZE); /* zero LS comps */ + return biR; +} +#endif + +/** + * @brief Allow a binary sequence to be imported as a bigint. + * @param ctx [in] The bigint session context. + * @param data [in] The data to be converted. + * @param size [in] The number of bytes of data. + * @return A bigint representing this data. + */ +bigint *bi_import(BI_CTX *ctx, const uint8_t *data, int size) +{ + bigint *biR = alloc(ctx, (size+COMP_BYTE_SIZE-1)/COMP_BYTE_SIZE); + int i, j = 0, offset = 0; + + memset(biR->comps, 0, biR->size*COMP_BYTE_SIZE); + + for (i = size-1; i >= 0; i--) + { + biR->comps[offset] += data[i] << (j*8); + + if (++j == COMP_BYTE_SIZE) + { + j = 0; + offset ++; + } + } + + return trim(biR); +} + +#ifdef CONFIG_SSL_FULL_MODE +/** + * @brief The testharness uses this code to import text hex-streams and + * convert them into bigints. + * @param ctx [in] The bigint session context. + * @param data [in] A string consisting of hex characters. The characters must + * be in upper case. + * @return A bigint representing this data. + */ +bigint *bi_str_import(BI_CTX *ctx, const char *data) +{ + int size = strlen(data); + bigint *biR = alloc(ctx, (size+COMP_NUM_NIBBLES-1)/COMP_NUM_NIBBLES); + int i, j = 0, offset = 0; + memset(biR->comps, 0, biR->size*COMP_BYTE_SIZE); + + for (i = size-1; i >= 0; i--) + { + int num = (data[i] <= '9') ? (data[i] - '0') : (data[i] - 'A' + 10); + biR->comps[offset] += num << (j*4); + + if (++j == COMP_NUM_NIBBLES) + { + j = 0; + offset ++; + } + } + + return biR; +} + +void bi_print(const char *label, bigint *x) +{ + int i, j; + + if (x == NULL) + { + printf("%s: (null)\n", label); + return; + } + + printf("%s: (size %d)\n", label, x->size); + for (i = x->size-1; i >= 0; i--) + { + for (j = COMP_NUM_NIBBLES-1; j >= 0; j--) + { + comp mask = 0x0f << (j*4); + comp num = (x->comps[i] & mask) >> (j*4); + putc((num <= 9) ? (num + '0') : (num + 'A' - 10), stdout); + } + } + + printf("\n"); +} +#endif + +/** + * @brief Take a bigint and convert it into a byte sequence. + * + * This is useful after a decrypt operation. + * @param ctx [in] The bigint session context. + * @param x [in] The bigint to be converted. + * @param data [out] The converted data as a byte stream. + * @param size [in] The maximum size of the byte stream. Unused bytes will be + * zeroed. + */ +void bi_export(BI_CTX *ctx, bigint *x, uint8_t *data, int size) +{ + int i, j, k = size-1; + + check(x); + memset(data, 0, size); /* ensure all leading 0's are cleared */ + + for (i = 0; i < x->size; i++) + { + for (j = 0; j < COMP_BYTE_SIZE; j++) + { + comp mask = 0xff << (j*8); + int num = (x->comps[i] & mask) >> (j*8); + data[k--] = num; + + if (k < 0) + { + goto buf_done; + } + } + } +buf_done: + + bi_free(ctx, x); +} + +/** + * @brief Pre-calculate some of the expensive steps in reduction. + * + * This function should only be called once (normally when a session starts). + * When the session is over, bi_free_mod() should be called. bi_mod_power() + * relies on this function being called. + * @param ctx [in] The bigint session context. + * @param bim [in] The bigint modulus that will be used. + * @param mod_offset [in] There are three moduluii that can be stored - the + * standard modulus, and its two primes p and q. This offset refers to which + * modulus we are referring to. + * @see bi_free_mod(), bi_mod_power(). + */ +void bi_set_mod(BI_CTX *ctx, bigint *bim, int mod_offset) +{ + int k = bim->size; + comp d = (comp)((long_comp)COMP_RADIX/(bim->comps[k-1]+1)); +#ifdef CONFIG_BIGINT_MONTGOMERY + bigint *R, *R2; +#endif + + ctx->bi_mod[mod_offset] = bim; + bi_permanent(ctx->bi_mod[mod_offset]); + ctx->bi_normalised_mod[mod_offset] = bi_int_multiply(ctx, bim, d); + bi_permanent(ctx->bi_normalised_mod[mod_offset]); + +#if defined(CONFIG_BIGINT_MONTGOMERY) + /* set montgomery variables */ + R = comp_left_shift(bi_clone(ctx, ctx->bi_radix), k-1); /* R */ + R2 = comp_left_shift(bi_clone(ctx, ctx->bi_radix), k*2-1); /* R^2 */ + ctx->bi_RR_mod_m[mod_offset] = bi_mod(ctx, R2); /* R^2 mod m */ + ctx->bi_R_mod_m[mod_offset] = bi_mod(ctx, R); /* R mod m */ + + bi_permanent(ctx->bi_RR_mod_m[mod_offset]); + bi_permanent(ctx->bi_R_mod_m[mod_offset]); + + ctx->N0_dash[mod_offset] = modular_inverse(ctx->bi_mod[mod_offset]); + +#elif defined (CONFIG_BIGINT_BARRETT) + ctx->bi_mu[mod_offset] = + bi_divide(ctx, comp_left_shift( + bi_clone(ctx, ctx->bi_radix), k*2-1), ctx->bi_mod[mod_offset], 0); + bi_permanent(ctx->bi_mu[mod_offset]); +#endif +} + +/** + * @brief Used when cleaning various bigints at the end of a session. + * @param ctx [in] The bigint session context. + * @param mod_offset [in] The offset to use. + * @see bi_set_mod(). + */ +void bi_free_mod(BI_CTX *ctx, int mod_offset) +{ + bi_depermanent(ctx->bi_mod[mod_offset]); + bi_free(ctx, ctx->bi_mod[mod_offset]); +#if defined (CONFIG_BIGINT_MONTGOMERY) + bi_depermanent(ctx->bi_RR_mod_m[mod_offset]); + bi_depermanent(ctx->bi_R_mod_m[mod_offset]); + bi_free(ctx, ctx->bi_RR_mod_m[mod_offset]); + bi_free(ctx, ctx->bi_R_mod_m[mod_offset]); +#elif defined(CONFIG_BIGINT_BARRETT) + bi_depermanent(ctx->bi_mu[mod_offset]); + bi_free(ctx, ctx->bi_mu[mod_offset]); +#endif + bi_depermanent(ctx->bi_normalised_mod[mod_offset]); + bi_free(ctx, ctx->bi_normalised_mod[mod_offset]); +} + +/** + * Perform a standard multiplication between two bigints. + * + * Barrett reduction has no need for some parts of the product, so ignore bits + * of the multiply. This routine gives Barrett its big performance + * improvements over Classical/Montgomery reduction methods. + */ +static bigint *regular_multiply(BI_CTX *ctx, bigint *bia, bigint *bib, + int inner_partial, int outer_partial) +{ + int i = 0, j; + int n = bia->size; + int t = bib->size; + bigint *biR = alloc(ctx, n + t); + comp *sr = biR->comps; + comp *sa = bia->comps; + comp *sb = bib->comps; + + check(bia); + check(bib); + + /* clear things to start with */ + memset(biR->comps, 0, ((n+t)*COMP_BYTE_SIZE)); + + do + { + long_comp tmp; + comp carry = 0; + int r_index = i; + j = 0; + + if (outer_partial && outer_partial-i > 0 && outer_partial < n) + { + r_index = outer_partial-1; + j = outer_partial-i-1; + } + + do + { + if (inner_partial && r_index >= inner_partial) + { + break; + } + + tmp = sr[r_index] + ((long_comp)sa[j])*sb[i] + carry; + sr[r_index++] = (comp)tmp; /* downsize */ + carry = tmp >> COMP_BIT_SIZE; + } while (++j < n); + + sr[r_index] = carry; + } while (++i < t); + + bi_free(ctx, bia); + bi_free(ctx, bib); + return trim(biR); +} + +#ifdef CONFIG_BIGINT_KARATSUBA +/* + * Karatsuba improves on regular multiplication due to only 3 multiplications + * being done instead of 4. The additional additions/subtractions are O(N) + * rather than O(N^2) and so for big numbers it saves on a few operations + */ +static bigint *karatsuba(BI_CTX *ctx, bigint *bia, bigint *bib, int is_square) +{ + bigint *x0, *x1; + bigint *p0, *p1, *p2; + int m; + + if (is_square) + { + m = (bia->size + 1)/2; + } + else + { + m = (max(bia->size, bib->size) + 1)/2; + } + + x0 = bi_clone(ctx, bia); + x0->size = m; + x1 = bi_clone(ctx, bia); + comp_right_shift(x1, m); + bi_free(ctx, bia); + + /* work out the 3 partial products */ + if (is_square) + { + p0 = bi_square(ctx, bi_copy(x0)); + p2 = bi_square(ctx, bi_copy(x1)); + p1 = bi_square(ctx, bi_add(ctx, x0, x1)); + } + else /* normal multiply */ + { + bigint *y0, *y1; + y0 = bi_clone(ctx, bib); + y0->size = m; + y1 = bi_clone(ctx, bib); + comp_right_shift(y1, m); + bi_free(ctx, bib); + + p0 = bi_multiply(ctx, bi_copy(x0), bi_copy(y0)); + p2 = bi_multiply(ctx, bi_copy(x1), bi_copy(y1)); + p1 = bi_multiply(ctx, bi_add(ctx, x0, x1), bi_add(ctx, y0, y1)); + } + + p1 = bi_subtract(ctx, + bi_subtract(ctx, p1, bi_copy(p2), NULL), bi_copy(p0), NULL); + + comp_left_shift(p1, m); + comp_left_shift(p2, 2*m); + return bi_add(ctx, p1, bi_add(ctx, p0, p2)); +} +#endif + +/** + * @brief Perform a multiplication operation between two bigints. + * @param ctx [in] The bigint session context. + * @param bia [in] A bigint. + * @param bib [in] Another bigint. + * @return The result of the multiplication. + */ +bigint *bi_multiply(BI_CTX *ctx, bigint *bia, bigint *bib) +{ + check(bia); + check(bib); + +#ifdef CONFIG_BIGINT_KARATSUBA + if (min(bia->size, bib->size) < MUL_KARATSUBA_THRESH) + { + return regular_multiply(ctx, bia, bib, 0, 0); + } + + return karatsuba(ctx, bia, bib, 0); +#else + return regular_multiply(ctx, bia, bib, 0, 0); +#endif +} + +#ifdef CONFIG_BIGINT_SQUARE +/* + * Perform the actual square operion. It takes into account overflow. + */ +static bigint *regular_square(BI_CTX *ctx, bigint *bi) +{ + int t = bi->size; + int i = 0, j; + bigint *biR = alloc(ctx, t*2+1); + comp *w = biR->comps; + comp *x = bi->comps; + long_comp carry; + memset(w, 0, biR->size*COMP_BYTE_SIZE); + + do + { + long_comp tmp = w[2*i] + (long_comp)x[i]*x[i]; + w[2*i] = (comp)tmp; + carry = tmp >> COMP_BIT_SIZE; + + for (j = i+1; j < t; j++) + { + uint8_t c = 0; + long_comp xx = (long_comp)x[i]*x[j]; + if ((COMP_MAX-xx) < xx) + c = 1; + + tmp = (xx<<1); + + if ((COMP_MAX-tmp) < w[i+j]) + c = 1; + + tmp += w[i+j]; + + if ((COMP_MAX-tmp) < carry) + c = 1; + + tmp += carry; + w[i+j] = (comp)tmp; + carry = tmp >> COMP_BIT_SIZE; + + if (c) + carry += COMP_RADIX; + } + + tmp = w[i+t] + carry; + w[i+t] = (comp)tmp; + w[i+t+1] = tmp >> COMP_BIT_SIZE; + } while (++i < t); + + bi_free(ctx, bi); + return trim(biR); +} + +/** + * @brief Perform a square operation on a bigint. + * @param ctx [in] The bigint session context. + * @param bia [in] A bigint. + * @return The result of the multiplication. + */ +bigint *bi_square(BI_CTX *ctx, bigint *bia) +{ + check(bia); + +#ifdef CONFIG_BIGINT_KARATSUBA + if (bia->size < SQU_KARATSUBA_THRESH) + { + return regular_square(ctx, bia); + } + + return karatsuba(ctx, bia, NULL, 1); +#else + return regular_square(ctx, bia); +#endif +} +#endif + +/** + * @brief Compare two bigints. + * @param bia [in] A bigint. + * @param bib [in] Another bigint. + * @return -1 if smaller, 1 if larger and 0 if equal. + */ +int bi_compare(bigint *bia, bigint *bib) +{ + int r, i; + + check(bia); + check(bib); + + if (bia->size > bib->size) + r = 1; + else if (bia->size < bib->size) + r = -1; + else + { + comp *a = bia->comps; + comp *b = bib->comps; + + /* Same number of components. Compare starting from the high end + * and working down. */ + r = 0; + i = bia->size - 1; + + do + { + if (a[i] > b[i]) + { + r = 1; + break; + } + else if (a[i] < b[i]) + { + r = -1; + break; + } + } while (--i >= 0); + } + + return r; +} + +/* + * Allocate and zero more components. Does not consume bi. + */ +static void more_comps(bigint *bi, int n) +{ + if (n > bi->max_comps) + { + bi->max_comps = max(bi->max_comps * 2, n); + bi->comps = (comp*)realloc(bi->comps, bi->max_comps * COMP_BYTE_SIZE); + } + + if (n > bi->size) + { + memset(&bi->comps[bi->size], 0, (n-bi->size)*COMP_BYTE_SIZE); + } + + bi->size = n; +} + +/* + * Make a new empty bigint. It may just use an old one if one is available. + * Otherwise get one off the heap. + */ +static bigint *alloc(BI_CTX *ctx, int size) +{ + bigint *biR; + + /* Can we recycle an old bigint? */ + if (ctx->free_list != NULL) + { + biR = ctx->free_list; + ctx->free_list = biR->next; + ctx->free_count--; + + if (biR->refs != 0) + { +#ifdef CONFIG_SSL_FULL_MODE + printf("alloc: refs was not 0\n"); +#endif + abort(); /* create a stack trace from a core dump */ + } + + more_comps(biR, size); + } + else + { + /* No free bigints available - create a new one. */ + biR = (bigint *)malloc(sizeof(bigint)); + biR->comps = (comp*)malloc(size * COMP_BYTE_SIZE); + biR->max_comps = size; /* give some space to spare */ + } + + biR->size = size; + biR->refs = 1; + biR->next = NULL; + ctx->active_count++; + return biR; +} + +/* + * Work out the highest '1' bit in an exponent. Used when doing sliding-window + * exponentiation. + */ +static int find_max_exp_index(bigint *biexp) +{ + int i = COMP_BIT_SIZE-1; + comp shift = COMP_RADIX/2; + comp test = biexp->comps[biexp->size-1]; /* assume no leading zeroes */ + + check(biexp); + + do + { + if (test & shift) + { + return i+(biexp->size-1)*COMP_BIT_SIZE; + } + + shift >>= 1; + } while (i-- != 0); + + return -1; /* error - must have been a leading 0 */ +} + +/* + * Is a particular bit is an exponent 1 or 0? Used when doing sliding-window + * exponentiation. + */ +static int exp_bit_is_one(bigint *biexp, int offset) +{ + comp test = biexp->comps[offset / COMP_BIT_SIZE]; + int num_shifts = offset % COMP_BIT_SIZE; + comp shift = 1; + int i; + + check(biexp); + + for (i = 0; i < num_shifts; i++) + { + shift <<= 1; + } + + return (test & shift) != 0; +} + +#ifdef CONFIG_BIGINT_CHECK_ON +/* + * Perform a sanity check on bi. + */ +static void check(const bigint *bi) +{ + if (bi->refs <= 0) + { + printf("check: zero or negative refs in bigint\n"); + abort(); + } + + if (bi->next != NULL) + { + printf("check: attempt to use a bigint from " + "the free list\n"); + abort(); + } +} +#endif + +/* + * Delete any leading 0's (and allow for 0). + */ +static bigint *trim(bigint *bi) +{ + check(bi); + + while (bi->comps[bi->size-1] == 0 && bi->size > 1) + { + bi->size--; + } + + return bi; +} + +#if defined(CONFIG_BIGINT_MONTGOMERY) +/** + * @brief Perform a single montgomery reduction. + * @param ctx [in] The bigint session context. + * @param bixy [in] A bigint. + * @return The result of the montgomery reduction. + */ +bigint *bi_mont(BI_CTX *ctx, bigint *bixy) +{ + int i = 0, n; + uint8_t mod_offset = ctx->mod_offset; + bigint *bim = ctx->bi_mod[mod_offset]; + comp mod_inv = ctx->N0_dash[mod_offset]; + + check(bixy); + + if (ctx->use_classical) /* just use classical instead */ + { + return bi_mod(ctx, bixy); + } + + n = bim->size; + + do + { + bixy = bi_add(ctx, bixy, comp_left_shift( + bi_int_multiply(ctx, bim, bixy->comps[i]*mod_inv), i)); + } while (++i < n); + + comp_right_shift(bixy, n); + + if (bi_compare(bixy, bim) >= 0) + { + bixy = bi_subtract(ctx, bixy, bim, NULL); + } + + return bixy; +} + +#elif defined(CONFIG_BIGINT_BARRETT) +/* + * Stomp on the most significant components to give the illusion of a "mod base + * radix" operation + */ +static bigint *comp_mod(bigint *bi, int mod) +{ + check(bi); + + if (bi->size > mod) + { + bi->size = mod; + } + + return bi; +} + +/** + * @brief Perform a single Barrett reduction. + * @param ctx [in] The bigint session context. + * @param bi [in] A bigint. + * @return The result of the Barrett reduction. + */ +bigint *bi_barrett(BI_CTX *ctx, bigint *bi) +{ + bigint *q1, *q2, *q3, *r1, *r2, *r; + uint8_t mod_offset = ctx->mod_offset; + bigint *bim = ctx->bi_mod[mod_offset]; + int k = bim->size; + + check(bi); + check(bim); + + /* use Classical method instead - Barrett cannot help here */ + if (bi->size > k*2) + { + return bi_mod(ctx, bi); + } + + q1 = comp_right_shift(bi_clone(ctx, bi), k-1); + + /* do outer partial multiply */ + q2 = regular_multiply(ctx, q1, ctx->bi_mu[mod_offset], 0, k-1); + q3 = comp_right_shift(q2, k+1); + r1 = comp_mod(bi, k+1); + + /* do inner partial multiply */ + r2 = comp_mod(regular_multiply(ctx, q3, bim, k+1, 0), k+1); + r = bi_subtract(ctx, r1, r2, NULL); + + /* if (r >= m) r = r - m; */ + if (bi_compare(r, bim) >= 0) + { + r = bi_subtract(ctx, r, bim, NULL); + } + + return r; +} +#endif /* CONFIG_BIGINT_BARRETT */ + +#ifdef CONFIG_BIGINT_SLIDING_WINDOW +/* + * Work out g1, g3, g5, g7... etc for the sliding-window algorithm + */ +static void precompute_slide_window(BI_CTX *ctx, int window, bigint *g1) +{ + int k = 1, i; + bigint *g2; + + for (i = 0; i < window-1; i++) /* compute 2^(window-1) */ + { + k <<= 1; + } + + ctx->g = (bigint **)malloc(k*sizeof(bigint *)); + ctx->g[0] = bi_clone(ctx, g1); + bi_permanent(ctx->g[0]); + g2 = bi_residue(ctx, bi_square(ctx, ctx->g[0])); /* g^2 */ + + for (i = 1; i < k; i++) + { + ctx->g[i] = bi_residue(ctx, bi_multiply(ctx, ctx->g[i-1], bi_copy(g2))); + bi_permanent(ctx->g[i]); + } + + bi_free(ctx, g2); + ctx->window = k; +} +#endif + +/** + * @brief Perform a modular exponentiation. + * + * This function requires bi_set_mod() to have been called previously. This is + * one of the optimisations used for performance. + * @param ctx [in] The bigint session context. + * @param bi [in] The bigint on which to perform the mod power operation. + * @param biexp [in] The bigint exponent. + * @return The result of the mod exponentiation operation + * @see bi_set_mod(). + */ +bigint *bi_mod_power(BI_CTX *ctx, bigint *bi, bigint *biexp) +{ + int i = find_max_exp_index(biexp), j, window_size = 1; + bigint *biR = int_to_bi(ctx, 1); + +#if defined(CONFIG_BIGINT_MONTGOMERY) + uint8_t mod_offset = ctx->mod_offset; + if (!ctx->use_classical) + { + /* preconvert */ + bi = bi_mont(ctx, + bi_multiply(ctx, bi, ctx->bi_RR_mod_m[mod_offset])); /* x' */ + bi_free(ctx, biR); + biR = ctx->bi_R_mod_m[mod_offset]; /* A */ + } +#endif + + check(bi); + check(biexp); + +#ifdef CONFIG_BIGINT_SLIDING_WINDOW + for (j = i; j > 32; j /= 5) /* work out an optimum size */ + window_size++; + + /* work out the slide constants */ + precompute_slide_window(ctx, window_size, bi); +#else /* just one constant */ + ctx->g = (bigint **)malloc(sizeof(bigint *)); + ctx->g[0] = bi_clone(ctx, bi); + ctx->window = 1; + bi_permanent(ctx->g[0]); +#endif + + /* if sliding-window is off, then only one bit will be done at a time and + * will reduce to standard left-to-right exponentiation */ + do + { + if (exp_bit_is_one(biexp, i)) + { + int l = i-window_size+1; + int part_exp = 0; + + if (l < 0) /* LSB of exponent will always be 1 */ + l = 0; + else + { + while (exp_bit_is_one(biexp, l) == 0) + l++; /* go back up */ + } + + /* build up the section of the exponent */ + for (j = i; j >= l; j--) + { + biR = bi_residue(ctx, bi_square(ctx, biR)); + if (exp_bit_is_one(biexp, j)) + part_exp++; + + if (j != l) + part_exp <<= 1; + } + + part_exp = (part_exp-1)/2; /* adjust for array */ + biR = bi_residue(ctx, bi_multiply(ctx, biR, ctx->g[part_exp])); + i = l-1; + } + else /* square it */ + { + biR = bi_residue(ctx, bi_square(ctx, biR)); + i--; + } + } while (i >= 0); + + /* cleanup */ + for (i = 0; i < ctx->window; i++) + { + bi_depermanent(ctx->g[i]); + bi_free(ctx, ctx->g[i]); + } + + free(ctx->g); + bi_free(ctx, bi); + bi_free(ctx, biexp); +#if defined CONFIG_BIGINT_MONTGOMERY + return ctx->use_classical ? biR : bi_mont(ctx, biR); /* convert back */ +#else /* CONFIG_BIGINT_CLASSICAL or CONFIG_BIGINT_BARRETT */ + return biR; +#endif +} + +#ifdef CONFIG_SSL_CERT_VERIFICATION +/** + * @brief Perform a modular exponentiation using a temporary modulus. + * + * We need this function to check the signatures of certificates. The modulus + * of this function is temporary as it's just used for authentication. + * @param ctx [in] The bigint session context. + * @param bi [in] The bigint to perform the exp/mod. + * @param bim [in] The temporary modulus. + * @param biexp [in] The bigint exponent. + * @return The result of the mod exponentiation operation + * @see bi_set_mod(). + */ +bigint *bi_mod_power2(BI_CTX *ctx, bigint *bi, bigint *bim, bigint *biexp) +{ + bigint *biR, *tmp_biR; + + /* Set up a temporary bigint context and transfer what we need between + * them. We need to do this since we want to keep the original modulus + * which is already in this context. This operation is only called when + * doing peer verification, and so is not expensive :-) */ + BI_CTX *tmp_ctx = bi_initialize(); + bi_set_mod(tmp_ctx, bi_clone(tmp_ctx, bim), BIGINT_M_OFFSET); + tmp_biR = bi_mod_power(tmp_ctx, + bi_clone(tmp_ctx, bi), + bi_clone(tmp_ctx, biexp)); + biR = bi_clone(ctx, tmp_biR); + bi_free(tmp_ctx, tmp_biR); + bi_free_mod(tmp_ctx, BIGINT_M_OFFSET); + bi_terminate(tmp_ctx); + + bi_free(ctx, bi); + bi_free(ctx, bim); + bi_free(ctx, biexp); + return biR; +} +#endif + +#ifdef CONFIG_BIGINT_CRT +/** + * @brief Use the Chinese Remainder Theorem to quickly perform RSA decrypts. + * + * @param ctx [in] The bigint session context. + * @param bi [in] The bigint to perform the exp/mod. + * @param dP [in] CRT's dP bigint + * @param dQ [in] CRT's dQ bigint + * @param p [in] CRT's p bigint + * @param q [in] CRT's q bigint + * @param qInv [in] CRT's qInv bigint + * @return The result of the CRT operation + */ +bigint *bi_crt(BI_CTX *ctx, bigint *bi, + bigint *dP, bigint *dQ, + bigint *p, bigint *q, bigint *qInv) +{ + bigint *m1, *m2, *h; + + /* Montgomery has a condition the 0 < x, y < m and these products violate + * that condition. So disable Montgomery when using CRT */ +#if defined(CONFIG_BIGINT_MONTGOMERY) + ctx->use_classical = 1; +#endif + ctx->mod_offset = BIGINT_P_OFFSET; + m1 = bi_mod_power(ctx, bi_copy(bi), dP); + + ctx->mod_offset = BIGINT_Q_OFFSET; + m2 = bi_mod_power(ctx, bi, dQ); + + h = bi_subtract(ctx, bi_add(ctx, m1, p), bi_copy(m2), NULL); + h = bi_multiply(ctx, h, qInv); + ctx->mod_offset = BIGINT_P_OFFSET; + h = bi_residue(ctx, h); +#if defined(CONFIG_BIGINT_MONTGOMERY) + ctx->use_classical = 0; /* reset for any further operation */ +#endif + return bi_add(ctx, m2, bi_multiply(ctx, q, h)); +} +#endif +/** @} */ diff --git a/src/crypto/bigint.h b/src/crypto/bigint.h new file mode 100644 index 0000000..2966a3e --- /dev/null +++ b/src/crypto/bigint.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BIGINT_HEADER +#define BIGINT_HEADER + +#include "crypto.h" + +BI_CTX *bi_initialize(void); +void bi_terminate(BI_CTX *ctx); +void bi_permanent(bigint *bi); +void bi_depermanent(bigint *bi); +void bi_clear_cache(BI_CTX *ctx); +void bi_free(BI_CTX *ctx, bigint *bi); +bigint *bi_copy(bigint *bi); +bigint *bi_clone(BI_CTX *ctx, const bigint *bi); +void bi_export(BI_CTX *ctx, bigint *bi, uint8_t *data, int size); +bigint *bi_import(BI_CTX *ctx, const uint8_t *data, int len); +bigint *int_to_bi(BI_CTX *ctx, comp i); + +/* the functions that actually do something interesting */ +bigint *bi_add(BI_CTX *ctx, bigint *bia, bigint *bib); +bigint *bi_subtract(BI_CTX *ctx, bigint *bia, + bigint *bib, int *is_negative); +bigint *bi_divide(BI_CTX *ctx, bigint *bia, bigint *bim, int is_mod); +bigint *bi_multiply(BI_CTX *ctx, bigint *bia, bigint *bib); +bigint *bi_mod_power(BI_CTX *ctx, bigint *bi, bigint *biexp); +bigint *bi_mod_power2(BI_CTX *ctx, bigint *bi, bigint *bim, bigint *biexp); +int bi_compare(bigint *bia, bigint *bib); +void bi_set_mod(BI_CTX *ctx, bigint *bim, int mod_offset); +void bi_free_mod(BI_CTX *ctx, int mod_offset); + +#ifdef CONFIG_SSL_FULL_MODE +void bi_print(const char *label, bigint *bi); +bigint *bi_str_import(BI_CTX *ctx, const char *data); +#endif + +/** + * @def bi_mod + * Find the residue of B. bi_set_mod() must be called before hand. + */ +#define bi_mod(A, B) bi_divide(A, B, ctx->bi_mod[ctx->mod_offset], 1) + +/** + * bi_residue() is technically the same as bi_mod(), but it uses the + * appropriate reduction technique (which is bi_mod() when doing classical + * reduction). + */ +#if defined(CONFIG_BIGINT_MONTGOMERY) +#define bi_residue(A, B) bi_mont(A, B) +bigint *bi_mont(BI_CTX *ctx, bigint *bixy); +#elif defined(CONFIG_BIGINT_BARRETT) +#define bi_residue(A, B) bi_barrett(A, B) +bigint *bi_barrett(BI_CTX *ctx, bigint *bi); +#else /* if defined(CONFIG_BIGINT_CLASSICAL) */ +#define bi_residue(A, B) bi_mod(A, B) +#endif + +#ifdef CONFIG_BIGINT_SQUARE +bigint *bi_square(BI_CTX *ctx, bigint *bi); +#else +#define bi_square(A, B) bi_multiply(A, bi_copy(B), B) +#endif + +#ifdef CONFIG_BIGINT_CRT +bigint *bi_crt(BI_CTX *ctx, bigint *bi, + bigint *dP, bigint *dQ, + bigint *p, bigint *q, + bigint *qInv); +#endif + +#endif diff --git a/src/crypto/bigint_impl.h b/src/crypto/bigint_impl.h new file mode 100644 index 0000000..820620d --- /dev/null +++ b/src/crypto/bigint_impl.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BIGINT_IMPL_HEADER +#define BIGINT_IMPL_HEADER + +/* Maintain a number of precomputed variables when doing reduction */ +#define BIGINT_M_OFFSET 0 /**< Normal modulo offset. */ +#ifdef CONFIG_BIGINT_CRT +#define BIGINT_P_OFFSET 1 /**< p modulo offset. */ +#define BIGINT_Q_OFFSET 2 /**< q module offset. */ +#define BIGINT_NUM_MODS 3 /**< The number of modulus constants used. */ +#else +#define BIGINT_NUM_MODS 1 +#endif + +/* Architecture specific functions for big ints */ +#if defined(CONFIG_INTEGER_8BIT) +#define COMP_RADIX 256U /**< Max component + 1 */ +#define COMP_MAX 0xFFFFU/**< (Max dbl comp -1) */ +#define COMP_BIT_SIZE 8 /**< Number of bits in a component. */ +#define COMP_BYTE_SIZE 1 /**< Number of bytes in a component. */ +#define COMP_NUM_NIBBLES 2 /**< Used For diagnostics only. */ +typedef uint8_t comp; /**< A single precision component. */ +typedef uint16_t long_comp; /**< A double precision component. */ +typedef int16_t slong_comp; /**< A signed double precision component. */ +#elif defined(CONFIG_INTEGER_16BIT) +#define COMP_RADIX 65536U /**< Max component + 1 */ +#define COMP_MAX 0xFFFFFFFFU/**< (Max dbl comp -1) */ +#define COMP_BIT_SIZE 16 /**< Number of bits in a component. */ +#define COMP_BYTE_SIZE 2 /**< Number of bytes in a component. */ +#define COMP_NUM_NIBBLES 4 /**< Used For diagnostics only. */ +typedef uint16_t comp; /**< A single precision component. */ +typedef uint32_t long_comp; /**< A double precision component. */ +typedef int32_t slong_comp; /**< A signed double precision component. */ +#else /* regular 32 bit */ +#if defined(WIN32) && !defined(__GNUC__) +#define COMP_RADIX 4294967296i64 +#define COMP_MAX 0xFFFFFFFFFFFFFFFFui64 +#else +#define COMP_RADIX 4294967296ULL /**< Max component + 1 */ +#define COMP_MAX 0xFFFFFFFFFFFFFFFFULL/**< (Max dbl comp -1) */ +#endif +#define COMP_BIT_SIZE 32 /**< Number of bits in a component. */ +#define COMP_BYTE_SIZE 4 /**< Number of bytes in a component. */ +#define COMP_NUM_NIBBLES 8 /**< Used For diagnostics only. */ +typedef uint32_t comp; /**< A single precision component. */ +typedef uint64_t long_comp; /**< A double precision component. */ +typedef int64_t slong_comp; /**< A signed double precision component. */ +#endif + +/** + * @struct _bigint + * @brief A big integer basic object + */ +struct _bigint +{ + struct _bigint* next; /**< The next bigint in the cache. */ + short size; /**< The number of components in this bigint. */ + short max_comps; /**< The heapsize allocated for this bigint */ + int refs; /**< An internal reference count. */ + comp* comps; /**< A ptr to the actual component data */ +}; + +typedef struct _bigint bigint; /**< An alias for _bigint */ + +/** + * Maintains the state of the cache, and a number of variables used in + * reduction. + */ +typedef struct /**< A big integer "session" context. */ +{ + bigint *active_list; /**< Bigints currently used. */ + bigint *free_list; /**< Bigints not used. */ + bigint *bi_radix; /**< The radix used. */ + bigint *bi_mod[BIGINT_NUM_MODS]; /**< modulus */ + +#if defined(CONFIG_BIGINT_MONTGOMERY) + bigint *bi_RR_mod_m[BIGINT_NUM_MODS]; /**< R^2 mod m */ + bigint *bi_R_mod_m[BIGINT_NUM_MODS]; /**< R mod m */ + comp N0_dash[BIGINT_NUM_MODS]; +#elif defined(CONFIG_BIGINT_BARRETT) + bigint *bi_mu[BIGINT_NUM_MODS]; /**< Storage for mu */ +#endif + bigint *bi_normalised_mod[BIGINT_NUM_MODS]; /**< Normalised mod storage. */ + bigint **g; /**< Used by sliding-window. */ + int window; /**< The size of the sliding window */ + int active_count; /**< Number of active bigints. */ + int free_count; /**< Number of free bigints. */ + +#ifdef CONFIG_BIGINT_MONTGOMERY + uint8_t use_classical; /**< Use classical reduction. */ +#endif + uint8_t mod_offset; /**< The mod offset we are using */ +} BI_CTX; + +#ifndef WIN32 +#define max(a,b) ((a)>(b)?(a):(b)) /**< Find the maximum of 2 numbers. */ +#define min(a,b) ((a)<(b)?(a):(b)) /**< Find the minimum of 2 numbers. */ +#endif + +#define PERMANENT 0x7FFF55AA /**< A magic number for permanents. */ + +#endif diff --git a/src/crypto/config.h b/src/crypto/config.h new file mode 100644 index 0000000..8da9dc2 --- /dev/null +++ b/src/crypto/config.h @@ -0,0 +1,6 @@ + +#define CONFIG_BIGINT_BARRETT 1 +#define CONFIG_BIGINT_CRT 1 +#define CONFIG_BIGINT_SQUARE 1 +#define CONFIG_BIGINT_32BIT 1 + diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h new file mode 100644 index 0000000..a1c6ada --- /dev/null +++ b/src/crypto/crypto.h @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file crypto.h + */ + +#ifndef HEADER_CRYPTO_H +#define HEADER_CRYPTO_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "config.h" +#include "bigint_impl.h" +#include "bigint.h" + +#ifndef STDCALL +#define STDCALL +#endif +#ifndef EXP_FUNC +#define EXP_FUNC +#endif + +/************************************************************************** + * AES declarations + **************************************************************************/ + +#define AES_MAXROUNDS 14 +#define AES_BLOCKSIZE 16 +#define AES_IV_SIZE 16 + +typedef struct aes_key_st +{ + uint16_t rounds; + uint16_t key_size; + uint32_t ks[(AES_MAXROUNDS+1)*8]; + uint8_t iv[AES_IV_SIZE]; +} AES_CTX; + +typedef enum +{ + AES_MODE_128, + AES_MODE_256 +} AES_MODE; + +void AES_set_key(AES_CTX *ctx, const uint8_t *key, + const uint8_t *iv, AES_MODE mode); +void AES_cbc_encrypt(AES_CTX *ctx, const uint8_t *msg, + uint8_t *out, int length); +void AES_cbc_decrypt(AES_CTX *ks, const uint8_t *in, uint8_t *out, int length); +void AES_convert_key(AES_CTX *ctx); + +/************************************************************************** + * RC4 declarations + **************************************************************************/ + +typedef struct +{ + uint8_t x, y, m[256]; +} RC4_CTX; + +void RC4_setup(RC4_CTX *s, const uint8_t *key, int length); +void RC4_crypt(RC4_CTX *s, const uint8_t *msg, uint8_t *data, int length); + +/************************************************************************** + * SHA1 declarations + **************************************************************************/ + +#define SHA1_SIZE 20 + +/* + * This structure will hold context information for the SHA-1 + * hashing operation + */ +typedef struct +{ + uint32_t Intermediate_Hash[SHA1_SIZE/4]; /* Message Digest */ + uint32_t Length_Low; /* Message length in bits */ + uint32_t Length_High; /* Message length in bits */ + uint16_t Message_Block_Index; /* Index into message block array */ + uint8_t Message_Block[64]; /* 512-bit message blocks */ +} SHA1_CTX; + +void SHA1_Init(SHA1_CTX *); +void SHA1_Update(SHA1_CTX *, const uint8_t * msg, int len); +void SHA1_Final(uint8_t *digest, SHA1_CTX *); + +/************************************************************************** + * MD5 declarations + **************************************************************************/ + +#define MD5_SIZE 16 + +typedef struct +{ + uint32_t state[4]; /* state (ABCD) */ + uint32_t count[2]; /* number of bits, modulo 2^64 (lsb first) */ + uint8_t buffer[64]; /* input buffer */ +} MD5_CTX; + +EXP_FUNC void STDCALL MD5_Init(MD5_CTX *); +EXP_FUNC void STDCALL MD5_Update(MD5_CTX *, const uint8_t *msg, int len); +EXP_FUNC void STDCALL MD5_Final(uint8_t *digest, MD5_CTX *); + +/************************************************************************** + * HMAC declarations + **************************************************************************/ +void hmac_md5(const uint8_t *msg, int length, const uint8_t *key, + int key_len, uint8_t *digest); +void hmac_sha1(const uint8_t *msg, int length, const uint8_t *key, + int key_len, uint8_t *digest); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/crypto/hmac.c b/src/crypto/hmac.c new file mode 100644 index 0000000..24a04d7 --- /dev/null +++ b/src/crypto/hmac.c @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * HMAC implementation - This code was originally taken from RFC2104 + * See http://www.ietf.org/rfc/rfc2104.txt and + * http://www.faqs.org/rfcs/rfc2202.html + */ + +#include +#include "os_port.h" +#include "crypto.h" + +/** + * Perform HMAC-MD5 + * NOTE: does not handle keys larger than the block size. + */ +void hmac_md5(const uint8_t *msg, int length, const uint8_t *key, + int key_len, uint8_t *digest) +{ + MD5_CTX context; + uint8_t k_ipad[64]; + uint8_t k_opad[64]; + int i; + + memset(k_ipad, 0, sizeof k_ipad); + memset(k_opad, 0, sizeof k_opad); + memcpy(k_ipad, key, key_len); + memcpy(k_opad, key, key_len); + + for (i = 0; i < 64; i++) + { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + + MD5_Init(&context); + MD5_Update(&context, k_ipad, 64); + MD5_Update(&context, msg, length); + MD5_Final(digest, &context); + MD5_Init(&context); + MD5_Update(&context, k_opad, 64); + MD5_Update(&context, digest, MD5_SIZE); + MD5_Final(digest, &context); +} + +/** + * Perform HMAC-SHA1 + * NOTE: does not handle keys larger than the block size. + */ +void hmac_sha1(const uint8_t *msg, int length, const uint8_t *key, + int key_len, uint8_t *digest) +{ + SHA1_CTX context; + uint8_t k_ipad[64]; + uint8_t k_opad[64]; + int i; + + memset(k_ipad, 0, sizeof k_ipad); + memset(k_opad, 0, sizeof k_opad); + memcpy(k_ipad, key, key_len); + memcpy(k_opad, key, key_len); + + for (i = 0; i < 64; i++) + { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + + SHA1_Init(&context); + SHA1_Update(&context, k_ipad, 64); + SHA1_Update(&context, msg, length); + SHA1_Final(digest, &context); + SHA1_Init(&context); + SHA1_Update(&context, k_opad, 64); + SHA1_Update(&context, digest, SHA1_SIZE); + SHA1_Final(digest, &context); +} diff --git a/src/crypto/md5.c b/src/crypto/md5.c new file mode 100644 index 0000000..7f50713 --- /dev/null +++ b/src/crypto/md5.c @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * This file implements the MD5 algorithm as defined in RFC1321 + */ + +#include +#include "os_port.h" +#include "crypto.h" + +/* Constants for MD5Transform routine. + */ +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +/* ----- static functions ----- */ +static void MD5Transform(uint32_t state[4], const uint8_t block[64]); +static void Encode(uint8_t *output, uint32_t *input, uint32_t len); +static void Decode(uint32_t *output, const uint8_t *input, uint32_t len); + +static const uint8_t PADDING[64] = +{ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G, H and I are basic MD5 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits. */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. + Rotation is separate from addition to prevent recomputation. */ +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (uint32_t)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (uint32_t)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (uint32_t)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (uint32_t)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +/** + * MD5 initialization - begins an MD5 operation, writing a new ctx. + */ +EXP_FUNC void STDCALL MD5_Init(MD5_CTX *ctx) +{ + ctx->count[0] = ctx->count[1] = 0; + + /* Load magic initialization constants. + */ + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xefcdab89; + ctx->state[2] = 0x98badcfe; + ctx->state[3] = 0x10325476; +} + +/** + * Accepts an array of octets as the next portion of the message. + */ +EXP_FUNC void STDCALL MD5_Update(MD5_CTX *ctx, const uint8_t * msg, int len) +{ + uint32_t x; + int i, partLen; + + /* Compute number of bytes mod 64 */ + x = (uint32_t)((ctx->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((ctx->count[0] += ((uint32_t)len << 3)) < ((uint32_t)len << 3)) + ctx->count[1]++; + ctx->count[1] += ((uint32_t)len >> 29); + + partLen = 64 - x; + + /* Transform as many times as possible. */ + if (len >= partLen) + { + memcpy(&ctx->buffer[x], msg, partLen); + MD5Transform(ctx->state, ctx->buffer); + + for (i = partLen; i + 63 < len; i += 64) + MD5Transform(ctx->state, &msg[i]); + + x = 0; + } + else + i = 0; + + /* Buffer remaining input */ + memcpy(&ctx->buffer[x], &msg[i], len-i); +} + +/** + * Return the 128-bit message digest into the user's array + */ +EXP_FUNC void STDCALL MD5_Final(uint8_t *digest, MD5_CTX *ctx) +{ + uint8_t bits[8]; + uint32_t x, padLen; + + /* Save number of bits */ + Encode(bits, ctx->count, 8); + + /* Pad out to 56 mod 64. + */ + x = (uint32_t)((ctx->count[0] >> 3) & 0x3f); + padLen = (x < 56) ? (56 - x) : (120 - x); + MD5_Update(ctx, PADDING, padLen); + + /* Append length (before padding) */ + MD5_Update(ctx, bits, 8); + + /* Store state in digest */ + Encode(digest, ctx->state, MD5_SIZE); +} + +/** + * MD5 basic transformation. Transforms state based on block. + */ +static void MD5Transform(uint32_t state[4], const uint8_t block[64]) +{ + uint32_t a = state[0], b = state[1], c = state[2], + d = state[3], x[MD5_SIZE]; + + Decode(x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; +} + +/** + * Encodes input (uint32_t) into output (uint8_t). Assumes len is + * a multiple of 4. + */ +static void Encode(uint8_t *output, uint32_t *input, uint32_t len) +{ + uint32_t i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + { + output[j] = (uint8_t)(input[i] & 0xff); + output[j+1] = (uint8_t)((input[i] >> 8) & 0xff); + output[j+2] = (uint8_t)((input[i] >> 16) & 0xff); + output[j+3] = (uint8_t)((input[i] >> 24) & 0xff); + } +} + +/** + * Decodes input (uint8_t) into output (uint32_t). Assumes len is + * a multiple of 4. + */ +static void Decode(uint32_t *output, const uint8_t *input, uint32_t len) +{ + uint32_t i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((uint32_t)input[j]) | (((uint32_t)input[j+1]) << 8) | + (((uint32_t)input[j+2]) << 16) | (((uint32_t)input[j+3]) << 24); +} diff --git a/src/crypto/os_port.h b/src/crypto/os_port.h new file mode 100644 index 0000000..80c09e8 --- /dev/null +++ b/src/crypto/os_port.h @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file os_port.h + * + * Some stuff to minimise the differences between windows and linux/unix + */ + +#ifndef HEADER_OS_PORT_H +#define HEADER_OS_PORT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#if defined(WIN32) +#define STDCALL __stdcall +#define EXP_FUNC __declspec(dllexport) +#else +#define STDCALL +#define EXP_FUNC +#endif + +#if defined(_WIN32_WCE) +#undef WIN32 +#define WIN32 +#endif + +#ifdef WIN32 + +/* Windows CE stuff */ +#if defined(_WIN32_WCE) +#include +#define abort() exit(1) +#else +#include +#include +#include +#include +#endif /* _WIN32_WCE */ + +#include +#include +#undef getpid +#undef open +#undef close +#undef sleep +#undef gettimeofday +#undef dup2 +#undef unlink + +#define SOCKET_READ(A,B,C) recv(A,B,C,0) +#define SOCKET_WRITE(A,B,C) send(A,B,C,0) +#define SOCKET_CLOSE(A) closesocket(A) +#define srandom(A) srand(A) +#define random() rand() +#define getpid() _getpid() +#define snprintf _snprintf +#define open(A,B) _open(A,B) +#define dup2(A,B) _dup2(A,B) +#define unlink(A) _unlink(A) +#define close(A) _close(A) +#define read(A,B,C) _read(A,B,C) +#define write(A,B,C) _write(A,B,C) +#define sleep(A) Sleep(A*1000) +#define usleep(A) Sleep(A/1000) +#define strdup(A) _strdup(A) +#define chroot(A) _chdir(A) +#define chdir(A) _chdir(A) +#define alloca(A) _alloca(A) +#ifndef lseek +#define lseek(A,B,C) _lseek(A,B,C) +#endif + +/* This fix gets around a problem where a win32 application on a cygwin xterm + doesn't display regular output (until a certain buffer limit) - but it works + fine under a normal DOS window. This is a hack to get around the issue - + see http://www.khngai.com/emacs/tty.php */ +#define TTY_FLUSH() if (!_isatty(_fileno(stdout))) fflush(stdout); + +/* +typedef UINT8 uint8_t; +typedef INT8 int8_t; +typedef UINT16 uint16_t; +typedef INT16 int16_t; +typedef UINT32 uint32_t; +typedef INT32 int32_t; +typedef UINT64 uint64_t; +typedef INT64 int64_t; +typedef int socklen_t; +*/ + +#else /* Not Win32 */ + +#ifdef __sun__ +#include +#else +#include +#endif /* Not Solaris */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SOCKET_READ(A,B,C) read(A,B,C) +#define SOCKET_WRITE(A,B,C) write(A,B,C) +#define SOCKET_CLOSE(A) if (A >= 0) close(A) +#define TTY_FLUSH() + +#endif /* Not Win32 */ + +/* some functions to mutate the way these work */ +#define ax_malloc(A) malloc(A) +#define ax_realloc(A) realloc(A) +#define ax_calloc(A) calloc(A) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/crypto/rc4.c b/src/crypto/rc4.c new file mode 100644 index 0000000..12a1211 --- /dev/null +++ b/src/crypto/rc4.c @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * An implementation of the RC4/ARC4 algorithm. + * Originally written by Christophe Devine. + */ + +#include +#include "os_port.h" +#include "crypto.h" + +/** + * Get ready for an encrypt/decrypt operation + */ +void RC4_setup(RC4_CTX *ctx, const uint8_t *key, int length) +{ + int i, j = 0, k = 0, a; + uint8_t *m; + + ctx->x = 0; + ctx->y = 0; + m = ctx->m; + + for (i = 0; i < 256; i++) + m[i] = i; + + for (i = 0; i < 256; i++) + { + a = m[i]; + j = (uint8_t)(j + a + key[k]); + m[i] = m[j]; + m[j] = a; + + if (++k >= length) + k = 0; + } +} + +/** + * Perform the encrypt/decrypt operation (can use it for either since + * this is a stream cipher). + * NOTE: *msg and *out must be the same pointer (performance tweak) + */ +void RC4_crypt(RC4_CTX *ctx, const uint8_t *msg, uint8_t *out, int length) +{ + int i; + uint8_t *m, x, y, a, b; + + x = ctx->x; + y = ctx->y; + m = ctx->m; + + for (i = 0; i < length; i++) + { + a = m[++x]; + y += a; + m[x] = b = m[y]; + m[y] = a; + out[i] ^= m[(uint8_t)(a + b)]; + } + + ctx->x = x; + ctx->y = y; +} diff --git a/src/crypto/sha1.c b/src/crypto/sha1.c new file mode 100644 index 0000000..1082733 --- /dev/null +++ b/src/crypto/sha1.c @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * SHA1 implementation - as defined in FIPS PUB 180-1 published April 17, 1995. + * This code was originally taken from RFC3174 + */ + +#include +#include "os_port.h" +#include "crypto.h" + +/* + * Define the SHA1 circular left shift macro + */ +#define SHA1CircularShift(bits,word) \ + (((word) << (bits)) | ((word) >> (32-(bits)))) + +/* ----- static functions ----- */ +static void SHA1PadMessage(SHA1_CTX *ctx); +static void SHA1ProcessMessageBlock(SHA1_CTX *ctx); + +/** + * Initialize the SHA1 context + */ +void SHA1_Init(SHA1_CTX *ctx) +{ + ctx->Length_Low = 0; + ctx->Length_High = 0; + ctx->Message_Block_Index = 0; + ctx->Intermediate_Hash[0] = 0x67452301; + ctx->Intermediate_Hash[1] = 0xEFCDAB89; + ctx->Intermediate_Hash[2] = 0x98BADCFE; + ctx->Intermediate_Hash[3] = 0x10325476; + ctx->Intermediate_Hash[4] = 0xC3D2E1F0; +} + +/** + * Accepts an array of octets as the next portion of the message. + */ +void SHA1_Update(SHA1_CTX *ctx, const uint8_t *msg, int len) +{ + while (len--) + { + ctx->Message_Block[ctx->Message_Block_Index++] = (*msg & 0xFF); + ctx->Length_Low += 8; + + if (ctx->Length_Low == 0) + ctx->Length_High++; + + if (ctx->Message_Block_Index == 64) + SHA1ProcessMessageBlock(ctx); + + msg++; + } +} + +/** + * Return the 160-bit message digest into the user's array + */ +void SHA1_Final(uint8_t *digest, SHA1_CTX *ctx) +{ + int i; + + SHA1PadMessage(ctx); + memset(ctx->Message_Block, 0, 64); + ctx->Length_Low = 0; /* and clear length */ + ctx->Length_High = 0; + + for (i = 0; i < SHA1_SIZE; i++) + { + digest[i] = ctx->Intermediate_Hash[i>>2] >> 8 * ( 3 - ( i & 0x03 ) ); + } +} + +/** + * Process the next 512 bits of the message stored in the array. + */ +static void SHA1ProcessMessageBlock(SHA1_CTX *ctx) +{ + const uint32_t K[] = { /* Constants defined in SHA-1 */ + 0x5A827999, + 0x6ED9EBA1, + 0x8F1BBCDC, + 0xCA62C1D6 + }; + int t; /* Loop counter */ + uint32_t temp; /* Temporary word value */ + uint32_t W[80]; /* Word sequence */ + uint32_t A, B, C, D, E; /* Word buffers */ + + /* + * Initialize the first 16 words in the array W + */ + for (t = 0; t < 16; t++) + { + W[t] = ctx->Message_Block[t * 4] << 24; + W[t] |= ctx->Message_Block[t * 4 + 1] << 16; + W[t] |= ctx->Message_Block[t * 4 + 2] << 8; + W[t] |= ctx->Message_Block[t * 4 + 3]; + } + + for (t = 16; t < 80; t++) + { + W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); + } + + A = ctx->Intermediate_Hash[0]; + B = ctx->Intermediate_Hash[1]; + C = ctx->Intermediate_Hash[2]; + D = ctx->Intermediate_Hash[3]; + E = ctx->Intermediate_Hash[4]; + + for (t = 0; t < 20; t++) + { + temp = SHA1CircularShift(5,A) + + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; + E = D; + D = C; + C = SHA1CircularShift(30,B); + + B = A; + A = temp; + } + + for (t = 20; t < 40; t++) + { + temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for (t = 40; t < 60; t++) + { + temp = SHA1CircularShift(5,A) + + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for (t = 60; t < 80; t++) + { + temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + ctx->Intermediate_Hash[0] += A; + ctx->Intermediate_Hash[1] += B; + ctx->Intermediate_Hash[2] += C; + ctx->Intermediate_Hash[3] += D; + ctx->Intermediate_Hash[4] += E; + ctx->Message_Block_Index = 0; +} + +/* + * According to the standard, the message must be padded to an even + * 512 bits. The first padding bit must be a '1'. The last 64 + * bits represent the length of the original message. All bits in + * between should be 0. This function will pad the message + * according to those rules by filling the Message_Block array + * accordingly. It will also call the ProcessMessageBlock function + * provided appropriately. When it returns, it can be assumed that + * the message digest has been computed. + * + * @param ctx [in, out] The SHA1 context + */ +static void SHA1PadMessage(SHA1_CTX *ctx) +{ + /* + * Check to see if the current message block is too small to hold + * the initial padding bits and length. If so, we will pad the + * block, process it, and then continue padding into a second + * block. + */ + if (ctx->Message_Block_Index > 55) + { + ctx->Message_Block[ctx->Message_Block_Index++] = 0x80; + while(ctx->Message_Block_Index < 64) + { + ctx->Message_Block[ctx->Message_Block_Index++] = 0; + } + + SHA1ProcessMessageBlock(ctx); + + while (ctx->Message_Block_Index < 56) + { + ctx->Message_Block[ctx->Message_Block_Index++] = 0; + } + } + else + { + ctx->Message_Block[ctx->Message_Block_Index++] = 0x80; + while(ctx->Message_Block_Index < 56) + { + + ctx->Message_Block[ctx->Message_Block_Index++] = 0; + } + } + + /* + * Store the message length as the last 8 octets + */ + ctx->Message_Block[56] = ctx->Length_High >> 24; + ctx->Message_Block[57] = ctx->Length_High >> 16; + ctx->Message_Block[58] = ctx->Length_High >> 8; + ctx->Message_Block[59] = ctx->Length_High; + ctx->Message_Block[60] = ctx->Length_Low >> 24; + ctx->Message_Block[61] = ctx->Length_Low >> 16; + ctx->Message_Block[62] = ctx->Length_Low >> 8; + ctx->Message_Block[63] = ctx->Length_Low; + SHA1ProcessMessageBlock(ctx); +} diff --git a/src/dnssd.c b/src/dnssd.c new file mode 100644 index 0000000..96e32dc --- /dev/null +++ b/src/dnssd.c @@ -0,0 +1,285 @@ +#include +#include +#include +#include + +#include "dnssd.h" +#include "dnssdint.h" +#include "global.h" +#include "compat.h" +#include "utils.h" + +#define MAX_DEVICEID 18 +#define MAX_SERVNAME 256 + +#ifndef WIN32 +# include +#else +# include +# if !defined(EFI32) && !defined(EFI64) +# define DNSSD_API __stdcall +# else +# define DNSSD_API +# endif + +typedef struct _DNSServiceRef_t *DNSServiceRef; +typedef union _TXTRecordRef_t { char PrivateData[16]; char *ForceNaturalAlignment; } TXTRecordRef; + +typedef uint32_t DNSServiceFlags; +typedef int32_t DNSServiceErrorType; + +typedef void (DNSSD_API *DNSServiceRegisterReply) + ( + DNSServiceRef sdRef, + DNSServiceFlags flags, + DNSServiceErrorType errorCode, + const char *name, + const char *regtype, + const char *domain, + void *context + ); +#endif + +typedef DNSServiceErrorType (DNSSD_API *DNSServiceRegister_t) + ( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, + const char *regtype, + const char *domain, + const char *host, + uint16_t port, + uint16_t txtLen, + const void *txtRecord, + DNSServiceRegisterReply callBack, + void *context + ); +typedef void (DNSSD_API *DNSServiceRefDeallocate_t)(DNSServiceRef sdRef); +typedef void (DNSSD_API *TXTRecordCreate_t) + ( + TXTRecordRef *txtRecord, + uint16_t bufferLen, + void *buffer + ); +typedef void (DNSSD_API *TXTRecordDeallocate_t)(TXTRecordRef *txtRecord); +typedef DNSServiceErrorType (DNSSD_API *TXTRecordSetValue_t) + ( + TXTRecordRef *txtRecord, + const char *key, + uint8_t valueSize, + const void *value + ); +typedef uint16_t (DNSSD_API *TXTRecordGetLength_t)(const TXTRecordRef *txtRecord); +typedef const void * (DNSSD_API *TXTRecordGetBytesPtr_t)(const TXTRecordRef *txtRecord); + + +struct dnssd_s { +#ifdef WIN32 + HMODULE module; +#endif + + DNSServiceRegister_t DNSServiceRegister; + DNSServiceRefDeallocate_t DNSServiceRefDeallocate; + TXTRecordCreate_t TXTRecordCreate; + TXTRecordSetValue_t TXTRecordSetValue; + TXTRecordGetLength_t TXTRecordGetLength; + TXTRecordGetBytesPtr_t TXTRecordGetBytesPtr; + TXTRecordDeallocate_t TXTRecordDeallocate; + + char hwaddr[MAX_HWADDR_LEN]; + int hwaddrlen; + + DNSServiceRef raopService; + DNSServiceRef airplayService; +}; + + + +dnssd_t * +dnssd_init(const char *hwaddr, int hwaddrlen, int *error) +{ + dnssd_t *dnssd; + + if (error) *error = DNSSD_ERROR_NOERROR; + if (hwaddrlen > MAX_HWADDR_LEN) { + if (error) *error = DNSSD_ERROR_HWADDRLEN; + return NULL; + } + + dnssd = calloc(1, sizeof(dnssd_t)); + if (!dnssd) { + if (error) *error = DNSSD_ERROR_OUTOFMEM; + return NULL; + } + +#ifdef WIN32 + dnssd->module = LoadLibraryA("dnssd.dll"); + if (!dnssd->module) { + if (error) *error = DNSSD_ERROR_LIBNOTFOUND; + free(dnssd); + return NULL; + } + dnssd->DNSServiceRegister = (DNSServiceRegister_t)GetProcAddress(dnssd->module, "DNSServiceRegister"); + dnssd->DNSServiceRefDeallocate = (DNSServiceRefDeallocate_t)GetProcAddress(dnssd->module, "DNSServiceRefDeallocate"); + dnssd->TXTRecordCreate = (TXTRecordCreate_t)GetProcAddress(dnssd->module, "TXTRecordCreate"); + dnssd->TXTRecordSetValue = (TXTRecordSetValue_t)GetProcAddress(dnssd->module, "TXTRecordSetValue"); + dnssd->TXTRecordGetLength = (TXTRecordGetLength_t)GetProcAddress(dnssd->module, "TXTRecordGetLength"); + dnssd->TXTRecordGetBytesPtr = (TXTRecordGetBytesPtr_t)GetProcAddress(dnssd->module, "TXTRecordGetBytesPtr"); + dnssd->TXTRecordDeallocate = (TXTRecordDeallocate_t)GetProcAddress(dnssd->module, "TXTRecordDeallocate"); + + if (!dnssd->DNSServiceRegister || !dnssd->DNSServiceRefDeallocate || !dnssd->TXTRecordCreate || + !dnssd->TXTRecordSetValue || !dnssd->TXTRecordGetLength || !dnssd->TXTRecordGetBytesPtr || + !dnssd->TXTRecordDeallocate) { + if (error) *error = DNSSD_ERROR_PROCNOTFOUND; + FreeLibrary(dnssd->module); + free(dnssd); + return NULL; + } +#else + dnssd->DNSServiceRegister = &DNSServiceRegister; + dnssd->DNSServiceRefDeallocate = &DNSServiceRefDeallocate; + dnssd->TXTRecordCreate = &TXTRecordCreate; + dnssd->TXTRecordSetValue = &TXTRecordSetValue; + dnssd->TXTRecordGetLength = &TXTRecordGetLength; + dnssd->TXTRecordGetBytesPtr = &TXTRecordGetBytesPtr; + dnssd->TXTRecordDeallocate = &TXTRecordDeallocate; +#endif + + memcpy(dnssd->hwaddr, hwaddr, hwaddrlen); + dnssd->hwaddrlen = hwaddrlen; + + return dnssd; +} + +void +dnssd_destroy(dnssd_t *dnssd) +{ + if (dnssd) { +#ifdef WIN32 + FreeLibrary(dnssd->module); +#endif + free(dnssd); + } +} + +int +dnssd_register_raop(dnssd_t *dnssd, const char *name, unsigned short port) +{ + TXTRecordRef txtRecord; + char servname[MAX_SERVNAME]; + int ret; + + assert(dnssd); + + dnssd->TXTRecordCreate(&txtRecord, 0, NULL); + dnssd->TXTRecordSetValue(&txtRecord, "txtvers", strlen(RAOP_TXTVERS), RAOP_TXTVERS); + dnssd->TXTRecordSetValue(&txtRecord, "ch", strlen(RAOP_CH), RAOP_CH); + dnssd->TXTRecordSetValue(&txtRecord, "cn", strlen(RAOP_CN), RAOP_CN); + dnssd->TXTRecordSetValue(&txtRecord, "et", strlen(RAOP_ET), RAOP_ET); + dnssd->TXTRecordSetValue(&txtRecord, "sv", strlen(RAOP_SV), RAOP_SV); + dnssd->TXTRecordSetValue(&txtRecord, "da", strlen(RAOP_DA), RAOP_DA); + dnssd->TXTRecordSetValue(&txtRecord, "sr", strlen(RAOP_SR), RAOP_SR); + dnssd->TXTRecordSetValue(&txtRecord, "ss", strlen(RAOP_SS), RAOP_SS); + dnssd->TXTRecordSetValue(&txtRecord, "pw", strlen(RAOP_PW), RAOP_PW); + dnssd->TXTRecordSetValue(&txtRecord, "vn", strlen(RAOP_VN), RAOP_VN); + dnssd->TXTRecordSetValue(&txtRecord, "tp", strlen(RAOP_TP), RAOP_TP); + dnssd->TXTRecordSetValue(&txtRecord, "md", strlen(RAOP_MD), RAOP_MD); + dnssd->TXTRecordSetValue(&txtRecord, "vs", strlen(GLOBAL_VERSION), GLOBAL_VERSION); + dnssd->TXTRecordSetValue(&txtRecord, "am", strlen(RAOP_AM), RAOP_AM); + dnssd->TXTRecordSetValue(&txtRecord, "sf", strlen(RAOP_SF), RAOP_SF); + + /* Convert hardware address to string */ + ret = utils_hwaddr_raop(servname, sizeof(servname), dnssd->hwaddr, dnssd->hwaddrlen); + if (ret < 0) { + /* FIXME: handle better */ + return -1; + } + + /* Check that we have bytes for 'hw@name' format */ + if (sizeof(servname) < strlen(servname)+1+strlen(name)+1) { + /* FIXME: handle better */ + return -2; + } + + strncat(servname, "@", sizeof(servname)-strlen(servname)-1); + strncat(servname, name, sizeof(servname)-strlen(servname)-1); + + /* Register the service */ + dnssd->DNSServiceRegister(&dnssd->raopService, 0, 0, + servname, "_raop._tcp", + NULL, NULL, + htons(port), + dnssd->TXTRecordGetLength(&txtRecord), + dnssd->TXTRecordGetBytesPtr(&txtRecord), + NULL, NULL); + + /* Deallocate TXT record */ + dnssd->TXTRecordDeallocate(&txtRecord); + return 1; +} + +int +dnssd_register_airplay(dnssd_t *dnssd, const char *name, unsigned short port) +{ + TXTRecordRef txtRecord; + char deviceid[3*MAX_HWADDR_LEN]; + char features[16]; + int ret; + + assert(dnssd); + + /* Convert hardware address to string */ + ret = utils_hwaddr_airplay(deviceid, sizeof(deviceid), dnssd->hwaddr, dnssd->hwaddrlen); + if (ret < 0) { + /* FIXME: handle better */ + return -1; + } + + features[sizeof(features)-1] = '\0'; + snprintf(features, sizeof(features)-1, "0x%x", GLOBAL_FEATURES); + + dnssd->TXTRecordCreate(&txtRecord, 0, NULL); + dnssd->TXTRecordSetValue(&txtRecord, "deviceid", strlen(deviceid), deviceid); + dnssd->TXTRecordSetValue(&txtRecord, "features", strlen(features), features); + dnssd->TXTRecordSetValue(&txtRecord, "model", strlen(GLOBAL_MODEL), GLOBAL_MODEL); + + /* Register the service */ + dnssd->DNSServiceRegister(&dnssd->airplayService, 0, 0, + name, "_airplay._tcp", + NULL, NULL, + htons(port), + dnssd->TXTRecordGetLength(&txtRecord), + dnssd->TXTRecordGetBytesPtr(&txtRecord), + NULL, NULL); + + /* Deallocate TXT record */ + dnssd->TXTRecordDeallocate(&txtRecord); + return 0; +} + +void +dnssd_unregister_raop(dnssd_t *dnssd) +{ + assert(dnssd); + + if (!dnssd->raopService) { + return; + } + + dnssd->DNSServiceRefDeallocate(dnssd->raopService); + dnssd->raopService = NULL; +} + +void +dnssd_unregister_airplay(dnssd_t *dnssd) +{ + assert(dnssd); + + if (!dnssd->airplayService) { + return; + } + + dnssd->DNSServiceRefDeallocate(dnssd->airplayService); + dnssd->airplayService = NULL; +} diff --git a/src/dnssd.m b/src/dnssd.m new file mode 100644 index 0000000..1335a73 --- /dev/null +++ b/src/dnssd.m @@ -0,0 +1,164 @@ +#include + +#include "dnssd.h" +#include "dnssdint.h" +#include "global.h" +#include "utils.h" + +#import + +#define MAX_SERVNAME 256 + +struct dnssd_s { + char hwaddr[MAX_HWADDR_LEN]; + int hwaddrlen; + + NSNetService *raopService; + NSNetService *airplayService; +}; + +dnssd_t * +dnssd_init(const char *hwaddr, int hwaddrlen, int *error) +{ + dnssd_t *dnssd; + + if (error) *error = DNSSD_ERROR_NOERROR; + if (hwaddrlen > MAX_HWADDR_LEN) { + if (error) *error = DNSSD_ERROR_HWADDRLEN; + return NULL; + } + + dnssd = calloc(1, sizeof(dnssd_t)); + if (!dnssd) { + if (error) *error = DNSSD_ERROR_OUTOFMEM; + return NULL; + } + memcpy(dnssd->hwaddr, hwaddr, hwaddrlen); + dnssd->hwaddrlen = hwaddrlen; + + return dnssd; +} + +void +dnssd_destroy(dnssd_t *dnssd) +{ + free(dnssd); +} + +int +dnssd_register_raop(dnssd_t *dnssd, const char *name, unsigned short port) +{ + char hwaddrstr[MAX_SERVNAME]; + NSString *serviceString; + NSMutableDictionary *txtDict; + NSData *txtData; + int ret; + + assert(dnssd); + + if (dnssd->raopService != nil) { + return -1; + } + + /* Convert the hardware address to string */ + ret = utils_hwaddr_raop(hwaddrstr, sizeof(hwaddrstr), dnssd->hwaddr, dnssd->hwaddrlen); + if (ret < 0) { + return -2; + } + serviceString = [NSString stringWithFormat:@"%s@%s", hwaddrstr, name]; + + txtDict = [NSMutableDictionary dictionaryWithCapacity:0]; + [txtDict setValue:[NSString stringWithUTF8String:RAOP_TXTVERS] forKey:@"txtvers"]; + [txtDict setValue:[NSString stringWithUTF8String:RAOP_CH] forKey:@"ch"]; + [txtDict setValue:[NSString stringWithUTF8String:RAOP_CN] forKey:@"cn"]; + [txtDict setValue:[NSString stringWithUTF8String:RAOP_ET] forKey:@"et"]; + [txtDict setValue:[NSString stringWithUTF8String:RAOP_SV] forKey:@"sv"]; + [txtDict setValue:[NSString stringWithUTF8String:RAOP_DA] forKey:@"da"]; + [txtDict setValue:[NSString stringWithUTF8String:RAOP_SR] forKey:@"sr"]; + [txtDict setValue:[NSString stringWithUTF8String:RAOP_SS] forKey:@"ss"]; + [txtDict setValue:[NSString stringWithUTF8String:RAOP_PW] forKey:@"pw"]; + [txtDict setValue:[NSString stringWithUTF8String:RAOP_VN] forKey:@"vn"]; + [txtDict setValue:[NSString stringWithUTF8String:RAOP_TP] forKey:@"tp"]; + [txtDict setValue:[NSString stringWithUTF8String:RAOP_MD] forKey:@"md"]; + [txtDict setValue:[NSString stringWithUTF8String:GLOBAL_VERSION] forKey:@"vs"]; + [txtDict setValue:[NSString stringWithUTF8String:RAOP_AM] forKey:@"am"]; + [txtDict setValue:[NSString stringWithUTF8String:RAOP_SF] forKey:@"sf"]; + txtData = [NSNetService dataFromTXTRecordDictionary:txtDict]; + + /* Create the service and publish it */ + dnssd->raopService = [[NSNetService alloc] initWithDomain:@"" + type:@"_raop._tcp" + name:serviceString + port:port]; + [dnssd->raopService setTXTRecordData:txtData]; + [dnssd->raopService publish]; + return 1; +} + +int +dnssd_register_airplay(dnssd_t *dnssd, const char *name, unsigned short port) +{ + NSMutableDictionary *txtDict; + NSData *txtData; + char deviceid[3*MAX_HWADDR_LEN]; + char features[16]; + int ret; + + assert(dnssd); + + if (dnssd->airplayService != nil) { + return -1; + } + + /* Convert hardware address to string */ + ret = utils_hwaddr_airplay(deviceid, sizeof(deviceid), dnssd->hwaddr, dnssd->hwaddrlen); + if (ret < 0) { + return -2; + } + + memset(features, 0, sizeof(features)); + snprintf(features, sizeof(features)-1, "0x%x", GLOBAL_FEATURES); + + txtDict = [NSMutableDictionary dictionaryWithCapacity:0]; + [txtDict setValue:[NSString stringWithUTF8String:deviceid] forKey:@"deviceid"]; + [txtDict setValue:[NSString stringWithUTF8String:features] forKey:@"features"]; + [txtDict setValue:[NSString stringWithUTF8String:GLOBAL_MODEL] forKey:@"model"]; + txtData = [NSNetService dataFromTXTRecordDictionary:txtDict]; + + /* Create the service and publish it */ + dnssd->airplayService = [[NSNetService alloc] initWithDomain:@"" + type:@"_airplay._tcp" + name:[NSString stringWithUTF8String:name] + port:port]; + [dnssd->airplayService setTXTRecordData:txtData]; + [dnssd->airplayService publish]; + return 1; +} + +void +dnssd_unregister_raop(dnssd_t *dnssd) +{ + assert(dnssd); + + if (dnssd->raopService == nil) { + return; + } + + [dnssd->raopService stop]; + [dnssd->raopService release]; + dnssd->raopService = nil; +} + +void +dnssd_unregister_airplay(dnssd_t *dnssd) +{ + assert(dnssd); + + if (dnssd->airplayService == nil) { + return; + } + + [dnssd->airplayService stop]; + [dnssd->airplayService release]; + dnssd->airplayService = nil; +} diff --git a/src/dnssdint.h b/src/dnssdint.h new file mode 100644 index 0000000..803cc2b --- /dev/null +++ b/src/dnssdint.h @@ -0,0 +1,19 @@ +#ifndef DNSSDINT_H +#define DNSSDINT_H + +#define RAOP_TXTVERS "1" +#define RAOP_CH "2" +#define RAOP_CN "0,1" +#define RAOP_ET "1" +#define RAOP_SV "false" +#define RAOP_DA "true" +#define RAOP_SR "44100" +#define RAOP_SS "16" +#define RAOP_PW "false" +#define RAOP_VN "65537" +#define RAOP_TP "TCP,UDP" +#define RAOP_MD "0,1,2" +#define RAOP_AM "AppleTV2,1" +#define RAOP_SF "0x4" + +#endif diff --git a/src/global.h b/src/global.h new file mode 100644 index 0000000..4acd5bf --- /dev/null +++ b/src/global.h @@ -0,0 +1,10 @@ +#ifndef GLOBAL_H +#define GLOBAL_H + +#define GLOBAL_FEATURES 0x7 +#define GLOBAL_MODEL "AppleTV2,1" +#define GLOBAL_VERSION "104.29" + +#define MAX_HWADDR_LEN 6 + +#endif diff --git a/src/http_parser.c b/src/http_parser.c new file mode 100644 index 0000000..2012330 --- /dev/null +++ b/src/http_parser.c @@ -0,0 +1,1820 @@ +/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev + * + * Additional changes are licensed under the same terms as NGINX and + * copyright Joyent, Inc. and other Node contributors. 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 "http_parser.h" +#include +#include + + +#ifndef MIN +# define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + + +#if HTTP_PARSER_DEBUG +#define SET_ERRNO(e) \ +do { \ + parser->http_errno = (e); \ + parser->error_lineno = __LINE__; \ +} while (0) +#else +#define SET_ERRNO(e) \ +do { \ + parser->http_errno = (e); \ +} while(0) +#endif + + +#define CALLBACK2(FOR) \ +do { \ + if (settings->on_##FOR) { \ + if (0 != settings->on_##FOR(parser)) { \ + SET_ERRNO(HPE_CB_##FOR); \ + return (p - data); \ + } \ + } \ +} while (0) + + +#define MARK(FOR) \ +do { \ + FOR##_mark = p; \ +} while (0) + +#define CALLBACK(FOR) \ +do { \ + if (FOR##_mark) { \ + if (settings->on_##FOR) { \ + if (0 != settings->on_##FOR(parser, \ + FOR##_mark, \ + p - FOR##_mark)) \ + { \ + SET_ERRNO(HPE_CB_##FOR); \ + return (p - data); \ + } \ + } \ + FOR##_mark = NULL; \ + } \ +} while (0) + + +#define PROXY_CONNECTION "proxy-connection" +#define CONNECTION "connection" +#define CONTENT_LENGTH "content-length" +#define TRANSFER_ENCODING "transfer-encoding" +#define UPGRADE "upgrade" +#define CHUNKED "chunked" +#define KEEP_ALIVE "keep-alive" +#define CLOSE "close" + + +static const char *method_strings[] = + { "DELETE" + , "GET" + , "HEAD" + , "POST" + , "PUT" + , "CONNECT" + , "OPTIONS" + , "TRACE" + , "COPY" + , "LOCK" + , "MKCOL" + , "MOVE" + , "PROPFIND" + , "PROPPATCH" + , "UNLOCK" + , "REPORT" + , "MKACTIVITY" + , "CHECKOUT" + , "MERGE" + , "M-SEARCH" + , "NOTIFY" + , "SUBSCRIBE" + , "UNSUBSCRIBE" + , "PATCH" + , "DESCRIBE" + , "ANNOUNCE" + , "SETUP" + , "PLAY" + , "PAUSE" + , "TEARDOWN" + , "GET_PARAMETER" + , "SET_PARAMETER" + , "REDIRECT" + , "RECORD" + , "FLUSH" + }; + + +/* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ +static const char tokens[256] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + ' ', '!', '"', '#', '$', '%', '&', '\'', +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 0, 0, '*', '+', 0, '-', '.', '/', +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + '0', '1', '2', '3', '4', '5', '6', '7', +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + '8', '9', 0, 0, 0, 0, 0, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 'x', 'y', 'z', 0, 0, 0, '^', '_', +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 'x', 'y', 'z', 0, '|', '}', '~', 0 }; + + +static const int8_t unhex[256] = + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + }; + + +static const uint8_t normal_url_char[256] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0, 1, 1, 0, 1, 1, 1, 1, +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1, 1, 1, 1, 1, 1, 1, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1, 1, 1, 1, 1, 1, 1, 0, }; + + +enum state + { s_dead = 1 /* important that this is > 0 */ + + , s_start_req_or_res + , s_res_or_resp_H + , s_start_res + , s_res_H + , s_res_HT + , s_res_HTT + , s_res_HTTP + , s_res_first_http_major + , s_res_http_major + , s_res_first_http_minor + , s_res_http_minor + , s_res_first_status_code + , s_res_status_code + , s_res_status + , s_res_line_almost_done + + , s_start_req + + , s_req_method + , s_req_spaces_before_url + , s_req_schema + , s_req_schema_slash + , s_req_schema_slash_slash + , s_req_host + , s_req_port + , s_req_path + , s_req_query_string_start + , s_req_query_string + , s_req_fragment_start + , s_req_fragment + , s_req_http_start + , s_req_http_H + , s_req_http_HT + , s_req_http_HTT + , s_req_http_HTTP + , s_req_first_http_major + , s_req_http_major + , s_req_first_http_minor + , s_req_http_minor + , s_req_line_almost_done + + , s_header_field_start + , s_header_field + , s_header_value_start + , s_header_value + , s_header_value_lws + + , s_header_almost_done + + , s_chunk_size_start + , s_chunk_size + , s_chunk_parameters + , s_chunk_size_almost_done + + , s_headers_almost_done + /* Important: 's_headers_almost_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + + , s_chunk_data + , s_chunk_data_almost_done + , s_chunk_data_done + + , s_body_identity + , s_body_identity_eof + }; + + +#define PARSING_HEADER(state) (state <= s_headers_almost_done) + + +enum header_states + { h_general = 0 + , h_C + , h_CO + , h_CON + + , h_matching_connection + , h_matching_proxy_connection + , h_matching_content_length + , h_matching_transfer_encoding + , h_matching_upgrade + + , h_connection + , h_content_length + , h_transfer_encoding + , h_upgrade + + , h_matching_transfer_encoding_chunked + , h_matching_connection_keep_alive + , h_matching_connection_close + + , h_transfer_encoding_chunked + , h_connection_keep_alive + , h_connection_close + }; + + +/* Macros for character classes; depends on strict-mode */ +#define CR '\r' +#define LF '\n' +#define LOWER(c) (unsigned char)(c | 0x20) +#define TOKEN(c) (tokens[(unsigned char)c]) +#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +#define IS_NUMERIC_CHAR(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f') || (c) == '.' || (c) == ':') + +#if HTTP_PARSER_STRICT +#define IS_URL_CHAR(c) (normal_url_char[(unsigned char) (c)]) +#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') +#else +#define IS_URL_CHAR(c) \ + (normal_url_char[(unsigned char) (c)] || ((c) & 0x80)) +#define IS_HOST_CHAR(c) \ + (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') +#endif + + +#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) + + +#if HTTP_PARSER_STRICT +# define STRICT_CHECK(cond) \ +do { \ + if (cond) { \ + SET_ERRNO(HPE_STRICT); \ + goto error; \ + } \ +} while (0) +# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) +#else +# define STRICT_CHECK(cond) +# define NEW_MESSAGE() start_state +#endif + + +/* Map errno values to strings for human-readable output */ +#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, +static struct { + const char *name; + const char *description; +} http_strerror_tab[] = { + HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) +}; +#undef HTTP_STRERROR_GEN + + +size_t http_parser_execute (http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len) +{ + char c, ch; + int8_t unhex_val; + const char *p = data, *pe; + int64_t to_read; + enum state state; + enum header_states header_state; + uint64_t index = parser->index; + uint64_t nread = parser->nread; + + /* technically we could combine all of these (except for url_mark) into one + variable, saving stack space, but it seems more clear to have them + separated. */ + const char *header_field_mark = 0; + const char *header_value_mark = 0; + const char *url_mark = 0; + + /* We're in an error state. Don't bother doing anything. */ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return 0; + } + + state = (enum state) parser->state; + header_state = (enum header_states) parser->header_state; + + if (len == 0) { + switch (state) { + case s_body_identity_eof: + CALLBACK2(message_complete); + return 0; + + case s_dead: + case s_start_req_or_res: + case s_start_res: + case s_start_req: + return 0; + + default: + SET_ERRNO(HPE_INVALID_EOF_STATE); + return 1; + } + } + + + if (state == s_header_field) + header_field_mark = data; + if (state == s_header_value) + header_value_mark = data; + if (state == s_req_path || state == s_req_schema || state == s_req_schema_slash + || state == s_req_schema_slash_slash || state == s_req_port + || state == s_req_query_string_start || state == s_req_query_string + || state == s_req_host + || state == s_req_fragment_start || state == s_req_fragment) + url_mark = data; + + for (p=data, pe=data+len; p != pe; p++) { + ch = *p; + + if (PARSING_HEADER(state)) { + ++nread; + /* Buffer overflow attack */ + if (nread > HTTP_MAX_HEADER_SIZE) { + SET_ERRNO(HPE_HEADER_OVERFLOW); + goto error; + } + } + + switch (state) { + + case s_dead: + /* this state is used after a 'Connection: close' message + * the parser will error out if it reads another message + */ + SET_ERRNO(HPE_CLOSED_CONNECTION); + goto error; + + case s_start_req_or_res: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = -1; + + CALLBACK2(message_begin); + + if (ch == 'H') + state = s_res_or_resp_H; + else { + parser->type = HTTP_REQUEST; + goto start_req_method_assign; + } + break; + } + + case s_res_or_resp_H: + if (ch == 'T') { + parser->type = HTTP_RESPONSE; + state = s_res_HT; + } else { + if (ch != 'E') { + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + parser->type = HTTP_REQUEST; + parser->method = HTTP_HEAD; + index = 2; + state = s_req_method; + } + break; + + case s_start_res: + { + parser->flags = 0; + parser->content_length = -1; + + CALLBACK2(message_begin); + + switch (ch) { + case 'H': + state = s_res_H; + break; + + case CR: + case LF: + break; + + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + break; + } + + case s_res_H: + STRICT_CHECK(ch != 'T'); + state = s_res_HT; + break; + + case s_res_HT: + STRICT_CHECK(ch != 'T'); + state = s_res_HTT; + break; + + case s_res_HTT: + STRICT_CHECK(ch != 'P'); + state = s_res_HTTP; + break; + + case s_res_HTTP: + STRICT_CHECK(ch != '/'); + state = s_res_first_http_major; + break; + + case s_res_first_http_major: + if (ch < '0' || ch > '9') { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + state = s_res_http_major; + break; + + /* major HTTP version or dot */ + case s_res_http_major: + { + if (ch == '.') { + state = s_res_first_http_minor; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (parser->http_major > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_res_first_http_minor: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + state = s_res_http_minor; + break; + + /* minor HTTP version or end of request line */ + case s_res_http_minor: + { + if (ch == ' ') { + state = s_res_first_status_code; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (parser->http_minor > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + case s_res_first_status_code: + { + if (!IS_NUM(ch)) { + if (ch == ' ') { + break; + } + + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + parser->status_code = ch - '0'; + state = s_res_status_code; + break; + } + + case s_res_status_code: + { + if (!IS_NUM(ch)) { + switch (ch) { + case ' ': + state = s_res_status; + break; + case CR: + state = s_res_line_almost_done; + break; + case LF: + state = s_header_field_start; + break; + default: + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + break; + } + + parser->status_code *= 10; + parser->status_code += ch - '0'; + + if (parser->status_code > 999) { + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + + break; + } + + case s_res_status: + /* the human readable status. e.g. "NOT FOUND" + * we are not humans so just ignore this */ + if (ch == CR) { + state = s_res_line_almost_done; + break; + } + + if (ch == LF) { + state = s_header_field_start; + break; + } + break; + + case s_res_line_almost_done: + STRICT_CHECK(ch != LF); + state = s_header_field_start; + break; + + case s_start_req: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = -1; + + CALLBACK2(message_begin); + + if (!IS_ALPHA(ch)) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + start_req_method_assign: + parser->method = (enum http_method) 0; + index = 1; + switch (ch) { + case 'A': parser->method = HTTP_ANNOUNCE; break; + case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; + case 'D': parser->method = HTTP_DELETE; /* or DESCRIBE */ break; + case 'F': parser->method = HTTP_FLUSH; break; + case 'G': parser->method = HTTP_GET; /* or GET_PARAMETER */ break; + case 'H': parser->method = HTTP_HEAD; break; + case 'L': parser->method = HTTP_LOCK; break; + case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */ break; + case 'N': parser->method = HTTP_NOTIFY; break; + case 'O': parser->method = HTTP_OPTIONS; break; + case 'P': parser->method = HTTP_POST; + /* or PROPFIND or PROPPATCH or PUT or PATCH or PLAY or PAUSE */ + break; + case 'R': parser->method = HTTP_REPORT; /* or REDIRECT, RECORD */ break; + case 'S': parser->method = HTTP_SUBSCRIBE; /* or SETUP, SET_PARAMETER */ break; + case 'T': parser->method = HTTP_TRACE; /* or TEARDOWN */ break; + case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + state = s_req_method; + break; + } + + case s_req_method: + { + const char *matcher; + if (ch == '\0') { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + matcher = method_strings[parser->method]; + if (ch == ' ' && matcher[index] == '\0') { + state = s_req_spaces_before_url; + } else if (ch == matcher[index]) { + ; /* nada */ + } else if (parser->method == HTTP_CONNECT) { + if (index == 1 && ch == 'H') { + parser->method = HTTP_CHECKOUT; + } else if (index == 2 && ch == 'P') { + parser->method = HTTP_COPY; + } else { + goto error; + } + } else if (index == 2 && parser->method == HTTP_DELETE && ch == 'S') { + parser->method = HTTP_DESCRIBE; + } else if (index == 3 && parser->method == HTTP_GET && ch == '_') { + parser->method = HTTP_GET_PARAMETER; + } else if (parser->method == HTTP_MKCOL) { + if (index == 1 && ch == 'O') { + parser->method = HTTP_MOVE; + } else if (index == 1 && ch == 'E') { + parser->method = HTTP_MERGE; + } else if (index == 1 && ch == '-') { + parser->method = HTTP_MSEARCH; + } else if (index == 2 && ch == 'A') { + parser->method = HTTP_MKACTIVITY; + } else { + goto error; + } + } else if (index == 1 && parser->method == HTTP_POST) { + if (ch == 'R') { + parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ + } else if (ch == 'U') { + parser->method = HTTP_PUT; + } else if (ch == 'A') { + parser->method = HTTP_PATCH; /* or HTTP_PAUSE */ + } else if (ch == 'L') { + parser->method = HTTP_PLAY; + } else { + goto error; + } + } else if (index == 2 && parser->method == HTTP_REPORT) { + if (ch == 'D') { + parser->method = HTTP_REDIRECT; + } else if (ch == 'C') { + parser->method = HTTP_RECORD; + } else { + goto error; + } + } else if (index == 1 && parser->method == HTTP_SUBSCRIBE && ch == 'E') { + parser->method = HTTP_SETUP; /* or HTTP_SET_PARAMETER */ + } else if (index == 3 && parser->method == HTTP_SETUP && ch == '_') { + parser->method = HTTP_SET_PARAMETER; + } else if (index == 1 && parser->method == HTTP_TRACE && ch == 'E') { + parser->method = HTTP_TEARDOWN; + } else if (index == 2 && parser->method == HTTP_UNLOCK && ch == 'S') { + parser->method = HTTP_UNSUBSCRIBE; + } else if (index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') { + parser->method = HTTP_PROPPATCH; + } else if (index == 2 && parser->method == HTTP_PATCH && ch == 'U') { + parser->method = HTTP_PAUSE; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + ++index; + break; + } + case s_req_spaces_before_url: + { + if (ch == ' ') break; + + if (ch == '/' || ch == '*') { + MARK(url); + state = s_req_path; + break; + } + + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * CONNECT is followed by a hostname, which begins with alphanum. + * All other methods are followed by '/' or '*' (handled above). + */ + if (IS_ALPHA(ch) || (parser->method == HTTP_CONNECT && IS_NUM(ch))) { + MARK(url); + state = (parser->method == HTTP_CONNECT) ? s_req_host : s_req_schema; + break; + } + + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + + case s_req_schema: + { + if (IS_ALPHA(ch)) break; + + if (ch == ':') { + state = s_req_schema_slash; + break; + } + + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + + case s_req_schema_slash: + STRICT_CHECK(ch != '/'); + state = s_req_schema_slash_slash; + break; + + case s_req_schema_slash_slash: + STRICT_CHECK(ch != '/'); + state = s_req_host; + break; + + case s_req_host: + { + if (parser->numerichost) { + if (IS_NUMERIC_CHAR(ch)) break; + } else { + if (IS_HOST_CHAR(ch)) break; + } + switch (ch) { + case ':': + state = s_req_port; + break; + case '/': + state = s_req_path; + break; + case ' ': + /* The request line looks like: + * "GET http://foo.bar.com HTTP/1.1" + * That is, there is no path. + */ + CALLBACK(url); + state = s_req_http_start; + break; + case '?': + state = s_req_query_string_start; + break; + default: + SET_ERRNO(HPE_INVALID_HOST); + goto error; + } + break; + } + + case s_req_port: + { + if (IS_NUM(ch)) break; + switch (ch) { + case '/': + state = s_req_path; + break; + case ' ': + /* The request line looks like: + * "GET http://foo.bar.com:1234 HTTP/1.1" + * That is, there is no path. + */ + CALLBACK(url); + state = s_req_http_start; + break; + case '?': + state = s_req_query_string_start; + break; + default: + SET_ERRNO(HPE_INVALID_PORT); + goto error; + } + break; + } + + case s_req_path: + { + if (IS_URL_CHAR(ch)) break; + + switch (ch) { + case ' ': + CALLBACK(url); + state = s_req_http_start; + break; + case CR: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_req_line_almost_done; + break; + case LF: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_header_field_start; + break; + case '?': + state = s_req_query_string_start; + break; + case '#': + state = s_req_fragment_start; + break; + default: + SET_ERRNO(HPE_INVALID_PATH); + goto error; + } + break; + } + + case s_req_query_string_start: + { + if (IS_URL_CHAR(ch)) { + state = s_req_query_string; + break; + } + + switch (ch) { + case '?': + break; /* XXX ignore extra '?' ... is this right? */ + case ' ': + CALLBACK(url); + state = s_req_http_start; + break; + case CR: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_req_line_almost_done; + break; + case LF: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_header_field_start; + break; + case '#': + state = s_req_fragment_start; + break; + default: + SET_ERRNO(HPE_INVALID_QUERY_STRING); + goto error; + } + break; + } + + case s_req_query_string: + { + if (IS_URL_CHAR(ch)) break; + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + break; + case ' ': + CALLBACK(url); + state = s_req_http_start; + break; + case CR: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_req_line_almost_done; + break; + case LF: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_header_field_start; + break; + case '#': + state = s_req_fragment_start; + break; + default: + SET_ERRNO(HPE_INVALID_QUERY_STRING); + goto error; + } + break; + } + + case s_req_fragment_start: + { + if (IS_URL_CHAR(ch)) { + state = s_req_fragment; + break; + } + + switch (ch) { + case ' ': + CALLBACK(url); + state = s_req_http_start; + break; + case CR: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_req_line_almost_done; + break; + case LF: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_header_field_start; + break; + case '?': + state = s_req_fragment; + break; + case '#': + break; + default: + SET_ERRNO(HPE_INVALID_FRAGMENT); + goto error; + } + break; + } + + case s_req_fragment: + { + if (IS_URL_CHAR(ch)) break; + + switch (ch) { + case ' ': + CALLBACK(url); + state = s_req_http_start; + break; + case CR: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_req_line_almost_done; + break; + case LF: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_header_field_start; + break; + case '?': + case '#': + break; + default: + SET_ERRNO(HPE_INVALID_FRAGMENT); + goto error; + } + break; + } + + case s_req_http_start: + switch (ch) { + case 'H': + case 'R': + state = s_req_http_H; + break; + case ' ': + break; + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + break; + + case s_req_http_H: + STRICT_CHECK(ch != 'T'); + state = s_req_http_HT; + break; + + case s_req_http_HT: + STRICT_CHECK(ch != 'T'); + state = s_req_http_HTT; + break; + + case s_req_http_HTT: + STRICT_CHECK(ch != 'P'); + state = s_req_http_HTTP; + break; + + case s_req_http_HTTP: + STRICT_CHECK(ch != '/'); + state = s_req_first_http_major; + break; + + /* first digit of major HTTP version */ + case s_req_first_http_major: + if (ch < '1' || ch > '9') { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + state = s_req_http_major; + break; + + /* major HTTP version or dot */ + case s_req_http_major: + { + if (ch == '.') { + state = s_req_first_http_minor; + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (parser->http_major > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_req_first_http_minor: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + state = s_req_http_minor; + break; + + /* minor HTTP version or end of request line */ + case s_req_http_minor: + { + if (ch == CR) { + state = s_req_line_almost_done; + break; + } + + if (ch == LF) { + state = s_header_field_start; + break; + } + + /* XXX allow spaces after digit? */ + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (parser->http_minor > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* end of request line */ + case s_req_line_almost_done: + { + if (ch != LF) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + + state = s_header_field_start; + break; + } + + case s_header_field_start: + header_field_start: + { + if (ch == CR) { + state = s_headers_almost_done; + break; + } + + if (ch == LF) { + /* they might be just sending \n instead of \r\n so this would be + * the second \n to denote the end of headers*/ + state = s_headers_almost_done; + goto headers_almost_done; + } + + c = TOKEN(ch); + + if (!c) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + MARK(header_field); + + index = 0; + state = s_header_field; + + switch (c) { + case 'c': + header_state = h_C; + break; + + case 'p': + header_state = h_matching_proxy_connection; + break; + + case 't': + header_state = h_matching_transfer_encoding; + break; + + case 'u': + header_state = h_matching_upgrade; + break; + + default: + header_state = h_general; + break; + } + break; + } + + case s_header_field: + { + c = TOKEN(ch); + + if (c) { + switch (header_state) { + case h_general: + break; + + case h_C: + index++; + header_state = (c == 'o' ? h_CO : h_general); + break; + + case h_CO: + index++; + header_state = (c == 'n' ? h_CON : h_general); + break; + + case h_CON: + index++; + switch (c) { + case 'n': + header_state = h_matching_connection; + break; + case 't': + header_state = h_matching_content_length; + break; + default: + header_state = h_general; + break; + } + break; + + /* connection */ + + case h_matching_connection: + index++; + if (index > sizeof(CONNECTION)-1 + || c != CONNECTION[index]) { + header_state = h_general; + } else if (index == sizeof(CONNECTION)-2) { + header_state = h_connection; + } + break; + + /* proxy-connection */ + + case h_matching_proxy_connection: + index++; + if (index > sizeof(PROXY_CONNECTION)-1 + || c != PROXY_CONNECTION[index]) { + header_state = h_general; + } else if (index == sizeof(PROXY_CONNECTION)-2) { + header_state = h_connection; + } + break; + + /* content-length */ + + case h_matching_content_length: + index++; + if (index > sizeof(CONTENT_LENGTH)-1 + || c != CONTENT_LENGTH[index]) { + header_state = h_general; + } else if (index == sizeof(CONTENT_LENGTH)-2) { + header_state = h_content_length; + } + break; + + /* transfer-encoding */ + + case h_matching_transfer_encoding: + index++; + if (index > sizeof(TRANSFER_ENCODING)-1 + || c != TRANSFER_ENCODING[index]) { + header_state = h_general; + } else if (index == sizeof(TRANSFER_ENCODING)-2) { + header_state = h_transfer_encoding; + } + break; + + /* upgrade */ + + case h_matching_upgrade: + index++; + if (index > sizeof(UPGRADE)-1 + || c != UPGRADE[index]) { + header_state = h_general; + } else if (index == sizeof(UPGRADE)-2) { + header_state = h_upgrade; + } + break; + + case h_connection: + case h_content_length: + case h_transfer_encoding: + case h_upgrade: + if (ch != ' ') header_state = h_general; + break; + + default: + assert(0 && "Unknown header_state"); + break; + } + break; + } + + if (ch == ':') { + CALLBACK(header_field); + state = s_header_value_start; + break; + } + + if (ch == CR) { + state = s_header_almost_done; + CALLBACK(header_field); + break; + } + + if (ch == LF) { + CALLBACK(header_field); + state = s_header_field_start; + break; + } + + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + case s_header_value_start: + { + if (ch == ' ' || ch == '\t') break; + + MARK(header_value); + + state = s_header_value; + index = 0; + + if (ch == CR) { + CALLBACK(header_value); + header_state = h_general; + state = s_header_almost_done; + break; + } + + if (ch == LF) { + CALLBACK(header_value); + state = s_header_field_start; + break; + } + + c = LOWER(ch); + + switch (header_state) { + case h_upgrade: + parser->flags |= F_UPGRADE; + header_state = h_general; + break; + + case h_transfer_encoding: + /* looking for 'Transfer-Encoding: chunked' */ + if ('c' == c) { + header_state = h_matching_transfer_encoding_chunked; + } else { + header_state = h_general; + } + break; + + case h_content_length: + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = ch - '0'; + break; + + case h_connection: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + header_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + header_state = h_matching_connection_close; + } else { + header_state = h_general; + } + break; + + default: + header_state = h_general; + break; + } + break; + } + + case s_header_value: + { + + if (ch == CR) { + CALLBACK(header_value); + state = s_header_almost_done; + break; + } + + if (ch == LF) { + CALLBACK(header_value); + goto header_almost_done; + } + + c = LOWER(ch); + + switch (header_state) { + case h_general: + break; + + case h_connection: + case h_transfer_encoding: + assert(0 && "Shouldn't get here."); + break; + + case h_content_length: + if (ch == ' ') break; + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length *= 10; + parser->content_length += ch - '0'; + break; + + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_chunked: + index++; + if (index > sizeof(CHUNKED)-1 + || c != CHUNKED[index]) { + header_state = h_general; + } else if (index == sizeof(CHUNKED)-2) { + header_state = h_transfer_encoding_chunked; + } + break; + + /* looking for 'Connection: keep-alive' */ + case h_matching_connection_keep_alive: + index++; + if (index > sizeof(KEEP_ALIVE)-1 + || c != KEEP_ALIVE[index]) { + header_state = h_general; + } else if (index == sizeof(KEEP_ALIVE)-2) { + header_state = h_connection_keep_alive; + } + break; + + /* looking for 'Connection: close' */ + case h_matching_connection_close: + index++; + if (index > sizeof(CLOSE)-1 || c != CLOSE[index]) { + header_state = h_general; + } else if (index == sizeof(CLOSE)-2) { + header_state = h_connection_close; + } + break; + + case h_transfer_encoding_chunked: + case h_connection_keep_alive: + case h_connection_close: + if (ch != ' ') header_state = h_general; + break; + + default: + state = s_header_value; + header_state = h_general; + break; + } + break; + } + + case s_header_almost_done: + header_almost_done: + { + STRICT_CHECK(ch != LF); + + state = s_header_value_lws; + + switch (header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + default: + break; + } + break; + } + + case s_header_value_lws: + { + if (ch == ' ' || ch == '\t') + state = s_header_value_start; + else + { + state = s_header_field_start; + goto header_field_start; + } + break; + } + + case s_headers_almost_done: + headers_almost_done: + { + STRICT_CHECK(ch != LF); + + if (parser->flags & F_TRAILING) { + /* End of a chunked request */ + CALLBACK2(message_complete); + state = NEW_MESSAGE(); + break; + } + + nread = 0; + + if (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT) { + parser->upgrade = 1; + } + + /* Here we call the headers_complete callback. This is somewhat + * different than other callbacks because if the user returns 1, we + * will interpret that as saying that this message has no body. This + * is needed for the annoying case of recieving a response to a HEAD + * request. + */ + if (settings->on_headers_complete) { + switch (settings->on_headers_complete(parser)) { + case 0: + break; + + case 1: + parser->flags |= F_SKIPBODY; + break; + + default: + parser->state = state; + SET_ERRNO(HPE_CB_headers_complete); + return p - data; /* Error */ + } + } + + /* Exit, the rest of the connect is in a different protocol. */ + if (parser->upgrade) { + CALLBACK2(message_complete); + return (p - data) + 1; + } + + if (parser->flags & F_SKIPBODY) { + CALLBACK2(message_complete); + state = NEW_MESSAGE(); + } else if (parser->flags & F_CHUNKED) { + /* chunked encoding - ignore Content-Length header */ + state = s_chunk_size_start; + } else { + if (parser->content_length == 0) { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + CALLBACK2(message_complete); + state = NEW_MESSAGE(); + } else if (parser->content_length > 0) { + /* Content-Length header given and non-zero */ + state = s_body_identity; + } else { + if (parser->type == HTTP_REQUEST || http_should_keep_alive(parser)) { + /* Assume content-length 0 - read the next */ + CALLBACK2(message_complete); + state = NEW_MESSAGE(); + } else { + /* Read body until EOF */ + state = s_body_identity_eof; + } + } + } + + break; + } + + case s_body_identity: + to_read = MIN(pe - p, (int64_t)parser->content_length); + if (to_read > 0) { + if (settings->on_body) settings->on_body(parser, p, to_read); + p += to_read - 1; + parser->content_length -= to_read; + if (parser->content_length == 0) { + CALLBACK2(message_complete); + state = NEW_MESSAGE(); + } + } + break; + + /* read until EOF */ + case s_body_identity_eof: + to_read = pe - p; + if (to_read > 0) { + if (settings->on_body) settings->on_body(parser, p, to_read); + p += to_read - 1; + } + break; + + case s_chunk_size_start: + { + assert(nread == 1); + assert(parser->flags & F_CHUNKED); + + unhex_val = unhex[(unsigned char)ch]; + if (unhex_val == -1) { + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + parser->content_length = unhex_val; + state = s_chunk_size; + break; + } + + case s_chunk_size: + { + assert(parser->flags & F_CHUNKED); + + if (ch == CR) { + state = s_chunk_size_almost_done; + break; + } + + unhex_val = unhex[(unsigned char)ch]; + + if (unhex_val == -1) { + if (ch == ';' || ch == ' ') { + state = s_chunk_parameters; + break; + } + + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + parser->content_length *= 16; + parser->content_length += unhex_val; + break; + } + + case s_chunk_parameters: + { + assert(parser->flags & F_CHUNKED); + /* just ignore this shit. TODO check for overflow */ + if (ch == CR) { + state = s_chunk_size_almost_done; + break; + } + break; + } + + case s_chunk_size_almost_done: + { + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + + nread = 0; + + if (parser->content_length == 0) { + parser->flags |= F_TRAILING; + state = s_header_field_start; + } else { + state = s_chunk_data; + } + break; + } + + case s_chunk_data: + { + assert(parser->flags & F_CHUNKED); + + to_read = MIN(pe - p, (int64_t)(parser->content_length)); + + if (to_read > 0) { + if (settings->on_body) settings->on_body(parser, p, to_read); + p += to_read - 1; + } + + if (to_read == parser->content_length) { + state = s_chunk_data_almost_done; + } + + parser->content_length -= to_read; + break; + } + + case s_chunk_data_almost_done: + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != CR); + state = s_chunk_data_done; + break; + + case s_chunk_data_done: + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + state = s_chunk_size_start; + break; + + default: + assert(0 && "unhandled state"); + SET_ERRNO(HPE_INVALID_INTERNAL_STATE); + goto error; + } + } + + CALLBACK(header_field); + CALLBACK(header_value); + CALLBACK(url); + + parser->state = state; + parser->header_state = header_state; + parser->index = index; + parser->nread = nread; + + return len; + +error: + if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { + SET_ERRNO(HPE_UNKNOWN); + } + + return (p - data); +} + + +int +http_should_keep_alive (http_parser *parser) +{ + if (parser->http_major > 0 && parser->http_minor > 0) { + /* HTTP/1.1 */ + if (parser->flags & F_CONNECTION_CLOSE) { + return 0; + } else { + return 1; + } + } else { + /* HTTP/1.0 or earlier */ + if (parser->flags & F_CONNECTION_KEEP_ALIVE) { + return 1; + } else { + return 0; + } + } +} + + +const char * http_method_str (enum http_method m) +{ + return method_strings[m]; +} + + +void +http_parser_init (http_parser *parser, enum http_parser_type t, char numerichost) +{ + parser->numerichost = numerichost; + parser->type = t; + parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); + parser->nread = 0; + parser->upgrade = 0; + parser->flags = 0; + parser->method = 0; + parser->http_errno = 0; +} + +const char * +http_errno_name(enum http_errno err) { + assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + return http_strerror_tab[err].name; +} + +const char * +http_errno_description(enum http_errno err) { + assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + return http_strerror_tab[err].description; +} diff --git a/src/http_parser.h b/src/http_parser.h new file mode 100644 index 0000000..4898fda --- /dev/null +++ b/src/http_parser.h @@ -0,0 +1,290 @@ +/* Copyright Joyent, Inc. and other Node contributors. 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. + */ +#ifndef http_parser_h +#define http_parser_h +#ifdef __cplusplus +extern "C" { +#endif + +#define HTTP_PARSER_VERSION_MAJOR 1 +#define HTTP_PARSER_VERSION_MINOR 0 + +#include +#if defined(_WIN32) && !defined(__MINGW32__) && !defined(_MSC_VER) +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; + +typedef unsigned int size_t; +typedef int ssize_t; +#else +#include +#endif + +/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run + * faster + */ +#ifndef HTTP_PARSER_STRICT +# define HTTP_PARSER_STRICT 0 +#endif + +/* Compile with -DHTTP_PARSER_DEBUG=1 to add extra debugging information to + * the error reporting facility. + */ +#ifndef HTTP_PARSER_DEBUG +# define HTTP_PARSER_DEBUG 0 +#endif + + +/* Maximium header size allowed */ +#define HTTP_MAX_HEADER_SIZE (80*1024) + + +typedef struct http_parser http_parser; +typedef struct http_parser_settings http_parser_settings; +typedef struct http_parser_result http_parser_result; + + +/* Callbacks should return non-zero to indicate an error. The parser will + * then halt execution. + * + * The one exception is on_headers_complete. In a HTTP_RESPONSE parser + * returning '1' from on_headers_complete will tell the parser that it + * should not expect a body. This is used when receiving a response to a + * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: + * chunked' headers that indicate the presence of a body. + * + * http_data_cb does not return data chunks. It will be call arbitrarally + * many times for each string. E.G. you might get 10 callbacks for "on_path" + * each providing just a few characters more data. + */ +typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); +typedef int (*http_cb) (http_parser*); + + +/* Request Methods */ +enum http_method + { HTTP_DELETE = 0 + , HTTP_GET + , HTTP_HEAD + , HTTP_POST + , HTTP_PUT + /* pathological */ + , HTTP_CONNECT + , HTTP_OPTIONS + , HTTP_TRACE + /* webdav */ + , HTTP_COPY + , HTTP_LOCK + , HTTP_MKCOL + , HTTP_MOVE + , HTTP_PROPFIND + , HTTP_PROPPATCH + , HTTP_UNLOCK + /* subversion */ + , HTTP_REPORT + , HTTP_MKACTIVITY + , HTTP_CHECKOUT + , HTTP_MERGE + /* upnp */ + , HTTP_MSEARCH + , HTTP_NOTIFY + , HTTP_SUBSCRIBE + , HTTP_UNSUBSCRIBE + /* RFC-5789 */ + , HTTP_PATCH + /* RTSP RFC 2326 */ + , HTTP_DESCRIBE + , HTTP_ANNOUNCE + , HTTP_SETUP + , HTTP_PLAY + , HTTP_PAUSE + , HTTP_TEARDOWN + , HTTP_GET_PARAMETER + , HTTP_SET_PARAMETER + , HTTP_REDIRECT + , HTTP_RECORD + /* RAOP FLUSH */ + , HTTP_FLUSH + }; + + +enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; + + +/* Flag values for http_parser.flags field */ +enum flags + { F_CHUNKED = 1 << 0 + , F_CONNECTION_KEEP_ALIVE = 1 << 1 + , F_CONNECTION_CLOSE = 1 << 2 + , F_TRAILING = 1 << 3 + , F_UPGRADE = 1 << 4 + , F_SKIPBODY = 1 << 5 + }; + + +/* Map for errno-related constants + * + * The provided argument should be a macro that takes 2 arguments. + */ +#define HTTP_ERRNO_MAP(XX) \ + /* No error */ \ + XX(OK, "success") \ + \ + /* Callback-related errors */ \ + XX(CB_message_begin, "the on_message_begin callback failed") \ + XX(CB_path, "the on_path callback failed") \ + XX(CB_query_string, "the on_query_string callback failed") \ + XX(CB_url, "the on_url callback failed") \ + XX(CB_fragment, "the on_fragment callback failed") \ + XX(CB_header_field, "the on_header_field callback failed") \ + XX(CB_header_value, "the on_header_value callback failed") \ + XX(CB_headers_complete, "the on_headers_complete callback failed") \ + XX(CB_body, "the on_body callback failed") \ + XX(CB_message_complete, "the on_message_complete callback failed") \ + \ + /* Parsing-related errors */ \ + XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ + XX(HEADER_OVERFLOW, \ + "too many header bytes seen; overflow detected") \ + XX(CLOSED_CONNECTION, \ + "data received after completed connection: close message") \ + XX(INVALID_VERSION, "invalid HTTP version") \ + XX(INVALID_STATUS, "invalid HTTP status code") \ + XX(INVALID_METHOD, "invalid HTTP method") \ + XX(INVALID_URL, "invalid URL") \ + XX(INVALID_HOST, "invalid host") \ + XX(INVALID_PORT, "invalid port") \ + XX(INVALID_PATH, "invalid path") \ + XX(INVALID_QUERY_STRING, "invalid query string") \ + XX(INVALID_FRAGMENT, "invalid fragment") \ + XX(LF_EXPECTED, "LF character expected") \ + XX(INVALID_HEADER_TOKEN, "invalid character in header") \ + XX(INVALID_CONTENT_LENGTH, \ + "invalid character in content-length header") \ + XX(INVALID_CHUNK_SIZE, \ + "invalid character in chunk size header") \ + XX(INVALID_CONSTANT, "invalid constant string") \ + XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ + XX(STRICT, "strict mode assertion failed") \ + XX(UNKNOWN, "an unknown error occurred") + + +/* Define HPE_* values for each errno value above */ +#define HTTP_ERRNO_GEN(n, s) HPE_##n, +enum http_errno { + HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) +}; +#undef HTTP_ERRNO_GEN + + +/* Get an http_errno value from an http_parser */ +#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) + +/* Get the line number that generated the current error */ +#if HTTP_PARSER_DEBUG +#define HTTP_PARSER_ERRNO_LINE(p) ((p)->error_lineno) +#else +#define HTTP_PARSER_ERRNO_LINE(p) 0 +#endif + + +struct http_parser { + /** PRIVATE **/ + unsigned char numerichost; + unsigned char type : 2; + unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */ + unsigned char state; + unsigned char header_state; + unsigned char index; + + uint32_t nread; + int64_t content_length; + + /** READ-ONLY **/ + unsigned short http_major; + unsigned short http_minor; + unsigned short status_code; /* responses only */ + unsigned char method; /* requests only */ + unsigned char http_errno : 7; + + /* 1 = Upgrade header was present and the parser has exited because of that. + * 0 = No upgrade header present. + * Should be checked when http_parser_execute() returns in addition to + * error checking. + */ + unsigned char upgrade : 1; + +#if HTTP_PARSER_DEBUG + uint32_t error_lineno; +#endif + + /** PUBLIC **/ + void *data; /* A pointer to get hook to the "connection" or "socket" object */ +}; + + +struct http_parser_settings { + http_cb on_message_begin; + http_data_cb on_url; + http_data_cb on_header_field; + http_data_cb on_header_value; + http_cb on_headers_complete; + http_data_cb on_body; + http_cb on_message_complete; +}; + + +void http_parser_init(http_parser *parser, enum http_parser_type type, char numerichost); + + +size_t http_parser_execute(http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len); + + +/* If http_should_keep_alive() in the on_headers_complete or + * on_message_complete callback returns true, then this will be should be + * the last message on the connection. + * If you are the server, respond with the "Connection: close" header. + * If you are the client, close the connection. + */ +int http_should_keep_alive(http_parser *parser); + +/* Returns a string version of the HTTP method. */ +const char *http_method_str(enum http_method m); + +/* Return a string name of the given error */ +const char *http_errno_name(enum http_errno err); + +/* Return a string description of the given error */ +const char *http_errno_description(enum http_errno err); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/http_request.c b/src/http_request.c new file mode 100644 index 0000000..656ccee --- /dev/null +++ b/src/http_request.c @@ -0,0 +1,238 @@ +#include +#include +#include + +#include "http_request.h" +#include "http_parser.h" + +struct http_request_s { + http_parser parser; + http_parser_settings parser_settings; + + const char *method; + char *url; + + char **headers; + int headers_size; + int headers_index; + + char *data; + int datalen; + + int complete; +}; + +static int +on_url(http_parser *parser, const char *at, size_t length) +{ + http_request_t *request = parser->data; + int urllen = request->url ? strlen(request->url) : 0; + + request->url = realloc(request->url, urllen+length+1); + assert(request->url); + + request->url[urllen] = '\0'; + strncat(request->url, at, length); + return 0; +} + +static int +on_header_field(http_parser *parser, const char *at, size_t length) +{ + http_request_t *request = parser->data; + + /* Check if our index is a value */ + if (request->headers_index%2 == 1) { + request->headers_index++; + } + + /* Allocate space for new field-value pair */ + if (request->headers_index == request->headers_size) { + request->headers_size += 2; + request->headers = realloc(request->headers, + request->headers_size*sizeof(char*)); + assert(request->headers); + request->headers[request->headers_index] = NULL; + request->headers[request->headers_index+1] = NULL; + } + + /* Allocate space in the current header string */ + if (request->headers[request->headers_index] == NULL) { + request->headers[request->headers_index] = calloc(1, length+1); + } else { + request->headers[request->headers_index] = realloc( + request->headers[request->headers_index], + strlen(request->headers[request->headers_index])+length+1 + ); + } + assert(request->headers[request->headers_index]); + + strncat(request->headers[request->headers_index], at, length); + return 0; +} + +static int +on_header_value(http_parser *parser, const char *at, size_t length) +{ + http_request_t *request = parser->data; + + /* Check if our index is a field */ + if (request->headers_index%2 == 0) { + request->headers_index++; + } + + /* Allocate space in the current header string */ + if (request->headers[request->headers_index] == NULL) { + request->headers[request->headers_index] = calloc(1, length+1); + } else { + request->headers[request->headers_index] = realloc( + request->headers[request->headers_index], + strlen(request->headers[request->headers_index])+length+1 + ); + } + assert(request->headers[request->headers_index]); + + strncat(request->headers[request->headers_index], at, length); + return 0; +} + +static int +on_body(http_parser *parser, const char *at, size_t length) +{ + http_request_t *request = parser->data; + + request->data = realloc(request->data, request->datalen+length); + assert(request->data); + + memcpy(request->data+request->datalen, at, length); + request->datalen += length; + return 0; +} + +static int +on_message_complete(http_parser *parser) +{ + http_request_t *request = parser->data; + + request->method = http_method_str(request->parser.method); + request->complete = 1; + return 0; +} + +http_request_t * +http_request_init(int numerichost) +{ + http_request_t *request; + + request = calloc(1, sizeof(http_request_t)); + if (!request) { + return NULL; + } + http_parser_init(&request->parser, HTTP_REQUEST, !!numerichost); + request->parser.data = request; + + request->parser_settings.on_url = &on_url; + request->parser_settings.on_header_field = &on_header_field; + request->parser_settings.on_header_value = &on_header_value; + request->parser_settings.on_body = &on_body; + request->parser_settings.on_message_complete = &on_message_complete; + + return request; +} + +void +http_request_destroy(http_request_t *request) +{ + int i; + + if (request) { + free(request->url); + for (i=0; iheaders_size; i++) { + free(request->headers[i]); + } + free(request->headers); + free(request->data); + free(request); + } +} + +int +http_request_add_data(http_request_t *request, const char *data, int datalen) +{ + int ret; + + assert(request); + + ret = http_parser_execute(&request->parser, + &request->parser_settings, + data, datalen); + return ret; +} + +int +http_request_is_complete(http_request_t *request) +{ + assert(request); + return request->complete; +} + +int +http_request_has_error(http_request_t *request) +{ + assert(request); + return (HTTP_PARSER_ERRNO(&request->parser) != HPE_OK); +} + +const char * +http_request_get_error_name(http_request_t *request) +{ + assert(request); + return http_errno_name(HTTP_PARSER_ERRNO(&request->parser)); +} + +const char * +http_request_get_error_description(http_request_t *request) +{ + assert(request); + return http_errno_description(HTTP_PARSER_ERRNO(&request->parser)); +} + +const char * +http_request_get_method(http_request_t *request) +{ + assert(request); + return request->method; +} + +const char * +http_request_get_url(http_request_t *request) +{ + assert(request); + return request->url; +} + +const char * +http_request_get_header(http_request_t *request, const char *name) +{ + int i; + + assert(request); + + for (i=0; iheaders_size; i+=2) { + if (!strcmp(request->headers[i], name)) { + return request->headers[i+1]; + } + } + return NULL; +} + +const char * +http_request_get_data(http_request_t *request, int *datalen) +{ + assert(request); + + if (datalen) { + *datalen = request->datalen; + } + return request->data; +} diff --git a/src/http_request.h b/src/http_request.h new file mode 100644 index 0000000..2323af5 --- /dev/null +++ b/src/http_request.h @@ -0,0 +1,22 @@ +#ifndef HTTP_REQUEST_H +#define HTTP_REQUEST_H + +typedef struct http_request_s http_request_t; + + +http_request_t *http_request_init(int numerichost); + +int http_request_add_data(http_request_t *request, const char *data, int datalen); +int http_request_is_complete(http_request_t *request); +int http_request_has_error(http_request_t *request); + +const char *http_request_get_error_name(http_request_t *request); +const char *http_request_get_error_description(http_request_t *request); +const char *http_request_get_method(http_request_t *request); +const char *http_request_get_url(http_request_t *request); +const char *http_request_get_header(http_request_t *request, const char *name); +const char *http_request_get_data(http_request_t *request, int *datalen); + +void http_request_destroy(http_request_t *request); + +#endif diff --git a/src/http_response.c b/src/http_response.c new file mode 100644 index 0000000..c3fe6c8 --- /dev/null +++ b/src/http_response.c @@ -0,0 +1,133 @@ +#include +#include +#include +#include + +#include "http_response.h" + +struct http_response_s { + int complete; + + char *data; + int data_size; + int data_length; +}; + + +static void +http_response_add_data(http_response_t *response, const char *data, int datalen) +{ + int newdatasize; + + assert(response); + assert(data); + assert(datalen > 0); + + newdatasize = response->data_size; + while (response->data_size+datalen > newdatasize) { + newdatasize *= 2; + } + if (newdatasize != response->data_size) { + response->data = realloc(response->data, newdatasize); + assert(response->data); + } + memcpy(response->data+response->data_length, data, datalen); + response->data_length += datalen; +} + +http_response_t * +http_response_init(const char *protocol, int code, const char *message) +{ + http_response_t *response; + char codestr[4]; + + assert(code >= 100 && code < 1000); + + /* Convert code into string */ + memset(codestr, 0, sizeof(codestr)); + snprintf(codestr, sizeof(codestr), "%u", code); + + response = calloc(1, sizeof(http_response_t)); + if (!response) { + return NULL; + } + + /* Allocate response data */ + response->data_size = 1024; + response->data = malloc(response->data_size); + if (!response->data) { + free(response); + return NULL; + } + + /* Add first line of response to the data array */ + http_response_add_data(response, protocol, strlen(protocol)); + http_response_add_data(response, " ", 1); + http_response_add_data(response, codestr, strlen(codestr)); + http_response_add_data(response, " ", 1); + http_response_add_data(response, message, strlen(message)); + http_response_add_data(response, "\r\n", 2); + + return response; +} + +void +http_response_destroy(http_response_t *response) +{ + if (response) { + free(response->data); + free(response); + } +} + +void +http_response_add_header(http_response_t *response, const char *name, const char *value) +{ + assert(response); + assert(name); + assert(value); + + http_response_add_data(response, name, strlen(name)); + http_response_add_data(response, ": ", 2); + http_response_add_data(response, value, strlen(value)); + http_response_add_data(response, "\r\n", 2); +} + +void +http_response_finish(http_response_t *response, const char *data, int datalen) +{ + assert(response); + assert(datalen==0 || (data && datalen > 0)); + + if (data && datalen > 0) { + const char *hdrname = "Content-Length"; + char hdrvalue[16]; + + memset(hdrvalue, 0, sizeof(hdrvalue)); + snprintf(hdrvalue, sizeof(hdrvalue)-1, "%d", datalen); + + /* Add Content-Length header first */ + http_response_add_data(response, hdrname, strlen(hdrname)); + http_response_add_data(response, ": ", 2); + http_response_add_data(response, hdrvalue, strlen(hdrvalue)); + http_response_add_data(response, "\r\n\r\n", 4); + + /* Add data to the end of response */ + http_response_add_data(response, data, datalen); + } else { + /* Add extra end of line after headers */ + http_response_add_data(response, "\r\n", 2); + } + response->complete = 1; +} + +const char * +http_response_get_data(http_response_t *response, int *datalen) +{ + assert(response); + assert(datalen); + assert(response->complete); + + *datalen = response->data_length; + return response->data; +} diff --git a/src/http_response.h b/src/http_response.h new file mode 100644 index 0000000..2e813f9 --- /dev/null +++ b/src/http_response.h @@ -0,0 +1,15 @@ +#ifndef HTTP_RESPONSE_H +#define HTTP_RESPONSE_H + +typedef struct http_response_s http_response_t; + +http_response_t *http_response_init(const char *protocol, int code, const char *message); + +void http_response_add_header(http_response_t *response, const char *name, const char *value); +void http_response_finish(http_response_t *response, const char *data, int datalen); + +const char *http_response_get_data(http_response_t *response, int *datalen); + +void http_response_destroy(http_response_t *response); + +#endif diff --git a/src/httpd.c b/src/httpd.c new file mode 100644 index 0000000..8a5dcba --- /dev/null +++ b/src/httpd.c @@ -0,0 +1,339 @@ +#include +#include +#include +#include + +#include "httpd.h" +#include "netutils.h" +#include "http_request.h" +#include "compat.h" +#include "logger.h" + +struct http_connection_s { + int connected; + + int socket_fd; + void *user_data; + http_request_t *request; +}; +typedef struct http_connection_s http_connection_t; + +struct httpd_s { + logger_t *logger; + httpd_callbacks_t callbacks; + + int use_rtsp; + + int max_connections; + http_connection_t *connections; + + /* These variables only edited mutex locked */ + int running; + int joined; + thread_handle_t thread; + mutex_handle_t run_mutex; + + /* Server fd for accepting connections */ + int server_fd; +}; + +httpd_t * +httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int max_connections, int use_rtsp) +{ + httpd_t *httpd; + + assert(logger); + assert(callbacks); + assert(max_connections > 0); + + /* Allocate the httpd_t structure */ + httpd = calloc(1, sizeof(httpd_t)); + if (!httpd) { + return NULL; + } + + httpd->use_rtsp = !!use_rtsp; + httpd->max_connections = max_connections; + httpd->connections = calloc(max_connections, sizeof(http_connection_t)); + if (!httpd->connections) { + free(httpd); + return NULL; + } + + /* Use the logger provided */ + httpd->logger = logger; + + /* Save callback pointers */ + memcpy(&httpd->callbacks, callbacks, sizeof(httpd_callbacks_t)); + + /* Initial status joined */ + httpd->running = 0; + httpd->joined = 1; + + return httpd; +} + +void +httpd_destroy(httpd_t *httpd) +{ + if (httpd) { + httpd_stop(httpd); + + free(httpd->connections); + free(httpd); + } +} + +static void +httpd_add_connection(httpd_t *httpd, int fd, unsigned char *local, int local_len, unsigned char *remote, int remote_len) +{ + int i; + + for (i=0; imax_connections; i++) { + if (!httpd->connections[i].connected) { + break; + } + } + if (i == httpd->max_connections) { + logger_log(httpd->logger, LOGGER_INFO, "Max connections reached\n"); + shutdown(fd, SHUT_RDWR); + closesocket(fd); + return; + } + + httpd->connections[i].socket_fd = fd; + httpd->connections[i].connected = 1; + httpd->connections[i].user_data = httpd->callbacks.conn_init(httpd->callbacks.opaque, local, local_len, remote, remote_len); +} + +static void +httpd_remove_connection(httpd_t *httpd, http_connection_t *connection) +{ + if (connection->request) { + http_request_destroy(connection->request); + connection->request = NULL; + } + httpd->callbacks.conn_destroy(connection->user_data); + shutdown(connection->socket_fd, SHUT_WR); + closesocket(connection->socket_fd); + connection->connected = 0; +} + +static THREAD_RETVAL +httpd_thread(void *arg) +{ + httpd_t *httpd = arg; + char buffer[1024]; + int i; + + assert(httpd); + + while (1) { + fd_set rfds; + struct timeval tv; + int nfds=0; + int ret; + + MUTEX_LOCK(httpd->run_mutex); + if (!httpd->running) { + MUTEX_UNLOCK(httpd->run_mutex); + break; + } + MUTEX_UNLOCK(httpd->run_mutex); + + /* Set timeout value to 5ms */ + tv.tv_sec = 1; + tv.tv_usec = 5000; + + /* Get the correct nfds value and set rfds */ + FD_ZERO(&rfds); + FD_SET(httpd->server_fd, &rfds); + nfds = httpd->server_fd+1; + for (i=0; imax_connections; i++) { + int socket_fd; + if (!httpd->connections[i].connected) { + continue; + } + socket_fd = httpd->connections[i].socket_fd; + FD_SET(socket_fd, &rfds); + if (nfds <= socket_fd) { + nfds = socket_fd+1; + } + } + + ret = select(nfds, &rfds, NULL, NULL, &tv); + if (ret == 0) { + /* Timeout happened */ + continue; + } else if (ret == -1) { + /* FIXME: Error happened */ + logger_log(httpd->logger, LOGGER_INFO, "Error in select\n"); + break; + } + + if (FD_ISSET(httpd->server_fd, &rfds)) { + struct sockaddr_storage remote_saddr; + socklen_t remote_saddrlen; + struct sockaddr_storage local_saddr; + socklen_t local_saddrlen; + unsigned char *local, *remote; + int local_len, remote_len; + int fd; + + remote_saddrlen = sizeof(remote_saddr); + fd = accept(httpd->server_fd, (struct sockaddr *)&remote_saddr, &remote_saddrlen); + if (fd == -1) { + /* FIXME: Error happened */ + break; + } + + local_saddrlen = sizeof(local_saddr); + ret = getsockname(fd, (struct sockaddr *)&local_saddr, &local_saddrlen); + if (ret == -1) { + closesocket(fd); + continue; + } + + logger_log(httpd->logger, LOGGER_INFO, "Accepted client on socket %d\n", fd); + local = netutils_get_address(&local_saddr, &local_len); + remote = netutils_get_address(&remote_saddr, &remote_len); + + httpd_add_connection(httpd, fd, local, local_len, remote, remote_len); + } + for (i=0; imax_connections; i++) { + http_connection_t *connection = &httpd->connections[i]; + + if (!connection->connected) { + continue; + } + if (!FD_ISSET(connection->socket_fd, &rfds)) { + continue; + } + + /* If not in the middle of request, allocate one */ + if (!connection->request) { + connection->request = http_request_init(httpd->use_rtsp); + assert(connection->request); + } + + logger_log(httpd->logger, LOGGER_INFO, "Receiving on socket %d\n", httpd->connections[i].socket_fd); + ret = recv(connection->socket_fd, buffer, sizeof(buffer), 0); + if (ret == 0) { + logger_log(httpd->logger, LOGGER_INFO, "Connection closed\n"); + httpd_remove_connection(httpd, connection); + continue; + } + + /* Parse HTTP request from data read from connection */ + http_request_add_data(connection->request, buffer, ret); + if (http_request_has_error(connection->request)) { + logger_log(httpd->logger, LOGGER_INFO, "Error in parsing: %s\n", http_request_get_error_name(connection->request)); + httpd_remove_connection(httpd, connection); + continue; + } + + /* If request is finished, process and deallocate */ + if (http_request_is_complete(connection->request)) { + http_response_t *response = NULL; + + httpd->callbacks.conn_request(connection->user_data, connection->request, &response); + http_request_destroy(connection->request); + connection->request = NULL; + + if (response) { + const char *data; + int datalen; + int written; + int ret; + + /* Get response data and datalen */ + data = http_response_get_data(response, &datalen); + + written = 0; + while (written < datalen) { + ret = send(connection->socket_fd, data+written, datalen-written, 0); + if (ret == -1) { + /* FIXME: Error happened */ + logger_log(httpd->logger, LOGGER_INFO, "Error in sending data\n"); + break; + } + written += ret; + } + } else { + logger_log(httpd->logger, LOGGER_INFO, "Didn't get response\n"); + } + http_response_destroy(response); + } + } + } + + /* Remove all connections that are still connected */ + for (i=0; imax_connections; i++) { + http_connection_t *connection = &httpd->connections[i]; + + if (!connection->connected) { + continue; + } + logger_log(httpd->logger, LOGGER_INFO, "Removing connection\n"); + httpd_remove_connection(httpd, connection); + } + + logger_log(httpd->logger, LOGGER_INFO, "Exiting thread\n"); + + return 0; +} + +int +httpd_start(httpd_t *httpd, unsigned short *port) +{ + assert(httpd); + assert(port); + + MUTEX_LOCK(httpd->run_mutex); + if (httpd->running || !httpd->joined) { + MUTEX_UNLOCK(httpd->run_mutex); + return 0; + } + + httpd->server_fd = netutils_init_socket(port, 1, 0); + if (httpd->server_fd == -1) { + logger_log(httpd->logger, LOGGER_INFO, "Error initialising socket %d\n", SOCKET_GET_ERROR()); + MUTEX_UNLOCK(httpd->run_mutex); + return -1; + } + if (listen(httpd->server_fd, 5) == -1) { + logger_log(httpd->logger, LOGGER_INFO, "Error listening to socket\n"); + MUTEX_UNLOCK(httpd->run_mutex); + return -2; + } + logger_log(httpd->logger, LOGGER_INFO, "Initialized server socket\n"); + + /* Set values correctly and create new thread */ + httpd->running = 1; + httpd->joined = 0; + THREAD_CREATE(httpd->thread, httpd_thread, httpd); + MUTEX_UNLOCK(httpd->run_mutex); + + return 1; +} + +void +httpd_stop(httpd_t *httpd) +{ + assert(httpd); + + MUTEX_LOCK(httpd->run_mutex); + if (!httpd->running || httpd->joined) { + MUTEX_UNLOCK(httpd->run_mutex); + return; + } + httpd->running = 0; + MUTEX_UNLOCK(httpd->run_mutex); + + THREAD_JOIN(httpd->thread); + + MUTEX_LOCK(httpd->run_mutex); + httpd->joined = 1; + MUTEX_UNLOCK(httpd->run_mutex); +} + diff --git a/src/httpd.h b/src/httpd.h new file mode 100644 index 0000000..08062ed --- /dev/null +++ b/src/httpd.h @@ -0,0 +1,27 @@ +#ifndef HTTPD_H +#define HTTPD_H + +#include "logger.h" +#include "http_request.h" +#include "http_response.h" + +typedef struct httpd_s httpd_t; + +struct httpd_callbacks_s { + void* opaque; + void* (*conn_init)(void *opaque, unsigned char *local, int locallen, unsigned char *remote, int remotelen); + void (*conn_request)(void *ptr, http_request_t *request, http_response_t **response); + void (*conn_destroy)(void *ptr); +}; +typedef struct httpd_callbacks_s httpd_callbacks_t; + + +httpd_t *httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int max_connections, int use_rtsp); + +int httpd_start(httpd_t *httpd, unsigned short *port); +void httpd_stop(httpd_t *httpd); + +void httpd_destroy(httpd_t *httpd); + + +#endif diff --git a/src/logger.c b/src/logger.c new file mode 100644 index 0000000..0ea1867 --- /dev/null +++ b/src/logger.c @@ -0,0 +1,90 @@ +#include +#include +#include +#include + +#include "logger.h" + +void +logger_init(logger_t *logger) +{ + assert(logger); + + logger->level = LOGGER_INFO; + logger->callback = NULL; +} + +void +logger_set_level(logger_t *logger, int level) +{ + assert(logger); + + logger->level = level; +} + +void +logger_set_callback(logger_t *logger, logger_callback_t callback) +{ + assert(logger); + + logger->callback = callback; +} + +static char * +logger_utf8_to_local(const char *str) +{ + char *ret = NULL; + +/* FIXME: This is only implemented on Windows for now */ +#if defined(_WIN32) || defined(_WIN64) + int wclen, mblen; + WCHAR *wcstr; + BOOL failed; + + wclen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + wcstr = malloc(sizeof(WCHAR) * wclen); + MultiByteToWideChar(CP_UTF8, 0, str, -1, wcstr, wclen); + + mblen = WideCharToMultiByte(CP_ACP, 0, wcstr, wclen, NULL, 0, NULL, &failed); + if (failed) { + /* Invalid characters in input, conversion failed */ + free(wcstr); + return NULL; + } + + ret = malloc(sizeof(CHAR) * mblen); + WideCharToMultiByte(CP_ACP, 0, wcstr, wclen, ret, mblen, NULL, NULL); + free(wcstr); +#endif + + return ret; +} + +void +logger_log(logger_t *logger, int level, const char *fmt, ...) +{ + char buffer[4096]; + va_list ap; + + if (level > logger->level) + return; + + buffer[sizeof(buffer)-1] = '\0'; + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer)-1, fmt, ap); + va_end(ap); + + if (logger->callback) { + logger->callback(level, buffer); + } else { + char *local = logger_utf8_to_local(buffer); + + if (local) { + fprintf(stderr, "%s", local); + free(local); + } else { + fprintf(stderr, "%s", buffer); + } + } +} + diff --git a/src/logger.h b/src/logger.h new file mode 100644 index 0000000..ba8b95f --- /dev/null +++ b/src/logger.h @@ -0,0 +1,28 @@ +#ifndef LOGGER_H +#define LOGGER_H + +/* Define syslog style log levels */ +#define LOGGER_EMERG 0 /* system is unusable */ +#define LOGGER_ALERT 1 /* action must be taken immediately */ +#define LOGGER_CRIT 2 /* critical conditions */ +#define LOGGER_ERR 3 /* error conditions */ +#define LOGGER_WARNING 4 /* warning conditions */ +#define LOGGER_NOTICE 5 /* normal but significant condition */ +#define LOGGER_INFO 6 /* informational */ +#define LOGGER_DEBUG 7 /* debug-level messages */ + +typedef void (*logger_callback_t)(int level, char *msg); + +struct logger_s { + int level; + logger_callback_t callback; +}; +typedef struct logger_s logger_t; + +void logger_init(logger_t *logger); +void logger_set_level(logger_t *logger, int level); +void logger_set_callback(logger_t *logger, logger_callback_t callback); + +void logger_log(logger_t *logger, int level, const char *fmt, ...); + +#endif diff --git a/src/memalign.h b/src/memalign.h new file mode 100644 index 0000000..422e241 --- /dev/null +++ b/src/memalign.h @@ -0,0 +1,39 @@ +#ifndef MEMALIGN_H +#define MEMALIGN_H + +#if defined(WIN32) + +#define SYSTEM_GET_PAGESIZE(ret) do {\ + SYSTEM_INFO si;\ + GetSystemInfo(&si);\ + ret = si.dwPageSize;\ +} while(0) +#define SYSTEM_GET_TIME(ret) ret = timeGetTime() + +#define ALIGNED_MALLOC(memptr, alignment, size) do {\ + char *ptr = malloc(sizeof(void*) + (size) + (alignment)-1);\ + memptr = NULL;\ + if (ptr) {\ + size_t ptrval = (size_t)ptr + sizeof(void*) + (alignment)-1;\ + ptrval = ptrval / (alignment) * (alignment);\ + memptr = (void *)ptrval;\ + *(((void **)memptr)-1) = ptr;\ + }\ +} while(0) +#define ALIGNED_FREE(memptr) free(*(((void **)memptr)-1)) + +#else + +#define SYSTEM_GET_PAGESIZE(ret) ret = sysconf(_SC_PAGESIZE) +#define SYSTEM_GET_TIME(ret) do {\ + struct timeval tv;\ + gettimeofday(&tv, NULL);\ + ret = (unsigned int)(tv.tv_sec*1000 + tv.tv_usec/1000);\ +} while(0) + +#define ALIGNED_MALLOC(memptr, alignment, size) if (posix_memalign((void **)&memptr, alignment, size)) memptr = NULL +#define ALIGNED_FREE(memptr) free(memptr) + +#endif + +#endif diff --git a/src/netutils.c b/src/netutils.c new file mode 100644 index 0000000..255822b --- /dev/null +++ b/src/netutils.c @@ -0,0 +1,139 @@ +#include +#include +#include + +#include "compat.h" + +int +netutils_init() +{ +#ifdef WIN32 + WORD wVersionRequested; + WSADATA wsaData; + int ret; + + wVersionRequested = MAKEWORD(2, 2); + ret = WSAStartup(wVersionRequested, &wsaData); + if (ret) { + return -1; + } + + if (LOBYTE(wsaData.wVersion) != 2 || + HIBYTE(wsaData.wVersion) != 2) { + /* Version mismatch, requested version not found */ + return -1; + } +#endif + return 0; +} + +void +netutils_cleanup() +{ +#ifdef WIN32 + WSACleanup(); +#endif +} + +int +netutils_init_socket(unsigned short *port, int use_ipv6, int use_udp) +{ + int family = use_ipv6 ? AF_INET6 : AF_INET; + int type = use_udp ? SOCK_DGRAM : SOCK_STREAM; + int proto = use_udp ? IPPROTO_UDP : IPPROTO_TCP; + + struct sockaddr_storage saddr; + socklen_t socklen; + int server_fd; + int ret; + + assert(port); + + server_fd = socket(family, type, proto); + if (server_fd == -1) { + goto cleanup; + } + + memset(&saddr, 0, sizeof(saddr)); + if (use_ipv6) { + struct sockaddr_in6 *sin6ptr = (struct sockaddr_in6 *)&saddr; + + /* Initialize sockaddr for bind */ + sin6ptr->sin6_family = family; + sin6ptr->sin6_addr = in6addr_any; + sin6ptr->sin6_port = htons(*port); + + socklen = sizeof(*sin6ptr); + ret = bind(server_fd, (struct sockaddr *)sin6ptr, socklen); + if (ret == -1) { + goto cleanup; + } + + ret = getsockname(server_fd, (struct sockaddr *)sin6ptr, &socklen); + if (ret == -1) { + goto cleanup; + } + *port = ntohs(sin6ptr->sin6_port); + } else { + struct sockaddr_in *sinptr = (struct sockaddr_in *)&saddr; + + /* Initialize sockaddr for bind */ + sinptr->sin_family = family; + sinptr->sin_addr.s_addr = INADDR_ANY; + sinptr->sin_port = htons(*port); + + socklen = sizeof(*sinptr); + ret = bind(server_fd, (struct sockaddr *)sinptr, socklen); + if (ret == -1) { + goto cleanup; + } + + ret = getsockname(server_fd, (struct sockaddr *)sinptr, &socklen); + if (ret == -1) { + goto cleanup; + } + *port = ntohs(sinptr->sin_port); + } + return server_fd; + +cleanup: + ret = SOCKET_GET_ERROR(); + if (server_fd != -1) { + closesocket(server_fd); + } + SOCKET_SET_ERROR(ret); + return -1; +} + +unsigned char * +netutils_get_address(void *sockaddr, int *length) +{ + unsigned char ipv4_prefix[] = { 0,0,0,0,0,0,0,0,0,0,255,255 }; + struct sockaddr *address = sockaddr; + + assert(address); + assert(length); + + if (address->sa_family == AF_INET) { + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *)address; + *length = sizeof(sin->sin_addr.s_addr); + return (unsigned char *)&sin->sin_addr.s_addr; + } else if (address->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *)address; + if (!memcmp(sin6->sin6_addr.s6_addr, ipv4_prefix, 12)) { + /* Actually an embedded IPv4 address */ + *length = sizeof(sin6->sin6_addr.s6_addr)-12; + return (sin6->sin6_addr.s6_addr+12); + } + *length = sizeof(sin6->sin6_addr.s6_addr); + return sin6->sin6_addr.s6_addr; + } + + *length = 0; + return NULL; +} + diff --git a/src/netutils.h b/src/netutils.h new file mode 100644 index 0000000..c469162 --- /dev/null +++ b/src/netutils.h @@ -0,0 +1,10 @@ +#ifndef NETUTILS_H +#define NETUTILS_H + +int netutils_init(); +void netutils_cleanup(); + +int netutils_init_socket(unsigned short *port, int use_ipv6, int use_udp); +unsigned char *netutils_get_address(void *sockaddr, int *length); + +#endif diff --git a/src/raop.c b/src/raop.c new file mode 100644 index 0000000..a83bb98 --- /dev/null +++ b/src/raop.c @@ -0,0 +1,340 @@ +#include +#include +#include +#include + +#include "raop.h" +#include "raop_rtp.h" +#include "rsakey.h" +#include "httpd.h" +#include "sdp.h" + +#include "global.h" +#include "utils.h" +#include "netutils.h" +#include "logger.h" + +/* Actually 345 bytes for 2048-bit key */ +#define MAX_SIGNATURE_LEN 512 + +struct raop_s { + /* Callbacks for audio */ + raop_callbacks_t callbacks; + + /* Logger instance */ + logger_t logger; + + /* HTTP daemon and RSA key */ + httpd_t *httpd; + rsakey_t *rsakey; + + /* Hardware address information */ + unsigned char hwaddr[MAX_HWADDR_LEN]; + int hwaddrlen; +}; + +struct raop_conn_s { + raop_t *raop; + raop_rtp_t *raop_rtp; + + unsigned char *local; + int locallen; + + unsigned char *remote; + int remotelen; +}; +typedef struct raop_conn_s raop_conn_t; + +static void * +conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remote, int remotelen) +{ + raop_conn_t *conn; + int i; + + conn = calloc(1, sizeof(raop_conn_t)); + if (!conn) { + return NULL; + } + conn->raop = opaque; + conn->raop_rtp = NULL; + + logger_log(&conn->raop->logger, LOGGER_INFO, "Local: "); + for (i=0; iraop->logger, LOGGER_INFO, "%02x", local[i]); + } + logger_log(&conn->raop->logger, LOGGER_INFO, "Remote: "); + for (i=0; iraop->logger, LOGGER_INFO, "%02x", remote[i]); + } + logger_log(&conn->raop->logger, LOGGER_INFO, "\n"); + + conn->local = malloc(locallen); + assert(conn->local); + memcpy(conn->local, local, locallen); + + conn->remote = malloc(remotelen); + assert(conn->remote); + memcpy(conn->remote, remote, remotelen); + + conn->locallen = locallen; + conn->remotelen = remotelen; + return conn; +} + +static void +conn_request(void *ptr, http_request_t *request, http_response_t **response) +{ + raop_conn_t *conn = ptr; + raop_t *raop = conn->raop; + + http_response_t *res; + const char *method; + const char *cseq; + const char *challenge; + + method = http_request_get_method(request); + cseq = http_request_get_header(request, "CSeq"); + if (!method || !cseq) { + return; + } + + res = http_response_init("RTSP/1.0", 200, "OK"); + http_response_add_header(res, "CSeq", cseq); + http_response_add_header(res, "Apple-Jack-Status", "connected; type=analog"); + + challenge = http_request_get_header(request, "Apple-Challenge"); + if (challenge) { + char signature[MAX_SIGNATURE_LEN]; + + memset(signature, 0, sizeof(signature)); + rsakey_sign(raop->rsakey, signature, sizeof(signature), challenge, + conn->local, conn->locallen, raop->hwaddr, raop->hwaddrlen); + logger_log(&conn->raop->logger, LOGGER_INFO, "Got signature: %s\n", signature); + http_response_add_header(res, "Apple-Response", signature); + } + if (!strcmp(method, "OPTIONS")) { + http_response_add_header(res, "Public", "ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER"); + } else if (!strcmp(method, "ANNOUNCE")) { + const char *data; + int datalen; + + unsigned char aeskey[16]; + unsigned char aesiv[16]; + int aeskeylen, aesivlen; + + data = http_request_get_data(request, &datalen); + if (data) { + sdp_t *sdp = sdp_init(data, datalen); + logger_log(&conn->raop->logger, LOGGER_INFO, "rsaaeskey: %s\n", sdp_get_rsaaeskey(sdp)); + logger_log(&conn->raop->logger, LOGGER_INFO, "aesiv: %s\n", sdp_get_aesiv(sdp)); + + aeskeylen = rsakey_decrypt(raop->rsakey, aeskey, sizeof(aeskey), + sdp_get_rsaaeskey(sdp)); + aesivlen = rsakey_parseiv(raop->rsakey, aesiv, sizeof(aesiv), + sdp_get_aesiv(sdp)); + logger_log(&conn->raop->logger, LOGGER_INFO, "aeskeylen: %d\n", aeskeylen); + logger_log(&conn->raop->logger, LOGGER_INFO, "aesivlen: %d\n", aesivlen); + + conn->raop_rtp = raop_rtp_init(&raop->logger, &raop->callbacks, sdp_get_fmtp(sdp), aeskey, aesiv); + sdp_destroy(sdp); + } + } else if (!strcmp(method, "SETUP")) { + unsigned short cport=0, tport=0, dport=0; + const char *transport; + char buffer[1024]; + int use_udp; + + transport = http_request_get_header(request, "Transport"); + assert(transport); + + logger_log(&conn->raop->logger, LOGGER_INFO, "Transport: %s\n", transport); + use_udp = strncmp(transport, "RTP/AVP/TCP", 11); + + /* FIXME: Should use the parsed ports for resend */ + raop_rtp_start(conn->raop_rtp, use_udp, 1234, 1234, &cport, &tport, &dport); + logger_log(&conn->raop->logger, LOGGER_INFO, "cport %d tport %d dport %d\n", cport, tport, dport); + + memset(buffer, 0, sizeof(buffer)); + if (use_udp) { + snprintf(buffer, sizeof(buffer)-1, + "RTP/AVP/UDP;unicast;mode=record;timing_port=%u;events;control_port=%u;server_port=%u", + tport, cport, dport); + } else { + snprintf(buffer, sizeof(buffer)-1, + "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record;server_port=%u", + dport); + } + logger_log(&conn->raop->logger, LOGGER_INFO, "Responding with %s\n", buffer); + http_response_add_header(res, "Transport", buffer); + http_response_add_header(res, "Session", "DEADBEEF"); + } else if (!strcmp(method, "SET_PARAMETER")) { + const char *data; + int datalen; + char *datastr; + + data = http_request_get_data(request, &datalen); + datastr = calloc(1, datalen+1); + if (datastr) { + memcpy(datastr, data, datalen); + if (!strncmp(datastr, "volume: ", 8)) { + float vol = 0.0; + sscanf(data+8, "%f", &vol); + raop_rtp_set_volume(conn->raop_rtp, vol); + } + } + } else if (!strcmp(method, "FLUSH")) { + const char *rtpinfo; + int next_seq = -1; + + rtpinfo = http_request_get_header(request, "RTP-Info"); + assert(rtpinfo); + + logger_log(&conn->raop->logger, LOGGER_INFO, "RTP-Info: %s\n", rtpinfo); + if (!strncmp(rtpinfo, "seq=", 4)) { + next_seq = strtol(rtpinfo+4, NULL, 10); + } + raop_rtp_flush(conn->raop_rtp, next_seq); + } else if (!strcmp(method, "TEARDOWN")) { + http_response_add_header(res, "Connection", "close"); + raop_rtp_stop(conn->raop_rtp); + raop_rtp_destroy(conn->raop_rtp); + conn->raop_rtp = NULL; + } + http_response_finish(res, NULL, 0); + + logger_log(&conn->raop->logger, LOGGER_INFO, "Got request %s with URL %s\n", method, http_request_get_url(request)); + *response = res; +} + +static void +conn_destroy(void *ptr) +{ + raop_conn_t *conn = ptr; + + if (conn->raop_rtp) { + raop_rtp_destroy(conn->raop_rtp); + } + free(conn->local); + free(conn->remote); + free(conn); +} + +raop_t * +raop_init(raop_callbacks_t *callbacks, const char *pemkey, const char *hwaddr, int hwaddrlen) +{ + raop_t *raop; + httpd_t *httpd; + rsakey_t *rsakey; + httpd_callbacks_t httpd_cbs; + + assert(callbacks); + assert(pemkey); + assert(hwaddr); + + /* Initialize the network */ + if (netutils_init() < 0) { + return NULL; + } + + /* Validate the callbacks structure */ + if (!callbacks->audio_init || !callbacks->audio_set_volume || + !callbacks->audio_process || !callbacks->audio_flush || + !callbacks->audio_destroy) { + return NULL; + } + + /* Validate hardware address */ + if (hwaddrlen > MAX_HWADDR_LEN) { + return NULL; + } + + /* Allocate the raop_t structure */ + raop = calloc(1, sizeof(raop_t)); + if (!raop) { + return NULL; + } + + /* Initialize the logger */ + logger_init(&raop->logger); + + /* Set HTTP callbacks to our handlers */ + memset(&httpd_cbs, 0, sizeof(httpd_cbs)); + httpd_cbs.opaque = raop; + httpd_cbs.conn_init = &conn_init; + httpd_cbs.conn_request = &conn_request; + httpd_cbs.conn_destroy = &conn_destroy; + + /* Initialize the http daemon */ + httpd = httpd_init(&raop->logger, &httpd_cbs, 10, 1); + if (!httpd) { + free(raop); + return NULL; + } + + /* Copy callbacks structure */ + memcpy(&raop->callbacks, callbacks, sizeof(raop_callbacks_t)); + + /* Initialize RSA key handler */ + rsakey = rsakey_init_pem(pemkey); + if (!rsakey) { + free(httpd); + free(raop); + return NULL; + } + + raop->httpd = httpd; + raop->rsakey = rsakey; + + /* Copy hwaddr to resulting structure */ + memcpy(raop->hwaddr, hwaddr, hwaddrlen); + raop->hwaddrlen = hwaddrlen; + + return raop; +} + +raop_t * +raop_init_from_keyfile(raop_callbacks_t *callbacks, const char *keyfile, const char *hwaddr, int hwaddrlen) +{ + raop_t *raop; + char *pemstr; + + if (utils_read_file(&pemstr, keyfile) < 0) { + return NULL; + } + raop = raop_init(callbacks, pemstr, hwaddr, hwaddrlen); + free(pemstr); + return raop; +} + +void +raop_destroy(raop_t *raop) +{ + if (raop) { + raop_stop(raop); + + httpd_destroy(raop->httpd); + rsakey_destroy(raop->rsakey); + free(raop); + + /* Cleanup the network */ + netutils_cleanup(); + } +} + +int +raop_start(raop_t *raop, unsigned short *port) +{ + assert(raop); + assert(port); + + return httpd_start(raop->httpd, port); +} + +void +raop_stop(raop_t *raop) +{ + assert(raop); + + httpd_stop(raop->httpd); +} + diff --git a/src/raop_buffer.c b/src/raop_buffer.c new file mode 100644 index 0000000..45a1aeb --- /dev/null +++ b/src/raop_buffer.c @@ -0,0 +1,380 @@ +#include +#include +#include +#include + +#include "raop_buffer.h" +#include "raop_rtp.h" +#include "utils.h" + +#include +#include "crypto/crypto.h" +#include "alac/alac.h" + +#define RAOP_BUFFER_LENGTH 16 + +typedef struct { + /* Packet available */ + int available; + + /* RTP header */ + unsigned char flags; + unsigned char type; + unsigned short seqnum; + unsigned int timestamp; + unsigned int ssrc; + + /* Audio buffer of valid length */ + int audio_buffer_size; + int audio_buffer_len; + void *audio_buffer; +} raop_buffer_entry_t; + +struct raop_buffer_s { + /* AES key and IV */ + unsigned char aeskey[RAOP_AESKEY_LEN]; + unsigned char aesiv[RAOP_AESIV_LEN]; + + /* ALAC decoder */ + ALACSpecificConfig alacConfig; + alac_file *alac; + + /* First and last seqnum */ + int is_empty; + unsigned short first_seqnum; + unsigned short last_seqnum; + + /* RTP buffer entries */ + raop_buffer_entry_t entries[RAOP_BUFFER_LENGTH]; + + /* Buffer of all audio buffers */ + int buffer_size; + void *buffer; +}; + + + +static int +get_fmtp_info(ALACSpecificConfig *config, const char *fmtp) +{ + int intarr[12]; + char *strptr; + int i; + + /* Parse fmtp string to integers */ + strptr = strdup(fmtp); + for (i=0; i<12; i++) { + if (strptr == NULL) { + free(strptr); + return -1; + } + intarr[i] = atoi(utils_strsep(&strptr, " ")); + } + free(strptr); + strptr = NULL; + + /* Fill the config struct */ + config->frameLength = intarr[1]; + config->compatibleVersion = intarr[2]; + config->bitDepth = intarr[3]; + config->pb = intarr[4]; + config->mb = intarr[5]; + config->kb = intarr[6]; + config->numChannels = intarr[7]; + config->maxRun = intarr[8]; + config->maxFrameBytes = intarr[9]; + config->avgBitRate = intarr[10]; + config->sampleRate = intarr[11]; + + /* Validate supported audio types */ + if (config->bitDepth != 16) { + return -2; + } + if (config->numChannels != 2) { + return -3; + } + + return 0; +} + +static void +set_decoder_info(alac_file *alac, ALACSpecificConfig *config) +{ + unsigned char decoder_info[48]; + memset(decoder_info, 0, sizeof(decoder_info)); + +#define SET_UINT16(buf, value)do{\ + (buf)[0] = (unsigned char)((value) >> 8);\ + (buf)[1] = (unsigned char)(value);\ + }while(0) + +#define SET_UINT32(buf, value)do{\ + (buf)[0] = (unsigned char)((value) >> 24);\ + (buf)[1] = (unsigned char)((value) >> 16);\ + (buf)[2] = (unsigned char)((value) >> 8);\ + (buf)[3] = (unsigned char)(value);\ + }while(0) + + /* Construct decoder info buffer */ + SET_UINT32(&decoder_info[24], config->frameLength); + decoder_info[28] = config->compatibleVersion; + decoder_info[29] = config->bitDepth; + decoder_info[30] = config->pb; + decoder_info[31] = config->mb; + decoder_info[32] = config->kb; + decoder_info[33] = config->numChannels; + SET_UINT16(&decoder_info[34], config->maxRun); + SET_UINT32(&decoder_info[36], config->maxFrameBytes); + SET_UINT32(&decoder_info[40], config->avgBitRate); + SET_UINT32(&decoder_info[44], config->sampleRate); + alac_set_info(alac, (char *) decoder_info); +} + +raop_buffer_t * +raop_buffer_init(const char *fmtp, + const unsigned char *aeskey, + const unsigned char *aesiv) +{ + raop_buffer_t *raop_buffer; + int audio_buffer_size; + ALACSpecificConfig *alacConfig; + int i; + + assert(fmtp); + assert(aeskey); + assert(aesiv); + + raop_buffer = calloc(1, sizeof(raop_buffer_t)); + if (!raop_buffer) { + return NULL; + } + + /* Parse fmtp information */ + alacConfig = &raop_buffer->alacConfig; + if (get_fmtp_info(alacConfig, fmtp) < 0) { + free(raop_buffer); + return NULL; + } + + /* Allocate the output audio buffers */ + audio_buffer_size = alacConfig->frameLength * + alacConfig->numChannels * + alacConfig->bitDepth/8; + raop_buffer->buffer_size = audio_buffer_size * + RAOP_BUFFER_LENGTH; + raop_buffer->buffer = malloc(raop_buffer->buffer_size); + if (!raop_buffer->buffer) { + free(raop_buffer); + return NULL; + } + for (i=0; ientries[i]; + entry->audio_buffer_size = audio_buffer_size; + entry->audio_buffer_len = 0; + entry->audio_buffer = raop_buffer->buffer+i*audio_buffer_size; + } + + /* Initialize ALAC decoder */ + raop_buffer->alac = create_alac(alacConfig->bitDepth, + alacConfig->numChannels); + if (!raop_buffer->alac) { + free(raop_buffer->buffer); + free(raop_buffer); + return NULL; + } + set_decoder_info(raop_buffer->alac, alacConfig); + + /* Initialize AES keys */ + memcpy(raop_buffer->aeskey, aeskey, RAOP_AESKEY_LEN); + memcpy(raop_buffer->aesiv, aesiv, RAOP_AESIV_LEN); + + /* Mark buffer as empty */ + raop_buffer->is_empty = 1; + return raop_buffer; +} + +void +raop_buffer_destroy(raop_buffer_t *raop_buffer) +{ + if (raop_buffer) { + free(raop_buffer->buffer); + free(raop_buffer->alac); + free(raop_buffer); + } +} + +const ALACSpecificConfig * +raop_buffer_get_config(raop_buffer_t *raop_buffer) +{ + assert(raop_buffer); + + return &raop_buffer->alacConfig; +} + +static short +seqnum_cmp(unsigned short s1, unsigned short s2) +{ + return (s1 - s2); +} + +int +raop_buffer_queue(raop_buffer_t *raop_buffer, unsigned char *data, unsigned short datalen, int use_seqnum) +{ + unsigned char packetbuf[RAOP_PACKET_LEN]; + unsigned short seqnum; + raop_buffer_entry_t *entry; + int encryptedlen; + AES_CTX aes_ctx; + int outputlen; + + assert(raop_buffer); + + /* Check packet data length is valid */ + if (datalen < 12 || datalen > RAOP_PACKET_LEN) { + return -1; + } + + /* Get correct seqnum for the packet */ + if (use_seqnum) { + seqnum = (data[2] << 8) | data[3]; + } else { + seqnum = raop_buffer->first_seqnum; + } + + /* If this packet is too late, just skip it */ + if (!raop_buffer->is_empty && seqnum_cmp(seqnum, raop_buffer->first_seqnum) < 0) { + return 0; + } + + /* Check that there is always space in the buffer, otherwise flush */ + if (seqnum_cmp(seqnum, raop_buffer->first_seqnum+RAOP_BUFFER_LENGTH) >= 0) { + raop_buffer_flush(raop_buffer, seqnum); + } + + /* Get entry corresponding our seqnum */ + entry = &raop_buffer->entries[seqnum % RAOP_BUFFER_LENGTH]; + if (entry->available && seqnum_cmp(entry->seqnum, seqnum) == 0) { + /* Packet resend, we can safely ignore */ + return 0; + } + + /* Update the raop_buffer entry header */ + entry->flags = data[0]; + entry->type = data[1]; + entry->seqnum = seqnum; + entry->timestamp = (data[4] << 24) | (data[5] << 16) | + (data[6] << 8) | data[7]; + entry->ssrc = (data[8] << 24) | (data[9] << 16) | + (data[10] << 8) | data[11]; + entry->available = 1; + + /* Decrypt audio data */ + encryptedlen = (datalen-12)/16*16; + AES_set_key(&aes_ctx, raop_buffer->aeskey, raop_buffer->aesiv, AES_MODE_128); + AES_convert_key(&aes_ctx); + AES_cbc_decrypt(&aes_ctx, &data[12], packetbuf, encryptedlen); + memcpy(packetbuf+encryptedlen, &data[12+encryptedlen], datalen-12-encryptedlen); + + /* Decode ALAC audio data */ + outputlen = entry->audio_buffer_size; + decode_frame(raop_buffer->alac, packetbuf, entry->audio_buffer, &outputlen); + entry->audio_buffer_len = outputlen; + + /* Update the raop_buffer seqnums */ + if (raop_buffer->is_empty) { + raop_buffer->first_seqnum = seqnum; + raop_buffer->last_seqnum = seqnum; + raop_buffer->is_empty = 0; + } + if (seqnum_cmp(seqnum, raop_buffer->last_seqnum) > 0) { + raop_buffer->last_seqnum = seqnum; + } + return 1; +} + +const void * +raop_buffer_dequeue(raop_buffer_t *raop_buffer, int *length, int no_resend) +{ + short buflen; + raop_buffer_entry_t *entry; + + /* Calculate number of entries in the current buffer */ + buflen = seqnum_cmp(raop_buffer->last_seqnum, raop_buffer->first_seqnum)+1; + + /* Cannot dequeue from empty buffer */ + if (raop_buffer->is_empty || buflen <= 0) { + return NULL; + } + + /* Get the first buffer entry for inspection */ + entry = &raop_buffer->entries[raop_buffer->first_seqnum % RAOP_BUFFER_LENGTH]; + if (no_resend) { + /* If we do no resends, always return the first entry */ + } else if (!entry->available) { + /* Check how much we have space left in the buffer */ + if (buflen < RAOP_BUFFER_LENGTH) { + /* Return nothing and hope resend gets on time */ + return NULL; + } + /* Risk of buffer overrun, return empty buffer */ + } + + /* Update buffer and validate entry */ + raop_buffer->first_seqnum += 1; + if (!entry->available) { + /* Return an empty audio buffer to skip audio */ + *length = entry->audio_buffer_size; + memset(entry->audio_buffer, 0, *length); + return entry->audio_buffer; + } + entry->available = 0; + + /* Return entry audio buffer */ + *length = entry->audio_buffer_len; + entry->audio_buffer_len = 0; + return entry->audio_buffer; +} + +void +raop_buffer_handle_resends(raop_buffer_t *raop_buffer, raop_resend_cb_t resend_cb, void *opaque) +{ + raop_buffer_entry_t *entry; + + assert(raop_buffer); + assert(resend_cb); + + if (seqnum_cmp(raop_buffer->first_seqnum, raop_buffer->last_seqnum) < 0) { + int seqnum, count; + + for (seqnum=raop_buffer->first_seqnum; seqnum_cmp(seqnum, raop_buffer->last_seqnum)<0; seqnum++) { + entry = &raop_buffer->entries[seqnum % RAOP_BUFFER_LENGTH]; + if (entry->available) { + break; + } + } + if (seqnum_cmp(seqnum, raop_buffer->first_seqnum) == 0) { + return; + } + count = seqnum_cmp(seqnum, raop_buffer->first_seqnum); + resend_cb(opaque, raop_buffer->first_seqnum, count); + } +} + +void +raop_buffer_flush(raop_buffer_t *raop_buffer, int next_seq) +{ + int i; + + assert(raop_buffer); + + for (i=0; ientries[i].available = 0; + raop_buffer->entries[i].audio_buffer_len = 0; + } + if (next_seq < 0 || next_seq > 0xffff) { + raop_buffer->is_empty = 1; + } else { + raop_buffer->first_seqnum = next_seq; + raop_buffer->last_seqnum = next_seq-1; + } +} diff --git a/src/raop_buffer.h b/src/raop_buffer.h new file mode 100644 index 0000000..619d8b2 --- /dev/null +++ b/src/raop_buffer.h @@ -0,0 +1,35 @@ +#ifndef RAOP_BUFFER_H +#define RAOP_BUFFER_H + +typedef struct raop_buffer_s raop_buffer_t; + +/* From ALACMagicCookieDescription.txt at http://http://alac.macosforge.org/ */ +typedef struct { + unsigned int frameLength; + unsigned char compatibleVersion; + unsigned char bitDepth; + unsigned char pb; + unsigned char mb; + unsigned char kb; + unsigned char numChannels; + unsigned short maxRun; + unsigned int maxFrameBytes; + unsigned int avgBitRate; + unsigned int sampleRate; +} ALACSpecificConfig; + +typedef int (*raop_resend_cb_t)(void *opaque, unsigned short seqno, unsigned short count); + +raop_buffer_t *raop_buffer_init(const char *fmtp, + const unsigned char *aeskey, + const unsigned char *aesiv); + +const ALACSpecificConfig *raop_buffer_get_config(raop_buffer_t *raop_buffer); +int raop_buffer_queue(raop_buffer_t *raop_buffer, unsigned char *data, unsigned short datalen, int use_seqnum); +const void *raop_buffer_dequeue(raop_buffer_t *raop_buffer, int *length, int no_resend); +void raop_buffer_handle_resends(raop_buffer_t *raop_buffer, raop_resend_cb_t resend_cb, void *opaque); +void raop_buffer_flush(raop_buffer_t *raop_buffer, int next_seq); + +void raop_buffer_destroy(raop_buffer_t *raop_buffer); + +#endif diff --git a/src/raop_rtp.c b/src/raop_rtp.c new file mode 100644 index 0000000..075770c --- /dev/null +++ b/src/raop_rtp.c @@ -0,0 +1,521 @@ +#include +#include +#include +#include +#include + +#include "raop_rtp.h" +#include "raop.h" +#include "raop_buffer.h" +#include "netutils.h" +#include "compat.h" +#include "logger.h" + +#define NO_FLUSH (-42) + +struct raop_rtp_s { + logger_t *logger; + raop_callbacks_t callbacks; + + raop_buffer_t *buffer; + + /* These variables only edited mutex locked */ + int running; + int joined; + float volume; + int flush; + thread_handle_t thread; + mutex_handle_t run_mutex; + + /* Remote control and timing ports */ + unsigned short control_rport; + unsigned short timing_rport; + + /* Sockets for control, timing and data */ + int csock, tsock, dsock; + + /* Local control, timing and data ports */ + unsigned short control_lport; + unsigned short timing_lport; + unsigned short data_lport; + + struct sockaddr_storage control_saddr; + socklen_t control_saddr_len; + unsigned short control_seqnum; +}; + +raop_rtp_t * +raop_rtp_init(logger_t *logger, raop_callbacks_t *callbacks, const char *fmtp, + const unsigned char *aeskey, const unsigned char *aesiv) +{ + raop_rtp_t *raop_rtp; + + assert(logger); + + raop_rtp = calloc(1, sizeof(raop_rtp_t)); + if (!raop_rtp) { + return NULL; + } + raop_rtp->logger = logger; + memcpy(&raop_rtp->callbacks, callbacks, sizeof(raop_callbacks_t)); + raop_rtp->buffer = raop_buffer_init(fmtp, aeskey, aesiv); + if (!raop_rtp->buffer) { + free(raop_rtp); + return NULL; + } + + raop_rtp->running = 0; + raop_rtp->joined = 1; + raop_rtp->flush = NO_FLUSH; + MUTEX_CREATE(raop_rtp->run_mutex); + + return raop_rtp; +} + +void +raop_rtp_destroy(raop_rtp_t *raop_rtp) +{ + if (raop_rtp) { + raop_rtp_stop(raop_rtp); + + MUTEX_DESTROY(raop_rtp->run_mutex); + raop_buffer_destroy(raop_rtp->buffer); + free(raop_rtp); + } +} + +static int +raop_rtp_init_sockets(raop_rtp_t *raop_rtp, int use_ipv6, int use_udp) +{ + int csock = -1, tsock = -1, dsock = -1; + unsigned short cport = 0, tport = 0, dport = 0; + + assert(raop_rtp); + + if (use_udp) { + csock = netutils_init_socket(&cport, use_ipv6, use_udp); + tsock = netutils_init_socket(&tport, use_ipv6, use_udp); + if (csock == -1 || tsock == -1) { + goto sockets_cleanup; + } + } + dsock = netutils_init_socket(&dport, use_ipv6, use_udp); + if (dsock == -1) { + goto sockets_cleanup; + } + + /* Listen to the data socket if using TCP */ + if (!use_udp) { + if (listen(dsock, 1) < 0) + goto sockets_cleanup; + } + + /* Set socket descriptors */ + raop_rtp->csock = csock; + raop_rtp->tsock = tsock; + raop_rtp->dsock = dsock; + + /* Set port values */ + raop_rtp->control_lport = cport; + raop_rtp->timing_lport = tport; + raop_rtp->data_lport = dport; + return 0; + +sockets_cleanup: + if (csock != -1) closesocket(csock); + if (tsock != -1) closesocket(tsock); + if (dsock != -1) closesocket(dsock); + return -1; +} + +static int +raop_rtp_resend_callback(void *opaque, unsigned short seqnum, unsigned short count) +{ + raop_rtp_t *raop_rtp = opaque; + unsigned char packet[8]; + unsigned short ourseqnum; + struct sockaddr *addr; + socklen_t addrlen; + + addr = (struct sockaddr *)&raop_rtp->control_saddr; + addrlen = raop_rtp->control_saddr_len; + + logger_log(raop_rtp->logger, LOGGER_INFO, "Got resend request %d %d\n", seqnum, count); + ourseqnum = raop_rtp->control_seqnum++; + + /* Fill the request buffer */ + packet[0] = 0x80; + packet[1] = 0x55|0x80; + packet[2] = (ourseqnum >> 8); + packet[3] = ourseqnum; + packet[4] = (seqnum >> 8); + packet[5] = seqnum; + packet[6] = (count >> 8); + packet[7] = count; + + sendto(raop_rtp->csock, (const char *)packet, sizeof(packet), 0, addr, addrlen); + return 0; +} + +static THREAD_RETVAL +raop_rtp_thread_udp(void *arg) +{ + raop_rtp_t *raop_rtp = arg; + unsigned char packet[RAOP_PACKET_LEN]; + unsigned int packetlen; + struct sockaddr_storage saddr; + socklen_t saddrlen; + + const ALACSpecificConfig *config; + void *cb_data = NULL; + + assert(raop_rtp); + + config = raop_buffer_get_config(raop_rtp->buffer); + raop_rtp->callbacks.audio_init(raop_rtp->callbacks.cls, &cb_data, + config->bitDepth, + config->numChannels, + config->sampleRate); + + while(1) { + int volume_changed; + float volume; + int flush; + + fd_set rfds; + struct timeval tv; + int nfds, ret; + + MUTEX_LOCK(raop_rtp->run_mutex); + if (!raop_rtp->running) { + MUTEX_UNLOCK(raop_rtp->run_mutex); + break; + } + /* Read the volume level */ + volume_changed = (volume != raop_rtp->volume); + volume = raop_rtp->volume; + + /* Read the flush value */ + flush = raop_rtp->flush; + raop_rtp->flush = NO_FLUSH; + MUTEX_UNLOCK(raop_rtp->run_mutex); + + /* Call set_volume callback if changed */ + if (volume_changed) { + raop_rtp->callbacks.audio_set_volume(raop_rtp->callbacks.cls, cb_data, volume); + } + if (flush != NO_FLUSH) { + raop_buffer_flush(raop_rtp->buffer, flush); + raop_rtp->callbacks.audio_flush(raop_rtp->callbacks.cls, cb_data); + } + + /* Set timeout value to 5ms */ + tv.tv_sec = 0; + tv.tv_usec = 5000; + + /* Get the correct nfds value */ + nfds = raop_rtp->csock+1; + if (raop_rtp->tsock >= nfds) + nfds = raop_rtp->tsock+1; + if (raop_rtp->dsock >= nfds) + nfds = raop_rtp->dsock+1; + + /* Set rfds and call select */ + FD_ZERO(&rfds); + FD_SET(raop_rtp->csock, &rfds); + FD_SET(raop_rtp->tsock, &rfds); + FD_SET(raop_rtp->dsock, &rfds); + ret = select(nfds, &rfds, NULL, NULL, &tv); + if (ret == 0) { + /* Timeout happened */ + continue; + } else if (ret == -1) { + /* FIXME: Error happened */ + break; + } + + if (FD_ISSET(raop_rtp->csock, &rfds)) { + saddrlen = sizeof(saddr); + packetlen = recvfrom(raop_rtp->csock, (char *)packet, sizeof(packet), 0, + (struct sockaddr *)&saddr, &saddrlen); + + /* FIXME: Get destination address here */ + memcpy(&raop_rtp->control_saddr, &saddr, saddrlen); + raop_rtp->control_saddr_len = saddrlen; + + if (packetlen >= 12) { + char type = packet[1] & ~0x80; + + logger_log(raop_rtp->logger, LOGGER_INFO, "Got control packet of type 0x%02x\n", type); + if (type == 0x56) { + /* Handle resent data packet */ + int ret = raop_buffer_queue(raop_rtp->buffer, packet+4, packetlen-4, 1); + assert(ret >= 0); + } + } + } else if (FD_ISSET(raop_rtp->tsock, &rfds)) { + logger_log(raop_rtp->logger, LOGGER_INFO, "Would have timing packet in queue\n"); + } else if (FD_ISSET(raop_rtp->dsock, &rfds)) { + saddrlen = sizeof(saddr); + packetlen = recvfrom(raop_rtp->dsock, (char *)packet, sizeof(packet), 0, + (struct sockaddr *)&saddr, &saddrlen); + if (packetlen >= 12) { + int no_resend = (raop_rtp->control_rport == 0); + int ret; + + const void *audiobuf; + int audiobuflen; + + ret = raop_buffer_queue(raop_rtp->buffer, packet, packetlen, 1); + assert(ret >= 0); + + /* Decode all frames in queue */ + while ((audiobuf = raop_buffer_dequeue(raop_rtp->buffer, &audiobuflen, no_resend))) { + raop_rtp->callbacks.audio_process(raop_rtp->callbacks.cls, cb_data, audiobuf, audiobuflen); + } + + /* Handle possible resend requests */ + if (!no_resend) { + raop_buffer_handle_resends(raop_rtp->buffer, raop_rtp_resend_callback, raop_rtp); + } + } + } + } + logger_log(raop_rtp->logger, LOGGER_INFO, "Exiting thread\n"); + raop_rtp->callbacks.audio_destroy(raop_rtp->callbacks.cls, cb_data); + + return 0; +} + +static THREAD_RETVAL +raop_rtp_thread_tcp(void *arg) +{ + raop_rtp_t *raop_rtp = arg; + int stream_fd = -1; + unsigned char packet[RAOP_PACKET_LEN]; + unsigned int packetlen = 0; + + const ALACSpecificConfig *config; + void *cb_data = NULL; + + assert(raop_rtp); + + config = raop_buffer_get_config(raop_rtp->buffer); + raop_rtp->callbacks.audio_init(raop_rtp->callbacks.cls, &cb_data, + config->bitDepth, + config->numChannels, + config->sampleRate); + + while (1) { + int volume_changed; + float volume; + + fd_set rfds; + struct timeval tv; + int nfds, ret; + + MUTEX_LOCK(raop_rtp->run_mutex); + if (!raop_rtp->running) { + MUTEX_UNLOCK(raop_rtp->run_mutex); + break; + } + volume_changed = (volume != raop_rtp->volume); + volume = raop_rtp->volume; + MUTEX_UNLOCK(raop_rtp->run_mutex); + + /* Call set_volume callback if changed */ + if (volume_changed) { + raop_rtp->callbacks.audio_set_volume(raop_rtp->callbacks.cls, cb_data, volume); + } + + /* Set timeout value to 5ms */ + tv.tv_sec = 0; + tv.tv_usec = 5000; + + /* Get the correct nfds value and set rfds */ + FD_ZERO(&rfds); + if (stream_fd == -1) { + FD_SET(raop_rtp->dsock, &rfds); + nfds = raop_rtp->dsock+1; + } else { + FD_SET(stream_fd, &rfds); + nfds = stream_fd+1; + } + ret = select(nfds, &rfds, NULL, NULL, &tv); + if (ret == 0) { + /* Timeout happened */ + continue; + } else if (ret == -1) { + /* FIXME: Error happened */ + logger_log(raop_rtp->logger, LOGGER_INFO, "Error in select\n"); + break; + } + if (stream_fd == -1 && FD_ISSET(raop_rtp->dsock, &rfds)) { + struct sockaddr_storage saddr; + socklen_t saddrlen; + + logger_log(raop_rtp->logger, LOGGER_INFO, "Accepting client\n"); + saddrlen = sizeof(saddr); + stream_fd = accept(raop_rtp->dsock, (struct sockaddr *)&saddr, &saddrlen); + if (stream_fd == -1) { + /* FIXME: Error happened */ + logger_log(raop_rtp->logger, LOGGER_INFO, "Error in accept %d %s\n", errno, strerror(errno)); + break; + } + } + if (stream_fd != -1 && FD_ISSET(stream_fd, &rfds)) { + unsigned int rtplen=0; + char type; + + const void *audiobuf; + int audiobuflen; + + ret = recv(stream_fd, (char *)(packet+packetlen), sizeof(packet)-packetlen, 0); + if (ret == 0) { + /* TCP socket closed */ + logger_log(raop_rtp->logger, LOGGER_INFO, "TCP socket closed\n"); + break; + } else if (ret == -1) { + /* FIXME: Error happened */ + logger_log(raop_rtp->logger, LOGGER_INFO, "Error in recv\n"); + break; + } + packetlen += ret; + + /* Check that we have enough bytes */ + if (packetlen < 4) { + continue; + } + if (packet[0] != '$' || packet[1] != '\0') { + /* FIXME: Incorrect RTP magic bytes */ + break; + } + rtplen = (packet[2] << 8) | packet[3]; + if (rtplen > sizeof(packet)) { + /* FIXME: Too long packet */ + logger_log(raop_rtp->logger, LOGGER_INFO, "Error, packet too long %d\n", rtplen); + break; + } + if (packetlen < 4+rtplen) { + continue; + } + + /* Packet is valid, process it */ + type = packet[4+1] & ~0x80; + ret = raop_buffer_queue(raop_rtp->buffer, packet+4, rtplen, 0); + assert(ret >= 0); + + /* Remove processed bytes from packet buffer */ + memmove(packet, packet+4+rtplen, packetlen-rtplen); + packetlen -= 4+rtplen; + + /* Decode the received frame */ + if ((audiobuf = raop_buffer_dequeue(raop_rtp->buffer, &audiobuflen, 1))) { + raop_rtp->callbacks.audio_process(raop_rtp->callbacks.cls, cb_data, audiobuf, audiobuflen); + } + } + } + + /* Close the stream file descriptor */ + if (stream_fd != -1) { + closesocket(stream_fd); + } + + logger_log(raop_rtp->logger, LOGGER_INFO, "Exiting thread\n"); + raop_rtp->callbacks.audio_destroy(raop_rtp->callbacks.cls, cb_data); + + return 0; +} + +void +raop_rtp_start(raop_rtp_t *raop_rtp, int use_udp, unsigned short control_rport, unsigned short timing_rport, + unsigned short *control_lport, unsigned short *timing_lport, unsigned short *data_lport) +{ + assert(raop_rtp); + + MUTEX_LOCK(raop_rtp->run_mutex); + if (raop_rtp->running || !raop_rtp->joined) { + MUTEX_UNLOCK(raop_rtp->run_mutex); + return; + } + + /* Initialize ports and sockets */ + raop_rtp->control_rport = control_rport; + raop_rtp->timing_rport = timing_rport; + if (raop_rtp_init_sockets(raop_rtp, 1, use_udp) < 0) { + logger_log(raop_rtp->logger, LOGGER_INFO, "Initializing sockets failed\n"); + MUTEX_UNLOCK(raop_rtp->run_mutex); + return; + } + if (control_lport) *control_lport = raop_rtp->control_lport; + if (timing_lport) *timing_lport = raop_rtp->timing_lport; + if (data_lport) *data_lport = raop_rtp->data_lport; + + /* Create the thread and initialize running values */ + raop_rtp->running = 1; + raop_rtp->joined = 0; + if (use_udp) { + THREAD_CREATE(raop_rtp->thread, raop_rtp_thread_udp, raop_rtp); + } else { + THREAD_CREATE(raop_rtp->thread, raop_rtp_thread_tcp, raop_rtp); + } + MUTEX_UNLOCK(raop_rtp->run_mutex); +} + +void +raop_rtp_set_volume(raop_rtp_t *raop_rtp, float volume) +{ + assert(raop_rtp); + + if (volume > 0.0f) { + volume = 0.0f; + } else if (volume < -144.0f) { + volume = -144.0f; + } + + /* Set volume in thread instead */ + MUTEX_LOCK(raop_rtp->run_mutex); + raop_rtp->volume = volume; + MUTEX_UNLOCK(raop_rtp->run_mutex); +} + +void +raop_rtp_flush(raop_rtp_t *raop_rtp, int next_seq) +{ + assert(raop_rtp); + + /* Call flush in thread instead */ + MUTEX_LOCK(raop_rtp->run_mutex); + raop_rtp->flush = next_seq; + MUTEX_UNLOCK(raop_rtp->run_mutex); +} + +void +raop_rtp_stop(raop_rtp_t *raop_rtp) +{ + assert(raop_rtp); + + /* Check that we are running and thread is not + * joined (should never be while still running) */ + MUTEX_LOCK(raop_rtp->run_mutex); + if (!raop_rtp->running || raop_rtp->joined) { + MUTEX_UNLOCK(raop_rtp->run_mutex); + return; + } + raop_rtp->running = 0; + MUTEX_UNLOCK(raop_rtp->run_mutex); + + /* Join the thread */ + THREAD_JOIN(raop_rtp->thread); + if (raop_rtp->csock != -1) closesocket(raop_rtp->csock); + if (raop_rtp->tsock != -1) closesocket(raop_rtp->tsock); + if (raop_rtp->dsock != -1) closesocket(raop_rtp->dsock); + + /* Flush buffer into initial state */ + raop_buffer_flush(raop_rtp->buffer, -1); + + /* Mark thread as joined */ + MUTEX_LOCK(raop_rtp->run_mutex); + raop_rtp->joined = 1; + MUTEX_UNLOCK(raop_rtp->run_mutex); +} diff --git a/src/raop_rtp.h b/src/raop_rtp.h new file mode 100644 index 0000000..25755e4 --- /dev/null +++ b/src/raop_rtp.h @@ -0,0 +1,23 @@ +#ifndef RAOP_RTP_H +#define RAOP_RTP_H + +/* For raop_callbacks_t */ +#include "raop.h" +#include "logger.h" + +#define RAOP_AESKEY_LEN 16 +#define RAOP_AESIV_LEN 16 +#define RAOP_PACKET_LEN 32768 + +typedef struct raop_rtp_s raop_rtp_t; + +raop_rtp_t *raop_rtp_init(logger_t *logger, raop_callbacks_t *callbacks, const char *fmtp, + const unsigned char *aeskey, const unsigned char *aesiv); +void raop_rtp_start(raop_rtp_t *raop_rtp, int use_udp, unsigned short control_rport, unsigned short timing_rport, + unsigned short *control_lport, unsigned short *timing_lport, unsigned short *data_lport); +void raop_rtp_set_volume(raop_rtp_t *raop_rtp, float volume); +void raop_rtp_flush(raop_rtp_t *raop_rtp, int next_seq); +void raop_rtp_stop(raop_rtp_t *raop_rtp); +void raop_rtp_destroy(raop_rtp_t *raop_rtp); + +#endif diff --git a/src/rsakey.c b/src/rsakey.c new file mode 100644 index 0000000..485e21e --- /dev/null +++ b/src/rsakey.c @@ -0,0 +1,371 @@ +#include +#include +#include +#include + +#include "rsakey.h" +#include "rsapem.h" +#include "base64.h" +#include "crypto/crypto.h" + +#define RSA_MIN_PADLEN 8 +#define MAX_KEYLEN 512 + +struct rsakey_s { + int keylen; /* length of modulus in bytes */ + BI_CTX *bi_ctx; /* bigint context */ + + bigint *n; /* modulus */ + bigint *e; /* public exponent */ + bigint *d; /* private exponent */ + + int use_crt; /* use chinese remainder theorem */ + bigint *p; /* p as in m = pq */ + bigint *q; /* q as in m = pq */ + bigint *dP; /* d mod (p-1) */ + bigint *dQ; /* d mod (q-1) */ + bigint *qInv; /* q^-1 mod p */ + + base64_t *base64; +}; + +rsakey_t * +rsakey_init(const unsigned char *modulus, int mod_len, + const unsigned char *pub_exp, int pub_len, + const unsigned char *priv_exp, int priv_len, + /* Optional, used for crt optimization */ + const unsigned char *p, int p_len, + const unsigned char *q, int q_len, + const unsigned char *dP, int dP_len, + const unsigned char *dQ, int dQ_len, + const unsigned char *qInv, int qInv_len) +{ + rsakey_t *rsakey; + int i; + + if (mod_len > MAX_KEYLEN) { + return NULL; + } + + rsakey = calloc(1, sizeof(rsakey_t)); + if (!rsakey) { + return NULL; + } + rsakey->base64 = base64_init(NULL, 0, 0); + if (!rsakey->base64) { + free(rsakey); + return NULL; + } + + /* Initialize structure */ + for (i=0; !modulus[i] && ikeylen = mod_len-i; + rsakey->bi_ctx = bi_initialize(); + + /* Import public and private keys */ + rsakey->n = bi_import(rsakey->bi_ctx, modulus, mod_len); + rsakey->e = bi_import(rsakey->bi_ctx, pub_exp, pub_len); + rsakey->d = bi_import(rsakey->bi_ctx, priv_exp, priv_len); + + if (p && q && dP && dQ && qInv) { + /* Import crt optimization keys */ + rsakey->p = bi_import(rsakey->bi_ctx, p, p_len); + rsakey->q = bi_import(rsakey->bi_ctx, q, q_len); + rsakey->dP = bi_import(rsakey->bi_ctx, dP, dP_len); + rsakey->dQ = bi_import(rsakey->bi_ctx, dQ, dQ_len); + rsakey->qInv = bi_import(rsakey->bi_ctx, qInv, qInv_len); + + /* Set imported keys either permanent or modulo */ + bi_permanent(rsakey->dP); + bi_permanent(rsakey->dQ); + bi_permanent(rsakey->qInv); + bi_set_mod(rsakey->bi_ctx, rsakey->p, BIGINT_P_OFFSET); + bi_set_mod(rsakey->bi_ctx, rsakey->q, BIGINT_Q_OFFSET); + + rsakey->use_crt = 1; + } + + /* Add keys to the bigint context */ + bi_set_mod(rsakey->bi_ctx, rsakey->n, BIGINT_M_OFFSET); + bi_permanent(rsakey->e); + bi_permanent(rsakey->d); + return rsakey; +} + +rsakey_t * +rsakey_init_pem(const char *pemstr) +{ + rsapem_t *rsapem; + unsigned char *modulus=NULL; unsigned int mod_len=0; + unsigned char *pub_exp=NULL; unsigned int pub_len=0; + unsigned char *priv_exp=NULL; unsigned int priv_len=0; + unsigned char *p=NULL; unsigned int p_len=0; + unsigned char *q=NULL; unsigned int q_len=0; + unsigned char *dP=NULL; unsigned int dP_len=0; + unsigned char *dQ=NULL; unsigned int dQ_len=0; + unsigned char *qInv=NULL; unsigned int qInv_len=0; + rsakey_t *rsakey=NULL; + + rsapem = rsapem_init(pemstr); + if (!rsapem) { + return NULL; + } + + /* Read public and private keys */ + mod_len = rsapem_read_vector(rsapem, &modulus); + pub_len = rsapem_read_vector(rsapem, &pub_exp); + priv_len = rsapem_read_vector(rsapem, &priv_exp); + /* Read private keys for crt optimization */ + p_len = rsapem_read_vector(rsapem, &p); + q_len = rsapem_read_vector(rsapem, &q); + dP_len = rsapem_read_vector(rsapem, &dP); + dQ_len = rsapem_read_vector(rsapem, &dQ); + qInv_len = rsapem_read_vector(rsapem, &qInv); + + if (modulus && pub_exp && priv_exp) { + /* Initialize rsakey value */ + rsakey = rsakey_init(modulus, mod_len, pub_exp, pub_len, priv_exp, priv_len, + p, p_len, q, q_len, dP, dP_len, dQ, dQ_len, qInv, qInv_len); + } + + free(modulus); + free(pub_exp); + free(priv_exp); + free(p); + free(q); + free(dP); + free(dQ); + free(qInv); + rsapem_destroy(rsapem); + return rsakey; +} + +void +rsakey_destroy(rsakey_t *rsakey) +{ + if (rsakey) { + bi_free_mod(rsakey->bi_ctx, BIGINT_M_OFFSET); + bi_depermanent(rsakey->e); + bi_depermanent(rsakey->d); + bi_free(rsakey->bi_ctx, rsakey->e); + bi_free(rsakey->bi_ctx, rsakey->d); + + if (rsakey->use_crt) { + bi_free_mod(rsakey->bi_ctx, BIGINT_P_OFFSET); + bi_free_mod(rsakey->bi_ctx, BIGINT_Q_OFFSET); + bi_depermanent(rsakey->dP); + bi_depermanent(rsakey->dQ); + bi_depermanent(rsakey->qInv); + bi_free(rsakey->bi_ctx, rsakey->dP); + bi_free(rsakey->bi_ctx, rsakey->dQ); + bi_free(rsakey->bi_ctx, rsakey->qInv); + } + bi_terminate(rsakey->bi_ctx); + + base64_destroy(rsakey->base64); + free(rsakey); + } +} + +static bigint * +rsakey_modpow(rsakey_t *rsakey, bigint *msg) +{ + if (rsakey->use_crt) { + return bi_crt(rsakey->bi_ctx, msg, + rsakey->dP, rsakey->dQ, + rsakey->p, rsakey->q, rsakey->qInv); + } else { + rsakey->bi_ctx->mod_offset = BIGINT_M_OFFSET; + return bi_mod_power(rsakey->bi_ctx, msg, rsakey->d); + } +} + +int +rsakey_sign(rsakey_t *rsakey, char *dst, int dstlen, const char *b64digest, + unsigned char *ipaddr, int ipaddrlen, + unsigned char *hwaddr, int hwaddrlen) +{ + unsigned char buffer[MAX_KEYLEN]; + unsigned char *digest; + int digestlen; + int inputlen; + bigint *bi_in; + bigint *bi_out; + int idx; + + assert(rsakey); + + if (dstlen < base64_encoded_length(rsakey->base64, rsakey->keylen)) { + return -1; + } + + /* Decode the base64 digest */ + digestlen = base64_decode(rsakey->base64, &digest, b64digest, strlen(b64digest)); + if (digestlen < 0) { + return -2; + } + + /* Calculate the input data length */ + inputlen = digestlen+ipaddrlen+hwaddrlen; + if (inputlen > rsakey->keylen-3-RSA_MIN_PADLEN) { + free(digest); + return -3; + } + if (inputlen < 32) { + /* Minimum size is 32 */ + inputlen = 32; + } + + /* Construct the input buffer with padding */ + /* See RFC 3447 9.2 for more information */ + idx = 0; + memset(buffer, 0, sizeof(buffer)); + buffer[idx++] = 0x00; + buffer[idx++] = 0x01; + memset(buffer+idx, 0xff, rsakey->keylen-inputlen-3); + idx += rsakey->keylen-inputlen-3; + buffer[idx++] = 0x00; + memcpy(buffer+idx, digest, digestlen); + idx += digestlen; + memcpy(buffer+idx, ipaddr, ipaddrlen); + idx += ipaddrlen; + memcpy(buffer+idx, hwaddr, hwaddrlen); + idx += hwaddrlen; + + /* Calculate the signature s = m^d (mod n) */ + bi_in = bi_import(rsakey->bi_ctx, buffer, rsakey->keylen); + bi_out = rsakey_modpow(rsakey, bi_in); + + /* Encode and save the signature into dst */ + bi_export(rsakey->bi_ctx, bi_out, buffer, rsakey->keylen); + base64_encode(rsakey->base64, dst, buffer, rsakey->keylen); + + free(digest); + return 0; +} + +/* Mask generation function with SHA-1 hash */ +/* See RFC 3447 B.2.1 for more information */ +static int +rsakey_mfg1(unsigned char *dst, int dstlen, const unsigned char *seed, int seedlen, int masklen) +{ + SHA1_CTX sha_ctx; + int iterations; + int dstpos; + int i; + + iterations = (masklen+SHA1_SIZE-1)/SHA1_SIZE; + if (dstlen < iterations*SHA1_SIZE) { + return -1; + } + + dstpos = 0; + for (i=0; i>24)&0xff; + counter[1] = (i>>16)&0xff; + counter[2] = (i>>8)&0xff; + counter[3] = i&0xff; + + SHA1_Init(&sha_ctx); + SHA1_Update(&sha_ctx, seed, seedlen); + SHA1_Update(&sha_ctx, counter, sizeof(counter)); + SHA1_Final(dst+dstpos, &sha_ctx); + dstpos += SHA1_SIZE; + } + return masklen; +} + +/* OAEP decryption with SHA-1 hash */ +/* See RFC 3447 7.1.2 for more information */ +int +rsakey_decrypt(rsakey_t *rsakey, unsigned char *dst, int dstlen, const char *b64input) +{ + unsigned char buffer[MAX_KEYLEN]; + unsigned char maskbuf[MAX_KEYLEN]; + unsigned char *input; + int inputlen; + bigint *bi_in; + bigint *bi_out; + int outlen; + int i, ret; + + assert(rsakey); + if (!dst || !b64input) { + return -1; + } + + memset(buffer, 0, sizeof(buffer)); + inputlen = base64_decode(rsakey->base64, &input, b64input, strlen(b64input)); + if (inputlen < 0 || inputlen > rsakey->keylen) { + return -2; + } + memcpy(buffer+rsakey->keylen-inputlen, input, inputlen); + free(input); + input = NULL; + + /* Decrypt the input data m = c^d (mod n) */ + bi_in = bi_import(rsakey->bi_ctx, buffer, rsakey->keylen); + bi_out = rsakey_modpow(rsakey, bi_in); + + memset(buffer, 0, sizeof(buffer)); + bi_export(rsakey->bi_ctx, bi_out, buffer, rsakey->keylen); + + /* First unmask seed in the buffer */ + ret = rsakey_mfg1(maskbuf, sizeof(maskbuf), + buffer+1+SHA1_SIZE, + rsakey->keylen-1-SHA1_SIZE, + SHA1_SIZE); + if (ret < 0) { + return -3; + } + for (i=0; ikeylen-1-SHA1_SIZE); + if (ret < 0) { + return -4; + } + for (i=0; ikeylen && !buffer[i++];); + + /* Calculate real output length and return */ + outlen = rsakey->keylen-i; + if (outlen > dstlen) { + return -5; + } + memcpy(dst, buffer+i, outlen); + return outlen; +} + +int +rsakey_parseiv(rsakey_t *rsakey, unsigned char *dst, int dstlen, const char *b64input) +{ + unsigned char *tmpptr; + int length; + + assert(rsakey); + if (!dst || !b64input) { + return -1; + } + + length = base64_decode(rsakey->base64, &tmpptr, b64input, strlen(b64input)); + if (length < 0) { + return -1; + } else if (length > dstlen) { + free(tmpptr); + return -2; + } + + memcpy(dst, tmpptr, length); + free(tmpptr); + return length; +} diff --git a/src/rsakey.h b/src/rsakey.h new file mode 100644 index 0000000..1bb69ef --- /dev/null +++ b/src/rsakey.h @@ -0,0 +1,25 @@ +#ifndef RSAKEY_H +#define RSAKEY_H + +typedef struct rsakey_s rsakey_t; + +rsakey_t *rsakey_init(const unsigned char *modulus, int mod_len, + const unsigned char *pub_exp, int pub_len, + const unsigned char *priv_exp, int priv_len, + const unsigned char *p, int p_len, + const unsigned char *q, int q_len, + const unsigned char *dP, int dP_len, + const unsigned char *dQ, int dQ_len, + const unsigned char *qInv, int qInv_len); +rsakey_t *rsakey_init_pem(const char *pemstr); + +int rsakey_sign(rsakey_t *rsakey, char *dst, int dstlen, const char *b64digest, + unsigned char *ipaddr, int ipaddrlen, + unsigned char *hwaddr, int hwaddrlen); + +int rsakey_decrypt(rsakey_t *rsakey, unsigned char *dst, int dstlen, const char *b64input); +int rsakey_parseiv(rsakey_t *rsakey, unsigned char *dst, int dstlen, const char *b64input); + +void rsakey_destroy(rsakey_t *rsakey); + +#endif diff --git a/src/rsapem.c b/src/rsapem.c new file mode 100644 index 0000000..1090ffe --- /dev/null +++ b/src/rsapem.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include + +#include "rsapem.h" +#include "base64.h" + +#define RSAPRIVHEADER "-----BEGIN RSA PRIVATE KEY-----" +#define RSAPRIVFOOTER "-----END RSA PRIVATE KEY-----" + +struct rsapem_s { + unsigned char *data; + unsigned int datalen; + unsigned int datapos; +}; + +rsapem_t * +rsapem_init(const char *pemstr) +{ + rsapem_t *rsapem=NULL; + const char *header; + const char *footer; + base64_t *b64dec; + unsigned char *data; + int datalen; + + header = strstr(pemstr, RSAPRIVHEADER); + footer = strstr(pemstr, RSAPRIVFOOTER); + if (!header || !footer) { + return NULL; + } + + + /* Base64 decode the whole input excluding header and footer */ + b64dec = base64_init(NULL, 0, 1); + datalen = base64_decode(b64dec, &data, pemstr+sizeof(RSAPRIVHEADER), + (footer-header)-sizeof(RSAPRIVHEADER)); + base64_destroy(b64dec); + b64dec = NULL; + + if (datalen < 0) { + return NULL; + } + +#ifdef RSAPEM_DEBUG + { + int i; + printf("Decoded output:\n"); + for (i=0; idata = data; + rsapem->datalen = datalen; + rsapem->datapos = 4; + + data = NULL; + datalen = rsapem_read_vector(rsapem, &data); + if (datalen != 1 && data[0] != 0x00) { + free(data); + rsapem_destroy(rsapem); + return NULL; + } + free(data); + return rsapem; +} + +void +rsapem_destroy(rsapem_t *rsapem) +{ + if (rsapem) { + free(rsapem->data); + free(rsapem); + } +} + +int +rsapem_read_vector(rsapem_t *rsapem, unsigned char **data) +{ + unsigned int length; + unsigned char *ptr; + + if (rsapem->datalen-rsapem->datapos < 2) { + return -1; + } + if (rsapem->data[rsapem->datapos] != 0x02) { + return -2; + } + + /* Read vector length */ + length = rsapem->data[rsapem->datapos+1]; + if (length <= 0x80) { + rsapem->datapos += 2; + } else if (length == 0x81) { + if (rsapem->datalen-rsapem->datapos < 3) { + return -3; + } + length = rsapem->data[rsapem->datapos+2]; + rsapem->datapos += 3; + } else if (length == 0x82) { + if (rsapem->datalen-rsapem->datapos < 4) { + return -3; + } + length = (rsapem->data[rsapem->datapos+2] << 8) | + rsapem->data[rsapem->datapos+3]; + rsapem->datapos += 4; + } else { + return -3; + } + + /* Check that we have enough data available */ + if (rsapem->datalen-rsapem->datapos < length) { + return -4; + } + + /* Allocate data buffer and read bytes */ + ptr = malloc(length); + if (!ptr) { + return -5; + } + memcpy(ptr, rsapem->data+rsapem->datapos, length); + rsapem->datapos += length; + + /* Return buffer and length */ + *data = ptr; + return length; +} + diff --git a/src/rsapem.h b/src/rsapem.h new file mode 100644 index 0000000..95ae5a8 --- /dev/null +++ b/src/rsapem.h @@ -0,0 +1,10 @@ +#ifndef RSAPEM_H +#define RSAPEM_H + +typedef struct rsapem_s rsapem_t; + +rsapem_t *rsapem_init(const char *pemstr); +int rsapem_read_vector(rsapem_t *rsapem, unsigned char **data); +void rsapem_destroy(rsapem_t *rsapem); + +#endif diff --git a/src/sdp.c b/src/sdp.c new file mode 100644 index 0000000..369de1c --- /dev/null +++ b/src/sdp.c @@ -0,0 +1,228 @@ +#include +#include +#include + +#include "sdp.h" + +struct sdp_s { + char *data; + + /* Actual SDP records */ + const char *version; + const char *origin; + const char *connection; + const char *session; + const char *time; + const char *media; + + /* Additional SDP records */ + const char *rtpmap; + const char *fmtp; + const char *rsaaeskey; + const char *aesiv; + const char *min_latency; +}; + +static void +parse_sdp_line(sdp_t *sdp, char *line) +{ + int len = strlen(line); + if (len < 2 || line[1] != '=') { + return; + } + + switch (line[0]) { + case 'v': + sdp->version = &line[2]; + break; + case 'o': + sdp->origin = &line[2]; + break; + case 's': + sdp->session = &line[2]; + break; + case 'c': + sdp->connection = &line[2]; + break; + case 't': + sdp->time = &line[2]; + break; + case 'm': + sdp->media = &line[2]; + break; + case 'a': + { + char *key; + char *value; + + /* Parse key and value */ + key = &line[2]; + value = strstr(line, ":"); + if (!value) break; + *(value++) = '\0'; + + if (!strcmp(key, "rtpmap")) { + sdp->rtpmap = value; + } else if (!strcmp(key, "fmtp")) { + sdp->fmtp = value; + } else if (!strcmp(key, "rsaaeskey")) { + sdp->rsaaeskey = value; + } else if (!strcmp(key, "aesiv")) { + sdp->aesiv = value; + } else if (!strcmp(key, "min-latency")) { + sdp->min_latency = value; + } + break; + } + } +} + +static void +parse_sdp_data(sdp_t *sdp) +{ + int pos, len; + + pos = 0; + len = strlen(sdp->data); + while (pos < len) { + int lfpos; + + /* Find newline in string */ + for (lfpos=pos; sdp->data[lfpos]; lfpos++) { + if (sdp->data[lfpos] == '\n') { + break; + } + } + if (sdp->data[lfpos] != '\n') { + break; + } + + /* Replace newline with '\0' and parse line */ + sdp->data[lfpos] = '\0'; + if (lfpos > pos && sdp->data[lfpos-1] == '\r') { + sdp->data[lfpos-1] = '\0'; + } + parse_sdp_line(sdp, sdp->data+pos); + pos = lfpos+1; + } +} + +sdp_t * +sdp_init(const char *sdpdata, int sdpdatalen) +{ + sdp_t *sdp; + + sdp = calloc(1, sizeof(sdp_t)); + if (!sdp) { + return NULL; + } + + /* Allocate data buffer */ + sdp->data = malloc(sdpdatalen+1); + if (!sdp->data) { + free(sdp); + return NULL; + } + memcpy(sdp->data, sdpdata, sdpdatalen); + sdp->data[sdpdatalen] = '\0'; + parse_sdp_data(sdp); + return sdp; +} + +void +sdp_destroy(sdp_t *sdp) +{ + if (sdp) { + free(sdp->data); + free(sdp); + } +} + +const char * +sdp_get_version(sdp_t *sdp) +{ + assert(sdp); + + return sdp->version; +} + +const char * +sdp_get_origin(sdp_t *sdp) +{ + assert(sdp); + + return sdp->origin; +} + +const char * +sdp_get_session(sdp_t *sdp) +{ + assert(sdp); + + return sdp->session; +} + +const char * +sdp_get_connection(sdp_t *sdp) +{ + assert(sdp); + + return sdp->connection; +} + +const char * +sdp_get_time(sdp_t *sdp) +{ + assert(sdp); + + return sdp->time; +} + +const char * +sdp_get_media(sdp_t *sdp) +{ + assert(sdp); + + return sdp->media; +} + +const char * +sdp_get_rtpmap(sdp_t *sdp) +{ + assert(sdp); + + return sdp->rtpmap; +} + +const char * +sdp_get_fmtp(sdp_t *sdp) +{ + assert(sdp); + + return sdp->fmtp; +} + +const char * +sdp_get_rsaaeskey(sdp_t *sdp) +{ + assert(sdp); + + return sdp->rsaaeskey; +} + +const char * +sdp_get_aesiv(sdp_t *sdp) +{ + assert(sdp); + + return sdp->aesiv; +} + +const char * +sdp_get_min_latency(sdp_t *sdp) +{ + assert(sdp); + + return sdp->min_latency; +} + diff --git a/src/sdp.h b/src/sdp.h new file mode 100644 index 0000000..474fae0 --- /dev/null +++ b/src/sdp.h @@ -0,0 +1,22 @@ +#ifndef SDP_H +#define SDP_H + +typedef struct sdp_s sdp_t; + +sdp_t *sdp_init(const char *sdpdata, int sdpdatalen); + +const char *sdp_get_version(sdp_t *sdp); +const char *sdp_get_origin(sdp_t *sdp); +const char *sdp_get_session(sdp_t *sdp); +const char *sdp_get_connection(sdp_t *sdp); +const char *sdp_get_time(sdp_t *sdp); +const char *sdp_get_media(sdp_t *sdp); +const char *sdp_get_rtpmap(sdp_t *sdp); +const char *sdp_get_fmtp(sdp_t *sdp); +const char *sdp_get_rsaaeskey(sdp_t *sdp); +const char *sdp_get_aesiv(sdp_t *sdp); +const char *sdp_get_min_latency(sdp_t *sdp); + +void sdp_destroy(sdp_t *sdp); + +#endif diff --git a/src/sockets.h b/src/sockets.h new file mode 100644 index 0000000..58ef250 --- /dev/null +++ b/src/sockets.h @@ -0,0 +1,35 @@ +#ifndef SOCKETS_H +#define SOCKETS_H + +#if defined(WIN32) +typedef int socklen_t; + +#ifndef SHUT_RD +# define SHUT_RD SD_RECEIVE +#endif +#ifndef SHUT_WR +# define SHUT_WR SD_SEND +#endif +#ifndef SHUT_RDWR +# define SHUT_RDWR SD_BOTH +#endif + +#define SOCKET_GET_ERROR() WSAGetLastError() +#define SOCKET_SET_ERROR(value) WSASetLastError(value) +#define SOCKET_ERRORNAME(name) WSA##name + +#define WSAEAGAIN WSAEWOULDBLOCK +#define WSAENOMEM WSA_NOT_ENOUGH_MEMORY + +#else + +#define closesocket close +#define ioctlsocket ioctl + +#define SOCKET_GET_ERROR() (errno) +#define SOCKET_SET_ERROR(value) (errno = (value)) +#define SOCKET_ERRORNAME(name) name + +#endif + +#endif diff --git a/src/threads.h b/src/threads.h new file mode 100644 index 0000000..7f79e6d --- /dev/null +++ b/src/threads.h @@ -0,0 +1,46 @@ +#ifndef THREADS_H +#define THREADS_H + +#if defined(WIN32) +#include + +#define sleepms(x) Sleep(x) + +typedef HANDLE thread_handle_t; + +#define THREAD_RETVAL DWORD WINAPI +#define THREAD_CREATE(handle, func, arg) \ + handle = CreateThread(NULL, 0, func, arg, 0, NULL) +#define THREAD_JOIN(handle) do { WaitForSingleObject(handle, INFINITE); CloseHandle(handle); } while(0) + +typedef HANDLE mutex_handle_t; + +#define MUTEX_CREATE(handle) handle = CreateMutex(NULL, FALSE, NULL) +#define MUTEX_LOCK(handle) WaitForSingleObject(handle, INFINITE) +#define MUTEX_UNLOCK(handle) ReleaseMutex(handle) +#define MUTEX_DESTROY(handle) CloseHandle(handle) + +#else /* Use pthread library */ + +#include +#include + +#define sleepms(x) usleep((x)*1000) + +typedef pthread_t thread_handle_t; + +#define THREAD_RETVAL void * +#define THREAD_CREATE(handle, func, arg) \ + if (pthread_create(&(handle), NULL, func, arg)) handle = 0 +#define THREAD_JOIN(handle) pthread_join(handle, NULL) + +typedef pthread_mutex_t mutex_handle_t; + +#define MUTEX_CREATE(handle) pthread_mutex_init(&(handle), NULL) +#define MUTEX_LOCK(handle) pthread_mutex_lock(&(handle)) +#define MUTEX_UNLOCK(handle) pthread_mutex_unlock(&(handle)) +#define MUTEX_DESTROY(handle) pthread_mutex_destroy(&(handle)) + +#endif + +#endif /* THREADS_H */ diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..8057eec --- /dev/null +++ b/src/utils.c @@ -0,0 +1,130 @@ +#include +#include +#include + +char * +utils_strsep(char **stringp, const char *delim) +{ + char *original; + char *strptr; + + if (*stringp == NULL) { + return NULL; + } + + original = *stringp; + strptr = strstr(*stringp, delim); + if (strptr == NULL) { + *stringp = NULL; + return original; + } + *strptr = '\0'; + *stringp = strptr+strlen(delim); + return original; +} + +int +utils_read_file(char **dst, const char *filename) +{ + FILE *stream; + int filesize; + char *buffer; + int read_bytes; + + /* Open stream for reading */ + stream = fopen(filename, "rb"); + if (!stream) { + return -1; + } + + /* Find out file size */ + fseek(stream, 0, SEEK_END); + filesize = ftell(stream); + fseek(stream, 0, SEEK_SET); + + /* Allocate one extra byte for zero */ + buffer = malloc(filesize+1); + if (!buffer) { + fclose(stream); + return -2; + } + + /* Read data in a loop to buffer */ + read_bytes = 0; + do { + int ret = fread(buffer+read_bytes, 1, + filesize-read_bytes, stream); + if (ret == 0) { + break; + } + read_bytes += ret; + } while (read_bytes < filesize); + + /* Add final null byte and close stream */ + buffer[read_bytes] = '\0'; + fclose(stream); + + /* If read didn't finish, return error */ + if (read_bytes != filesize) { + free(buffer); + return -3; + } + + /* Return buffer */ + *dst = buffer; + return filesize; +} + +int +utils_hwaddr_raop(char *str, int strlen, const char *hwaddr, int hwaddrlen) +{ + int i,j; + + /* Check that our string is long enough */ + if (strlen == 0 || strlen < 2*hwaddrlen+1) + return -1; + + /* Convert hardware address to hex string */ + for (i=0,j=0; i>4) & 0x0f; + int lo = hwaddr[i] & 0x0f; + + if (hi < 10) str[j++] = '0' + hi; + else str[j++] = 'A' + hi-10; + if (lo < 10) str[j++] = '0' + lo; + else str[j++] = 'A' + lo-10; + } + + /* Add string terminator */ + str[j++] = '\0'; + return j; +} + +int +utils_hwaddr_airplay(char *str, int strlen, const char *hwaddr, int hwaddrlen) +{ + int i,j; + + /* Check that our string is long enough */ + if (strlen == 0 || strlen < 2*hwaddrlen+hwaddrlen) + return -1; + + /* Convert hardware address to hex string */ + for (i=0,j=0; i>4) & 0x0f; + int lo = hwaddr[i] & 0x0f; + + if (hi < 10) str[j++] = '0' + hi; + else str[j++] = 'a' + hi-10; + if (lo < 10) str[j++] = '0' + lo; + else str[j++] = 'a' + lo-10; + + str[j++] = ':'; + } + + /* Add string terminator */ + if (j != 0) j--; + str[j++] = '\0'; + return j; +} + diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..f1b0b5a --- /dev/null +++ b/src/utils.h @@ -0,0 +1,9 @@ +#ifndef UTILS_H +#define UTILS_H + +char *utils_strsep(char **stringp, const char *delim); +int utils_read_file(char **dst, const char *pemstr); +int utils_hwaddr_raop(char *str, int strlen, const char *hwaddr, int hwaddrlen); +int utils_hwaddr_airplay(char *str, int strlen, const char *hwaddr, int hwaddrlen); + +#endif diff --git a/test/dnssd_test.c b/test/dnssd_test.c new file mode 100644 index 0000000..7e329c4 --- /dev/null +++ b/test/dnssd_test.c @@ -0,0 +1,48 @@ +#include +#include +#include + +#include "dnssd.h" +#include "compat.h" + +int +main(int argc, char *argv[]) +{ + const char hwaddr[] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB }; + dnssd_t *dnssd; + + +#ifdef WIN32 + WORD wVersionRequested; + WSADATA wsaData; + + wVersionRequested = MAKEWORD(2, 2); + if (WSAStartup(wVersionRequested, &wsaData) != 0) { + return -1; + } + if (LOBYTE(wsaData.wVersion) != 2 || + HIBYTE(wsaData.wVersion) != 2) { + return -1; + } +#endif + + dnssd = dnssd_init(hwaddr, sizeof(hwaddr), NULL); + if (!dnssd) { + printf("Failed to init dnssd\n"); + return -1; + } + dnssd_register_raop(dnssd, "Test", 5000); + dnssd_register_airplay(dnssd, "Test", 6000); + + sleepms(60000); + + dnssd_unregister_raop(dnssd); + dnssd_unregister_airplay(dnssd); + dnssd_destroy(dnssd); + +#ifdef WIN32 + WSACleanup(); +#endif + + return 0; +} diff --git a/test/dnssd_test.m b/test/dnssd_test.m new file mode 100644 index 0000000..d4ccbe2 --- /dev/null +++ b/test/dnssd_test.m @@ -0,0 +1,32 @@ +#include +#include +#include +#include + +#include "dnssd.h" +#include "compat.h" + +int +main(int argc, char *argv[]) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + const char hwaddr[] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB }; + dnssd_t *dnssd; + + dnssd = dnssd_init(hwaddr, sizeof(hwaddr), NULL); + if (!dnssd) { + printf("Failed to init dnssd\n"); + return -1; + } + dnssd_register_raop(dnssd, "Test", 5000); + dnssd_register_airplay(dnssd, "Test", 6000); + + sleepms(60000); + + dnssd_unregister_raop(dnssd); + dnssd_unregister_airplay(dnssd); + dnssd_destroy(dnssd); + [pool release]; + + return 0; +} diff --git a/test/main.c b/test/main.c new file mode 100644 index 0000000..e902dea --- /dev/null +++ b/test/main.c @@ -0,0 +1,213 @@ +#include +#include +#include +#include +#include + +#include + +#include "dnssd.h" +#include "airplay.h" +#include "raop.h" +#include "rsakey.h" +#include "utils.h" + +#define CHALLENGE "LfBLs2pkGT4yYAJrxj2K9Q==" +static unsigned char ipaddr[] = { 192, 168, 1, 10 }; +static unsigned char hwaddr[] = { 0x00, 0x5A, 0xDB, 0xE4, 0xE6, 0xFD }; + +#define AESENC "B69yRGHoriZZeJNKghotJi6Pt7dEPEwKSoieM6dk8YE1D23/jyOsg0ZoJZmodHB4lR9Q9CmeoqgU0GX1KkYNafCrNUlXxJAZuMTISGmyeDegnH73ul1NQwIjfphGgwrx7dp7J+p4lyQi+Yt/daQyjE//Od0viD37GQtI9B7GukaiWrMl94wJmSLUL94VpKUxnD9E7T/LesX7bEMfUgSQUpIE+T2anp6eRqE/5R3kNYdEH9JcCEFu5DLqbbvMqgc0ewr81BNeVG5ck1iI2eF+OJVm9g082ZXqGAPFGwmcYiiLfjrQY5hnEUi7IeWqgX5Xd82DyW9BeDzT5MXVyI/GwQ==" + +static void +test_rsa(const char *pemstr) +{ + char buffer[2048]; + int ret; + rsakey_t *rsakey; + + rsakey = rsakey_init_pem(pemstr); + if (!rsakey) { + printf("Initializing RSA failed\n"); + return; + } + + rsakey_sign(rsakey, buffer, sizeof(buffer), CHALLENGE, + ipaddr, sizeof(ipaddr), hwaddr, sizeof(hwaddr)); + printf("Signature:\n%s\n", buffer); + + ret = rsakey_decrypt(rsakey, (unsigned char *)buffer, sizeof(buffer), AESENC); + printf("Decrypted length: %d\n", ret); + + rsakey_destroy(rsakey); +} + +static void +photo_cb(char *data, int datalen) +{ + char template[512]; + int written = 0; + int fd, ret; + + printf("Got photo with data length: %d\n", datalen); + + memset(template, 0, sizeof(template)); + strcpy(template, "/tmp/tmpXXXXXX.JPG"); + fd = mkstemps(template, 4); + + while (written < datalen) { + ret = write(fd, data+written, datalen-written); + if (ret <= 0) break; + written += ret; + } + if (written == datalen) { + printf("Wrote to file %s\n", template); + } + close(fd); +} + +static void +play_cb() +{ +} + +static void +stop_cb() +{ +} + +static void +rate_set_cb() +{ +} + +static void +scrub_get_cb() +{ +} + +static void +scrub_set_cb() +{ +} + +static void +playback_info_cb() +{ +} + +static void * +audio_init(void *opaque, int bits, int channels, int samplerate) +{ + int driver; + ao_sample_format format; + ao_option *ao_opts = NULL; + ao_device *device; + + printf("Opening audio device\n"); + driver = ao_driver_id("pulse"); + + memset(&format, 0, sizeof(format)); + format.bits = bits; + format.channels = channels; + format.rate = samplerate; + format.byte_format = AO_FMT_LITTLE; + + ao_append_option(&ao_opts, "id", "0"); + device = ao_open_live(driver, &format, ao_opts); + if (!device) { + fprintf(stderr, "Error opening audio device.\n"); + } else { + printf("Opening device successful\n"); + } + return device; +} + +static void +audio_process(void *ptr, const void *buffer, int buflen) +{ + ao_device *device = ptr; + + assert(device); + + printf("Got %d bytes of audio\n", buflen); + ao_play(device, (char *)buffer, buflen); +} + +static void +audio_flush(void *ptr) +{ +} + +static void +audio_destroy(void *ptr) +{ + ao_device *device = ptr; + + printf("Closing audio device\n"); + ao_close(device); +} + +int +main(int argc, char *argv[]) +{ + const char *name = "AppleTV"; + unsigned short raop_port = 5000; + unsigned short airplay_port = 7000; + const char hwaddr[] = { 0x48, 0x5d, 0x60, 0x7c, 0xee, 0x22 }; + char *pemstr; + + dnssd_t *dnssd; + airplay_t *airplay; + airplay_callbacks_t ap_cbs; + raop_t *raop; + raop_callbacks_t raop_cbs; + + if (utils_read_file(&pemstr, "airport.key") < 0) { + return -1; + } + if (argc > 1) { + test_rsa(pemstr); + } + + ao_initialize(); + + dnssd = dnssd_init(hwaddr, sizeof(hwaddr), NULL); + dnssd_register_raop(dnssd, name, raop_port); + dnssd_register_airplay(dnssd, name, airplay_port); + + ap_cbs.photo_cb = &photo_cb; + ap_cbs.play_cb = &play_cb; + ap_cbs.stop_cb = &stop_cb; + ap_cbs.rate_set_cb = &rate_set_cb; + ap_cbs.scrub_get_cb = &scrub_get_cb; + ap_cbs.scrub_set_cb = &scrub_set_cb; + ap_cbs.playback_info_cb = &playback_info_cb; + + raop_cbs.audio_init = audio_init; + raop_cbs.audio_process = audio_process; + raop_cbs.audio_flush = audio_flush; + raop_cbs.audio_destroy = audio_destroy; + + airplay = airplay_init(&ap_cbs, hwaddr, sizeof(hwaddr)); + airplay_start(airplay, airplay_port); + + raop = raop_init(&raop_cbs, pemstr, hwaddr, sizeof(hwaddr)); + raop_start(raop, &raop_port); + + sleep(100); + + raop_stop(raop); + raop_destroy(raop); + + airplay_stop(airplay); + airplay_destroy(airplay); + + dnssd_unregister_airplay(dnssd); + dnssd_unregister_raop(dnssd); + dnssd_destroy(dnssd); + + ao_shutdown(); + + return 0; +} + diff --git a/test/shairport.c b/test/shairport.c new file mode 100644 index 0000000..15f91f6 --- /dev/null +++ b/test/shairport.c @@ -0,0 +1,75 @@ +#include +#include +#include + +#include "dnssd.h" +#include "raop.h" + +static void +audio_init(void *cls, void **session, int bits, int channels, int samplerate) +{ + *session = fopen("audio.pcm", "wb"); +} + +static void +audio_set_volume(void *cls, void *session, float volume) +{ + printf("Setting volume to %f\n", volume); +} + +static void +audio_process(void *cls, void *session, const void *buffer, int buflen) +{ + int orig = buflen; + while (buflen > 0) { + buflen -= fwrite(buffer+orig-buflen, 1, buflen, session); + } +} + +static void +audio_flush(void *cls, void *session) +{ + printf("Flushing audio\n"); +} + +static void +audio_destroy(void *cls, void *session) +{ + fclose(session); +} + +int +main(int argc, char *argv[]) +{ + const char *name = "AppleTV"; + unsigned short raop_port = 5000; + const char hwaddr[] = { 0x48, 0x5d, 0x60, 0x7c, 0xee, 0x22 }; + + dnssd_t *dnssd; + raop_t *raop; + raop_callbacks_t raop_cbs; + + raop_cbs.cls = NULL; + raop_cbs.audio_init = audio_init; + raop_cbs.audio_set_volume = audio_set_volume; + raop_cbs.audio_process = audio_process; + raop_cbs.audio_flush = audio_flush; + raop_cbs.audio_destroy = audio_destroy; + + raop = raop_init_from_keyfile(&raop_cbs, "airport.key", hwaddr, sizeof(hwaddr)); + raop_start(raop, &raop_port); + + dnssd = dnssd_init(hwaddr, sizeof(hwaddr), NULL); + dnssd_register_raop(dnssd, name, raop_port); + + sleep(100); + + dnssd_unregister_raop(dnssd); + dnssd_destroy(dnssd); + + raop_stop(raop); + raop_destroy(raop); + + return 0; +} + -- 2.34.1