RDK-38472: Implement Cobalt platform extension for ContentEntitlement service

Reason for change: Support Content Entiltements for Cobalt.
Test Procedure: refer ticket.
Risks: None
Priority: P1
Signed-off-by: sramul630 <sunil_ramulu@comcast.com>

Change-Id: Id77f173872f07d3751d6f42995b00374ade7a9b5
diff --git a/plugin/CobaltImplementation.cpp b/plugin/CobaltImplementation.cpp
index 042fc5a..371cb99 100644
--- a/plugin/CobaltImplementation.cpp
+++ b/plugin/CobaltImplementation.cpp
@@ -111,6 +111,7 @@
       Add(_T("autosuspenddelay"), &AutoSuspendDelay);
       Add(_T("systemproperties"), &SystemProperties);
       Add(_T("closurepolicy"), &ClosurePolicy);
+      Add(_T("fireboltendpoint"), &FireboltEndpoint);
     }
     ~Config() {
     }
@@ -126,6 +127,7 @@
     Core::JSON::DecUInt16 AutoSuspendDelay;
     Core::JSON::VariantContainer SystemProperties;
     Core::JSON::String ClosurePolicy;
+    Core::JSON::String FireboltEndpoint;
   };
 
   class NotificationSink: public Core::Thread {
@@ -318,6 +320,9 @@
           SbRdkSetSetting("systemproperties", properties.c_str());
       }
 
+      if (config.FireboltEndpoint.IsSet() == true)
+        Core::SystemInfo::SetEnvironment(_T("FIREBOLT_ENDPOINT"), config.FireboltEndpoint.Value());
+
       SYSLOG(Logging::Notification, (_T("Preload is set to: %s\n"), _preloadEnabled ? "true" : "false"));
 
       if (config.ClosurePolicy.IsSet() == true) {
diff --git a/plugin/CobaltPlugin.json b/plugin/CobaltPlugin.json
index 739c5aa..56e1398 100644
--- a/plugin/CobaltPlugin.json
+++ b/plugin/CobaltPlugin.json
@@ -87,6 +87,10 @@
           },
           "systemproperties": {
             "$ref": "#/definitions/systemproperties"
+          },
+          "fireboltendpoint": {
+            "type": "string",
+            "description": "A URL that specifies access point to Firebolt Riple. Should include session id in the query."
           }
         }
       }
diff --git a/plugin/doc/CobaltPlugin.md b/plugin/doc/CobaltPlugin.md
index 80e03e5..3690478 100644
--- a/plugin/doc/CobaltPlugin.md
+++ b/plugin/doc/CobaltPlugin.md
@@ -92,6 +92,7 @@
 | configuration?.systemproperties?.integratorname | string | <sup>*(optional)*</sup> The original manufacture of the device |
 | configuration?.systemproperties?.friendlyname | string | <sup>*(optional)*</sup> A friendly name for this actual device |
 | configuration?.systemproperties?.devicetype | string | <sup>*(optional)*</sup> The type of the device. (must be one of the following: *SetTopBox*, *OverTheTopBox*, *TV*) |
+| configuration?.fireboltendpoint | string | <sup>*(optional)*</sup> A URL that specifies access point to Firebolt Riple. Should include session id in the query |
 
 <a name="head.Methods"></a>
 # Methods
