Commit e0f537c521d3dace44e222e1d29546ed9b58f988

start of work on client-server fu
  
1/*
2* libMendeley: Assorted classes used by Mendeley Desktop
3* Copyright (C) 2009 Mendeley Limited <copyright@mendeley.com>
4
5* Mendeley contact: Mendeley Copyright (copyrightmendeley.com)
6*
7* This library is free software; you can redistribute it and/or
8* modify it under the terms of the GNU Lesser General Public
9* License as published by the Free Software Foundation; version 2.1.
10*
11* This library is distributed in the hope that it will be useful,
12* but WITHOUT ANY WARRANTY; without even the implied warranty of
13* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14* Lesser General Public License for more details.
15*
16* You should have received a copy of the GNU Lesser General Public
17* License along with this library; if not, write to the Free Software
18* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19*/
20#ifndef _MENDELEY_UUID_H
21#define _MENDELEY_UUID_H
22
23#include <QAtomicInt>
24
25namespace Mendeley
26{
27 /** Alternative to QUuid which makes UUIDs that are less likely to have collisions.
28 *
29 * QUuid is purely based on date and time.
30 *
31 * This class takes into account:
32 * - The current date and time
33 * - The application PID
34 * - The current thread
35 * - The MAC addresses of all interfaces on the local host
36 *
37 * Not as fast, but safer if you have multiple clients which need to have non-colliding UUIDs.
38 *
39 * @author Fred Emmott <fred.emmott@mendeley.com>
40 */
41 class Uuid
42 {
43 public:
44 /** Alternative to QUuid::createUuid() which works when used across
45 * multiple threads simultaneously.
46 */
47 static QString createUuid();
48 private:
49 /// Random seed for the first call to this function per thread.
50 static void randomSeed(char* seed, int bytes);
51 static void nextValue(char* value, int bytes);
52 };
53}
54
55#endif
  
99 TagReader
1010 ToolBar
1111 TrackData
12 Uuid
1213 Utilities
1314 WizardPage
1415)
  
1/*
2* libMendeley: Assorted classes used by Mendeley Desktop
3* Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
4* Copyright (C) 2009 Mendeley Limited
5*
6* This version is maintained by Mendeley Limited, and most queries should
7* be directed to them.
8*
9* Nokia contact: Qt Software Information (qt-info@nokia.com)
10* Mendeley contact: Mendeley Copyright (copyright@mendeley.com)
11*
12* This library is free software; you can redistribute it and/or
13* modify it under the terms of the GNU Lesser General Public
14* License as published by the Free Software Foundation, version 2.1.
15*
16* This library is distributed in the hope that it will be useful,
17* but WITHOUT ANY WARRANTY; without even the implied warranty of
18* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19* Lesser General Public License for more details.
20*
21* You should have received a copy of the GNU Lesser General Public
22* License along with this library; if not, write to the Free Software
23* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1161 USA
24*/
25#include "Uuid.h"
26#include "memcpy_safe.h"
27
28// This needs to be included before QNetworkInterface to work around a bug
29// in the Qt 4.5 headers.
30#include <QPair>
31
32#include <QByteArray>
33#include <QCoreApplication>
34#include <QCryptographicHash>
35#include <QDataStream>
36#include <QDateTime>
37#include <QNetworkInterface>
38#include <QString>
39#include <QThread>
40#include <QThreadStorage>
41#include <QUuid>
42
43#include <cstdlib>
44
45namespace Mendeley
46{
47 void Uuid::nextValue(char* value, int bytes)
48 {
49 // progress by md5
50 const QByteArray hash(QCryptographicHash::hash(QByteArray(value, bytes), QCryptographicHash::Md5));
51 ::memcpy_safe(value, bytes, hash.constData(), qMin(bytes, hash.count()));
52 }
53
54 // wrap seed inside a struct because QThreadStorage<> uses delete rather than
55 // delete[] to free the stored value
56 struct UuidSeed
57 {
58 char seed[16];
59 };
60
61 QString Uuid::createUuid()
62 {
63 // Begin section lifted from QUuid::createUuid(), modified to set a
64 // different random seed for each thread - otherwise qrand() always returns the same
65 // sequence of random numbers for each thread and the resulting uuids will be the same
66 // when called from different threads
67 //
68 // See Qt Software Task Tracker Bug #171206
69 // (http://www.qtsoftware.com/developer/task-tracker/index_html?method=entry&id=171206)
70 static QThreadStorage<UuidSeed*> threadStorage;
71 if(!threadStorage.hasLocalData())
72 {
73 UuidSeed* seedValue = new UuidSeed;
74 randomSeed(seedValue->seed, 16);
75 threadStorage.setLocalData(seedValue);
76 }
77
78 char* value = threadStorage.localData()->seed;
79 nextValue(value, 16);
80
81 struct
82 {
83 uint data1;
84 ushort data2;
85 ushort data3;
86 uchar data4[8];
87 } parts;
88
89 uint *data = &(parts.data1);
90 ::memcpy_safe(data, sizeof(parts), value, 16);
91
92 parts.data4[0] = (parts.data4[0] & 0x3F) | 0x80; // UV_DCE
93 parts.data3 = (parts.data3 & 0x0FFF) | 0x4000; // UV_Random
94 // End Section lifted from QUuid::createUuid()
95
96 QUuid uuid(parts.data1,parts.data2,parts.data3,
97 parts.data4[0],parts.data4[1],parts.data4[2],
98 parts.data4[3],parts.data4[4],parts.data4[5],
99 parts.data4[6],parts.data4[7]);
100
101 return uuid.toString();
102 }
103
104 void Uuid::randomSeed(char* seed, int bytes)
105 {
106 Q_ASSERT(bytes == 16);
107 // Seed based on:
108 // - 'mendeley' (salt)
109 // - date and time
110 // - PID
111 // - thread
112 // - all MAC addresses
113 QByteArray data;
114 {
115 QDataStream stream(&data, QIODevice::WriteOnly);
116 stream << "mendeley";
117 stream << QDateTime::currentDateTime();
118 stream << QCoreApplication::applicationPid();
119 stream << reinterpret_cast<qint64>(QThread::currentThread());
120 Q_FOREACH(const QNetworkInterface& interface, QNetworkInterface::allInterfaces())
121 {
122 stream << interface.hardwareAddress() << interface.name();
123 }
124 }
125 const QByteArray hash(QCryptographicHash::hash(data, QCryptographicHash::Md5));
126 ::memcpy_safe(seed, bytes, hash.constData(), qMin(bytes, hash.count()));
127 }
128}
  
