/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2022 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * * without restriction, including without limitation the rights to use, copy, modify, * * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to the following * * conditions: * * * * The above copyright notice and this permission notice shall be included in all copies * * or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr const char* _loggerCat = "ServerModule: Connection"; constexpr const char* MessageKeyType = "type"; constexpr const char* MessageKeyPayload = "payload"; constexpr const char* MessageKeyTopic = "topic"; constexpr const char* VersionTopicKey = "version"; constexpr const char* AuthenticationTopicKey = "authorize"; constexpr const char* DocumentationTopicKey = "documentation"; constexpr const char* GetPropertyTopicKey = "get"; constexpr const char* LuaScriptTopicKey = "luascript"; constexpr const char* SessionRecordingTopicKey = "sessionRecording"; constexpr const char* SetPropertyTopicKey = "set"; constexpr const char* ShortcutTopicKey = "shortcuts"; constexpr const char* SubscriptionTopicKey = "subscribe"; constexpr const char* TimeTopicKey = "time"; constexpr const char* TriggerPropertyTopicKey = "trigger"; constexpr const char* BounceTopicKey = "bounce"; constexpr const char* FlightControllerTopicKey = "flightcontroller"; } // namespace namespace openspace { Connection::Connection(std::unique_ptr s, std::string address, bool authorized, const std::string& password) : _socket(std::move(s)) , _address(std::move(address)) , _isAuthorized(authorized) { ghoul_assert(_socket, "Socket must not be nullptr"); _topicFactory.registerClass( AuthenticationTopicKey, [password](bool, const ghoul::Dictionary&, ghoul::MemoryPoolBase* pool) { if (pool) { void* ptr = pool->allocate(sizeof(AuthorizationTopic)); return new (ptr) AuthorizationTopic(password); } else { return new AuthorizationTopic(password); } } ); _topicFactory.registerClass(DocumentationTopicKey); _topicFactory.registerClass(GetPropertyTopicKey); _topicFactory.registerClass(LuaScriptTopicKey); _topicFactory.registerClass(SessionRecordingTopicKey); _topicFactory.registerClass(SetPropertyTopicKey); _topicFactory.registerClass(ShortcutTopicKey); _topicFactory.registerClass(SubscriptionTopicKey); _topicFactory.registerClass(TimeTopicKey); _topicFactory.registerClass(TriggerPropertyTopicKey); _topicFactory.registerClass(BounceTopicKey); _topicFactory.registerClass(FlightControllerTopicKey); _topicFactory.registerClass(VersionTopicKey); } void Connection::handleMessage(const std::string& message) { ZoneScoped try { nlohmann::json j = nlohmann::json::parse(message.c_str()); try { handleJson(j); } catch (const std::domain_error& e) { LERROR(fmt::format("JSON handling error from: {}. {}", message, e.what())); } } catch (const std::out_of_range& e) { LERROR(fmt::format("JSON handling error from: {}. {}", message, e.what())); } catch (const std::exception& e) { LERROR(e.what()); } catch (...) { if (!isAuthorized()) { _socket->disconnect(); LERROR(fmt::format( "Could not parse JSON: '{}'. Connection is unauthorized. Disconnecting.", message )); return; } else { std::string sanitizedString = message; std::transform( message.begin(), message.end(), sanitizedString.begin(), [](wchar_t c) { return std::isprint(c, std::locale("")) ? char(c) : ' '; } ); LERROR(fmt::format("Could not parse JSON: '{}'", sanitizedString)); } } } void Connection::handleJson(const nlohmann::json& json) { ZoneScoped auto topicJson = json.find(MessageKeyTopic); auto payloadJson = json.find(MessageKeyPayload); if (topicJson == json.end() || !topicJson->is_number_integer()) { LERROR("Topic must be an integer"); return; } if (payloadJson == json.end() || !payloadJson->is_object()) { LERROR("Payload must be an object"); return; } // The topic id may be an already discussed topic, or a new one. TopicId topicId = *topicJson; auto topicIt = _topics.find(topicId); if (topicIt == _topics.end()) { // The topic id is not registered: Initialize a new topic. auto typeJson = json.find(MessageKeyType); if (typeJson == json.end() || !typeJson->is_string()) { LERROR(fmt::format( "A type must be specified (`{}`) as a string when " "a new topic is initialized", MessageKeyType )); return; } std::string type = *typeJson; if (!isAuthorized() && type != AuthenticationTopicKey) { LERROR("Connection isn't authorized."); return; } std::unique_ptr topic = std::unique_ptr(_topicFactory.create(type)); topic->initialize(this, topicId); topic->handleJson(*payloadJson); if (!topic->isDone()) { _topics.emplace(topicId, std::move(topic)); } } else { if (!isAuthorized()) { LERROR("Connection isn't authorized."); return; } // Dispatch the message to the existing topic. Topic& topic = *topicIt->second; topic.handleJson(*payloadJson); if (topic.isDone()) { _topics.erase(topicIt); } } } void Connection::sendMessage(const std::string& message) { ZoneScoped _socket->putMessage(message); } void Connection::sendJson(const nlohmann::json& json) { ZoneScoped sendMessage(json.dump()); } bool Connection::isAuthorized() const { return _isAuthorized; } void Connection::setThread(std::thread&& thread) { _thread = std::move(thread); } std::thread& Connection::thread() { return _thread; } ghoul::io::Socket* Connection::socket() { return _socket.get(); } void Connection::setAuthorized(bool status) { _isAuthorized = status; } } // namespace openspace