diff --git a/src/third_party/starboard/rdk/shared/content_entitlement_platform_service.cc b/src/third_party/starboard/rdk/shared/content_entitlement_platform_service.cc
new file mode 100644
index 0000000..57c2ae7
--- /dev/null
+++ b/src/third_party/starboard/rdk/shared/content_entitlement_platform_service.cc
@@ -0,0 +1,201 @@
+//
+// Copyright 2020 Comcast Cable Communications Management, LLC
+//
+// 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.
+//
+// SPDX-License-Identifier: Apache-2.0//
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// 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.
+
+#include "third_party/starboard/rdk/shared/content_entitlement_platform_service.h"
+
+#include <memory>
+#include <string>
+
+#include "cobalt/extension/platform_service.h"
+#include "starboard/common/log.h"
+#include "starboard/common/string.h"
+#include "starboard/configuration.h"
+#include "starboard/shared/starboard/application.h"
+
+#include "third_party/starboard/rdk/shared/firebolt/firebolt.h"
+#include "third_party/starboard/rdk/shared/log_override.h"
+
+#include <core/JSON.h>
+
+const char kCobaltExtensionContentEntitlementName[] = "com.google.youtube.tv.ContentEntitlement";
+
+typedef struct CobaltExtensionPlatformServicePrivate {
+  void* context;
+  ReceiveMessageCallback receive_callback;
+  CobaltExtensionPlatformServicePrivate(void* context, ReceiveMessageCallback receive_callback)
+    : context(context), receive_callback(receive_callback) {};
+  virtual ~CobaltExtensionPlatformServicePrivate() = default;
+  virtual void* Send(void* data, uint64_t data_length, uint64_t* output_length, bool* invalid_state) = 0;
+} CobaltExtensionPlatformServicePrivate;
+
+namespace third_party {
+namespace starboard {
+namespace rdk {
+namespace shared {
+
+namespace {
+
+using namespace WPEFramework;
+
+struct ContentEntitlementCobaltExtensionPlatformService : public CobaltExtensionPlatformServicePrivate {
+  struct ContentEntitlementUpdate : Core::JSON::Container {
+    struct Entitlement : Core::JSON::Container {
+      Entitlement()
+        : Core::JSON::Container() {
+        Init();
+      }
+      Entitlement(const Entitlement& other)
+        : Core::JSON::Container()
+        , id(other.id) {
+        Init();
+      }
+      Entitlement& operator=(const Entitlement& rhs) {
+        id = rhs.id;
+        return (*this);
+      }
+      Core::JSON::String id;
+    private:
+      void Init() {
+        Add(_T("id"), &id);
+      }
+    };
+    struct Payload : Core::JSON::Container {
+      Payload()
+        : Core::JSON::Container() {
+        Add(_T("entitlements"), &entitlements);
+      }
+      Core::JSON::ArrayType<Entitlement> entitlements;
+    };
+    ContentEntitlementUpdate()
+      : Core::JSON::Container() {
+      Add(_T("message"), &message);
+      Add(_T("payload"), &payload);
+    }
+    Core::JSON::String message;
+    Payload payload;
+  };
+
+  ContentEntitlementCobaltExtensionPlatformService(void* context, ReceiveMessageCallback callback)
+    : CobaltExtensionPlatformServicePrivate(context, callback) {
+  }
+
+  void* Send(void* data, uint64_t length, uint64_t* output_length, bool* invalid_state) override {
+    bool result = false;
+
+    if (data && length) {
+      ContentEntitlementUpdate update;
+      std::string json_string(static_cast<const char*>(data), length);
+
+      if(!update.FromString(json_string)) {
+        SB_LOG(ERROR) << "Failed to parse ContentEntitlement message";
+      }
+      else if(update.message == "updateEntitlements") {
+        const auto &payload_entitlements = update.payload.entitlements;
+
+        std::vector<firebolt::Entitlement> entitlements;
+        for(int i = 0; i < payload_entitlements.Length(); ++i)
+          entitlements.push_back({payload_entitlements[i].id.Value()});
+
+        firebolt::Discovery discovery;
+        result = discovery.entitlements(entitlements);
+      }
+    }
+
+    bool* ptr = reinterpret_cast<bool*>(SbMemoryAllocate(sizeof(bool)));
+    *ptr = result;
+    return static_cast<void*>(ptr);
+  }
+};
+
+bool Has(const char* name) {
+  // Check if platform has service name.
+  bool result = strcmp(name, kCobaltExtensionContentEntitlementName) == 0 && firebolt::IsAvailable();
+  SB_LOG(INFO) << "Entitlement Has service called " << name << " result = " << result;
+  return result;
+}
+
+CobaltExtensionPlatformService Open(void* context,
+                                    const char* name,
+                                    ReceiveMessageCallback receive_callback) {
+  SB_DCHECK(context);
+
+  CobaltExtensionPlatformService service;
+
+  if (strcmp(name, kCobaltExtensionContentEntitlementName) == 0 && firebolt::IsAvailable()) {
+    SB_LOG(INFO) << "Open() service created: " << name;
+    service =
+      new ContentEntitlementCobaltExtensionPlatformService(context, receive_callback);
+  } else {
+    SB_LOG(ERROR) << "Open() service name does not exist: " << name;
+    service = kCobaltExtensionPlatformServiceInvalid;
+  }
+
+  delete[] name;
+
+  return service;
+}
+
+void Close(CobaltExtensionPlatformService service) {
+  SB_LOG(INFO) << "Close() Service.";
+  delete static_cast<CobaltExtensionPlatformServicePrivate*>(service);
+}
+
+void* Send(CobaltExtensionPlatformService service,
+           void* data,
+           uint64_t length,
+           uint64_t* output_length,
+           bool* invalid_state) {
+  SB_DCHECK(data);
+  SB_DCHECK(length);
+  SB_DCHECK(output_length);
+  SB_DCHECK(invalid_state);
+  return static_cast<CobaltExtensionPlatformServicePrivate*>(service)->Send(data, length, output_length, invalid_state);
+}
+
+const CobaltExtensionPlatformServiceApi kPlatformServiceApi = {
+  kCobaltExtensionPlatformServiceName,
+  1,  // API version that's implemented.
+  &Has,
+  &Open,
+  &Close,
+  &Send
+};
+
+}  // namespace
+
+const void* GetPlatformServiceApi() {
+  SB_LOG(INFO) << "GetContentEntitlementPlatformServiceApi return ";
+  return &kPlatformServiceApi;
+}
+
+}  // namespace thirdpary
+}  // namespace starboard
+}  // namespace rdk
+}  // namespace shared
diff --git a/src/third_party/starboard/rdk/shared/content_entitlement_platform_service.h b/src/third_party/starboard/rdk/shared/content_entitlement_platform_service.h
new file mode 100644
index 0000000..06174a8
--- /dev/null
+++ b/src/third_party/starboard/rdk/shared/content_entitlement_platform_service.h
@@ -0,0 +1,46 @@
+//
+// Copyright 2020 Comcast Cable Communications Management, LLC
+//
+// 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.
+//
+// SPDX-License-Identifier: Apache-2.0//
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// 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.
+
+#ifndef THIRD_PARTY_STARBOARD_RDK_SHARED_CONTENT_ENTITLEMENT_H_
+#define THIRD_PARTY_STARBOARD_RDK_SHARED_CONTENT_ENTITLEMENT_H_
+
+namespace third_party {
+namespace starboard {
+namespace rdk {
+namespace shared {
+
+const void* GetPlatformServiceApi();
+
+}  // namespace shared
+}  // namespace rdk
+}  // namespace starboard
+}  // namespace third_party
+
+#endif  // THIRD_PARTY_STARBOARD_RDK_SHARED_CONTENT_ENTITLEMENT_H_
diff --git a/src/third_party/starboard/rdk/shared/firebolt/firebolt.cc b/src/third_party/starboard/rdk/shared/firebolt/firebolt.cc
new file mode 100644
index 0000000..9c5a362
--- /dev/null
+++ b/src/third_party/starboard/rdk/shared/firebolt/firebolt.cc
@@ -0,0 +1,468 @@
+//
+// Copyright 2020 Comcast Cable Communications Management, LLC
+//
+// 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.
+//
+// SPDX-License-Identifier: Apache-2.0
+#ifndef MODULE_NAME
+#define MODULE_NAME CobaltRDKServices
+#endif
+
+#include "third_party/starboard/rdk/shared/firebolt/firebolt.h"
+
+#include <mutex>
+#include <chrono>
+#include <condition_variable>
+#include <memory>
+#include <websocket/WebSocketLink.h>
+#include <websocket/URL.h>
+
+#include "third_party/starboard/rdk/shared/log_override.h"
+
+using namespace WPEFramework;
+
+namespace third_party {
+namespace starboard {
+namespace rdk {
+namespace shared {
+namespace firebolt {
+
+static const auto kDefaultTimeout = std::chrono::milliseconds(50);
+
+namespace {
+
+template<typename T>
+T valueOr(const Core::OptionalType<T>& opt, T defaultValue = {}) {
+  return opt.IsSet() ? opt.Value() : defaultValue;
+};
+
+// Simplefied version of WPEFramework::JSONRPC::Link
+class Link {
+private:
+  class CommunicationChannel {
+  private:
+    class FactoryImpl {
+    private:
+      friend Core::SingletonType<FactoryImpl>;
+      FactoryImpl(const FactoryImpl&) = delete;
+      FactoryImpl& operator=(const FactoryImpl&) = delete;
+      FactoryImpl() = default;
+
+      Core::ProxyPoolType<Core::JSONRPC::Message> _jsonRPCFactory { 2 };
+    public:
+      ~FactoryImpl() = default;
+
+      static FactoryImpl& Instance()
+      {
+        static FactoryImpl& _singleton = Core::SingletonType<FactoryImpl>::Instance();
+        return (_singleton);
+      }
+
+      Core::ProxyType<Core::JSONRPC::Message> Element(const string&)
+      {
+        return (_jsonRPCFactory.Element());
+      }
+    };
+
+    class ChannelImpl : public Core::StreamJSONType<Web::WebSocketClientType<Core::SocketStream>, FactoryImpl&, Core::JSON::IElement> {
+    private:
+      ChannelImpl(const ChannelImpl&) = delete;
+      ChannelImpl& operator=(const ChannelImpl&) = delete;
+
+      typedef Core::StreamJSONType<Web::WebSocketClientType<Core::SocketStream>, FactoryImpl&, Core::JSON::IElement> BaseClass;
+
+    public:
+      ChannelImpl(CommunicationChannel* parent, const Core::NodeId& remoteNode, const string& path, const string& query)
+        : BaseClass(5, FactoryImpl::Instance(), path, "", query, "", false, true, false, remoteNode.AnyInterface(), remoteNode, 4096, 4096)
+        , _parent(*parent)
+      {
+      }
+      virtual ~ChannelImpl() = default;
+      virtual void Received(Core::ProxyType<Core::JSON::IElement>& jsonObject) override
+      {
+        Core::ProxyType<Core::JSONRPC::Message> inbound(jsonObject);
+        ASSERT(inbound.IsValid() == true);
+        if (inbound.IsValid() == true) {
+          _parent.Inbound(inbound);
+        }
+      }
+      virtual void Send(Core::ProxyType<Core::JSON::IElement>& jsonObject) override
+      {
+#if 0
+        string message;
+        ToMessage(jsonObject, message);
+        SB_LOG(INFO) << "sending message: " << message;
+#endif
+      }
+      virtual void StateChange() override
+      {
+        _parent.StateChange();
+      }
+      virtual bool IsIdle() const
+      {
+        return (true);
+      }
+    private:
+      void ToMessage(const Core::ProxyType<Core::JSON::IElement>& jsonObject, string& message) const
+      {
+        Core::ProxyType<Core::JSONRPC::Message> inbound(jsonObject);
+        ASSERT(inbound.IsValid());
+        if (inbound.IsValid())
+          inbound->ToString(message);
+      }
+    private:
+      CommunicationChannel& _parent;
+    };
+
+  protected:
+    CommunicationChannel(const Core::NodeId& remoteNode, const string& path, const string& query)
+      : _channel(this, remoteNode, path, query)
+      , _sequence(0)
+    {
+    }
+
+  public:
+    virtual ~CommunicationChannel()
+    {
+      Close();
+    }
+    static Core::ProxyType<CommunicationChannel> Instance(const Core::NodeId& remoteNode, const string& path, const string& query)
+    {
+      static Core::ProxyMapType<string, CommunicationChannel> channelMap;
+      string searchLine = remoteNode.HostAddress() + '@' + path + '?' + query;
+      return (channelMap.template Instance<CommunicationChannel>(searchLine, remoteNode, path, query));
+    }
+    static Core::ProxyType<Core::JSONRPC::Message> Message()
+    {
+      return (FactoryImpl::Instance().Element(string()));
+    }
+    void Register(Link& client)
+    {
+      typename std::unique_lock<std::mutex> lock(_adminLock);
+      ASSERT(std::find(_observers.begin(), _observers.end(), &client) == _observers.end());
+      _observers.push_back(&client);
+    }
+    void Unregister(Link& client)
+    {
+      typename std::unique_lock<std::mutex> lock(_adminLock);
+      auto index = std::find(_observers.begin(), _observers.end(), &client);
+      if (index != _observers.end())
+        _observers.erase(index);
+    }
+    void Submit(const Core::ProxyType<Core::JSON::IElement>& message)
+    {
+      _channel.Submit(message);
+    }
+    bool IsSuspended() const
+    {
+      return (_channel.IsSuspended());
+    }
+    bool IsOpen() const
+    {
+      return _channel.IsOpen();
+    }
+    uint32_t Initialize()
+    {
+      return (Open(1000));
+    }
+    uint32_t Sequence() const
+    {
+      return (++_sequence);
+    }
+  protected:
+    void StateChange()
+    {
+    }
+    bool Open(const uint32_t waitTime)
+    {
+      bool result = true;
+      if (_channel.IsClosed() == true) {
+        result = (_channel.Open(waitTime) == Core::ERROR_NONE);
+      }
+      return (result);
+    }
+    void Close()
+    {
+      _channel.Close(Core::infinite);
+    }
+  private:
+    uint32_t Inbound(const Core::ProxyType<Core::JSONRPC::Message>& inbound)
+    {
+      uint32_t result = Core::ERROR_UNAVAILABLE;
+
+      typename std::unique_lock<std::mutex> lock(_adminLock);
+      auto index = _observers.begin();
+      while ((result != Core::ERROR_NONE) && (index != _observers.end())) {
+        result = (*index)->Inbound(inbound);
+        index++;
+      }
+
+      return (result);
+    }
+
+  private:
+    std::mutex _adminLock;
+    ChannelImpl _channel;
+    std::list< Link *> _observers;
+    mutable std::atomic<uint32_t> _sequence;
+  };
+
+  static Core::ProxyType<CommunicationChannel> CommunicationChannelInstance(const Core::URL& url)
+  {
+    const auto& host  = valueOr(url.Host(), string("127.0.0.1"));
+    const auto& path  = string("/") + valueOr(url.Path());
+    const auto& port  = valueOr(url.Port(), Core::URL::Port(url.Type()));
+    const auto& query = valueOr(url.Query());
+    Core::NodeId nodeId = Core::NodeId(host.c_str(), port);
+    return CommunicationChannel::Instance(nodeId, path, query);
+  }
+
+  Link(const Link&) = delete;
+  Link& operator=(Link&) = delete;
+
+public:
+  using ErrorInfo = Core::JSONRPC::Message::Info;
+  using CallbackFunction = std::function<void(const Core::JSONRPC::Message&)>;
+  using CallbackMap = std::map<uint32_t, CallbackFunction>;
+
+  explicit Link(const Core::URL& url)
+    : _channel ( CommunicationChannelInstance(url) )
+  {
+    _channel->Register(*this);
+  }
+
+  virtual ~Link()
+  {
+    _channel->Unregister(*this);
+  }
+
+  uint32_t SendAsync(const char method[], const Core::JSON::IElement& parameters, CallbackFunction&& cb, uint32_t& callbackId)
+  {
+    uint32_t result = Core::ERROR_UNAVAILABLE;
+    if ( method ) {
+      if ( _channel->IsSuspended() == true || _channel->IsOpen() == false ) {
+        result = Core::ERROR_ASYNC_FAILED;
+      } else {
+        uint32_t id;
+        Core::ProxyType<Core::JSONRPC::Message> message(CommunicationChannel::Message());
+
+        id = _channel->Sequence();
+        message->Id = id;
+        message->Designator = method;
+        if ( parameters.IsSet() ) {
+          string params;
+          parameters.ToString(params);
+          message->Parameters = params;
+        }
+
+        callbackId = id;
+        InsertCallback(id, std::move(cb));
+
+        _channel->Submit(Core::ProxyType<Core::JSON::IElement>(message));
+        message.Release();
+
+        result = Core::ERROR_NONE;
+      }
+    }
+    return (result);
+  }
+
+  uint32_t Send(std::chrono::microseconds waitTime, const char method[], const Core::JSON::IElement& parameters, Core::JSON::IElement& result, ErrorInfo& errorInfo)
+  {
+    uint32_t rc;
+    uint32_t callbackId = -1u;
+    std::condition_variable signal;
+    std::shared_ptr<bool> done = std::make_shared<bool>(false);
+
+    CallbackFunction cb = [this, weak_done = std::weak_ptr<bool>(done), &result, &errorInfo, &signal](const Core::JSONRPC::Message& m) {
+      auto done = weak_done.lock();
+      if ( done == nullptr )
+        return;
+      std::unique_lock<std::mutex> lock(_adminLock);
+      if ( *done == false )  // timeout check
+      {
+        if ( m.Result.IsSet() )
+          result.FromString(m.Result.Value());
+        else
+          errorInfo = m.Error;
+        *done = true;
+        lock.unlock();
+        signal.notify_one();
+      }
+    };
+
+    result.Clear();
+    errorInfo.Clear();
+
+    rc = SendAsync(method, parameters, std::move(cb), callbackId);
+
+    std::unique_lock<std::mutex> lock(_adminLock);
+    if (rc == Core::ERROR_NONE) {
+      if ( ! signal.wait_for(lock, waitTime, [&]{ return *done; }) ) {
+        *done = true;  // set to indicate timeout
+        rc = Core::ERROR_ASYNC_FAILED;
+      }
+    }
+
+    RemoveCallback(lock, callbackId);
+
+    return rc;
+  }
+
+  uint32_t RemoveCallbackById(int32_t id)
+  {
+    std::unique_lock<std::mutex> lock(_adminLock);
+    return RemoveCallback(lock, id);
+  }
+
+private:
+  friend CommunicationChannel;
+
+  uint32_t RemoveCallback(std::unique_lock<std::mutex>&, int32_t id)
+  {
+    auto it = _callbackMap.find(id);
+    if (it != _callbackMap.end())
+      _callbackMap.erase(it);
+    return Core::ERROR_NONE;
+  }
+
+  void InsertCallback(uint32_t id, CallbackFunction&& cb)
+  {
+    std::unique_lock<std::mutex> lock(_adminLock);
+
+    _callbackMap.emplace(
+      std::piecewise_construct,
+      std::forward_as_tuple(id),
+      std::forward_as_tuple(std::move(cb)));
+  }
+
+  uint32_t Inbound(const Core::ProxyType<Core::JSONRPC::Message>& inbound)
+  {
+    uint32_t result = Core::ERROR_INVALID_SIGNATURE;
+    if ( inbound->Id.IsSet() ) {
+      if ( inbound->Result.IsSet() || inbound->Error.IsSet() ) {
+        uint32_t id = inbound->Id.Value();
+        CallbackFunction cb;
+
+        _adminLock.lock();
+        auto it = _callbackMap.find(id);
+        if (it != _callbackMap.end()) {
+          cb = std::move(it->second);
+          _callbackMap.erase(it);
+        }
+        _adminLock.unlock();
+
+        if (cb)
+          cb(*inbound);
+
+        result = Core::ERROR_NONE;
+      }
+    }
+    return (result);
+  }
+
+private:
+  Core::ProxyType< CommunicationChannel > _channel;
+  std::mutex _adminLock;
+  CallbackMap _callbackMap;
+};
+
+static Core::URL FireboltEndpoint()
+{
+  static std::once_flag flag;
+  static Core::URL url;
+  std::call_once(flag, [&](){
+    string envVar;
+    if ((Core::SystemInfo::GetEnvironment(_T("FIREBOLT_ENDPOINT"), envVar) == true) && (envVar.empty() == false))
+      url = Core::URL(envVar.c_str());
+  });
+  return url;
+}
+
+struct Entitlements : Core::JSON::Container
+{
+  struct Entitlement : Core::JSON::Container
+  {
+    Entitlement()
+      : Core::JSON::Container()
+    {
+      Init();
+    }
+    Entitlement(const Entitlement& other)
+      : Core::JSON::Container()
+      , entitlementId(other.entitlementId)
+    {
+      Init();
+    }
+    Entitlement& operator=(const Entitlement& rhs)
+    {
+      entitlementId = rhs.entitlementId;
+      return (*this);
+    }
+    Core::JSON::String entitlementId;
+  private:
+    void Init() {
+      Add(_T("entitlementId"), &entitlementId);
+    }
+  };
+  Entitlements()
+    : Core::JSON::Container()
+  {
+    Add(_T("entitlements"), &entitlements);
+  }
+
+  Core::JSON::ArrayType<Entitlement> entitlements;
+};
+
+}  // namespace
+
+bool IsAvailable()
+{
+  auto url = FireboltEndpoint();
+  return url.IsValid() && (url.Type() == Core::URL::SCHEME_WS || url.Type() == Core::URL::SCHEME_WSS);
+}
+
+bool Discovery::entitlements(const std::vector<Entitlement>& entitlements)
+{
+  if (IsAvailable())
+  {
+    Link link( FireboltEndpoint() );
+
+    uint32_t rc;
+    Entitlements params;
+    Link::ErrorInfo error;
+    Core::JSON::String result;
+
+    for ( const auto& i : entitlements )
+      params.entitlements.Add().entitlementId = i.entitlementId;
+
+    rc = link.Send(kDefaultTimeout, "discovery.entitlements", params, result, error);
+
+    if (rc != Core::ERROR_NONE || result.IsSet() == false)
+    {
+      SB_LOG(ERROR) << "Failed to send 'discovery.entitlements', rc=" << rc
+                    << " ( " << Core::ErrorToString(rc) << " )"
+                    << ", error code = " << error.Code.Value()
+                    <<  " (" << error.Text.Value() << ")";
+    }
+    else
+      return true;
+  }
+  return false;
+}
+
+}  // namespace firebolt
+}  // namespace shared
+}  // namespace rdk
+}  // namespace starboard
+}  // namespace third_party
diff --git a/src/third_party/starboard/rdk/shared/firebolt/firebolt.h b/src/third_party/starboard/rdk/shared/firebolt/firebolt.h
new file mode 100644
index 0000000..2773f27
--- /dev/null
+++ b/src/third_party/starboard/rdk/shared/firebolt/firebolt.h
@@ -0,0 +1,42 @@
+//
+// Copyright 2020 Comcast Cable Communications Management, LLC
+//
+// 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.
+//
+// SPDX-License-Identifier: Apache-2.0
+#pragma once
+
+#include <string>
+#include <vector>
+
+namespace third_party {
+namespace starboard {
+namespace rdk {
+namespace shared {
+namespace firebolt {
+
+bool IsAvailable();
+
+struct Entitlement {
+    std::string entitlementId;
+};
+
+struct Discovery {
+    bool entitlements(const std::vector<Entitlement>&);
+};
+
+}  // namespace firebolt
+}  // namespace shared
+}  // namespace rdk
+}  // namespace starboard
+}  // namespace third_party
diff --git a/src/third_party/starboard/rdk/shared/sources.gypi b/src/third_party/starboard/rdk/shared/sources.gypi
index a3d9201..4dd0bac 100644
--- a/src/third_party/starboard/rdk/shared/sources.gypi
+++ b/src/third_party/starboard/rdk/shared/sources.gypi
@@ -498,6 +498,10 @@
         '<(DEPTH)/third_party/starboard/rdk/shared/hang_detector.cc',
         '<(DEPTH)/third_party/starboard/rdk/shared/linux_key_mapping.h',
         '<(DEPTH)/third_party/starboard/rdk/shared/linux_key_mapping.cc',
