Checkpoint. Code does not run. Still converting over to new

event loop model.  Streams, stream filters, and sockets are
converted.  Client proxies are almost converted.  CServer is
in progress.  Removed all HTTP code.  Haven't converted the
necessary win32 arch stuff.
This commit is contained in:
crs 2004-02-01 21:09:22 +00:00
parent 618aa7fedd
commit 848aee7a3a
64 changed files with 2581 additions and 2942 deletions

View File

@ -88,7 +88,6 @@ public:
const char* m_logFilter;
CString m_name;
CNetworkAddress m_synergyAddress;
CNetworkAddress m_httpAddress;
CConfig m_config;
};
@ -191,11 +190,6 @@ realMain(void)
ARG->m_config.setSynergyAddress(CNetworkAddress(kDefaultPort));
}
// set HTTP address if provided
if (ARG->m_httpAddress.isValid()) {
ARG->m_config.setHTTPAddress(ARG->m_httpAddress);
}
// create server
s_server = new CServer(ARG->m_name);
s_server->setConfig(ARG->m_config);
@ -454,20 +448,6 @@ parse(int argc, const char* const* argv)
++i;
}
else if (isArg(i, argc, argv, NULL, "--http", 1)) {
// save listen address
try {
ARG->m_httpAddress = CNetworkAddress(argv[i + 1],
kDefaultPort + 1);
}
catch (XSocketAddress& e) {
LOG((CLOG_PRINT "%s: %s" BYE,
ARG->m_pname, e.what(), ARG->m_pname));
bye(kExitArgs);
}
++i;
}
else if (isArg(i, argc, argv, "-n", "--name", 1)) {
// save screen name
ARG->m_name = argv[++i];

View File

@ -387,6 +387,18 @@ CArch::getIDOfThread(CArchThread thread)
return m_mt->getIDOfThread(thread);
}
void
CArch::setInterruptHandler(InterruptFunc func, void* userData)
{
return m_mt->setInterruptHandler(func, userData);
}
void
CArch::interrupt()
{
return m_mt->interrupt();
}
CArchSocket
CArch::newSocket(EAddressFamily family, ESocketType type)
{

View File

@ -122,6 +122,8 @@ public:
virtual bool isExitedThread(CArchThread);
virtual void* getResultOfThread(CArchThread);
virtual ThreadID getIDOfThread(CArchThread);
virtual void setInterruptHandler(InterruptFunc, void*);
virtual void interrupt();
// IArchNetwork overrides
virtual CArchSocket newSocket(EAddressFamily, ESocketType);

View File

@ -83,7 +83,9 @@ CArchMultithreadPosix* CArchMultithreadPosix::s_instance = NULL;
CArchMultithreadPosix::CArchMultithreadPosix() :
m_newThreadCalled(false),
m_nextID(0)
m_nextID(0),
m_signalFunc(NULL),
m_signalUserData(NULL)
{
assert(s_instance == NULL);
@ -558,6 +560,28 @@ CArchMultithreadPosix::getIDOfThread(CArchThread thread)
return thread->m_id;
}
void
CArchMultithreadPosix::setInterruptHandler(InterruptFunc func, void* userData)
{
lockMutex(m_threadMutex);
m_signalFunc = func;
m_signalUserData = userData;
unlockMutex(m_threadMutex);
}
void
CArchMultithreadPosix::interrupt()
{
lockMutex(m_threadMutex);
if (m_signalFunc != NULL) {
m_signalFunc(m_signalUserData);
}
else {
ARCH->cancelThread(m_mainThread);
}
unlockMutex(m_threadMutex);
}
void
CArchMultithreadPosix::startSignalHandler()
{
@ -578,7 +602,7 @@ CArchMultithreadPosix::startSignalHandler()
if (status == 0) {
status = pthread_create(&m_signalThread, &attr,
&CArchMultithreadPosix::threadSignalHandler,
m_mainThread);
NULL);
pthread_attr_destroy(&attr);
}
if (status != 0) {
@ -735,10 +759,8 @@ CArchMultithreadPosix::threadCancel(int)
}
void*
CArchMultithreadPosix::threadSignalHandler(void* vrep)
CArchMultithreadPosix::threadSignalHandler(void*)
{
CArchThreadImpl* mainThread = reinterpret_cast<CArchThreadImpl*>(vrep);
// detach
pthread_detach(pthread_self());
@ -768,8 +790,7 @@ CArchMultithreadPosix::threadSignalHandler(void* vrep)
sigwait(&sigset);
#endif
// if we get here then the signal was raised. cancel the main
// thread so it can shut down cleanly.
ARCH->cancelThread(mainThread);
// if we get here then the signal was raised
ARCH->interrupt();
}
}

View File

@ -61,6 +61,8 @@ public:
virtual bool isExitedThread(CArchThread);
virtual void* getResultOfThread(CArchThread);
virtual ThreadID getIDOfThread(CArchThread);
virtual void setInterruptHandler(InterruptFunc, void*);
virtual void interrupt();
private:
void startSignalHandler();
@ -91,6 +93,8 @@ private:
ThreadID m_nextID;
pthread_t m_signalThread;
InterruptFunc m_signalFunc;
void* m_signalUserData;
};
#endif

View File

@ -437,6 +437,10 @@ CArchNetworkBSD::readSocket(CArchSocket s, void* buf, size_t len)
ARCH->testCancelThread();
continue;
}
else if (errno == EAGAIN) {
n = 0;
break;
}
throwError(errno);
}
} while (false);
@ -458,6 +462,11 @@ CArchNetworkBSD::writeSocket(CArchSocket s, const void* buf, size_t len)
ARCH->testCancelThread();
continue;
}
else if (errno == EAGAIN) {
// no buffer space
n = 0;
break;
}
throwError(errno);
}
} while (false);
@ -794,6 +803,8 @@ CArchNetworkBSD::throwError(int err)
throw XArchNetworkNotConnected(new XArchEvalUnix(err));
case EPIPE:
throw XArchNetworkShutdown(new XArchEvalUnix(err));
case ECONNABORTED:
case ECONNRESET:
throw XArchNetworkDisconnected(new XArchEvalUnix(err));

View File

@ -268,6 +268,18 @@ public:
*/
virtual ThreadID getIDOfThread(CArchThread thread) = 0;
//! Set the interrupt handler
/*!
Sets the function to call on receipt of an external interrupt.
By default and when \p func is NULL, the main thread is cancelled.
*/
typedef void (*InterruptFunc)(void*);
virtual void setInterruptHandler(InterruptFunc func,
void* userData) = 0;
//! Invoke the interrupt handler
virtual void interrupt() = 0;
//@}
};

View File

@ -89,7 +89,7 @@ library to indicate various errors.
*/
XARCH_SUBCLASS(XArchNetwork, XArch);
//! Network insufficient permission
//! Operation would block
XARCH_SUBCLASS(XArchNetworkWouldBlock, XArchNetwork);
//! Network insufficient permission
@ -116,6 +116,9 @@ XARCH_SUBCLASS(XArchNetworkNoRoute, XArchNetwork);
//! Socket not connected
XARCH_SUBCLASS(XArchNetworkNotConnected, XArchNetwork);
//! Remote read end of socket has closed
XARCH_SUBCLASS(XArchNetworkShutdown, XArchNetwork);
//! Remote end of socket has disconnected
XARCH_SUBCLASS(XArchNetworkDisconnected, XArchNetwork);

View File