1/* LICENSE NOTICE
2 Copyright (c) 2009, Frederick Emmott <mail@fredemmott.co.uk>
3
4 Permission to use, copy, modify, and/or distribute this software for any
5 purpose with or without fee is hereby granted, provided that the above
6 copyright notice and this permission notice appear in all copies.
7
8 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15*/
16#pragma once
17
18#include <string.h>
19
20// TODO: use the windows function on windows
21
22inline void* memcpy_safe(void* destination, size_t destinationSize, const void* source, size_t count)
23{
24 Q_ASSERT(destinationSize >= count);
25 if(destinationSize < count)
26 {
27 return 0;
28 }
29 return ::memcpy(destination, source, count);
30}
  
1SET(
2 CLASSES
3 NetworkClient
4)
5
6SET(SOURCES)
7SET(HEADERS)
8FOREACH(class ${CLASSES})
9 LIST(APPEND SOURCES ${class}.cpp)
10 LIST(APPEND HEADERS ${class}.h)
11ENDFOREACH()
12
13INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR})
14QT4_WRAP_CPP(MOC_SOURCES ${HEADERS})
15ADD_LIBRARY(
16 NetworkClient
17 ${SOURCES}
18 ${MOC_SOURCES}
19)
  
1#include "NetworkClient.h"
2
3#include <QApplication>
4#include <QDebug>
5#include <QtPlugin>
6
7NetworkClient::NetworkClient()
8: QObject(0)
9{
10}
11
12QString NetworkClient::pluginName() const
13{
14 return tr("Network Client");
15}
16
17QString NetworkClient::pluginAuthor() const
18{
19 return QString::fromLatin1("Fred Emmott");
20}
21
22QString NetworkClient::uniqueId() const
23{
24 return "org.jerboaplayer.NetworkClient";
25}
26
27Q_EXPORT_PLUGIN2(Jerboa_NetworkClient, NetworkClient);
  
1#pragma once
2
3#include "Plugin.h"
4
5#include <QObject>
6
7class NetworkClient : public QObject, public Jerboa::Plugin
8{
9 Q_OBJECT;
10 Q_INTERFACES(Jerboa::Plugin);
11 public:
12 NetworkClient();
13 QString pluginName() const;
14 QString pluginAuthor() const;
15 QString uniqueId() const;
16 private:
17 class Implementation;
18};
  
