diff --git a/connection/connection.cpp b/connection/connection.cpp index 2d4b575..7e85ab7 100644 --- a/connection/connection.cpp +++ b/connection/connection.cpp @@ -1,728 +1,733 @@ /* Copyright (C) 2013 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LGPL. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Alternatively, this file is available under the Mozilla Public License Version 1.1. You may obtain a copy of the License at http://www.mozilla.org/MPL/ */ #include "connection.h" #include "connection_p.h" #include "arguments.h" #include "authclient.h" #include "event.h" #include "eventdispatcher_p.h" #include "icompletionlistener.h" #include "imessagereceiver.h" #include "iserver.h" #include "localsocket.h" #include "message.h" #include "message_p.h" #include "pendingreply.h" #include "pendingreply_p.h" #include "stringtools.h" #include #include #include class HelloReceiver : public IMessageReceiver { public: void handlePendingReplyFinished(PendingReply *pr) override { assert(pr == &m_helloReply); (void) pr; m_parent->handleHelloReply(); } PendingReply m_helloReply; // keep it here so it conveniently goes away when it's done ConnectionPrivate *m_parent; }; class ClientConnectedHandler : public ICompletionListener { public: ~ClientConnectedHandler() override { delete m_server; } void handleCompletion(void *) override { m_parent->handleClientConnected(); } IServer *m_server; ConnectionPrivate *m_parent; }; ConnectionPrivate::ConnectionPrivate(Connection *connection, EventDispatcher *dispatcher) : m_state(Unconnected), m_connection(connection), m_client(nullptr), m_receivingMessage(nullptr), m_transport(nullptr), m_helloReceiver(nullptr), m_clientConnectedHandler(nullptr), m_eventDispatcher(dispatcher), m_authClient(nullptr), m_defaultTimeout(25000), m_sendSerial(1), m_mainThreadConnection(nullptr) { } Connection::Connection(EventDispatcher *dispatcher, const ConnectAddress &ca) : d(new ConnectionPrivate(this, dispatcher)) { d->m_connectAddress = ca; assert(d->m_eventDispatcher); EventDispatcherPrivate::get(d->m_eventDispatcher)->m_connectionToNotify = d; if (ca.type() == ConnectAddress::Type::None || ca.role() == ConnectAddress::Role::None) { std::cerr << "\nConnection: connection constructor Exit A\n\n"; return; } if (ca.role() == ConnectAddress::Role::PeerServer) { // this sets up a server that will be destroyed after accepting exactly one connection d->m_clientConnectedHandler = new ClientConnectedHandler; d->m_clientConnectedHandler->m_server = IServer::create(ca); d->m_clientConnectedHandler->m_server->setEventDispatcher(dispatcher); d->m_clientConnectedHandler->m_server->setNewConnectionListener(d->m_clientConnectedHandler); d->m_clientConnectedHandler->m_parent = d; d->m_state = ConnectionPrivate::ServerWaitingForClient; } else { d->m_transport = ITransport::create(ca); d->m_transport->setEventDispatcher(dispatcher); if (ca.role() == ConnectAddress::Role::BusClient) { d->startAuthentication(); d->m_state = ConnectionPrivate::Authenticating; } else { assert(ca.role() == ConnectAddress::Role::PeerClient); // get ready to receive messages right away d->receiveNextMessage(); d->m_state = ConnectionPrivate::Connected; } } } Connection::Connection(EventDispatcher *dispatcher, CommRef mainConnectionRef) : d(new ConnectionPrivate(this, dispatcher)) { EventDispatcherPrivate::get(d->m_eventDispatcher)->m_connectionToNotify = d; d->m_mainThreadLink = std::move(mainConnectionRef.commutex); CommutexLocker locker(&d->m_mainThreadLink); assert(locker.hasLock()); Commutex *const id = d->m_mainThreadLink.id(); if (!id) { assert(false); std::cerr << "\nConnection: slave constructor Exit A\n\n"; return; // stay in Unconnected state } // TODO how do we handle m_state? d->m_mainThreadConnection = mainConnectionRef.connection; ConnectionPrivate *mainD = d->m_mainThreadConnection; // get the current values - if we got them from e.g. the CommRef they could be outdated // and we don't want to wait for more event ping-pong SpinLocker mainLocker(&mainD->m_lock); d->m_connectAddress = mainD->m_connectAddress; // register with the main Connection SecondaryConnectionConnectEvent *evt = new SecondaryConnectionConnectEvent(); evt->connection = d; evt->id = id; EventDispatcherPrivate::get(mainD->m_eventDispatcher) ->queueEvent(std::unique_ptr(evt)); } Connection::~Connection() { d->close(); delete d->m_transport; delete d->m_authClient; delete d->m_helloReceiver; delete d->m_receivingMessage; delete d; d = nullptr; } void Connection::close() { d->close(); } void ConnectionPrivate::close() { // Can't be main and secondary at the main time - it could be made to work, but what for? assert(m_secondaryThreadLinks.empty() || !m_mainThreadConnection); if (m_mainThreadConnection) { CommutexUnlinker unlinker(&m_mainThreadLink); if (unlinker.hasLock()) { SecondaryConnectionDisconnectEvent *evt = new SecondaryConnectionDisconnectEvent(); evt->connection = this; EventDispatcherPrivate::get(m_mainThreadConnection->m_eventDispatcher) ->queueEvent(std::unique_ptr(evt)); } } // Destroy whatever is suitable and available at a given time, in order to avoid things like // one secondary thread blocking another indefinitely and smaller dependency-related slowdowns. while (!m_secondaryThreadLinks.empty()) { for (auto it = m_secondaryThreadLinks.begin(); it != m_secondaryThreadLinks.end(); ) { CommutexUnlinker unlinker(&it->second, false); if (unlinker.willSucceed()) { if (unlinker.hasLock()) { MainConnectionDisconnectEvent *evt = new MainConnectionDisconnectEvent(); EventDispatcherPrivate::get(it->first->m_eventDispatcher) ->queueEvent(std::unique_ptr(evt)); } unlinker.unlinkNow(); // don't access the element after erasing it, finish it now it = m_secondaryThreadLinks.erase(it); } else { ++it; // don't block, try again next iteration } } } cancelAllPendingReplies(); EventDispatcherPrivate::get(m_eventDispatcher)->m_connectionToNotify = nullptr; } void ConnectionPrivate::startAuthentication() { m_authClient = new AuthClient(m_transport); m_authClient->setCompletionListener(this); } void ConnectionPrivate::handleHelloReply() { if (!m_helloReceiver->m_helloReply.hasNonErrorReply()) { delete m_helloReceiver; m_helloReceiver = nullptr; m_state = Unconnected; // TODO set an error, provide access to it, also set it on messages when trying to send / receive them return; } Arguments argList = m_helloReceiver->m_helloReply.reply()->arguments(); delete m_helloReceiver; m_helloReceiver = nullptr; Arguments::Reader reader(argList); assert(reader.state() == Arguments::String); cstring busName = reader.readString(); assert(reader.state() == Arguments::Finished); m_uniqueName = toStdString(busName); // tell current secondaries UniqueNameReceivedEvent evt; evt.uniqueName = m_uniqueName; for (auto &it : m_secondaryThreadLinks) { CommutexLocker otherLocker(&it.second); if (otherLocker.hasLock()) { EventDispatcherPrivate::get(it.first->m_eventDispatcher) ->queueEvent(std::unique_ptr(new UniqueNameReceivedEvent(evt))); } } m_state = Connected; } void ConnectionPrivate::handleClientConnected() { m_transport = m_clientConnectedHandler->m_server->takeNextClient(); delete m_clientConnectedHandler; m_clientConnectedHandler = nullptr; assert(m_transport); m_transport->setEventDispatcher(m_eventDispatcher); receiveNextMessage(); m_state = Connected; } void Connection::setDefaultReplyTimeout(int msecs) { d->m_defaultTimeout = msecs; } int Connection::defaultReplyTimeout() const { return d->m_defaultTimeout; } uint32 ConnectionPrivate::takeNextSerial() { uint32 ret; do { ret = m_sendSerial.fetch_add(1, std::memory_order_relaxed); } while (unlikely(ret == 0)); return ret; } Error ConnectionPrivate::prepareSend(Message *msg) { if (msg->serial() == 0) { if (!m_mainThreadConnection) { msg->setSerial(takeNextSerial()); } else { // we take a serial from the other Connection and then serialize locally in order to keep the CPU // expense of serialization local, even though it's more complicated than doing everything in the // other thread / Connection. CommutexLocker locker(&m_mainThreadLink); if (locker.hasLock()) { msg->setSerial(m_mainThreadConnection->takeNextSerial()); } else { return Error::LocalDisconnect; } } } MessagePrivate *const mpriv = MessagePrivate::get(msg); // this is unchanged by move()ing the owning Message. if (!mpriv->serialize()) { return mpriv->m_error; } return Error::NoError; } void ConnectionPrivate::sendPreparedMessage(Message msg) { MessagePrivate *const mpriv = MessagePrivate::get(&msg); mpriv->setCompletionListener(this); m_sendQueue.push_back(std::move(msg)); if (m_state == ConnectionPrivate::Connected && m_sendQueue.size() == 1) { // first in queue, don't wait for some other event to trigger sending mpriv->send(m_transport); } } PendingReply Connection::send(Message m, int timeoutMsecs) { if (timeoutMsecs == DefaultTimeout) { timeoutMsecs = d->m_defaultTimeout; } Error error = d->prepareSend(&m); PendingReplyPrivate *pendingPriv = new PendingReplyPrivate(d->m_eventDispatcher, timeoutMsecs); pendingPriv->m_connectionOrReply.connection = d; pendingPriv->m_receiver = nullptr; pendingPriv->m_serial = m.serial(); // even if we're handing off I/O to a main Connection, keep a record because that simplifies // aborting all pending replies when we disconnect from the main Connection, no matter which // side initiated the disconnection. d->m_pendingReplies.emplace(m.serial(), pendingPriv); if (error.isError()) { // Signal the error asynchronously, in order to get the same delayed completion callback as in // the non-error case. This should make the behavior more predictable and client code harder to // accidentally get wrong. To detect errors immediately, PendingReply::error() can be used. pendingPriv->m_error = error; pendingPriv->m_replyTimeout.start(0); } else { if (!d->m_mainThreadConnection) { d->sendPreparedMessage(std::move(m)); } else { CommutexLocker locker(&d->m_mainThreadLink); if (locker.hasLock()) { std::unique_ptr evt(new SendMessageWithPendingReplyEvent); evt->message = std::move(m); evt->connection = d; EventDispatcherPrivate::get(d->m_mainThreadConnection->m_eventDispatcher) ->queueEvent(std::move(evt)); } else { pendingPriv->m_error = Error::LocalDisconnect; } } } return PendingReply(pendingPriv); } Error Connection::sendNoReply(Message m) { // ### (when not called from send()) warn if sending a message without the noreply flag set? // doing that is wasteful, but might be common. needs investigation. Error error = d->prepareSend(&m); if (error.isError()) { return error; } // pass ownership to the send queue now because if the IO system decided to send the message without // going through an event loop iteration, handleCompletion would be called and expects the message to // be in the queue if (!d->m_mainThreadConnection) { d->sendPreparedMessage(std::move(m)); } else { CommutexLocker locker(&d->m_mainThreadLink); if (locker.hasLock()) { std::unique_ptr evt(new SendMessageEvent); evt->message = std::move(m); EventDispatcherPrivate::get(d->m_mainThreadConnection->m_eventDispatcher) ->queueEvent(std::move(evt)); } else { return Error::LocalDisconnect; } } return Error::NoError; } void Connection::waitForConnectionEstablished() { if (d->m_state != ConnectionPrivate::Authenticating) { return; } while (d->m_state == ConnectionPrivate::Authenticating) { d->m_authClient->handleTransportCanRead(); } if (d->m_state != ConnectionPrivate::AwaitingUniqueName) { return; } // Send the hello message assert(!d->m_sendQueue.empty()); // the hello message should be in the queue MessagePrivate *helloPriv = MessagePrivate::get(&d->m_sendQueue.front()); helloPriv->handleTransportCanWrite(); // Receive the hello reply while (d->m_state == ConnectionPrivate::AwaitingUniqueName) { MessagePrivate::get(d->m_receivingMessage)->handleTransportCanRead(); } } ConnectAddress Connection::connectAddress() const { return d->m_connectAddress; } std::string Connection::uniqueName() const { return d->m_uniqueName; } bool Connection::isConnected() const { return d->m_transport && d->m_transport->isOpen(); } EventDispatcher *Connection::eventDispatcher() const { return d->m_eventDispatcher; } IMessageReceiver *Connection::spontaneousMessageReceiver() const { return d->m_client; } void Connection::setSpontaneousMessageReceiver(IMessageReceiver *receiver) { d->m_client = receiver; } void ConnectionPrivate::handleCompletion(void *task) { switch (m_state) { case Authenticating: { assert(task == m_authClient); if (!m_authClient->isAuthenticated()) { m_state = Unconnected; } delete m_authClient; m_authClient = nullptr; if (m_state == Unconnected) { break; } m_state = AwaitingUniqueName; // Announce our presence to the bus and have it send some introductory information of its own Message hello = Message::createCall("/org/freedesktop/DBus", "org.freedesktop.DBus", "Hello"); hello.setExpectsReply(false); hello.setDestination(std::string("org.freedesktop.DBus")); MessagePrivate *const helloPriv = MessagePrivate::get(&hello); m_helloReceiver = new HelloReceiver; m_helloReceiver->m_helloReply = m_connection->send(std::move(hello)); // Small hack: Connection::send() refuses to really start sending if the connection isn't in // Connected state. So force the sending here to actually get to Connected state. helloPriv->send(m_transport); // Also ensure that the hello message is sent before any other messages that may have been // already enqueued by an API client hello = std::move(m_sendQueue.back()); m_sendQueue.pop_back(); m_sendQueue.push_front(std::move(hello)); m_helloReceiver->m_helloReply.setReceiver(m_helloReceiver); m_helloReceiver->m_parent = this; // get ready to receive the first message, the hello reply receiveNextMessage(); break; } case AwaitingUniqueName: // the code paths for these two states only diverge in the PendingReply handler case Connected: { assert(!m_authClient); if (!m_sendQueue.empty() && task == &m_sendQueue.front()) { m_sendQueue.pop_front(); if (!m_sendQueue.empty()) { MessagePrivate::get(&m_sendQueue.front())->send(m_transport); } } else { assert(task == m_receivingMessage); Message *const receivedMessage = m_receivingMessage; receiveNextMessage(); if (!maybeDispatchToPendingReply(receivedMessage)) { if (m_client) { m_client->handleSpontaneousMessageReceived(Message(std::move(*receivedMessage))); } // dispatch to other threads listening to spontaneous messages, if any for (auto it = m_secondaryThreadLinks.begin(); it != m_secondaryThreadLinks.end(); ) { SpontaneousMessageReceivedEvent *evt = new SpontaneousMessageReceivedEvent(); evt->message = *receivedMessage; CommutexLocker otherLocker(&it->second); if (otherLocker.hasLock()) { EventDispatcherPrivate::get(it->first->m_eventDispatcher) ->queueEvent(std::unique_ptr(evt)); ++it; } else { ConnectionPrivate *connection = it->first; it = m_secondaryThreadLinks.erase(it); discardPendingRepliesForSecondaryThread(connection); delete evt; } } delete receivedMessage; } } break; } default: // ### decide what to do here break; }; } bool ConnectionPrivate::maybeDispatchToPendingReply(Message *receivedMessage) { if (receivedMessage->type() != Message::MethodReturnMessage && receivedMessage->type() != Message::ErrorMessage) { return false; } auto it = m_pendingReplies.find(receivedMessage->replySerial()); if (it == m_pendingReplies.end()) { return false; } if (PendingReplyPrivate *pr = it->second.asPendingReply()) { m_pendingReplies.erase(it); assert(!pr->m_isFinished); pr->handleReceived(receivedMessage); } else { // forward to other thread's Connection ConnectionPrivate *connection = it->second.asConnection(); m_pendingReplies.erase(it); assert(connection); PendingReplySuccessEvent *evt = new PendingReplySuccessEvent; evt->reply = std::move(*receivedMessage); delete receivedMessage; EventDispatcherPrivate::get(connection->m_eventDispatcher)->queueEvent(std::unique_ptr(evt)); } return true; } void ConnectionPrivate::receiveNextMessage() { m_receivingMessage = new Message; MessagePrivate *const mpriv = MessagePrivate::get(m_receivingMessage); mpriv->setCompletionListener(this); mpriv->receive(m_transport); } void ConnectionPrivate::unregisterPendingReply(PendingReplyPrivate *p) { if (m_mainThreadConnection) { CommutexLocker otherLocker(&m_mainThreadLink); if (otherLocker.hasLock()) { PendingReplyCancelEvent *evt = new PendingReplyCancelEvent; evt->serial = p->m_serial; EventDispatcherPrivate::get(m_mainThreadConnection->m_eventDispatcher) ->queueEvent(std::unique_ptr(evt)); } } #ifndef NDEBUG auto it = m_pendingReplies.find(p->m_serial); assert(it != m_pendingReplies.end()); if (!m_mainThreadConnection) { assert(it->second.asPendingReply()); assert(it->second.asPendingReply() == p); } #endif m_pendingReplies.erase(p->m_serial); } void ConnectionPrivate::cancelAllPendingReplies() { // No locking because we should have no connections to other threads anymore at this point. // No const iteration followed by container clear because that has different semantics - many // things can happen in a callback... // In case we have pending replies for secondary threads, and we cancel all pending replies, // that is because we're shutting down, which we told the secondary thread, and it will deal // with bulk cancellation of replies. We just throw away our records about them. for (auto it = m_pendingReplies.begin() ; it != m_pendingReplies.end(); ) { PendingReplyPrivate *pendingPriv = it->second.asPendingReply(); it = m_pendingReplies.erase(it); if (pendingPriv) { // if from this thread pendingPriv->handleError(Error::LocalDisconnect); } } } void ConnectionPrivate::discardPendingRepliesForSecondaryThread(ConnectionPrivate *connection) { for (auto it = m_pendingReplies.begin() ; it != m_pendingReplies.end(); ) { if (it->second.asConnection() == connection) { it = m_pendingReplies.erase(it); // notification and deletion are handled on the event's source thread } else { ++it; } } } void ConnectionPrivate::processEvent(Event *evt) { // std::cerr << "ConnectionPrivate::processEvent() with event type " << evt->type << std::endl; switch (evt->type) { case Event::SendMessage: sendPreparedMessage(std::move(static_cast(evt)->message)); break; case Event::SendMessageWithPendingReply: { SendMessageWithPendingReplyEvent *pre = static_cast(evt); m_pendingReplies.emplace(pre->message.serial(), pre->connection); sendPreparedMessage(std::move(pre->message)); break; } case Event::SpontaneousMessageReceived: if (m_client) { SpontaneousMessageReceivedEvent *smre = static_cast(evt); m_client->handleSpontaneousMessageReceived(Message(std::move(smre->message))); } break; case Event::PendingReplySuccess: maybeDispatchToPendingReply(&static_cast(evt)->reply); break; case Event::PendingReplyFailure: { PendingReplyFailureEvent *prfe = static_cast(evt); const auto it = m_pendingReplies.find(prfe->m_serial); if (it == m_pendingReplies.end()) { // not a disaster, but when it happens in debug mode I want to check it out assert(false); break; } PendingReplyPrivate *pendingPriv = it->second.asPendingReply(); m_pendingReplies.erase(it); pendingPriv->handleError(prfe->m_error); break; } case Event::PendingReplyCancel: // This comes from a secondary thread, which handles PendingReply notification itself. m_pendingReplies.erase(static_cast(evt)->serial); break; case Event::SecondaryConnectionConnect: { SecondaryConnectionConnectEvent *sce = static_cast(evt); const auto it = find_if(m_unredeemedCommRefs.begin(), m_unredeemedCommRefs.end(), [sce](const CommutexPeer &item) { return item.id() == sce->id; } ); assert(it != m_unredeemedCommRefs.end()); const auto emplaced = m_secondaryThreadLinks.emplace(sce->connection, std::move(*it)).first; m_unredeemedCommRefs.erase(it); // "welcome package" - it's done (only) as an event to avoid locking order issues CommutexLocker locker(&emplaced->second); if (locker.hasLock()) { UniqueNameReceivedEvent *evt = new UniqueNameReceivedEvent; evt->uniqueName = m_uniqueName; EventDispatcherPrivate::get(sce->connection->m_eventDispatcher) ->queueEvent(std::unique_ptr(evt)); } break; } case Event::SecondaryConnectionDisconnect: { SecondaryConnectionDisconnectEvent *sde = static_cast(evt); // delete our records to make sure we don't call into it in the future! const auto found = m_secondaryThreadLinks.find(sde->connection); if (found == m_secondaryThreadLinks.end()) { // looks like we've noticed the disappearance of the other thread earlier return; } m_secondaryThreadLinks.erase(found); discardPendingRepliesForSecondaryThread(sde->connection); break; } case Event::MainConnectionDisconnect: // since the main thread *sent* us the event, it already knows to drop all our PendingReplies m_mainThreadConnection = nullptr; cancelAllPendingReplies(); break; case Event::UniqueNameReceived: // We get this when the unique name became available after we were linked up with the main thread m_uniqueName = static_cast(evt)->uniqueName; break; } } Connection::CommRef Connection::createCommRef() { // TODO this is a good time to clean up "dead" CommRefs, where the counterpart was destroyed. CommRef ret; ret.connection = d; std::pair link = CommutexPeer::createLink(); { SpinLocker mainLocker(&d->m_lock); d->m_unredeemedCommRefs.emplace_back(std::move(link.first)); } ret.commutex = std::move(link.second); return ret; } + +bool Connection::supportsPassingFileDescriptors() const +{ + return d->m_transport && d->m_transport->supportsPassingFileDescriptors(); +} diff --git a/connection/connection.h b/connection/connection.h index 138032f..d8c805e 100644 --- a/connection/connection.h +++ b/connection/connection.h @@ -1,105 +1,107 @@ /* Copyright (C) 2013 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LGPL. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Alternatively, this file is available under the Mozilla Public License Version 1.1. You may obtain a copy of the License at http://www.mozilla.org/MPL/ */ #ifndef CONNECTION_H #define CONNECTION_H #include "commutex.h" #include "types.h" #include class ConnectAddress; class ConnectionPrivate; class Error; class EventDispatcher; class IMessageReceiver; class Message; class PendingReply; class DFERRY_EXPORT Connection { public: enum ThreadAffinity { MainConnection = 0, ThreadLocalConnection }; // Reference for passing to another thread; it guarantees that the target Connection // either exists or not, but is not currently being destroyed. Yes, the data is all private. class CommRef { friend class Connection; ConnectionPrivate *connection; CommutexPeer commutex; }; // for connecting to the session or system bus Connection(EventDispatcher *dispatcher, const ConnectAddress &connectAddress); // for reusing the connection of a Connection in another thread Connection(EventDispatcher *dispatcher, CommRef otherConnection); ~Connection(); Connection(Connection &other) = delete; Connection &operator=(Connection &other) = delete; void close(); CommRef createCommRef(); + bool supportsPassingFileDescriptors() const; + void setDefaultReplyTimeout(int msecs); int defaultReplyTimeout() const; enum TimeoutSpecialValues { DefaultTimeout = -1, NoTimeout = -2 }; // if a message expects no reply, that is not absolutely binding; this method allows to send a message that // does not expect (request) a reply, but we get it if it comes - not terribly useful in most cases // NOTE: this takes ownership of the message! The message will be deleted after sending in some future // event loop iteration, so it is guaranteed to stay valid before the next event loop iteration. PendingReply send(Message m, int timeoutMsecs = DefaultTimeout); // Mostly same as above. // This one ignores the reply, if any. Reports any locally detectable errors in the return value. Error sendNoReply(Message m); void waitForConnectionEstablished(); ConnectAddress connectAddress() const; std::string uniqueName() const; bool isConnected() const; EventDispatcher *eventDispatcher() const; // TODO matching patterns for subscription; note that a signal requires path, interface and // "method" (signal name) of sender void subscribeToSignal(); IMessageReceiver *spontaneousMessageReceiver() const; void setSpontaneousMessageReceiver(IMessageReceiver *receiver); private: friend class ConnectionPrivate; ConnectionPrivate *d; }; #endif // CONNECTION_H diff --git a/serialization/message.cpp b/serialization/message.cpp index 54c56d8..5c40d26 100644 --- a/serialization/message.cpp +++ b/serialization/message.cpp @@ -1,1260 +1,1283 @@ /* Copyright (C) 2013 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LGPL. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Alternatively, this file is available under the Mozilla Public License Version 1.1. You may obtain a copy of the License at http://www.mozilla.org/MPL/ */ #include "message.h" #include "message_p.h" #include "basictypeio.h" #include "malloccache.h" #include "stringtools.h" #ifndef DFERRY_SERDES_ONLY #include "icompletionlistener.h" #include "itransport.h" #endif #include #include #include #include #include +#ifdef __unix__ +#include +#endif + #ifdef BIGENDIAN static const byte s_thisMachineEndianness = 'b'; #else static const byte s_thisMachineEndianness = 'l'; #endif struct MsgAllocCaches { MallocCache msgPrivate; MallocCache<256, 4> msgBuffer; }; thread_local static MsgAllocCaches msgAllocCaches; static const byte s_storageForHeader[Message::UnixFdsHeader + 1] = { 0, // dummy entry: there is no enum value for 0 0xf0 | 0, // PathHeader 0xf0 | 1, // InterfaceHeader 0xf0 | 2, // MethodHeader 0xf0 | 3, // ErrorNameHeader 0 | 0, // ReplySerialHeader 0xf0 | 4, // DestinationHeader 0xf0 | 5, // SenderHeader 0xf0 | 6, // SignatureHeader 0 | 1 // UnixFdsHeader }; static bool isStringHeader(int field) { return s_storageForHeader[field] & 0xf0; } static int indexOfHeader(int field) { return s_storageForHeader[field] & ~0xf0; } static const Message::VariableHeader s_stringHeaderAtIndex[VarHeaderStorage::s_stringHeaderCount] = { Message::PathHeader, Message::InterfaceHeader, Message::MethodHeader, Message::ErrorNameHeader, Message::DestinationHeader, Message::SenderHeader, Message::SignatureHeader }; static const Message::VariableHeader s_intHeaderAtIndex[VarHeaderStorage::s_intHeaderCount] = { Message::ReplySerialHeader, Message::UnixFdsHeader }; VarHeaderStorage::VarHeaderStorage() {} // initialization values are in class declaration VarHeaderStorage::VarHeaderStorage(const VarHeaderStorage &other) { // ### very suboptimal for (int i = 0; i < Message::UnixFdsHeader + 1; i++) { Message::VariableHeader vh = static_cast(i); if (other.hasHeader(vh)) { if (isStringHeader(vh)) { setStringHeader(vh, other.stringHeader(vh)); } else { setIntHeader(vh, other.intHeader(vh)); } } } } VarHeaderStorage::~VarHeaderStorage() { for (int i = 0; i < s_stringHeaderCount; i++) { const Message::VariableHeader field = s_stringHeaderAtIndex[i]; if (hasHeader(field)) { // ~basic_string() instead of ~string() to work around a GCC bug stringHeaders()[i].~basic_string(); } } } bool VarHeaderStorage::hasHeader(Message::VariableHeader header) const { return m_headerPresenceBitmap & (1u << header); } bool VarHeaderStorage::hasStringHeader(Message::VariableHeader header) const { return hasHeader(header) && isStringHeader(header); } bool VarHeaderStorage::hasIntHeader(Message::VariableHeader header) const { return hasHeader(header) && !isStringHeader(header); } std::string VarHeaderStorage::stringHeader(Message::VariableHeader header) const { return hasStringHeader(header) ? stringHeaders()[indexOfHeader(header)] : std::string(); } cstring VarHeaderStorage::stringHeaderRaw(Message::VariableHeader header) { // this one is supposed to be a const method in the intended use, but it is dangerous so // outwardly non-const is kind of okay as a warning cstring ret; assert(isStringHeader(header)); if (hasHeader(header)) { std::string &str = stringHeaders()[indexOfHeader(header)]; ret.ptr = const_cast(str.c_str()); ret.length = str.length(); } return ret; } void VarHeaderStorage::setStringHeader(Message::VariableHeader header, const std::string &value) { if (!isStringHeader(header)) { return; } const int idx = indexOfHeader(header); if (hasHeader(header)) { stringHeaders()[idx] = value; } else { m_headerPresenceBitmap |= 1u << header; new(stringHeaders() + idx) std::string(value); } } bool VarHeaderStorage::setStringHeader_deser(Message::VariableHeader header, cstring value) { assert(isStringHeader(header)); if (hasHeader(header)) { return false; } m_headerPresenceBitmap |= 1u << header; new(stringHeaders() + indexOfHeader(header)) std::string(value.ptr, value.length); return true; } void VarHeaderStorage::clearStringHeader(Message::VariableHeader header) { if (!isStringHeader(header)) { return; } if (hasHeader(header)) { m_headerPresenceBitmap &= ~(1u << header); stringHeaders()[indexOfHeader(header)].~basic_string(); } } uint32 VarHeaderStorage::intHeader(Message::VariableHeader header) const { return hasIntHeader(header) ? m_intHeaders[indexOfHeader(header)] : 0; } void VarHeaderStorage::setIntHeader(Message::VariableHeader header, uint32 value) { if (isStringHeader(header)) { return; } m_headerPresenceBitmap |= 1u << header; m_intHeaders[indexOfHeader(header)] = value; } bool VarHeaderStorage::setIntHeader_deser(Message::VariableHeader header, uint32 value) { assert(!isStringHeader(header)); if (hasHeader(header)) { return false; } m_headerPresenceBitmap |= 1u << header; m_intHeaders[indexOfHeader(header)] = value; return true; } void VarHeaderStorage::clearIntHeader(Message::VariableHeader header) { if (isStringHeader(header)) { return; } m_headerPresenceBitmap &= ~(1u << header); } // TODO think of copying signature from and to output! MessagePrivate::MessagePrivate(Message *parent) : m_message(parent), m_bufferPos(0), m_isByteSwapped(false), m_state(Empty), m_messageType(Message::InvalidMessage), m_flags(0), m_protocolVersion(1), m_dirty(true), m_headerLength(0), m_headerPadding(0), m_bodyLength(0), m_serial(0) {} MessagePrivate::MessagePrivate(const MessagePrivate &other, Message *parent) : m_message(parent), m_bufferPos(other.m_bufferPos), m_isByteSwapped(other.m_isByteSwapped), m_state(other.m_state), m_messageType(other.m_messageType), m_flags(other.m_flags), m_protocolVersion(other.m_protocolVersion), m_dirty(other.m_dirty), m_headerLength(other.m_headerLength), m_headerPadding(other.m_headerPadding), m_bodyLength(other.m_bodyLength), m_serial(other.m_serial), m_error(other.m_error), m_mainArguments(other.m_mainArguments), m_varHeaders(other.m_varHeaders) { if (other.m_buffer.ptr) { // we don't keep pointers into the buffer (only indexes), right? right? m_buffer.ptr = static_cast(malloc(other.m_buffer.length)); m_buffer.length = other.m_buffer.length; // Simplification: don't try to figure out which part of other.m_buffer contains "valid" data, // just copy everything. memcpy(m_buffer.ptr, other.m_buffer.ptr, other.m_buffer.length); +#ifdef __unix__ + // TODO ensure all "actual" file descriptor handling everywhere is inside this ifdef + // (note conditional compilation of whole file localsocket.cpp) + m_fileDescriptors.clear(); + for (int fd : other.m_fileDescriptors) { + int fdCopy = ::dup(fd); + if (fdCopy == -1) { + // TODO error... + } + m_fileDescriptors.push_back(fdCopy); + } +#endif } else { assert(!m_buffer.length); } // ### Maybe warn when copying a Message which is currently (de)serializing. It might even be impossible // to do that from client code. If that is the case, the "warning" could even be an assertion because // we should never do such a thing. } MessagePrivate::~MessagePrivate() { clearBuffer(); } Message::Message() : d(new(msgAllocCaches.msgPrivate.allocate()) MessagePrivate(this)) { } Message::Message(Message &&other) : d(other.d) { other.d = nullptr; d->m_message = this; } Message &Message::operator=(Message &&other) { if (this != &other) { if (d) { d->~MessagePrivate(); msgAllocCaches.msgPrivate.free(d); } d = other.d; if (other.d) { other.d = nullptr; d->m_message = this; } } return *this; } Message::Message(const Message &other) : d(nullptr) { if (!other.d) { return; } d = new(msgAllocCaches.msgPrivate.allocate()) MessagePrivate(*other.d, this); } Message &Message::operator=(const Message &other) { if (this != &other) { if (d) { d->~MessagePrivate(); msgAllocCaches.msgPrivate.free(d); } if (other.d) { // ### can be optimized by implementing and using assignment of MessagePrivate d = new(msgAllocCaches.msgPrivate.allocate()) MessagePrivate(*other.d, this); } else { d = nullptr; } } return *this; } - Message::~Message() { if (d) { d->~MessagePrivate(); msgAllocCaches.msgPrivate.free(d); d = nullptr; } } Error Message::error() const { return d->m_error; } void Message::setCall(const std::string &path, const std::string &interface, const std::string &method) { setType(MethodCallMessage); setPath(path); setInterface(interface); setMethod(method); } void Message::setCall(const std::string &path, const std::string &method) { setType(MethodCallMessage); setPath(path); setMethod(method); } void Message::setReplyTo(const Message &call) { setType(MethodReturnMessage); setDestination(call.sender()); setReplySerial(call.serial()); } void Message::setErrorReplyTo(const Message &call, const std::string &errorName) { setType(ErrorMessage); setErrorName(errorName); setDestination(call.sender()); setReplySerial(call.serial()); } void Message::setSignal(const std::string &path, const std::string &interface, const std::string &method) { setType(SignalMessage); setPath(path); setInterface(interface); setMethod(method); } Message Message::createCall(const std::string &path, const std::string &interface, const std::string &method) { Message ret; ret.setCall(path, interface, method); return ret; } Message Message::createCall(const std::string &path, const std::string &method) { Message ret; ret.setCall(path, method); return ret; } Message Message::createReplyTo(const Message &call) { Message ret; ret.setReplyTo(call); return ret; } Message Message::createErrorReplyTo(const Message &call, const std::string &errorName) { Message ret; ret.setErrorReplyTo(call, errorName); return ret; } Message Message::createSignal(const std::string &path, const std::string &interface, const std::string &method) { Message ret; ret.setSignal(path, interface, method); return ret; } struct VarHeaderPrinter { Message::VariableHeader field; const char *name; }; static const int stringHeadersCount = 7; static VarHeaderPrinter stringHeaderPrinters[stringHeadersCount] = { { Message::PathHeader, "path" }, { Message::InterfaceHeader, "interface" }, { Message::MethodHeader, "method" }, { Message::ErrorNameHeader, "error name" }, { Message::DestinationHeader, "destination" }, { Message::SenderHeader, "sender" }, { Message::SignatureHeader, "signature" } }; static const int intHeadersCount = 2; static VarHeaderPrinter intHeaderPrinters[intHeadersCount] = { { Message::ReplySerialHeader, "reply serial" }, { Message::UnixFdsHeader, "#unix fds" } }; static const int messageTypeCount = 5; static const char *printableMessageTypes[messageTypeCount] = { "", // handled in code "Method call", "Method return", "Method error return", "Signal" }; std::string Message::prettyPrint() const { std::string ret; if (d->m_messageType >= 1 && d->m_messageType < messageTypeCount) { ret += printableMessageTypes[d->m_messageType]; } else { return std::string("Invalid message.\n"); } std::ostringstream os; for (int i = 0; i < stringHeadersCount; i++ ) { bool isPresent = false; std::string str = stringHeader(stringHeaderPrinters[i].field, &isPresent); if (isPresent) { os << "; " << stringHeaderPrinters[i].name << ": \"" << str << '"'; } } for (int i = 0; i < intHeadersCount; i++ ) { bool isPresent = false; uint32 intValue = intHeader(intHeaderPrinters[i].field, &isPresent); if (isPresent) { os << "; " << intHeaderPrinters[i].name << ": " << intValue; } } ret += os.str(); ret += '\n'; ret += d->m_mainArguments.prettyPrint(); return ret; } Message::Type Message::type() const { return d->m_messageType; } void Message::setType(Type type) { if (d->m_messageType == type) { return; } d->m_dirty = true; d->m_messageType = type; setExpectsReply(d->m_messageType == MethodCallMessage); } uint32 Message::protocolVersion() const { return d->m_protocolVersion; } void Message::setSerial(uint32 serial) { d->m_serial = serial; if (d->m_state == MessagePrivate::Serialized && !d->m_dirty) { // performance hack: setSerial is likely to happen just before sending - don't re-serialize, // just patch it. byte *p = d->m_buffer.ptr; basic::writeUint32(p + 4 /* bytes */ + sizeof(uint32), d->m_serial); return; } d->m_dirty = true; } uint32 Message::serial() const { return d->m_serial; } std::string Message::path() const { return stringHeader(PathHeader, 0); } void Message::setPath(const std::string &path) { setStringHeader(PathHeader, path); } std::string Message::interface() const { return stringHeader(InterfaceHeader, 0); } void Message::setInterface(const std::string &interface) { setStringHeader(InterfaceHeader, interface); } std::string Message::method() const { return stringHeader(MethodHeader, 0); } void Message::setMethod(const std::string &method) { setStringHeader(MethodHeader, method); } std::string Message::errorName() const { return stringHeader(ErrorNameHeader, 0); } void Message::setErrorName(const std::string &errorName) { setStringHeader(ErrorNameHeader, errorName); } uint32 Message::replySerial() const { return intHeader(ReplySerialHeader, 0); } void Message::setReplySerial(uint32 replySerial) { setIntHeader(ReplySerialHeader, replySerial); } std::string Message::destination() const { return stringHeader(DestinationHeader, 0); } void Message::setDestination(const std::string &destination) { setStringHeader(DestinationHeader, destination); } std::string Message::sender() const { return stringHeader(SenderHeader, 0); } void Message::setSender(const std::string &sender) { setStringHeader(SenderHeader, sender); } std::string Message::signature() const { return stringHeader(SignatureHeader, 0); } uint32 Message::unixFdCount() const { return intHeader(UnixFdsHeader, 0); } -void Message::setUnixFdCount(uint32 fdCount) -{ - setIntHeader(UnixFdsHeader, fdCount); -} - std::string Message::stringHeader(VariableHeader header, bool *isPresent) const { const bool exists = d->m_varHeaders.hasStringHeader(header); if (isPresent) { *isPresent = exists; } return exists ? d->m_varHeaders.stringHeader(header) : std::string(); } void Message::setStringHeader(VariableHeader header, const std::string &value) { if (header == SignatureHeader) { // ### warning? - this is a public method, and setting the signature separately does not make sense return; } d->m_dirty = true; d->m_varHeaders.setStringHeader(header, value); } uint32 Message::intHeader(VariableHeader header, bool *isPresent) const { const bool exists = d->m_varHeaders.hasIntHeader(header); if (isPresent) { *isPresent = exists; } return d->m_varHeaders.intHeader(header); } void Message::setIntHeader(VariableHeader header, uint32 value) { d->m_dirty = true; d->m_varHeaders.setIntHeader(header, value); } bool Message::expectsReply() const { return (d->m_flags & MessagePrivate::NoReplyExpectedFlag) == 0; } void Message::setExpectsReply(bool expectsReply) { if (expectsReply) { d->m_flags &= ~MessagePrivate::NoReplyExpectedFlag; } else { d->m_flags |= MessagePrivate::NoReplyExpectedFlag; } } void Message::setArguments(Arguments arguments) { d->m_dirty = true; d->m_error = arguments.error(); - d->m_mainArguments = std::move(arguments); + const size_t fdCount = arguments.fileDescriptors().size(); + if (fdCount) { + d->m_varHeaders.setIntHeader(Message::UnixFdsHeader, fdCount); + } else { + d->m_varHeaders.clearIntHeader(Message::UnixFdsHeader); + } - cstring signature = d->m_mainArguments.signature(); + cstring signature = arguments.signature(); if (signature.length) { d->m_varHeaders.setStringHeader(Message::SignatureHeader, toStdString(signature)); } else { d->m_varHeaders.clearStringHeader(Message::SignatureHeader); } + d->m_mainArguments = std::move(arguments); } const Arguments &Message::arguments() const { return d->m_mainArguments; } static const uint32 s_properFixedHeaderLength = 12; static const uint32 s_extendedFixedHeaderLength = 16; #ifndef DFERRY_SERDES_ONLY void MessagePrivate::receive(ITransport *transport) { if (m_state >= FirstIoState) { // Can only do one I/O operation at a time std::cerr << "MessagePrivate::receive() Error A.\n"; return; } transport->addListener(this); setReadNotificationEnabled(true); m_state = MessagePrivate::Receiving; m_headerLength = 0; m_bodyLength = 0; } bool Message::isReceiving() const { return d->m_state == MessagePrivate::Receiving; } void MessagePrivate::send(ITransport *transport) { if (!serialize()) { std::cerr << "MessagePrivate::send() Error A.\n"; // m_error.setCode(); // notifyCompletionListener(); would call into Connection, but it's easier for Connection to handle // the error from non-callback code, directly in the caller of send(). return; } transport->addListener(this); setWriteNotificationEnabled(true); m_state = MessagePrivate::Sending; } bool Message::isSending() const { return d->m_state == MessagePrivate::Sending; } void MessagePrivate::setCompletionListener(ICompletionListener *listener) { m_completionListener = listener; } void MessagePrivate::notifyCompletionListener() { if (m_completionListener) { m_completionListener->handleCompletion(m_message); } } void MessagePrivate::handleTransportCanRead() { if (m_state != Receiving) { return; } bool isError = false; chunk in; do { uint32 readMax = 0; if (!m_headerLength) { // the message might only consist of the header, so we must be careful to avoid reading // data meant for the next message readMax = s_extendedFixedHeaderLength - m_bufferPos; } else { // reading variable headers and/or body readMax = m_headerLength + m_bodyLength - m_bufferPos; } reserveBuffer(m_bufferPos + readMax); const bool headersDone = m_headerLength > 0 && m_bufferPos >= m_headerLength; if (m_bufferPos == 0) { // File descriptors should arrive only with the first byte in = transport()->readWithFileDescriptors(m_buffer.ptr + m_bufferPos, readMax, &m_fileDescriptors); } else { in = transport()->read(m_buffer.ptr + m_bufferPos, readMax); } m_bufferPos += in.length; assert(m_bufferPos <= m_buffer.length); if (!headersDone) { if (m_headerLength == 0 && m_bufferPos >= s_extendedFixedHeaderLength) { if (!deserializeFixedHeaders()) { isError = true; break; } } if (m_headerLength > 0 && m_bufferPos >= m_headerLength) { - if (!deserializeVariableHeaders()) { + if (deserializeVariableHeaders()) { + const uint32 fdsCount = m_varHeaders.intHeader(Message::UnixFdsHeader); + if (fdsCount != m_fileDescriptors.size()) { + isError = true; + break; + } + } else { isError = true; break; } } } if (m_headerLength > 0 && m_bufferPos >= m_headerLength + m_bodyLength) { // all done! assert(m_bufferPos == m_headerLength + m_bodyLength); setReadNotificationEnabled(false); m_state = Serialized; chunk bodyData(m_buffer.ptr + m_headerLength, m_bodyLength); m_mainArguments = Arguments(nullptr, m_varHeaders.stringHeaderRaw(Message::SignatureHeader), bodyData, std::move(m_fileDescriptors), m_isByteSwapped); m_fileDescriptors.clear(); // put it into a well-defined state assert(!isError); transport()->removeListener(this); notifyCompletionListener(); // do not access members after this because it might delete us! break; } if (!transport()->isOpen()) { isError = true; break; } } while (in.length); if (isError) { setReadNotificationEnabled(false); m_state = Empty; clearBuffer(); transport()->removeListener(this); notifyCompletionListener(); // TODO reset other data members, generally revisit error handling to make it robust } } void MessagePrivate::handleTransportCanWrite() { if (m_state != Sending) { return; } while (true) { assert(m_buffer.length >= m_bufferPos); const uint32 toWrite = m_buffer.length - m_bufferPos; if (!toWrite) { setWriteNotificationEnabled(false); m_state = Serialized; transport()->removeListener(this); assert(transport() == nullptr); notifyCompletionListener(); break; } uint32 written = 0; if (m_bufferPos == 0) { written = transport()->writeWithFileDescriptors(chunk(m_buffer.ptr + m_bufferPos, toWrite), m_mainArguments.fileDescriptors()); } else { written = transport()->write(chunk(m_buffer.ptr + m_bufferPos, toWrite)); } if (written <= 0) { // TODO error handling break; } m_bufferPos += written; } } #endif // !DFERRY_SERDES_ONLY chunk Message::serializeAndView() { chunk ret; // one return variable to enable return value optimization (RVO) in gcc if (d->m_state >= MessagePrivate::FirstIoState) { return ret; } if (!d->m_buffer.length && !d->serialize()) { // TODO report error? return ret; } ret = d->m_buffer; return ret; } std::vector Message::save() { std::vector ret; if (!d->serialize()) { return ret; } ret.reserve(d->m_buffer.length); for (uint32 i = 0; i < d->m_buffer.length; i++) { ret.push_back(d->m_buffer.ptr[i]); } return ret; } void Message::deserializeAndTake(chunk memOwnership) { if (d->m_state >= MessagePrivate::FirstIoState) { free(memOwnership.ptr); return; } d->m_headerLength = 0; d->m_bodyLength = 0; d->clearBuffer(); d->m_buffer = memOwnership; d->m_bufferPos = d->m_buffer.length; bool ok = d->m_buffer.length >= s_extendedFixedHeaderLength; ok = ok && d->deserializeFixedHeaders(); ok = ok && d->m_buffer.length >= d->m_headerLength; ok = ok && d->deserializeVariableHeaders(); ok = ok && d->m_buffer.length == d->m_headerLength + d->m_bodyLength; if (!ok) { d->m_state = MessagePrivate::Empty; d->clearBuffer(); return; } chunk bodyData(d->m_buffer.ptr + d->m_headerLength, d->m_bodyLength); d->m_mainArguments = Arguments(nullptr, d->m_varHeaders.stringHeaderRaw(SignatureHeader), bodyData, d->m_isByteSwapped); d->m_state = MessagePrivate::Serialized; } // This does not return bool because full validation of the main arguments would take quite // a few cycles. Validating only the header of the message doesn't seem to be worth it. void Message::load(const std::vector &data) { if (d->m_state >= MessagePrivate::FirstIoState || data.empty()) { return; } chunk buf; buf.length = data.size(); buf.ptr = reinterpret_cast(malloc(buf.length)); deserializeAndTake(buf); } bool MessagePrivate::requiredHeadersPresent() { m_error = checkRequiredHeaders(); return !m_error.isError(); } Error MessagePrivate::checkRequiredHeaders() const { if (m_serial == 0) { return Error::MessageSerial; } if (m_protocolVersion != 1) { return Error::MessageProtocolVersion; } // might want to check for DestinationHeader if the transport is a bus (not peer-to-peer) // very strange that this isn't in the spec! switch (m_messageType) { case Message::SignalMessage: // required: PathHeader, InterfaceHeader, MethodHeader if (!m_varHeaders.hasStringHeader(Message::InterfaceHeader)) { return Error::MessageInterface; } // fall through case Message::MethodCallMessage: // required: PathHeader, MethodHeader if (!m_varHeaders.hasStringHeader(Message::PathHeader)) { return Error::MessagePath; } if (!m_varHeaders.hasStringHeader(Message::MethodHeader)) { return Error::MessageMethod; } break; case Message::ErrorMessage: // required: ErrorNameHeader, ReplySerialHeader if (!m_varHeaders.hasStringHeader(Message::ErrorNameHeader)) { return Error::MessageErrorName; } // fall through case Message::MethodReturnMessage: // required: ReplySerialHeader if (!m_varHeaders.hasIntHeader(Message::ReplySerialHeader) ) { return Error::MessageReplySerial; } break; case Message::InvalidMessage: default: return Error::MessageType; } return Error::NoError; } bool MessagePrivate::deserializeFixedHeaders() { assert(m_bufferPos >= s_extendedFixedHeaderLength); byte *p = m_buffer.ptr; byte endianness = *p++; if (endianness != 'l' && endianness != 'B') { return false; } m_isByteSwapped = endianness != s_thisMachineEndianness; // TODO validate the values read here m_messageType = static_cast(*p++); m_flags = *p++; m_protocolVersion = *p++; m_bodyLength = basic::readUint32(p, m_isByteSwapped); m_serial = basic::readUint32(p + sizeof(uint32), m_isByteSwapped); // peek into the var-length header and use knowledge about array serialization to infer the // number of bytes still required for the header uint32 varArrayLength = basic::readUint32(p + 2 * sizeof(uint32), m_isByteSwapped); uint32 unpaddedHeaderLength = s_extendedFixedHeaderLength + varArrayLength; m_headerLength = align(unpaddedHeaderLength, 8); m_headerPadding = m_headerLength - unpaddedHeaderLength; return m_headerLength + m_bodyLength <= Arguments::MaxMessageLength; } bool MessagePrivate::deserializeVariableHeaders() { // use Arguments to parse the variable header fields // HACK: the fake first int argument is there to start the Arguments's data 8 byte aligned byte *base = m_buffer.ptr + s_properFixedHeaderLength - sizeof(int32); chunk headerData(base, m_headerLength - m_headerPadding - s_properFixedHeaderLength + sizeof(int32)); cstring varHeadersSig("ia(yv)"); Arguments argList(nullptr, varHeadersSig, headerData, m_isByteSwapped); Arguments::Reader reader(argList); assert(reader.isValid()); if (reader.state() != Arguments::Int32) { return false; } reader.readInt32(); if (reader.state() != Arguments::BeginArray) { return false; } reader.beginArray(); while (reader.state() == Arguments::BeginStruct) { reader.beginStruct(); const byte headerField = reader.readByte(); if (headerField < Message::PathHeader || headerField > Message::UnixFdsHeader) { return false; } const Message::VariableHeader eHeader = static_cast(headerField); reader.beginVariant(); bool ok = true; // short-circuit evaluation ftw if (isStringHeader(headerField)) { if (headerField == Message::PathHeader) { ok = ok && reader.state() == Arguments::ObjectPath; ok = ok && m_varHeaders.setStringHeader_deser(eHeader, reader.readObjectPath()); } else if (headerField == Message::SignatureHeader) { ok = ok && reader.state() == Arguments::Signature; // The spec allows having no signature header, which means "empty signature". However... // We do not drop empty signature headers when deserializing, in order to preserve // the original message contents. This could be useful for debugging and testing. ok = ok && m_varHeaders.setStringHeader_deser(eHeader, reader.readSignature()); } else { ok = ok && reader.state() == Arguments::String; ok = ok && m_varHeaders.setStringHeader_deser(eHeader, reader.readString()); } } else { ok = ok && reader.state() == Arguments::Uint32; - if (headerField == Message::UnixFdsHeader) { - reader.readUint32(); // discard it, for now (TODO) - } else { - ok = ok && m_varHeaders.setIntHeader_deser(eHeader, reader.readUint32()); - } + ok = ok && m_varHeaders.setIntHeader_deser(eHeader, reader.readUint32()); } if (!ok) { return false; } reader.endVariant(); reader.endStruct(); } reader.endArray(); // check that header->body padding is in fact zero filled base = m_buffer.ptr; for (uint32 i = m_headerLength - m_headerPadding; i < m_headerLength; i++) { if (base[i] != '\0') { return false; } } return true; } bool MessagePrivate::serialize() { if (m_state >= FirstIoState) { // Marshalled data must not be touched while doing I/O return false; } if (m_state == Serialized && !m_dirty) { return true; } clearBuffer(); if (m_error.isError() || !requiredHeadersPresent()) { return false; } Arguments headerArgs = serializeVariableHeaders(); // we need to cut out alignment padding bytes 4 to 7 in the variable header data stream because // the original dbus code aligns based on address in the final data stream // (offset s_properFixedHeaderLength == 12), we align based on address in the Arguments's buffer // (offset 0) - note that our modification keeps the stream valid because length is measured from end // of padding assert(headerArgs.data().length > 0); // if this fails the headerLength hack will break down const uint32 unalignedHeaderLength = s_properFixedHeaderLength + headerArgs.data().length - sizeof(uint32); m_headerLength = align(unalignedHeaderLength, 8); m_bodyLength = m_mainArguments.data().length; const uint32 messageLength = m_headerLength + m_bodyLength; if (messageLength > Arguments::MaxMessageLength) { m_error.setCode(Error::ArgumentsTooLong); return false; } reserveBuffer(messageLength); serializeFixedHeaders(); // copy header data: uint32 length... memcpy(m_buffer.ptr + s_properFixedHeaderLength, headerArgs.data().ptr, sizeof(uint32)); // skip four bytes of padding and copy the rest memcpy(m_buffer.ptr + s_properFixedHeaderLength + sizeof(uint32), headerArgs.data().ptr + 2 * sizeof(uint32), headerArgs.data().length - 2 * sizeof(uint32)); // zero padding between variable headers and message body for (uint32 i = unalignedHeaderLength; i < m_headerLength; i++) { m_buffer.ptr[i] = '\0'; } // copy message body (if any - arguments are not mandatory) if (m_mainArguments.data().length) { memcpy(m_buffer.ptr + m_headerLength, m_mainArguments.data().ptr, m_mainArguments.data().length); } m_bufferPos = m_headerLength + m_mainArguments.data().length; assert(m_bufferPos <= m_buffer.length); // for the upcoming message sending, "reuse" m_bufferPos for read position (formerly write position), // and m_buffer.length for end of data to read (formerly buffer capacity) m_buffer.length = m_bufferPos; m_bufferPos = 0; m_dirty = false; m_state = Serialized; return true; } void MessagePrivate::serializeFixedHeaders() { assert(m_buffer.length >= s_extendedFixedHeaderLength); byte *p = m_buffer.ptr; *p++ = s_thisMachineEndianness; *p++ = byte(m_messageType); *p++ = m_flags; *p++ = m_protocolVersion; basic::writeUint32(p, m_bodyLength); basic::writeUint32(p + sizeof(uint32), m_serial); } static void doVarHeaderPrologue(Arguments::Writer *writer, Message::VariableHeader field) { writer->beginStruct(); writer->writeByte(byte(field)); } Arguments MessagePrivate::serializeVariableHeaders() { Arguments::Writer writer; // note that we don't have to deal with empty arrays because all valid message types require // at least one of the variable headers writer.beginArray(); for (int i = 0; i < VarHeaderStorage::s_stringHeaderCount; i++) { const Message::VariableHeader field = s_stringHeaderAtIndex[i]; if (m_varHeaders.hasHeader(field)) { doVarHeaderPrologue(&writer, field); const std::string &str = m_varHeaders.stringHeaders()[i]; if (field == Message::PathHeader) { writer.writeVariantForMessageHeader('o'); writer.writeObjectPath(cstring(str.c_str(), str.length())); } else if (field == Message::SignatureHeader) { writer.writeVariantForMessageHeader('g'); writer.writeSignature(cstring(str.c_str(), str.length())); } else { writer.writeVariantForMessageHeader('s'); writer.writeString(cstring(str.c_str(), str.length())); } writer.fixupAfterWriteVariantForMessageHeader(); writer.endStruct(); if (unlikely(writer.error().isError())) { static const Error::Code stringHeaderErrors[VarHeaderStorage::s_stringHeaderCount] = { Error::MessagePath, Error::MessageInterface, Error::MessageMethod, Error::MessageErrorName, Error::MessageDestination, Error::MessageSender, Error::MessageSignature }; m_error.setCode(stringHeaderErrors[i]); return Arguments(); } } } for (int i = 0; i < VarHeaderStorage::s_intHeaderCount; i++) { const Message::VariableHeader field = s_intHeaderAtIndex[i]; if (m_varHeaders.hasHeader(field)) { doVarHeaderPrologue(&writer, field); writer.writeVariantForMessageHeader('u'); writer.writeUint32(m_varHeaders.m_intHeaders[i]); writer.fixupAfterWriteVariantForMessageHeader(); writer.endStruct(); } } writer.endArray(); return writer.finish(); } void MessagePrivate::clearBuffer() { if (m_buffer.ptr) { free(m_buffer.ptr); m_buffer = chunk(); m_bufferPos = 0; } else { assert(m_buffer.length == 0); assert(m_bufferPos == 0); } +#ifdef __unix__ + for (int fd : m_fileDescriptors) { + ::close(fd); + } +#endif m_fileDescriptors.clear(); } static uint32 nextPowerOf2(uint32 x) { --x; x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; return ++x; } void MessagePrivate::reserveBuffer(uint32 newLen) { const uint32 oldLen = m_buffer.length; if (newLen <= oldLen) { return; } if (newLen <= 256) { assert(oldLen == 0); newLen = 256; m_buffer.ptr = reinterpret_cast(msgAllocCaches.msgBuffer.allocate()); } else { newLen = nextPowerOf2(newLen); if (oldLen == 256) { byte *newAlloc = reinterpret_cast(malloc(newLen)); memcpy(newAlloc, m_buffer.ptr, oldLen); msgAllocCaches.msgBuffer.free(m_buffer.ptr); m_buffer.ptr = newAlloc; } else { m_buffer.ptr = reinterpret_cast(realloc(m_buffer.ptr, newLen)); } } m_buffer.length = newLen; } diff --git a/serialization/message.h b/serialization/message.h index 9f696b1..7705f31 100644 --- a/serialization/message.h +++ b/serialization/message.h @@ -1,162 +1,162 @@ /* Copyright (C) 2013 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LGPL. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Alternatively, this file is available under the Mozilla Public License Version 1.1. You may obtain a copy of the License at http://www.mozilla.org/MPL/ */ #ifndef MESSAGE_H #define MESSAGE_H #include "arguments.h" #include "types.h" #include #include class Arguments; class Error; class MessagePrivate; class DFERRY_EXPORT Message { public: // this class contains header data in deserialized form (maybe also serialized) and the payload // in serialized form Message(); // constructs an invalid message (to be filled in later, usually) ~Message(); // prefer these over copy construction / assignment whenever possible, for performance reasons Message(Message &&other); Message &operator=(Message &&other); Message(const Message &other); Message &operator=(const Message &other); // error (if any) propagates to PendingReply, so it is still available later Error error() const; // convenience void setCall(const std::string &path, const std::string &interface, const std::string &method); void setCall(const std::string &path, const std::string &method); // deprecated? remove? void setReplyTo(const Message &call); // fills in all available details as appropriate for a reply void setErrorReplyTo(const Message &call, const std::string &errorName); void setSignal(const std::string &path, const std::string &interface, const std::string &method); // decadence static Message createCall(const std::string &path, const std::string &interface, const std::string &method); static Message createCall(const std::string &path, const std::string &method); static Message createReplyTo(const Message &call); static Message createErrorReplyTo(const Message &call, const std::string &errorName); static Message createSignal(const std::string &path, const std::string &interface, const std::string &method); std::string prettyPrint() const; enum Type { InvalidMessage = 0, MethodCallMessage, MethodReturnMessage, ErrorMessage, SignalMessage }; Type type() const; void setType(Type type); uint32 protocolVersion() const; enum VariableHeader { PathHeader = 1, InterfaceHeader, MethodHeader, // called "member" in the spec ErrorNameHeader, ReplySerialHeader, DestinationHeader, SenderHeader, SignatureHeader, - UnixFdsHeader + UnixFdsHeader // TODO UnixFdCountHeader }; // enum-based access to headers // These are validated during serialization, not now; the message cannot expected to be in a // completely valid state before that anyway. Yes, we could validate some things, but let's just // do it all at once. std::string stringHeader(VariableHeader header, bool *isPresent = nullptr) const; void setStringHeader(VariableHeader header, const std::string &value); uint32 intHeader(VariableHeader header, bool *isPresent = nullptr) const; void setIntHeader(VariableHeader header, uint32 value); // convenience access to headers, these directly call enum-based getters / setters std::string path() const; void setPath(const std::string &path); std::string interface() const; void setInterface(const std::string &interface); std::string method() const; void setMethod(const std::string &method); std::string errorName() const; void setErrorName(const std::string &errorName); uint32 replySerial() const; void setReplySerial(uint32 replySerial); std::string destination() const; void setDestination(const std::string &destination); std::string sender() const; void setSender(const std::string &sender); std::string signature() const; // no setSignature() - setArguments() also sets the signature uint32 unixFdCount() const; - void setUnixFdCount(uint32 fdCount); + // no setUnixFdCount() - setArguments() also sets the Unix file descriptor count bool expectsReply() const; void setExpectsReply(bool); // setArguments also sets the signature header of the message void setArguments(Arguments arguments); const Arguments &arguments() const; std::vector save(); void load(const std::vector &data); // TODO actual guarantees? // Serialize the message and return a view on the serialized data. The view points to memory that // is still owned by the Message instance. It is valid as long as no non-const methods are called // on the message. Well, that's the idea. In practice, it is best to copy out the data ASAP. // If the message could not be serialized, an empty chunk is returned. chunk serializeAndView(); // Deserialize the message from chunk memOwnership and take ownership. memOwnership.ptr must // point to the beginning of a malloc() ed block of data. memOwnership.length is the length // of the serialized data, but the malloc()ed chunk may be larger. void deserializeAndTake(chunk memOwnership); // The rest of public methods is low-level API that should only be used in very special situations void setSerial(uint32 serial); uint32 serial() const; #ifndef DFERRY_SERDES_ONLY bool isReceiving() const; bool isSending() const; #endif private: friend class MessagePrivate; MessagePrivate *d; }; #endif // MESSAGE_H diff --git a/tests/serialization/tst_arguments.cpp b/tests/serialization/tst_arguments.cpp index 81206a1..639addd 100644 --- a/tests/serialization/tst_arguments.cpp +++ b/tests/serialization/tst_arguments.cpp @@ -1,1952 +1,1967 @@ /* Copyright (C) 2013 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LGPL. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Alternatively, this file is available under the Mozilla Public License Version 1.1. You may obtain a copy of the License at http://www.mozilla.org/MPL/ */ #include "arguments.h" #include "../testutil.h" #include #include #include // Handy helpers static void printChunk(chunk a) { std::cout << "Array: "; for (uint32 i = 0; i < a.length; i++) { std::cout << int(a.ptr[i]) << '|'; } std::cout << '\n'; } static bool chunksEqual(chunk a1, chunk a2) { if (a1.length != a2.length) { std::cout << "Different lengths.\n"; printChunk(a1); printChunk(a2); return false; } for (uint32 i = 0; i < a1.length; i++) { if (a1.ptr[i] != a2.ptr[i]) { std::cout << "Different content.\n"; printChunk(a1); printChunk(a2); return false; } } return true; } static bool stringsEqual(cstring s1, cstring s2) { return chunksEqual(chunk(s1.ptr, s1.length), chunk(s2.ptr, s2.length)); } static void maybeBeginDictEntry(Arguments::Writer *writer) { (void) writer; #ifdef WITH_DICT_ENTRY writer->beginDictEntry(); #endif } static void maybeEndDictEntry(Arguments::Writer *writer) { (void) writer; #ifdef WITH_DICT_ENTRY writer->endDictEntry(); #endif } // This class does: // 1) iterates over the full Arguments with m_reader // 2) skips whole aggregates at and below nesting level m_skipAggregatesFromLevel with m_skippingReader // 3) skips nil arrays at and below nil array nesting level m_skipNilArraysFromLevel with m_skippingReader // It even skips aggregates inside nil arrays as 2) + 3) imply. // It checks: // a) where nothing is skipped, that the aggregate structure and data read are the same class SkipChecker { public: SkipChecker(Arguments::Reader *reader, Arguments::Reader *skippingReader, int skipAggregatesFromLevel, int skipNilArraysFromLevel) : m_nestingLevel(0), m_nilArrayNesting(0), m_skipAggregatesFromLevel(skipAggregatesFromLevel), m_skipNilArraysFromLevel(skipNilArraysFromLevel), m_reader(reader), m_skippingReader(skippingReader) {} template void readAndCompare(F readFunc) { Arguments::IoState rState = m_reader->state(); auto rval = (*m_reader.*readFunc)(); if (m_nestingLevel < m_skipAggregatesFromLevel && m_nilArrayNesting < m_skipNilArraysFromLevel) { Arguments::IoState sState = m_skippingReader->state(); TEST(rState == sState); auto sval = (*m_skippingReader.*readFunc)(); if (!m_nilArrayNesting) { TEST(myEqual(rval, sval)); } } } #ifdef WITH_DICT_ENTRY void beginDictEntry() { m_reader->beginDictEntry(); if (m_nestingLevel < m_skipAggregatesFromLevel && m_nilArrayNesting < m_skipNilArraysFromLevel) { m_skippingReader->beginDictEntry(); } } void endDictEntry() { m_reader->endDictEntry(); if (m_nestingLevel < m_skipAggregatesFromLevel && m_nilArrayNesting < m_skipNilArraysFromLevel) { m_skippingReader->endDictEntry(); } } #endif template void beginAggregate(F beginFunc, G skipFunc) { (*m_reader.*beginFunc)(); m_nestingLevel++; if (m_nilArrayNesting < m_skipNilArraysFromLevel) { if (m_nestingLevel < m_skipAggregatesFromLevel) { (*m_skippingReader.*beginFunc)(); } else if (m_nestingLevel == m_skipAggregatesFromLevel) { (*m_skippingReader.*skipFunc)(); } } } template void beginArrayAggregate(F beginFunc, G skipFunc) { const bool hasData = (*m_reader.*beginFunc)(Arguments::Reader::ReadTypesOnlyIfEmpty); m_nestingLevel++; m_nilArrayNesting += hasData ? 0 : 1; if (m_nestingLevel > m_skipAggregatesFromLevel || m_nilArrayNesting > m_skipNilArraysFromLevel) { // we're already skipping, do nothing } else if (m_nestingLevel == m_skipAggregatesFromLevel) { (*m_skippingReader.*skipFunc)(); } else if (m_nilArrayNesting == m_skipNilArraysFromLevel) { (*m_skippingReader.*beginFunc)(Arguments::Reader::SkipIfEmpty); } else { (*m_skippingReader.*beginFunc)(Arguments::Reader::ReadTypesOnlyIfEmpty); } } template void endAggregate(F endFunc, bool isArrayType) { (*m_reader.*endFunc)(); // when skipping a nil array: do the last part of the beginArray(), endArray() sequence // when using skip*(): do not call end() on that level, skip*() moves right past the aggregate if (m_nestingLevel < m_skipAggregatesFromLevel && (m_nilArrayNesting < m_skipNilArraysFromLevel || (isArrayType && m_nilArrayNesting == m_skipNilArraysFromLevel))) { (*m_skippingReader.*endFunc)(); } else { // we've already skipped the current aggregate } m_nestingLevel--; if (isArrayType && m_nilArrayNesting) { m_nilArrayNesting--; } } int m_nestingLevel; int m_nilArrayNesting; const int m_skipAggregatesFromLevel; const int m_skipNilArraysFromLevel; private: template bool myEqual(const T &a, const T &b) { return a == b; } bool myEqua(const chunk &a, const chunk &b) { return chunksEqual(a, b); } bool myEqual(const cstring &a, const cstring &b) { return stringsEqual(a, b); } Arguments::Reader *m_reader; Arguments::Reader *m_skippingReader; }; static void testReadWithSkip(const Arguments &arg, bool debugPrint) { // it would be even better to decide when to skip more "randomly", but given that it doesn't make // much difference in the implementation, this should do. // loop over when to skip aggregates voluntarily (on "skipper") for (int aggregateSkipLevel = 1 /* 1 orig, 15 = H4X disabled*/; aggregateSkipLevel < 16; aggregateSkipLevel++) { // loop over when to skip empty aka nil arrays - on "reader", which: // - cross checks aggregate skipping vs. skipping nil arrays // - is also the primary test for nil arrays for (int nilArraySkipLevel = 1; nilArraySkipLevel < 8; nilArraySkipLevel++) { // loop over *how* to skip empty aka nil arrays, // beginArray(Arguments::Reader::ReadTypesOnlyIfEmpty) or skipArray() Arguments::Reader reader(arg); Arguments::Reader skippingReader(arg); SkipChecker checker(&reader, &skippingReader, aggregateSkipLevel, nilArraySkipLevel); bool isDone = false; while (!isDone) { TEST(reader.state() != Arguments::InvalidData); TEST(skippingReader.state() != Arguments::InvalidData); if (debugPrint) { std::cerr << "Reader state: " << reader.stateString().ptr << '\n'; std::cerr << "Skipping reader state: " << skippingReader.stateString().ptr << '\n'; } switch(reader.state()) { case Arguments::Finished: TEST(checker.m_nestingLevel == 0); TEST(checker.m_nilArrayNesting == 0); isDone = true; break; case Arguments::BeginStruct: //std::cerr << "Beginning struct\n"; checker.beginAggregate(&Arguments::Reader::beginStruct, &Arguments::Reader::skipStruct); break; case Arguments::EndStruct: checker.endAggregate(&Arguments::Reader::endStruct, false); break; case Arguments::BeginVariant: //std::cerr << "Beginning variant\n"; checker.beginAggregate(&Arguments::Reader::beginVariant, &Arguments::Reader::skipVariant); break; case Arguments::EndVariant: checker.endAggregate(&Arguments::Reader::endVariant, false); break; case Arguments::BeginArray: checker.beginArrayAggregate(&Arguments::Reader::beginArray, &Arguments::Reader::skipArray); break; case Arguments::EndArray: checker.endAggregate(&Arguments::Reader::endArray, true); break; case Arguments::BeginDict: checker.beginArrayAggregate(&Arguments::Reader::beginDict, &Arguments::Reader::skipDict); break; #ifdef WITH_DICT_ENTRY case Arguments::BeginDictEntry: checker.beginDictEntry(); break; case Arguments::EndDictEntry: checker.endDictEntry(); break; #endif case Arguments::EndDict: checker.endAggregate(&Arguments::Reader::endDict, true); break; case Arguments::Byte: checker.readAndCompare(&Arguments::Reader::readByte); break; case Arguments::Boolean: checker.readAndCompare(&Arguments::Reader::readBoolean); break; case Arguments::Int16: checker.readAndCompare(&Arguments::Reader::readInt16); break; case Arguments::Uint16: checker.readAndCompare(&Arguments::Reader::readUint16); break; case Arguments::Int32: checker.readAndCompare(&Arguments::Reader::readInt32); break; case Arguments::Uint32: checker.readAndCompare(&Arguments::Reader::readUint32); break; case Arguments::Int64: checker.readAndCompare(&Arguments::Reader::readInt64); break; case Arguments::Uint64: checker.readAndCompare(&Arguments::Reader::readUint64); break; case Arguments::Double: checker.readAndCompare(&Arguments::Reader::readDouble); break; case Arguments::String: checker.readAndCompare(&Arguments::Reader::readString); break; case Arguments::ObjectPath: checker.readAndCompare(&Arguments::Reader::readObjectPath); break; case Arguments::Signature: checker.readAndCompare(&Arguments::Reader::readSignature); break; case Arguments::UnixFd: checker.readAndCompare(&Arguments::Reader::readUnixFd); break; case Arguments::NeedMoreData: // ### would be nice to test this as well default: TEST(false); break; } } TEST(reader.state() == Arguments::Finished); TEST(skippingReader.state() == Arguments::Finished); } } } // When using this to iterate over the reader, it will make an exact copy using the Writer. // You need to do something only in states where something special should happen. static void defaultReadToWrite(Arguments::Reader *reader, Arguments::Writer *writer) { switch(reader->state()) { case Arguments::BeginStruct: case Arguments::EndStruct: case Arguments::BeginVariant: case Arguments::EndVariant: case Arguments::EndArray: #ifdef WITH_DICT_ENTRY case Arguments::BeginDictEntry: case Arguments::EndDictEntry: #endif case Arguments::EndDict: case Arguments::Byte: case Arguments::Boolean: case Arguments::Int16: case Arguments::Uint16: case Arguments::Int32: case Arguments::Uint32: case Arguments::Int64: case Arguments::Uint64: case Arguments::Double: case Arguments::UnixFd: Arguments::copyOneElement(reader, writer); break; // special handling for BeginArray and BeginDict to avoid "fast copy" for primitive arrays case Arguments::BeginArray: { const bool hasData = reader->beginArray(Arguments::Reader::ReadTypesOnlyIfEmpty); writer->beginArray(hasData ? Arguments::Writer::NonEmptyArray : Arguments::Writer::WriteTypesOfEmptyArray); break; } case Arguments::BeginDict: { const bool hasData = reader->beginDict(Arguments::Reader::ReadTypesOnlyIfEmpty); writer->beginDict(hasData ? Arguments::Writer::NonEmptyArray : Arguments::Writer::WriteTypesOfEmptyArray); break; } case Arguments::String: { const cstring s = reader->readString(); if (!reader->isInsideEmptyArray()) { TEST(Arguments::isStringValid(s)); } writer->writeString(s); break; } case Arguments::ObjectPath: { const cstring objectPath = reader->readObjectPath(); if (!reader->isInsideEmptyArray()) { TEST(Arguments::isObjectPathValid(objectPath)); } writer->writeObjectPath(objectPath); break; } case Arguments::Signature: { const cstring signature = reader->readSignature(); if (!reader->isInsideEmptyArray()) { TEST(Arguments::isSignatureValid(signature)); } writer->writeSignature(signature); break; } writer->writeUnixFd(reader->readUnixFd()); break; // special cases follow case Arguments::Finished: break; // You *probably* want to handle that one in the caller, but you don't have to case Arguments::NeedMoreData: TEST(false); // No way to handle that one here break; default: TEST(false); break; } } static void verifyAfterRoundtrip(const Arguments &original, const Arguments::Reader &originalReader, const Arguments ©, const Arguments::Writer ©Writer, bool debugPrint) { TEST(originalReader.state() == Arguments::Finished); TEST(copyWriter.state() == Arguments::Finished); cstring originalSignature = original.signature(); cstring copySignature = copy.signature(); if (originalSignature.length) { TEST(Arguments::isSignatureValid(copySignature)); TEST(stringsEqual(originalSignature, copySignature)); } else { TEST(copySignature.length == 0); } chunk originalData = original.data(); chunk copyData = copy.data(); TEST(originalData.length == copyData.length); if (debugPrint && !chunksEqual(originalData, copyData)) { printChunk(originalData); printChunk(copyData); } TEST(chunksEqual(originalData, copyData)); } static void doRoundtripWithShortReads(const Arguments &original, uint32 dataIncrement, bool debugPrint) { const chunk data = original.data(); chunk shortData; Arguments arg(nullptr, original.signature(), shortData, original.fileDescriptors()); Arguments::Reader reader(arg); Arguments::Writer writer; bool isDone = false; while (!isDone) { TEST(writer.state() != Arguments::InvalidData); if (debugPrint) { std::cout << "Reader state: " << reader.stateString().ptr << '\n'; } switch(reader.state()) { case Arguments::Finished: isDone = true; break; case Arguments::NeedMoreData: { TEST(shortData.length < data.length); // reallocate shortData to test that Reader can handle the data moving around - and // allocate the new one before destroying the old one to make sure that the pointer differs chunk oldData = shortData; shortData.length = std::min(shortData.length + dataIncrement, data.length); shortData.ptr = reinterpret_cast(malloc(shortData.length)); for (uint32 i = 0; i < shortData.length; i++) { shortData.ptr[i] = data.ptr[i]; } // clobber it to provoke errors that only valgrind might find otherwise for (uint32 i = 0; i < oldData.length; i++) { oldData.ptr[i] = 0xff; } if (oldData.ptr) { free(oldData.ptr); } reader.replaceData(shortData); break; } default: defaultReadToWrite(&reader, &writer); break; } } Arguments copy = writer.finish(); verifyAfterRoundtrip(original, reader, copy, writer, debugPrint); if (shortData.ptr) { free(shortData.ptr); } } static void doRoundtripWithReaderCopy(const Arguments &original, uint32 dataIncrement, bool debugPrint) { Arguments::Reader *reader = new Arguments::Reader(original); Arguments::Writer writer; bool isDone = false; uint32 i = 0; while (!isDone) { TEST(writer.state() != Arguments::InvalidData); if (i++ == dataIncrement) { Arguments::Reader *copy = new Arguments::Reader(*reader); delete reader; reader = copy; } if (debugPrint) { std::cout << "Reader state: " << reader->stateString().ptr << '\n'; } switch(reader->state()) { case Arguments::Finished: isDone = true; break; default: defaultReadToWrite(reader, &writer); break; } } Arguments copy = writer.finish(); verifyAfterRoundtrip(original, *reader, copy, writer, debugPrint); delete reader; } static void doRoundtripWithWriterCopy(const Arguments &original, uint32 dataIncrement, bool debugPrint) { Arguments::Reader reader(original); Arguments::Writer *writer = new Arguments::Writer; bool isDone = false; uint32 i = 0; while (!isDone) { TEST(writer->state() != Arguments::InvalidData); if (i++ == dataIncrement) { Arguments::Writer *copy = new Arguments::Writer(*writer); delete writer; writer = copy; } if (debugPrint) { std::cout << "Reader state: " << reader.stateString().ptr << '\n'; } switch(reader.state()) { case Arguments::Finished: isDone = true; break; default: defaultReadToWrite(&reader, writer); break; } } Arguments copy = writer->finish(); verifyAfterRoundtrip(original, reader, copy, *writer, debugPrint); delete writer; } static void doRoundtripForReal(const Arguments &original, uint32 dataIncrement, bool debugPrint) { doRoundtripWithShortReads(original, dataIncrement, debugPrint); doRoundtripWithReaderCopy(original, dataIncrement, debugPrint); doRoundtripWithWriterCopy(original, dataIncrement, debugPrint); } // not returning by value to avoid the move constructor or assignment operator - // those should have separate tests static Arguments *shallowCopy(const Arguments &original) { // File descriptors can't do shallow copies - don't care for now, file descriptors are an identity // type, not a value type (and therefore don't fit well into the whole data model), and in the vast // majority of messages there aren't any. cstring signature = original.signature(); chunk data = original.data(); return new Arguments(nullptr, signature, data, original.fileDescriptors()); } static void shallowAssign(Arguments *copy, const Arguments &original) { cstring signature = original.signature(); chunk data = original.data(); *copy = Arguments(nullptr, signature, data, original.fileDescriptors()); } static void doRoundtripWithCopyAssignEtc(const Arguments &arg_in, uint32 dataIncrement, bool debugPrint) { { // just pass through doRoundtripForReal(arg_in, dataIncrement, debugPrint); } { // shallow copy Arguments *shallowDuplicate = shallowCopy(arg_in); doRoundtripForReal(*shallowDuplicate, dataIncrement, debugPrint); delete shallowDuplicate; } { // assignment from shallow copy Arguments shallowAssigned; shallowAssign(&shallowAssigned, arg_in); doRoundtripForReal(shallowAssigned, dataIncrement, debugPrint); } { // deep copy Arguments original(arg_in); doRoundtripForReal(original, dataIncrement, debugPrint); } { // move construction from shallow copy Arguments *shallowDuplicate = shallowCopy(arg_in); Arguments shallowMoveConstructed(std::move(*shallowDuplicate)); doRoundtripForReal(shallowMoveConstructed, dataIncrement, debugPrint); delete shallowDuplicate; } { // move assignment (hopefully, may the compiler optimize this to move-construction?) from shallow copy Arguments *shallowDuplicate = shallowCopy(arg_in); Arguments shallowMoveAssigned; shallowMoveAssigned = std::move(*shallowDuplicate); doRoundtripForReal(shallowMoveAssigned, dataIncrement, debugPrint); delete shallowDuplicate; } { // move construction from deep copy Arguments duplicate(arg_in); Arguments moveConstructed(std::move(duplicate)); doRoundtripForReal(moveConstructed, dataIncrement, debugPrint); } { // move assignment (hopefully, may the compiler optimize this to move-construction?) from deep copy Arguments duplicate(arg_in); Arguments moveAssigned; moveAssigned = std::move(duplicate); doRoundtripForReal(moveAssigned, dataIncrement, debugPrint); } } static void doRoundtrip(const Arguments &arg, bool debugPrint = false) { const uint32 maxIncrement = arg.data().length; for (uint32 i = 1; i <= maxIncrement; i++) { doRoundtripWithCopyAssignEtc(arg, i, debugPrint); } testReadWithSkip(arg, debugPrint); } // Tests proper static void test_stringValidation() { { cstring emptyWithNull(""); cstring emptyWithoutNull; TEST(!Arguments::isStringValid(emptyWithoutNull)); TEST(Arguments::isStringValid(emptyWithNull)); TEST(!Arguments::isObjectPathValid(emptyWithoutNull)); TEST(!Arguments::isObjectPathValid(emptyWithNull)); TEST(Arguments::isSignatureValid(emptyWithNull)); TEST(!Arguments::isSignatureValid(emptyWithoutNull)); TEST(!Arguments::isSignatureValid(emptyWithNull, Arguments::VariantSignature)); TEST(!Arguments::isSignatureValid(emptyWithoutNull, Arguments::VariantSignature)); } { cstring trivial("i"); TEST(Arguments::isSignatureValid(trivial)); TEST(Arguments::isSignatureValid(trivial, Arguments::VariantSignature)); } { cstring list("iqb"); TEST(Arguments::isSignatureValid(list)); TEST(!Arguments::isSignatureValid(list, Arguments::VariantSignature)); cstring list2("aii"); TEST(Arguments::isSignatureValid(list2)); TEST(!Arguments::isSignatureValid(list2, Arguments::VariantSignature)); } { cstring simpleArray("ai"); TEST(Arguments::isSignatureValid(simpleArray)); TEST(Arguments::isSignatureValid(simpleArray, Arguments::VariantSignature)); } { cstring messyArray("a(iaia{ia{iv}})"); TEST(Arguments::isSignatureValid(messyArray)); TEST(Arguments::isSignatureValid(messyArray, Arguments::VariantSignature)); } { cstring dictFail("a{vi}"); TEST(!Arguments::isSignatureValid(dictFail)); TEST(!Arguments::isSignatureValid(dictFail, Arguments::VariantSignature)); } { cstring emptyStruct("()"); TEST(!Arguments::isSignatureValid(emptyStruct)); TEST(!Arguments::isSignatureValid(emptyStruct, Arguments::VariantSignature)); cstring emptyStruct2("(())"); TEST(!Arguments::isSignatureValid(emptyStruct2)); TEST(!Arguments::isSignatureValid(emptyStruct2, Arguments::VariantSignature)); cstring miniStruct("(t)"); TEST(Arguments::isSignatureValid(miniStruct)); TEST(Arguments::isSignatureValid(miniStruct, Arguments::VariantSignature)); cstring badStruct("((i)"); TEST(!Arguments::isSignatureValid(badStruct)); TEST(!Arguments::isSignatureValid(badStruct, Arguments::VariantSignature)); cstring badStruct2("(i))"); TEST(!Arguments::isSignatureValid(badStruct2)); TEST(!Arguments::isSignatureValid(badStruct2, Arguments::VariantSignature)); } { cstring nullStr; cstring emptyStr(""); TEST(!Arguments::isObjectPathValid(nullStr)); TEST(!Arguments::isObjectPathValid(emptyStr)); TEST(Arguments::isObjectPathValid(cstring("/"))); TEST(!Arguments::isObjectPathValid(cstring("/abc/"))); TEST(Arguments::isObjectPathValid(cstring("/abc"))); TEST(Arguments::isObjectPathValid(cstring("/abc/def"))); TEST(!Arguments::isObjectPathValid(cstring("/abc&def"))); TEST(!Arguments::isObjectPathValid(cstring("/abc//def"))); TEST(Arguments::isObjectPathValid(cstring("/aZ/0123_zAZa9_/_"))); } { cstring maxStruct("((((((((((((((((((((((((((((((((i" "))))))))))))))))))))))))))))))))"); TEST(Arguments::isSignatureValid(maxStruct)); TEST(Arguments::isSignatureValid(maxStruct, Arguments::VariantSignature)); cstring struct33("(((((((((((((((((((((((((((((((((i" // too much nesting by one ")))))))))))))))))))))))))))))))))"); TEST(!Arguments::isSignatureValid(struct33)); TEST(!Arguments::isSignatureValid(struct33, Arguments::VariantSignature)); cstring maxArray("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai"); TEST(Arguments::isSignatureValid(maxArray)); TEST(Arguments::isSignatureValid(maxArray, Arguments::VariantSignature)); cstring array33("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaai"); TEST(!Arguments::isSignatureValid(array33)); TEST(!Arguments::isSignatureValid(array33, Arguments::VariantSignature)); } } static void test_nesting() { { Arguments::Writer writer; for (int i = 0; i < 32; i++) { writer.beginArray(); } TEST(writer.state() != Arguments::InvalidData); writer.beginArray(); TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; for (int i = 0; i < 32; i++) { writer.beginDict(); maybeBeginDictEntry(&writer); writer.writeInt32(i); // key, next nested dict is value } TEST(writer.state() != Arguments::InvalidData); writer.beginStruct(); TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; for (int i = 0; i < 32; i++) { writer.beginDict(); maybeBeginDictEntry(&writer); writer.writeInt32(i); // key, next nested dict is value } TEST(writer.state() != Arguments::InvalidData); writer.beginArray(); TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; for (int i = 0; i < 64; i++) { writer.beginVariant(); } TEST(writer.state() != Arguments::InvalidData); writer.beginVariant(); TEST(writer.state() == Arguments::InvalidData); } } struct LengthPrefixedData { uint32 length; byte data[256]; }; static void test_roundtrip() { doRoundtrip(Arguments(nullptr, cstring(""), chunk())); { byte data[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; doRoundtrip(Arguments(nullptr, cstring("i"), chunk(data, 4))); doRoundtrip(Arguments(nullptr, cstring("yyyy"), chunk(data, 4))); doRoundtrip(Arguments(nullptr, cstring("iy"), chunk(data, 5))); doRoundtrip(Arguments(nullptr, cstring("iiy"), chunk(data, 9))); doRoundtrip(Arguments(nullptr, cstring("nquy"), chunk(data, 9))); doRoundtrip(Arguments(nullptr, cstring("unqy"), chunk(data, 9))); doRoundtrip(Arguments(nullptr, cstring("nqy"), chunk(data, 5))); doRoundtrip(Arguments(nullptr, cstring("qny"), chunk(data, 5))); doRoundtrip(Arguments(nullptr, cstring("yyny"), chunk(data, 5))); doRoundtrip(Arguments(nullptr, cstring("qyyy"), chunk(data, 5))); doRoundtrip(Arguments(nullptr, cstring("d"), chunk(data, 8))); doRoundtrip(Arguments(nullptr, cstring("dy"), chunk(data, 9))); doRoundtrip(Arguments(nullptr, cstring("x"), chunk(data, 8))); doRoundtrip(Arguments(nullptr, cstring("xy"), chunk(data, 9))); doRoundtrip(Arguments(nullptr, cstring("t"), chunk(data, 8))); doRoundtrip(Arguments(nullptr, cstring("ty"), chunk(data, 9))); } { LengthPrefixedData testArray = {0, {0}}; for (int i = 0; i < 64; i++) { testArray.data[i] = i; } byte *testData = reinterpret_cast(&testArray); testArray.length = 1; doRoundtrip(Arguments(nullptr, cstring("ay"), chunk(testData, 5))); testArray.length = 4; doRoundtrip(Arguments(nullptr, cstring("ai"), chunk(testData, 8))); testArray.length = 8; doRoundtrip(Arguments(nullptr, cstring("ai"), chunk(testData, 12))); testArray.length = 64; doRoundtrip(Arguments(nullptr, cstring("ai"), chunk(testData, 68))); doRoundtrip(Arguments(nullptr, cstring("an"), chunk(testData, 68))); testArray.data[0] = 0; testArray.data[1] = 0; // zero out padding testArray.data[2] = 0; testArray.data[3] = 0; testArray.length = 56; doRoundtrip(Arguments(nullptr, cstring("ad"), chunk(testData, 64))); } { LengthPrefixedData testString; for (int i = 0; i < 200; i++) { testString.data[i] = 'A' + i % 53; // stay in the 7-bit ASCII range } testString.data[200] = '\0'; testString.length = 200; byte *testData = reinterpret_cast(&testString); doRoundtrip(Arguments(nullptr, cstring("s"), chunk(testData, 205))); } { LengthPrefixedData testDict; testDict.length = 2; testDict.data[0] = 0; testDict.data[1] = 0; // zero padding; dict entries are always 8-aligned. testDict.data[2] = 0; testDict.data[3] = 0; testDict.data[4] = 23; testDict.data[5] = 42; byte *testData = reinterpret_cast(&testDict); doRoundtrip(Arguments(nullptr, cstring("a{yy}"), chunk(testData, 10))); } { byte testData[36] = { 5, // variant signature length '(', 'y', 'g', 'd', ')', '\0', // signature: struct of: byte, signature (easiest because // its length prefix is byte order independent), double 0, // pad to 8-byte boundary for struct 23, // the byte 6, 'i', 'a', '{', 'i', 'v', '}', '\0', // the signature 0, 0, 0, 0, 0, 0, 0, // padding to 24 bytes (next 8-byte boundary) 1, 2, 3, 4, 5, 6, 7, 8, // the double 20, 21, 22, 23 // the int (not part of the variant) }; doRoundtrip(Arguments(nullptr, cstring("vi"), chunk(testData, sizeof(testData)))); } { // Spec says: alignment padding after array length, even if the array contains no data. Test this // with different types and alignment situations. byte testData[40] = { 0, 0, 0, 0, // length of array of uint64s - zero 0, 0, 0, 0, // alignment padding to 8 bytes (= natural alignment of uint64) // ... zero uint64s ... 1, 2, 3, 4, // a uint32 to change the alignment, just to test 0, 0, 0, 0, // length of array of int64s - zero // no alignment padding needed here 0, 0, 0, 0, // length of dict {uint32, uint32} - zero 0, 0, 0, 0, // alignment padding to 8 bytes (= alignment of dict entry) // some data (single bytes) between the arrays to prevent all those zeros from accidentally // looking valid when the Reader is confused. Also upset the alignment a bit. 101, 102, 103, 104, 105, 0, 0, 0, // padding to alignment of array size 0, 0, 0, 0, // length of array of structs - zero 0, 0, 0, 0 // alignment padding to 8 bytes (= alignment of struct) }; doRoundtrip(Arguments(nullptr, cstring("atuaxa{uu}yyyyya(u)"), chunk(testData, sizeof(testData)))); } } static void test_writerMisuse() { // Array { Arguments::Writer writer; writer.beginArray(); writer.endArray(); // wrong, must contain exactly one type TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.endArray(); // even with no elements it, must contain exactly one type TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginArray(); writer.writeByte(1); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); } { Arguments::Writer writer; writer.beginArray(); writer.endArray(); // wrong, must contain exactly one type TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginArray(); writer.writeByte(1); writer.writeUint16(2); // wrong, different from first element TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.beginVariant(); writer.endVariant(); // empty variants are okay if and only if inside an empty array writer.endArray(); TEST(writer.state() != Arguments::InvalidData); } // Dict { Arguments::Writer writer; writer.beginDict(); writer.endDict(); // wrong, must contain exactly two types TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); writer.endDict(); // wrong, must contain exactly two types TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginDict(); writer.writeByte(1); writer.endDict(); // wrong, must contain exactly two types TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); writer.writeByte(1); writer.endDict(); // wrong, must contain exactly two types TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginDict(); maybeBeginDictEntry(&writer); writer.writeByte(1); writer.writeByte(2); maybeEndDictEntry(&writer); writer.endDict(); TEST(writer.state() != Arguments::InvalidData); } { Arguments::Writer writer; writer.beginDict(); maybeBeginDictEntry(&writer); writer.writeByte(1); writer.writeByte(2); maybeEndDictEntry(&writer); // second key-value pair maybeBeginDictEntry(&writer); TEST(writer.state() != Arguments::InvalidData); writer.writeUint16(3); // wrong, incompatible with first element TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginDict(); maybeBeginDictEntry(&writer); writer.writeByte(1); writer.writeByte(2); maybeEndDictEntry(&writer); // second key-value pair maybeBeginDictEntry(&writer); writer.writeByte(3); TEST(writer.state() != Arguments::InvalidData); writer.writeUint16(4); // wrong, incompatible with first element TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginDict(); maybeBeginDictEntry(&writer); writer.beginVariant(); // wrong, key type must be basic TEST(writer.state() == Arguments::InvalidData); } // Variant { // this and the next are a baseline to make sure that the following test fails for a good reason Arguments::Writer writer; writer.beginVariant(); writer.writeByte(1); writer.endVariant(); TEST(writer.state() != Arguments::InvalidData); } { Arguments::Writer writer; writer.beginVariant(); writer.endVariant(); TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginVariant(); writer.writeByte(1); writer.writeByte(2); // wrong, a variant may contain only one or zero single complete types TEST(writer.state() == Arguments::InvalidData); } { Arguments::Writer writer; writer.beginStruct(); writer.writeByte(1); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::InvalidData); // can't finish while inside an aggregate TEST(arg.signature().length == 0); // should not be written on error } } static void addSomeVariantStuff(Arguments::Writer *writer) { // maybe should have typed the following into hackertyper.com to make it look more "legit" ;) static const char *aVeryLongString = "ujfgosuideuvcevfgeoauiyetoraedtmzaubeodtraueonuljfgonuiljofnuilojf" "0ij948h534ownlyejglunh4owny9hw3v9woni09ulgh4wuvcbeginVariant(); writer->beginVariant(); writer->beginVariant(); writer->beginStruct(); writer->writeString(cstring("Smoerebroed smoerebroed")); writer->beginStruct(); writer->writeString(cstring(aVeryLongString)); writer->writeString(cstring("Bork bork bork")); writer->beginVariant(); writer->beginStruct(); writer->writeString(cstring("Quite nesty")); writer->writeObjectPath(cstring("/path/to/object")); writer->writeUint64(234234234); writer->writeByte(2); writer->writeUint64(234234223434); writer->writeUint16(34); writer->endStruct(); writer->endVariant(); writer->beginStruct(); writer->writeByte(34); writer->endStruct(); writer->endStruct(); writer->writeString(cstring("Another string")); writer->endStruct(); writer->endVariant(); writer->endVariant(); writer->endVariant(); } static void test_complicated() { Arguments arg; { Arguments::Writer writer; // NeedMoreData-related bugs are less dangerous inside arrays, so we try to provoke one here; // the reason for arrays preventing failures is that they have a length prefix which enables // and encourages pre-fetching all the array's data before processing *anything* inside the // array. therefore no NeedMoreData state happens while really deserializing the array's // contents. but we exactly want NeedMoreData while in the middle of deserializing something // meaty, specifically variants. see Reader::replaceData(). addSomeVariantStuff(&writer); writer.writeInt64(234234); writer.writeByte(115); writer.beginVariant(); writer.beginDict(); maybeBeginDictEntry(&writer); writer.writeByte(23); writer.beginVariant(); writer.writeString(cstring("twenty-three")); writer.endVariant(); maybeEndDictEntry(&writer); // key-value pair 2 maybeBeginDictEntry(&writer); writer.writeByte(83); writer.beginVariant(); writer.writeObjectPath(cstring("/foo/bar/object")); writer.endVariant(); maybeEndDictEntry(&writer); // key-value pair 3 maybeBeginDictEntry(&writer); writer.writeByte(234); writer.beginVariant(); writer.beginArray(); writer.writeUint16(234); writer.writeUint16(234); writer.writeUint16(234); writer.endArray(); writer.endVariant(); maybeEndDictEntry(&writer); // key-value pair 4 maybeBeginDictEntry(&writer); writer.writeByte(25); writer.beginVariant(); addSomeVariantStuff(&writer); writer.endVariant(); maybeEndDictEntry(&writer); writer.endDict(); writer.endVariant(); writer.writeString("Hello D-Bus!"); writer.beginArray(); writer.writeDouble(1.567898); writer.writeDouble(1.523428); writer.writeDouble(1.621133); writer.writeDouble(1.982342); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); arg = writer.finish(); TEST(writer.state() != Arguments::InvalidData); } doRoundtrip(arg); } static void test_alignment() { { Arguments::Writer writer; writer.writeByte(123); writer.beginArray(); writer.writeByte(64); writer.endArray(); for (int i = 123; i < 150; i++) { writer.writeByte(i); } TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() != Arguments::InvalidData); doRoundtrip(arg); } { Arguments::Writer writer; writer.writeByte(123); writer.beginStruct(); writer.writeByte(110); writer.endStruct(); writer.writeByte(200); Arguments arg = writer.finish(); doRoundtrip(arg); } } static void test_arrayOfVariant() { // non-empty array { Arguments::Writer writer; writer.writeByte(123); writer.beginArray(); writer.beginVariant(); writer.writeByte(64); writer.endVariant(); writer.endArray(); writer.writeByte(123); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() != Arguments::InvalidData); doRoundtrip(arg); } // empty array { Arguments::Writer writer; writer.writeByte(123); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.beginVariant(); writer.endVariant(); writer.endArray(); writer.writeByte(123); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() != Arguments::InvalidData); doRoundtrip(arg); } } static void test_realMessage() { Arguments arg; // non-empty array { Arguments::Writer writer; writer.writeString(cstring("message")); writer.writeString(cstring("konversation")); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.beginVariant(); writer.endVariant(); writer.endArray(); writer.writeString(cstring("")); writer.writeString(cstring("<fredrikh> he's never on irc")); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.writeByte(123); // may not show up in the output writer.endArray(); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.writeString(cstring("dummy, I may not show up in the output!")); writer.endArray(); writer.writeInt32(-1); writer.writeInt64(46137372); TEST(writer.state() != Arguments::InvalidData); arg = writer.finish(); TEST(writer.state() != Arguments::InvalidData); } doRoundtrip(arg); } static void test_isWritingSignatureBug() { { // This was the original test, so it's the one with the comments :) Arguments::Writer writer; writer.beginArray(); writer.beginStruct(); writer.beginDict(); maybeBeginDictEntry(&writer); writer.writeByte(1); writer.writeByte(2); maybeEndDictEntry(&writer); writer.endDict(); // Must add more stuff after the inner dict to ensure that the signature position of the // dict's value is well inside the existing signature in the second dict entry. // See isWritingSignature in Writer::advanceState(). writer.writeUint16(1); writer.writeUint16(2); writer.endStruct(); writer.beginStruct(); writer.beginDict(); maybeBeginDictEntry(&writer); writer.writeByte(1); writer.writeByte(2); maybeEndDictEntry(&writer); // In the second pass, we are definitely NOT writing a new part of the dict signature, // which used to go (that was the bug!!) through a different code path in // Arguments::Writer::advanceState(). maybeBeginDictEntry(&writer); writer.writeByte(1); TEST(writer.state() != Arguments::InvalidData); writer.writeUint16(2); TEST(writer.state() == Arguments::InvalidData); } { // For completeness, do the equivalent of the previous test with an array inside Arguments::Writer writer; writer.beginArray(); writer.beginStruct(); writer.beginArray(); writer.writeByte(1); writer.endArray(); writer.writeUint16(1); writer.writeUint16(2); writer.endStruct(); writer.beginStruct(); writer.beginArray(); writer.writeByte(1); writer.writeByte(1); TEST(writer.state() != Arguments::InvalidData); writer.writeUint16(2); TEST(writer.state() == Arguments::InvalidData); } } static void writeValue(Arguments::Writer *writer, uint32 typeIndex, const void *value) { switch (typeIndex) { case 0: break; case 1: writer->writeByte(*static_cast(value)); break; case 2: writer->writeUint16(*static_cast(value)); break; case 3: writer->writeUint32(*static_cast(value)); break; case 4: writer->writeUint64(*static_cast(value)); break; default: TEST(false); } } static bool checkValue(Arguments::Reader *reader, uint32 typeIndex, const void *expected) { switch (typeIndex) { case 0: return true; case 1: return reader->readByte() == *static_cast(expected); case 2: return reader->readUint16() == *static_cast(expected); case 3: return reader->readUint32() == *static_cast(expected); case 4: return reader->readUint64() == *static_cast(expected); default: TEST(false); } return false; } static void test_primitiveArray() { // TODO also test some error cases static const uint32 testDataSize = 16384; byte testData[testDataSize]; for (uint32 i = 0; i < testDataSize; i++) { testData[i] = i & 0xff; } for (uint i = 0; i < 4; i++) { const bool writeAsPrimitive = i & 0x1; const bool readAsPrimitive = i & 0x2; static const uint32 arrayTypesCount = 5; // those types must be compatible with writeValue() and readValue() static Arguments::IoState arrayTypes[arrayTypesCount] = { Arguments::InvalidData, Arguments::Byte, Arguments::Uint16, Arguments::Uint32, Arguments::Uint64 }; for (uint otherType = 0; otherType < arrayTypesCount; otherType++) { // an array with no type in it is ill-formed, so we start with 1 (Byte) for (uint typeInArray = 1; typeInArray < arrayTypesCount; typeInArray++) { static const uint32 arraySizesCount = 12; static const uint32 arraySizes[arraySizesCount] = { 0, 1, 2, 3, 4, 7, 8, 9, 511, 512, 513, 2048 // dataSize / sizeof(uint64) == 2048 }; for (uint k = 0; k < arraySizesCount; k++) { static const uint64_t otherValue = ~0llu; const uint32 arraySize = arraySizes[k]; const uint32 dataSize = arraySize << (typeInArray - 1); TEST(dataSize <= testDataSize); Arguments arg; { Arguments::Writer writer; // write something before the array to test different starting position alignments writeValue(&writer, otherType, &otherValue); if (writeAsPrimitive) { writer.writePrimitiveArray(arrayTypes[typeInArray], chunk(testData, dataSize)); } else { writer.beginArray(arraySize ? Arguments::Writer::NonEmptyArray : Arguments::Writer::WriteTypesOfEmptyArray); byte *testDataPtr = testData; if (arraySize) { for (uint m = 0; m < arraySize; m++) { writeValue(&writer, typeInArray, testDataPtr); testDataPtr += 1 << (typeInArray - 1); } } else { writeValue(&writer, typeInArray, testDataPtr); } writer.endArray(); } TEST(writer.state() != Arguments::InvalidData); // TEST(writer.state() == Arguments::AnyData); // TODO do we handle AnyData consistently, and do we really need it anyway? writeValue(&writer, otherType, &otherValue); TEST(writer.state() != Arguments::InvalidData); arg = writer.finish(); TEST(writer.state() == Arguments::Finished); } { Arguments::Reader reader(arg); TEST(checkValue(&reader, otherType, &otherValue)); if (readAsPrimitive) { TEST(reader.state() == Arguments::BeginArray); std::pair ret = reader.readPrimitiveArray(); TEST(ret.first == arrayTypes[typeInArray]); TEST(chunksEqual(chunk(testData, dataSize), ret.second)); } else { TEST(reader.state() == Arguments::BeginArray); const bool hasData = reader.beginArray(Arguments::Reader::ReadTypesOnlyIfEmpty); TEST(hasData == (arraySize != 0)); TEST(reader.state() != Arguments::InvalidData); byte *testDataPtr = testData; if (arraySize) { for (uint m = 0; m < arraySize; m++) { TEST(reader.state() != Arguments::InvalidData); TEST(checkValue(&reader, typeInArray, testDataPtr)); TEST(reader.state() != Arguments::InvalidData); testDataPtr += 1 << (typeInArray - 1); } } else { TEST(reader.state() == arrayTypes[typeInArray]); // next: dummy read, necessary to move forward; value is ignored checkValue(&reader, typeInArray, testDataPtr); TEST(reader.state() != Arguments::InvalidData); } TEST(reader.state() == Arguments::EndArray); reader.endArray(); TEST(reader.state() != Arguments::InvalidData); } TEST(reader.state() != Arguments::InvalidData); TEST(checkValue(&reader, otherType, &otherValue)); TEST(reader.state() == Arguments::Finished); } // the data generated here nicely stresses the empty array skipping code if (i == 0 && arraySize < 100) { testReadWithSkip(arg, false); } } } } } } static void test_signatureLengths() { for (int i = 0; i <= 256; i++) { Arguments::Writer writer; for (int j = 0; j < i; j++) { writer.writeByte(255); } if (i == 256) { TEST(writer.state() == Arguments::InvalidData); break; } TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); // The full doRoundtrip() just here makes this whole file take several seconds to execute // instead of a fraction of a second. This way is much quicker. doRoundtripForReal(arg, 2048, false); Arguments argCopy = arg; doRoundtripForReal(argCopy, 2048, false); } for (int i = 1 /* variants may not be empty */; i <= 256; i++) { Arguments::Writer writer; writer.beginVariant(); switch (i) { case 0: TEST(false); break; case 1: writer.writeByte(255); break; case 2: // "ay" signature is two letters writer.beginArray(); writer.writeByte(255); writer.endArray(); break; default: // (y), (yy), ... writer.beginStruct(); for (int j = strlen("()"); j < i; j++) { writer.writeByte(255); } writer.endStruct(); break; } writer.endVariant(); if (i == 256) { TEST(writer.state() == Arguments::InvalidData); break; } TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtripForReal(arg, 2048, false); Arguments argCopy = arg; doRoundtripForReal(argCopy, 2048, false); } } static void test_emptyArrayAndDict() { // Arrays { Arguments::Writer writer; writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.writeByte(0); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { Arguments::Writer writer; writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.writeByte(0); writer.endArray(); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { Arguments::Writer writer; writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.beginStruct(); writer.writeByte(0); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.writeByte(0); writer.endArray(); writer.endStruct(); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { Arguments::Writer writer; writer.writeUint32(987654321); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.beginStruct(); writer.writeDouble(0); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.writeByte(0); writer.endArray(); writer.endStruct(); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { Arguments::Writer writer; writer.writeString(cstring("xy")); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.beginStruct(); writer.writeUint32(12345678); //It is implicitly clear that an array inside a nil array is also nil //writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); //TODO add a test for writing >1 element in nested empty array - I've tried that and it fails // like it should, but it needs a proper standalone test writer.beginArray(); writer.writeByte(0); writer.endArray(); writer.writeByte(12); writer.endStruct(); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { Arguments::Writer writer; writer.writeString(cstring("xy")); writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); writer.beginStruct(); writer.writeByte(123); writer.beginVariant(); writer.endVariant(); writer.endStruct(); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { for (int i = 0; i < 8; i++) { Arguments::Writer writer; writer.beginStruct(); writer.writeByte(123); writer.beginArray(i ? Arguments::Writer::NonEmptyArray : Arguments::Writer::WriteTypesOfEmptyArray); for (int j = 0; j < std::max(i, 1); j++) { writer.writeUint16(52345); } writer.endArray(); writer.writeByte(123); writer.endStruct(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } } for (int i = 0; i < 4; i++) { // Test RestartEmptyArrayToWriteTypes and writing an empty array inside the >1st iteration of another array Arguments::Writer writer; writer.beginArray((i & 2) ? Arguments::Writer::WriteTypesOfEmptyArray : Arguments::Writer::NonEmptyArray); // v don't care, the logic error is only in the second iteration writer.beginArray(Arguments::Writer::NonEmptyArray); writer.writeString(cstring("a")); writer.endArray(); if (i & 1) { writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); } else { writer.beginArray(Arguments::Writer::NonEmptyArray); writer.beginArray(Arguments::Writer::RestartEmptyArrayToWriteTypes); } writer.writeString(cstring("a")); writer.endArray(); writer.endArray(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } for (int i = 0; i < 3; i++) { // Test arrays inside empty arrays and especially peekPrimitiveArray / readPrimitiveArray Arguments::Writer writer; const bool outerEmpty = i > 1; const bool innerEmpty = i > 0; writer.beginArray(outerEmpty ? Arguments::Writer::WriteTypesOfEmptyArray : Arguments::Writer::NonEmptyArray); writer.beginArray(innerEmpty ? Arguments::Writer::WriteTypesOfEmptyArray : Arguments::Writer::NonEmptyArray); // Iterating several times through an empty array is allowed while writing writer.writeUint64(1234); writer.writeUint64(1234); TEST(writer.state() != Arguments::InvalidData); writer.endArray(); writer.endArray(); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); { Arguments::Reader reader(arg); reader.beginArray(); if (outerEmpty) { TEST(reader.state() == Arguments::EndArray); reader.endArray(); } else { TEST(reader.state() == Arguments::BeginArray); // the inner array reader.beginArray(Arguments::Reader::ReadTypesOnlyIfEmpty); TEST(reader.state() == Arguments::Uint64); reader.readUint64(); if (!innerEmpty) { reader.readUint64(); } TEST(reader.state() == Arguments::EndArray); reader.endArray(); reader.endArray(); } TEST(reader.state() == Arguments::Finished); } { Arguments::Reader reader(arg); TEST(reader.peekPrimitiveArray(Arguments::Reader::ReadTypesOnlyIfEmpty) == Arguments::BeginArray); reader.beginArray(Arguments::Reader::ReadTypesOnlyIfEmpty); TEST(reader.state() == Arguments::BeginArray); if (innerEmpty) { TEST(reader.peekPrimitiveArray() == Arguments::BeginArray); } else { TEST(reader.peekPrimitiveArray() == Arguments::Uint64); } TEST(reader.peekPrimitiveArray(Arguments::Reader::ReadTypesOnlyIfEmpty) == Arguments::Uint64); std::pair array = reader.readPrimitiveArray(); TEST(array.first == Arguments::Uint64); if (innerEmpty) { TEST(array.second.ptr == nullptr); TEST(array.second.length == 0); } else { TEST(array.second.length == 2 * sizeof(uint64)); } reader.endArray(); TEST(reader.state() == Arguments::Finished); } } { for (int i = 0; i <= 32; i++) { Arguments::Writer writer; for (int j = 0; j <= i; j++) { writer.beginArray(Arguments::Writer::WriteTypesOfEmptyArray); if (j == 32) { TEST(writer.state() == Arguments::InvalidData); } } if (i == 32) { TEST(writer.state() == Arguments::InvalidData); break; } writer.writeUint16(52345); for (int j = 0; j <= i; j++) { writer.endArray(); } TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } } // Dicts { Arguments::Writer writer; writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); maybeBeginDictEntry(&writer); writer.writeByte(0); writer.writeString(cstring("a")); maybeEndDictEntry(&writer); writer.endDict(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { Arguments::Writer writer; writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); writer.beginVariant(); writer.endVariant(); maybeEndDictEntry(&writer); writer.endDict(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { Arguments::Writer writer; writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); writer.beginVariant(); writer.endVariant(); maybeEndDictEntry(&writer); maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); writer.beginVariant(); writer.endVariant(); maybeEndDictEntry(&writer); writer.endDict(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { Arguments::Writer writer; writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); writer.beginVariant(); TEST(writer.state() != Arguments::InvalidData); writer.writeByte(0); // variants in nil arrays may contain data but it will be discarded, i.e. there will only be an // empty variant in the output writer.endVariant(); maybeEndDictEntry(&writer); writer.endDict(); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } for (int i = 0; i < 4; i++) { // Test RestartEmptyArrayToWriteTypes and writing an empty dict inside the >1st iteration of another dict Arguments::Writer writer; writer.beginDict((i & 2) ? Arguments::Writer::WriteTypesOfEmptyArray : Arguments::Writer::NonEmptyArray); maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); // v don't care, the logic error is only in the second iteration writer.beginDict(Arguments::Writer::NonEmptyArray); maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); writer.writeInt32(1234); maybeEndDictEntry(&writer); writer.endDict(); maybeEndDictEntry(&writer); maybeBeginDictEntry(&writer); writer.writeString(cstring("a")); if (i & 1) { writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); maybeBeginDictEntry(&writer); } else { writer.beginDict(Arguments::Writer::NonEmptyArray); writer.beginDict(Arguments::Writer::RestartEmptyArrayToWriteTypes); maybeBeginDictEntry(&writer); } writer.writeString(cstring("a")); writer.writeInt32(1234); maybeEndDictEntry(&writer); writer.endDict(); maybeEndDictEntry(&writer); writer.endDict(); TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } { for (int i = 0; i <= 32; i++) { Arguments::Writer writer; for (int j = 0; j <= i; j++) { writer.beginDict(Arguments::Writer::WriteTypesOfEmptyArray); maybeBeginDictEntry(&writer); if (j == 32) { TEST(writer.state() == Arguments::InvalidData); } writer.writeUint16(12345); } if (i == 32) { TEST(writer.state() == Arguments::InvalidData); break; } writer.writeUint16(52345); for (int j = 0; j <= i; j++) { maybeEndDictEntry(&writer); writer.endDict(); } TEST(writer.state() != Arguments::InvalidData); Arguments arg = writer.finish(); TEST(writer.state() == Arguments::Finished); doRoundtrip(arg, false); } } } static void test_fileDescriptors() { #ifdef __unix__ - Arguments::Writer writer; - writer.writeUnixFd(200); - writer.writeByte(12); - writer.writeUnixFd(1); - Arguments arg = writer.finish(); - doRoundtrip(arg, false); - // doRoundtrip only checks the serialized data, but unfortunately file descriptors - // are out of band, so check explicitly - Arguments::Reader reader(arg); - TEST(reader.readUnixFd() == 200); - TEST(reader.readByte() == 12); - TEST(reader.readUnixFd() == 1); - TEST(reader.state() == Arguments::Finished); + { + Arguments::Writer writer; + writer.writeUnixFd(200); + writer.writeByte(12); + writer.writeUnixFd(1); + Arguments arg = writer.finish(); + doRoundtrip(arg, false); + // doRoundtrip only checks the serialized data, but unfortunately file descriptors + // are out of band, so check explicitly + Arguments::Reader reader(arg); + TEST(reader.readUnixFd() == 200); + TEST(reader.readByte() == 12); + TEST(reader.readUnixFd() == 1); + TEST(reader.state() == Arguments::Finished); + } + { + Arguments::Writer writer; + writer.writeUnixFd(400); + Arguments arg = writer.finish(); + doRoundtrip(arg, false); + // doRoundtrip only checks the serialized data, but unfortunately file descriptors + // are out of band, so check explicitly + Arguments::Reader reader(arg); + TEST(reader.state() == Arguments::UnixFd); + TEST(reader.readUnixFd() == 400); + TEST(reader.state() == Arguments::Finished); + + } #endif } static void test_closeWrongAggregate() { for (int i = 0; i < 8; i++) { for (int j = 0; j < 4; j++) { Arguments::Writer writer; switch (i % 4) { case 0: writer.beginStruct(); break; case 1: writer.beginVariant(); break; case 2: writer.beginArray(); break; case 3: writer.beginDict(); break; } if (i < 4) { writer.writeByte(123); if (i == 3) { writer.writeByte(123); // value for dict } } switch (j) { case 0: writer.endStruct(); break; case 1: writer.endVariant(); break; case 2: writer.endArray(); break; case 3: writer.endDict(); break; } const bool isValid = writer.state() != Arguments::InvalidData; TEST(isValid == (i == j)); } } } // TODO: test where we compare data and signature lengths of all combinations of zero/nonzero array // length and long/short type signature, to make sure that the signature is written but not // any data if the array is zero-length. // TODO test empty dicts, too int main(int, char *[]) { test_stringValidation(); test_nesting(); test_roundtrip(); test_writerMisuse(); // TODO test arrays where array length does not align with end of an element // (corruption of serialized data) test_complicated(); test_alignment(); test_arrayOfVariant(); test_realMessage(); test_isWritingSignatureBug(); test_primitiveArray(); test_signatureLengths(); test_emptyArrayAndDict(); test_fileDescriptors(); // TODO (maybe): specific tests for begin/endDictEntry() for both Reader and Writer. // TODO more misuse tests for Writer and maybe some for Reader test_closeWrongAggregate(); std::cout << "Passed!\n"; } diff --git a/tests/serialization/tst_message.cpp b/tests/serialization/tst_message.cpp index 3211ef5..423c51b 100644 --- a/tests/serialization/tst_message.cpp +++ b/tests/serialization/tst_message.cpp @@ -1,159 +1,301 @@ /* Copyright (C) 2013 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LGPL. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Alternatively, this file is available under the Mozilla Public License Version 1.1. You may obtain a copy of the License at http://www.mozilla.org/MPL/ */ #include "arguments.h" #include "connectaddress.h" #include "error.h" #include "eventdispatcher.h" #include "imessagereceiver.h" #include "message.h" +#include "pendingreply.h" #include "testutil.h" #include "connection.h" +#include +#include + #include #include static void test_signatureHeader() { Message msg; Arguments::Writer writer; writer.writeByte(123); writer.writeUint64(1); msg.setArguments(writer.finish()); TEST(msg.signature() == "yt"); } class PrintAndTerminateClient : public IMessageReceiver { public: void handleSpontaneousMessageReceived(Message msg) override { std::cout << msg.prettyPrint(); m_eventDispatcher->interrupt(); } EventDispatcher *m_eventDispatcher; }; class PrintAndReplyClient : public IMessageReceiver { public: void handleSpontaneousMessageReceived(Message msg) override { std::cout << msg.prettyPrint(); m_connection->sendNoReply(Message::createErrorReplyTo(msg, "Unable to get out of hammock!")); //m_connection->eventDispatcher()->interrupt(); } Connection *m_connection; }; // used during implementation, is supposed to not crash and be valgrind-clean afterwards void testBasic(const ConnectAddress &clientAddress) { EventDispatcher dispatcher; ConnectAddress serverAddress = clientAddress; serverAddress.setRole(ConnectAddress::Role::PeerServer); Connection serverConnection(&dispatcher, serverAddress); std::cout << "Created server connection. " << &serverConnection << std::endl; Connection clientConnection(&dispatcher, clientAddress); std::cout << "Created client connection. " << &clientConnection << std::endl; PrintAndReplyClient printAndReplyClient; printAndReplyClient.m_connection = &serverConnection; serverConnection.setSpontaneousMessageReceiver(&printAndReplyClient); PrintAndTerminateClient printAndTerminateClient; printAndTerminateClient.m_eventDispatcher = &dispatcher; clientConnection.setSpontaneousMessageReceiver(&printAndTerminateClient); Message msg = Message::createCall("/foo", "org.foo.interface", "laze"); Arguments::Writer writer; writer.writeString("couch"); msg.setArguments(writer.finish()); clientConnection.sendNoReply(std::move(msg)); while (dispatcher.poll()) { } } void testMessageLength() { static const uint32 bufferSize = Arguments::MaxArrayLength + 1024; byte *buffer = static_cast(malloc(bufferSize)); memset(buffer, 0, bufferSize); for (int i = 0; i < 2; i++) { const bool makeTooLong = i == 1; Arguments::Writer writer; writer.writePrimitiveArray(Arguments::Byte, chunk(buffer, Arguments::MaxArrayLength)); // Our minimal Message is going to have the following variable headers (in that order): // Array: 4 byte length prefix // PathHeader: 4 byte length prefix // MethodHeader: 4 byte length prefix // SignatureHeader: 1 byte length prefix // This is VERY tedious to calculate, so let's just take it as an experimentally determined value uint32 left = Arguments::MaxMessageLength - Arguments::MaxArrayLength - 72; if (makeTooLong) { left += 1; } writer.writePrimitiveArray(Arguments::Byte, chunk(buffer, left)); Message msg = Message::createCall("/a", "x"); msg.setSerial(1); msg.setArguments(writer.finish()); std::vector saved = msg.save(); TEST(msg.error().isError() == makeTooLong); } } +enum { + // a small integer could be confused with an index into the fd array (in the implementation), + // so make it large + DummyFdOffset = 1000000 +}; + +static Arguments createArgumentsWithDummyFileDescriptors(uint fdCount) +{ + Arguments::Writer writer; + for (uint i = 0; i < fdCount; i++) { + writer.writeUnixFd(DummyFdOffset - i); + } + return writer.finish(); +} + +void testFileDescriptorsInArguments() +{ + // Note: This replaces round-trip tests with file descriptors in tst_arguments. + // A full roundtrip test must go through Message due to the out-of-band way that file + // descriptors are stored (which is so because they are also transmitted out-of-band). + Message msg = Message::createCall("/foo", "org.foo.interface", "doNothing"); + for (uint i = 0; i < 4; i++) { + msg.setArguments(createArgumentsWithDummyFileDescriptors(i)); + { + // const ref to arguments + const Arguments &args = msg.arguments(); + Arguments::Reader reader(args); + for (uint j = 0; j < i; j++) { + TEST(reader.readUnixFd() == int(DummyFdOffset - j)); + TEST(reader.isValid()); + } + TEST(reader.isFinished()); + } + { + // copy of arguments + Arguments args = msg.arguments(); + Arguments::Reader reader(args); + for (uint j = 0; j < i; j++) { + TEST(reader.readUnixFd() == int(DummyFdOffset - j)); + TEST(reader.isValid()); + } + TEST(reader.isFinished()); + } + } +} + +void testTooManyFileDescriptors() +{ + // TODO re-think what is the best place to catch too many file descriptors... + Arguments::Writer writer; +} + +void testFileDescriptorsHeader() +{ + Message msg = Message::createCall("/foo", "org.foo.interface", "doNothing"); + for (uint i = 0; i < 4; i++) { + msg.setArguments(createArgumentsWithDummyFileDescriptors(i)); + TEST(msg.unixFdCount() == i); + } +} + +enum { + // for pipe2() file descriptor array + ReadSide = 0, + WriteSide = 1, + // how many file descriptors to send in test + FdCountToSend = 10 +}; + +class FileDescriptorTestReceiver : public IMessageReceiver +{ +public: + void handleSpontaneousMessageReceived(Message msg) override + { + // we're on the session bus, so we'll receive all kinds of notifications we don't care about here + if (msg.type() != Message::MethodCallMessage + || msg.method() != "testFileDescriptorsForDataTransfer") { + return; + } + + Arguments::Reader reader(msg.arguments()); + for (uint i = 0; i < FdCountToSend; i++) { + int fd = reader.readUnixFd(); + uint readBuf = 12345; + ::read(fd, &readBuf, sizeof(uint)); + ::close(fd); + TEST(readBuf == i); + } + Message reply = Message::createReplyTo(msg); + m_connection->sendNoReply(std::move(reply)); + } + + Connection *m_connection = nullptr; +}; + +void testFileDescriptorsForDataTransfer() +{ + EventDispatcher eventDispatcher; + Connection conn(&eventDispatcher, ConnectAddress::StandardBus::Session); + conn.waitForConnectionEstablished(); + TEST(conn.isConnected()); + + int pipeFds[2 * FdCountToSend]; + + Message msg = Message::createCall("/foo", "org.foo.interface", "testFileDescriptorsForDataTransfer"); + msg.setDestination(conn.uniqueName()); + + Arguments::Writer writer; + for (uint i = 0; i < FdCountToSend; i++) { + TEST(pipe2(pipeFds + 2 * i, O_NONBLOCK) == 0); + // write into write side of the pipe... will be read when the message is received back from bus + ::write(pipeFds[2 * i + WriteSide], &i, sizeof(uint)); + + writer.writeUnixFd(pipeFds[2 * i + ReadSide]); + } + + msg.setArguments(writer.finish()); + + PendingReply reply = conn.send(std::move(msg), 500 /* fail quickly */); + FileDescriptorTestReceiver fdTestReceiver; + conn.setSpontaneousMessageReceiver(&fdTestReceiver); + fdTestReceiver.m_connection = &conn; + + while (!reply.isFinished()) { + eventDispatcher.poll(); + } + TEST(reply.hasNonErrorReply()); // otherwise timeout, the message exchange failed somehow + + for (uint i = 0; i < FdCountToSend; i++) { + ::close(pipeFds[2 * i + WriteSide]); + } +} + int main(int, char *[]) { test_signatureHeader(); #ifdef __linux__ { ConnectAddress clientAddress; clientAddress.setType(ConnectAddress::Type::AbstractUnixPath); clientAddress.setRole(ConnectAddress::Role::PeerClient); clientAddress.setPath("dferry.Test.Message"); testBasic(clientAddress); } #endif // TODO: SocketType::Unix works on any Unix-compatible OS, but we'll need to construct a path { ConnectAddress clientAddress; clientAddress.setType(ConnectAddress::Type::Tcp); clientAddress.setPort(6800); clientAddress.setRole(ConnectAddress::Role::PeerClient); testBasic(clientAddress); } testMessageLength(); + testFileDescriptorsInArguments(); + testTooManyFileDescriptors(); + testFileDescriptorsHeader(); + testFileDescriptorsForDataTransfer(); + // TODO testSaveLoad(); // TODO testDeepCopy(); std::cout << "\nNote that the hammock error is part of the test.\nPassed!\n"; } diff --git a/transport/itransport.h b/transport/itransport.h index 2fb6704..eef6649 100644 --- a/transport/itransport.h +++ b/transport/itransport.h @@ -1,85 +1,87 @@ /* Copyright (C) 2013 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LGPL. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Alternatively, this file is available under the Mozilla Public License Version 1.1. You may obtain a copy of the License at http://www.mozilla.org/MPL/ */ #ifndef ITRANSPORT_H #define ITRANSPORT_H #include "iioeventlistener.h" #include "platform.h" #include "types.h" #include class ConnectAddress; class EventDispatcher; class ITransportListener; class SelectEventPoller; class ITransport : public IioEventListener { public: // An ITransport subclass must have a file descriptor after construction and it must not change // except to the invalid file descriptor when disconnected. ITransport(); // TODO event dispatcher as constructor argument? ~ITransport() override; // usually, the maximum sensible number of listeners is two: one for reading and one for writing. // avoiding (independent) readers and writers blocking each other is good for IO efficiency. void addListener(ITransportListener *listener); void removeListener(ITransportListener *listener); virtual uint32 availableBytesForReading() = 0; virtual chunk read(byte *buffer, uint32 maxSize) = 0; virtual chunk readWithFileDescriptors(byte *buffer, uint32 maxSize, std::vector *fileDescriptors); virtual uint32 write(chunk data) = 0; virtual uint32 writeWithFileDescriptors(chunk data, const std::vector &fileDescriptors); virtual void close() = 0; virtual bool isOpen() = 0; + bool supportsPassingFileDescriptors() const { return m_supportsFileDescriptors; } + void setEventDispatcher(EventDispatcher *ed) override; EventDispatcher *eventDispatcher() const override; // factory method - creates a suitable subclass to connect to address static ITransport *create(const ConnectAddress &connectAddress); protected: friend class EventDispatcher; // IioEventListener void handleCanRead() override; void handleCanWrite() override; bool m_supportsFileDescriptors; private: friend class ITransportListener; friend class SelectEventPoller; void updateReadWriteInterest(); // called internally and from ITransportListener EventDispatcher *m_eventDispatcher; std::vector m_listeners; bool m_readNotificationEnabled; bool m_writeNotificationEnabled; }; #endif // ITRANSPORT_H diff --git a/transport/localsocket.cpp b/transport/localsocket.cpp index fd20fcc..fac83dc 100644 --- a/transport/localsocket.cpp +++ b/transport/localsocket.cpp @@ -1,307 +1,336 @@ /* Copyright (C) 2013 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LGPL. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Alternatively, this file is available under the Mozilla Public License Version 1.1. You may obtain a copy of the License at http://www.mozilla.org/MPL/ */ #include "localsocket.h" #include #include #include #include #include "sys/uio.h" #include #include #include #include #include #include -// HACK, put this somewhere else (get the value from original d-bus? or is it infinite?) -static const int maxFds = 12; +enum { + // ### This is configurable in libdbus-1 but nobody ever seems to change it from the default of 16. + MaxFds = 16, + MaxFdPayloadSize = MaxFds * sizeof(int) +}; LocalSocket::LocalSocket(const std::string &socketFilePath) : m_fd(-1) { m_supportsFileDescriptors = true; const int fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) { return; } // don't let forks inherit the file descriptor - that can cause confusion... fcntl(fd, F_SETFD, FD_CLOEXEC); struct sockaddr_un addr; addr.sun_family = PF_UNIX; bool ok = socketFilePath.length() < sizeof(addr.sun_path); if (ok) { memcpy(addr.sun_path, socketFilePath.c_str(), socketFilePath.length() + 1); } ok = ok && (connect(fd, (struct sockaddr *)&addr, sizeof(sa_family_t) + socketFilePath.length() + 1) == 0); if (ok) { m_fd = fd; } else { ::close(fd); } } LocalSocket::LocalSocket(int fd) : m_fd(fd) { } LocalSocket::~LocalSocket() { close(); } void LocalSocket::close() { setEventDispatcher(nullptr); if (m_fd >= 0) { ::close(m_fd); } m_fd = -1; } uint32 LocalSocket::write(chunk data) { - if (m_fd < 0) { + if (m_fd < 0 || data.length == 0) { return 0; // TODO -1? } const uint32 initialLength = data.length; while (data.length > 0) { ssize_t nbytes = send(m_fd, data.ptr, data.length, MSG_DONTWAIT); if (nbytes < 0) { if (errno == EINTR) { continue; } // see EAGAIN comment in read() - if (errno == EAGAIN /* && iov.iov_len < a.length */ ) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { break; } close(); - return false; + return 0; } data.ptr += nbytes; data.length -= size_t(nbytes); } return initialLength - data.length; } // TODO: consider using iovec to avoid "copying together" message parts before sending; iovec tricks // are probably not going to help for receiving, though. uint32 LocalSocket::writeWithFileDescriptors(chunk data, const std::vector &fileDescriptors) { - if (m_fd < 0) { - return 0; // TODO -1? + if (m_fd < 0 || data.length == 0) { + return 0; } // sendmsg boilerplate struct msghdr send_msg; struct iovec iov; send_msg.msg_name = 0; send_msg.msg_namelen = 0; send_msg.msg_flags = 0; send_msg.msg_iov = &iov; send_msg.msg_iovlen = 1; iov.iov_base = data.ptr; iov.iov_len = data.length; // we can only send a fixed number of fds anyway due to the non-flexible size of the control message // receive buffer, so we set an arbitrary limit. const uint32 numFds = fileDescriptors.size(); - assert(fileDescriptors.size() <= maxFds); // TODO proper error + if (fileDescriptors.size() > MaxFds) { + // TODO allow a proper error return + close(); + return 0; + } - char cmsgBuf[CMSG_SPACE(sizeof(int) * maxFds)]; + char cmsgBuf[CMSG_SPACE(MaxFdPayloadSize)]; + const uint32 fdPayloadSize = numFds * sizeof(int); if (numFds) { // fill in a control message send_msg.msg_control = cmsgBuf; - send_msg.msg_controllen = CMSG_SPACE(sizeof(int) * numFds); + send_msg.msg_controllen = CMSG_SPACE(fdPayloadSize); struct cmsghdr *c_msg = CMSG_FIRSTHDR(&send_msg); - c_msg->cmsg_len = CMSG_LEN(sizeof(int) * numFds); + c_msg->cmsg_len = CMSG_LEN(fdPayloadSize); c_msg->cmsg_level = SOL_SOCKET; c_msg->cmsg_type = SCM_RIGHTS; // set the control data to pass - this is why we don't use the simpler write() + int *const fdPayload = reinterpret_cast(CMSG_DATA(c_msg)); for (uint32 i = 0; i < numFds; i++) { - reinterpret_cast(CMSG_DATA(c_msg))[i] = fileDescriptors[i]; + fdPayload[i] = fileDescriptors[i]; } } else { // no file descriptor to send, no control message - send_msg.msg_control = 0; + send_msg.msg_control = nullptr; send_msg.msg_controllen = 0; } while (iov.iov_len > 0) { ssize_t nbytes = sendmsg(m_fd, &send_msg, MSG_DONTWAIT); if (nbytes < 0) { if (errno == EINTR) { continue; } // see EAGAIN comment in read() - if (errno == EAGAIN /* && iov.iov_len < a.length */ ) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { break; } close(); - return false; + return data.length - iov.iov_len; + } else if (nbytes > 0) { + // control message already sent, don't send again + send_msg.msg_control = nullptr; + send_msg.msg_controllen = 0; } iov.iov_base = static_cast(iov.iov_base) + nbytes; iov.iov_len -= size_t(nbytes); } return data.length - iov.iov_len; } uint32 LocalSocket::availableBytesForReading() { uint32 available = 0; if (ioctl(m_fd, FIONREAD, &available) < 0) { available = 0; } return available; } chunk LocalSocket::read(byte *buffer, uint32 maxSize) { chunk ret(buffer, 0); while (ret.length < maxSize) { ssize_t nbytes = recv(m_fd, ret.ptr + ret.length, maxSize - ret.length, MSG_DONTWAIT); if (nbytes < 0) { if (errno == EINTR) { continue; } // If we were notified for reading directly by the event dispatcher, we must be able to read at // least one byte before getting AGAIN aka EWOULDBLOCK - *however* the event loop might notify // something that is very eager to read everything (like Message::notifyRead()...) by reading // multiple times and in that case, we may be called in an attempt to read more when there is // currently no more data. // Just return zero bytes and no error in that case. - if (errno == EAGAIN /* && iov.iov_len < maxSize */) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { break; } close(); return ret; + } else if (nbytes == 0) { + // orderly shutdown + close(); + return ret; } ret.length += size_t(nbytes); } return ret; } chunk LocalSocket::readWithFileDescriptors(byte *buffer, uint32 maxSize, std::vector *fileDescriptors) { chunk ret; - if (maxSize <= 0) { + if (maxSize == 0) { return ret; } // recvmsg-with-control-message boilerplate struct msghdr recv_msg; - char cmsgBuf[CMSG_SPACE(sizeof(int) * maxFds)]; - memset(cmsgBuf, 0, sizeof(cmsgBuf)); + char cmsgBuf[CMSG_SPACE(sizeof(int) * MaxFds)]; recv_msg.msg_control = cmsgBuf; - recv_msg.msg_controllen = sizeof(cmsgBuf); + recv_msg.msg_controllen = CMSG_SPACE(MaxFdPayloadSize); + memset(cmsgBuf, 0, recv_msg.msg_controllen); + // prevent equivalent to CVE-2014-3635 in libdbus-1: We could receive and ignore an extra file + // descriptor, thus eventually run out of file descriptors + recv_msg.msg_controllen = CMSG_LEN(MaxFdPayloadSize); recv_msg.msg_name = 0; recv_msg.msg_namelen = 0; recv_msg.msg_flags = 0; struct iovec iov; recv_msg.msg_iov = &iov; recv_msg.msg_iovlen = 1; // end boilerplate ret.ptr = buffer; ret.length = 0; iov.iov_base = ret.ptr; iov.iov_len = maxSize; while (iov.iov_len > 0) { ssize_t nbytes = recvmsg(m_fd, &recv_msg, MSG_DONTWAIT); if (nbytes < 0) { if (errno == EINTR) { continue; } // If we were notified for reading directly by the event dispatcher, we must be able to read at // least one byte before getting AGAIN aka EWOULDBLOCK - *however* the event loop might notify // something that is very eager to read everything (like Message::notifyRead()...) by reading // multiple times and in that case, we may be called in an attempt to read more when there is // currently no more data. // Just return zero bytes and no error in that case. - if (errno == EAGAIN /* && iov.iov_len < maxSize */) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { break; } close(); return ret; + } else if (nbytes == 0) { + // orderly shutdown + close(); + return ret; + } else { + // read any file descriptors passed via control messages + + struct cmsghdr *c_msg = CMSG_FIRSTHDR(&recv_msg); + if (c_msg && c_msg->cmsg_level == SOL_SOCKET && c_msg->cmsg_type == SCM_RIGHTS) { + const int count = (c_msg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + const int *const fdPayload = reinterpret_cast(CMSG_DATA(c_msg)); + for (int i = 0; i < count; i++) { + fileDescriptors->push_back(fdPayload[i]); + } + } + + // control message already received, don't receive another + recv_msg.msg_control = nullptr; + recv_msg.msg_controllen = 0; } + ret.length += size_t(nbytes); iov.iov_base = static_cast(iov.iov_base) + nbytes; iov.iov_len -= size_t(nbytes); } - // done reading "regular data", now read any file descriptors passed via control messages - - struct cmsghdr *c_msg = CMSG_FIRSTHDR(&recv_msg); - if (c_msg && c_msg->cmsg_level == SOL_SOCKET && c_msg->cmsg_type == SCM_RIGHTS) { - const int len = c_msg->cmsg_len / sizeof(int); - int *cmsgData = reinterpret_cast(CMSG_DATA(c_msg)); - for (int i = 0; i < len; i++) { - fileDescriptors->push_back(cmsgData[i]); - } - } - return ret; } bool LocalSocket::isOpen() { return m_fd != -1; } int LocalSocket::fileDescriptor() const { return m_fd; } void LocalSocket::handleCanRead() { if (availableBytesForReading()) { ITransport::handleCanRead(); } else { // This should really only happen in error cases! ### TODO test? close(); } }