#include "StarUniverseController.hpp" #include "StarRoot.hpp" #include "StarException.hpp" #include "StarBuffer.hpp" #include "StarJsonVariant.hpp" #include "StarPerformance.hpp" #include "StarLogging.hpp" namespace Star { UniverseController::UniverseController(TcpSocketPtr socket) : Thread("UniverseController") { m_socket = socket; m_parent = 0; m_stop = false; m_closed = false; } void UniverseController::stop() { MutexLocker lock(m_lock); m_stop = true; m_condition.broadcast(); m_socket->close(); } bool UniverseController::isClosed() { return m_closed; } void UniverseController::bind(UniverseControllerSet* parent) { m_parent = parent; } void UniverseController::receiveResponse(Variant const& response) { MutexLocker lock(m_lock); m_responses.append(response); m_condition.broadcast(); } void UniverseController::run() { try { Buffer buffer(1024); MutexLocker lock(m_lock); while (!m_stop && m_socket->isOpen()) { buffer.clear(); List modeStack; bool trivial = true; int mode = 0; /* * Mode list * 0 primitive mode, accepts json or a single line of text which is interpreted as list of strings * 1 list mode * 20 object initial mode * 21 object next mode * 22 object colon mode * 3 string mode 31 espaced char in string mode * 4 value mode * 5 literal mode * 6 done, but search for newline * -1 complete request * -2 syntax error * */ while (mode >= 0) { char c; if (m_socket->read(&c, 1) != 1) throw StarException("Unexpected result from socket read\n"); if (c == '\r') continue; // hello dos buffer.write(&c, 1); revisit: if ((mode == 0) || (mode == 1) || (mode == 20) || (mode == 4) || (mode == 6)) { if ((c == ' ') || (c == '\t')) { // trim whitespace continue; } } if ((mode == 1) || (mode == 20) || (mode == 4)) { if (c == '\n') { // trim newlines continue; } } switch (mode) { case 0: { // primitive mode if (c == '\n') { mode = -1; //empty line, meh } else if (c == '[') { modeStack.push_back(6); modeStack.push_back(1); trivial = false; mode = 4; } else if (c == '{') { modeStack.push_back(6); modeStack.push_back(20); trivial = false; mode = 4; } else if (c == '"') { modeStack.push_back(6); mode = 3; } else { } break; } case 1: { // list mode if (c == ',') { modeStack.push_back(1); mode = 4; } else if (c == ']') { mode = modeStack.takeLast(); } else mode = -2; break; } case 20: { // object if (c == '}') { mode = modeStack.takeLast(); } else { modeStack.push_back(22); mode = 4; goto revisit; } break; } case 21: { // continue bit of object pairs if (c == '}') { mode = modeStack.takeLast(); } else if (c == ',') { modeStack.push_back(22); mode = 4; } break; } case 22: { // colon bit of object pairs if (c == ':') { modeStack.push_back(21); mode = 4; } else { mode = -2; } break; } case 3: { // string mode if (c == '\\') mode = 31; else if (c == '"') mode = modeStack.takeLast(); break; } case 31: { // escaped in string mode mode = 3; break; } case 4: { // value mode if (c == '[') { modeStack.push_back(mode); modeStack.push_back(1); trivial = false; mode = 4; } else if (c == '{') { modeStack.push_back(mode); modeStack.push_back(20); trivial = false; mode = 4; } else if (c == '"') { modeStack.push_back(mode); mode = 3; } else { mode = 5; goto revisit; } break; } case 5: { // literal mode if ((c == ' ') || (c == '\t') || (c == '\n') || (c == ',') || (c == ']') || (c == '}') || (c == ':')) { mode = modeStack.takeLast(); goto revisit; } break; } case 6: { if (c == '\n') { mode = -1; } else { mode = -2; } break; } } } bool hasCommand = false; VariantList request; if (mode == -2) { while (true) { char c; if (m_socket->read(&c, 1) != 1) throw StarException("Unexpected result from socket read\n"); if (c != '\n') continue; } VariantList failure; failure.append("syntaxError"); m_responses.push_back(failure); } else { String requestStr = String(buffer.ptr(), buffer.dataSize()); if (requestStr.size() == 0) { VariantList failure; failure.append("typeHelpForMoreInfo"); m_responses.push_back(failure); } else if (trivial) { VariantList parts; for (auto part : requestStr.splitWhitespace()) { if (part.size() > 0) parts.append(part); } if (parts.size() == 0) { VariantList failure; failure.append("typeHelpForMoreInfo"); m_responses.push_back(failure); } else { request = parts; hasCommand = true; } } else { try { Variant parseResult = Variant::parse(requestStr); bool valid = true; if (valid && (parseResult.type() != Variant::Type::LIST)) valid = false; if (valid) request = parseResult.toList(); if (valid && (request.size() < 1)) valid = false; if (valid && (request[0].type() != Variant::Type::STRING)) valid = false; if (!valid) { VariantList failure; failure.append("parseError"); failure.append("Must be a list with the first element of string type"); m_responses.push_back(failure); } else { hasCommand = true; } } catch (const std::exception& e) { VariantList failure; failure.append("parseError"); failure.append(e.what()); m_responses.push_back(failure); } } } if (hasCommand) { m_parent->commandReceived(make_shared(shared_from_this(), request)); } if (hasCommand) while ((m_responses.size() == 0) && (!m_stop)) m_condition.wait(m_lock); for (auto response : m_responses) { buffer.clear(); outputUtf8Json(response, IODeviceOutputIterator(&buffer), 0, false); char c = '\n'; buffer.write(&c, 1); if (m_socket->write(buffer.ptr(), buffer.dataSize()) != buffer.dataSize()) throw StarException("Unexpected result from socket write\n"); m_socket->flush(); } m_responses.clear(); } } catch (const NetworkException& e) { goto finally; } catch (const std::exception& e) { Logger::error("UniverseController: Exception caught: %s", e.what()); goto finally; } catch (...) { goto finally; } finally: { m_closed = true; } m_parent->notify(); } UniverseControllerSet::UniverseControllerSet(UniverseServer* universeServer): Thread("UniverseControllerSet") { m_universeServer = universeServer; m_stop = false; m_active = true; } void UniverseControllerSet::addNewConnection(UniverseControllerPtr controller) { MutexLocker lock(m_lock); if (m_stop) throw StarException("Shutting down"); controller->bind(this); m_controllers.add(controller); m_active = true; m_condition.broadcast(); controller->start(); } void UniverseControllerSet::stop() { MutexLocker lock(m_lock); m_stop = true; m_active = true; m_condition.broadcast(); } void UniverseControllerSet::notify() { MutexLocker lock(m_lock); m_condition.broadcast(); } void UniverseControllerSet::commandTypeHelpForMoreInfo(ControllerCommandPtr command) { VariantList result; result.append("todo"); command->respond(result); } void UniverseControllerSet::commandCounters(ControllerCommandPtr command) { VariantList result; result.append("counters"); VariantMap config; if (command->request().size() >=2) config = command->request()[1].toMap(); bool reset = config.value("reset", true).toBool(); auto assets = Root::singleton().assets(); for (auto records : assets->variant("/perf.config:parenting").list()) Performance::setCounterHierarchy(records.getString(1).utf8Ptr(), records.getString(0).utf8Ptr()); result.append(Star::Performance::dumpToVariant(reset)); command->respond(result); } void UniverseControllerSet::commandUnknown(ControllerCommandPtr command) { VariantList result; result.append("unkownCommand"); result.append(command->command()); command->respond(result); } void UniverseControllerSet::run() { while (!m_stop) { { MutexLocker lock(m_lock); m_active = false; } Set closed; { MutexLocker lock(m_lock); for (auto iter = m_controllers.begin(); iter != m_controllers.end(); iter++) { auto controller = *iter; if (controller->isClosed()) closed.add(controller); } } for (auto controller : closed) { m_controllers.remove(controller); controller->stop(); controller->join(); } List commands; { MutexLocker lock(m_lock); for (auto command : m_commandQueue) commands.push_back(command); m_commandQueue.clear(); } for (auto command : commands) { if (command->command() == "typeHelpForMoreInfo") commandTypeHelpForMoreInfo(command); else if (command->command() == "counters") commandCounters(command); else if (command->command() == "worlds") m_universeServer->commandReceived(command); else if (command->command() == "world") m_universeServer->commandReceived(command); else commandUnknown(command); } { MutexLocker lock(m_lock); if (!m_active) m_condition.wait(m_lock); } } for (auto iter = m_controllers.begin(); iter != m_controllers.end(); iter++) { auto controller = *iter; controller->stop(); controller->join(); } m_controllers.clear(); } void UniverseControllerSet::commandReceived(ControllerCommandPtr command) { MutexLocker lock(m_lock); m_commandQueue.push_back(command); m_active = true; m_condition.broadcast(); } ControllerCommand::ControllerCommand(weak_ptr controller, VariantList request) { m_controller = controller; m_request = request; m_command = request[0].toString(); } void ControllerCommand::popOuterCommand(int innerIndex) { m_request = m_request[innerIndex].toList(); m_command = m_request[0].toString(); } String const& ControllerCommand::command() { return m_command; } VariantList& ControllerCommand::request() { return m_request; } void ControllerCommand::respond(VariantList const& response) { if (auto controller = m_controller.lock()) controller->receiveResponse(response); } }