1SET(
2 CLASSES
3 NetworkServer
4 NetworkServer_Implementation
5)
6
7SET(SOURCES)
8SET(HEADERS)
9FOREACH(class ${CLASSES})
10 LIST(APPEND SOURCES ${class}.cpp)
11 LIST(APPEND HEADERS ${class}.h)
12ENDFOREACH()
13
14INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR})
15QT4_WRAP_CPP(MOC_SOURCES ${HEADERS})
16ADD_LIBRARY(
17 NetworkServer
18 ${SOURCES}
19 ${MOC_SOURCES}
20)
  
1#include "NetworkServer.h"
2#include "NetworkServer_Implementation.h"
3
4#include "CollectionInterface.h"
5#include "PlayerInterface.h"
6#include "PlaylistInterface.h"
7
8#include <QApplication>
9#include <QDebug>
10#include <QtPlugin>
11
12NetworkServer::NetworkServer()
13: QObject(0)
14, m_collection(0)
15, m_player(0)
16, m_playlist(0)
17{
18}
19
20QString NetworkServer::pluginName() const
21{
22 return tr("Network Server");
23}
24
25QString NetworkServer::pluginAuthor() const
26{
27 return QString::fromLatin1("Fred Emmott");
28}
29
30QString NetworkServer::uniqueId() const
31{
32 return "org.jerboaplayer.NetworkServer";
33}
34
35void NetworkServer::initialize() const
36{
37 if(m_collection && m_player && m_playlist)
38 {
39 new Implementation(m_collection, m_player, m_playlist);
40 }
41}
42
43void NetworkServer::addComponent(ComponentType type, QObject* component)
44{
45 switch(type)
46 {
47 case CollectionSource:
48 Q_ASSERT(qobject_cast<Jerboa::CollectionInterface*>(component));
49 m_collection = static_cast<Jerboa::CollectionInterface*>(component);
50 initialize();
51 break;
52 case Player:
53 Q_ASSERT(qobject_cast<Jerboa::PlayerInterface*>(component));
54 m_player = static_cast<Jerboa::PlayerInterface*>(component);
55 initialize();
56 break;
57 case PlaylistSource:
58 Q_ASSERT(qobject_cast<Jerboa::PlaylistInterface*>(component));
59 m_playlist = static_cast<Jerboa::PlaylistInterface*>(component);
60 initialize();
61 break;
62 default:
63 Plugin::addComponent(type, component);
64 break;
65 }
66}
67
68Q_EXPORT_PLUGIN2(Jerboa_NetworkServer, NetworkServer);
  
1#pragma once
2
3#include "Plugin.h"
4
5#include <QObject>
6
7namespace Jerboa
8{
9 class CollectionInterface;
10 class PlayerInterface;
11 class PlaylistInterface;
12};
13
14class NetworkServer : public QObject, public Jerboa::Plugin
15{
16 Q_OBJECT;
17 Q_INTERFACES(Jerboa::Plugin);
18 public:
19 NetworkServer();
20 QString pluginName() const;
21 QString pluginAuthor() const;
22 QString uniqueId() const;
23 void addComponent(ComponentType type, QObject* component);
24 private:
25 class Implementation;
26
27 void initialize() const;
28
29 Jerboa::CollectionInterface* m_collection;
30 Jerboa::PlayerInterface* m_player;
31 Jerboa::PlaylistInterface* m_playlist;
32};
  