+        '<(DEPTH)/third_party/starboard/rdk/shared/content_entitlement_platform_service.h',
+        '<(DEPTH)/third_party/starboard/rdk/shared/content_entitlement_platform_service.cc',
+        '<(DEPTH)/third_party/starboard/rdk/shared/firebolt/firebolt.cc',
+        '<(DEPTH)/third_party/starboard/rdk/shared/firebolt/firebolt.h',
     ],
     'conditions': [
       ['sb_api_version == 12', {
diff --git a/src/third_party/starboard/rdk/shared/system/system_get_extensions.cc b/src/third_party/starboard/rdk/shared/system/system_get_extensions.cc
index 9eaa625..1f35297 100644
--- a/src/third_party/starboard/rdk/shared/system/system_get_extensions.cc
+++ b/src/third_party/starboard/rdk/shared/system/system_get_extensions.cc
@@ -32,10 +32,13 @@
 
 #include <cstring>
 
+#include "cobalt/extension/platform_service.h"
 #include "cobalt/extension/configuration.h"
 #include "cobalt/extension/crash_handler.h"
 #include "starboard/common/string.h"
+#include "starboard/common/log.h"
 #include "third_party/starboard/rdk/shared/configuration.h"
+#include "third_party/starboard/rdk/shared/content_entitlement_platform_service.h"
 #if SB_IS(EVERGREEN_COMPATIBLE)
 #include "starboard/elf_loader/evergreen_config.h"
 #include "starboard/shared/starboard/crash_handler.h"
@@ -59,5 +62,8 @@
   if (strcmp(name, kCobaltExtensionConfigurationName) == 0) {
     return third_party::starboard::rdk::shared::GetConfigurationApi();
   }
+  else if (strcmp(name, kCobaltExtensionPlatformServiceName) == 0) {
+    return third_party::starboard::rdk::shared::GetPlatformServiceApi();
+  }
   return NULL;
 }