void Client::receiveMessages() { while(d->msgqueue->isPending()) { MessagePtr msg = d->msgqueue->getPending(); if(d->session.isNull()) { // No session? We must be in the login phase if(msg->type() == protocol::MSG_COMMAND) emit loginMessage(msg); else log(Log().about(Log::Level::Warn, Log::Topic::RuleBreak).message( QString("Got non-login message (type=%1) in login state").arg(msg->type()) )); } else { // Enforce origin ID, except when receiving a snapshot if(d->session->initUserId() != d->id) msg->setContextId(d->id); if(isHoldLocked()) d->holdqueue << msg; else d->session->handleClientMessage(*this, msg); } } }
void Actor::Main() { is_working_ = true; MessagePtr msg; while (mailbox_->Pop(msg)) { if (handlers_.find(msg->type()) != handlers_.end()) { handlers_[msg->type()](msg); } else if (handlers_.find(MsgType::Default) != handlers_.end()) { handlers_[MsgType::Default](msg); } else { Log::Fatal("Unexpected msg type\n"); } } }
void Client::receiveMessages() { while(_msgqueue->isPending()) { MessagePtr msg = _msgqueue->getPending(); if(_state == LOGIN) { if(msg->type() == protocol::MSG_LOGIN) emit loginMessage(msg); else logger::notice() << this << "Got non-login message (type=" << msg->type() << ") in login state"; } else { handleSessionMessage(msg); } } }
void MessageManager::send(MessagePtr message) { MessageMap::iterator pos = messageMap.find(message->type()); if (pos != messageMap.end()) { for (boost::unordered_set<EntitySystem*>::iterator itr = pos->second.begin(); itr != pos->second.end(); itr++) { (*itr)->receive(message); } } }
void Client::receiveSnapshot() { if(!_uploading_snapshot) { logger::notice() << this << "Received snapshot data when not expecting it!"; disconnectError("Didn't expect snapshot data"); return; } while(_msgqueue->isPendingSnapshot()) { MessagePtr msg = _msgqueue->getPendingSnapshot(); // Filter away blatantly unallowed messages switch(msg->type()) { using namespace protocol; case MSG_LOGIN: case MSG_SESSION_CONFIG: case MSG_STREAMPOS: case MSG_DISCONNECT: continue; default: break; } // Add message if(_session->addToSnapshotStream(msg)) { logger::debug() << this << "Finished getting snapshot."; _uploading_snapshot = false; // If this was the hosting user, graduate to full session status // The server now needs to be brought up to date with the initial uploaded state if(_state == WAIT_FOR_SYNC) { logger::debug() << this << "Session sync complete."; _state = IN_SESSION; _streampointer = _session->mainstream().snapshotPointIndex(); _session->syncInitialState(_session->mainstream().snapshotPoint().cast<protocol::SnapshotPoint>().substream()); enqueueHeldCommands(); sendAvailableCommands(); } if(_msgqueue->isPendingSnapshot()) { logger::notice() << this << "Received too much snapshot data!"; disconnectError("too much snapshot data"); } break; } } }
void MessageQueue::writeData() { if(_sendbuflen==0) { // If send buffer is empty, serialize the next message in the queue. // The snapshot upload queue has lower priority than the normal queue. if(!_sendqueue.isEmpty()) { // There are messages in the higher priority queue, send one MessagePtr msg = _sendqueue.dequeue(); _sendbuflen = msg->serialize(_sendbuffer); if(msg->type() == protocol::MSG_DISCONNECT) { // Automatically disconnect after Disconnect notification is sent _closeWhenReady = true; _sendqueue.clear(); } } else if(!_snapshot_send.isEmpty()) { // When the main send queue is empty, messages from the snapshot queue are sent SnapshotMode mode(SnapshotMode::SNAPSHOT); _sendbuflen = mode.serialize(_sendbuffer); _sendbuflen += _snapshot_send.takeFirst()->serialize(_sendbuffer + _sendbuflen); } } if(_sentcount < _sendbuflen) { int sent = _socket->write(_sendbuffer+_sentcount, _sendbuflen-_sentcount); if(sent<0) { // Error emit socketError(_socket->errorString()); return; } _sentcount += sent; if(_sentcount == _sendbuflen) { _sendbuflen=0; _sentcount=0; if(_closeWhenReady) { _socket->disconnectFromHost(); } else { writeData(); } } } }
void Client::sendAvailableCommands() { if(_state != IN_SESSION) return; if(_substreampointer>=0) { // Are we downloading a substream? const protocol::MessagePtr sptr = _session->mainstream().at(_streampointer); Q_ASSERT(sptr->type() == protocol::MSG_SNAPSHOTPOINT); const protocol::SnapshotPoint &sp = sptr.cast<const protocol::SnapshotPoint>(); if(_substreampointer == 0) { // User is in the beginning of a stream, send stream position message uint streamlen = 0; for(int i=0;i<sp.substream().length();++i) streamlen += sp.substream().at(i)->length(); for(int i=_streampointer+1;i<_session->mainstream().end();++i) streamlen += _session->mainstream().at(i)->length(); _msgqueue->send(MessagePtr(new protocol::StreamPos(streamlen))); } // Enqueue substream while(_substreampointer < sp.substream().length()) _msgqueue->send(sp.substream().at(_substreampointer++)); if(sp.isComplete()) { _substreampointer = -1; ++_streampointer; sendAvailableCommands(); } } else { // No substream in progress, enqueue normal commands // Snapshot points (substreams) are skipped. while(_streampointer < _session->mainstream().end()) { MessagePtr msg = _session->mainstream().at(_streampointer++); if(msg->type() != protocol::MSG_SNAPSHOTPOINT) _msgqueue->send(msg); } } }
bool Client::handleUndoCommand(protocol::Undo &undo) { // First check if user context override is used if(undo.overrideId()) { undo.setContextId(undo.overrideId()); } // Undo history is limited to last UNDO_HISTORY_LIMIT undopoints // or the latest snapshot point, whichever comes first // Clients usually store more than that, but to ensure a consistent // experience, we enforce the limit here. if(undo.points()>0) { // Undo mode: iterator towards the beginning of the history, // marking not-undone UndoPoints as undone. int limit = protocol::UNDO_HISTORY_LIMIT; int pos = _session->mainstream().end()-1; int points = 0; while(limit>0 && points<undo.points() && _session->mainstream().isValidIndex(pos)) { MessagePtr msg = _session->mainstream().at(pos); if(msg->type() == protocol::MSG_SNAPSHOTPOINT) break; if(msg->type() == protocol::MSG_UNDOPOINT) { --limit; if(msg->contextId() == undo.contextId() && msg->undoState()==protocol::DONE) { msg->setUndoState(protocol::UNDONE); ++points; } } --pos; } // Did we undo anything? if(points==0) return false; // Number of undoable actions may be less than expected, but undo what we got. undo.setPoints(points); return true; } else if(undo.points()<0) { // Redo mode: find the start of the latest undo sequence, then mark // (points) UndoPoints as redone. int redostart = _session->mainstream().end(); int limit = protocol::UNDO_HISTORY_LIMIT; int pos = _session->mainstream().end(); while(_session->mainstream().isValidIndex(--pos) && limit>0) { protocol::MessagePtr msg = _session->mainstream().at(pos); if(msg->type() == protocol::MSG_UNDOPOINT) { --limit; if(msg->contextId() == undo.contextId()) { if(msg->undoState() != protocol::DONE) redostart = pos; else break; } } } // There may be nothing to redo if(redostart == _session->mainstream().end()) return false; pos = redostart; // Mark undone actions as done again int actions = -undo.points() + 1; int points = 0; while(pos < _session->mainstream().end()) { protocol::MessagePtr msg = _session->mainstream().at(pos); if(msg->contextId() == undo.contextId() && msg->type() == protocol::MSG_UNDOPOINT) { if(msg->undoState() == protocol::UNDONE) { if(--actions==0) break; msg->setUndoState(protocol::DONE); ++points; } } ++pos; } // Did we redo anything if(points==0) return false; undo.setPoints(-points); return true; } else { // points==0 is invalid return false; } }
/** * @brief Handle messages in normal session mode * * This one is pretty simple. The message is validated to make sure * the client is authorized to send it, etc. and it is added to the * main message stream, from which it is distributed to all connected clients. * @param msg the message received from the client */ void Client::handleSessionMessage(MessagePtr msg) { // Filter away blatantly unallowed messages switch(msg->type()) { using namespace protocol; case MSG_LOGIN: case MSG_USER_JOIN: case MSG_USER_ATTR: case MSG_USER_LEAVE: case MSG_SESSION_CONFIG: case MSG_STREAMPOS: logger::notice() << this << "Got server-to-user only command" << msg->type(); return; case MSG_DISCONNECT: // we don't do anything with disconnect notifications from the client return; default: break; } if(msg->isOpCommand() && !isOperator()) { logger::notice() << this << "Tried to use operator command" << msg->type(); return; } // Layer control locking if(_session->isLayerControlLocked() && !isOperator()) { switch(msg->type()) { using namespace protocol; case MSG_LAYER_CREATE: case MSG_LAYER_ATTR: case MSG_LAYER_ORDER: case MSG_LAYER_RETITLE: case MSG_LAYER_DELETE: logger::debug() << this << "Non-operator use of layer control command"; return; default: break; } } // Locking (note. applies only to command stream) if(msg->isCommand()) { if(isDropLocked()) { // ignore command return; } else if(isHoldLocked()) { _holdqueue.append(msg); return; } // Layer specific locking. Drop commands that affect layer contents switch(msg->type()) { using namespace protocol; case MSG_PEN_MOVE: if(isLayerLocked(_session->drawingContext(_id).currentLayer)) return; break; case MSG_LAYER_ATTR: if(!isOperator() && isLayerLocked(msg.cast<LayerAttributes>().id())) return; break; case MSG_LAYER_RETITLE: if(!isOperator() && isLayerLocked(msg.cast<LayerRetitle>().id())) return; break; case MSG_LAYER_DELETE: { const LayerDelete &ld = msg.cast<LayerDelete>(); if(!isOperator() && isLayerLocked(ld.id())) return; // When merging, the layer below must be unlocked (and exist!) if(ld.merge()) { const LayerState *layer = _session->getLayerBelowId(ld.id()); if(!layer || isLayerLocked(layer->id)) return; } } break; case MSG_PUTIMAGE: if(isLayerLocked(msg.cast<PutImage>().layer())) return; break; case MSG_FILLRECT: if(isLayerLocked(msg.cast<FillRect>().layer())) return; break; default: /* other types are always allowed */ break; } } // Make sure the origin user ID is set msg->setContextId(_id); // Track state and special commands switch(msg->type()) { using namespace protocol; case MSG_TOOLCHANGE: _session->drawingContextToolChange(msg.cast<ToolChange>()); break; case MSG_PEN_MOVE: _session->drawingContextPenDown(msg.cast<PenMove>()); break; case MSG_PEN_UP: _session->drawingContextPenUp(msg.cast<PenUp>()); if(_barrierlock == BARRIER_WAIT) { _barrierlock = BARRIER_LOCKED; emit barrierLocked(); } break; case MSG_LAYER_CREATE: _session->createLayer(msg.cast<LayerCreate>(), true); break; case MSG_LAYER_ORDER: _session->reorderLayers(msg.cast<LayerOrder>()); break; case MSG_LAYER_DELETE: // drop message if layer didn't exist if(!_session->deleteLayer(msg.cast<LayerDelete>().id())) return; break; case MSG_LAYER_ACL: // drop message if layer didn't exist if(!_session->updateLayerAcl(msg.cast<LayerACL>())) return; break; case MSG_ANNOTATION_CREATE: _session->createAnnotation(msg.cast<AnnotationCreate>(), true); break; case MSG_ANNOTATION_DELETE: // drop message if annotation didn't exist if(!_session->deleteAnnotation(msg.cast<AnnotationDelete>().id())) return; break; case MSG_UNDOPOINT: // keep track of undo points handleUndoPoint(); break; case MSG_UNDO: // validate undo command if(!handleUndoCommand(msg.cast<Undo>())) return; break; case MSG_CHAT: // Chat is used also for operator commands if(msg->isOpCommand()) { handleSessionOperatorCommand(this, msg.cast<Chat>().message()); return; } // Normal chat messages are not included in the session history, // except when preservechat setting is set if(!msg.cast<Chat>().isAnnouncement() && !_session->isChatPreserved()) { for(Client *c : _session->clients()) c->sendDirectMessage(msg); return; } break; case MSG_SNAPSHOT: handleSnapshotStart(msg.cast<SnapshotMode>()); return; case MSG_SESSION_TITLE: // Keep track of title changes _session->setTitle(msg.cast<SessionTitle>().title()); break; default: break; } // Add to main command stream to be distributed to everyone _session->addToCommandStream(msg); }
/** * @brief Handle messages in normal session mode * * This one is pretty simple. The message is validated to make sure * the client is authorized to send it, etc. and it is added to the * main message stream, from which it is distributed to all connected clients. * @param msg the message received from the client */ void Client::handleSessionMessage(MessagePtr msg) { // Filter away blatantly unallowed messages switch(msg->type()) { using namespace protocol; case MSG_LOGIN: case MSG_USER_JOIN: case MSG_USER_ATTR: case MSG_USER_LEAVE: case MSG_SESSION_CONFIG: case MSG_STREAMPOS: _server->printDebug(QString("Warning: user #%1 sent server-to-user only command %2").arg(_id).arg(msg->type())); return; default: break; } if(msg->isOpCommand() && !_isOperator) { _server->printDebug(QString("Warning: normal user #%1 tried to use operator command %2").arg(_id).arg(msg->type())); return; } // Layer control locking if(_server->session().layerctrllocked && !_isOperator) { switch(msg->type()) { using namespace protocol; case MSG_LAYER_CREATE: case MSG_LAYER_ATTR: case MSG_LAYER_ORDER: case MSG_LAYER_RETITLE: case MSG_LAYER_DELETE: _server->printDebug(QString("Blocked layer control command from non-operator #%1").arg(_id)); return; default: break; } } // Locking (note. applies only to command stream) if(msg->isCommand()) { if(isDropLocked()) { // ignore command return; } else if(isHoldLocked()) { _holdqueue.append(msg); return; } // Layer specific locking. Drop commands that affect layer contents switch(msg->type()) { using namespace protocol; case MSG_PEN_MOVE: if(isLayerLocked(_server->session().drawingctx[_id].currentLayer)) return; break; case MSG_LAYER_ATTR: if(!_isOperator && isLayerLocked(msg.cast<LayerAttributes>().id())) return; break; case MSG_LAYER_RETITLE: if(!_isOperator && isLayerLocked(msg.cast<LayerRetitle>().id())) return; break; case MSG_LAYER_DELETE: if(!_isOperator && isLayerLocked(msg.cast<LayerDelete>().id())) return; break; case MSG_PUTIMAGE: if(isLayerLocked(msg.cast<PutImage>().layer())) return; break; default: /* other types are always allowed */ break; } } // Make sure the origin user ID is set msg->setContextId(_id); // Track state and special commands switch(msg->type()) { using namespace protocol; case MSG_TOOLCHANGE: _server->session().drawingContextToolChange(msg.cast<ToolChange>()); break; case MSG_PEN_MOVE: _server->session().drawingContextPenDown(msg.cast<PenMove>()); break; case MSG_PEN_UP: _server->session().drawingContextPenUp(msg.cast<PenUp>()); if(_barrierlock == BARRIER_WAIT) { _barrierlock = BARRIER_LOCKED; emit barrierLocked(); } break; case MSG_LAYER_CREATE: _server->session().createLayer(msg.cast<LayerCreate>(), true); break; case MSG_LAYER_ORDER: _server->session().reorderLayers(msg.cast<LayerOrder>()); break; case MSG_LAYER_DELETE: // drop message if layer didn't exist if(!_server->session().deleteLayer(msg.cast<LayerDelete>().id())) return; break; case MSG_LAYER_ACL: // drop message if layer didn't exist if(!_server->session().updateLayerAcl(msg.cast<LayerACL>())) return; break; case MSG_ANNOTATION_CREATE: _server->session().createAnnotation(msg.cast<AnnotationCreate>(), true); break; case MSG_ANNOTATION_DELETE: // drop message if annotation didn't exist if(!_server->session().deleteAnnotation(msg.cast<AnnotationDelete>().id())) return; break; case MSG_UNDOPOINT: // keep track of undo points handleUndoPoint(); break; case MSG_UNDO: // validate undo command if(!handleUndoCommand(msg.cast<Undo>())) return; break; case MSG_CHAT: // Chat is used also for operator commands if(_isOperator && handleOperatorCommand(msg->contextId(), msg.cast<Chat>().message())) return; break; case MSG_SNAPSHOT: handleSnapshotStart(msg.cast<SnapshotMode>()); return; default: break; } // Add to main command stream to be distributed to everyone _server->addToCommandStream(msg); }