@ -16,37 +16,31 @@
#include "IEventJob.h"
#include "CArch.h"
// interrupt handler. this just adds a quit event to the queue.
static
void
interrupt(void*)
{
EVENTQUEUE->addEvent(CEvent(CEvent::kQuit));
}
//
// CEventQueue
//
static int g_systemTarget = 0;
CEventQueue* CEventQueue::s_instance = NULL;
CEventQueue::CEventQueue()
{
assert(s_instance == NULL);
s_instance = this;
m_mutex = ARCH->newMutex();
setInstance(this);
m_mutex = ARCH->newMutex();
ARCH->setInterruptHandler(&interrupt, NULL);
}
CEventQueue::~CEventQueue()
{
ARCH->setInterruptHandler(NULL, NULL);
ARCH->closeMutex(m_mutex);
s_instance = NULL;
}
void*
CEventQueue::getSystemTarget()
{
// any unique arbitrary pointer will do
return &g_systemTarget;
}
CEventQueue*
CEventQueue::getInstance()
{
return s_instance;
setInstance(NULL);
}
bool
@ -83,7 +77,7 @@ bool
CEventQueue::dispatchEvent(const CEvent& event)
{
void* target = event.getTarget();
IEventJob* job = getHandler(target);
IEventJob* job = getHandler(event.getType(), target);
if (job != NULL) {
job->run(event);
return true;
@ -164,16 +158,56 @@ void
CEventQueue::adoptHandler(void* target, IEventJob* handler)
{
CArchMutexLock lock(m_mutex);
IEventJob*& job = m_handlers[target];
delete job;
job = handler;
doAdoptHandler(CEvent::kUnknown, target, handler);
}
void
CEventQueue::adoptHandler(CEvent::Type type, void* target, IEventJob* handler)
{
assert(type != CEvent::kUnknown);
CArchMutexLock lock(m_mutex);
doAdoptHandler(type, target, handler);
}
IEventJob*
CEventQueue::orphanHandler(void* target)
{
CArchMutexLock lock(m_mutex);
CHandlerTable::iterator index = m_handlers.find(target);
return doOrphanHandler(CEvent::kUnknown, target);
}
IEventJob*
CEventQueue::orphanHandler(CEvent::Type type, void* target)
{
assert(type != CEvent::kUnknown);
CArchMutexLock lock(m_mutex);
return doOrphanHandler(type, target);
}
void
CEventQueue::removeHandler(void* target)
{
delete orphanHandler(target);
}
void
CEventQueue::removeHandler(CEvent::Type type, void* target)
{
delete orphanHandler(type, target);
}
void
CEventQueue::doAdoptHandler(CEvent::Type type, void* target, IEventJob* handler)
{
IEventJob*& job = m_handlers[CTypeTarget(type, target)];
delete job;
job = handler;
}
IEventJob*
CEventQueue::doOrphanHandler(CEvent::Type type, void* target)
{
CHandlerTable::iterator index = m_handlers.find(CTypeTarget(type, target));
if (index != m_handlers.end()) {
IEventJob* handler = index->second;
m_handlers.erase(index);
@ -191,16 +225,19 @@ CEventQueue::isEmpty() const
}
IEventJob*
CEventQueue::getHandler(void* target) const
CEventQueue::getHandler(CEvent::Type type, void* target) const
{
CArchMutexLock lock(m_mutex);
CHandlerTable::const_iterator index = m_handlers.find(target);
CHandlerTable::const_iterator index =
m_handlers.find(CTypeTarget(type, target));
if (index != m_handlers.end()) {
return index->second;
}
else {
return NULL;
index = m_handlers.find(CTypeTarget(CEvent::kUnknown, target));
if (index != m_handlers.end()) {
return index->second;
}
return NULL;
}
UInt32
@ -217,7 +254,7 @@ CEventQueue::saveEvent(const CEvent& event)
}
else {
// make a new id
id = static_cast<UInt32>(m_oldEventIDs.size());
id = static_cast<UInt32>(m_events.size());
}
// save data
@ -309,7 +346,31 @@ CEventQueue::getNextTimerTimeout() const
//
// CXWindowsScreen::CTimer
// CEventQueue::CTypeTarget
//
CEventQueue::CTypeTarget::CTypeTarget(CEvent::Type type, void* target) :
m_type(type),
m_target(target)
{
// do nothing
}
CEventQueue::CTypeTarget::~CTypeTarget()
{
// do nothing
}
bool
CEventQueue::CTypeTarget::operator<(const CTypeTarget& tt) const
{
return (m_type < tt.m_type ||
(m_type == tt.m_type && m_target < tt.m_target));
}
//
// CEventQueue::CTimer
//
CEventQueue::CTimer::CTimer(CEventQueueTimer* timer,

View File

@ -33,27 +33,6 @@ public:
CEventQueue();
virtual ~CEventQueue();
//! @name manipulators
//@{
//@}
//! @name accessors
//@{
//! Get the system event type target
/*!
Returns the target to use for dispatching \c CEvent::kSystem events.
*/
static void* getSystemTarget();
//! Get the singleton instance
/*!
Returns the singleton instance of the event queue
*/
static CEventQueue* getInstance();
//@}
// IEventQueue overrides
virtual bool getEvent(CEvent& event, double timeout = -1.0);
virtual bool dispatchEvent(const CEvent& event);
@ -64,9 +43,14 @@ public:
newOneShotTimer(double duration, void* target = NULL);
virtual void deleteTimer(CEventQueueTimer*);
virtual void adoptHandler(void* target, IEventJob* dispatcher);
virtual void adoptHandler(CEvent::Type type,
void* target, IEventJob* handler);
virtual IEventJob* orphanHandler(void* target);
virtual IEventJob* orphanHandler(CEvent::Type type, void* target);
virtual void removeHandler(void* target);
virtual void removeHandler(CEvent::Type type, void* target);
virtual bool isEmpty() const;
virtual IEventJob* getHandler(void* target) const;
virtual IEventJob* getHandler(CEvent::Type type, void* target) const;
protected:
//! @name manipulators
@ -137,11 +121,26 @@ protected:
//@}
private:
void doAdoptHandler(CEvent::Type type,
void* target, IEventJob* handler);
IEventJob* doOrphanHandler(CEvent::Type type, void* target);
UInt32 saveEvent(const CEvent& event);
bool hasTimerExpired(CEvent& event);
double getNextTimerTimeout() const;
private:
class CTypeTarget {
public:
CTypeTarget(CEvent::Type type, void* target);
~CTypeTarget();
bool operator<(const CTypeTarget&) const;
private:
CEvent::Type m_type;
void* m_target;
};
class CTimer {
public:
CTimer(CEventQueueTimer*, double timeout, void* target, bool oneShot);
@ -172,9 +171,7 @@ private:
typedef CPriorityQueue<CTimer> CTimerQueue;
typedef std::map<UInt32, CEvent> CEventTable;
typedef std::vector<UInt32> CEventIDList;
typedef std::map<void*, IEventJob*> CHandlerTable;
static CEventQueue* s_instance;
typedef std::map<CTypeTarget, IEventJob*> CHandlerTable;
CArchMutex m_mutex;

View File

@ -1,6 +1,6 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
* Copyright (C) 2004 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -12,28 +12,32 @@
* GNU General Public License for more details.
*/
#include "CInputStreamFilter.h"
#include "IEventQueue.h"
//
// CInputStreamFilter
// IEventQueue
//
CInputStreamFilter::CInputStreamFilter(IInputStream* stream, bool adopted) :
m_stream(stream),
m_adopted(adopted)
static int g_systemTarget = 0;
IEventQueue* IEventQueue::s_instance = NULL;
void*
IEventQueue::getSystemTarget()
{
assert(m_stream != NULL);
// any unique arbitrary pointer will do
return &g_systemTarget;
}
CInputStreamFilter::~CInputStreamFilter()
IEventQueue*
IEventQueue::getInstance()
{
if (m_adopted) {
delete m_stream;
}
assert(s_instance != NULL);
return s_instance;
}
IInputStream*
CInputStreamFilter::getStream() const
void
IEventQueue::setInstance(IEventQueue* instance)
{
return m_stream;
assert(s_instance == NULL || instance == NULL);
s_instance = instance;
}

View File

@ -16,9 +16,10 @@
#define IEVENTQUEUE_H
#include "IInterface.h"
#include "BasicTypes.h"
#include "CEvent.h"
#define EVENTQUEUE IEventQueue::getInstance()
class CEvent;
class IEventJob;
// Opaque type for timer info. This is defined by subclasses of
@ -107,9 +108,21 @@ public:
/*!
Registers an event handler for \p target. The \p handler is
adopted. Any existing handler for the target is deleted.
\c dispatchEvent() will invoke \p handler for any event for
\p target that doesn't have a type specific handler.
*/
virtual void adoptHandler(void* target, IEventJob* handler) = 0;
//! Register an event handler for an event type
/*!
Registers an event handler for \p type and \p target. The \p handler
is adopted. Any existing handler for the type,target pair is deleted.
\c dispatchEvent() will invoke \p handler for any event for \p target
of type \p type.
*/
virtual void adoptHandler(CEvent::Type type,
void* target, IEventJob* handler) = 0;
//! Unregister an event handler
/*!
Unregisters an event handler for \p target and returns it.
@ -118,6 +131,27 @@ public:
*/
virtual IEventJob* orphanHandler(void* target) = 0;
//! Unregister an event handler for an event type
/*!
Unregisters an event handler for the \p type, \p target pair and
returns it. Returns NULL if there was no such handler. The
client becomes responsible for deleting the returned handler.
*/
virtual IEventJob* orphanHandler(CEvent::Type type, void* target) = 0;
//! Unregister an event handler
/*!
Unregisters an event handler for \p target and deletes it.
*/
virtual void removeHandler(void* target) = 0;
//! Unregister an event handler for an event type
/*!
Unregisters an event handler for the \p type, \p target pair and
deletes it.
*/
virtual void removeHandler(CEvent::Type type, void* target) = 0;
//@}
//! @name accessors
//@{
@ -131,12 +165,40 @@ public:
//! Get an event handler
/*!
Finds and returns the event handler for \p target, or NULL if
there is no such handler.
Finds and returns the event handler for the \p type, \p target pair.
If there is no such handler, returns the handler for \p target. If
that doesn't exist, returns NULL.
*/
virtual IEventJob* getHandler(void* target) const = 0;
virtual IEventJob* getHandler(CEvent::Type type, void* target) const = 0;
//! Get the system event type target
/*!
Returns the target to use for dispatching \c CEvent::kSystem events.
*/
static void* getSystemTarget();
//! Get the singleton instance
/*!
Returns the singleton instance of the event queue
*/
static IEventQueue* getInstance();
//@}
protected:
//! @name manipulators
//@{
//! Set the singleton instance
/*!
Sets the singleton instance of the event queue
*/
static void setInstance(IEventQueue*);
//@}
private:
static IEventQueue* s_instance;
};
#endif

View File

@ -35,6 +35,7 @@ libbase_a_SOURCES = \
CStopwatch.cpp \
CStringUtil.cpp \
CUnicode.cpp \
IEventQueue.cpp \
LogOutputters.cpp \
XBase.cpp \
CEvent.h \

View File

@ -1,140 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "CBufferedInputStream.h"
#include "CLock.h"
#include "CMutex.h"
#include "CThread.h"
#include "CStopwatch.h"
#include "IJob.h"
#include "XIO.h"
#include <cstring>
//
// CBufferedInputStream
//
CBufferedInputStream::CBufferedInputStream(
CMutex* mutex, IJob* adoptedEmptyCB, IJob* adoptedCloseCB) :
m_mutex(mutex),
m_empty(mutex, true),
m_emptyCB(adoptedEmptyCB),
m_closeCB(adoptedCloseCB),
m_closed(false),
m_hungup(false)
{
assert(m_mutex != NULL);
}
CBufferedInputStream::~CBufferedInputStream()
{
delete m_closeCB;
delete m_emptyCB;
}
void
CBufferedInputStream::write(const void* buffer, UInt32 n)
{
if (!m_hungup && n > 0) {
m_buffer.write(buffer, n);
m_empty = (m_buffer.getSize() == 0);
m_empty.broadcast();
}
}
void
CBufferedInputStream::hangup()
{
m_hungup = true;
m_empty.broadcast();
}
UInt32
CBufferedInputStream::readNoLock(void* buffer, UInt32 n, double timeout)
{
if (m_closed) {
throw XIOClosed();
}
if (n == 0) {
return n;
}
// wait for data, hangup, or timeout
CStopwatch timer(true);
while (!m_hungup && m_empty == true) {
if (!m_empty.wait(timer, timeout)) {
// timed out
return (UInt32)-1;
}
}
// read data
const UInt32 count = m_buffer.getSize();
if (n > count) {
n = count;
}
if (n > 0) {
if (buffer != NULL) {
memcpy(buffer, m_buffer.peek(n), n);
}
m_buffer.pop(n);
}
// update empty state
if (m_buffer.getSize() == 0) {
m_empty = true;
m_empty.broadcast();
if (m_emptyCB != NULL) {
m_emptyCB->run();
}
}
return n;
}
UInt32
CBufferedInputStream::getSizeNoLock() const
{
return m_buffer.getSize();
}
void
CBufferedInputStream::close()
{
CLock lock(m_mutex);
if (m_closed) {
throw XIOClosed();
}
m_closed = true;
m_hungup = true;
m_buffer.pop(m_buffer.getSize());
m_empty.broadcast();
if (m_closeCB != NULL) {
m_closeCB->run();
}
}
UInt32
CBufferedInputStream::read(void* buffer, UInt32 n, double timeout)
{
CLock lock(m_mutex);
return readNoLock(buffer, n, timeout);
}
UInt32
CBufferedInputStream::getSize() const
{
CLock lock(m_mutex);
return getSizeNoLock();
}

View File

@ -1,101 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef CBUFFEREDINPUTSTREAM_H
#define CBUFFEREDINPUTSTREAM_H
#include "IInputStream.h"
#include "CStreamBuffer.h"
#include "CCondVar.h"
class CMutex;
class IJob;
//! Memory buffer input stream
/*!
This class provides an input stream that reads from a memory buffer.
It also provides a means for the owner to ensure thread safe access.
Typically, an owner object will make this object visible to clients
that need access to an IInputStream while using the CBufferedInputStream
methods to write data to the stream.
*/
class CBufferedInputStream : public IInputStream {
public:
/*!
The \c mutex must not be NULL and will be used to ensure thread
safe access. If \c adoptedCloseCB is not NULL it will be called
when close() is called, allowing the creator to detect the close.
If adoptedEmptyCB is not NULL, it will be called whenever the
buffer becomes empty (except it won't be called by the c'tor nor
when the buffer is closed).
*/
CBufferedInputStream(CMutex* mutex,
IJob* adoptedEmptyCB, IJob* adoptedCloseCB);
~CBufferedInputStream();
//! @name manipulators
//@{
//! Write data to stream
/*!
Write \c n bytes from \c buffer to the stream. The mutex must
be locked before calling this.
*/
void write(const void* buffer, UInt32 n);
//! Hangup stream
/*!
Causes read() to always return immediately. If there is no
more data to read then it returns 0. Further writes are discarded.
The mutex must be locked before calling this.
*/
void hangup();
//! Read from stream
/*!
This is the same as read() but the mutex must be locked before
calling this.
*/
UInt32 readNoLock(void*, UInt32 n, double timeout);
//@}
//! @name accessors
//@{
//! Get remaining size of stream
/*!
This is the same as getSize() but the mutex must be locked before
calling this.
*/
UInt32 getSizeNoLock() const;
//@}
// IInputStream overrides
// these all lock the mutex for their duration
virtual void close();
virtual UInt32 read(void*, UInt32 n, double timeout);
virtual UInt32 getSize() const;
private:
CMutex* m_mutex;
CCondVar<bool> m_empty;
IJob* m_emptyCB;
IJob* m_closeCB;
CStreamBuffer m_buffer;
bool m_closed;
bool m_hungup;
};
#endif

View File

@ -1,104 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "CBufferedOutputStream.h"
#include "XIO.h"
#include "CLock.h"
#include "CMutex.h"
#include "CThread.h"
#include "IJob.h"
//
// CBufferedOutputStream
//
CBufferedOutputStream::CBufferedOutputStream(
CMutex* mutex, IJob* adoptedFillCB, IJob* adoptedCloseCB) :
m_mutex(mutex),
m_fillCB(adoptedFillCB),
m_closeCB(adoptedCloseCB),
m_empty(mutex, true),
m_closed(false)
{
assert(m_mutex != NULL);
}
CBufferedOutputStream::~CBufferedOutputStream()
{
delete m_closeCB;
delete m_fillCB;
}
const void*
CBufferedOutputStream::peek(UInt32 n)
{
return m_buffer.peek(n);
}
void
CBufferedOutputStream::pop(UInt32 n)
{
m_buffer.pop(n);
if (m_buffer.getSize() == 0) {
m_empty.broadcast();
}
}
UInt32
CBufferedOutputStream::getSize() const
{
return m_buffer.getSize();
}
void
CBufferedOutputStream::close()
{
CLock lock(m_mutex);
if (m_closed) {
throw XIOClosed();
}
m_closed = true;
if (m_closeCB != NULL) {
m_closeCB->run();
}
}
UInt32
CBufferedOutputStream::write(const void* buffer, UInt32 n)
{
CLock lock(m_mutex);
if (m_closed) {
throw XIOClosed();
}
bool wasEmpty = (m_buffer.getSize() == 0);
m_buffer.write(buffer, n);
if (wasEmpty && n > 0) {
if (m_fillCB != NULL) {
m_fillCB->run();
}
}
return n;
}
void
CBufferedOutputStream::flush()
{
// wait until all data is written
CLock lock(m_mutex);
while (m_buffer.getSize() > 0) {
m_empty.wait();
}
}

View File

@ -1,92 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef CBUFFEREDOUTPUTSTREAM_H
#define CBUFFEREDOUTPUTSTREAM_H
#include "IOutputStream.h"
#include "CStreamBuffer.h"
#include "CCondVar.h"
class CMutex;
class IJob;
//! Memory buffer output stream
/*!
This class provides an output stream that writes to a memory buffer.
It also provides a means for the owner to ensure thread safe access.
Typically, an owner object will make this object visible to clients
that need access to an IOutputStream while using the CBufferedOutputStream
methods to read the data written to the stream.
*/
class CBufferedOutputStream : public IOutputStream {
public:
/*!
The \c mutex must not be NULL and will be used to ensure thread
safe access. If \c adoptedCloseCB is not NULL it will be called
when close() is called, allowing the creator to detect the close.
If \c adoptedFillCB is not NULL, it will be called whenever the
buffer becomes non-empty.
*/
CBufferedOutputStream(CMutex* mutex,
IJob* adoptedFillCB, IJob* adoptedCloseCB);
~CBufferedOutputStream();
//! @name manipulators
//@{
//! Read data without removing from buffer
/*!
Returns a buffer of \c n bytes (which must be <= getSize()). The
caller must not modify the buffer nor delete it. The mutex must
be locked before calling this.
*/
const void* peek(UInt32 n);
//! Discard data
/*!
Discards the next \c n bytes. If \c n >= getSize() then the buffer
is cleared. The mutex must be locked before calling this.
*/
void pop(UInt32 n);
//@}
//! @name accessors
//@{
//! Get size of buffer
/*!
Returns the number of bytes in the buffer. The mutex must be locked
before calling this.
*/
UInt32 getSize() const;
//@}
// IOutputStream overrides
// these all lock the mutex for their duration
virtual void close();
virtual UInt32 write(const void*, UInt32 n);
virtual void flush();
private:
CMutex* m_mutex;
IJob* m_fillCB;
IJob* m_closeCB;
CCondVar<bool> m_empty;
CStreamBuffer m_buffer;
bool m_closed;
};
#endif

View File

@ -1,52 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef CINPUTSTREAMFILTER_H
#define CINPUTSTREAMFILTER_H
#include "IInputStream.h"
//! A filtering input stream
/*!
This class wraps an input stream. Subclasses provide indirect access
to the stream, typically performing some filtering.
*/
class CInputStreamFilter : public IInputStream {
public:
/*!
Create a wrapper around \c stream. Iff \c adoptStream is true then
this object takes ownership of the stream and will delete it in the
d'tor.
*/
CInputStreamFilter(IInputStream* stream, bool adoptStream = true);
~CInputStreamFilter();
// IInputStream overrides
virtual void close() = 0;
virtual UInt32 read(void*, UInt32 n, double timeout) = 0;
virtual UInt32 getSize() const = 0;
protected:
//! Get the stream
/*!
Returns the stream passed to the c'tor.
*/
IInputStream* getStream() const;
private:
IInputStream* m_stream;
bool m_adopted;
};
#endif

View File

@ -1,39 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "COutputStreamFilter.h"
//
// COutputStreamFilter
//
COutputStreamFilter::COutputStreamFilter(IOutputStream* stream, bool adopted) :
m_stream(stream),
m_adopted(adopted)
{
assert(m_stream != NULL);
}
COutputStreamFilter::~COutputStreamFilter()
{
if (m_adopted) {
delete m_stream;
}
}
IOutputStream*
COutputStreamFilter::getStream() const
{
return m_stream;
}

View File

@ -1,52 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef COUTPUTSTREAMFILTER_H
#define COUTPUTSTREAMFILTER_H
#include "IOutputStream.h"
//! A filtering output stream
/*!
This class wraps an output stream. Subclasses provide indirect access
to the stream, typically performing some filtering.
*/
class COutputStreamFilter : public IOutputStream {
public:
/*!
Create a wrapper around \c stream. Iff \c adoptStream is true then
this object takes ownership of the stream and will delete it in the
d'tor.
*/
COutputStreamFilter(IOutputStream* stream, bool adoptStream = true);
~COutputStreamFilter();
// IOutputStream overrides
virtual void close() = 0;
virtual UInt32 write(const void*, UInt32 count) = 0;
virtual void flush() = 0;
protected:
//! Get the stream
/*!
Returns the stream passed to the c'tor.
*/
IOutputStream* getStream() const;
private:
IOutputStream* m_stream;
bool m_adopted;
};
#endif

105
lib/io/CStreamFilter.cpp Normal file
View File

@ -0,0 +1,105 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2004 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "CStreamFilter.h"
//
// CStreamFilter
//
CStreamFilter::CStreamFilter(IStream* stream, bool adoptStream) :
m_stream(stream),
m_adopted(adoptStream)
{
// do nothing
}
CStreamFilter::~CStreamFilter()
{
if (m_adopted) {
delete m_stream;
}
}
void
CStreamFilter::close()
{
getStream()->close();
}
UInt32
CStreamFilter::read(void* buffer, UInt32 n)
{
return getStream()->read(buffer, n);
}
void
CStreamFilter::write(const void* buffer, UInt32 n)
{
getStream()->write(buffer, n);
}
void
CStreamFilter::flush()
{
getStream()->flush();
}
void
CStreamFilter::shutdownInput()
{
getStream()->shutdownInput();
}
void
CStreamFilter::shutdownOutput()
{
getStream()->shutdownOutput();
}
void
CStreamFilter::setEventFilter(IEventJob* filter)
{
getStream()->setEventFilter(filter);
}
void*
CStreamFilter::getEventTarget() const
{
return const_cast<void*>(reinterpret_cast<const void*>(this));
}
bool
CStreamFilter::isReady() const
{
return getStream()->isReady();
}
UInt32
CStreamFilter::getSize() const
{
return getStream()->getSize();
}
IEventJob*
CStreamFilter::getEventFilter() const
{
return getStream()->getEventFilter();
}
IStream*
CStreamFilter::getStream() const
{
return m_stream;
}

62
lib/io/CStreamFilter.h Normal file
View File

@ -0,0 +1,62 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2004 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef CSTREAMFILTER_H
#define CSTREAMFILTER_H
#include "IStream.h"
//! A stream filter
/*!
This class wraps a stream. Subclasses provide indirect access
to the wrapped stream, typically performing some filtering.
*/
class CStreamFilter : public IStream {
public:
/*!
Create a wrapper around \c stream. Iff \c adoptStream is true then
this object takes ownership of the stream and will delete it in the
d'tor.
*/
CStreamFilter(IStream* stream, bool adoptStream = true);
~CStreamFilter();
// IStream overrides
// These all just forward to the underlying stream. Override as
// necessary.
virtual void close();
virtual UInt32 read(void* buffer, UInt32 n);
virtual void write(const void* buffer, UInt32 n);
virtual void flush();
virtual void shutdownInput();
virtual void shutdownOutput();
virtual void setEventFilter(IEventJob* filter);
virtual void* getEventTarget() const;
virtual bool isReady() const;
virtual UInt32 getSize() const;
virtual IEventJob* getEventFilter() const;
protected:
//! Get the stream
/*!
Returns the stream passed to the c'tor.
*/
IStream* getStream() const;
private:
IStream* m_stream;
bool m_adopted;
};
#endif

View File

@ -1,67 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef IINPUTSTREAM_H
#define IINPUTSTREAM_H
#include "IInterface.h"
#include "BasicTypes.h"
//! Input stream interface
/*!
Defines the interface for all input streams.
*/
class IInputStream : public IInterface {
public:
//! @name manipulators
//@{
//! Close the stream
/*!
Closes the stream. Attempting to read() after close() throws
XIOClosed and getSize() always returns zero.
*/
virtual void close() = 0;
//! Read from stream
/*!
Read up to \c n bytes into buffer, returning the number read.
Blocks for up to \c timeout seconds if no data is available but does
not wait if any data is available, even if less than \c n bytes.
If \c timeout < 0 then it blocks indefinitely until data is available.
If \c buffer is NULL then the data is discarded. Returns (UInt32)-1 if
it times out and 0 if no data is available and the other end of the
stream has hungup.
(cancellation point)
*/
virtual UInt32 read(void* buffer, UInt32 n, double timeout) = 0;
//@}
//! @name accessors
//@{
//! Get remaining size of stream
/*!
Returns a conservative estimate of the available bytes to read
(i.e. a number not greater than the actual number of bytes).
Some streams may not be able to determine this and will always
return zero.
*/
virtual UInt32 getSize() const = 0;
//@}
};
#endif

View File

@ -1,58 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef IOUTPUTSTREAM_H
#define IOUTPUTSTREAM_H
#include "IInterface.h"
#include "BasicTypes.h"
//! Output stream interface
/*!
Defines the interface for all output streams.
*/
class IOutputStream : public IInterface {
public:
//! @name manipulators
//@{
//! Close the stream
/*!
Closes the stream. Attempting to write() after close() throws
XIOClosed.
*/
virtual void close() = 0;
//! Write to stream
/*!
Write \c n bytes from \c buffer to the stream. If this can't
complete immeditely it will block. If cancelled, an indeterminate
amount of data may have been written.
(cancellation point)
*/
virtual UInt32 write(const void* buffer, UInt32 n) = 0;
//! Flush the stream
/*!
Waits until all buffered data has been written to the stream.
(cancellation point)
*/
virtual void flush() = 0;
//@}
};
#endif

55
lib/io/IStream.cpp Normal file
View File

@ -0,0 +1,55 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2004 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "IStream.h"
//
// IStream
//
CEvent::Type IStream::s_inputReadyEvent = CEvent::kUnknown;
CEvent::Type IStream::s_outputFlushedEvent = CEvent::kUnknown;
CEvent::Type IStream::s_outputErrorEvent = CEvent::kUnknown;
CEvent::Type IStream::s_inputShutdownEvent = CEvent::kUnknown;
CEvent::Type IStream::s_outputShutdownEvent = CEvent::kUnknown;
CEvent::Type
IStream::getInputReadyEvent()
{
return CEvent::registerTypeOnce(s_inputReadyEvent);
}
CEvent::Type
IStream::getOutputFlushedEvent()
{
return CEvent::registerTypeOnce(s_outputFlushedEvent);
}
CEvent::Type
IStream::getOutputErrorEvent()
{
return CEvent::registerTypeOnce(s_outputErrorEvent);
}
CEvent::Type
IStream::getInputShutdownEvent()
{
return CEvent::registerTypeOnce(s_inputShutdownEvent);
}
CEvent::Type
IStream::getOutputShutdownEvent()
{
return CEvent::registerTypeOnce(s_outputShutdownEvent);
}

175
lib/io/IStream.h Normal file
View File

@ -0,0 +1,175 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2004 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef ISTREAM_H
#define ISTREAM_H
#include "IInterface.h"
#include "CEvent.h"
class IEventJob;
//! Bidirectional stream interface
/*!
Defines the interface for all streams.
*/
class IStream : public IInterface {
public:
//! @name manipulators
//@{
//! Close the stream
/*!
Closes the stream. Pending input data and buffered output data
are discarded. Use \c flush() before \c close() to send buffered
output data. Attempts to \c read() after a close return 0,
attempts to \c write() generate output error events, and attempts
to \c flush() return immediately.
*/
virtual void close() = 0;
//! Read from stream
/*!
Read up to \p n bytes into \p buffer, returning the number read
(zero if no data is available or input is shutdown). \p buffer
may be NULL in which case the data is discarded.
*/
virtual UInt32 read(void* buffer, UInt32 n) = 0;
//! Write to stream
/*!
Write \c n bytes from \c buffer to the stream. If this can't
complete immediately it will block. Data may be buffered in
order to return more quickly. A output error event is generated
when writing fails.
*/
virtual void write(const void* buffer, UInt32 n) = 0;
//! Flush the stream
/*!
Waits until all buffered data has been written to the stream.
*/
virtual void flush() = 0;
//! Shutdown input
/*!
Shutdown the input side of the stream. Any pending input data is
discarded and further reads immediately return 0.
*/
virtual void shutdownInput() = 0;
//! Shutdown output
/*!
Shutdown the output side of the stream. Any buffered output data
is discarded and further writes generate output error events. Use
\c flush() before \c shutdownOutput() to send buffered output data.
*/
virtual void shutdownOutput() = 0;
//! Set the event filter
/*!
If not NULL, the \p filter is passed any event that would've been
added to the queue. The filter can discard the event, modify it
and add it to the queue, and add other events. The default filter
is NULL. The caller retains ownership of the filter.
*/
virtual void setEventFilter(IEventJob* filter) = 0;
//@}
//@}
//! @name accessors
//@{
//! Get event target
/*!
Returns the event target for events generated by this stream.
*/
virtual void* getEventTarget() const = 0;
//! Test if \c read() will succeed
/*!
Returns true iff an immediate \c read() will return data. This
may or may not be the same as \c getSize() > 0, depending on the
stream type.
*/
virtual bool isReady() const = 0;
//! Get bytes available to read
/*!
Returns a conservative estimate of the available bytes to read
(i.e. a number not greater than the actual number of bytes).
Some streams may not be able to determine this and will always
return zero.
*/
virtual UInt32 getSize() const = 0;
//! Get the event filter
/*!
Returns the current event filter.
*/
virtual IEventJob* getEventFilter() const = 0;
//! Get input ready event type
/*!
Returns the input ready event type. A stream sends this event
when \c read() will return with data.
*/
static CEvent::Type getInputReadyEvent();
//! Get output flushed event type
/*!
Returns the output flushed event type. A stream sends this event
when the output buffer has been flushed. If there have been no
writes since the event was posted, calling \c shutdownOutput() or
\c close() will not discard any data and \c flush() will return
immediately.
*/
static CEvent::Type getOutputFlushedEvent();
//! Get output error event type
/*!
Returns the output error event type. A stream sends this event
when a write has failed.
*/
static CEvent::Type getOutputErrorEvent();
//! Get input shutdown event type
/*!
Returns the input shutdown event type. This is sent when the
input side of the stream has shutdown. When the input has
shutdown, no more data will ever be available to read.
*/
static CEvent::Type getInputShutdownEvent();
//! Get output shutdown event type
/*!
Returns the output shutdown event type. This is sent when the
output side of the stream has shutdown. When the output has
shutdown, no more data can ever be written to the stream. Any
attempt to do so will generate a output error event.
*/
static CEvent::Type getOutputShutdownEvent();
//@}
private:
static CEvent::Type s_inputReadyEvent;
static CEvent::Type s_outputFlushedEvent;
static CEvent::Type s_outputErrorEvent;
static CEvent::Type s_inputShutdownEvent;
static CEvent::Type s_outputShutdownEvent;
};
#endif

View File

@ -17,10 +17,7 @@
#include "IInterface.h"
class CInputStreamFilter;
class COutputStreamFilter;
class IInputStream;
class IOutputStream;
class IStream;
//! Stream filter factory interface
/*!
@ -28,21 +25,12 @@ This interface provides factory methods to create stream filters.
*/
class IStreamFilterFactory : public IInterface {
public:
//! Create input filter
//! Create filter
/*!
Create and return an input stream filter. The caller must delete the
returned object.
Create and return a stream filter on \p stream. The caller must
delete the returned object.
*/
virtual CInputStreamFilter*
createInput(IInputStream*, bool adoptStream) = 0;
//! Create output filter
/*!
Create and return an output stream filter. The caller must delete the
returned object.
*/
virtual COutputStreamFilter*
createOutput(IOutputStream*, bool adoptStream) = 0;
virtual IStream* create(IStream* stream, bool adoptStream) = 0;
};
#endif

View File

@ -25,19 +25,13 @@ MAINTAINERCLEANFILES = \
noinst_LIBRARIES = libio.a
libio_a_SOURCES = \
CBufferedInputStream.cpp \
CBufferedOutputStream.cpp \
CInputStreamFilter.cpp \
COutputStreamFilter.cpp \
CStreamBuffer.cpp \
CStreamFilter.cpp \
IStream.cpp \
XIO.cpp \
CBufferedInputStream.h \
CBufferedOutputStream.h \
CInputStreamFilter.h \
COutputStreamFilter.h \
CStreamBuffer.h \
IInputStream.h \
IOutputStream.h \
CStreamFilter.h \
IStream.h \
IStreamFilterFactory.h \
XIO.h \
$(NULL)

View File

@ -34,3 +34,14 @@ XIOEndOfStream::getWhat() const throw()
{
return format("XIOEndOfStream", "reached end of stream");
}
//
// XIOWouldBlock
//
CString
XIOWouldBlock::getWhat() const throw()
{
return format("XIOWouldBlock", "stream operation would block");
}

View File

@ -39,4 +39,10 @@ Thrown when attempting to read beyond the end of a stream.
*/
XBASE_SUBCLASS_WHAT(XIOEndOfStream, XIO);
//! I/O would block exception
/*!
Thrown if an operation on a stream would block.
*/
XBASE_SUBCLASS_WHAT(XIOWouldBlock, XIO);
#endif

View File

@ -0,0 +1,304 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2004 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "CSocketMultiplexer.h"
#include "ISocketMultiplexerJob.h"
#include "CCondVar.h"
#include "CLock.h"
#include "CMutex.h"
#include "CThread.h"
#include "TMethodJob.h"
#include "CArch.h"
#include "XArch.h"
#include "stdvector.h"
//
// CSocketMultiplexer
//
CSocketMultiplexer* CSocketMultiplexer::s_instance = NULL;
CSocketMultiplexer::CSocketMultiplexer() :
m_mutex(new CMutex),
m_pollable(new CCondVar<bool>(m_mutex, false)),
m_polling(new CCondVar<bool>(m_mutex, false)),
m_thread(NULL),
m_update(false)
{
assert(s_instance == NULL);
// this pointer just has to be unique and not NULL. it will
// never be dereferenced. it's used to identify cursor nodes
// in the jobs list.
m_cursorMark = reinterpret_cast<ISocketMultiplexerJob*>(this);
// start thread
m_thread = new CThread(new TMethodJob<CSocketMultiplexer>(
this, &CSocketMultiplexer::serviceThread));
s_instance = this;
}
CSocketMultiplexer::~CSocketMultiplexer()
{
m_thread->cancel();
m_thread->wait();
delete m_thread;
delete m_polling;
delete m_pollable;
delete m_mutex;
// clean up jobs
for (CSocketJobMap::iterator i = m_socketJobMap.begin();
i != m_socketJobMap.end(); ++i) {
delete *(i->second);
}
s_instance = NULL;
}
CSocketMultiplexer*
CSocketMultiplexer::getInstance()
{
return s_instance;
}
void
CSocketMultiplexer::addSocket(ISocket* socket, ISocketMultiplexerJob* job)
{
assert(socket != NULL);
assert(job != NULL);
CLock lock(m_mutex);
// prevent service thread from restarting poll
*m_pollable = false;
// break thread out of poll
m_thread->unblock();
// wait for poll to finish
while (*m_polling) {
m_polling->wait();
}
// insert/replace job
CSocketJobMap::iterator i = m_socketJobMap.find(socket);
if (i == m_socketJobMap.end()) {
// we *must* put the job at the end so the order of jobs in
// the list continue to match the order of jobs in pfds in
// serviceThread().
CJobCursor j = m_socketJobs.insert(m_socketJobs.end(), job);
m_update = true;
m_socketJobMap.insert(std::make_pair(socket, j));
}
else {
CJobCursor j = i->second;
if (*j != job) {
delete *j;
*j = job;
}
m_update = true;
}
// there must be at least one socket so we can poll now
*m_pollable = true;
m_pollable->broadcast();
}
void
CSocketMultiplexer::removeSocket(ISocket* socket)
{
assert(socket != NULL);
CLock lock(m_mutex);
// prevent service thread from restarting poll
*m_pollable = false;
// break thread out of poll
m_thread->unblock();
// wait until thread finishes poll
while (*m_polling) {
m_polling->wait();
}
// remove job. rather than removing it from the map we put NULL
// in the list instead so the order of jobs in the list continues
// to match the order of jobs in pfds in serviceThread().
CSocketJobMap::iterator i = m_socketJobMap.find(socket);
if (i != m_socketJobMap.end()) {
if (*(i->second) != NULL) {
delete *(i->second);
*(i->second) = NULL;
m_update = true;
}
}
*m_pollable = true;
m_pollable->broadcast();
}
void
CSocketMultiplexer::serviceThread(void*)
{
std::vector<IArchNetwork::CPollEntry> pfds;
IArchNetwork::CPollEntry pfd;
// service the connections
for (;;) {
{
CLock lock(m_mutex);
// wait until pollable
while (!*m_pollable) {
m_pollable->wait();
}
// now we're polling
*m_polling = true;
m_polling->broadcast();
}
// we're now the only thread that can access m_sockets and
// m_update because m_polling and m_pollable are both true.
// therefore, we don't need to hold the mutex.
// collect poll entries
if (m_update) {
m_update = false;
pfds.clear();
pfds.reserve(m_socketJobMap.size());
CJobCursor cursor = newCursor();
CJobCursor jobCursor = nextCursor(cursor);
while (jobCursor != m_socketJobs.end()) {
ISocketMultiplexerJob* job = *jobCursor;
if (job != NULL) {
pfd.m_socket = job->getSocket();
pfd.m_events = 0;
if (job->isReadable()) {
pfd.m_events |= IArchNetwork::kPOLLIN;
}
if (job->isWritable()) {
pfd.m_events |= IArchNetwork::kPOLLOUT;
}
pfds.push_back(pfd);
}
jobCursor = nextCursor(cursor);
}
deleteCursor(cursor);
}
int status;
try {
// check for status
status = ARCH->pollSocket(&pfds[0], pfds.size(), -1);
}
catch (XArchNetwork&) {
// FIXME -- uh oh
status = 0;
}
if (status != 0) {
// iterate over socket jobs, invoking each and saving the
// new job.
UInt32 i = 0;
CJobCursor cursor = newCursor();
CJobCursor jobCursor = nextCursor(cursor);
while (i < pfds.size() && jobCursor != m_socketJobs.end()) {
if (*jobCursor != NULL) {
// get poll state
unsigned short revents = pfds[i].m_revents;
bool read = ((revents & IArchNetwork::kPOLLIN) != 0);
bool write = ((revents & IArchNetwork::kPOLLOUT) != 0);
bool error = ((revents & (IArchNetwork::kPOLLERR |
IArchNetwork::kPOLLNVAL)) != 0);
// run job
ISocketMultiplexerJob* job = *jobCursor;
ISocketMultiplexerJob* newJob = job->run(read, write, error);
// save job, if different
if (newJob != job) {
CLock lock(m_mutex);
delete job;
*jobCursor = newJob;
m_update = true;
}
++i;
}
// next job
jobCursor = nextCursor(cursor);
}
deleteCursor(cursor);
}
// delete any removed socket jobs
CLock lock(m_mutex);
for (CSocketJobMap::iterator i = m_socketJobMap.begin();
i != m_socketJobMap.end();) {
if (*(i->second) == NULL) {
m_socketJobMap.erase(i++);
m_update = true;
}
else {
++i;
}
}
if (m_socketJobMap.empty()) {
*m_pollable = false;
}
// done polling
*m_polling = false;
m_polling->broadcast();
}
}
CSocketMultiplexer::CJobCursor
CSocketMultiplexer::newCursor()
{
CLock lock(m_mutex);
return m_socketJobs.insert(m_socketJobs.begin(), m_cursorMark);
}
CSocketMultiplexer::CJobCursor
CSocketMultiplexer::nextCursor(CJobCursor cursor)
{
CLock lock(m_mutex);
ISocketMultiplexerJob* job = NULL;
CJobCursor j = m_socketJobs.end();
CJobCursor i = cursor;
while (++i != m_socketJobs.end()) {
if (*i != m_cursorMark) {
// found a real job (as opposed to a cursor)
j = i;
// move our cursor just past the job
m_socketJobs.splice(++i, m_socketJobs, cursor);
break;
}
}
return j;
}
void
CSocketMultiplexer::deleteCursor(CJobCursor cursor)
{
CLock lock(m_mutex);
m_socketJobs.erase(cursor);
}

View File

@ -0,0 +1,95 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2004 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef CSOCKETMULTIPLEXER_H
#define CSOCKETMULTIPLEXER_H
#include "IArchNetwork.h"
#include "stdlist.h"
#include "stdmap.h"
template <class T>
class CCondVar;
class CMutex;
class CThread;
class ISocket;
class ISocketMultiplexerJob;
//! Socket multiplexer
/*!
A socket multiplexer services multiple sockets simultaneously.
*/
class CSocketMultiplexer {
public:
CSocketMultiplexer();
~CSocketMultiplexer();
//! @name manipulators
//@{
void addSocket(ISocket*, ISocketMultiplexerJob*);
void removeSocket(ISocket*);
//@}
//! @name accessors
//@{
// maybe belongs on ISocketMultiplexer
static CSocketMultiplexer*
getInstance();
//@}
private:
// list of jobs. we use a list so we can safely iterate over it
// while other threads modify it.
typedef std::list<ISocketMultiplexerJob*> CSocketJobs;
typedef CSocketJobs::iterator CJobCursor;
typedef std::map<ISocket*, CJobCursor> CSocketJobMap;
// service sockets. the service thread will only access m_sockets
// and m_update while m_pollable and m_polling are true. all other
// threads must only modify these when m_pollable and m_polling are
// false. only the service thread sets m_polling.
void serviceThread(void*);
// create, iterate, and destroy a cursor. a cursor is used to
// safely iterate through the job list while other threads modify
// the list. it works by inserting a dummy item in the list and
// moving that item through the list. the dummy item will never
// be removed by other edits so an iterator pointing at the item
// remains valid until we remove the dummy item in deleteCursor().
// nextCursor() finds the next non-dummy item, moves our dummy
// item just past it, and returns an iterator for the non-dummy
// item. all cursor calls lock the mutex for their duration.
CJobCursor newCursor();
CJobCursor nextCursor(CJobCursor);
void deleteCursor(CJobCursor);
private:
CMutex* m_mutex;
CCondVar<bool>* m_pollable;
CCondVar<bool>* m_polling;
CThread* m_thread;
bool m_update;
CSocketJobs m_socketJobs;
CSocketJobMap m_socketJobMap;
ISocketMultiplexerJob* m_cursorMark;
static CSocketMultiplexer* s_instance;
};
#endif

View File

@ -30,8 +30,7 @@
// CTCPListenSocket
//
CTCPListenSocket::CTCPListenSocket() :
m_target(NULL)
CTCPListenSocket::CTCPListenSocket()
{
m_mutex = new CMutex;
try {
@ -76,21 +75,6 @@ CTCPListenSocket::bind(const CNetworkAddress& addr)
}
}
IDataSocket*
CTCPListenSocket::accept()
{
try {
CSocketMultiplexer::getInstance()->addSocket(this,
new TSocketMultiplexerMethodJob<CTCPListenSocket>(
this, &CTCPListenSocket::serviceListening,
m_socket, true, false));
return new CTCPSocket(ARCH->acceptSocket(m_socket, NULL));
}
catch (XArchNetwork&) {
return NULL;
}
}
void
CTCPListenSocket::close()
{
@ -108,11 +92,25 @@ CTCPListenSocket::close()
}
}
void
CTCPListenSocket::setEventTarget(void* target)
void*
CTCPListenSocket::getEventTarget() const
{
CLock lock(m_mutex);
m_target = target;
return const_cast<void*>(reinterpret_cast<const void*>(this));
}
IDataSocket*
CTCPListenSocket::accept()
{
try {
CSocketMultiplexer::getInstance()->addSocket(this,
new TSocketMultiplexerMethodJob<CTCPListenSocket>(
this, &CTCPListenSocket::serviceListening,
m_socket, true, false));
return new CTCPSocket(ARCH->acceptSocket(m_socket, NULL));
}
catch (XArchNetwork&) {
return NULL;
}
}
ISocketMultiplexerJob*
@ -125,7 +123,7 @@ CTCPListenSocket::serviceListening(ISocketMultiplexerJob* job,
}
if (read) {
CEventQueue::getInstance()->addEvent(
CEvent(getConnectingEvent(), m_target, NULL));
CEvent(getConnectingEvent(), this, NULL));
// stop polling on this socket until the client accepts
return NULL;
}

View File

@ -33,7 +33,7 @@ public:
// ISocket overrides
virtual void bind(const CNetworkAddress&);
virtual void close();
virtual void setEventTarget(void*);
virtual void* getEventTarget() const;
// IListenSocket overrides
virtual IDataSocket* accept();
@ -46,7 +46,6 @@ private:
private:
CArchSocket m_socket;
CMutex* m_mutex;
void* m_target;
};
#endif

View File

@ -16,14 +16,10 @@
#include "CNetworkAddress.h"
#include "CSocketMultiplexer.h"
#include "TSocketMultiplexerMethodJob.h"
#include "CBufferedInputStream.h"
#include "CBufferedOutputStream.h"
#include "XSocket.h"
#include "XIO.h"
#include "CLock.h"
#include "CMutex.h"
#include "CEventQueue.h"
#include "TMethodJob.h"
#include "IEventJob.h"
#include "CArch.h"
#include "XArch.h"
@ -31,7 +27,10 @@
// CTCPSocket
//
CTCPSocket::CTCPSocket()
CTCPSocket::CTCPSocket() :
m_mutex(),
m_flushed(&m_mutex, true),
m_eventFilter(NULL)
{
try {
m_socket = ARCH->newSocket(IArchNetwork::kINET, IArchNetwork::kSTREAM);
@ -39,34 +38,32 @@ CTCPSocket::CTCPSocket()
catch (XArchNetwork& e) {
throw XSocketCreate(e.what());
}
init();
}
CTCPSocket::CTCPSocket(CArchSocket socket) :
m_socket(socket)
m_mutex(),
m_socket(socket),
m_flushed(&m_mutex, true),
m_eventFilter(NULL)
{
assert(m_socket != NULL);
// socket starts in connected state
init();
setState(kReadWrite, true);
onConnected();
setJob(newJob());
}
CTCPSocket::~CTCPSocket()
{
try {
if (m_socket != NULL) {
close();
}
close();
}
catch (...) {
// ignore
}
// clean up
delete m_input;
delete m_output;
delete m_mutex;
}
void
@ -86,92 +83,202 @@ CTCPSocket::bind(const CNetworkAddress& addr)
void
CTCPSocket::close()
{
// flush buffers
m_output->flush();
CLock lock(&m_mutex);
// now closed
setState(kClosed, true);
// clear buffers and enter disconnected state
if (m_connected) {
sendSocketEvent(getDisconnectedEvent());
}
onDisconnected();
// close buffers
try {
m_input->close();
}
catch (...) {
// ignore
}
try {
m_output->close();
}
catch (...) {
// ignore
}
// remove ourself from the multiplexer
setJob(NULL);
// close socket
CLock lock(m_mutex);
// close the socket
if (m_socket != NULL) {
CArchSocket socket = m_socket;
m_socket = NULL;
try {
ARCH->closeSocket(m_socket);
m_socket = NULL;
ARCH->closeSocket(socket);
}
catch (XArchNetwork& e) {
throw XSocketIOClose(e.what());
// FIXME -- just discard this for now
//throw XSocketIOClose(e.what());
}
}
}
void
CTCPSocket::setEventTarget(void* target)
void*
CTCPSocket::getEventTarget() const
{
CLock lock(m_mutex);
m_target = target;
return const_cast<void*>(reinterpret_cast<const void*>(this));
}
UInt32
CTCPSocket::read(void* buffer, UInt32 n)
{
// copy data directly from our input buffer
CLock lock(&m_mutex);
UInt32 size = m_inputBuffer.getSize();
if (n > size) {
n = size;
}
if (buffer != NULL) {
memcpy(buffer, m_inputBuffer.peek(n), n);
}
m_inputBuffer.pop(n);
// if no more data and we cannot read or write then send disconnected
if (n > 0 && !m_readable && !m_writable) {
sendSocketEvent(getDisconnectedEvent());
}
return n;
}
void
CTCPSocket::write(const void* buffer, UInt32 n)
{
CLock lock(&m_mutex);
// must not have shutdown output
if (!m_writable) {
sendStreamEvent(getOutputErrorEvent());
return;
}
// ignore empty writes
if (n == 0) {
return;
}
// copy data to the output buffer
bool wasEmpty = (m_outputBuffer.getSize() == 0);
m_outputBuffer.write(buffer, n);
// there's data to write
m_flushed = false;
// make sure we're waiting to write
if (wasEmpty) {
setJob(newJob());
}
}
void
CTCPSocket::flush()
{
CLock lock(&m_mutex);
while (m_flushed == false) {
m_flushed.wait();
}
}
void
CTCPSocket::shutdownInput()
{
CLock lock(&m_mutex);
// shutdown socket for reading
try {
ARCH->closeSocketForRead(m_socket);
}
catch (XArchNetwork&) {
// ignore
}
// shutdown buffer for reading
if (m_readable) {
sendStreamEvent(getInputShutdownEvent());
onInputShutdown();
setJob(newJob());
}
}
void
CTCPSocket::shutdownOutput()
{
CLock lock(&m_mutex);
// shutdown socket for writing
try {
ARCH->closeSocketForWrite(m_socket);
}
catch (XArchNetwork&) {
// ignore
}
// shutdown buffer for writing
if (m_writable) {
sendStreamEvent(getOutputShutdownEvent());
onOutputShutdown();
setJob(newJob());
}
}
void
CTCPSocket::setEventFilter(IEventJob* filter)
{
CLock lock(&m_mutex);
m_eventFilter = filter;
}
bool
CTCPSocket::isReady() const
{
CLock lock(&m_mutex);
return (m_inputBuffer.getSize() > 0);
}
UInt32
CTCPSocket::getSize() const
{
CLock lock(&m_mutex);
return m_inputBuffer.getSize();
}
IEventJob*
CTCPSocket::getEventFilter() const
{
CLock lock(&m_mutex);
return m_eventFilter;
}
void
CTCPSocket::connect(const CNetworkAddress& addr)
{
CLock lock(&m_mutex);
// fail on attempts to reconnect
if (m_socket == NULL || m_connected) {
sendSocketEvent(getConnectionFailedEvent());
return;
}
try {
// FIXME -- don't throw if in progress, just return that info
ARCH->connectSocket(m_socket, addr.getAddress());
setState(kReadWrite, true);
sendSocketEvent(getConnectedEvent());
onConnected();
setJob(newJob());
}
catch (XArchNetworkConnecting&) {
// connection is in progress
setState(kConnecting, true);
m_writable = true;
setJob(newJob());
}
catch (XArchNetwork& e) {
throw XSocketConnect(e.what());
}
}
IInputStream*
CTCPSocket::getInputStream()
{
return m_input;
}
IOutputStream*
CTCPSocket::getOutputStream()
{
return m_output;
}
void
CTCPSocket::init()
{
m_mutex = new CMutex;
m_input = new CBufferedInputStream(m_mutex,
new TMethodJob<CTCPSocket>(
this, &CTCPSocket::emptyInput),
new TMethodJob<CTCPSocket>(
this, &CTCPSocket::closeInput));
m_output = new CBufferedOutputStream(m_mutex,
new TMethodJob<CTCPSocket>(
this, &CTCPSocket::fillOutput),
new TMethodJob<CTCPSocket>(
this, &CTCPSocket::closeOutput));
m_state = kUnconnected;
m_target = NULL;
m_job = NULL;
// default state
m_connected = false;
m_readable = false;
m_writable = false;
// make socket non-blocking
// FIXME -- check for error
@ -196,172 +303,97 @@ CTCPSocket::init()
}
}
ISocketMultiplexerJob*
CTCPSocket::newMultiplexerJob(JobFunc func, bool readable, bool writable)
{
return new TSocketMultiplexerMethodJob<CTCPSocket>(
this, func, m_socket, readable, writable);
}
ISocketMultiplexerJob*
CTCPSocket::setState(State state, bool setJob)
{
if (m_state == state || m_state == kClosed) {
return m_job;
}
State oldState = m_state;
m_state = state;
bool read = (m_input->getSize() > 0);
bool write = (m_output->getSize() > 0);
CEvent::Type eventType = 0;
m_job = NULL;
switch (m_state) {
case kUnconnected:
assert(0 && "cannot re-enter unconnected state");
break;
case kConnecting:
m_job = newMultiplexerJob(&CTCPSocket::serviceConnecting, false, true);
break;
case kReadWrite:
if (oldState == kConnecting) {
eventType = IDataSocket::getConnectedEvent();
}
m_job = newMultiplexerJob(&CTCPSocket::serviceConnected, true, write);
break;
case kReadOnly:
if (!write) {
eventType = IDataSocket::getShutdownOutputEvent();
}
if (oldState == kWriteOnly) {
goto shutdown;
}
m_job = newMultiplexerJob(&CTCPSocket::serviceConnected, true, write);
break;
case kWriteOnly:
if (!read) {
m_input->hangup();
eventType = IDataSocket::getShutdownInputEvent();
}
if (oldState == kReadOnly) {
goto shutdown;
}
m_job = newMultiplexerJob(&CTCPSocket::serviceConnected, false, write);
break;
case kShutdown:
shutdown:
if (!read && !write) {
eventType = ISocket::getDisconnectedEvent();
m_state = kClosed;
}
else {
m_state = kShutdown;
}
break;
case kClosed:
m_input->hangup();
if (oldState == kConnecting) {
eventType = IDataSocket::getConnectionFailedEvent();
}
else {
eventType = ISocket::getDisconnectedEvent();
}
break;
}
// notify
if (eventType != 0) {
sendEvent(eventType);
}
// cut over to new job. multiplexer will delete the old job.
if (setJob) {
if (m_job == NULL) {
CSocketMultiplexer::getInstance()->removeSocket(this);
}
else {
CSocketMultiplexer::getInstance()->addSocket(this, m_job);
}
}
return m_job;
}
void
CTCPSocket::closeInput(void*)
CTCPSocket::setJob(ISocketMultiplexerJob* job)
{
// note -- m_mutex should already be locked
try {
ARCH->closeSocketForRead(m_socket);
setState(kWriteOnly, true);
}
catch (XArchNetwork&) {
// ignore
}
}
void
CTCPSocket::closeOutput(void*)
{
// note -- m_mutex should already be locked
try {
// ARCH->closeSocketForWrite(m_socket);
setState(kReadOnly, true);
}
catch (XArchNetwork&) {
// ignore
}
}
void
CTCPSocket::emptyInput(void*)
{
// note -- m_mutex should already be locked
bool write = (m_output->getSize() > 0);
if (m_state == kWriteOnly && !write) {
m_state = kShutdown;
}
if (m_state == kWriteOnly) {
m_job = newMultiplexerJob(&CTCPSocket::serviceConnected, false, write);
CSocketMultiplexer::getInstance()->addSocket(this, m_job);
m_input->hangup();
sendEvent(IDataSocket::getShutdownInputEvent());
}
else if (m_state == kShutdown) {
m_job = NULL;
// multiplexer will delete the old job
if (job == NULL) {
CSocketMultiplexer::getInstance()->removeSocket(this);
if (!write) {
sendEvent(ISocket::getDisconnectedEvent());
m_state = kClosed;
}
}
else {
CSocketMultiplexer::getInstance()->addSocket(this, job);
}
}
ISocketMultiplexerJob*
CTCPSocket::newJob()
{
// note -- must have m_mutex locked on entry
if (m_socket == NULL || !(m_readable || m_writable)) {
return NULL;
}
else if (!m_connected) {
assert(!m_readable);
return new TSocketMultiplexerMethodJob<CTCPSocket>(
this, &CTCPSocket::serviceConnecting,
m_socket, m_readable, m_writable);
}
else {
return new TSocketMultiplexerMethodJob<CTCPSocket>(
this, &CTCPSocket::serviceConnected,
m_socket, m_readable,
m_writable && (m_outputBuffer.getSize() > 0));
}
}
void
CTCPSocket::fillOutput(void*)
CTCPSocket::sendSocketEvent(CEvent::Type type)
{
// note -- m_mutex should already be locked
if (m_state == kReadWrite) {
m_job = newMultiplexerJob(&CTCPSocket::serviceConnected, true, true);
CSocketMultiplexer::getInstance()->addSocket(this, m_job);
EVENTQUEUE->addEvent(CEvent(type, this, NULL));
}
void
CTCPSocket::sendStreamEvent(CEvent::Type type)
{
if (m_eventFilter != NULL) {
m_eventFilter->run(CEvent(type, this, NULL));
}
else if (m_state == kWriteOnly) {
m_job = newMultiplexerJob(&CTCPSocket::serviceConnected, false, true);
CSocketMultiplexer::getInstance()->addSocket(this, m_job);
else {
EVENTQUEUE->addEvent(CEvent(type, this, NULL));
}
}
void
CTCPSocket::onConnected()
{
m_connected = true;
m_readable = true;
m_writable = true;
}
void
CTCPSocket::onInputShutdown()
{
m_inputBuffer.pop(m_inputBuffer.getSize());
m_readable = false;
}
void
CTCPSocket::onOutputShutdown()
{
m_outputBuffer.pop(m_outputBuffer.getSize());
m_writable = false;
// we're now flushed
m_flushed = true;
m_flushed.broadcast();
}
void
CTCPSocket::onDisconnected()
{
// disconnected
onInputShutdown();
onOutputShutdown();
m_connected = false;
}
ISocketMultiplexerJob*
CTCPSocket::serviceConnecting(ISocketMultiplexerJob* job,
bool, bool write, bool error)
{
CLock lock(m_mutex);
CLock lock(&m_mutex);
if (write && !error) {
try {
@ -374,11 +406,15 @@ CTCPSocket::serviceConnecting(ISocketMultiplexerJob* job,
}
if (error) {
return setState(kClosed, false);
sendSocketEvent(getConnectionFailedEvent());
onDisconnected();
return newJob();
}
if (write) {
return setState(kReadWrite, false);
sendSocketEvent(getConnectedEvent());
onConnected();
return newJob();
}
return job;
@ -388,79 +424,99 @@ ISocketMultiplexerJob*
CTCPSocket::serviceConnected(ISocketMultiplexerJob* job,
bool read, bool write, bool error)
{
CLock lock(m_mutex);
CLock lock(&m_mutex);
if (error) {
return setState(kClosed, false);
sendSocketEvent(getDisconnectedEvent());
onDisconnected();
return newJob();
}
if (write) {
// get amount of data to write
UInt32 n = m_output->getSize();
bool needNewJob = false;
// write data
if (write) {
try {
const void* buffer = m_output->peek(n);
size_t n2 = ARCH->writeSocket(m_socket, buffer, n);
// write data
UInt32 n = m_outputBuffer.getSize();
const void* buffer = m_outputBuffer.peek(n);
n = (UInt32)ARCH->writeSocket(m_socket, buffer, n);
// discard written data
if (n2 > 0) {
m_output->pop(n2);
if (n > 0) {
m_outputBuffer.pop(n);
if (m_outputBuffer.getSize() == 0) {
sendStreamEvent(getOutputFlushedEvent());
m_flushed = true;
m_flushed.broadcast();
needNewJob = true;
}
}
}
catch (XArchNetworkShutdown&) {
// remote read end of stream hungup. our output side
// has therefore shutdown.
onOutputShutdown();
sendStreamEvent(getOutputShutdownEvent());
if (!m_readable && m_inputBuffer.getSize() == 0) {
sendSocketEvent(getDisconnectedEvent());
}
needNewJob = true;
}
catch (XArchNetworkDisconnected&) {
// stream hungup
onDisconnected();
sendSocketEvent(getDisconnectedEvent());
needNewJob = true;
}
catch (XArchNetwork&) {
// other write error
onDisconnected();
sendStreamEvent(getOutputErrorEvent());
sendSocketEvent(getDisconnectedEvent());
needNewJob = true;
}
}
if (read && m_readable) {
try {
UInt8 buffer[4096];
size_t n = ARCH->readSocket(m_socket, buffer, sizeof(buffer));
if (n > 0) {
bool wasEmpty = (m_inputBuffer.getSize() == 0);
// slurp up as much as possible
do {
m_inputBuffer.write(buffer, n);
n = ARCH->readSocket(m_socket, buffer, sizeof(buffer));
} while (n > 0);
// send input ready if input buffer was empty
if (wasEmpty) {
sendStreamEvent(getInputReadyEvent());
}
}
else {
// remote write end of stream hungup. our input side
// has therefore shutdown but don't flush our buffer
// since there's still data to be read.
sendStreamEvent(getInputShutdownEvent());
if (!m_writable && m_inputBuffer.getSize() == 0) {
sendSocketEvent(getDisconnectedEvent());
}
m_readable = false;
needNewJob = true;
}
}
catch (XArchNetworkDisconnected&) {
// stream hungup
return setState(kReadOnly, false);
sendSocketEvent(getDisconnectedEvent());
onDisconnected();
needNewJob = true;
}
catch (XArchNetwork&) {
// ignore other read error
}
}
if (read) {
UInt8 buffer[4096];
size_t n = ARCH->readSocket(m_socket, buffer, sizeof(buffer));
if (n > 0) {
// slurp up as much as possible
do {
m_input->write(buffer, n);
try {
n = ARCH->readSocket(m_socket, buffer, sizeof(buffer));
}
catch (XArchNetworkWouldBlock&) {
break;
}
} while (n > 0);
// notify
sendEvent(IDataSocket::getInputEvent());
}
else {
// stream hungup
return setState(kWriteOnly, false);
}
}
if (write && m_output->getSize() == 0) {
if (m_state == kReadOnly) {
ARCH->closeSocketForWrite(m_socket);
sendEvent(IDataSocket::getShutdownOutputEvent());
m_job = newMultiplexerJob(&CTCPSocket::serviceConnected,
true, false);
job = m_job;
}
else if (m_state == kReadWrite || m_state == kReadOnly) {
m_job = newMultiplexerJob(&CTCPSocket::serviceConnected,
true, false);
job = m_job;
}
else if (m_state == kWriteOnly) {
m_job = NULL;
job = m_job;
}
}
return job;
}
void
CTCPSocket::sendEvent(CEvent::Type type)
{
CEventQueue::getInstance()->addEvent(CEvent(type, m_target, NULL));
return needNewJob ? newJob() : job;
}

View File

@ -16,14 +16,13 @@
#define CTCPSOCKET_H
#include "IDataSocket.h"
#include "CEvent.h"
#include "BasicTypes.h"
#include "CStreamBuffer.h"
#include "CCondVar.h"
#include "CMutex.h"
#include "IArchNetwork.h"
class CMutex;
class CThread;
class CBufferedInputStream;
class CBufferedOutputStream;
class ISocketMultiplexerJob;
//! TCP data socket
@ -39,33 +38,34 @@ public:
// ISocket overrides
virtual void bind(const CNetworkAddress&);
virtual void close();
virtual void setEventTarget(void*);
virtual void* getEventTarget() const;
// IStream overrides
virtual UInt32 read(void* buffer, UInt32 n);
virtual void write(const void* buffer, UInt32 n);
virtual void flush();
virtual void shutdownInput();
virtual void shutdownOutput();
virtual void setEventFilter(IEventJob* filter);
virtual bool isReady() const;
virtual UInt32 getSize() const;
virtual IEventJob* getEventFilter() const;
// IDataSocket overrides
virtual void connect(const CNetworkAddress&);
virtual IInputStream* getInputStream();
virtual IOutputStream* getOutputStream();
private:
enum State {
kUnconnected,
kConnecting,
kReadWrite,
kReadOnly,
kWriteOnly,
kShutdown,
kClosed
};
void init();
ISocketMultiplexerJob*
setState(State, bool setJob);
void setJob(ISocketMultiplexerJob*);
ISocketMultiplexerJob* newJob();
void sendSocketEvent(CEvent::Type);
void sendStreamEvent(CEvent::Type);
void closeInput(void*);
void closeOutput(void*);
void emptyInput(void*);
void fillOutput(void*);
void onConnected();
void onInputShutdown();
void onOutputShutdown();
void onDisconnected();
ISocketMultiplexerJob*
serviceConnecting(ISocketMultiplexerJob*,
@ -74,25 +74,16 @@ private:
serviceConnected(ISocketMultiplexerJob*,
bool, bool, bool);
typedef ISocketMultiplexerJob* (CTCPSocket::*JobFunc)(
ISocketMultiplexerJob*,
bool, bool, bool);
ISocketMultiplexerJob*
newMultiplexerJob(JobFunc,
bool readable, bool writable);
void sendEvent(CEvent::Type);
private:
CArchSocket m_socket;
CBufferedInputStream* m_input;
CBufferedOutputStream* m_output;
CMutex* m_mutex;
State m_state;
void* m_target;
ISocketMultiplexerJob* m_job;
CMutex m_mutex;
CArchSocket m_socket;
CStreamBuffer m_inputBuffer;
CStreamBuffer m_outputBuffer;
CCondVar<bool> m_flushed;
bool m_connected;
bool m_readable;
bool m_writable;
IEventJob* m_eventFilter;
};
#endif

View File

@ -18,11 +18,8 @@
// IDataSocket
//
CEvent::Type IDataSocket::s_connectedEvent = CEvent::kUnknown;
CEvent::Type IDataSocket::s_failedEvent = CEvent::kUnknown;
CEvent::Type IDataSocket::s_inputEvent = CEvent::kUnknown;
CEvent::Type IDataSocket::s_shutdownInputEvent = CEvent::kUnknown;
CEvent::Type IDataSocket::s_shutdownOutputEvent = CEvent::kUnknown;
CEvent::Type IDataSocket::s_connectedEvent = CEvent::kUnknown;
CEvent::Type IDataSocket::s_failedEvent = CEvent::kUnknown;
CEvent::Type
IDataSocket::getConnectedEvent()
@ -35,21 +32,3 @@ IDataSocket::getConnectionFailedEvent()
{
return CEvent::registerTypeOnce(s_failedEvent);
}
CEvent::Type
IDataSocket::getInputEvent()
{
return CEvent::registerTypeOnce(s_inputEvent);
}
CEvent::Type
IDataSocket::getShutdownInputEvent()
{
return CEvent::registerTypeOnce(s_shutdownInputEvent);
}
CEvent::Type
IDataSocket::getShutdownOutputEvent()
{
return CEvent::registerTypeOnce(s_shutdownOutputEvent);
}

View File

@ -1,6 +1,6 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
* Copyright (C) 2004 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -16,44 +16,27 @@
#define IDATASOCKET_H
#include "ISocket.h"
class IInputStream;
class IOutputStream;
#include "IStream.h"
//! Data stream socket interface
/*!
This interface defines the methods common to all network sockets that
represent a full-duplex data stream.
*/
class IDataSocket : public ISocket {
class IDataSocket : public ISocket, public IStream {
public:
//! @name manipulators
//@{
//! Connect socket
/*!
Attempt to connect to a remote endpoint. This waits until the
connection is established or fails. If it fails it throws an
XSocketConnect exception.
(cancellation point)
Attempt to connect to a remote endpoint. This returns immediately
and sends a connected event when successful or a connection failed
event when it fails. The stream acts as if shutdown for input and
output until the stream connects.
*/
virtual void connect(const CNetworkAddress&) = 0;
//! Get input stream
/*!
Returns the input stream for reading from the socket. Closing this
stream will shutdown the socket for reading.
*/
virtual IInputStream* getInputStream() = 0;
//! Get output stream
/*!
Returns the output stream for writing to the socket. Closing this
stream will shutdown the socket for writing.
*/
virtual IOutputStream* getOutputStream() = 0;
//@}
//! @name accessors
//@{
@ -72,42 +55,27 @@ public:
*/
static CEvent::Type getConnectionFailedEvent();
//! Get input event type
/*!
Returns the socket input event type. A socket sends this
event when data is available to read from the input stream.
*/
static CEvent::Type getInputEvent();
//! Get shutdown input event type
/*!
Returns the socket shutdown input event type. A socket sends this
event when the remote side of the connection has shutdown for
writing and there is no more data to read from the socket.
*/
static CEvent::Type getShutdownInputEvent();
//! Get shutdown input event type
/*!
Returns the socket shutdown input event type. A socket sends this
event when the remote side of the connection has shutdown for
writing and there is no more data to read from the socket.
*/
static CEvent::Type getShutdownOutputEvent();
//@}
// ISocket overrides
virtual void bind(const CNetworkAddress&) = 0;
virtual void close() = 0;
virtual void setEventTarget(void*) = 0;
virtual void* getEventTarget() const = 0;
// IStream overrides
virtual UInt32 read(void* buffer, UInt32 n) = 0;
virtual void write(const void* buffer, UInt32 n) = 0;
virtual void flush() = 0;
virtual void shutdownInput() = 0;
virtual void shutdownOutput() = 0;
virtual void setEventFilter(IEventJob* filter) = 0;
virtual bool isReady() const = 0;
virtual UInt32 getSize() const = 0;
virtual IEventJob* getEventFilter() const = 0;
private:
static CEvent::Type s_connectedEvent;
static CEvent::Type s_failedEvent;
static CEvent::Type s_inputEvent;
static CEvent::Type s_shutdownInputEvent;
static CEvent::Type s_shutdownOutputEvent;
};
#endif

View File

@ -53,7 +53,7 @@ public:
// ISocket overrides
virtual void bind(const CNetworkAddress&) = 0;
virtual void close() = 0;
virtual void setEventTarget(void*) = 0;
virtual void* getEventTarget() const = 0;
private:
static CEvent::Type s_connectingEvent;

View File

@ -23,6 +23,7 @@ class CNetworkAddress;
//! Generic socket interface
/*!
This interface defines the methods common to all network sockets.
Generated events use \c this as the target.
*/
class ISocket : public IInterface {
public:
@ -41,21 +42,21 @@ public:
*/
virtual void close() = 0;
//! Set the socket's event target
/*!
Sets the target of any events sent by the socket. The default is NULL.
*/
virtual void setEventTarget(void*) = 0;
//@}
//! @name accessors
//@{
//! Get event target
/*!
Returns the event target for events generated by this socket.
*/
virtual void* getEventTarget() const = 0;
//! Get disconnected event type
/*!
Returns the socket disconnected event type. A socket sends this
event when the remote side of the socket has disconnected or
shutdown.
shutdown both input and output.
*/
static CEvent::Type getDisconnectedEvent();

View File

@ -0,0 +1,75 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2004 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef ISOCKETMULTIPLEXERJOB_H
#define ISOCKETMULTIPLEXERJOB_H
#include "IArchNetwork.h"
#include "IInterface.h"
//! Socket multiplexer job
/*!
A socket multiplexer job handles events on a socket.
*/
class ISocketMultiplexerJob : public IInterface {
public:
//! @name manipulators
//@{
//! Handle socket event
/*!
Called by a socket multiplexer when the socket becomes readable,
writable, or has an error. It should return itself if the same
job can continue to service events, a new job if the socket must
be serviced differently, or NULL if the socket should no longer
be serviced. The socket is readable if \p readable is true,
writable if \p writable is true, and in error if \p error is
true.
This call must not attempt to directly change the job for this
socket by calling \c addSocket() or \c removeSocket() on the
multiplexer. It must instead return the new job. It can,
however, add or remove jobs for other sockets.
*/
virtual ISocketMultiplexerJob*
run(bool readable, bool writable, bool error) = 0;
//@}
//! @name accessors
//@{
//! Get the socket
/*!
Return the socket to multiplex
*/
virtual CArchSocket getSocket() const = 0;
//! Check for interest in readability
/*!
Return true if the job is interested in being run if the socket
becomes readable.
*/
virtual bool isReadable() const = 0;
//! Check for interest in writability
/*!
Return true if the job is interested in being run if the socket
becomes writable.
*/
virtual bool isWritable() const = 0;
//@}
};
#endif

View File

@ -0,0 +1,108 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2004 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef TSOCKERMULTIPLEXERMETHODJOB_H
#define TSOCKERMULTIPLEXERMETHODJOB_H
#include "ISocketMultiplexerJob.h"
#include "CArch.h"
//! Use a method as a socket multiplexer job
/*!
A socket multiplexer job class that invokes a member function.
*/
template <class T>
class TSocketMultiplexerMethodJob : public ISocketMultiplexerJob {
public:
typedef ISocketMultiplexerJob*
(T::*Method)(ISocketMultiplexerJob*, bool, bool, bool);
//! run() invokes \c object->method(arg)
TSocketMultiplexerMethodJob(T* object, Method method,
CArchSocket socket, bool readable, bool writeable);
virtual ~TSocketMultiplexerMethodJob();
// IJob overrides
virtual ISocketMultiplexerJob*
run(bool readable, bool writable, bool error);
virtual CArchSocket getSocket() const;
virtual bool isReadable() const;
virtual bool isWritable() const;
private:
T* m_object;
Method m_method;
CArchSocket m_socket;
bool m_readable;
bool m_writable;
void* m_arg;
};
template <class T>
inline
TSocketMultiplexerMethodJob<T>::TSocketMultiplexerMethodJob(T* object,
Method method, CArchSocket socket,
bool readable, bool writable) :
m_object(object),
m_method(method),
m_socket(ARCH->copySocket(socket)),
m_readable(readable),
m_writable(writable)
{
// do nothing
}
template <class T>
inline
TSocketMultiplexerMethodJob<T>::~TSocketMultiplexerMethodJob()
{
ARCH->closeSocket(m_socket);
}
template <class T>
inline
ISocketMultiplexerJob*
TSocketMultiplexerMethodJob<T>::run(bool read, bool write, bool error)
{
if (m_object != NULL) {
return (m_object->*m_method)(this, read, write, error);
}
return NULL;
}
template <class T>
inline
CArchSocket
TSocketMultiplexerMethodJob<T>::getSocket() const
{
return m_socket;
}
template <class T>
inline
bool
TSocketMultiplexerMethodJob<T>::isReadable() const
{
return m_readable;
}
template <class T>
inline
bool
TSocketMultiplexerMethodJob<T>::isWritable() const
{
return m_writable;
}
#endif

View File

@ -13,27 +13,24 @@
*/
#include "CClientProxy.h"
#include "IInputStream.h"
#include "IOutputStream.h"
#include "IStream.h"
//
// CClientProxy
//
CClientProxy::CClientProxy(IServer* server, const CString& name,
IInputStream* input, IOutputStream* output) :
CClientProxy::CClientProxy(IServer* server,
const CString& name, IStream* stream) :
m_server(server),
m_name(name),
m_input(input),
m_output(output)
m_stream(stream)
{
// do nothing
}
CClientProxy::~CClientProxy()
{
delete m_output;
delete m_input;
delete m_stream;
}
IServer*
@ -42,16 +39,10 @@ CClientProxy::getServer() const
return m_server;
}
IInputStream*
CClientProxy::getInputStream() const
IStream*
CClientProxy::getStream() const
{
return m_input;
}
IOutputStream*
CClientProxy::getOutputStream() const
{
return m_output;
return m_stream;
}
CString

View File

@ -19,8 +19,7 @@
#include "CMutex.h"
#include "CString.h"
class IInputStream;
class IOutputStream;
class IStream;
class IServer;
//! Generic proxy for client
@ -29,9 +28,7 @@ public:
/*!
\c name is the name of the client.
*/
CClientProxy(IServer* server, const CString& name,
IInputStream* adoptedInput,
IOutputStream* adoptedOutput);
CClientProxy(IServer* server, const CString& name, IStream* adoptedStream);
~CClientProxy();
//! @name accessors
@ -43,17 +40,11 @@ public:
*/
IServer* getServer() const;
//! Get input stream
//! Get stream
/*!
Returns the input stream passed to the c'tor.
Returns the stream passed to the c'tor.
*/
IInputStream* getInputStream() const;
//! Get output stream
/*!
Returns the output stream passed to the c'tor.
*/
IOutputStream* getOutputStream() const;
IStream* getStream() const;
//@}
@ -98,8 +89,7 @@ private:
CMutex m_mutex;
IServer* m_server;
CString m_name;
IInputStream* m_input;
IOutputStream* m_output;
IStream* m_stream;
};
#endif

View File

@ -17,46 +17,189 @@
#include "CClipboard.h"
#include "CProtocolUtil.h"
#include "XSynergy.h"
#include "IInputStream.h"
#include "IOutputStream.h"
#include "IStream.h"
#include "CLock.h"
#include "CThread.h"
#include "CLog.h"
#include "CStopwatch.h"
#include "IEventQueue.h"
#include "TMethodEventJob.h"
#include <cstring>
//
// CClientProxy1_0
//
CClientProxy1_0::CClientProxy1_0(IServer* server, const CString& name,
IInputStream* input, IOutputStream* output) :
CClientProxy(server, name, input, output),
m_heartRate(kHeartRate),
m_heartDeath(kHeartRate * kHeartBeatsUntilDeath)
CClientProxy1_0::CClientProxy1_0(IServer* server,
const CString& name, IStream* stream) :
CClientProxy(server, name, stream),
m_heartbeatAlarm(kHeartRate * kHeartBeatsUntilDeath),
m_heartbeatTimer(NULL)
{
for (UInt32 i = 0; i < kClipboardEnd; ++i) {
m_clipboardDirty[i] = true;
}
// install event handlers
EVENTQUEUE->adoptHandler(IStream::getInputReadyEvent(),
stream->getEventTarget(),
new TMethodEventJob<CClientProxy1_0>(this,
&CClientProxy1_0::handleData, NULL));
EVENTQUEUE->adoptHandler(IStream::getOutputErrorEvent(),
stream->getEventTarget(),
new TMethodEventJob<CClientProxy1_0>(this,
&CClientProxy1_0::handleWriteError, NULL));
EVENTQUEUE->adoptHandler(IStream::getInputShutdownEvent(),
stream->getEventTarget(),
new TMethodEventJob<CClientProxy1_0>(this,
&CClientProxy1_0::handleDisconnect, NULL));
EVENTQUEUE->adoptHandler(IStream::getOutputShutdownEvent(),
stream->getEventTarget(),
new TMethodEventJob<CClientProxy1_0>(this,
&CClientProxy1_0::handleWriteError, NULL));
EVENTQUEUE->adoptHandler(CEvent::kTimer, this,
new TMethodEventJob<CClientProxy1_0>(this,
&CClientProxy1_0::handleFlatline, NULL));
// FIXME -- open() replacement must install initial heartbeat timer
}
CClientProxy1_0::~CClientProxy1_0()
{
// do nothing
removeHandlers();
}
void
CClientProxy1_0::disconnect()
{
CLock lock(getMutex());
removeHandlers();
// FIXME -- send disconnect event (server should be listening for this)
getStream()->flush();
getStream()->close();
}
void
CClientProxy1_0::removeHandlers()
{
// uninstall event handlers
EVENTQUEUE->removeHandler(IStream::getInputReadyEvent(),
getStream()->getEventTarget());
EVENTQUEUE->removeHandler(IStream::getOutputErrorEvent(),
getStream()->getEventTarget());
EVENTQUEUE->removeHandler(IStream::getInputShutdownEvent(),
getStream()->getEventTarget());
EVENTQUEUE->removeHandler(IStream::getOutputShutdownEvent(),
getStream()->getEventTarget());
EVENTQUEUE->removeHandler(CEvent::kTimer, this);
// remove timer
removeHeartbeatTimer();
}
void
CClientProxy1_0::addHeartbeatTimer()
{
CLock lock(getMutex());
if (m_heartbeatAlarm > 0.0) {
m_heartbeatTimer = EVENTQUEUE->newOneShotTimer(m_heartbeatAlarm, this);
}
}
void
CClientProxy1_0::removeHeartbeatTimer()
{
CLock lock(getMutex());
if (m_heartbeatTimer != NULL) {
EVENTQUEUE->deleteTimer(m_heartbeatTimer);
m_heartbeatTimer = NULL;
}
}
void
CClientProxy1_0::handleData(const CEvent&, void*)
{
// handle messages until there are no more. first read message code.
UInt8 code[4];
UInt32 n = getStream()->read(code, 4);
while (n != 0) {
// verify we got an entire code
if (n != 4) {
LOG((CLOG_ERR "incomplete message from \"%s\": %d bytes", getName().c_str(), n));
disconnect();
return;
}
// parse message
LOG((CLOG_DEBUG2 "msg from \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3]));
if (!parseMessage(code)) {
LOG((CLOG_ERR "invalid message from client \"%s\"", getName().c_str()));
disconnect();
return;
}
// next message
n = getStream()->read(code, 4);
}
// restart heartbeat timer
removeHeartbeatTimer();
addHeartbeatTimer();
}
bool
CClientProxy1_0::parseMessage(const UInt8* code)
{
if (memcmp(code, kMsgDInfo, 4) == 0) {
return recvInfo(true);
}
else if (memcmp(code, kMsgCNoop, 4) == 0) {
// discard no-ops
LOG((CLOG_DEBUG2 "no-op from", getName().c_str()));
return true;
}
else if (memcmp(code, kMsgCClipboard, 4) == 0) {
return recvGrabClipboard();
}
else if (memcmp(code, kMsgDClipboard, 4) == 0) {
return recvClipboard();
}
return false;
}
void
CClientProxy1_0::handleDisconnect(const CEvent&, void*)
{
LOG((CLOG_NOTE "client \"%s\" disconnected", getName().c_str()));
disconnect();
}
void
CClientProxy1_0::handleWriteError(const CEvent&, void*)
{
LOG((CLOG_ERR "error writing to client \"%s\"", getName().c_str()));
disconnect();
}
void
CClientProxy1_0::handleFlatline(const CEvent&, void*)
{
// didn't get a heartbeat fast enough. assume client is dead.
LOG((CLOG_NOTE "client \"%s\" is dead", getName().c_str()));
disconnect();
}
// FIXME -- replace this
void
CClientProxy1_0::open()
{
// send request
LOG((CLOG_DEBUG1 "querying client \"%s\" info", getName().c_str()));
CProtocolUtil::writef(getOutputStream(), kMsgQInfo);
getOutputStream()->flush();
CProtocolUtil::writef(getStream(), kMsgQInfo);
getStream()->flush();
// wait for and verify reply
UInt8 code[4];
for (;;) {
UInt32 n = getInputStream()->read(code, 4, -1.0);
UInt32 n = getStream()->read(code, 4);
if (n == 4) {
if (memcmp(code, kMsgCNoop, 4) == 0) {
// discard heartbeats
@ -73,79 +216,15 @@ CClientProxy1_0::open()
recvInfo(false);
}
void
CClientProxy1_0::mainLoop()
{
// handle messages until the client hangs up or stops sending heartbeats
CStopwatch heartTimer;
for (;;) {
CThread::testCancel();
// wait for a message
UInt8 code[4];
UInt32 n = getInputStream()->read(code, 4, m_heartRate);
CThread::testCancel();
// check if client hungup
if (n == 0) {
LOG((CLOG_NOTE "client \"%s\" disconnected", getName().c_str()));
return;
}
// check if client has stopped sending heartbeats
if (n == (UInt32)-1) {
if (m_heartDeath >= 0.0 && heartTimer.getTime() > m_heartDeath) {
LOG((CLOG_NOTE "client \"%s\" is dead", getName().c_str()));
return;
}
continue;
}
// got a message so reset heartbeat monitor
heartTimer.reset();
// verify we got an entire code
if (n != 4) {
LOG((CLOG_ERR "incomplete message from \"%s\": %d bytes", getName().c_str(), n));
// client sent an incomplete message
throw XBadClient();
}
// parse message
LOG((CLOG_DEBUG2 "msg from \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3]));
if (memcmp(code, kMsgDInfo, 4) == 0) {
recvInfo(true);
}
else if (memcmp(code, kMsgCNoop, 4) == 0) {
// discard no-ops
LOG((CLOG_DEBUG2 "no-op from", getName().c_str()));
continue;
}
else if (memcmp(code, kMsgCClipboard, 4) == 0) {
recvGrabClipboard();
}
else if (memcmp(code, kMsgDClipboard, 4) == 0) {
recvClipboard();
}
// note -- more message handlers go here
else {
LOG((CLOG_ERR "invalid message from client \"%s\"", getName().c_str()));
// unknown message
throw XBadClient();
}
}
}
// FIXME -- replace this
void
CClientProxy1_0::close()
{
LOG((CLOG_DEBUG1 "send close to \"%s\"", getName().c_str()));
CProtocolUtil::writef(getOutputStream(), kMsgCClose);
CProtocolUtil::writef(getStream(), kMsgCClose);
// force the close to be sent before we return
getOutputStream()->flush();
getStream()->flush();
}
void
@ -153,7 +232,7 @@ CClientProxy1_0::enter(SInt32 xAbs, SInt32 yAbs,
UInt32 seqNum, KeyModifierMask mask, bool)
{
LOG((CLOG_DEBUG1 "send enter to \"%s\", %d,%d %d %04x", getName().c_str(), xAbs, yAbs, seqNum, mask));
CProtocolUtil::writef(getOutputStream(), kMsgCEnter,
CProtocolUtil::writef(getStream(), kMsgCEnter,
xAbs, yAbs, seqNum, mask);
}
@ -161,7 +240,7 @@ bool
CClientProxy1_0::leave()
{
LOG((CLOG_DEBUG1 "send leave to \"%s\"", getName().c_str()));
CProtocolUtil::writef(getOutputStream(), kMsgCLeave);
CProtocolUtil::writef(getStream(), kMsgCLeave);
// we can never prevent the user from leaving
return true;
@ -177,7 +256,7 @@ CClientProxy1_0::setClipboard(ClipboardID id, const CString& data)
m_clipboardDirty[id] = false;
LOG((CLOG_DEBUG "send clipboard %d to \"%s\" size=%d", id, getName().c_str(), data.size()));
CProtocolUtil::writef(getOutputStream(), kMsgDClipboard, id, 0, &data);
CProtocolUtil::writef(getStream(), kMsgDClipboard, id, 0, &data);
}
}
@ -185,7 +264,7 @@ void
CClientProxy1_0::grabClipboard(ClipboardID id)
{
LOG((CLOG_DEBUG "send grab clipboard %d to \"%s\"", id, getName().c_str()));
CProtocolUtil::writef(getOutputStream(), kMsgCClipboard, id, 0);
CProtocolUtil::writef(getStream(), kMsgCClipboard, id, 0);
// this clipboard is now dirty
CLock lock(getMutex());
@ -203,7 +282,7 @@ void
CClientProxy1_0::keyDown(KeyID key, KeyModifierMask mask, KeyButton)
{
LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x", getName().c_str(), key, mask));
CProtocolUtil::writef(getOutputStream(), kMsgDKeyDown1_0, key, mask);
CProtocolUtil::writef(getStream(), kMsgDKeyDown1_0, key, mask);
}
void
@ -211,78 +290,81 @@ CClientProxy1_0::keyRepeat(KeyID key, KeyModifierMask mask,
SInt32 count, KeyButton)
{
LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d", getName().c_str(), key, mask, count));
CProtocolUtil::writef(getOutputStream(), kMsgDKeyRepeat1_0, key, mask, count);
CProtocolUtil::writef(getStream(), kMsgDKeyRepeat1_0, key, mask, count);
}
void
CClientProxy1_0::keyUp(KeyID key, KeyModifierMask mask, KeyButton)
{
LOG((CLOG_DEBUG1 "send key up to \"%s\" id=%d, mask=0x%04x", getName().c_str(), key, mask));
CProtocolUtil::writef(getOutputStream(), kMsgDKeyUp1_0, key, mask);
CProtocolUtil::writef(getStream(), kMsgDKeyUp1_0, key, mask);
}
void
CClientProxy1_0::mouseDown(ButtonID button)
{
LOG((CLOG_DEBUG1 "send mouse down to \"%s\" id=%d", getName().c_str(), button));
CProtocolUtil::writef(getOutputStream(), kMsgDMouseDown, button);
CProtocolUtil::writef(getStream(), kMsgDMouseDown, button);
}
void
CClientProxy1_0::mouseUp(ButtonID button)
{
LOG((CLOG_DEBUG1 "send mouse up to \"%s\" id=%d", getName().c_str(), button));
CProtocolUtil::writef(getOutputStream(), kMsgDMouseUp, button);
CProtocolUtil::writef(getStream(), kMsgDMouseUp, button);
}
void
CClientProxy1_0::mouseMove(SInt32 xAbs, SInt32 yAbs)
{
LOG((CLOG_DEBUG2 "send mouse move to \"%s\" %d,%d", getName().c_str(), xAbs, yAbs));
CProtocolUtil::writef(getOutputStream(), kMsgDMouseMove, xAbs, yAbs);
CProtocolUtil::writef(getStream(), kMsgDMouseMove, xAbs, yAbs);
}
void
CClientProxy1_0::mouseWheel(SInt32 delta)
{
LOG((CLOG_DEBUG2 "send mouse wheel to \"%s\" %+d", getName().c_str(), delta));
CProtocolUtil::writef(getOutputStream(), kMsgDMouseWheel, delta);
CProtocolUtil::writef(getStream(), kMsgDMouseWheel, delta);
}
void
CClientProxy1_0::screensaver(bool on)
{
LOG((CLOG_DEBUG1 "send screen saver to \"%s\" on=%d", getName().c_str(), on ? 1 : 0));
CProtocolUtil::writef(getOutputStream(), kMsgCScreenSaver, on ? 1 : 0);
CProtocolUtil::writef(getStream(), kMsgCScreenSaver, on ? 1 : 0);
}
void
CClientProxy1_0::resetOptions()
{
LOG((CLOG_DEBUG1 "send reset options to \"%s\"", getName().c_str()));
CProtocolUtil::writef(getOutputStream(), kMsgCResetOptions);
CProtocolUtil::writef(getStream(), kMsgCResetOptions);
// reset heart rate and death
CLock lock(getMutex());
m_heartRate = kHeartRate;
m_heartDeath = kHeartRate * kHeartBeatsUntilDeath;
m_heartbeatAlarm = kHeartRate * kHeartBeatsUntilDeath;
removeHeartbeatTimer();
addHeartbeatTimer();
}
void
CClientProxy1_0::setOptions(const COptionsList& options)
{
LOG((CLOG_DEBUG1 "send set options to \"%s\" size=%d", getName().c_str(), options.size()));
CProtocolUtil::writef(getOutputStream(), kMsgDSetOptions, &options);
CProtocolUtil::writef(getStream(), kMsgDSetOptions, &options);
// check options
CLock lock(getMutex());
for (UInt32 i = 0, n = options.size(); i < n; i += 2) {
if (options[i] == kOptionHeartbeat) {
m_heartRate = 1.0e-3 * static_cast<double>(options[i + 1]);
if (m_heartRate <= 0.0) {
m_heartRate = -1.0;
double rate = 1.0e-3 * static_cast<double>(options[i + 1]);
if (rate <= 0.0) {
rate = -1.0;
}
m_heartDeath = m_heartRate * kHeartBeatsUntilDeath;
m_heartbeatAlarm = rate * kHeartBeatsUntilDeath;
removeHeartbeatTimer();
addHeartbeatTimer();
}
}
}
@ -318,7 +400,7 @@ CClientProxy1_0::getCursorCenter(SInt32& x, SInt32& y) const
y = m_info.m_my;
}
void
bool
CClientProxy1_0::recvInfo(bool notify)
{
{
@ -326,16 +408,18 @@ CClientProxy1_0::recvInfo(bool notify)
// parse the message
SInt16 x, y, w, h, zoneSize, mx, my;
CProtocolUtil::readf(getInputStream(), kMsgDInfo + 4,
&x, &y, &w, &h, &zoneSize, &mx, &my);
if (!CProtocolUtil::readf(getStream(), kMsgDInfo + 4,
&x, &y, &w, &h, &zoneSize, &mx, &my)) {
return false;
}
LOG((CLOG_DEBUG "received client \"%s\" info shape=%d,%d %dx%d, zone=%d, pos=%d,%d", getName().c_str(), x, y, w, h, zoneSize, mx, my));
// validate
if (w <= 0 || h <= 0 || zoneSize < 0) {
throw XBadClient();
return false;
}
if (mx < x || my < y || mx >= x + w || my >= y + h) {
throw XBadClient();
return false;
}
// save
@ -355,44 +439,52 @@ CClientProxy1_0::recvInfo(bool notify)
// acknowledge receipt
LOG((CLOG_DEBUG1 "send info ack to \"%s\"", getName().c_str()));
CProtocolUtil::writef(getOutputStream(), kMsgCInfoAck);
CProtocolUtil::writef(getStream(), kMsgCInfoAck);
return true;
}
void
bool
CClientProxy1_0::recvClipboard()
{
// parse message
ClipboardID id;
UInt32 seqNum;
CString data;
CProtocolUtil::readf(getInputStream(), kMsgDClipboard + 4, &id, &seqNum, &data);
if (!CProtocolUtil::readf(getStream(),
kMsgDClipboard + 4, &id, &seqNum, &data)) {
return false;
}
LOG((CLOG_DEBUG "received client \"%s\" clipboard %d seqnum=%d, size=%d", getName().c_str(), id, seqNum, data.size()));
// validate
if (id >= kClipboardEnd) {
throw XBadClient();
return false;
}
// send update. this calls us back to reset our clipboard dirty flag
// so don't hold a lock during the call.
getServer()->onClipboardChanged(id, seqNum, data);
return true;
}
void
bool
CClientProxy1_0::recvGrabClipboard()
{
// parse message
ClipboardID id;
UInt32 seqNum;
CProtocolUtil::readf(getInputStream(), kMsgCClipboard + 4, &id, &seqNum);
if (!CProtocolUtil::readf(getStream(), kMsgCClipboard + 4, &id, &seqNum)) {
return false;
}
LOG((CLOG_DEBUG "received client \"%s\" grabbed clipboard %d seqnum=%d", getName().c_str(), id, seqNum));
// validate
if (id >= kClipboardEnd) {
throw XBadClient();
return false;
}
// send update. this calls us back to reset our clipboard dirty flag
// so don't hold a lock during the call.
getServer()->onGrabClipboard(getName(), id, seqNum);
return true;
}

View File

@ -18,12 +18,14 @@
#include "CClientProxy.h"
#include "ProtocolTypes.h"
class CEvent;
class CEventQueueTimer;
//! Proxy for client implementing protocol version 1.0
class CClientProxy1_0 : public CClientProxy {
public:
CClientProxy1_0(IServer* server, const CString& name,
IInputStream* adoptedInput,
IOutputStream* adoptedOutput);
IStream* adoptedStream);
~CClientProxy1_0();
// IClient overrides
@ -54,16 +56,29 @@ public:
virtual void getCursorPos(SInt32& x, SInt32& y) const;
virtual void getCursorCenter(SInt32& x, SInt32& y) const;
protected:
virtual bool parseMessage(const UInt8* code);
private:
void recvInfo(bool notify);
void recvClipboard();
void recvGrabClipboard();
void disconnect();
void removeHandlers();
void addHeartbeatTimer();
void removeHeartbeatTimer();
void handleData(const CEvent&, void*);
void handleDisconnect(const CEvent&, void*);
void handleWriteError(const CEvent&, void*);
void handleFlatline(const CEvent&, void*);
bool recvInfo(bool notify);
bool recvClipboard();
bool recvGrabClipboard();
private:
CClientInfo m_info;
bool m_clipboardDirty[kClipboardEnd];
double m_heartRate;
double m_heartDeath;
double m_heartbeatAlarm;
CEventQueueTimer* m_heartbeatTimer;
};
#endif

View File

@ -21,9 +21,9 @@
// CClientProxy1_1
//
CClientProxy1_1::CClientProxy1_1(IServer* server, const CString& name,
IInputStream* input, IOutputStream* output) :
CClientProxy1_0(server, name, input, output)
CClientProxy1_1::CClientProxy1_1(IServer* server,
const CString& name, IStream* stream) :
CClientProxy1_0(server, name, stream)
{
// do nothing
}
@ -37,7 +37,7 @@ void
CClientProxy1_1::keyDown(KeyID key, KeyModifierMask mask, KeyButton button)
{
LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x, button=0x%04x", getName().c_str(), key, mask, button));
CProtocolUtil::writef(getOutputStream(), kMsgDKeyDown, key, mask, button);
CProtocolUtil::writef(getStream(), kMsgDKeyDown, key, mask, button);
}
void
@ -45,12 +45,12 @@ CClientProxy1_1::keyRepeat(KeyID key, KeyModifierMask mask,
SInt32 count, KeyButton button)
{
LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d, button=0x%04x", getName().c_str(), key, mask, count, button));
CProtocolUtil::writef(getOutputStream(), kMsgDKeyRepeat, key, mask, count, button);
CProtocolUtil::writef(getStream(), kMsgDKeyRepeat, key, mask, count, button);
}
void
CClientProxy1_1::keyUp(KeyID key, KeyModifierMask mask, KeyButton button)
{
LOG((CLOG_DEBUG1 "send key up to \"%s\" id=%d, mask=0x%04x, button=0x%04x", getName().c_str(), key, mask, button));
CProtocolUtil::writef(getOutputStream(), kMsgDKeyUp, key, mask, button);
CProtocolUtil::writef(getStream(), kMsgDKeyUp, key, mask, button);
}

View File

@ -21,8 +21,7 @@
class CClientProxy1_1 : public CClientProxy1_0 {
public:
CClientProxy1_1(IServer* server, const CString& name,
IInputStream* adoptedInput,
IOutputStream* adoptedOutput);
IStream* adoptedStream);
~CClientProxy1_1();
// IClient overrides

View File

@ -239,12 +239,6 @@ CConfig::setSynergyAddress(const CNetworkAddress& addr)
m_synergyAddress = addr;
}
void
CConfig::setHTTPAddress(const CNetworkAddress& addr)
{
m_httpAddress = addr;
}
bool
CConfig::addOption(const CString& name, OptionID option, OptionValue value)
{
@ -430,12 +424,6 @@ CConfig::getSynergyAddress() const
return m_synergyAddress;
}
const CNetworkAddress&
CConfig::getHTTPAddress() const
{
return m_httpAddress;
}
const CConfig::CScreenOptions*
CConfig::getOptions(const CString& name) const
{
@ -462,9 +450,6 @@ CConfig::operator==(const CConfig& x) const
if (m_synergyAddress != x.m_synergyAddress) {
return false;
}
if (m_httpAddress != x.m_httpAddress) {
return false;
}
*/
if (m_map.size() != x.m_map.size()) {
return false;
@ -782,14 +767,6 @@ CConfig::readSectionOptions(std::istream& s)
throw XConfigRead("invalid address argument");
}
}
else if (name == "http") {
try {
m_httpAddress = CNetworkAddress(value, kDefaultPort + 1);
}
catch (XSocketAddress&) {
throw XConfigRead("invalid http argument");
}
}
else if (name == "heartbeat") {
addOption("", kOptionHeartbeat, parseInt(value));
}
@ -1063,10 +1040,6 @@ operator<<(std::ostream& s, const CConfig& config)
s << "\taddress = " <<
config.m_synergyAddress.getHostname().c_str() << std::endl;
}
if (config.m_httpAddress.isValid()) {
s << "\thttp = " <<
config.m_httpAddress.getHostname().c_str() << std::endl;
}
s << "end" << std::endl;
// screens section

View File

@ -175,13 +175,6 @@ public:
*/
void setSynergyAddress(const CNetworkAddress&);
//! Set HTTP server address
/*!
Set the HTTP listen addresses. There is no default address so
this must be called to run an HTTP server using this configuration.
*/
void setHTTPAddress(const CNetworkAddress&);
//! Add a screen option
/*!
Adds an option and its value to the named screen. Replaces the
@ -255,8 +248,6 @@ public:
//! Get the server address
const CNetworkAddress& getSynergyAddress() const;
//! Get the HTTP server address
const CNetworkAddress& getHTTPAddress() const;
//! Get the screen options
/*!
@ -308,7 +299,6 @@ private:
CCellMap m_map;
CNameMap m_nameToCanonicalName;
CNetworkAddress m_synergyAddress;
CNetworkAddress m_httpAddress;
CScreenOptions m_globalOptions;
};

View File

@ -1,799 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "CHTTPServer.h"
#include "CConfig.h"
#include "CHTTPProtocol.h"
#include "CServer.h"
#include "XHTTP.h"
#include "IDataSocket.h"
#include "XThread.h"
#include "CLog.h"
#include "stdset.h"
#include "stdsstream.h"
//
// CHTTPServer
//
// maximum size of an HTTP request. this should be large enough to
// handle any reasonable request but small enough to prevent a
// malicious client from causing us to use too much memory.
const UInt32 CHTTPServer::s_maxRequestSize = 32768;
CHTTPServer::CHTTPServer(
CServer* server) :
m_server(server)
{
// do nothing
}
CHTTPServer::~CHTTPServer()
{
// do nothing
}
void
CHTTPServer::processRequest(IDataSocket* socket)
{
assert(socket != NULL);
CHTTPRequest* request = NULL;
try {
// parse request
request = CHTTPProtocol::readRequest(
socket->getInputStream(), s_maxRequestSize);
if (request == NULL) {
throw XHTTP(400);
}
// if absolute uri then strip off scheme and host
if (request->m_uri[0] != '/') {
CString::size_type n = request->m_uri.find('/');
if (n == CString::npos) {
throw XHTTP(404);
}
request->m_uri = request->m_uri.substr(n);
}
// prepare reply
CHTTPReply reply;
reply.m_majorVersion = request->m_majorVersion;
reply.m_minorVersion = request->m_minorVersion;
reply.m_status = 200;
reply.m_reason = "OK";
reply.m_method = request->m_method;
// process
doProcessRequest(*request, reply);
// send reply
CHTTPProtocol::reply(socket->getOutputStream(), reply);
LOG((CLOG_INFO "HTTP reply %d for %s %s", reply.m_status, request->m_method.c_str(), request->m_uri.c_str()));
// clean up
delete request;
}
catch (XHTTP& e) {
LOG((CLOG_WARN "returning HTTP error %d %s for %s", e.getStatus(), e.getReason().c_str(), (request != NULL) ? request->m_uri.c_str() : "<unknown>"));
// clean up
delete request;
// return error
CHTTPReply reply;
reply.m_majorVersion = 1;
reply.m_minorVersion = 0;
reply.m_status = e.getStatus();
reply.m_reason = e.getReason();
reply.m_method = "GET";
// FIXME -- use a nicer error page
reply.m_headers.push_back(std::make_pair(CString("Content-Type"),
CString("text/plain")));
reply.m_body = e.getReason();
e.addHeaders(reply);
CHTTPProtocol::reply(socket->getOutputStream(), reply);
}
catch (...) {
// ignore other exceptions
RETHROW_XTHREAD
}
}
void
CHTTPServer::doProcessRequest(CHTTPRequest& request, CHTTPReply& reply)
{
// switch based on uri
if (request.m_uri == "/editmap") {
if (request.m_method == "GET" || request.m_method == "HEAD") {
doProcessGetEditMap(request, reply);
reply.m_headers.push_back(std::make_pair(
CString("Content-Type"),
CString("text/html")));
}
else if (request.m_method == "POST") {
doProcessPostEditMap(request, reply);
reply.m_headers.push_back(std::make_pair(
CString("Content-Type"),
CString("text/html")));
}
else {
throw XHTTPAllow("GET, HEAD, POST");
}
}
else {
// unknown page
throw XHTTP(404);
}
}
void
CHTTPServer::doProcessGetEditMap(CHTTPRequest& /*request*/, CHTTPReply& reply)
{
static const char* s_editMapProlog1 =
"<html>\r\n"
"<head>\r\n"
" <title>Synergy -- Edit Screens</title>\r\n"
"</head>\r\n"
"<body>\r\n"
" <form method=\"POST\" action=\"editmap\" "
"enctype=\"multipart/form-data\">\r\n"
" <input type=hidden name=\"size\" value=\"";
static const char* s_editMapProlog2 =
"\">\r\n";
static const char* s_editMapEpilog =
" <input type=submit name=\"submit\">\r\n"
" <input type=reset>\r\n"
" <br>\r\n"
" </form>\r\n"
"</body>\r\n"
"</html>\r\n";
static const char* s_editMapTableProlog =
"<table border=\"1\" cellspacing=\"0\" cellpadding=\"0\"><tr><td>"
" <table border=\"0\" cellspacing=\"2\" cellpadding=\"6\">\r\n";
static const char* s_editMapTableEpilog =
" </table>"
"</td></tr></table>\r\n";
static const char* s_editMapRowProlog =
"<tr align=\"center\" valign=\"top\">\r\n";
static const char* s_editMapRowEpilog =
"</tr>\r\n";
static const char* s_editMapScreenDummy =
"<td>";
static const char* s_editMapScreenPrimary =
"<td bgcolor=\"#2222288\"><input type=\"text\" readonly value=\"";
static const char* s_editMapScreenLive =
"<td bgcolor=\"#cccccc\"><input type=\"text\" value=\"";
static const char* s_editMapScreenDead =
"<td><input type=\"text\" value=\"";
static const char* s_editMapScreenLiveDead1 =
"\" size=\"16\" maxlength=\"64\" name=\"";
static const char* s_editMapScreenLiveDead2 =
"\">";
static const char* s_editMapScreenEnd =
"</td>\r\n";
std::ostringstream s;
// convert screen map into a temporary screen map
CScreenArray screens;
{
CConfig config;
m_server->getConfig(&config);
screens.convertFrom(config);
// FIXME -- note to user if config couldn't be exactly represented
}
// insert blank columns and rows around array (to allow the user
// to insert new screens)
screens.insertColumn(0);
screens.insertColumn(screens.getWidth());
screens.insertRow(0);
screens.insertRow(screens.getHeight());
// get array size
const SInt32 w = screens.getWidth();
const SInt32 h = screens.getHeight();
// construct reply
reply.m_body += s_editMapProlog1;
s << w << "x" << h;
reply.m_body += s.str();
reply.m_body += s_editMapProlog2;
// add screen map for editing
const CString primaryName = m_server->getPrimaryScreenName();
reply.m_body += s_editMapTableProlog;
for (SInt32 y = 0; y < h; ++y) {
reply.m_body += s_editMapRowProlog;
for (SInt32 x = 0; x < w; ++x) {
s.str("");
if (!screens.isAllowed(x, y) && screens.get(x, y) != primaryName) {
s << s_editMapScreenDummy;
}
else {
if (!screens.isSet(x, y)) {
s << s_editMapScreenDead;
}
else if (screens.get(x, y) == primaryName) {
s << s_editMapScreenPrimary;
}
else {
s << s_editMapScreenLive;
}
s << screens.get(x, y) <<
s_editMapScreenLiveDead1 <<
"n" << x << "x" << y <<
s_editMapScreenLiveDead2;
}
s << s_editMapScreenEnd;
reply.m_body += s.str();
}
reply.m_body += s_editMapRowEpilog;
}
reply.m_body += s_editMapTableEpilog;
reply.m_body += s_editMapEpilog;
}
void
CHTTPServer::doProcessPostEditMap(CHTTPRequest& request, CHTTPReply& reply)
{
typedef std::vector<CString> ScreenArray;
typedef std::set<CString> ScreenSet;
// parse the result
CHTTPProtocol::CFormParts parts;
if (!CHTTPProtocol::parseFormData(request, parts)) {
LOG((CLOG_WARN "editmap: cannot parse form data"));
throw XHTTP(400);
}
try {
std::ostringstream s;
// convert post data into a temporary screen map. also check
// that no screen name is invalid or used more than once.
SInt32 w, h;
CHTTPProtocol::CFormParts::iterator index = parts.find("size");
if (index == parts.end() ||
!parseXY(index->second, w, h) ||
w <= 0 || h <= 0) {
LOG((CLOG_WARN "editmap: cannot parse size or size is invalid"));
throw XHTTP(400);
}
ScreenSet screenNames;
CScreenArray screens;
screens.resize(w, h);
for (SInt32 y = 0; y < h; ++y) {
for (SInt32 x = 0; x < w; ++x) {
// find part
s.str("");
s << "n" << x << "x" << y;
index = parts.find(s.str());
if (index == parts.end()) {
// FIXME -- screen is missing. error?
continue;
}
// skip blank names
const CString& name = index->second;
if (name.empty()) {
continue;
}
// check name. name must be legal and must not have
// already been seen.
if (screenNames.count(name)) {
// FIXME -- better error message
LOG((CLOG_WARN "editmap: duplicate name %s", name.c_str()));
throw XHTTP(400);
}
// FIXME -- check that name is legal
// save name. if we've already seen the name then
// report an error.
screens.set(x, y, name);
screenNames.insert(name);
}
}
// if new map is invalid then return error. map is invalid if:
// there are no screens, or
// the screens are not 4-connected.
if (screenNames.empty()) {
// no screens
// FIXME -- need better no screens
LOG((CLOG_WARN "editmap: no screens"));
throw XHTTP(400);
}
if (!screens.isValid()) {
// FIXME -- need better unconnected screens error
LOG((CLOG_WARN "editmap: unconnected screens"));
throw XHTTP(400);
}
// convert temporary screen map into a regular map
CConfig config;
m_server->getConfig(&config);
screens.convertTo(config);
// set new screen map on server
m_server->setConfig(config);
// now reply with current map
doProcessGetEditMap(request, reply);
}
catch (XHTTP&) {
// FIXME -- construct a more meaningful error?
throw;
}
}
bool
CHTTPServer::parseXY(const CString& xy, SInt32& x, SInt32& y)
{
std::istringstream s(xy);
char delimiter;
s >> x;
s.get(delimiter);
s >> y;
return (!!s && delimiter == 'x');
}
//
// CHTTPServer::CScreenArray
//
CHTTPServer::CScreenArray::CScreenArray() :
m_w(0),
m_h(0)
{
// do nothing
}
CHTTPServer::CScreenArray::~CScreenArray()
{
// do nothing
}
void
CHTTPServer::CScreenArray::resize(SInt32 w, SInt32 h)
{
m_screens.clear();
m_screens.resize(w * h);
m_w = w;
m_h = h;
}
void
CHTTPServer::CScreenArray::insertRow(SInt32 i)
{
assert(i >= 0 && i <= m_h);
CNames newScreens;
newScreens.resize(m_w * (m_h + 1));
for (SInt32 y = 0; y < i; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
newScreens[x + y * m_w] = m_screens[x + y * m_w];
}
}
for (SInt32 y = i; y < m_h; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
newScreens[x + (y + 1) * m_w] = m_screens[x + y * m_w];
}
}
m_screens.swap(newScreens);
++m_h;
}
void
CHTTPServer::CScreenArray::insertColumn(SInt32 i)
{
assert(i >= 0 && i <= m_w);
CNames newScreens;
newScreens.resize((m_w + 1) * m_h);
for (SInt32 y = 0; y < m_h; ++y) {
for (SInt32 x = 0; x < i; ++x) {
newScreens[x + y * (m_w + 1)] = m_screens[x + y * m_w];
}
for (SInt32 x = i; x < m_w; ++x) {
newScreens[(x + 1) + y * (m_w + 1)] = m_screens[x + y * m_w];
}
}
m_screens.swap(newScreens);
++m_w;
}
void
CHTTPServer::CScreenArray::eraseRow(SInt32 i)
{
assert(i >= 0 && i < m_h);
CNames newScreens;
newScreens.resize(m_w * (m_h - 1));
for (SInt32 y = 0; y < i; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
newScreens[x + y * m_w] = m_screens[x + y * m_w];
}
}
for (SInt32 y = i + 1; y < m_h; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
newScreens[x + (y - 1) * m_w] = m_screens[x + y * m_w];
}
}
m_screens.swap(newScreens);
--m_h;
}
void
CHTTPServer::CScreenArray::eraseColumn(SInt32 i)
{
assert(i >= 0 && i < m_w);
CNames newScreens;
newScreens.resize((m_w - 1) * m_h);
for (SInt32 y = 0; y < m_h; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
newScreens[x + y * (m_w - 1)] = m_screens[x + y * m_w];
}
for (SInt32 x = i + 1; x < m_w; ++x) {
newScreens[(x - 1) + y * (m_w - 1)] = m_screens[x + y * m_w];
}
}
m_screens.swap(newScreens);
--m_w;
}
void
CHTTPServer::CScreenArray::rotateRows(SInt32 i)
{
// nothing to do if no rows
if (m_h == 0) {
return;
}
// convert to canonical form
if (i < 0) {
i = m_h - ((-i) % m_h);
}
else {
i %= m_h;
}
if (i == 0 || i == m_h) {
return;
}
while (i > 0) {
// rotate one row
for (SInt32 x = 0; x < m_w; ++x) {
CString tmp = m_screens[x];
for (SInt32 y = 1; y < m_h; ++y) {
m_screens[x + (y - 1) * m_w] = m_screens[x + y * m_w];
}
m_screens[x + (m_h - 1) * m_w] = tmp;
}
}
}
void
CHTTPServer::CScreenArray::rotateColumns(SInt32 i)
{
// nothing to do if no columns
if (m_h == 0) {
return;
}
// convert to canonical form
if (i < 0) {
i = m_w - ((-i) % m_w);
}
else {
i %= m_w;
}
if (i == 0 || i == m_w) {
return;
}
while (i > 0) {
// rotate one column
for (SInt32 y = 0; y < m_h; ++y) {
CString tmp = m_screens[0 + y * m_w];
for (SInt32 x = 1; x < m_w; ++x) {
m_screens[x - 1 + y * m_w] = m_screens[x + y * m_w];
}
m_screens[m_w - 1 + y * m_w] = tmp;
}
}
}
void
CHTTPServer::CScreenArray::remove(SInt32 x, SInt32 y)
{
set(x, y, CString());
}
void
CHTTPServer::CScreenArray::set(SInt32 x, SInt32 y, const CString& name)
{
assert(x >= 0 && x < m_w);
assert(y >= 0 && y < m_h);
m_screens[x + y * m_w] = name;
}
bool
CHTTPServer::CScreenArray::isAllowed(SInt32 x, SInt32 y) const
{
assert(x >= 0 && x < m_w);
assert(y >= 0 && y < m_h);
if (x > 0 && !m_screens[(x - 1) + y * m_w].empty()) {
return true;
}
if (x < m_w - 1 && !m_screens[(x + 1) + y * m_w].empty()) {
return true;
}
if (y > 0 && !m_screens[x + (y - 1) * m_w].empty()) {
return true;
}
if (y < m_h - 1 && !m_screens[x + (y + 1) * m_w].empty()) {
return true;
}
return false;
}
bool
CHTTPServer::CScreenArray::isSet(SInt32 x, SInt32 y) const
{
assert(x >= 0 && x < m_w);
assert(y >= 0 && y < m_h);
return !m_screens[x + y * m_w].empty();
}
CString
CHTTPServer::CScreenArray::get(SInt32 x, SInt32 y) const
{
assert(x >= 0 && x < m_w);
assert(y >= 0 && y < m_h);
return m_screens[x + y * m_w];
}
bool
CHTTPServer::CScreenArray::find(const CString& name,
SInt32& xOut, SInt32& yOut) const
{
for (SInt32 y = 0; y < m_h; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
if (m_screens[x + y * m_w] == name) {
xOut = x;
yOut = y;
return true;
}
}
}
return false;
}
bool
CHTTPServer::CScreenArray::isValid() const
{
SInt32 count = 0, isolated = 0;
for (SInt32 y = 0; y < m_h; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
if (isSet(x, y)) {
++count;
if (!isAllowed(x, y)) {
++isolated;
}
}
}
}
return (count <= 1 || isolated == 0);
}
bool
CHTTPServer::CScreenArray::convertFrom(const CConfig& config)
{
typedef std::set<CString> ScreenSet;
// insert the first screen
CConfig::const_iterator index = config.begin();
if (index == config.end()) {
// no screens
resize(0, 0);
return true;
}
CString name = *index;
resize(1, 1);
set(0, 0, name);
// flood fill state
CNames screenStack;
ScreenSet doneSet;
// put all but the first screen on the stack
// note -- if all screens are 4-connected then we can skip this
while (++index != config.end()) {
screenStack.push_back(*index);
}
// put the first screen on the stack last so we process it first
screenStack.push_back(name);
// perform a flood fill using the stack as the seeds
while (!screenStack.empty()) {
// get next screen from stack
CString name = screenStack.back();
screenStack.pop_back();
// skip screen if we've seen it before
if (doneSet.count(name) > 0) {
continue;
}
// add this screen to doneSet so we don't process it again
doneSet.insert(name);
// find the screen. if it's not found then not all of the
// screens are 4-connected. discard disconnected screens.
SInt32 x, y;
if (!find(name, x, y)) {
continue;
}
// insert the screen's neighbors
// FIXME -- handle edge wrapping
CString neighbor;
neighbor = config.getNeighbor(name, kLeft);
if (!neighbor.empty() && doneSet.count(neighbor) == 0) {
// insert left neighbor, adding a column if necessary
if (x == 0 || get(x - 1, y) != neighbor) {
++x;
insertColumn(x - 1);
set(x - 1, y, neighbor);
}
screenStack.push_back(neighbor);
}
neighbor = config.getNeighbor(name, kRight);
if (!neighbor.empty() && doneSet.count(neighbor) == 0) {
// insert right neighbor, adding a column if necessary
if (x == m_w - 1 || get(x + 1, y) != neighbor) {
insertColumn(x + 1);
set(x + 1, y, neighbor);
}
screenStack.push_back(neighbor);
}
neighbor = config.getNeighbor(name, kTop);
if (!neighbor.empty() && doneSet.count(neighbor) == 0) {
// insert top neighbor, adding a row if necessary
if (y == 0 || get(x, y - 1) != neighbor) {
++y;
insertRow(y - 1);
set(x, y - 1, neighbor);
}
screenStack.push_back(neighbor);
}
neighbor = config.getNeighbor(name, kBottom);
if (!neighbor.empty() && doneSet.count(neighbor) == 0) {
// insert bottom neighbor, adding a row if necessary
if (y == m_h - 1 || get(x, y + 1) != neighbor) {
insertRow(y + 1);
set(x, y + 1, neighbor);
}
screenStack.push_back(neighbor);
}
}
// check symmetry
// FIXME -- handle edge wrapping
for (index = config.begin(); index != config.end(); ++index) {
const CString& name = *index;
SInt32 x, y;
if (!find(name, x, y)) {
return false;
}
CString neighbor;
neighbor = config.getNeighbor(name, kLeft);
if ((x == 0 && !neighbor.empty()) ||
(x > 0 && get(x - 1, y) != neighbor)) {
return false;
}
neighbor = config.getNeighbor(name, kRight);
if ((x == m_w - 1 && !neighbor.empty()) ||
(x < m_w - 1 && get(x + 1, y) != neighbor)) {
return false;
}
neighbor = config.getNeighbor(name, kTop);
if ((y == 0 && !neighbor.empty()) ||
(y > 0 && get(x, y - 1) != neighbor)) {
return false;
}
neighbor = config.getNeighbor(name, kBottom);
if ((y == m_h - 1 && !neighbor.empty()) ||
(y < m_h - 1 && get(x, y + 1) != neighbor)) {
return false;
}
}
return true;
}
void
CHTTPServer::CScreenArray::convertTo(CConfig& config) const
{
config.removeAllScreens();
// add screens and find smallest box containing all screens
SInt32 x0 = m_w, x1 = 0, y0 = m_h, y1 = 0;
for (SInt32 y = 0; y < m_h; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
if (isSet(x, y)) {
config.addScreen(get(x, y));
if (x < x0) {
x0 = x;
}
if (x > x1) {
x1 = x;
}
if (y < y0) {
y0 = y;
}
if (y > y1) {
y1 = y;
}
}
}
}
// make connections between screens
// FIXME -- add support for wrapping
// FIXME -- mark topmost and leftmost screens
for (SInt32 y = 0; y < m_h; ++y) {
for (SInt32 x = 0; x < m_w; ++x) {
if (!isSet(x, y)) {
continue;
}
if (x > x0 && isSet(x - 1, y)) {
config.connect(get(x, y), kLeft, get(x - 1, y));
}
if (x < x1 && isSet(x + 1, y)) {
config.connect(get(x, y), kRight, get(x + 1, y));
}
if (y > y0 && isSet(x, y - 1)) {
config.connect(get(x, y), kTop, get(x, y - 1));
}
if (y < y1 && isSet(x, y + 1)) {
config.connect(get(x, y), kBottom, get(x, y + 1));
}
}
}
}

View File

@ -1,142 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef CHTTPSERVER_H
#define CHTTPSERVER_H
#include "CString.h"
#include "BasicTypes.h"
#include "stdvector.h"
class CServer;
class CConfig;
class CHTTPRequest;
class CHTTPReply;
class IDataSocket;
//! Simple HTTP server
/*!
This class implements a simple HTTP server for interacting with the
synergy server.
*/
class CHTTPServer {
public:
CHTTPServer(CServer*);
virtual ~CHTTPServer();
//! @name manipulators
//@{
//! Process HTTP request
/*!
Synchronously processes an HTTP request on the given socket.
*/
void processRequest(IDataSocket*);
//@}
protected:
//! Process HTTP request
/*!
Processes a successfully read HTTP request. The reply is partially
filled in (version, method, status (200) and reason (OK)). This
method checks the URI and handles the request, filling in the rest
of the reply. If the request cannot be satisfied it throws an
appropriate XHTTP exception.
*/
virtual void doProcessRequest(CHTTPRequest&, CHTTPReply&);
//! Process request for map
virtual void doProcessGetEditMap(CHTTPRequest&, CHTTPReply&);
//! Process request for changing map
virtual void doProcessPostEditMap(CHTTPRequest&, CHTTPReply&);
//! Parse coordinate string
static bool parseXY(const CString&, SInt32& x, SInt32& y);
//! Screen map helper
/*!
This class represents the screen map as a resizable array. It's
used to handle map requests.
*/
class CScreenArray {
public:
CScreenArray();
~CScreenArray();
// resize the array. this also clears all the elements.
void resize(SInt32 w, SInt32 h);
// insert/remove a row/column. all elements in a new row/column
// are unset.
void insertRow(SInt32 insertedBeforeRow);
void insertColumn(SInt32 insertedBeforeColumn);
void eraseRow(SInt32 row);
void eraseColumn(SInt32 column);
// rotate rows or columns
void rotateRows(SInt32 rowsDown);
void rotateColumns(SInt32 columnsDown);
// remove/set a screen name. setting an empty name is the
// same as removing a name. names are not checked for
// validity.
void remove(SInt32 x, SInt32 y);
void set(SInt32 x, SInt32 y, const CString&);
// convert a CConfig to a CScreenArray. returns true iff
// all connections are symmetric and therefore exactly
// representable by a CScreenArray.
bool convertFrom(const CConfig&);
// accessors
// get the array size
SInt32 getWidth() const { return m_w; }
SInt32 getHeight() const { return m_h; }
// returns true iff the cell has a 4-connected neighbor
bool isAllowed(SInt32 x, SInt32 y) const;
// returns true iff the cell has a (non-empty) name
bool isSet(SInt32 x, SInt32 y) const;
// get a screen name
CString get(SInt32 x, SInt32 y) const;
// find a screen by name. returns true iff found.
bool find(const CString&, SInt32& x, SInt32& y) const;
// return true iff the overall array is valid. that means
// just zero or one screen or all screens are 4-connected
// to other screens.
bool isValid() const;
// convert this to a CConfig
void convertTo(CConfig&) const;
private:
typedef std::vector<CString> CNames;
SInt32 m_w, m_h;
CNames m_screens;
};
private:
CServer* m_server;
static const UInt32 s_maxRequestSize;
};
#endif

View File

@ -13,7 +13,6 @@
*/
#include "CServer.h"
#include "CHTTPServer.h"
#include "CPrimaryClient.h"
#include "IScreenFactory.h"
#include "CInputPacketStream.h"
@ -45,8 +44,6 @@
// CServer
//
const SInt32 CServer::s_httpMaxSimultaneousRequests = 3;
CServer::CServer(const CString& serverName) :
m_name(serverName),
m_error(false),
@ -59,8 +56,6 @@ CServer::CServer(const CString& serverName) :
m_primaryClient(NULL),
m_seqNum(0),
m_activeSaver(NULL),
m_httpServer(NULL),
m_httpAvailable(&m_mutex, s_httpMaxSimultaneousRequests),
m_switchDir(kNoDirection),
m_switchScreen(NULL),
m_switchWaitDelay(0.0),
@ -118,18 +113,15 @@ CServer::mainLoop()
setStatus(kNotRunning);
LOG((CLOG_NOTE "starting server"));
// FIXME -- started here
createClientListener();
// FIXME -- finished here
// start listening for new clients
m_acceptClientThread = new CThread(startThread(
new TMethodJob<CServer>(this,
&CServer::acceptClients)));
// start listening for HTTP requests
if (m_config.getHTTPAddress().isValid()) {
m_httpServer = new CHTTPServer(this);
startThread(new TMethodJob<CServer>(this,
&CServer::acceptHTTPClients));
}
// handle events
m_primaryClient->mainLoop();
@ -144,8 +136,6 @@ CServer::mainLoop()
// bucket.
#define FINALLY do { \
stopThreads(); \
delete m_httpServer; \
m_httpServer = NULL; \
runStatusJobs(); \
} while (false)
FINALLY;
@ -360,6 +350,16 @@ CServer::getConfig(CConfig* config) const
*config = m_config;
}
CString
CServer::getCanonicalName(const CString& name) const
{
CLock lock(&m_mutex);
if (m_config.isScreen(name)) {
return m_config.getCanonicalName(name);
}
return name;
}
void
CServer::runStatusJobs() const
{
@ -411,11 +411,6 @@ CServer::onError()
// threads may be unable to proceed until this thread returns.
stopThreads(3.0);
// done with the HTTP server
CLock lock(&m_mutex);
delete m_httpServer;
m_httpServer = NULL;
// note -- we do not attempt to close down the primary screen
}
@ -1673,23 +1668,6 @@ CServer::handshakeClient(IDataSocket* socket)
{
LOG((CLOG_DEBUG1 "negotiating with new client"));
// get the input and output streams
IInputStream* input = socket->getInputStream();
IOutputStream* output = socket->getOutputStream();
bool own = false;
// attach filters
if (m_streamFilterFactory != NULL) {
input = m_streamFilterFactory->createInput(input, own);
output = m_streamFilterFactory->createOutput(output, own);
own = true;
}
// attach the packetizing filters
input = new CInputPacketStream(input, own);
output = new COutputPacketStream(output, own);
own = true;
CClientProxy* proxy = NULL;
CString name("<unknown>");
try {
@ -1810,111 +1788,6 @@ CServer::handshakeClient(IDataSocket* socket)
return NULL;
}
void
CServer::acceptHTTPClients(void*)
{
LOG((CLOG_DEBUG1 "starting to wait for HTTP clients"));
IListenSocket* listen = NULL;
try {
// create socket listener
listen = new CTCPListenSocket;
// bind to the desired port. keep retrying if we can't bind
// the address immediately.
CStopwatch timer;
for (;;) {
try {
LOG((CLOG_DEBUG1 "binding HTTP listen socket"));
listen->bind(m_config.getHTTPAddress());
break;
}
catch (XSocketBind& e) {
LOG((CLOG_DEBUG1 "bind HTTP failed: %s", e.what()));
// give up if we've waited too long
if (timer.getTime() >= m_bindTimeout) {
LOG((CLOG_DEBUG1 "waited too long to bind HTTP, giving up"));
throw;
}
// wait a bit before retrying
ARCH->sleep(5.0);
}
}
// accept connections and begin processing them
LOG((CLOG_DEBUG1 "waiting for HTTP connections"));
for (;;) {
// limit the number of HTTP requests being handled at once
{
CLock lock(&m_httpAvailable);
while (m_httpAvailable == 0) {
m_httpAvailable.wait();
}
assert(m_httpAvailable > 0);
m_httpAvailable = m_httpAvailable - 1;
}
// accept connection
CThread::testCancel();
IDataSocket* socket = listen->accept();
LOG((CLOG_NOTE "accepted HTTP connection"));
CThread::testCancel();
// handle HTTP request
startThread(new TMethodJob<CServer>(
this, &CServer::processHTTPRequest, socket));
}
// clean up
delete listen;
}
catch (XBase& e) {
LOG((CLOG_ERR "cannot listen for HTTP clients: %s", e.what()));
delete listen;
exitMainLoopWithError();
}
catch (...) {
delete listen;
throw;
}
}
void
CServer::processHTTPRequest(void* vsocket)
{
IDataSocket* socket = reinterpret_cast<IDataSocket*>(vsocket);
try {
// process the request and force delivery
m_httpServer->processRequest(socket);
socket->getOutputStream()->flush();
// wait a moment to give the client a chance to hangup first
ARCH->sleep(3.0);
// clean up
socket->close();
delete socket;
// increment available HTTP handlers
{
CLock lock(&m_httpAvailable);
m_httpAvailable = m_httpAvailable + 1;
m_httpAvailable.signal();
}
}
catch (...) {
delete socket;
{
CLock lock(&m_httpAvailable);
m_httpAvailable = m_httpAvailable + 1;
m_httpAvailable.signal();
}
throw;
}
}
void
CServer::sendOptions(IClient* client) const
{
@ -2141,3 +2014,259 @@ CServer::CClipboardInfo::CClipboardInfo() :
{
// do nothing
}
// --- transitional ---
void
CServer::clientConnecting(const CEvent&, void*)
{
// accept client connection
IDataSocket* socket = m_listen->accept();
if (socket == NULL) {
return;
}
LOG((CLOG_NOTE "accepted client connection"));
// filter socket's streams then wrap the result in a new socket
IInputStream* input = socket->getInputStream();
IOutputStream* output = socket->getOutputStream();
bool own = false;
if (m_streamFilterFactory != NULL) {
input = m_streamFilterFactory->createInput(input, own);
output = m_streamFilterFactory->createOutput(output, own);
own = true;
}
input = new CInputPacketStream(input, own);
output = new COutputPacketStream(output, own);
socket = new CDataSocket(socket,
new CInputOutputStream(input, output, true));
// FIXME -- may want to move these event handlers (but not the
// handshake timer?) and the handshake into a new proxy object.
// we save this proxy as a provisional connection. it calls
// back to us (or maybe sends an event) to notify of failure or
// success. failure is socket failure or protocol error.
// success returns a new proxy of the appropriate version. we
// need to verify the validity of the client's name then remove
// the provisional connection and install a true connection.
// if we keep the timer then when it expires we just remove and
// delete the provisional connection. if we install a true
// connection then we remove the timer.
CProvisionalClient* client;
try {
client = new CProvisionalClient(this, socket);
}
catch (XBase&) {
delete socket;
// handle error
return;
}
// start timer for handshake
CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(30.0, client);
// add client to client map
m_provisional.insert(client, timer);
}
void
CServer::recvClientHello(const CEvent&, void* vsocket)
{
IDataSocket* socket = reinterpret_cast<IDataSocket*>(vsocket);
LOG((CLOG_DEBUG1 "parsing hello reply"));
CClientProxy* proxy = NULL;
CString name("<unknown>");
try {
// limit the maximum length of the hello
UInt32 n = socket->getInputStream()->getSize();
if (n > kMaxHelloLength) {
LOG((CLOG_DEBUG1 "hello reply too long"));
throw XBadClient();
}
// parse the reply to hello
SInt16 major, minor;
CProtocolUtil::readf(socket->getInputStream(), kMsgHelloBack,
&major, &minor, &name);
// disallow invalid version numbers
if (major <= 0 || minor < 0) {
throw XIncompatibleClient(major, minor);
}
// convert name to canonical form (if any)
// FIXME -- need lock?
if (m_config.isScreen(name)) {
name = m_config.getCanonicalName(name);
}
// create client proxy for highest version supported by the client
LOG((CLOG_DEBUG1 "creating proxy for client \"%s\" version %d.%d", name.c_str(), major, minor));
if (major == 1) {
switch (minor) {
case 0:
// FIXME -- should pass socket, not input and output
proxy = new CClientProxy1_0(this, name, input, output);
break;
case 1:
// FIXME -- should pass socket, not input and output
proxy = new CClientProxy1_1(this, name, input, output);
break;
}
}
// hangup (with error) if version isn't supported
if (proxy == NULL) {
throw XIncompatibleClient(major, minor);
}
// the proxy now owns the socket
socket = NULL;
// add provisional client connection. this also checks if
// the client's name is okay (i.e. in the map and not in use).
addProvisionalClient(proxy);
// negotiate
// FIXME
// request the client's info and install a handler for it. note
// that the handshake timer is still going.
EVENTQUEUE->adoptHandler(IInputStream::getInputReadyEvent(),
socket->getInputStream(),
new TMethodEventJob<CServer>(this,
&CServer::recvClientInfo, socket));
LOG((CLOG_DEBUG1 "request info for client \"%s\"", name.c_str()));
// FIXME -- maybe should send this request some other way.
// could have the proxy itself handle the input. that makes sense
// but will have to check if that'll work. for one thing, the proxy
// will have to inform this object of any errors. it takes this
// object as a parameter so it probably can do that.
/*
proxy->open();
*/
return;
}
catch (XDuplicateClient& e) {
// client has duplicate name
LOG((CLOG_WARN "a client with name \"%s\" is already connected", e.getName().c_str()));
CProtocolUtil::writefNoError(proxy->getOutputStream(), kMsgEBusy);
}
catch (XUnknownClient& e) {
// client has unknown name
LOG((CLOG_WARN "a client with name \"%s\" is not in the map", e.getName().c_str()));
CProtocolUtil::writefNoError(proxy->getOutputStream(), kMsgEUnknown);
}
catch (XIncompatibleClient& e) {
// client is incompatible
LOG((CLOG_WARN "client \"%s\" has incompatible version %d.%d)", name.c_str(), e.getMajor(), e.getMinor()));
CProtocolUtil::writefNoError(socket->getOutputStream(),
kMsgEIncompatible,
kProtocolMajorVersion, kProtocolMinorVersion);
}
catch (XBadClient&) {
// client not behaving
LOG((CLOG_WARN "protocol error from client \"%s\"", name.c_str()));
CProtocolUtil::writefNoError(socket->getOutputStream(), kMsgEBad);
}
catch (XIO&) {
// client not behaving
LOG((CLOG_WARN "protocol error from client \"%s\"", name.c_str()));
CProtocolUtil::writefNoError(socket->getOutputStream(), kMsgEBad);
}
catch (XBase& e) {
// misc error
LOG((CLOG_WARN "error communicating with client \"%s\": %s", name.c_str(), e.what()));
}
// FIXME -- really clean up, including event handlers and timer.
// should have a method for this.
delete proxy;
delete socket;
}
void
CServer::recvClientInfo(const CEvent&, void* vsocket)
{
// FIXME -- get client's info and proxy becomes first class citizen.
// adopt new input handler aimed at clientInput. remove timer.
}
void
CServer::clientTimeout(const CEvent&, void* vsocket)
{
// FIXME -- client failed to connect fast enough. if client is
// already first class then just ignore this (that should never
// happen though because timer events are synthesized in the
// getEvent() so there can't be a race, so don't fuss over
// checking).
}
void
CServer::clientInput(const CEvent&, void* vsocket)
{
// FIXME -- client has sent a message
}
void
CServer::clientDisconnected(const CEvent&, void* vsocket)
{
// FIXME -- handle disconnection of client
}
void
CServer::createClientListener()
{
LOG((CLOG_DEBUG1 "creating socket to listen for clients"));
assert(m_socketFactory != NULL);
IListenSocket* listen = NULL;
try {
// create socket
listen = m_socketFactory->createListen();
// setup event handler
EVENTQUEUE->adoptHandler(IListenSocket::getConnectingEvent(), listen,
new TMethodEventJob<CServer>(this,
&CServer::clientConnecting));
// bind listen address
LOG((CLOG_DEBUG1 "binding listen socket"));
listen->bind(m_config.getSynergyAddress());
}
catch (XSocketAddressInUse& e) {
if (listen != NULL) {
EVENTQUEUE->removeHandler(
IListenSocket::getConnectingEvent(), listen);
delete listen;
}
setStatus(kError, e.what());
LOG((CLOG_WARN "bind failed: %s", e.what()));
// FIXME -- throw retry in X seconds object. the caller should
// catch this and optionally wait X seconds and try again.
// alternatively we could install a timer and retry automatically.
throw;
}
catch (...) {
if (listen != NULL) {
EVENTQUEUE->removeHandler(
IListenSocket::getConnectingEvent(), listen);
delete listen;
}
/* FIXME -- set some status and log error
setStatus(kNotRunning);
setStatus(kError, e.what());
LOG((CLOG_ERR "cannot listen for clients: %s", e.what()));
*/
throw;
}
m_listen = listen;
setStatus(kRunning);
LOG((CLOG_DEBUG1 "waiting for client connections"));
}

View File

@ -29,7 +29,6 @@
#include "stdvector.h"
class CClientProxy;
class CHTTPServer;
class CPrimaryClient;
class IClient;
class IDataSocket;
@ -151,6 +150,12 @@ public:
*/
void getConfig(CConfig*) const;
//! Get canonical screen name
/*!
Returns the canonical version of a screen name.
*/
CString getCanonicalName(const CString& name) const;
//! Get name
/*!
Returns the server's name passed to the c'tor
@ -295,12 +300,6 @@ private:
void runClient(void*);
CClientProxy* handshakeClient(IDataSocket*);
// thread method to accept incoming HTTP connections
void acceptHTTPClients(void*);
// thread method to process HTTP requests
void processHTTPRequest(void*);
// connection list maintenance
void addConnection(IClient*);
void removeConnection(const CString& name);
@ -381,11 +380,6 @@ private:
IClient* m_activeSaver;
SInt32 m_xSaver, m_ySaver;
// HTTP request processing stuff
CHTTPServer* m_httpServer;
CCondVar<SInt32> m_httpAvailable;
static const SInt32 s_httpMaxSimultaneousRequests;
// common state for screen switch tests. all tests are always
// trying to reach the same screen in the same direction.
EDirection m_switchDir;
@ -408,6 +402,15 @@ private:
CJobList m_statusJobs;
EStatus m_status;
CString m_statusMessage;
//---
/*
IListenSocket* m_listen;
typedef std::map<CProvisionalClient*,
CEventQueueTimer*> CProvisionalClients;
CProvisionalClients m_provisional;
*/
};
#endif

View File

@ -29,15 +29,15 @@ libserver_a_SOURCES = \
CClientProxy1_0.cpp \
CClientProxy1_1.cpp \
CConfig.cpp \
CHTTPServer.cpp \
CPrimaryClient.cpp \
CProvisionalClient.cpp \
CServer.cpp \
CClientProxy.h \
CClientProxy1_0.h \
CClientProxy1_1.h \
CConfig.h \
CHTTPServer.h \
CPrimaryClient.h \
CProvisionalClient.h \
CServer.h \
$(NULL)
INCLUDES = \
@ -46,7 +46,6 @@ INCLUDES = \
-I$(VDEPTH)/lib/base \
-I$(VDEPTH)/lib/mt \
-I$(VDEPTH)/lib/io \
-I$(VDEPTH)/lib/http \
-I$(VDEPTH)/lib/net \
-I$(VDEPTH)/lib/synergy \
-I$(VDEPTH)/lib/platform \

View File

@ -1,181 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "CInputPacketStream.h"
#include "CLock.h"
#include "CStopwatch.h"
//
// CInputPacketStream
//
CInputPacketStream::CInputPacketStream(IInputStream* stream, bool adopt) :
CInputStreamFilter(stream, adopt),
m_mutex(),
m_size(0),
m_buffer(&m_mutex, NULL)
{
// do nothing
}
CInputPacketStream::~CInputPacketStream()
{
// do nothing
}
void
CInputPacketStream::close()
{
getStream()->close();
}
UInt32
CInputPacketStream::read(void* buffer, UInt32 n, double timeout)
{
CLock lock(&m_mutex);
// wait for entire message to be read. return if stream
// hungup or timeout.
switch (waitForFullMessage(timeout)) {
case kData:
break;
case kHungup:
return 0;
case kTimedout:
return (UInt32)-1;
}
// limit number of bytes to read to the number of bytes left in the
// current message.
if (n > m_size) {
n = m_size;
}
// now read from our buffer
n = m_buffer.readNoLock(buffer, n, -1.0);
assert(n <= m_size);
m_size -= n;
return n;
}
UInt32
CInputPacketStream::getSize() const
{
CLock lock(&m_mutex);
return getSizeNoLock();
}
UInt32
CInputPacketStream::getSizeNoLock() const
{
CStopwatch timer(true);
while (!hasFullMessage() && getStream()->getSize() > 0) {
// read more data
if (getMoreMessage(-1.0) != kData) {
// stream hungup
return 0;
}
}
return m_size;
}
CInputPacketStream::EResult
CInputPacketStream::waitForFullMessage(double timeout) const
{
CStopwatch timer(true);
while (!hasFullMessage()) {
// compute remaining timeout
double t = timeout - timer.getTime();
if (timeout >= 0.0 && t <= 0.0) {
// timeout
return kTimedout;
}
// read more data
switch (getMoreMessage(t)) {
case kData:
break;
case kHungup:
// stream hungup
return kHungup;
case kTimedout:
// stream timed out
return kTimedout;
}
}
return kData;
}
CInputPacketStream::EResult
CInputPacketStream::getMoreMessage(double timeout) const
{
// read more data
char buffer[4096];
UInt32 n = getStream()->read(buffer, sizeof(buffer), timeout);
// return if stream timed out
if (n == (UInt32)-1) {
return kTimedout;
}
// return if stream hungup
if (n == 0) {
m_buffer.hangup();
return kHungup;
}
// append to our buffer
m_buffer.write(buffer, n);
return kData;
}
bool
CInputPacketStream::hasFullMessage() const
{
// get payload length if we don't have it yet
if (m_size == 0) {
// check if length field has been read yet
if (m_buffer.getSizeNoLock() < 4) {
// not enough data for payload length
return false;
}
// save payload length
UInt8 buffer[4];
UInt32 n = m_buffer.readNoLock(buffer, sizeof(buffer), -1.0);
assert(n == 4);
m_size = ((UInt32)buffer[0] << 24) |
((UInt32)buffer[1] << 16) |
((UInt32)buffer[2] << 8) |
(UInt32)buffer[3];
// if payload length is zero then discard null message
if (m_size == 0) {
return false;
}
}
assert(m_size > 0);
// we have the full message when we have at least m_size bytes in
// the buffer
return (m_buffer.getSizeNoLock() >= m_size);
}

View File

@ -1,51 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef CINPUTPACKETSTREAM_H
#define CINPUTPACKETSTREAM_H
#include "CInputStreamFilter.h"
#include "CBufferedInputStream.h"
#include "CMutex.h"
//! Packetizing input stream filter
/*!
Filters an input stream to extract packet by packet.
*/
class CInputPacketStream : public CInputStreamFilter {
public:
CInputPacketStream(IInputStream*, bool adoptStream = true);
~CInputPacketStream();
// IInputStream overrides
virtual void close();
virtual UInt32 read(void*, UInt32 maxCount, double timeout);
virtual UInt32 getSize() const;
private:
enum EResult { kData, kHungup, kTimedout };
UInt32 getSizeNoLock() const;
EResult waitForFullMessage(double timeout) const;
EResult getMoreMessage(double timeout) const;
bool hasFullMessage() const;
private:
CMutex m_mutex;
mutable UInt32 m_size;
mutable CBufferedInputStream m_buffer;
};
#endif

View File

@ -1,72 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "COutputPacketStream.h"
//
// COuputPacketStream
//
COutputPacketStream::COutputPacketStream(IOutputStream* stream, bool adopt) :
COutputStreamFilter(stream, adopt)
{
// do nothing
}
COutputPacketStream::~COutputPacketStream()
{
// do nothing
}
void
COutputPacketStream::close()
{
getStream()->close();
}
UInt32
COutputPacketStream::write(const void* buffer, UInt32 count)
{
// write the length of the payload
UInt8 length[4];
length[0] = (UInt8)((count >> 24) & 0xff);
length[1] = (UInt8)((count >> 16) & 0xff);
length[2] = (UInt8)((count >> 8) & 0xff);
length[3] = (UInt8)( count & 0xff);
UInt32 count2 = sizeof(length);
const UInt8* cbuffer = length;
while (count2 > 0) {
UInt32 n = getStream()->write(cbuffer, count2);
cbuffer += n;
count2 -= n;
}
// write the payload
count2 = count;
cbuffer = reinterpret_cast<const UInt8*>(buffer);
while (count2 > 0) {
UInt32 n = getStream()->write(cbuffer, count2);
cbuffer += n;
count2 -= n;
}
return count;
}
void
COutputPacketStream::flush()
{
getStream()->flush();
}

View File

@ -1,36 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2002 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef COUTPUTPACKETSTREAM_H
#define COUTPUTPACKETSTREAM_H
#include "COutputStreamFilter.h"
//! Packetizing output stream filter
/*!
Filters an output stream to create packets that include message
boundaries. Each write() is considered a single packet.
*/
class COutputPacketStream : public COutputStreamFilter {
public:
COutputPacketStream(IOutputStream*, bool adoptStream = true);
~COutputPacketStream();
// IOutputStream overrides
virtual void close();
virtual UInt32 write(const void*, UInt32 count);
virtual void flush();
};
#endif

View File

@ -0,0 +1,221 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2004 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "CPacketStreamFilter.h"
#include "IEventQueue.h"
#include "CLock.h"
#include "TMethodEventJob.h"
//
// CPacketStreamFilter
//
CPacketStreamFilter::CPacketStreamFilter(IStream* stream, bool adoptStream) :
CStreamFilter(stream, adoptStream),
m_size(0),
m_eventFilter(NULL),
m_inputShutdown(false)
{
// install event filter
getStream()->setEventFilter(new TMethodEventJob<CPacketStreamFilter>(
this, &CPacketStreamFilter::filterEvent, NULL));
}
CPacketStreamFilter::~CPacketStreamFilter()
{
delete getStream()->getEventFilter();
}
void
CPacketStreamFilter::close()
{
CLock lock(&m_mutex);
m_size = 0;
m_buffer.pop(m_buffer.getSize());
CStreamFilter::close();
}
UInt32
CPacketStreamFilter::read(void* buffer, UInt32 n)
{
if (n == 0) {
return 0;
}
CLock lock(&m_mutex);
// if not enough data yet then give up
if (!isReadyNoLock()) {
return 0;
}
// read no more than what's left in the buffered packet
if (n > m_size) {
n = m_size;
}
// read it
if (buffer != NULL) {
memcpy(buffer, m_buffer.peek(n), n);
}
m_buffer.pop(n);
m_size -= n;
// get next packet's size if we've finished with this packet and
// there's enough data to do so.
readPacketSize();
if (m_inputShutdown && m_size == 0) {
sendEvent(CEvent(getInputShutdownEvent(), getEventTarget(), NULL));
}
return n;
}
void
CPacketStreamFilter::write(const void* buffer, UInt32 count)
{
// write the length of the payload
UInt8 length[4];
length[0] = (UInt8)((count >> 24) & 0xff);
length[1] = (UInt8)((count >> 16) & 0xff);
length[2] = (UInt8)((count >> 8) & 0xff);
length[3] = (UInt8)( count & 0xff);
getStream()->write(length, sizeof(length));
// write the payload
getStream()->write(buffer, count);
}
void
CPacketStreamFilter::shutdownInput()
{
CLock lock(&m_mutex);
m_size = 0;
m_buffer.pop(m_buffer.getSize());
CStreamFilter::shutdownInput();
}
void
CPacketStreamFilter::setEventFilter(IEventJob* filter)
{
CLock lock(&m_mutex);
m_eventFilter = filter;
}
bool
CPacketStreamFilter::isReady() const
{
CLock lock(&m_mutex);
return isReadyNoLock();
}
UInt32
CPacketStreamFilter::getSize() const
{
CLock lock(&m_mutex);
return isReadyNoLock() ? m_size : 0;
}
IEventJob*
CPacketStreamFilter::getEventFilter() const
{
CLock lock(&m_mutex);
return m_eventFilter;
}
bool
CPacketStreamFilter::isReadyNoLock() const
{
return (m_size != 0 && m_buffer.getSize() >= m_size);
}
void
CPacketStreamFilter::readPacketSize()
{
// note -- m_mutex must be locked on entry
if (m_size == 0 && m_buffer.getSize() >= 4) {
UInt8 buffer[4];
memcpy(buffer, m_buffer.peek(sizeof(buffer)), sizeof(buffer));
m_buffer.pop(sizeof(buffer));
m_size = ((UInt32)buffer[0] << 24) |
((UInt32)buffer[1] << 16) |
((UInt32)buffer[2] << 8) |
(UInt32)buffer[3];
}
}
void
CPacketStreamFilter::readMore()
{
// note -- m_mutex must be locked on entry
// note if we have whole packet
bool wasReady = isReadyNoLock();
// read more data
char buffer[4096];
UInt32 n = getStream()->read(buffer, sizeof(buffer));
while (n > 0) {
m_buffer.write(buffer, n);
n = getStream()->read(buffer, sizeof(buffer));
}
// if we don't yet have the next packet size then get it,
// if possible.
readPacketSize();
// note if we now have a whole packet
bool isReady = isReadyNoLock();
// if we weren't ready before but now we are then send a
// input ready event apparently from the filtered stream.
if (wasReady != isReady) {
sendEvent(CEvent(getInputReadyEvent(), getEventTarget(), NULL));
}
}
void
CPacketStreamFilter::sendEvent(const CEvent& event)
{
if (m_eventFilter != NULL) {
m_eventFilter->run(event);
}
else {
EVENTQUEUE->addEvent(event);
}
}
void
CPacketStreamFilter::filterEvent(const CEvent& event, void*)
{
CLock lock(&m_mutex);
if (event.getType() == getInputReadyEvent()) {
readMore();
return;
}
else if (event.getType() == getInputShutdownEvent()) {
// discard this if we have buffered data
m_inputShutdown = true;
if (m_size == 0) {
sendEvent(CEvent(getInputShutdownEvent(), getEventTarget(), NULL));
}
return;
}
// pass event
sendEvent(event);
}

View File

@ -0,0 +1,57 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2004 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file COPYING that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef CPACKETSTREAMFILTER_H
#define CPACKETSTREAMFILTER_H
#include "CStreamFilter.h"
#include "CStreamBuffer.h"
#include "CMutex.h"
//! Packetizing stream filter
/*!
Filters a stream to read and write packets.
*/
class CPacketStreamFilter : public CStreamFilter {
public:
CPacketStreamFilter(IStream* stream, bool adoptStream = true);
~CPacketStreamFilter();
// IStream overrides
virtual void close();
virtual UInt32 read(void* buffer, UInt32 n);
virtual void write(const void* buffer, UInt32 n);
virtual void shutdownInput();
virtual void setEventFilter(IEventJob* filter);
virtual bool isReady() const;
virtual UInt32 getSize() const;
virtual IEventJob* getEventFilter() const;
private:
bool isReadyNoLock() const;
void readPacketSize();
void readMore();
void sendEvent(const CEvent&);
void filterEvent(const CEvent&, void*);
private:
CMutex m_mutex;
UInt32 m_size;
CStreamBuffer m_buffer;
IEventJob* m_eventFilter;
bool m_inputShutdown;
};
#endif

View File

@ -13,8 +13,7 @@
*/
#include "CProtocolUtil.h"
#include "IInputStream.h"
#include "IOutputStream.h"
#include "IStream.h"
#include "CLog.h"
#include "stdvector.h"
#include <cctype>
@ -25,51 +24,77 @@
//
void
CProtocolUtil::writef(IOutputStream* stream, const char* fmt, ...)
CProtocolUtil::writef(IStream* stream, const char* fmt, ...)
{
assert(stream != NULL);
assert(fmt != NULL);
LOG((CLOG_DEBUG2 "writef(%s)", fmt));
va_list args;
// determine total size to write
va_start(args, fmt);
UInt32 count = getLength(fmt, args);
UInt32 size = getLength(fmt, args);
va_end(args);
// done if nothing to write
if (count == 0) {
return;
}
// fill buffer
UInt8* buffer = new UInt8[count];
va_start(args, fmt);
writef(buffer, fmt, args);
vwritef(stream, fmt, size, args);
va_end(args);
// write buffer
UInt8* scan = buffer;
while (count > 0) {
const UInt32 n = stream->write(scan, count);
LOG((CLOG_DEBUG2 "wrote %d of %d bytes", n, count));
count -= n;
scan += n;
}
delete[] buffer;
}
void
CProtocolUtil::readf(IInputStream* stream, const char* fmt, ...)
bool
CProtocolUtil::readf(IStream* stream, const char* fmt, ...)
{
assert(stream != NULL);
assert(fmt != NULL);
LOG((CLOG_DEBUG2 "readf(%s)", fmt));
bool result;
va_list args;
va_start(args, fmt);
try {
vreadf(stream, fmt, args);
result = true;
}
catch (XIO&) {
result = false;
}
va_end(args);
return result;
}
void
CProtocolUtil::vwritef(IStream* stream,
const char* fmt, UInt32 size, va_list args)
{
assert(stream != NULL);
assert(fmt != NULL);
// done if nothing to write
if (size == 0) {
return;
}
// fill buffer
// FIXME -- can we use alloca?
UInt8* buffer = new UInt8[size];
writef(buffer, fmt, args);
try {
// write buffer
stream->write(buffer, size);
LOG((CLOG_DEBUG2 "wrote %d bytes", size));
delete[] buffer;
}
catch (XBase&) {
delete[] buffer;
throw;
}
}
bool
CProtocolUtil::vreadf(IStream* stream, const char* fmt, va_list args)
{
assert(stream != NULL);
assert(fmt != NULL);
// begin scanning
while (*fmt) {
@ -242,8 +267,6 @@ CProtocolUtil::readf(IInputStream* stream, const char* fmt, ...)
++fmt;
}
}
va_end(args);
}
UInt32
@ -485,7 +508,7 @@ CProtocolUtil::eatLength(const char** pfmt)
}
void
CProtocolUtil::read(IInputStream* stream, void* vbuffer, UInt32 count)
CProtocolUtil::read(IStream* stream, void* vbuffer, UInt32 count)
{
assert(stream != NULL);
assert(vbuffer != NULL);
@ -493,7 +516,7 @@ CProtocolUtil::read(IInputStream* stream, void* vbuffer, UInt32 count)
UInt8* buffer = reinterpret_cast<UInt8*>(vbuffer);
while (count > 0) {
// read more
UInt32 n = stream->read(buffer, count, -1.0);
UInt32 n = stream->read(buffer, count);
// bail if stream has hungup
if (n == 0) {

View File

@ -19,8 +19,7 @@
#include "XIO.h"
#include <stdarg.h>
class IInputStream;
class IOutputStream;
class IStream;
//! Synergy protocol utilities
/*!
@ -47,13 +46,14 @@ public:
- \%s -- converts CString* to stream of bytes
- \%S -- converts integer N and const UInt8* to stream of N bytes
*/
static void writef(IOutputStream*,
static void writef(IStream*,
const char* fmt, ...);
//! Read formatted data
/*!
Read formatted binary data from a buffer. This performs the
reverse operation of writef().
reverse operation of writef(). Returns true if the entire
format was successfully parsed, false otherwise.
Format specifiers are:
- \%\% -- read (and discard) a literal `\%'
@ -65,14 +65,19 @@ public:
- \%4I -- reads NBO 4 byte integers; arg is std::vector<UInt32>*
- \%s -- reads bytes; argument must be a CString*, \b not a char*
*/
static void readf(IInputStream*,
static bool readf(IStream*,
const char* fmt, ...);
private:
static void vwritef(IStream*,
const char* fmt, UInt32 size, va_list);
static bool vreadf(IStream*,
const char* fmt, va_list);
static UInt32 getLength(const char* fmt, va_list);
static void writef(void*, const char* fmt, va_list);
static UInt32 eatLength(const char** fmt);
static void read(IInputStream*, void*, UInt32);
static void read(IStream*, void*, UInt32);
};
//! Mismatched read exception

View File

@ -26,15 +26,13 @@ MAINTAINERCLEANFILES = \
noinst_LIBRARIES = libsynergy.a
libsynergy_a_SOURCES = \
CClipboard.cpp \
CInputPacketStream.cpp \
COutputPacketStream.cpp \
CPacketStreamFilter.cpp \
CProtocolUtil.cpp \
CScreen.cpp \
XScreen.cpp \
XSynergy.cpp \
CClipboard.h \
CInputPacketStream.h \
COutputPacketStream.h \
CPacketStreamFilter.h \
CProtocolUtil.h \
CScreen.h \
ClipboardTypes.h \