1 #pragma once 2 #include <crow/app.h> 3 #include <crow/websocket.h> 4 #include <sys/socket.h> 5 6 #include <boost/container/flat_map.hpp> 7 #include <boost/container/flat_set.hpp> 8 #include <webserver_common.hpp> 9 10 namespace crow 11 { 12 namespace obmc_console 13 { 14 15 static std::unique_ptr<boost::asio::local::stream_protocol::socket> host_socket; 16 17 static std::array<char, 4096> outputBuffer; 18 static std::string inputBuffer; 19 20 static boost::container::flat_set<crow::websocket::Connection*> sessions; 21 22 static bool doingWrite = false; 23 24 void doWrite() 25 { 26 if (doingWrite) 27 { 28 BMCWEB_LOG_DEBUG << "Already writing. Bailing out"; 29 return; 30 } 31 32 if (inputBuffer.empty()) 33 { 34 BMCWEB_LOG_DEBUG << "Outbuffer empty. Bailing out"; 35 return; 36 } 37 38 doingWrite = true; 39 host_socket->async_write_some( 40 boost::asio::buffer(inputBuffer.data(), inputBuffer.size()), 41 [](boost::beast::error_code ec, std::size_t bytes_written) { 42 doingWrite = false; 43 inputBuffer.erase(0, bytes_written); 44 45 if (ec == boost::asio::error::eof) 46 { 47 for (auto session : sessions) 48 { 49 session->close("Error in reading to host port"); 50 } 51 return; 52 } 53 if (ec) 54 { 55 BMCWEB_LOG_ERROR << "Error in host serial write " << ec; 56 return; 57 } 58 doWrite(); 59 }); 60 } 61 62 void doRead() 63 { 64 BMCWEB_LOG_DEBUG << "Reading from socket"; 65 host_socket->async_read_some( 66 boost::asio::buffer(outputBuffer.data(), outputBuffer.size()), 67 [](const boost::system::error_code& ec, std::size_t bytesRead) { 68 BMCWEB_LOG_DEBUG << "read done. Read " << bytesRead << " bytes"; 69 if (ec) 70 { 71 BMCWEB_LOG_ERROR << "Couldn't read from host serial port: " 72 << ec; 73 for (auto session : sessions) 74 { 75 session->close("Error in connecting to host port"); 76 } 77 return; 78 } 79 boost::beast::string_view payload(outputBuffer.data(), bytesRead); 80 for (auto session : sessions) 81 { 82 session->sendText(payload); 83 } 84 doRead(); 85 }); 86 } 87 88 void connectHandler(const boost::system::error_code& ec) 89 { 90 if (ec) 91 { 92 BMCWEB_LOG_ERROR << "Couldn't connect to host serial port: " << ec; 93 for (auto session : sessions) 94 { 95 session->close("Error in connecting to host port"); 96 } 97 return; 98 } 99 100 doWrite(); 101 doRead(); 102 } 103 104 void requestRoutes(CrowApp& app) 105 { 106 BMCWEB_ROUTE(app, "/console0") 107 .websocket() 108 .onopen([](crow::websocket::Connection& conn) { 109 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; 110 111 sessions.insert(&conn); 112 if (host_socket == nullptr) 113 { 114 const std::string consoleName("\0obmc-console", 13); 115 boost::asio::local::stream_protocol::endpoint ep(consoleName); 116 117 // This is a hack. For whatever reason boost local endpoint has 118 // a check to see if a string is null terminated, and if it is, 119 // it drops the path character count by 1. For abstract 120 // sockets, we need the count to be the full sizeof(s->sun_path) 121 // (ie 108), even though our path _looks_ like it's null 122 // terminated. This is likely a bug in asio that needs to be 123 // submitted Todo(ed). so the cheat here is to break the 124 // abstraction for a minute, write a 1 to the last byte, this 125 // causes the check at the end of resize here: 126 // https://www.boost.org/doc/libs/1_68_0/boost/asio/local/detail/impl/endpoint.ipp 127 // to not decrement us unesssesarily. 128 struct sockaddr_un* s = 129 reinterpret_cast<sockaddr_un*>(ep.data()); 130 s->sun_path[sizeof(s->sun_path) - 1] = 1; 131 ep.resize(sizeof(sockaddr_un)); 132 s->sun_path[sizeof(s->sun_path) - 1] = 0; 133 134 host_socket = std::make_unique< 135 boost::asio::local::stream_protocol::socket>( 136 conn.getIoService()); 137 host_socket->async_connect(ep, connectHandler); 138 } 139 }) 140 .onclose( 141 [](crow::websocket::Connection& conn, const std::string& reason) { 142 sessions.erase(&conn); 143 if (sessions.empty()) 144 { 145 host_socket = nullptr; 146 inputBuffer.clear(); 147 inputBuffer.shrink_to_fit(); 148 } 149 }) 150 .onmessage([](crow::websocket::Connection& conn, 151 const std::string& data, bool is_binary) { 152 inputBuffer += data; 153 doWrite(); 154 }); 155 } 156 } // namespace obmc_console 157 } // namespace crow 158