diff options
| -rw-r--r-- | CMakeLists.txt | 38 | ||||
| -rw-r--r-- | boost-dbus/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | boost-dbus/include/dbus/element.hpp | 17 | ||||
| -rw-r--r-- | boost-dbus/include/dbus/impl/message_iterator.hpp | 3 | ||||
| -rw-r--r-- | boost-dbus/include/dbus/impl/message_iterator.ipp | 7 | ||||
| -rw-r--r-- | boost-dbus/include/dbus/message.hpp | 198 | ||||
| -rw-r--r-- | boost-dbus/test/avahi.cpp | 121 | ||||
| -rw-r--r-- | boost-dbus/test/message.cpp | 17 | ||||
| -rw-r--r-- | crow/include/crow/http_server.h | 7 | ||||
| -rw-r--r-- | crow/include/crow/logging.h | 140 | ||||
| -rw-r--r-- | crow/include/crow/websocket.h | 4 | ||||
| -rw-r--r-- | docs/profile.md | 5 | ||||
| -rw-r--r-- | g3log/g3log.cpp | 2 | ||||
| -rw-r--r-- | include/ast_jpeg_decoder.hpp | 1 | ||||
| -rw-r--r-- | include/ast_video_puller.hpp | 11 | ||||
| -rw-r--r-- | include/crow/g3_logger.hpp | 6 | ||||
| -rw-r--r-- | include/ssl_key_handler.hpp | 27 | ||||
| -rw-r--r-- | include/web_kvm.hpp | 12 | ||||
| -rw-r--r-- | src/token_authorization_middleware_test.cpp | 17 | ||||
| -rw-r--r-- | src/webserver_main.cpp | 249 | ||||
| -rw-r--r-- | static/js/sensorController.js | 45 |
21 files changed, 635 insertions, 296 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index b1971d2..f5b913a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,28 +106,28 @@ message("OPENSSL_INCLUDE_DIR ${OPENSSL_INCLUDE_DIR}") #g3 logging # G3logger does some unfortunate compile options, so cheat a little bit and copy/paste -set(LOG_SRC ${CMAKE_CURRENT_SOURCE_DIR}/g3log) +#set(LOG_SRC ${CMAKE_CURRENT_SOURCE_DIR}/g3log) -file(GLOB_RECURSE SRC_FILES ${LOG_SRC}/*.cpp ${LOG_SRC}/*.ipp) -file(GLOB_RECURSE HEADER_FILES ${LOG_SRC}/*.hpp) +#file(GLOB_RECURSE SRC_FILES ${LOG_SRC}/*.cpp ${LOG_SRC}/*.ipp) +#file(GLOB_RECURSE HEADER_FILES ${LOG_SRC}/*.hpp) -IF (MSVC OR MINGW) - list(REMOVE_ITEM SRC_FILES ${LOG_SRC}/crashhandler_unix.cpp) -ELSE() - list(REMOVE_ITEM SRC_FILES ${LOG_SRC}/crashhandler_windows.cpp ${LOG_SRC}/g3log/stacktrace_windows.hpp ${LOG_SRC}/stacktrace_windows.cpp) -ENDIF (MSVC OR MINGW) +#IF (MSVC OR MINGW) +# list(REMOVE_ITEM SRC_FILES ${LOG_SRC}/crashhandler_unix.cpp) +#ELSE() +# list(REMOVE_ITEM SRC_FILES ${LOG_SRC}/crashhandler_windows.cpp ${LOG_SRC}/g3log/stacktrace_windows.hpp ${LOG_SRC}/stacktrace_windows.cpp) +#ENDIF (MSVC OR MINGW) # Create the g3log library -include_directories(${LOG_SRC}) +#include_directories(${LOG_SRC}) -add_library(g3logger ${SRC_FILES}) -set_target_properties(g3logger PROPERTIES LINKER_LANGUAGE CXX) +#add_library(g3logger ${SRC_FILES}) +#set_target_properties(g3logger PROPERTIES LINKER_LANGUAGE CXX) # clean up some warnings in files we don't own -if(("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")) - set_source_files_properties(g3log/src/logcapture.cpp PROPERTIES COMPILE_FLAGS -Wno-braced-scalar-init) - set_source_files_properties(g3log/src/filesink.cpp PROPERTIES COMPILE_FLAGS -Wno-braced-scalar-init) - set_source_files_properties(g3log/src/logworker.cpp PROPERTIES COMPILE_FLAGS -Wno-braced-scalar-init) -endif() +#if(("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")) +# set_source_files_properties(g3log/src/logcapture.cpp PROPERTIES COMPILE_FLAGS -Wno-braced-scalar-init) +# set_source_files_properties(g3log/src/filesink.cpp PROPERTIES COMPILE_FLAGS -Wno-braced-scalar-init) +# set_source_files_properties(g3log/src/logworker.cpp PROPERTIES COMPILE_FLAGS -Wno-braced-scalar-init) +#endif() #lib jpeg set(BUILD_STATIC ON) @@ -230,7 +230,7 @@ if(${BUILD_UT}) add_executable(webtest ${HDR_FILES} ${SRC_FILES} ${UT_FILES}) target_link_libraries(webtest gmock gtest) target_link_libraries(webtest pthread) - target_link_libraries(webtest g3logger) + #target_link_libraries(webtest g3logger) target_link_libraries(webtest ${OPENSSL_LIBRARIES}) target_link_libraries(webtest ${ZLIB_LIBRARIES}) @@ -247,7 +247,7 @@ add_subdirectory(static) # bmcweb add_executable(bmcweb ${WEBSERVER_MAIN} ${HDR_FILES} ${SRC_FILES}) target_link_libraries(bmcweb pthread) -target_link_libraries(bmcweb g3logger) +#target_link_libraries(bmcweb g3logger) target_link_libraries(bmcweb ${OPENSSL_LIBRARIES}) target_link_libraries(bmcweb ${ZLIB_LIBRARIES}) target_link_libraries(bmcweb ${DBUS_LIBRARIES}) @@ -257,7 +257,7 @@ install (TARGETS bmcweb DESTINATION bin) add_executable(getvideo src/getvideo_main.cpp) target_link_libraries(getvideo pthread) -target_link_libraries(getvideo g3logger) +#target_link_libraries(getvideo g3logger) # Visual Studio Code helper # this needs to be at the end to make sure all includes are handled correctly diff --git a/boost-dbus/CMakeLists.txt b/boost-dbus/CMakeLists.txt index 9d07a5b..736d030 100644 --- a/boost-dbus/CMakeLists.txt +++ b/boost-dbus/CMakeLists.txt @@ -20,7 +20,11 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/test) ############### # import Boost +add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY) +add_definitions(-DBOOST_SYSTEM_NO_DEPRECATED) +add_definitions(-DBOOST_ALL_NO_LIB) find_package(Boost REQUIRED) + include_directories(${Boost_INCLUDE_DIRS}) link_directories(${Boost_LIBRARY_DIRS}) diff --git a/boost-dbus/include/dbus/element.hpp b/boost-dbus/include/dbus/element.hpp index 276b593..32cdcdc 100644 --- a/boost-dbus/include/dbus/element.hpp +++ b/boost-dbus/include/dbus/element.hpp @@ -8,7 +8,9 @@ #include <dbus/dbus.h> #include <string> +#include <vector> #include <boost/cstdint.hpp> +#include <boost/variant.hpp> namespace dbus { @@ -31,6 +33,11 @@ typedef boost::uint64_t uint64; // unix_fd typedef std::string string; + +typedef boost::variant<std::string, bool, byte, int16, uint16, int32, uint32, int64, + uint64, double> + dbus_variant; + struct object_path { string value; }; @@ -97,6 +104,16 @@ struct element<string> { static const int code = DBUS_TYPE_STRING; }; +template <typename Element> +struct element<std::vector<Element>> { + static const int code = DBUS_TYPE_ARRAY; +}; + +template <> +struct element<dbus_variant> { + static const int code = DBUS_TYPE_VARIANT; +}; + template <> struct element<object_path> { static const int code = DBUS_TYPE_OBJECT_PATH; diff --git a/boost-dbus/include/dbus/impl/message_iterator.hpp b/boost-dbus/include/dbus/impl/message_iterator.hpp index 44bcf2e..6969b8b 100644 --- a/boost-dbus/include/dbus/impl/message_iterator.hpp +++ b/boost-dbus/include/dbus/impl/message_iterator.hpp @@ -34,7 +34,8 @@ class message_iterator { bool next(); bool has_next(); - int get_arg_type(); + char get_arg_type(); + int get_element_count(); void get_basic(void *value); diff --git a/boost-dbus/include/dbus/impl/message_iterator.ipp b/boost-dbus/include/dbus/impl/message_iterator.ipp index eb2584f..034f658 100644 --- a/boost-dbus/include/dbus/impl/message_iterator.ipp +++ b/boost-dbus/include/dbus/impl/message_iterator.ipp @@ -58,7 +58,12 @@ inline bool message_iterator::has_next() return dbus_message_iter_has_next(&DBusMessageIter_); } -inline int message_iterator::get_arg_type() +inline int message_iterator::get_element_count() +{ + return dbus_message_iter_get_element_count(&DBusMessageIter_); +} + +inline char message_iterator::get_arg_type() { return dbus_message_iter_get_arg_type(&DBusMessageIter_); } diff --git a/boost-dbus/include/dbus/message.hpp b/boost-dbus/include/dbus/message.hpp index aa5e89c..695e1fc 100644 --- a/boost-dbus/include/dbus/message.hpp +++ b/boost-dbus/include/dbus/message.hpp @@ -104,6 +104,7 @@ class message { struct packer { impl::message_iterator iter_; packer(message& m) { impl::message_iterator::init_append(m, iter_); } + packer(){}; template <typename Element> packer& pack(const Element& e) { return *this << e; @@ -112,6 +113,7 @@ class message { struct unpacker { impl::message_iterator iter_; unpacker(message& m) { impl::message_iterator::init(m, iter_); } + unpacker() {} template <typename Element> unpacker& unpack(Element& e) { @@ -147,16 +149,106 @@ operator<<(message::packer& p, const Element& e) { return p; } +template <typename Key, typename Value> +message::packer& operator<<(message::packer& p, + const std::vector<std::pair<Key, Value>>& v) { + message::packer sub; + char signature[] = {'{', element<Key>::code, element<Value>::code, '}', 0}; + + p.iter_.open_container(DBUS_TYPE_ARRAY, signature, sub.iter_); + for (auto& element : v) { + sub << element; + } + + p.iter_.close_container(sub.iter_); + return p; +} + +template <typename Element> +message::packer& operator<<(message::packer& p, const std::vector<Element>& v) { + message::packer sub; + char signature[] = {element<Element>::code, 0}; + p.iter_.open_container(element<std::vector<Element>>::code, signature, + sub.iter_); + for (auto& element : v) { + sub << element; + } + + p.iter_.close_container(sub.iter_); + return p; +} + inline message::packer& operator<<(message::packer& p, const char* c) { p.iter_.append_basic(element<string>::code, &c); return p; } +template <typename Key, typename Value> +inline message::packer& operator<<(message::packer& p, + const std::pair<Key, Value> element) { + message::packer dict_entry; + p.iter_.open_container(DBUS_TYPE_DICT_ENTRY, NULL, dict_entry.iter_); + dict_entry << element.first; + dict_entry << element.second; + p.iter_.close_container(dict_entry.iter_); + return p; +} + inline message::packer& operator<<(message::packer& p, const string& e) { const char* c = e.c_str(); return p << c; } +inline message::packer& operator<<(message::packer& p, const dbus_variant& v) { + message::packer sub; + char type = 0; + // TODO(ed) there must be a better (more typesafe) way to do this + switch (v.which()) { + case 0: + type = element<std::string>::code; + break; + case 1: + type = element<bool>::code; + break; + case 2: + type = element<byte>::code; + break; + case 3: + type = element<int16>::code; + break; + case 4: + type = element<uint16>::code; + break; + case 5: + type = element<int32>::code; + break; + case 6: + type = element<uint32>::code; + break; + case 7: + type = element<int64>::code; + break; + case 8: + type = element<uint64>::code; + break; + case 9: + type = element<double>::code; + break; + + default: + // TODO(ed) throw exception + break; + } + char signature[] = {type, 0}; + + p.iter_.open_container(element<dbus_variant>::code, signature, sub.iter_); + boost::apply_visitor([&](auto val) { sub << val; }, v); + // sub << element; + p.iter_.close_container(sub.iter_); + + return p; +} + template <typename Element> message::unpacker operator>>(message m, Element& e) { return message::unpacker(m).unpack(e); @@ -178,32 +270,98 @@ inline message::unpacker& operator>>(message::unpacker& u, string& s) { return u; } +inline message::unpacker& operator>>(message::unpacker& u, dbus_variant& v) { + message::unpacker sub; + u.iter_.recurse(sub.iter_); + + auto arg_type = sub.iter_.get_arg_type(); + // sub.iter_.get_basic(&c); + // Todo(ed) find a better way to do this lookup table + switch (arg_type) { + case element<std::string>::code: { + std::string s; + sub >> s; + v = s; + } break; + case element<bool>::code: { + bool b; + sub >> b; + v = b; + } break; + case element<byte>::code: { + byte b; + sub >> b; + v = b; + } break; + case element<int16>::code: { + int16 b; + sub >> b; + v = b; + } break; + case element<uint16>::code: { + uint16 b; + sub >> b; + v = b; + } break; + case element<int32>::code: { + int32 b; + sub >> b; + v = b; + } break; + case element<uint32>::code: { + uint32 b; + sub >> b; + v = b; + } break; + case element<int64>::code: { + int64 b; + sub >> b; + v = b; + } break; + case element<uint64>::code: { + uint64 b; + sub >> b; + v = b; + } break; + case element<double>::code: { + double b; + sub >> b; + v = b; + } break; + + default: + // TODO(ed) throw exception + break; + } + u.iter_.next(); + return u; +} + +template <typename Key, typename Value> +inline message::unpacker& operator>>(message::unpacker& u, + std::pair<Key, Value>& v) { + message::unpacker sub; + u.iter_.recurse(sub.iter_); + sub >> v.first; + sub >> v.second; + + u.iter_.next(); + return u; +} + template <typename Element> inline message::unpacker& operator>>(message::unpacker& u, std::vector<Element>& s) { - static_assert(std::is_same<Element, std::string>::value, - "only std::vector<std::string> is implemented for now"); - impl::message_iterator sub; - u.iter_.recurse(sub); + message::unpacker sub; - const char* c; - while (sub.has_next()) { - sub.get_basic(&c); - s.emplace_back(c); - sub.next(); - } - - // TODO(ed) - // Make this generic for all types. The below code is close, but there's - // template issues and things I don't understand; - /* - auto e = message::unpacker(sub); - while (sub.has_next()) { + u.iter_.recurse(sub.iter_); + auto arg_type = sub.iter_.get_arg_type(); + while (arg_type != DBUS_TYPE_INVALID) { s.emplace_back(); - Element& element = s.back(); - e.unpack(element); + sub >> s.back(); + arg_type = sub.iter_.get_arg_type(); } -*/ + u.iter_.next(); return u; } diff --git a/boost-dbus/test/avahi.cpp b/boost-dbus/test/avahi.cpp index debbff3..8cbb82f 100644 --- a/boost-dbus/test/avahi.cpp +++ b/boost-dbus/test/avahi.cpp @@ -150,27 +150,114 @@ void query_interfaces(dbus::connection& system_bus, std::string& service_name, } } -TEST(BOOST_DBUS, ListObjects) { +TEST(BOOST_DBUS, SingleSensorChanged) { boost::asio::io_service io; dbus::connection system_bus(io, dbus::bus::system); - dbus::endpoint test_daemon("org.freedesktop.DBus", "/", - "org.freedesktop.DBus"); + dbus::match ma(system_bus, + "type='signal',path_namespace='/xyz/openbmc_project/sensors'"); + dbus::filter f(system_bus, [](dbus::message& m) { + auto member = m.get_member(); + return member == "PropertiesChanged"; + }); - // create new service browser - dbus::message m = dbus::message::new_call(test_daemon, "ListNames"); - auto r = system_bus.send(m); - - std::vector<std::string> services; - r.unpack(services); - // todo(ed) find out why this needs to be static - static std::atomic<int> dbus_count(0); - std::cout << dbus_count << " Callers\n"; - auto names = std::make_shared<std::vector<std::string>>(); - for (auto& service : services) { - std::string name = "/"; - query_interfaces(system_bus, service, name); - } + // std::function<void(boost::system::error_code, dbus::message)> event_handler + // = + + f.async_dispatch([&](boost::system::error_code ec, dbus::message s) { + std::string object_name; + EXPECT_EQ(s.get_path(), + "/xyz/openbmc_project/sensors/temperature/LR_Brd_Temp"); + + std::vector<std::pair<std::string, dbus::dbus_variant>> values; + s.unpack(object_name).unpack(values); + + EXPECT_EQ(object_name, "xyz.openbmc_project.Sensor.Value"); + + EXPECT_EQ(values.size(), 1); + auto expected = std::pair<std::string, dbus::dbus_variant>("Value", 42); + EXPECT_EQ(values[0], expected); + + io.stop(); + }); + + dbus::endpoint test_endpoint( + "org.freedesktop.Avahi", + "/xyz/openbmc_project/sensors/temperature/LR_Brd_Temp", + "org.freedesktop.DBus.Properties"); + + auto signal_name = std::string("PropertiesChanged"); + auto m = dbus::message::new_signal(test_endpoint, signal_name); + + m.pack("xyz.openbmc_project.Sensor.Value"); + + std::vector<std::pair<std::string, dbus::dbus_variant>> map2; + + map2.emplace_back("Value", 42); + + m.pack(map2); + + auto removed = std::vector<uint32_t>(); + m.pack(removed); + system_bus.async_send(m, + [&](boost::system::error_code ec, dbus::message s) {}); io.run(); } + +TEST(BOOST_DBUS, MultipleSensorChanged) { + boost::asio::io_service io; + dbus::connection system_bus(io, dbus::bus::system); + + dbus::match ma(system_bus, + "type='signal',path_namespace='/xyz/openbmc_project/sensors'"); + dbus::filter f(system_bus, [](dbus::message& m) { + auto member = m.get_member(); + return member == "PropertiesChanged"; + }); + + int count = 0; + f.async_dispatch([&](boost::system::error_code ec, dbus::message s) { + std::string object_name; + EXPECT_EQ(s.get_path(), + "/xyz/openbmc_project/sensors/temperature/LR_Brd_Temp"); + + std::vector<std::pair<std::string, dbus::dbus_variant>> values; + s.unpack(object_name).unpack(values); + + EXPECT_EQ(object_name, "xyz.openbmc_project.Sensor.Value"); + + EXPECT_EQ(values.size(), 1); + auto expected = std::pair<std::string, dbus::dbus_variant>("Value", 42); + EXPECT_EQ(values[0], expected); + count++; + if (count == 2) { + io.stop(); + } + + }); + + dbus::endpoint test_endpoint( + "org.freedesktop.Avahi", + "/xyz/openbmc_project/sensors/temperature/LR_Brd_Temp", + "org.freedesktop.DBus.Properties"); + + auto signal_name = std::string("PropertiesChanged"); + auto m = dbus::message::new_signal(test_endpoint, signal_name); + + m.pack("xyz.openbmc_project.Sensor.Value"); + + std::vector<std::pair<std::string, dbus::dbus_variant>> map2; + + map2.emplace_back("Value", 42); + + m.pack(map2); + + auto removed = std::vector<uint32_t>(); + m.pack(removed); + system_bus.async_send(m, + [&](boost::system::error_code ec, dbus::message s) {}); + system_bus.async_send(m, + [&](boost::system::error_code ec, dbus::message s) {}); + io.run(); +}
\ No newline at end of file diff --git a/boost-dbus/test/message.cpp b/boost-dbus/test/message.cpp index d591f61..8c8169f 100644 --- a/boost-dbus/test/message.cpp +++ b/boost-dbus/test/message.cpp @@ -3,9 +3,9 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -#include <dbus/error.hpp> #include <dbus/connection.hpp> #include <dbus/endpoint.hpp> +#include <dbus/error.hpp> #include <dbus/filter.hpp> #include <dbus/match.hpp> #include <dbus/message.hpp> @@ -35,6 +35,21 @@ TEST(MessageTest, CallMessage) { // m.get_sender(); } +TEST(MessageTest, Misc) { + auto signal_name = std::string("PropertiesChanged"); + dbus::endpoint test_endpoint( + "org.freedesktop.Avahi", + "/xyz/openbmc_project/sensors/temperature/LR_Brd_Temp", + "org.freedesktop.DBus.Properties"); + auto m = dbus::message::new_signal(test_endpoint, signal_name); + + dbus::dbus_variant v(std::string("hello world")); + m.pack(v); + + std::vector<dbus::dbus_variant> av{{std::string("hello world"), 1, 42}}; + m.pack(av); +} + // I actually don't know what to do with these yet. /* TEST(MessageTest, ErrorMessage) diff --git a/crow/include/crow/http_server.h b/crow/include/crow/http_server.h index 5ed2927..b0d63d3 100644 --- a/crow/include/crow/http_server.h +++ b/crow/include/crow/http_server.h @@ -140,10 +140,9 @@ class Server { do_accept(); - std::thread([this] { - io_service_.run(); - CROW_LOG_INFO << "Exiting."; - }).join(); + io_service_.run(); + CROW_LOG_INFO << "Exiting."; + } void stop() { diff --git a/crow/include/crow/logging.h b/crow/include/crow/logging.h index bfdedb9..4e142b1 100644 --- a/crow/include/crow/logging.h +++ b/crow/include/crow/logging.h @@ -1,3 +1,141 @@ #pragma once -#include <crow/g3_logger.hpp>
\ No newline at end of file +#include <string> +#include <cstdio> +#include <cstdlib> +#include <ctime> +#include <iostream> +#include <sstream> + +#include "crow/settings.h" + +namespace crow +{ + enum class LogLevel + { +#ifndef ERROR + DEBUG = 0, + INFO, + WARNING, + ERROR, + CRITICAL, +#endif + + Debug = 0, + Info, + Warning, + Error, + Critical, + }; + + class ILogHandler { + public: + virtual void log(std::string message, LogLevel level) = 0; + }; + + class CerrLogHandler : public ILogHandler { + public: + void log(std::string message, LogLevel /*level*/) override { + std::cerr << message; + } + }; + + class logger { + + private: + // + static std::string timestamp() + { + char date[32]; + time_t t = time(0); + + tm my_tm; + +#ifdef _MSC_VER + gmtime_s(&my_tm, &t); +#else + gmtime_r(&t, &my_tm); +#endif + + size_t sz = strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", &my_tm); + return std::string(date, date+sz); + } + + public: + + + logger(std::string prefix, LogLevel level) : level_(level) { + #ifdef CROW_ENABLE_LOGGING + stringstream_ << "(" << timestamp() << ") [" << prefix << "] "; + #endif + + } + ~logger() { + #ifdef CROW_ENABLE_LOGGING + if(level_ >= get_current_log_level()) { + stringstream_ << std::endl; + get_handler_ref()->log(stringstream_.str(), level_); + } + #endif + } + + // + template <typename T> + logger& operator<<(T const &value) { + + #ifdef CROW_ENABLE_LOGGING + if(level_ >= get_current_log_level()) { + stringstream_ << value; + } + #endif + return *this; + } + + // + static void setLogLevel(LogLevel level) { + get_log_level_ref() = level; + } + + static void setHandler(ILogHandler* handler) { + get_handler_ref() = handler; + } + + static LogLevel get_current_log_level() { + return get_log_level_ref(); + } + + private: + // + static LogLevel& get_log_level_ref() + { + static LogLevel current_level = (LogLevel)CROW_LOG_LEVEL; + return current_level; + } + static ILogHandler*& get_handler_ref() + { + static CerrLogHandler default_handler; + static ILogHandler* current_handler = &default_handler; + return current_handler; + } + + // + std::ostringstream stringstream_; + LogLevel level_; + }; +} + +#define CROW_LOG_CRITICAL \ + if (crow::logger::get_current_log_level() <= crow::LogLevel::Critical) \ + crow::logger("CRITICAL", crow::LogLevel::Critical) +#define CROW_LOG_ERROR \ + if (crow::logger::get_current_log_level() <= crow::LogLevel::Error) \ + crow::logger("ERROR ", crow::LogLevel::Error) +#define CROW_LOG_WARNING \ + if (crow::logger::get_current_log_level() <= crow::LogLevel::Warning) \ + crow::logger("WARNING ", crow::LogLevel::Warning) +#define CROW_LOG_INFO \ + if (crow::logger::get_current_log_level() <= crow::LogLevel::Info) \ + crow::logger("INFO ", crow::LogLevel::Info) +#define CROW_LOG_DEBUG \ + if (crow::logger::get_current_log_level() <= crow::LogLevel::Debug) \ + crow::logger("DEBUG ", crow::LogLevel::Debug) diff --git a/crow/include/crow/websocket.h b/crow/include/crow/websocket.h index 65a5836..cfd75d8 100644 --- a/crow/include/crow/websocket.h +++ b/crow/include/crow/websocket.h @@ -71,7 +71,7 @@ class Connection : public connection { adaptor_.get_io_service().post(handler); } - boost::asio::io_service& get_io_service(){ + boost::asio::io_service& get_io_service() override { return adaptor_.get_io_service(); } @@ -143,7 +143,7 @@ class Connection : public connection { "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" - "Sec-WebSocket-Protocol: binary\r\n" // TODO(ed): this hardcodes binary mode + //"Sec-WebSocket-Protocol: binary\r\n" // TODO(ed): this hardcodes binary mode // find a better way "Sec-WebSocket-Accept: "; static std::string crlf = "\r\n"; diff --git a/docs/profile.md b/docs/profile.md index b6dcf98..b3ceff0 100644 --- a/docs/profile.md +++ b/docs/profile.md @@ -12,4 +12,7 @@ export HOME=/nv scp ed@hades.jf.intel.com:/home/ed/webserver/buildarm/getvideo /tmp -i /nv/.ssh/id_rsa && cd /tmp && ./getvideo && scp screen.jpg ed@hades.jf.intel.com:~/screen.foo -i /nv/.ssh/id_rsa -kill -12 1480
\ No newline at end of file +kill -12 1480 + + +dbus-monitor --system "type='signal',path_namespace='/xyz/openbmc_project/sensors'"
\ No newline at end of file diff --git a/g3log/g3log.cpp b/g3log/g3log.cpp index 86a437b..9b6539a 100644 --- a/g3log/g3log.cpp +++ b/g3log/g3log.cpp @@ -141,7 +141,7 @@ namespace g3 { */ bool shutDownLoggingForActiveOnly(LogWorker *active) { if (isLoggingInitialized() && nullptr != active && (active != g_logger_instance)) { - LOG(WARNING) << "\n\t\tAttempted to shut down logging, but the ID of the Logger is not the one that is active." + std::cerr << "\n\t\tAttempted to shut down logging, but the ID of the Logger is not the one that is active." << "\n\t\tHaving multiple instances of the g3::LogWorker is likely a BUG" << "\n\t\tEither way, this call to shutDownLogging was ignored" << "\n\t\tTry g3::internal::shutDownLogging() instead"; diff --git a/include/ast_jpeg_decoder.hpp b/include/ast_jpeg_decoder.hpp index 6e5a3d4..b5144ab 100644 --- a/include/ast_jpeg_decoder.hpp +++ b/include/ast_jpeg_decoder.hpp @@ -6,7 +6,6 @@ #include <ast_video_types.hpp> #include <cassert> #include <cstdint> -#include <g3log/g3log.hpp> #include <iostream> #include <vector> diff --git a/include/ast_video_puller.hpp b/include/ast_video_puller.hpp index fd29ca5..6575d7e 100644 --- a/include/ast_video_puller.hpp +++ b/include/ast_video_puller.hpp @@ -2,7 +2,6 @@ #include <assert.h> #include <ast_video_types.hpp> -#include <g3log/g3log.hpp> #include <iostream> #include <mutex> #include <vector> @@ -96,13 +95,13 @@ class SimpleVideoPuller { std::cout << "Write done\n"; */ - LOG(DEBUG) << "Reading\n"; + std::cout << "Reading\n"; status = read(video_fd, reinterpret_cast<char *>(&image_info), sizeof(image_info)); - LOG(DEBUG) << "Done reading\n"; + std::cout << "Done reading\n"; if (status != 0) { - LOG(WARNING) << "Read failed with status " << status << "\n"; + std::cerr << "Read failed with status " << status << "\n"; } raw.buffer.resize(image_info.len); @@ -150,7 +149,7 @@ class AsyncVideoPuller { dev_video, mutable_buffer, [this](const boost::system::error_code &ec, std::size_t bytes_transferred) { if (ec) { - LOG(WARNING) << "Read failed with status " << ec << "\n"; + std::cerr << "Read failed with status " << ec << "\n"; } else { this->read_done(); } @@ -158,7 +157,7 @@ class AsyncVideoPuller { } void read_done() { - LOG(DEBUG) << "Done reading\n"; + std::cout << "Done reading\n"; videobuf->buffer.resize(image_info.len); videobuf->height = image_info.parameter.features.h; diff --git a/include/crow/g3_logger.hpp b/include/crow/g3_logger.hpp index 91e2935..7dfedde 100644 --- a/include/crow/g3_logger.hpp +++ b/include/crow/g3_logger.hpp @@ -68,10 +68,10 @@ class logger { #define CROW_LOG_CRITICAL \ if (!CROW_DISABLE_LOGGING) LOG(FATAL) #define CROW_LOG_ERROR \ - if (!CROW_DISABLE_LOGGING) LOG(WARNING) + if (!CROW_DISABLE_LOGGING) std::cerr #define CROW_LOG_WARNING \ - if (!CROW_DISABLE_LOGGING) LOG(WARNING) + if (!CROW_DISABLE_LOGGING) std::cerr #define CROW_LOG_INFO \ if (!CROW_DISABLE_LOGGING) LOG(INFO) #define CROW_LOG_DEBUG \ - if (!CROW_DISABLE_LOGGING) LOG(DEBUG) + if (!CROW_DISABLE_LOGGING) std::cout diff --git a/include/ssl_key_handler.hpp b/include/ssl_key_handler.hpp index aade3fb..4948025 100644 --- a/include/ssl_key_handler.hpp +++ b/include/ssl_key_handler.hpp @@ -11,7 +11,6 @@ #include <openssl/rand.h> #include <openssl/rsa.h> #include <openssl/ssl.h> -#include <g3log/g3log.hpp> #include <random> #include <boost/asio.hpp> @@ -26,7 +25,7 @@ inline bool verify_openssl_key_cert(const std::string &filepath) { bool private_key_valid = false; bool cert_valid = false; - LOG(DEBUG) << "Checking certs in file " << filepath; + std::cout << "Checking certs in file " << filepath << "\n"; FILE *file = fopen(filepath.c_str(), "r"); if (file != NULL) { @@ -35,21 +34,21 @@ inline bool verify_openssl_key_cert(const std::string &filepath) { if (pkey) { RSA *rsa = EVP_PKEY_get1_RSA(pkey); if (rsa) { - LOG(DEBUG) << "Found an RSA key"; + std::cout << "Found an RSA key\n"; if (RSA_check_key(rsa) == 1) { // private_key_valid = true; } else { - LOG(WARNING) << "Key not valid error number " << ERR_get_error(); + std::cerr << "Key not valid error number " << ERR_get_error() << "\n"; } RSA_free(rsa); } else { EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey); if (ec) { - LOG(DEBUG) << "Found an EC key"; + std::cout << "Found an EC key\n"; if (EC_KEY_check_key(ec) == 1) { private_key_valid = true; } else { - LOG(WARNING) << "Key not valid error number " << ERR_get_error(); + std::cerr << "Key not valid error number " << ERR_get_error() << "\n"; } EC_KEY_free(ec); } @@ -58,14 +57,14 @@ inline bool verify_openssl_key_cert(const std::string &filepath) { if (private_key_valid) { X509 *x509 = PEM_read_X509(file, NULL, NULL, NULL); if (!x509) { - LOG(DEBUG) << "error getting x509 cert " << ERR_get_error(); + std::cout << "error getting x509 cert " << ERR_get_error() << "\n"; } else { rc = X509_verify(x509, pkey); if (rc == 1) { cert_valid = true; } else { - LOG(WARNING) << "Error in verifying private key signature " - << ERR_get_error(); + std::cerr << "Error in verifying private key signature " + << ERR_get_error() << "\n"; } } } @@ -79,16 +78,16 @@ inline bool verify_openssl_key_cert(const std::string &filepath) { inline void generate_ssl_certificate(const std::string &filepath) { FILE *pFile = NULL; - LOG(WARNING) << "Generating new keys"; + std::cout << "Generating new keys\n"; init_openssl(); - // LOG(WARNING) << "Generating RSA key"; + // std::cerr << "Generating RSA key"; // EVP_PKEY *pRsaPrivKey = create_rsa_key(); - LOG(WARNING) << "Generating EC key"; + std::cerr << "Generating EC key\n"; EVP_PKEY *pRsaPrivKey = create_ec_key(); if (pRsaPrivKey) { - LOG(WARNING) << "Generating x509 Certificate"; + std::cerr << "Generating x509 Certificate\n"; // Use this code to directly generate a certificate X509 *x509; x509 = X509_new(); @@ -221,7 +220,7 @@ inline void ensure_openssl_key_present_and_valid(const std::string &filepath) { pem_file_valid = verify_openssl_key_cert(filepath); if (!pem_file_valid) { - LOG(WARNING) << "Error in verifying signature, regenerating"; + std::cerr << "Error in verifying signature, regenerating\n"; generate_ssl_certificate(filepath); } } diff --git a/include/web_kvm.hpp b/include/web_kvm.hpp index 1671382..3d33347 100644 --- a/include/web_kvm.hpp +++ b/include/web_kvm.hpp @@ -205,7 +205,7 @@ void request_routes(Crow<Middlewares...>& app) { bool is_binary) { switch (meta.vnc_state) { case VncState::AWAITING_CLIENT_VERSION: { - LOG(DEBUG) << "Client sent: " << data; + std::cout << "Client sent: " << data; if (data == rfb_3_8_version_string || data == rfb_3_7_version_string) { std::string auth_types{1, @@ -248,20 +248,20 @@ void request_routes(Crow<Middlewares...>& app) { server_init_msg.pixel_format.green_shift = 8; server_init_msg.pixel_format.blue_shift = 0; server_init_msg.name_length = 0; - LOG(DEBUG) << "size: " << sizeof(server_init_msg); + std::cout << "size: " << sizeof(server_init_msg); // TODO(ed) this is ugly. Crow should really have a span type // interface // to avoid the copy, but alas, today it does not. std::string s(reinterpret_cast<char*>(&server_init_msg), sizeof(server_init_msg)); - LOG(DEBUG) << "s.size() " << s.size(); + std::cout << "s.size() " << s.size(); conn.send_binary(s); meta.vnc_state = VncState::MAIN_LOOP; } break; case VncState::MAIN_LOOP: { if (data.size() >= sizeof(client_to_server_msg_type)) { auto type = static_cast<client_to_server_msg_type>(data[0]); - LOG(DEBUG) << "Received client message type " << (uint32_t)type + std::cout << "Received client message type " << (uint32_t)type << "\n"; switch (type) { case client_to_server_msg_type::set_pixel_format: { @@ -301,10 +301,10 @@ void request_routes(Crow<Middlewares...>& app) { this_rect.height = out.height; this_rect.encoding = static_cast<uint8_t>(encoding_type::raw); - LOG(DEBUG) << "Encoding is " << this_rect.encoding; + std::cout << "Encoding is " << this_rect.encoding; this_rect.data.reserve(this_rect.width * this_rect.height * 4); - LOG(DEBUG) << "Width " << out.width << " Height " + std::cout << "Width " << out.width << " Height " << out.height; for (int i = 0; i < out.width * out.height; i++) { diff --git a/src/token_authorization_middleware_test.cpp b/src/token_authorization_middleware_test.cpp index 49933c9..e3a18f1 100644 --- a/src/token_authorization_middleware_test.cpp +++ b/src/token_authorization_middleware_test.cpp @@ -6,13 +6,7 @@ using namespace crow; using namespace std; -class KnownLoginAuthenticator { - public: - inline bool authenticate(const std::string& username, - const std::string& password) { - return (username == "dude") && (password == "foo"); - } -}; + // Tests that static urls are correctly passed TEST(TokenAuthentication, TestBasicReject) { @@ -185,6 +179,15 @@ TEST(TokenAuthentication, TestPostBadLoginUrl) { app.stop(); } +// Test class that allows login for a fixed password. +class KnownLoginAuthenticator { + public: + inline bool authenticate(const std::string& username, + const std::string& password) { + return (username == "dude") && (password == "foo"); + } +}; + TEST(TokenAuthentication, TestSuccessfulLogin) { App<crow::TokenAuthorization<KnownLoginAuthenticator>> app; app.bindaddr("127.0.0.1").port(45451); diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp index 3baf388..d20b8d5 100644 --- a/src/webserver_main.cpp +++ b/src/webserver_main.cpp @@ -20,7 +20,6 @@ #include "crow/utility.h" #include "crow/websocket.h" -#include "color_cout_g3_sink.hpp" #include "security_headers_middleware.hpp" #include "ssl_key_handler.hpp" #include "token_authorization_middleware.hpp" @@ -42,171 +41,30 @@ #include <string> #include <unordered_set> -using sensor_values = std::vector<std::pair<std::string, int32_t>>; - -sensor_values read_sensor_values() { - sensor_values values; - DBusError err; - - int ret; - bool stat; - dbus_uint32_t level; - - // initialiset the errors - dbus_error_init(&err); - - // connect to the system bus and check for errors - DBusConnection* conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err); - if (dbus_error_is_set(&err)) { - fprintf(stderr, "Connection Error (%s)\n", err.message); - dbus_error_free(&err); - } - if (NULL == conn) { - exit(1); - } - - // create a new method call and check for errors - DBusMessage* msg = dbus_message_new_method_call( - "org.openbmc.Sensors", // target for the method call - "/org/openbmc/sensors/tach", // object to call on - "org.freedesktop.DBus.Introspectable", // interface to call on - "Introspect"); // method name - if (NULL == msg) { - fprintf(stderr, "Message Null\n"); - exit(1); - } - - DBusPendingCall* pending; - // send message and get a handle for a reply - if (!dbus_connection_send_with_reply(conn, msg, &pending, - -1)) { // -1 is default timeout - fprintf(stderr, "Out Of Memory!\n"); - exit(1); - } - if (NULL == pending) { - fprintf(stderr, "Pending Call Null\n"); - exit(1); - } - dbus_connection_flush(conn); - - // free message - dbus_message_unref(msg); - - // block until we recieve a reply - dbus_pending_call_block(pending); - - // get the reply message - msg = dbus_pending_call_steal_reply(pending); - if (NULL == msg) { - fprintf(stderr, "Reply Null\n"); - exit(1); - } - // free the pending message handle - dbus_pending_call_unref(pending); - - // read the parameters - DBusMessageIter args; - char* xml_struct = NULL; - if (!dbus_message_iter_init(msg, &args)) { - fprintf(stderr, "Message has no arguments!\n"); - } - - // read the arguments - if (!dbus_message_iter_init(msg, &args)) { - fprintf(stderr, "Message has no arguments!\n"); - } else if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&args)) { - fprintf(stderr, "Argument is not string!\n"); - } else { - dbus_message_iter_get_basic(&args, &xml_struct); +static std::shared_ptr<dbus::connection> system_bus; +static std::shared_ptr<dbus::match> sensor_match; +static std::shared_ptr<dbus::filter> sensor_filter; +static std::shared_ptr<dbus::filter> sensor_callback; + +std::unordered_set<crow::websocket::connection*> users; + +void on_sensor_update(boost::system::error_code ec, dbus::message s) { + std::string object_name; + std::vector<std::pair<std::string, dbus::dbus_variant>> values; + s.unpack(object_name).unpack(values); + crow::json::wvalue j; + for (auto& value : values) { + //std::cout << "Got sensor value for " << s.get_path() << "\n"; + boost::apply_visitor([&](auto val) { j[s.get_path()] = val; }, + value.second); } - std::vector<std::string> methods; - if (xml_struct != NULL) { - std::string xml_data(xml_struct); - std::vector<std::string> names; - dbus::read_dbus_xml_names(xml_data, methods); + for (auto conn : users) { + conn->send_text(crow::json::dump(j)); } - - fprintf(stdout, "Found %zd sensors \n", methods.size()); - - for (auto& method : methods) { - // TODO(Ed) make sure sensor exposes SensorValue interface - // create a new method call and check for errors - DBusMessage* msg = dbus_message_new_method_call( - "org.openbmc.Sensors", // target for the method call - ("/org/openbmc/sensors/tach/" + method).c_str(), // object to call on - "org.openbmc.SensorValue", // interface to call on - "getValue"); // method name - if (NULL == msg) { - fprintf(stderr, "Message Null\n"); - exit(1); - } - - DBusPendingCall* pending; - // send message and get a handle for a reply - if (!dbus_connection_send_with_reply(conn, msg, &pending, - -1)) { // -1 is default timeout - fprintf(stderr, "Out Of Memory!\n"); - exit(1); - } - if (NULL == pending) { - fprintf(stderr, "Pending Call Null\n"); - exit(1); - } - dbus_connection_flush(conn); - - // free message - dbus_message_unref(msg); - - // block until we recieve a reply - dbus_pending_call_block(pending); - - // get the reply message - msg = dbus_pending_call_steal_reply(pending); - if (NULL == msg) { - fprintf(stderr, "Reply Null\n"); - exit(1); - } - // free the pending message handle - dbus_pending_call_unref(pending); - - // read the parameters - DBusMessageIter args; - int32_t value; - if (!dbus_message_iter_init(msg, &args)) { - fprintf(stderr, "Message has no arguments!\n"); - } - - // read the arguments - if (!dbus_message_iter_init(msg, &args)) { - fprintf(stderr, "Message has no arguments!\n"); - } else if (DBUS_TYPE_VARIANT != dbus_message_iter_get_arg_type(&args)) { - fprintf(stderr, "Argument is not string!\n"); - } else { - DBusMessageIter sub; - dbus_message_iter_recurse(&args, &sub); - auto type = dbus_message_iter_get_arg_type(&sub); - if (DBUS_TYPE_INT32 != type) { - fprintf(stderr, "Variant subType is not int32 it is %d\n", type); - } else { - dbus_message_iter_get_basic(&sub, &value); - values.emplace_back(method.c_str(), value); - } - } - } - - // free reply and close connection - dbus_message_unref(msg); - return values; -} + sensor_filter->async_dispatch(on_sensor_update); +}; int main(int argc, char** argv) { - auto worker(g3::LogWorker::createLogWorker()); - if (false) { - auto handle = worker->addDefaultLogger("bmcweb", "/tmp/"); - } - g3::initializeLogging(worker.get()); - auto sink_handle = worker->addSink(std::make_unique<crow::ColorCoutSink>(), - &crow::ColorCoutSink::ReceiveLogMessage); bool enable_ssl = true; std::string ssl_pem_file("server.pem"); @@ -249,32 +107,53 @@ int main(int argc, char** argv) { CROW_ROUTE(app, "/sensorws") .websocket() .onopen([&](crow::websocket::connection& conn) { - dbus::connection system_bus(conn.get_io_service(), dbus::bus::system); - dbus::match ma(system_bus, - "type='signal',sender='org.freedesktop.DBus', " - "interface='org.freedesktop.DBus.Properties',member=" - "'PropertiesChanged'"); - dbus::filter f(system_bus, [](dbus::message& m) { return true; }); - - f.async_dispatch([&](boost::system::error_code ec, dbus::message s) { - std::cout << "got event\n"; - //f.async_dispatch(event_handler); - }); - + system_bus = std::make_shared<dbus::connection>(conn.get_io_service(), + dbus::bus::system); + sensor_match = std::make_shared<dbus::match>( + *system_bus, + "type='signal',path_namespace='/xyz/openbmc_project/sensors'"); + + sensor_filter = + std::make_shared<dbus::filter>(*system_bus, [](dbus::message& m) { + auto member = m.get_member(); + return member == "PropertiesChanged"; + }); + /* + std::function<void(boost::system::error_code, dbus::message)> + sensor_callback = [&conn, sensor_callback]( + boost::system::error_code ec, dbus::message s) { + std::string object_name; + std::vector<std::pair<std::string, dbus::dbus_variant>> values; + s.unpack(object_name).unpack(values); + crow::json::wvalue j; + for (auto& value : values) { + std::cout << "Got sensor value for " << s.get_path() << "\n"; + boost::apply_visitor([&](auto val) { j[s.get_path()] = val; }, + value.second); + } + for (auto conn : users) { + conn.send_text(crow::json::dump(j)); + } + sensor_filter->async_dispatch(sensor_callback); + }; + */ + sensor_filter->async_dispatch(on_sensor_update); + users.insert(&conn); + ; }) .onclose( [&](crow::websocket::connection& conn, const std::string& reason) { - + // TODO(ed) needs lock + users.erase(&conn); }) .onmessage([&](crow::websocket::connection& conn, const std::string& data, bool is_binary) { - + CROW_LOG_ERROR << "Got unexpected message from client on sensorws"; }); CROW_ROUTE(app, "/sensortest") ([](const crow::request& req, crow::response& res) { crow::json::wvalue j; - auto values = read_sensor_values(); dbus::connection system_bus(*req.io_service, dbus::bus::system); dbus::endpoint test_daemon("org.openbmc.Sensors", @@ -294,7 +173,7 @@ int main(int argc, char** argv) { "/org/openbmc/sensors/tach/" + object, "org.openbmc.SensorValue"); dbus::message m2 = dbus::message::new_call(test_daemon, "getValue"); - + system_bus.async_send( m2, [&](const boost::system::error_code ec, dbus::message r) { int32_t value; @@ -311,9 +190,9 @@ int main(int argc, char** argv) { CROW_ROUTE(app, "/intel/firmwareupload") .methods("POST"_method)([](const crow::request& req) { // TODO(ed) handle errors here (file exists already and is locked, ect) - std::ofstream out("/tmp/fw_update_image", std::ofstream::out | - std::ofstream::binary | - std::ofstream::trunc); + std::ofstream out( + "/tmp/fw_update_image", + std::ofstream::out | std::ofstream::binary | std::ofstream::trunc); out << req.body; out.close(); @@ -323,17 +202,17 @@ int main(int argc, char** argv) { return j; }); - LOG(DEBUG) << "Building SSL context"; + std::cout << "Building SSL context\n"; int port = 18080; - LOG(DEBUG) << "Starting webserver on port " << port; + std::cout << "Starting webserver on port " << port << "\n"; app.port(port); if (enable_ssl) { - LOG(DEBUG) << "SSL Enabled"; + std::cout << "SSL Enabled\n"; auto ssl_context = ensuressl::get_ssl_context(ssl_pem_file); app.ssl(std::move(ssl_context)); } - //app.concurrency(4); + // app.concurrency(4); app.run(); } diff --git a/static/js/sensorController.js b/static/js/sensorController.js index f1e0812..67c9d82 100644 --- a/static/js/sensorController.js +++ b/static/js/sensorController.js @@ -1,8 +1,41 @@ angular.module('bmcApp').controller('sensorController', [ - '$scope', '$http', - function($scope, $http) { - $http.get('/sensortest').then(function(sensor_values) { - $scope.sensor_values = sensor_values; - }) + '$scope', '$http', '$location', 'websocketService', + function($scope, $http, $location, websocketService) { + $scope.sensor_values = {}; + + var host = $location.host(); + var port = $location.port(); + var protocol = "ws://"; + if ($location.protocol() === 'https') { + protocol = 'wss://'; + } + websocketService.start(protocol + host + ":" + port + "/sensorws", function (evt) { + var obj = JSON.parse(evt.data); + $scope.$apply(function () { + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + console.log(key + " -> " + obj[key]); + $scope.sensor_values[key] = obj[key]; + } + } + }); + }); + } -]);
\ No newline at end of file +]); + +app.factory('websocketService', function () { + return { + start: function (url, callback) { + var websocket = new WebSocket(url); + websocket.onopen = function () { + }; + websocket.onclose = function () { + }; + websocket.onmessage = function (evt) { + callback(evt); + }; + } + } + } +);
\ No newline at end of file |

