xref: /openbmc/bmcweb/include/kvm_websocket.hpp (revision 3eb2f35f28249b9b5dc2159a44ca75a0fa7677a5)
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_kvm
13 {
14 
15 static std::unique_ptr<boost::asio::ip::tcp::socket> hostSocket;
16 
17 // TODO(ed) validate that these buffer sizes are sane
18 static boost::beast::flat_static_buffer<1024U * 50U> outputBuffer;
19 static boost::beast::flat_static_buffer<1024U> inputBuffer;
20 
21 static crow::websocket::Connection* session = nullptr;
22 
23 static bool doingWrite = false;
24 
25 inline void doWrite()
26 {
27     if (doingWrite)
28     {
29         BMCWEB_LOG_DEBUG << "Already writing.  Bailing out";
30         return;
31     }
32     if (inputBuffer.size() == 0)
33     {
34         BMCWEB_LOG_DEBUG << "inputBuffer empty.  Bailing out";
35         return;
36     }
37 
38     doingWrite = true;
39     hostSocket->async_write_some(
40         inputBuffer.data(),
41         [](boost::beast::error_code ec, std::size_t bytes_written) {
42             BMCWEB_LOG_DEBUG << "Wrote " << bytes_written << "bytes";
43             doingWrite = false;
44             inputBuffer.consume(bytes_written);
45 
46             if (session == nullptr)
47             {
48                 return;
49             }
50             if (ec == boost::asio::error::eof)
51             {
52                 session->close("KVM socket port closed");
53                 return;
54             }
55             if (ec)
56             {
57                 session->close("Error in reading to host port");
58                 BMCWEB_LOG_ERROR << "Error in KVM socket write " << ec;
59                 return;
60             }
61             doWrite();
62         });
63 }
64 
65 inline void doRead();
66 
67 inline void readDone(const boost::system::error_code& ec, std::size_t bytesRead)
68 {
69     outputBuffer.commit(bytesRead);
70     BMCWEB_LOG_DEBUG << "read done.  Read " << bytesRead << " bytes";
71     if (ec)
72     {
73         BMCWEB_LOG_ERROR << "Couldn't read from KVM socket port: " << ec;
74         if (session != nullptr)
75         {
76             session->close("Error in connecting to KVM port");
77         }
78         return;
79     }
80     if (session == nullptr)
81     {
82         return;
83     }
84 
85     boost::beast::string_view payload(
86         static_cast<const char*>(outputBuffer.data().data()), bytesRead);
87     BMCWEB_LOG_DEBUG << "Sending payload size " << payload.size();
88     session->sendBinary(payload);
89     outputBuffer.consume(bytesRead);
90 
91     doRead();
92 }
93 
94 inline void doRead()
95 {
96     std::size_t bytes = outputBuffer.capacity() - outputBuffer.size();
97     BMCWEB_LOG_DEBUG << "Reading " << bytes << " from kvm socket";
98     hostSocket->async_read_some(
99         outputBuffer.prepare(outputBuffer.capacity() - outputBuffer.size()),
100         readDone);
101 }
102 
103 inline void connectHandler(const boost::system::error_code& ec)
104 {
105     if (ec)
106     {
107         BMCWEB_LOG_ERROR << "Couldn't connect to KVM socket port: " << ec;
108         if (session != nullptr)
109         {
110             session->close("Error in connecting to KVM port");
111         }
112         return;
113     }
114 
115     doWrite();
116     doRead();
117 }
118 
119 inline void requestRoutes(CrowApp& app)
120 {
121     BMCWEB_ROUTE(app, "/kvm/0")
122         .websocket()
123         .onopen([](crow::websocket::Connection& conn) {
124             BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
125 
126             if (session != nullptr)
127             {
128                 conn.close("User already connected");
129                 return;
130             }
131 
132             session = &conn;
133             if (hostSocket == nullptr)
134             {
135                 boost::asio::ip::tcp::endpoint endpoint(
136                     boost::asio::ip::address::from_string("127.0.0.1"), 5900);
137 
138                 hostSocket = std::make_unique<boost::asio::ip::tcp::socket>(
139                     conn.get_io_context());
140                 hostSocket->async_connect(endpoint, connectHandler);
141             }
142         })
143         .onclose(
144             [](crow::websocket::Connection& conn, const std::string& reason) {
145                 session = nullptr;
146                 hostSocket = nullptr;
147                 inputBuffer.reset();
148                 outputBuffer.reset();
149             })
150         .onmessage([](crow::websocket::Connection& conn,
151                       const std::string& data, bool is_binary) {
152             if (data.length() > inputBuffer.capacity())
153             {
154                 BMCWEB_LOG_ERROR << "Buffer overrun when writing "
155                                  << data.length() << " bytes";
156                 conn.close("Buffer overrun");
157                 return;
158             }
159 
160             BMCWEB_LOG_DEBUG << "Read " << data.size()
161                              << " bytes from websocket";
162             boost::asio::buffer_copy(inputBuffer.prepare(data.size()),
163                                      boost::asio::buffer(data));
164             BMCWEB_LOG_DEBUG << "commiting " << data.size()
165                              << " bytes from websocket";
166             inputBuffer.commit(data.size());
167 
168             BMCWEB_LOG_DEBUG << "inputbuffer size " << inputBuffer.size();
169             doWrite();
170         });
171 }
172 } // namespace obmc_kvm
173 } // namespace crow
174