summaryrefslogtreecommitdiff
path: root/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice
diff options
context:
space:
mode:
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice')
-rw-r--r--meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0001-EventService-Fix-retry-handling-for-http-client.patch543
-rw-r--r--meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0002-EventService-https-client-support.patch401
-rw-r--r--meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0003-Move-EventService-init-to-later-stage.patch50
-rw-r--r--meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0004-Add-Server-Sent-Events-support.patch477
-rw-r--r--meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0005-Add-SSE-style-subscription-support-to-eventservice.patch627
-rw-r--r--meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0006-Add-EventService-SSE-filter-support.patch243
-rw-r--r--meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/README22
7 files changed, 2363 insertions, 0 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0001-EventService-Fix-retry-handling-for-http-client.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0001-EventService-Fix-retry-handling-for-http-client.patch
new file mode 100644
index 000000000..b46d30149
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0001-EventService-Fix-retry-handling-for-http-client.patch
@@ -0,0 +1,543 @@
+From ae55e89c14ea5abef0895409c956f5f4c38f330f Mon Sep 17 00:00:00 2001
+From: Sunitha Harish <sunithaharish04@gmail.com>
+Date: Fri, 19 Feb 2021 13:38:31 +0530
+Subject: [PATCH 1/2] EventService : Fix retry handling for http-client
+
+When the event send/receive is failed, the bmcweb does not handle
+the failure to tear-down the complete connection and start a fresh
+
+The keep-alive header from the event listener is read to update
+the connection states, so that the connection will be kept alive
+or closed as per the subscriber's specifications
+
+Updated the connection state machine to handle retry logic properly.
+Avoided multiple simultaneous async calls which crashes the bmcweb. So
+added connBusy flag which protects simultaneous async calls.
+
+Used boost http response parser as parser for producing the response
+message. Set the parser skip option to handle the empty response message
+from listening server.
+
+Tested by:
+ - Subscribe for the events at BMC using DMTF event listener
+ - Generate an event and see the same is received at the listener's console
+ - Update the listner to change the keep-alive to true/false and
+ observe the http-client connection states at bmcweb
+
+Change-Id: Ibb45691f139916ba2954da37beda9d4f91c7cef3
+Signed-off-by: Sunitha Harish <sunithaharish04@gmail.com>
+Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com>
+---
+ http/http_client.hpp | 289 ++++++++++--------
+ .../include/event_service_manager.hpp | 2 +-
+ 2 files changed, 163 insertions(+), 128 deletions(-)
+
+diff --git a/http/http_client.hpp b/http/http_client.hpp
+index 992ac2b..d116f6d 100644
+--- a/http/http_client.hpp
++++ b/http/http_client.hpp
+@@ -34,22 +34,28 @@ namespace crow
+ {
+
+ static constexpr uint8_t maxRequestQueueSize = 50;
++static constexpr unsigned int httpReadBodyLimit = 8192;
+
+ enum class ConnState
+ {
+ initialized,
+ resolveInProgress,
+ resolveFailed,
++ resolved,
+ connectInProgress,
+ connectFailed,
+ connected,
+ sendInProgress,
+ sendFailed,
++ recvInProgress,
+ recvFailed,
+ idle,
+- suspended,
++ closeInProgress,
+ closed,
+- terminated
++ suspended,
++ terminated,
++ abortConnection,
++ retry
+ };
+
+ class HttpClient : public std::enable_shared_from_this<HttpClient>
+@@ -58,11 +64,14 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ crow::async_resolve::Resolver resolver;
+ boost::beast::tcp_stream conn;
+ boost::asio::steady_timer timer;
+- boost::beast::flat_buffer buffer;
++ boost::beast::flat_static_buffer<httpReadBodyLimit> buffer;
+ boost::beast::http::request<boost::beast::http::string_body> req;
+- boost::beast::http::response<boost::beast::http::string_body> res;
+- std::vector<std::pair<std::string, std::string>> headers;
+- std::queue<std::string> requestDataQueue;
++ std::optional<
++ boost::beast::http::response_parser<boost::beast::http::string_body>>
++ parser;
++ boost::asio::ip::tcp::endpoint endpoint;
++ boost::circular_buffer_space_optimized<std::string> requestDataQueue{};
++ std::vector<boost::asio::ip::tcp::endpoint> endPoints;
+ ConnState state;
+ std::string subId;
+ std::string host;
+@@ -76,12 +85,7 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+
+ void doResolve()
+ {
+- if (state == ConnState::resolveInProgress)
+- {
+- return;
+- }
+ state = ConnState::resolveInProgress;
+-
+ BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":" << port;
+
+ auto respHandler =
+@@ -89,78 +93,56 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ const boost::beast::error_code ec,
+ const std::vector<boost::asio::ip::tcp::endpoint>&
+ endpointList) {
+- if (ec)
++ if (ec || (endpointList.size() == 0))
+ {
+ BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
+ self->state = ConnState::resolveFailed;
+- self->checkQueue();
++ self->handleConnState();
+ return;
+ }
+ BMCWEB_LOG_DEBUG << "Resolved";
+- self->doConnect(endpointList);
++ self->endPoints.assign(endpointList.begin(),
++ endpointList.end());
++ self->state = ConnState::resolved;
++ self->handleConnState();
+ };
+ resolver.asyncResolve(host, port, std::move(respHandler));
+ }
+
+- void doConnect(
+- const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
++ void doConnect()
+ {
+- if (state == ConnState::connectInProgress)
+- {
+- return;
+- }
+ state = ConnState::connectInProgress;
+
+ BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port;
+
+ conn.expires_after(std::chrono::seconds(30));
+ conn.async_connect(
+- endpointList, [self(shared_from_this())](
+- const boost::beast::error_code ec,
+- const boost::asio::ip::tcp::endpoint& endpoint) {
++ endPoints, [self(shared_from_this())](
++ const boost::beast::error_code ec,
++ const boost::asio::ip::tcp::endpoint& endpoint) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "Connect " << endpoint
+ << " failed: " << ec.message();
+ self->state = ConnState::connectFailed;
+- self->checkQueue();
++ self->handleConnState();
+ return;
+ }
+- self->state = ConnState::connected;
+ BMCWEB_LOG_DEBUG << "Connected to: " << endpoint;
+-
+- self->checkQueue();
++ self->state = ConnState::connected;
++ self->handleConnState();
+ });
+ }
+
+ void sendMessage(const std::string& data)
+ {
+- if (state == ConnState::sendInProgress)
+- {
+- return;
+- }
+ state = ConnState::sendInProgress;
+
+ BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port;
+
+- req.version(static_cast<int>(11)); // HTTP 1.1
+- req.target(uri);
+- req.method(boost::beast::http::verb::post);
+-
+- // Set headers
+- for (const auto& [key, value] : headers)
+- {
+- req.set(key, value);
+- }
+- req.set(boost::beast::http::field::host, host);
+- req.keep_alive(true);
+-
+ req.body() = data;
+ req.prepare_payload();
+
+- // Set a timeout on the operation
+- conn.expires_after(std::chrono::seconds(30));
+-
+ // Send the HTTP request to the remote host
+ boost::beast::http::async_write(
+ conn, req,
+@@ -171,7 +153,7 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ BMCWEB_LOG_ERROR << "sendMessage() failed: "
+ << ec.message();
+ self->state = ConnState::sendFailed;
+- self->checkQueue();
++ self->handleConnState();
+ return;
+ }
+ BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
+@@ -184,9 +166,18 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+
+ void recvMessage()
+ {
++ state = ConnState::recvInProgress;
++
++ parser.emplace(std::piecewise_construct, std::make_tuple());
++ parser->body_limit(httpReadBodyLimit);
++ // Since these are all push style eventing, we are not
++ // bothered about response body parsing.
++ // Check only for the response header
++ parser->skip(true);
++
+ // Receive the HTTP response
+ boost::beast::http::async_read(
+- conn, buffer, res,
++ conn, buffer, *parser,
+ [self(shared_from_this())](const boost::beast::error_code& ec,
+ const std::size_t& bytesTransferred) {
+ if (ec)
+@@ -194,30 +185,46 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ BMCWEB_LOG_ERROR << "recvMessage() failed: "
+ << ec.message();
+ self->state = ConnState::recvFailed;
+- self->checkQueue();
++ self->handleConnState();
+ return;
+ }
+ BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
+ << bytesTransferred;
+- boost::ignore_unused(bytesTransferred);
+-
+- // Discard received data. We are not interested.
+- BMCWEB_LOG_DEBUG << "recvMessage() data: " << self->res;
++ BMCWEB_LOG_DEBUG << "recvMessage() data: "
++ << self->parser->get();
+
+ // Send is successful, Lets remove data from queue
+ // check for next request data in queue.
+- self->requestDataQueue.pop();
++ if (!self->requestDataQueue.empty())
++ {
++ self->requestDataQueue.pop_front();
++ }
+ self->state = ConnState::idle;
+- self->checkQueue();
++
++ // Keep the connection alive if server supports it
++ // Else close the connection
++ BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
++ << self->parser->keep_alive();
++ if (!self->parser->keep_alive())
++ {
++ // Abort the connection since server is not keep-alive
++ // enabled
++ self->state = ConnState::abortConnection;
++ }
++ // Transfer ownership of the response
++ self->parser->release();
++
++ self->handleConnState();
+ });
+ }
+
+ void doClose()
+ {
++ state = ConnState::closeInProgress;
+ boost::beast::error_code ec;
+ conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
++ conn.close();
+
+- state = ConnState::closed;
+ // not_connected happens sometimes so don't bother reporting it.
+ if (ec && ec != boost::beast::errc::not_connected)
+ {
+@@ -225,112 +232,139 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ return;
+ }
+ BMCWEB_LOG_DEBUG << "Connection closed gracefully";
+- }
+-
+- void checkQueue(const bool newRecord = false)
+- {
+- if (requestDataQueue.empty())
++ if ((state != ConnState::suspended) && (state != ConnState::terminated))
+ {
+- // TODO: Having issue in keeping connection alive. So lets close if
+- // nothing to be transferred.
+- doClose();
+-
+- BMCWEB_LOG_DEBUG << "requestDataQueue is empty\n";
+- return;
++ state = ConnState::closed;
++ handleConnState();
+ }
++ }
+
++ void waitAndRetry()
++ {
+ if (retryCount >= maxRetryAttempts)
+ {
+- BMCWEB_LOG_ERROR << "Maximum number of retries is reached.";
++ BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
+
+ // Clear queue.
+ while (!requestDataQueue.empty())
+ {
+- requestDataQueue.pop();
++ requestDataQueue.pop_front();
+ }
+
+- BMCWEB_LOG_DEBUG << "Retry policy is set to " << retryPolicyAction;
++ BMCWEB_LOG_DEBUG << "Retry policy: " << retryPolicyAction;
+ if (retryPolicyAction == "TerminateAfterRetries")
+ {
+ // TODO: delete subscription
+ state = ConnState::terminated;
+- return;
+ }
+ if (retryPolicyAction == "SuspendRetries")
+ {
+ state = ConnState::suspended;
+- return;
+ }
+- // keep retrying, reset count and continue.
++ // Reset the retrycount to zero so that client can try connecting
++ // again if needed
+ retryCount = 0;
++ handleConnState();
++ return;
+ }
+
+- if ((state == ConnState::connectFailed) ||
+- (state == ConnState::sendFailed) ||
+- (state == ConnState::recvFailed))
++ if (runningTimer)
+ {
+- if (newRecord)
+- {
+- // We are already running async wait and retry.
+- // Since record is added to queue, it gets the
+- // turn in FIFO.
+- return;
+- }
+-
+- if (runningTimer)
+- {
+- BMCWEB_LOG_DEBUG << "Retry timer is already running.";
+- return;
+- }
+- runningTimer = true;
+-
+- retryCount++;
+-
+- BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs
+- << " seconds. RetryCount = " << retryCount;
+- timer.expires_after(std::chrono::seconds(retryIntervalSecs));
+- timer.async_wait(
+- [self = shared_from_this()](const boost::system::error_code&) {
+- self->runningTimer = false;
+- self->connStateCheck();
+- });
++ BMCWEB_LOG_DEBUG << "Retry timer is already running.";
+ return;
+ }
+- // reset retry count.
+- retryCount = 0;
+- connStateCheck();
++ runningTimer = true;
+
++ retryCount++;
++
++ BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs
++ << " seconds. RetryCount = " << retryCount;
++ timer.expires_after(std::chrono::seconds(retryIntervalSecs));
++ timer.async_wait(
++ [self = shared_from_this()](const boost::system::error_code ec) {
++ if (ec)
++ {
++ BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
++ // Ignore the error and continue the retry loop to attempt
++ // sending the event as per the retry policy
++ }
++ self->runningTimer = false;
++
++ // Lets close connection and start from resolve.
++ self->doClose();
++ });
+ return;
+ }
+
+- void connStateCheck()
++ void handleConnState()
+ {
+ switch (state)
+ {
+ case ConnState::resolveInProgress:
+ case ConnState::connectInProgress:
+ case ConnState::sendInProgress:
+- case ConnState::suspended:
+- case ConnState::terminated:
+- // do nothing
++ case ConnState::recvInProgress:
++ case ConnState::closeInProgress:
++ {
++ BMCWEB_LOG_DEBUG << "Async operation is already in progress";
+ break;
++ }
+ case ConnState::initialized:
+ case ConnState::closed:
++ {
++ if (requestDataQueue.empty())
++ {
++ BMCWEB_LOG_DEBUG << "requestDataQueue is empty";
++ return;
++ }
++ doResolve();
++ break;
++ }
++ case ConnState::resolved:
++ {
++ doConnect();
++ break;
++ }
++ case ConnState::suspended:
++ case ConnState::terminated:
++ {
++ doClose();
++ break;
++ }
++ case ConnState::resolveFailed:
+ case ConnState::connectFailed:
+ case ConnState::sendFailed:
+ case ConnState::recvFailed:
+- case ConnState::resolveFailed:
++ case ConnState::retry:
+ {
+- doResolve();
++ // In case of failures during connect and handshake
++ // the retry policy will be applied
++ waitAndRetry();
+ break;
+ }
+ case ConnState::connected:
+ case ConnState::idle:
+ {
++ // State idle means, previous attempt is successful
++ // State connected means, client connection is established
++ // successfully
++ if (requestDataQueue.empty())
++ {
++ BMCWEB_LOG_DEBUG << "requestDataQueue is empty";
++ return;
++ }
+ std::string data = requestDataQueue.front();
+ sendMessage(data);
+ break;
+ }
++ case ConnState::abortConnection:
++ {
++ // Server did not want to keep alive the session
++ doClose();
++ break;
++ }
++ default:
++ break;
+ }
+ }
+
+@@ -339,37 +373,38 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ const std::string& destIP, const std::string& destPort,
+ const std::string& destUri) :
+ conn(ioc),
+- timer(ioc), subId(id), host(destIP), port(destPort), uri(destUri),
+- retryCount(0), maxRetryAttempts(5), retryIntervalSecs(0),
++ timer(ioc), req(boost::beast::http::verb::post, destUri, 11),
++ state(ConnState::initialized), subId(id), host(destIP), port(destPort),
++ uri(destUri), retryCount(0), maxRetryAttempts(5), retryIntervalSecs(0),
+ retryPolicyAction("TerminateAfterRetries"), runningTimer(false)
+ {
+- state = ConnState::initialized;
++ // Set the request header
++ req.set(boost::beast::http::field::host, host);
++ req.set(boost::beast::http::field::content_type, "application/json");
++ req.keep_alive(true);
++
++ requestDataQueue.set_capacity(maxRequestQueueSize);
+ }
+
+ void sendData(const std::string& data)
+ {
+- if (state == ConnState::suspended)
++ if ((state == ConnState::suspended) || (state == ConnState::terminated))
+ {
+ return;
+ }
+-
+- if (requestDataQueue.size() <= maxRequestQueueSize)
+- {
+- requestDataQueue.push(data);
+- checkQueue(true);
+- }
+- else
+- {
+- BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data.";
+- }
+-
++ requestDataQueue.push_back(data);
++ handleConnState();
+ return;
+ }
+
+- void setHeaders(
++ void addHeaders(
+ const std::vector<std::pair<std::string, std::string>>& httpHeaders)
+ {
+- headers = httpHeaders;
++ // Set custom headers
++ for (const auto& [key, value] : httpHeaders)
++ {
++ req.set(key, value);
++ }
+ }
+
+ void setRetryConfig(const uint32_t retryAttempts,
+diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp
+index 148c703..bffa68f 100644
+--- a/redfish-core/include/event_service_manager.hpp
++++ b/redfish-core/include/event_service_manager.hpp
+@@ -412,7 +412,7 @@ class Subscription
+ reqHeaders.emplace_back(std::pair(key, val));
+ }
+ }
+- conn->setHeaders(reqHeaders);
++ conn->addHeaders(reqHeaders);
+ conn->sendData(msg);
+ this->eventSeqNum++;
+ }
+--
+2.17.1
+
diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0002-EventService-https-client-support.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0002-EventService-https-client-support.patch
new file mode 100644
index 000000000..b1f61c6fd
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0002-EventService-https-client-support.patch
@@ -0,0 +1,401 @@
+From 579fda953ec991b4e7f9d7194b08f6aa103fa0ec Mon Sep 17 00:00:00 2001
+From: AppaRao Puli <apparao.puli@linux.intel.com>
+Date: Mon, 22 Feb 2021 17:07:47 +0000
+Subject: [PATCH 2/2] EventService: https client support
+
+Add https client support for push style
+eventing. Using this BMC can push the event
+logs/telemetry data to event listener over
+secure http channel.
+
+Tested:
+ - Created subscription with https destination
+ url. Using SubmitTestEvent action set the
+ event and can see event on event listener.
+ - Validator passed.
+
+Change-Id: I44c3918b39baa2eb5fddda9d635f99aa280a422a
+Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com>
+---
+ http/http_client.hpp | 255 ++++++++++++------
+ .../include/event_service_manager.hpp | 2 +-
+ 2 files changed, 175 insertions(+), 82 deletions(-)
+
+diff --git a/http/http_client.hpp b/http/http_client.hpp
+index d116f6d..cebc857 100644
+--- a/http/http_client.hpp
++++ b/http/http_client.hpp
+@@ -20,6 +20,7 @@
+ #include <boost/beast/core/flat_buffer.hpp>
+ #include <boost/beast/core/tcp_stream.hpp>
+ #include <boost/beast/http/message.hpp>
++#include <boost/beast/ssl/ssl_stream.hpp>
+ #include <boost/beast/version.hpp>
+ #include <include/async_resolve.hpp>
+
+@@ -44,6 +45,8 @@ enum class ConnState
+ resolved,
+ connectInProgress,
+ connectFailed,
++ handshakeInProgress,
++ handshakeFailed,
+ connected,
+ sendInProgress,
+ sendFailed,
+@@ -62,7 +65,9 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ {
+ private:
+ crow::async_resolve::Resolver resolver;
++ boost::asio::ssl::context ctx{boost::asio::ssl::context::tlsv12_client};
+ boost::beast::tcp_stream conn;
++ std::optional<boost::beast::ssl_stream<boost::beast::tcp_stream&>> sslConn;
+ boost::asio::steady_timer timer;
+ boost::beast::flat_static_buffer<httpReadBodyLimit> buffer;
+ boost::beast::http::request<boost::beast::http::string_body> req;
+@@ -112,23 +117,52 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ void doConnect()
+ {
+ state = ConnState::connectInProgress;
++ sslConn.emplace(conn, ctx);
+
+ BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port;
++ auto respHandler = [self(shared_from_this())](
++ const boost::beast::error_code ec,
++ const boost::asio::ip::tcp::endpoint& endpoint) {
++ if (ec)
++ {
++ BMCWEB_LOG_ERROR << "Connect " << endpoint
++ << " failed: " << ec.message();
++ self->state = ConnState::connectFailed;
++ self->handleConnState();
++ return;
++ }
+
++ BMCWEB_LOG_DEBUG << "Connected to: " << endpoint;
++ if (self->sslConn)
++ {
++ self->performHandshake();
++ }
++ else
++ {
++ self->handleConnState();
++ }
++ };
+ conn.expires_after(std::chrono::seconds(30));
+- conn.async_connect(
+- endPoints, [self(shared_from_this())](
+- const boost::beast::error_code ec,
+- const boost::asio::ip::tcp::endpoint& endpoint) {
++ conn.async_connect(endPoints, std::move(respHandler));
++ }
++
++ void performHandshake()
++ {
++ state = ConnState::handshakeInProgress;
++
++ sslConn->async_handshake(
++ boost::asio::ssl::stream_base::client,
++ [self(shared_from_this())](const boost::beast::error_code ec) {
+ if (ec)
+ {
+- BMCWEB_LOG_ERROR << "Connect " << endpoint
+- << " failed: " << ec.message();
+- self->state = ConnState::connectFailed;
++ BMCWEB_LOG_ERROR << "SSL handshake failed: "
++ << ec.message();
++ self->state = ConnState::handshakeFailed;
+ self->handleConnState();
+ return;
+ }
+- BMCWEB_LOG_DEBUG << "Connected to: " << endpoint;
++
++ BMCWEB_LOG_DEBUG << "SSL Handshake successfull";
+ self->state = ConnState::connected;
+ self->handleConnState();
+ });
+@@ -136,106 +170,159 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+
+ void sendMessage(const std::string& data)
+ {
+- state = ConnState::sendInProgress;
+-
+ BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port;
++ state = ConnState::sendInProgress;
+
+ req.body() = data;
+ req.prepare_payload();
+
+- // Send the HTTP request to the remote host
+- boost::beast::http::async_write(
+- conn, req,
+- [self(shared_from_this())](const boost::beast::error_code& ec,
+- const std::size_t& bytesTransferred) {
+- if (ec)
+- {
+- BMCWEB_LOG_ERROR << "sendMessage() failed: "
+- << ec.message();
+- self->state = ConnState::sendFailed;
+- self->handleConnState();
+- return;
+- }
+- BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
+- << bytesTransferred;
+- boost::ignore_unused(bytesTransferred);
++ auto respHandler = [self(shared_from_this())](
++ const boost::beast::error_code ec,
++ const std::size_t& bytesTransferred) {
++ if (ec)
++ {
++ BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message();
++ self->state = ConnState::sendFailed;
++ self->handleConnState();
++ return;
++ }
+
+- self->recvMessage();
+- });
+- }
++ BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
++ << bytesTransferred;
++ boost::ignore_unused(bytesTransferred);
++ self->recvMessage();
++ };
+
++ conn.expires_after(std::chrono::seconds(30));
++ if (sslConn)
++ {
++ boost::beast::http::async_write(*sslConn, req,
++ std::move(respHandler));
++ }
++ else
++ {
++ boost::beast::http::async_write(conn, req, std::move(respHandler));
++ }
++ }
+ void recvMessage()
+ {
+ state = ConnState::recvInProgress;
+
++ auto respHandler = [self(shared_from_this())](
++ const boost::beast::error_code ec,
++ const std::size_t& bytesTransferred) {
++ if (ec && ec != boost::asio::ssl::error::stream_truncated)
++ {
++ BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message();
++
++ self->state = ConnState::recvFailed;
++ self->handleConnState();
++ return;
++ }
++
++ BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
++ << bytesTransferred;
++ boost::ignore_unused(bytesTransferred);
++ // Send is successful, Lets remove data from queue
++ // check for next request data in queue.
++ if (!self->requestDataQueue.empty())
++ {
++ self->requestDataQueue.pop_front();
++ }
++ self->state = ConnState::idle;
++ // Keep the connection alive if server supports it
++ // Else close the connection
++ BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
++ << self->parser->keep_alive();
++ if (!self->parser->keep_alive())
++ {
++ // Abort the connection since server is not keep-alive enabled
++ self->state = ConnState::abortConnection;
++ }
++ // Transfer ownership of the response
++ self->parser->release();
++
++ self->handleConnState();
++ };
+ parser.emplace(std::piecewise_construct, std::make_tuple());
+ parser->body_limit(httpReadBodyLimit);
+ // Since these are all push style eventing, we are not
+ // bothered about response body parsing.
+ // Check only for the response header
+ parser->skip(true);
++ conn.expires_after(std::chrono::seconds(30));
++ if (sslConn)
++ {
++ boost::beast::http::async_read(*sslConn, buffer, *parser,
++ std::move(respHandler));
++ }
++ else
++ {
++ boost::beast::http::async_read(conn, buffer, *parser,
++ std::move(respHandler));
++ }
++ }
++ void doClose()
++ {
++ state = ConnState::closeInProgress;
+
+- // Receive the HTTP response
+- boost::beast::http::async_read(
+- conn, buffer, *parser,
+- [self(shared_from_this())](const boost::beast::error_code& ec,
+- const std::size_t& bytesTransferred) {
++ // Set the timeout on the tcp stream socket for the async operation
++ conn.expires_after(std::chrono::seconds(30));
++ if (sslConn)
++ {
++ sslConn->async_shutdown([self = shared_from_this()](
++ const boost::system::error_code ec) {
+ if (ec)
+ {
+- BMCWEB_LOG_ERROR << "recvMessage() failed: "
+- << ec.message();
+- self->state = ConnState::recvFailed;
+- self->handleConnState();
+- return;
++ // Many https server closes connection abruptly
++ // i.e witnout close_notify. More details are at
++ // https://github.com/boostorg/beast/issues/824
++ if (ec == boost::asio::ssl::error::stream_truncated)
++ {
++ BMCWEB_LOG_INFO << "doClose(): Connection "
++ "closed by server. ";
++ }
++ else
++ {
++ BMCWEB_LOG_ERROR << "doClose() failed: "
++ << ec.message();
++ }
+ }
+- BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
+- << bytesTransferred;
+- BMCWEB_LOG_DEBUG << "recvMessage() data: "
+- << self->parser->get();
+-
+- // Send is successful, Lets remove data from queue
+- // check for next request data in queue.
+- if (!self->requestDataQueue.empty())
++ else
+ {
+- self->requestDataQueue.pop_front();
++ BMCWEB_LOG_DEBUG << "Connection closed gracefully...";
+ }
+- self->state = ConnState::idle;
++ self->conn.close();
+
+- // Keep the connection alive if server supports it
+- // Else close the connection
+- BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
+- << self->parser->keep_alive();
+- if (!self->parser->keep_alive())
++ if ((self->state != ConnState::suspended) &&
++ (self->state != ConnState::terminated))
+ {
+- // Abort the connection since server is not keep-alive
+- // enabled
+- self->state = ConnState::abortConnection;
++ self->state = ConnState::closed;
++ self->handleConnState();
+ }
+- // Transfer ownership of the response
+- self->parser->release();
+-
+- self->handleConnState();
+ });
+- }
+-
+- void doClose()
+- {
+- state = ConnState::closeInProgress;
+- boost::beast::error_code ec;
+- conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
+- conn.close();
+-
+- // not_connected happens sometimes so don't bother reporting it.
+- if (ec && ec != boost::beast::errc::not_connected)
+- {
+- BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
+- return;
+ }
+- BMCWEB_LOG_DEBUG << "Connection closed gracefully";
+- if ((state != ConnState::suspended) && (state != ConnState::terminated))
++ else
+ {
+- state = ConnState::closed;
+- handleConnState();
++ boost::beast::error_code ec;
++ conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both,
++ ec);
++ if (ec)
++ {
++ BMCWEB_LOG_ERROR << "doClose() failed: " << ec.message();
++ }
++ else
++ {
++ BMCWEB_LOG_DEBUG << "Connection closed gracefully...";
++ }
++ conn.close();
++
++ if ((state != ConnState::suspended) &&
++ (state != ConnState::terminated))
++ {
++ state = ConnState::closed;
++ handleConnState();
++ }
+ }
+ }
+
+@@ -302,6 +389,7 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ {
+ case ConnState::resolveInProgress:
+ case ConnState::connectInProgress:
++ case ConnState::handshakeInProgress:
+ case ConnState::sendInProgress:
+ case ConnState::recvInProgress:
+ case ConnState::closeInProgress:
+@@ -333,6 +421,7 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ }
+ case ConnState::resolveFailed:
+ case ConnState::connectFailed:
++ case ConnState::handshakeFailed:
+ case ConnState::sendFailed:
+ case ConnState::recvFailed:
+ case ConnState::retry:
+@@ -371,7 +460,8 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ public:
+ explicit HttpClient(boost::asio::io_context& ioc, const std::string& id,
+ const std::string& destIP, const std::string& destPort,
+- const std::string& destUri) :
++ const std::string& destUri,
++ const std::string& uriProto) :
+ conn(ioc),
+ timer(ioc), req(boost::beast::http::verb::post, destUri, 11),
+ state(ConnState::initialized), subId(id), host(destIP), port(destPort),
+@@ -384,8 +474,11 @@ class HttpClient : public std::enable_shared_from_this<HttpClient>
+ req.keep_alive(true);
+
+ requestDataQueue.set_capacity(maxRequestQueueSize);
++ if (uriProto == "https")
++ {
++ sslConn.emplace(conn, ctx);
++ }
+ }
+-
+ void sendData(const std::string& data)
+ {
+ if ((state == ConnState::suspended) || (state == ConnState::terminated))
+diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp
+index bffa68f..1e6f496 100644
+--- a/redfish-core/include/event_service_manager.hpp
++++ b/redfish-core/include/event_service_manager.hpp
+@@ -387,7 +387,7 @@ class Subscription
+ {
+ conn = std::make_shared<crow::HttpClient>(
+ crow::connections::systemBus->get_io_context(), id, host, port,
+- path);
++ path, uriProto);
+ }
+
+ Subscription(const std::shared_ptr<boost::beast::tcp_stream>& adaptor) :
+--
+2.17.1
+
diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0003-Move-EventService-init-to-later-stage.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0003-Move-EventService-init-to-later-stage.patch
new file mode 100644
index 000000000..f910915ce
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0003-Move-EventService-init-to-later-stage.patch
@@ -0,0 +1,50 @@
+From 2dcfef0170ca5ba1f934de6934db64b545ab3b55 Mon Sep 17 00:00:00 2001
+From: AppaRao Puli <apparao.puli@linux.intel.com>
+Date: Tue, 23 Mar 2021 23:36:40 +0000
+Subject: [PATCH] Move EventService init to later stage
+
+The bmcweb crash issue seen when there is eventservice
+config with subscriptions in persistent file.
+
+During EventService instantiation, it uses the "get_io_context()"
+from systemBus, so it should be called after systemBus init. So
+moved the EventService instantiation after systemBus initialization.
+
+Tested:
+ - bmcweb crash issue resolved.
+
+Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com>
+Change-Id: Iab52f0e89478e306af475066fb5691153a05677d
+---
+ src/webserver_main.cpp | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp
+index 3b9631a..f0e2207 100644
+--- a/src/webserver_main.cpp
++++ b/src/webserver_main.cpp
+@@ -80,9 +80,6 @@ int main(int /*argc*/, char** /*argv*/)
+ #ifdef BMCWEB_ENABLE_REDFISH
+ crow::redfish::requestRoutes(app);
+ redfish::RedfishService redfish(app);
+-
+- // Create EventServiceManager instance and initialize Config
+- redfish::EventServiceManager::getInstance();
+ #endif
+
+ #ifdef BMCWEB_ENABLE_DBUS_REST
+@@ -116,6 +113,11 @@ int main(int /*argc*/, char** /*argv*/)
+ crow::connections::systemBus =
+ std::make_shared<sdbusplus::asio::connection>(*io);
+
++#ifdef BMCWEB_ENABLE_REDFISH
++ // Create EventServiceManager instance and initialize Config
++ redfish::EventServiceManager::getInstance();
++#endif
++
+ #ifdef BMCWEB_ENABLE_VM_NBDPROXY
+ crow::nbd_proxy::requestRoutes(app);
+ #endif
+--
+2.17.1
+
diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0004-Add-Server-Sent-Events-support.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0004-Add-Server-Sent-Events-support.patch
new file mode 100644
index 000000000..0d31fbc72
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0004-Add-Server-Sent-Events-support.patch
@@ -0,0 +1,477 @@
+From e93a6a02b0fba3371144d474422fadacc3f25fde Mon Sep 17 00:00:00 2001
+From: AppaRao Puli <apparao.puli@linux.intel.com>
+Date: Fri, 12 Mar 2021 18:53:25 +0000
+Subject: [PATCH 4/6] Add Server-Sent-Events support
+
+Server-Sent Events is a standard describing how servers can
+initiate data transmission towards clients once an initial
+client connection has been established. Unlike websockets
+(which are bidirectional), Server-Sent Events are
+unidirectional and commonly used to send message updates or
+continuous data streams to a browser client.
+
+This is base patch for adding Server-Sent events support to
+bmcweb. Redfish eventservice SSE style subscription uses
+this and will be loaded on top of this commit.
+
+Tested:
+ - Tested using follow-up patch on top which adds
+ support for Redfish EventService SSE style subscription
+ and observed events are getting sent periodically.
+
+Change-Id: I36956565cbba30c2007852c9471f477f6d1736e9
+Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com>
+---
+ http/http_connection.hpp | 14 +-
+ http/http_response.hpp | 7 +-
+ http/routing.hpp | 70 ++++++++++
+ http/server_sent_event.hpp | 279 +++++++++++++++++++++++++++++++++++++
+ 4 files changed, 364 insertions(+), 6 deletions(-)
+ create mode 100644 http/server_sent_event.hpp
+
+diff --git a/http/http_connection.hpp b/http/http_connection.hpp
+index 4482f8d..2c8bf40 100644
+--- a/http/http_connection.hpp
++++ b/http/http_connection.hpp
+@@ -326,7 +326,7 @@ class Connection :
+ BMCWEB_LOG_INFO << "Request: "
+ << " " << this << " HTTP/" << req->version() / 10 << "."
+ << req->version() % 10 << ' ' << req->methodString()
+- << " " << req->target() << " " << req->ipAddress;
++ << " " << req->url << " " << req->ipAddress;
+
+ needToCallAfterHandlers = false;
+
+@@ -345,11 +345,15 @@ class Connection :
+ boost::asio::post(self->adaptor.get_executor(),
+ [self] { self->completeRequest(); });
+ };
+- if (req->isUpgrade() &&
+- boost::iequals(
+- req->getHeaderValue(boost::beast::http::field::upgrade),
+- "websocket"))
++
++ if ((req->isUpgrade() &&
++ boost::iequals(req->getHeaderValue(
++ boost::beast::http::field::upgrade),
++ "websocket")) ||
++ (req->url == "/sse"))
+ {
++ BMCWEB_LOG_DEBUG << "Request: " << this
++ << " is getting upgraded";
+ handler->handleUpgrade(*req, res, std::move(adaptor));
+ // delete lambda with self shared_ptr
+ // to enable connection destruction
+diff --git a/http/http_response.hpp b/http/http_response.hpp
+index cd00ec8..ffd6dda 100644
+--- a/http/http_response.hpp
++++ b/http/http_response.hpp
+@@ -13,10 +13,15 @@ namespace crow
+ template <typename Adaptor, typename Handler>
+ class Connection;
+
++template <typename Adaptor>
++class SseConnectionImpl;
++
+ struct Response
+ {
+ template <typename Adaptor, typename Handler>
+ friend class crow::Connection;
++ template <typename Adaptor>
++ friend class crow::SseConnectionImpl;
+ using response_type =
+ boost::beast::http::response<boost::beast::http::string_body>;
+
+@@ -136,8 +141,8 @@ struct Response
+
+ private:
+ bool completed{};
+- std::function<void()> completeRequestHandler;
+ std::function<bool()> isAliveHelper;
++ std::function<void()> completeRequestHandler;
+
+ // In case of a JSON object, set the Content-Type header
+ void jsonMode()
+diff --git a/http/routing.hpp b/http/routing.hpp
+index 65c7b70..0824939 100644
+--- a/http/routing.hpp
++++ b/http/routing.hpp
+@@ -6,6 +6,7 @@
+ #include "http_response.hpp"
+ #include "logging.hpp"
+ #include "privileges.hpp"
++#include "server_sent_event.hpp"
+ #include "sessions.hpp"
+ #include "utility.hpp"
+ #include "websocket.hpp"
+@@ -390,6 +391,67 @@ class WebSocketRule : public BaseRule
+ std::function<void(crow::websocket::Connection&)> errorHandler;
+ };
+
++class SseSocketRule : public BaseRule
++{
++ using self_t = SseSocketRule;
++
++ public:
++ SseSocketRule(const std::string& ruleIn) : BaseRule(ruleIn)
++ {}
++
++ void validate() override
++ {}
++
++ void handle(const Request&, Response& res, const RoutingParams&) override
++ {
++ res.result(boost::beast::http::status::not_found);
++ res.end();
++ }
++
++ void handleUpgrade(const Request& req, Response&,
++ boost::asio::ip::tcp::socket&& adaptor) override
++ {
++ std::shared_ptr<crow::SseConnectionImpl<boost::asio::ip::tcp::socket>>
++ myConnection = std::make_shared<
++ crow::SseConnectionImpl<boost::asio::ip::tcp::socket>>(
++ req, std::move(adaptor), openHandler, closeHandler);
++ myConnection->start();
++ }
++#ifdef BMCWEB_ENABLE_SSL
++ void handleUpgrade(const Request& req, Response&,
++ boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&&
++ adaptor) override
++ {
++ std::shared_ptr<crow::SseConnectionImpl<
++ boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>>
++ myConnection = std::make_shared<crow::SseConnectionImpl<
++ boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>>(
++ req, std::move(adaptor), openHandler, closeHandler);
++ myConnection->start();
++ }
++#endif
++
++ template <typename Func>
++ self_t& onopen(Func f)
++ {
++ openHandler = f;
++ return *this;
++ }
++
++ template <typename Func>
++ self_t& onclose(Func f)
++ {
++ closeHandler = f;
++ return *this;
++ }
++
++ private:
++ std::function<void(std::shared_ptr<crow::SseConnection>&,
++ const crow::Request&, crow::Response&)>
++ openHandler;
++ std::function<void(std::shared_ptr<crow::SseConnection>&)> closeHandler;
++};
++
+ template <typename T>
+ struct RuleParameterTraits
+ {
+@@ -402,6 +464,14 @@ struct RuleParameterTraits
+ return *p;
+ }
+
++ SseSocketRule& serverSentEvent()
++ {
++ self_t* self = static_cast<self_t*>(this);
++ SseSocketRule* p = new SseSocketRule(self->rule);
++ self->ruleToUpgrade.reset(p);
++ return *p;
++ }
++
+ self_t& name(const std::string_view name) noexcept
+ {
+ self_t* self = static_cast<self_t*>(this);
+diff --git a/http/server_sent_event.hpp b/http/server_sent_event.hpp
+new file mode 100644
+index 0000000..41d18ed
+--- /dev/null
++++ b/http/server_sent_event.hpp
+@@ -0,0 +1,279 @@
++#pragma once
++#include "http_request.hpp"
++
++#include <boost/algorithm/string/predicate.hpp>
++#include <boost/asio/buffer.hpp>
++#include <boost/beast/http/buffer_body.hpp>
++#include <boost/beast/websocket.hpp>
++
++#include <array>
++#include <functional>
++
++#ifdef BMCWEB_ENABLE_SSL
++#include <boost/beast/websocket/ssl.hpp>
++#endif
++
++namespace crow
++{
++
++struct SseConnection : std::enable_shared_from_this<SseConnection>
++{
++ public:
++ SseConnection(const crow::Request& reqIn) : req(reqIn)
++ {}
++ virtual ~SseConnection() = default;
++
++ virtual boost::asio::io_context& getIoContext() = 0;
++ virtual void sendSSEHeader() = 0;
++ virtual void completeRequest() = 0;
++ virtual void close(const std::string_view msg = "quit") = 0;
++ virtual void sendEvent(const std::string_view id,
++ const std::string_view msg) = 0;
++
++ crow::Request req;
++ crow::Response res;
++};
++
++template <typename Adaptor>
++class SseConnectionImpl : public SseConnection
++{
++ public:
++ SseConnectionImpl(
++ const crow::Request& reqIn, Adaptor adaptorIn,
++ std::function<void(std::shared_ptr<SseConnection>&,
++ const crow::Request&, crow::Response&)>
++ openHandler,
++ std::function<void(std::shared_ptr<SseConnection>&)> closeHandler) :
++ SseConnection(reqIn),
++ adaptor(std::move(adaptorIn)), openHandler(std::move(openHandler)),
++ closeHandler(std::move(closeHandler))
++ {
++ BMCWEB_LOG_DEBUG << "SseConnectionImpl: SSE constructor " << this;
++ }
++
++ ~SseConnectionImpl() override
++ {
++ res.completeRequestHandler = nullptr;
++ BMCWEB_LOG_DEBUG << "SseConnectionImpl: SSE destructor " << this;
++ }
++
++ boost::asio::io_context& getIoContext() override
++ {
++ return static_cast<boost::asio::io_context&>(
++ adaptor.get_executor().context());
++ }
++
++ void start()
++ {
++ // Register for completion callback.
++ res.completeRequestHandler = [this, self(shared_from_this())] {
++ boost::asio::post(this->adaptor.get_executor(),
++ [self] { self->completeRequest(); });
++ };
++
++ if (openHandler)
++ {
++ std::shared_ptr<SseConnection> self = this->shared_from_this();
++ openHandler(self, req, res);
++ }
++ }
++
++ void close(const std::string_view msg) override
++ {
++ BMCWEB_LOG_DEBUG << "Closing SSE connection " << this << " - " << msg;
++ boost::beast::get_lowest_layer(adaptor).close();
++
++ // send notification to handler for cleanup
++ if (closeHandler)
++ {
++ std::shared_ptr<SseConnection> self = this->shared_from_this();
++ closeHandler(self);
++ }
++ }
++
++ void sendSSEHeader() override
++ {
++ BMCWEB_LOG_DEBUG << "Starting SSE connection";
++ using BodyType = boost::beast::http::buffer_body;
++ auto response =
++ std::make_shared<boost::beast::http::response<BodyType>>(
++ boost::beast::http::status::ok, 11);
++ auto serializer =
++ std::make_shared<boost::beast::http::response_serializer<BodyType>>(
++ *response);
++
++ response->set(boost::beast::http::field::server, "bmcweb");
++ response->set(boost::beast::http::field::content_type,
++ "text/event-stream");
++ response->body().data = nullptr;
++ response->body().size = 0;
++ response->body().more = true;
++
++ boost::beast::http::async_write_header(
++ adaptor, *serializer,
++ [this, self(shared_from_this()), response, serializer](
++ const boost::beast::error_code& ec, const std::size_t&) {
++ if (ec)
++ {
++ BMCWEB_LOG_ERROR << "Error sending header" << ec;
++ close("async_write_header failed");
++ return;
++ }
++ BMCWEB_LOG_DEBUG << "SSE header sent - Connection established";
++
++ // SSE stream header sent, So lets setup monitor.
++ // Any read data on this stream will be error in case of SSE.
++ setupRead();
++ });
++ }
++
++ void setupRead()
++ {
++ adaptor.async_read_some(
++ outputBuffer.prepare(outputBuffer.capacity() - outputBuffer.size()),
++ [this](const boost::system::error_code& ec, std::size_t bytesRead) {
++ BMCWEB_LOG_DEBUG << "async_read_some: Read " << bytesRead
++ << " bytes";
++ if (ec)
++ {
++ BMCWEB_LOG_ERROR << "Read error: " << ec;
++ }
++ outputBuffer.commit(bytesRead);
++ outputBuffer.consume(bytesRead);
++
++ // After establishing SSE stream, Reading data on this
++ // stream means client is disobeys the SSE protocol.
++ // Read the data to avoid buffer attacks and close connection.
++ close("Close SSE connection");
++ return;
++ });
++ }
++
++ void doWrite()
++ {
++ if (doingWrite)
++ {
++ return;
++ }
++ if (inputBuffer.size() == 0)
++ {
++ BMCWEB_LOG_DEBUG << "inputBuffer is empty... Bailing out";
++ return;
++ }
++ doingWrite = true;
++
++ adaptor.async_write_some(
++ inputBuffer.data(), [this, self(shared_from_this())](
++ boost::beast::error_code ec,
++ const std::size_t& bytesTransferred) {
++ doingWrite = false;
++ inputBuffer.consume(bytesTransferred);
++
++ if (ec == boost::asio::error::eof)
++ {
++ BMCWEB_LOG_ERROR << "async_write_some() SSE stream closed";
++ close("SSE stream closed");
++ return;
++ }
++
++ if (ec)
++ {
++ BMCWEB_LOG_ERROR << "async_write_some() failed: "
++ << ec.message();
++ close("async_write_some failed");
++ return;
++ }
++ BMCWEB_LOG_DEBUG << "async_write_some() bytes transferred: "
++ << bytesTransferred;
++
++ doWrite();
++ });
++ }
++
++ void completeRequest() override
++ {
++ BMCWEB_LOG_DEBUG << "SSE completeRequest() handler";
++ if (res.body().empty() && !res.jsonValue.empty())
++ {
++ res.addHeader("Content-Type", "application/json");
++ res.body() = res.jsonValue.dump(
++ 2, ' ', true, nlohmann::json::error_handler_t::replace);
++ }
++
++ res.preparePayload();
++ auto serializer =
++ std::make_shared<boost::beast::http::response_serializer<
++ boost::beast::http::string_body>>(*res.stringResponse);
++
++ boost::beast::http::async_write(
++ adaptor, *serializer,
++ [this, self(shared_from_this()),
++ serializer](const boost::system::error_code& ec,
++ std::size_t bytesTransferred) {
++ BMCWEB_LOG_DEBUG << this << " async_write " << bytesTransferred
++ << " bytes";
++ if (ec)
++ {
++ BMCWEB_LOG_DEBUG << this << " from async_write failed";
++ return;
++ }
++ res.clear();
++
++ BMCWEB_LOG_DEBUG << this
++ << " Closing SSE connection - Request invalid";
++ close("Request invalid");
++ });
++
++ // delete lambda with self shared_ptr
++ // to enable connection destruction
++ res.completeRequestHandler = nullptr;
++ }
++
++ void sendEvent(const std::string_view id,
++ const std::string_view msg) override
++ {
++ if (msg.empty())
++ {
++ BMCWEB_LOG_DEBUG << "Empty data, bailing out.";
++ return;
++ }
++
++ std::string rawData;
++ if (!id.empty())
++ {
++ rawData += "id: ";
++ rawData.append(id.begin(), id.end());
++ rawData += "\n";
++ }
++
++ rawData += "data: ";
++ for (char character : msg)
++ {
++ rawData += character;
++ if (character == '\n')
++ {
++ rawData += "data: ";
++ }
++ }
++ rawData += "\n\n";
++
++ boost::asio::buffer_copy(inputBuffer.prepare(rawData.size()),
++ boost::asio::buffer(rawData));
++ inputBuffer.commit(rawData.size());
++
++ doWrite();
++ }
++
++ private:
++ Adaptor adaptor;
++
++ boost::beast::flat_static_buffer<1024U * 8U> outputBuffer;
++ boost::beast::flat_static_buffer<1024U * 64U> inputBuffer;
++ bool doingWrite = false;
++
++ std::function<void(std::shared_ptr<SseConnection>&, const crow::Request&,
++ crow::Response&)>
++ openHandler;
++ std::function<void(std::shared_ptr<SseConnection>&)> closeHandler;
++};
++} // namespace crow
+--
+2.17.1
+
diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0005-Add-SSE-style-subscription-support-to-eventservice.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0005-Add-SSE-style-subscription-support-to-eventservice.patch
new file mode 100644
index 000000000..e2eaf1761
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0005-Add-SSE-style-subscription-support-to-eventservice.patch
@@ -0,0 +1,627 @@
+From dbaf8c1dc402da97fec723d050e063cd283d8736 Mon Sep 17 00:00:00 2001
+From: AppaRao Puli <apparao.puli@linux.intel.com>
+Date: Tue, 16 Mar 2021 15:37:24 +0000
+Subject: [PATCH 5/6] Add SSE style subscription support to eventservice
+
+This commit adds the SSE style eventservice subscription
+style event. Using this, end user can subscribe for
+Redfish event logs using GET on SSE usri from
+browser.
+URI: /redfish/v1/EventService/Subscriptions/SSE
+
+Tested:
+ - From Browser did GET on above SSE URI and
+ generated some Redfish event logs(power cycle)
+ and saw redfish event logs streaming on browser.
+ - After SSE registration, Check Subscription collections
+ and GET on individual subscription and saw desired
+ response.
+ - Ran RedfishValidation and its passed.
+
+Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com>
+Change-Id: I7f4b7a34974080739c4ba968ed570489af0474de
+---
+ http/http_connection.hpp | 2 +-
+ include/eventservice_sse.hpp | 75 +++++
+ .../include/event_service_manager.hpp | 77 ++++-
+ redfish-core/include/server_sent_events.hpp | 291 ------------------
+ redfish-core/lib/event_service.hpp | 4 +-
+ src/webserver_main.cpp | 2 +
+ 6 files changed, 149 insertions(+), 302 deletions(-)
+ create mode 100644 include/eventservice_sse.hpp
+ delete mode 100644 redfish-core/include/server_sent_events.hpp
+
+diff --git a/http/http_connection.hpp b/http/http_connection.hpp
+index 2c8bf40..1ab776c 100644
+--- a/http/http_connection.hpp
++++ b/http/http_connection.hpp
+@@ -350,7 +350,7 @@ class Connection :
+ boost::iequals(req->getHeaderValue(
+ boost::beast::http::field::upgrade),
+ "websocket")) ||
+- (req->url == "/sse"))
++ (req->url == "/redfish/v1/EventService/Subscriptions/SSE"))
+ {
+ BMCWEB_LOG_DEBUG << "Request: " << this
+ << " is getting upgraded";
+diff --git a/include/eventservice_sse.hpp b/include/eventservice_sse.hpp
+new file mode 100644
+index 0000000..6c98e6e
+--- /dev/null
++++ b/include/eventservice_sse.hpp
+@@ -0,0 +1,75 @@
++#pragma once
++
++#include <app.hpp>
++#include <event_service_manager.hpp>
++
++namespace redfish
++{
++namespace eventservice_sse
++{
++
++static bool createSubscription(std::shared_ptr<crow::SseConnection>& conn,
++ const crow::Request& req, crow::Response& res)
++{
++ if ((EventServiceManager::getInstance().getNumberOfSubscriptions() >=
++ maxNoOfSubscriptions) ||
++ EventServiceManager::getInstance().getNumberOfSSESubscriptions() >=
++ maxNoOfSSESubscriptions)
++ {
++ BMCWEB_LOG_ERROR << "Max SSE subscriptions reached";
++ messages::eventSubscriptionLimitExceeded(res);
++ res.end();
++ return false;
++ }
++ BMCWEB_LOG_DEBUG << "Request query param size: " << req.urlParams.size();
++
++ std::shared_ptr<redfish::Subscription> subValue =
++ std::make_shared<redfish::Subscription>(std::move(conn));
++
++ // GET on this URI means, Its SSE subscriptionType.
++ subValue->subscriptionType = redfish::subscriptionTypeSSE;
++
++ // TODO: parse $filter query params and fill config.
++ subValue->protocol = "Redfish";
++ subValue->retryPolicy = "TerminateAfterRetries";
++ subValue->eventFormatType = "Event";
++
++ std::string id =
++ redfish::EventServiceManager::getInstance().addSubscription(subValue,
++ false);
++ if (id.empty())
++ {
++ messages::internalError(res);
++ res.end();
++ return false;
++ }
++
++ return true;
++}
++
++static void deleteSubscription(std::shared_ptr<crow::SseConnection>& conn)
++{
++ redfish::EventServiceManager::getInstance().deleteSubscription(conn);
++}
++
++inline void requestRoutes(App& app)
++{
++ BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/SSE")
++ .privileges({"ConfigureComponents", "ConfigureManager"})
++ .serverSentEvent()
++ .onopen([](std::shared_ptr<crow::SseConnection>& conn,
++ const crow::Request& req, crow::Response& res) {
++ BMCWEB_LOG_DEBUG << "Connection " << conn << " opened.";
++ if (createSubscription(conn, req, res))
++ {
++ // All success, lets send SSE haader
++ conn->sendSSEHeader();
++ }
++ })
++ .onclose([](std::shared_ptr<crow::SseConnection>& conn) {
++ BMCWEB_LOG_DEBUG << "Connection " << conn << " closed";
++ deleteSubscription(conn);
++ });
++}
++} // namespace eventservice_sse
++} // namespace redfish
+diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp
+index 148c703..e3eba86 100644
+--- a/redfish-core/include/event_service_manager.hpp
++++ b/redfish-core/include/event_service_manager.hpp
+@@ -22,13 +22,15 @@
+ #include <sys/inotify.h>
+
+ #include <boost/asio/io_context.hpp>
++#include <boost/beast/core/span.hpp>
+ #include <boost/container/flat_map.hpp>
+ #include <error_messages.hpp>
+ #include <http_client.hpp>
+ #include <random.hpp>
+-#include <server_sent_events.hpp>
++#include <server_sent_event.hpp>
+ #include <utils/json_utils.hpp>
+
++#include <algorithm>
+ #include <cstdlib>
+ #include <ctime>
+ #include <fstream>
+@@ -45,9 +47,13 @@ using EventServiceConfig = std::tuple<bool, uint32_t, uint32_t>;
+ static constexpr const char* eventFormatType = "Event";
+ static constexpr const char* metricReportFormatType = "MetricReport";
+
++static constexpr const char* subscriptionTypeSSE = "SSE";
+ static constexpr const char* eventServiceFile =
+ "/var/lib/bmcweb/eventservice_config.json";
+
++static constexpr const uint8_t maxNoOfSubscriptions = 20;
++static constexpr const uint8_t maxNoOfSSESubscriptions = 10;
++
+ #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
+ static std::optional<boost::asio::posix::stream_descriptor> inotifyConn;
+ static constexpr const char* redfishEventLogDir = "/var/log";
+@@ -390,11 +396,9 @@ class Subscription
+ path);
+ }
+
+- Subscription(const std::shared_ptr<boost::beast::tcp_stream>& adaptor) :
+- eventSeqNum(1)
+- {
+- sseConn = std::make_shared<crow::ServerSentEvents>(adaptor);
+- }
++ Subscription(const std::shared_ptr<crow::SseConnection>& adaptor) :
++ sseConn(adaptor), eventSeqNum(1)
++ {}
+
+ ~Subscription() = default;
+
+@@ -419,7 +423,7 @@ class Subscription
+
+ if (sseConn != nullptr)
+ {
+- sseConn->sendData(eventSeqNum, msg);
++ sseConn->sendEvent(std::to_string(eventSeqNum), msg);
+ }
+ }
+
+@@ -509,6 +513,7 @@ class Subscription
+
+ this->sendEvent(
+ msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace));
++ this->eventSeqNum++;
+ }
+ #endif
+
+@@ -579,14 +584,39 @@ class Subscription
+ return eventSeqNum;
+ }
+
++ void setSubscriptionId(const std::string& id)
++ {
++ BMCWEB_LOG_DEBUG << "Subscription ID: " << id;
++ subId = id;
++ }
++
++ std::string getSubscriptionId()
++ {
++ return subId;
++ }
++
++ std::optional<std::string>
++ getSubscriptionId(const std::shared_ptr<crow::SseConnection>& connPtr)
++ {
++ if (sseConn != nullptr && connPtr == sseConn)
++ {
++ BMCWEB_LOG_DEBUG << __FUNCTION__
++ << " conn matched, subId: " << subId;
++ return subId;
++ }
++
++ return std::nullopt;
++ }
++
+ private:
++ std::shared_ptr<crow::SseConnection> sseConn = nullptr;
+ uint64_t eventSeqNum;
+ std::string host;
+ std::string port;
+ std::string path;
+ std::string uriProto;
+ std::shared_ptr<crow::HttpClient> conn = nullptr;
+- std::shared_ptr<crow::ServerSentEvents> sseConn = nullptr;
++ std::string subId;
+ };
+
+ static constexpr const bool defaultEnabledState = true;
+@@ -977,6 +1007,8 @@ class EventServiceManager
+ subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval);
+ subValue->updateRetryPolicy();
+
++ // Set Subscription ID for back trace
++ subValue->setSubscriptionId(id);
+ return id;
+ }
+
+@@ -1001,11 +1033,40 @@ class EventServiceManager
+ }
+ }
+
++ void deleteSubscription(const std::shared_ptr<crow::SseConnection>& connPtr)
++ {
++ for (const auto& it : this->subscriptionsMap)
++ {
++ std::shared_ptr<Subscription> entry = it.second;
++ if (entry->subscriptionType == subscriptionTypeSSE)
++ {
++ std::optional<std::string> id =
++ entry->getSubscriptionId(connPtr);
++ if (id)
++ {
++ deleteSubscription(*id);
++ return;
++ }
++ }
++ }
++ }
++
+ size_t getNumberOfSubscriptions()
+ {
+ return subscriptionsMap.size();
+ }
+
++ size_t getNumberOfSSESubscriptions() const
++ {
++ auto count = std::count_if(
++ subscriptionsMap.begin(), subscriptionsMap.end(),
++ [this](const std::pair<std::string, std::shared_ptr<Subscription>>&
++ entry) {
++ return (entry.second->subscriptionType == subscriptionTypeSSE);
++ });
++ return static_cast<size_t>(count);
++ }
++
+ std::vector<std::string> getAllIDs()
+ {
+ std::vector<std::string> idList;
+diff --git a/redfish-core/include/server_sent_events.hpp b/redfish-core/include/server_sent_events.hpp
+deleted file mode 100644
+index 578fa19..0000000
+--- a/redfish-core/include/server_sent_events.hpp
++++ /dev/null
+@@ -1,291 +0,0 @@
+-
+-/*
+-// Copyright (c) 2020 Intel Corporation
+-//
+-// Licensed under the Apache License, Version 2.0 (the "License");
+-// you may not use this file except in compliance with the License.
+-// You may obtain a copy of the License at
+-//
+-// http://www.apache.org/licenses/LICENSE-2.0
+-//
+-// Unless required by applicable law or agreed to in writing, software
+-// distributed under the License is distributed on an "AS IS" BASIS,
+-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-// See the License for the specific language governing permissions and
+-// limitations under the License.
+-*/
+-#pragma once
+-#include "node.hpp"
+-
+-#include <boost/asio/strand.hpp>
+-#include <boost/beast/core/span.hpp>
+-#include <boost/beast/http/buffer_body.hpp>
+-#include <boost/beast/http/message.hpp>
+-#include <boost/beast/version.hpp>
+-
+-#include <cstdlib>
+-#include <functional>
+-#include <iostream>
+-#include <memory>
+-#include <queue>
+-#include <string>
+-
+-namespace crow
+-{
+-
+-static constexpr uint8_t maxReqQueueSize = 50;
+-
+-enum class SseConnState
+-{
+- startInit,
+- initInProgress,
+- initialized,
+- initFailed,
+- sendInProgress,
+- sendFailed,
+- idle,
+- suspended,
+- closed
+-};
+-
+-class ServerSentEvents : public std::enable_shared_from_this<ServerSentEvents>
+-{
+- private:
+- std::shared_ptr<boost::beast::tcp_stream> sseConn;
+- std::queue<std::pair<uint64_t, std::string>> requestDataQueue;
+- std::string outBuffer;
+- SseConnState state;
+- int retryCount;
+- int maxRetryAttempts;
+-
+- void sendEvent(const std::string& id, const std::string& msg)
+- {
+- if (msg.empty())
+- {
+- BMCWEB_LOG_DEBUG << "Empty data, bailing out.";
+- return;
+- }
+-
+- if (state == SseConnState::sendInProgress)
+- {
+- return;
+- }
+- state = SseConnState::sendInProgress;
+-
+- if (!id.empty())
+- {
+- outBuffer += "id: ";
+- outBuffer.append(id.begin(), id.end());
+- outBuffer += "\n";
+- }
+-
+- outBuffer += "data: ";
+- for (char character : msg)
+- {
+- outBuffer += character;
+- if (character == '\n')
+- {
+- outBuffer += "data: ";
+- }
+- }
+- outBuffer += "\n\n";
+-
+- doWrite();
+- }
+-
+- void doWrite()
+- {
+- if (outBuffer.empty())
+- {
+- BMCWEB_LOG_DEBUG << "All data sent successfully.";
+- // Send is successful, Lets remove data from queue
+- // check for next request data in queue.
+- requestDataQueue.pop();
+- state = SseConnState::idle;
+- checkQueue();
+- return;
+- }
+-
+- sseConn->async_write_some(
+- boost::asio::buffer(outBuffer.data(), outBuffer.size()),
+- [self(shared_from_this())](
+- boost::beast::error_code ec,
+- [[maybe_unused]] const std::size_t& bytesTransferred) {
+- self->outBuffer.erase(0, bytesTransferred);
+-
+- if (ec == boost::asio::error::eof)
+- {
+- // Send is successful, Lets remove data from queue
+- // check for next request data in queue.
+- self->requestDataQueue.pop();
+- self->state = SseConnState::idle;
+- self->checkQueue();
+- return;
+- }
+-
+- if (ec)
+- {
+- BMCWEB_LOG_ERROR << "async_write_some() failed: "
+- << ec.message();
+- self->state = SseConnState::sendFailed;
+- self->checkQueue();
+- return;
+- }
+- BMCWEB_LOG_DEBUG << "async_write_some() bytes transferred: "
+- << bytesTransferred;
+-
+- self->doWrite();
+- });
+- }
+-
+- void startSSE()
+- {
+- if (state == SseConnState::initInProgress)
+- {
+- return;
+- }
+- state = SseConnState::initInProgress;
+-
+- BMCWEB_LOG_DEBUG << "starting SSE connection ";
+- using BodyType = boost::beast::http::buffer_body;
+- auto response =
+- std::make_shared<boost::beast::http::response<BodyType>>(
+- boost::beast::http::status::ok, 11);
+- auto serializer =
+- std::make_shared<boost::beast::http::response_serializer<BodyType>>(
+- *response);
+-
+- // TODO: Add hostname in http header.
+- response->set(boost::beast::http::field::server, "iBMC");
+- response->set(boost::beast::http::field::content_type,
+- "text/event-stream");
+- response->body().data = nullptr;
+- response->body().size = 0;
+- response->body().more = true;
+-
+- boost::beast::http::async_write_header(
+- *sseConn, *serializer,
+- [this, response,
+- serializer](const boost::beast::error_code& ec,
+- [[maybe_unused]] const std::size_t& bytesTransferred) {
+- if (ec)
+- {
+- BMCWEB_LOG_ERROR << "Error sending header" << ec;
+- state = SseConnState::initFailed;
+- checkQueue();
+- return;
+- }
+-
+- BMCWEB_LOG_DEBUG << "startSSE Header sent.";
+- state = SseConnState::initialized;
+- checkQueue();
+- });
+- }
+-
+- void checkQueue(const bool newRecord = false)
+- {
+- if (requestDataQueue.empty())
+- {
+- BMCWEB_LOG_DEBUG << "requestDataQueue is empty\n";
+- return;
+- }
+-
+- if (retryCount >= maxRetryAttempts)
+- {
+- BMCWEB_LOG_ERROR << "Maximum number of retries is reached.";
+-
+- // Clear queue.
+- while (!requestDataQueue.empty())
+- {
+- requestDataQueue.pop();
+- }
+-
+- // TODO: Take 'DeliveryRetryPolicy' action.
+- // For now, doing 'SuspendRetries' action.
+- state = SseConnState::suspended;
+- return;
+- }
+-
+- if ((state == SseConnState::initFailed) ||
+- (state == SseConnState::sendFailed))
+- {
+- if (newRecord)
+- {
+- // We are already running async wait and retry.
+- // Since record is added to queue, it gets the
+- // turn in FIFO.
+- return;
+- }
+-
+- retryCount++;
+- // TODO: Perform async wait for retryTimeoutInterval before proceed.
+- }
+- else
+- {
+- // reset retry count.
+- retryCount = 0;
+- }
+-
+- switch (state)
+- {
+- case SseConnState::initInProgress:
+- case SseConnState::sendInProgress:
+- case SseConnState::suspended:
+- case SseConnState::startInit:
+- case SseConnState::closed:
+- // do nothing
+- break;
+- case SseConnState::initFailed:
+- {
+- startSSE();
+- break;
+- }
+- case SseConnState::initialized:
+- case SseConnState::idle:
+- case SseConnState::sendFailed:
+- {
+- std::pair<uint64_t, std::string> reqData =
+- requestDataQueue.front();
+- sendEvent(std::to_string(reqData.first), reqData.second);
+- break;
+- }
+- }
+-
+- return;
+- }
+-
+- public:
+- ServerSentEvents(const ServerSentEvents&) = delete;
+- ServerSentEvents& operator=(const ServerSentEvents&) = delete;
+- ServerSentEvents(ServerSentEvents&&) = delete;
+- ServerSentEvents& operator=(ServerSentEvents&&) = delete;
+-
+- ServerSentEvents(const std::shared_ptr<boost::beast::tcp_stream>& adaptor) :
+- sseConn(adaptor), state(SseConnState::startInit), retryCount(0),
+- maxRetryAttempts(5)
+- {
+- startSSE();
+- }
+-
+- ~ServerSentEvents() = default;
+-
+- void sendData(const uint64_t& id, const std::string& data)
+- {
+- if (state == SseConnState::suspended)
+- {
+- return;
+- }
+-
+- if (requestDataQueue.size() <= maxReqQueueSize)
+- {
+- requestDataQueue.push(std::pair(id, data));
+- checkQueue(true);
+- }
+- else
+- {
+- BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data.";
+- }
+- }
+-};
+-
+-} // namespace crow
+diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp
+index be6f04d..1875ec9 100644
+--- a/redfish-core/lib/event_service.hpp
++++ b/redfish-core/lib/event_service.hpp
+@@ -34,8 +34,6 @@ static constexpr const std::array<const char*, 1> supportedResourceTypes = {
+ "Task"};
+ #endif
+
+-static constexpr const uint8_t maxNoOfSubscriptions = 20;
+-
+ class EventService : public Node
+ {
+ public:
+@@ -59,6 +57,8 @@ class EventService : public Node
+ {"@odata.type", "#EventService.v1_5_0.EventService"},
+ {"Id", "EventService"},
+ {"Name", "Event Service"},
++ {"ServerSentEventUri",
++ "/redfish/v1/EventService/Subscriptions/SSE"},
+ {"Subscriptions",
+ {{"@odata.id", "/redfish/v1/EventService/Subscriptions"}}},
+ {"Actions",
+diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp
+index 3b9631a..bf8e705 100644
+--- a/src/webserver_main.cpp
++++ b/src/webserver_main.cpp
+@@ -5,6 +5,7 @@
+ #include <cors_preflight.hpp>
+ #include <dbus_monitor.hpp>
+ #include <dbus_singleton.hpp>
++#include <eventservice_sse.hpp>
+ #include <hostname_monitor.hpp>
+ #include <ibm/management_console_rest.hpp>
+ #include <image_upload.hpp>
+@@ -79,6 +80,7 @@ int main(int /*argc*/, char** /*argv*/)
+
+ #ifdef BMCWEB_ENABLE_REDFISH
+ crow::redfish::requestRoutes(app);
++ redfish::eventservice_sse::requestRoutes(app);
+ redfish::RedfishService redfish(app);
+
+ // Create EventServiceManager instance and initialize Config
+--
+2.17.1
+
diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0006-Add-EventService-SSE-filter-support.patch b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0006-Add-EventService-SSE-filter-support.patch
new file mode 100644
index 000000000..e83e05c96
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/0006-Add-EventService-SSE-filter-support.patch
@@ -0,0 +1,243 @@
+From 693de7b901e7618585e0e8d848fc37fa0cccd18e Mon Sep 17 00:00:00 2001
+From: AppaRao Puli <apparao.puli@linux.intel.com>
+Date: Wed, 17 Mar 2021 01:16:50 +0000
+Subject: [PATCH 6/6] Add EventService SSE filter support
+
+This commit implements the Event Service SSE stream
+filters support. As per redfish specification:
+The SSE streams have these formats:
+ - Metric report SSE stream
+ - Event message SSE stream
+
+To reduce the amount of data, service supports $filter
+query parameter in SSE URI.
+Below properties support as filter criteria:
+ - EventFormatType( Event & MetricReport)
+ - MessageId
+ - RegistryPrefix
+ - MetricReportDefinition
+
+For more details, refer Redfish specification section 13.5.2
+
+Tested:
+ Created SSE stream with different filters and observed
+ desired events on SSE stream client(browser), some examples
+ - To get all Redfish events,
+ URI: /redfish/v1/EventService/Subscriptions/SSE?$filter=(EventFormatType%20eq%20Event)
+ - To get Redfish events with RegistryPrefix "OpenBMC"
+ URi: /redfish/v1/EventService/Subscriptions/SSE?$filter=(RegistryPrefix%20eq%20OpenBMC)
+ - To get only DC power of Events,
+ URI: /redfish/v1/EventService/Subscriptions/SSE?$filter=(EventFormatType%20eq%20Event)%20and%20(MessageId%20eq%20DCPowerOff)
+
+Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com>
+Change-Id: I55c6f53bb5e57aa1f2d1601f1a16525a33b13bd2
+---
+ include/eventservice_sse.hpp | 94 ++++++++++++++++++-
+ redfish-core/include/error_messages.hpp | 9 ++
+ .../include/event_service_manager.hpp | 5 +
+ redfish-core/lib/event_service.hpp | 4 -
+ redfish-core/src/error_messages.cpp | 26 +++++
+ 5 files changed, 130 insertions(+), 8 deletions(-)
+
+diff --git a/include/eventservice_sse.hpp b/include/eventservice_sse.hpp
+index 6c98e6e..ff72c4d 100644
+--- a/include/eventservice_sse.hpp
++++ b/include/eventservice_sse.hpp
+@@ -23,16 +23,102 @@ static bool createSubscription(std::shared_ptr<crow::SseConnection>& conn,
+ }
+ BMCWEB_LOG_DEBUG << "Request query param size: " << req.urlParams.size();
+
++ // EventService SSE supports only "$filter" query param.
++ if (req.urlParams.size() > 1)
++ {
++ messages::invalidQueryFilter(res);
++ res.end();
++ return false;
++ }
++ std::string eventFormatType;
++ std::string queryFilters;
++ if (req.urlParams.size())
++ {
++ boost::urls::url_view::params_type::iterator it =
++ req.urlParams.find("$filter");
++ if (it == req.urlParams.end())
++ {
++ messages::invalidQueryFilter(res);
++ res.end();
++ return false;
++ }
++ queryFilters = it->value();
++ }
++ else
++ {
++ eventFormatType = "Event";
++ }
++
++ std::vector<std::string> msgIds;
++ std::vector<std::string> regPrefixes;
++ std::vector<std::string> mrdsArray;
++ if (!queryFilters.empty())
++ {
++ // Reading from query params.
++ bool status = readSSEQueryParams(queryFilters, eventFormatType, msgIds,
++ regPrefixes, mrdsArray);
++ if (!status)
++ {
++ messages::invalidObject(res, queryFilters);
++ res.end();
++ return false;
++ }
++
++ // RegsitryPrefix and messageIds are mutuly exclusive as per redfish
++ // specification.
++ if (regPrefixes.size() && msgIds.size())
++ {
++ messages::mutualExclusiveProperties(res, "RegistryPrefix",
++ "MessageId");
++ res.end();
++ return false;
++ }
++
++ if (!eventFormatType.empty())
++ {
++ if (std::find(supportedEvtFormatTypes.begin(),
++ supportedEvtFormatTypes.end(),
++ eventFormatType) == supportedEvtFormatTypes.end())
++ {
++ messages::propertyValueNotInList(res, eventFormatType,
++ "EventFormatType");
++ res.end();
++ return false;
++ }
++ }
++ else
++ {
++ // If nothing specified, using default "Event"
++ eventFormatType = "Event";
++ }
++
++ if (!regPrefixes.empty())
++ {
++ for (const std::string& it : regPrefixes)
++ {
++ if (std::find(supportedRegPrefixes.begin(),
++ supportedRegPrefixes.end(),
++ it) == supportedRegPrefixes.end())
++ {
++ messages::propertyValueNotInList(res, it, "RegistryPrefix");
++ res.end();
++ return false;
++ }
++ }
++ }
++ }
++
+ std::shared_ptr<redfish::Subscription> subValue =
+ std::make_shared<redfish::Subscription>(std::move(conn));
+
+ // GET on this URI means, Its SSE subscriptionType.
+- subValue->subscriptionType = redfish::subscriptionTypeSSE;
+-
+- // TODO: parse $filter query params and fill config.
++ subValue->subscriptionType = subscriptionTypeSSE;
+ subValue->protocol = "Redfish";
+ subValue->retryPolicy = "TerminateAfterRetries";
+- subValue->eventFormatType = "Event";
++ subValue->eventFormatType = eventFormatType;
++ subValue->registryMsgIds = msgIds;
++ subValue->registryPrefixes = regPrefixes;
++ subValue->metricReportDefinitions = mrdsArray;
+
+ std::string id =
+ redfish::EventServiceManager::getInstance().addSubscription(subValue,
+diff --git a/redfish-core/include/error_messages.hpp b/redfish-core/include/error_messages.hpp
+index 7dfdc80..922dae9 100644
+--- a/redfish-core/include/error_messages.hpp
++++ b/redfish-core/include/error_messages.hpp
+@@ -959,6 +959,15 @@ nlohmann::json mutualExclusiveProperties(const std::string& arg1,
+ void mutualExclusiveProperties(crow::Response& res, const std::string& arg1,
+ const std::string& arg2);
+
++/**
++ * @brief Formats InvalidQueryFilter message into JSON
++ * Message body: "The requested URL contains the invalid query filters"
++ *
++ * @returns Message InvalidQueryFilter formatted to JSON */
++nlohmann::json invalidQueryFilter();
++
++void invalidQueryFilter(crow::Response& res);
++
+ } // namespace messages
+
+ } // namespace redfish
+diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp
+index e3eba86..8f120b1 100644
+--- a/redfish-core/include/event_service_manager.hpp
++++ b/redfish-core/include/event_service_manager.hpp
+@@ -54,6 +54,11 @@ static constexpr const char* eventServiceFile =
+ static constexpr const uint8_t maxNoOfSubscriptions = 20;
+ static constexpr const uint8_t maxNoOfSSESubscriptions = 10;
+
++static constexpr const std::array<const char*, 2> supportedEvtFormatTypes = {
++ eventFormatType, metricReportFormatType};
++static constexpr const std::array<const char*, 3> supportedRegPrefixes = {
++ "Base", "OpenBMC", "Task"};
++
+ #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
+ static std::optional<boost::asio::posix::stream_descriptor> inotifyConn;
+ static constexpr const char* redfishEventLogDir = "/var/log";
+diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp
+index 1875ec9..4d1ac9f 100644
+--- a/redfish-core/lib/event_service.hpp
++++ b/redfish-core/lib/event_service.hpp
+@@ -19,10 +19,6 @@
+ namespace redfish
+ {
+
+-static constexpr const std::array<const char*, 2> supportedEvtFormatTypes = {
+- eventFormatType, metricReportFormatType};
+-static constexpr const std::array<const char*, 3> supportedRegPrefixes = {
+- "Base", "OpenBMC", "Task"};
+ static constexpr const std::array<const char*, 3> supportedRetryPolicies = {
+ "TerminateAfterRetries", "SuspendRetries", "RetryForever"};
+
+diff --git a/redfish-core/src/error_messages.cpp b/redfish-core/src/error_messages.cpp
+index cfbc9c2..3493132 100644
+--- a/redfish-core/src/error_messages.cpp
++++ b/redfish-core/src/error_messages.cpp
+@@ -2147,6 +2147,32 @@ void mutualExclusiveProperties(crow::Response& res, const std::string& arg1,
+ addMessageToErrorJson(res.jsonValue, mutualExclusiveProperties(arg1, arg2));
+ }
+
++/**
++ * @internal
++ * @brief Formats InvalidQueryFilter into JSON
++ *
++ * See header file for more information
++ * @endinternal
++ */
++nlohmann::json invalidQueryFilter()
++{
++ return nlohmann::json{
++ {"@odata.type", "#Message.v1_0_0.Message"},
++ {"MessageId", "Base.1.5.0.InvalidQueryFilter"},
++ {"Message", "The requested url contains the invalid query filter."},
++ {"MessageArgs", nlohmann::json::array()},
++ {"Severity", "Warning"},
++ {"Resolution",
++ "Ensure the correct query filter is specified in requested url "
++ "and resubmit the request."}};
++}
++
++void invalidQueryFilter(crow::Response& res)
++{
++ res.result(boost::beast::http::status::bad_request);
++ addMessageToErrorJson(res.jsonValue, invalidQueryFilter());
++}
++
+ } // namespace messages
+
+ } // namespace redfish
+--
+2.17.1
+
diff --git a/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/README b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/README
new file mode 100644
index 000000000..55340bb5c
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-phosphor/interfaces/bmcweb/eventservice/README
@@ -0,0 +1,22 @@
+Eventservice specific patches: Temporary pulling down
+the upstream patches. These will be remove as soon as
+thee gets merged upstream.
+
+Upstream revision information:
+ - EventService : Fix retry handling for http-client
+ https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/40731/18
+
+ - EventService: https client support
+ https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/31735/38
+
+ - Move EventService init to later stage
+ https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/41516
+
+ - Add Server-Sent-Events support
+ https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/41258/5
+
+ - Add SSE style subscription support to eventservice
+ https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/41319/5
+
+ - Add EventService SSE filter support
+ https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/41349/2