1#include "NetworkServer_Implementation.h"
2
3#include "Uuid.h"
4
5#include <QDebug>
6#include <QHostInfo>
7#include <QSettings>
8#include <QTcpServer>
9#include <QUdpSocket>
10
11const quint16 PORT_NUMBER = 61719; // pseudo-random from private range
12const QByteArray DISCOVERY_MESSAGE = "Jerboa Network Discovery/1.0\n";
13
14NetworkServer::Implementation::Implementation(Jerboa::CollectionInterface* collection, Jerboa::PlayerInterface* player, Jerboa::PlaylistInterface* playlist)
15: QObject(0)
16, m_collection(collection)
17, m_player(player)
18, m_playlist(playlist)
19, m_discoverySocket(new QUdpSocket(this))
20, m_server(new QTcpServer(this))
21{
22 const bool startedDiscovery = m_discoverySocket->bind(QHostAddress::Any, PORT_NUMBER);
23 if(!startedDiscovery)
24 {
25 qDebug() << "Failed to start network discovery:" << m_discoverySocket->errorString();
26 return;
27 }
28
29 if(!m_server->listen())
30 {
31 qDebug() << "Failed to bind network socket:" << m_server->errorString();
32 return;
33 }
34
35 qDebug() << "Network server started.";
36
37 connect(m_discoverySocket, SIGNAL(readyRead()), SLOT(sendDiscoveryResponses()));
38
39
40 QSettings settings;
41
42 // Deal with strings in QSettings so the saved values are more human-readable
43 QString serverUuid = settings.value("network/serverUuid").toString();
44 if(serverUuid.isEmpty())
45 {
46 serverUuid = Mendeley::Uuid::createUuid();
47 settings.setValue("network/serverUuid", serverUuid);
48 }
49
50 m_serverUuid = serverUuid.toLatin1();
51
52 // Load up the response
53 {
54 QByteArray payload;
55 payload = QString::number(m_serverUuid.length()).toLatin1();
56 payload.append(',');
57 payload.append(m_serverUuid);
58 payload.append(',');
59 const QByteArray host = QHostInfo::localHostName().toUtf8();
60 payload.append(QString::number(host.length()).toLatin1());
61 payload.append(',');
62 payload.append(host);
63 payload.append(',');
64 const QByteArray port = QString::number(m_server->serverPort()).toLatin1();
65 payload.append(QString::number(port.length()).toLatin1());
66 payload.append(',');
67 payload.append(port);
68 payload.append(',');
69
70 m_discoveryResponse = createMessage("ANNOUNCE", payload, UnsignedMessage);
71 }
72}
73
74QByteArray NetworkServer::Implementation::createMessage(const QByteArray& command, const QByteArray& payload, MessageType messageType) const
75{
76 // netstrings-influenced
77 // CommandLength,Command,PayloadLength,Payload,{U|S,timestamp-length,timestamp,message-uuid,signature};
78 //
79 // Examples:
80 // 3,foo,0,,U;
81 // 3,bar,3,baz,S,10,1266356591,{fc8d8c6c-2ede-4bb8-8685-2cc47cdd8b2b},0000000000000000000000000000000000000000
82 //
83 // Where:
84 // - timestamp is the number of seconds since 1970-01-01T00:00:00 UTC
85 // - message-uuid is a per-message UUID to prevent replays
86 // - signature is an HMAC-SHA1 of everything up to and including the previous comma
87 QByteArray out;
88 Q_ASSERT(messageType == UnsignedMessage);
89 Q_UNUSED(messageType);
90
91 out.append(QString::number(command.length()).toLatin1());
92 out.append(',');
93 out.append(command);
94 out.append(',');
95 out.append(QString::number(payload.length()).toLatin1());
96 out.append(',');
97 out.append(payload);
98 out.append(",U;");
99 return out;
100}
101
102void NetworkServer::Implementation::sendDiscoveryResponses()
103{
104 while(m_discoverySocket->hasPendingDatagrams())
105 {
106 // don't worry about overflow, UDP packets have reasonable maximum size
107 QByteArray payload;
108 payload.resize(m_discoverySocket->pendingDatagramSize());
109 QHostAddress source;
110 quint16 sourcePort;
111 m_discoverySocket->readDatagram(payload.data(), payload.size(), &source, &sourcePort);
112 if(payload == DISCOVERY_MESSAGE)
113 {
114 qDebug() << "Received a discovery message from" << QString("%1:%2").arg(source.toString()).arg(sourcePort);
115 // send a response
116 m_discoverySocket->writeDatagram(m_discoveryResponse, source, sourcePort);
117 }
118 }
119}
  
1#include "NetworkServer.h"
2
3#include <QByteArray>
4
5class QTcpServer;
6class QUdpSocket;
7
8namespace Jerboa
9{
10 class CollectionInterface;
11 class PlayerInterface;
12 class PlaylistInterface;
13};
14
15class NetworkServer::Implementation : public QObject
16{
17 Q_OBJECT;
18 public:
19 Implementation(Jerboa::CollectionInterface* collection, Jerboa::PlayerInterface* player, Jerboa::PlaylistInterface* playlist);
20 private slots:
21 void sendDiscoveryResponses();
22 private:
23 enum MessageType
24 {
25 UnsignedMessage,
26 SignedMessage
27 };
28 QByteArray createMessage(const QByteArray& command, const QByteArray& payload, MessageType messageType) const;
29
30 Jerboa::CollectionInterface* m_collection;
31 Jerboa::PlayerInterface* m_player;
32 Jerboa::PlaylistInterface* m_playlist;
33
34 QByteArray m_serverUuid;
35 QByteArray m_discoveryResponse;
36
37 QUdpSocket* m_discoverySocket;
38 QTcpServer* m_server;
39};