Merge pull request #4001 from Subv/http2
Services/HTTP: Corrected some error codes and added a few new ones.
This commit is contained in:
commit
8ae126eb33
2 changed files with 206 additions and 26 deletions
|
@ -11,30 +11,82 @@ namespace HTTP {
|
||||||
|
|
||||||
namespace ErrCodes {
|
namespace ErrCodes {
|
||||||
enum {
|
enum {
|
||||||
|
InvalidRequestState = 22,
|
||||||
|
TooManyContexts = 26,
|
||||||
InvalidRequestMethod = 32,
|
InvalidRequestMethod = 32,
|
||||||
InvalidContext = 102,
|
ContextNotFound = 100,
|
||||||
|
|
||||||
|
/// This error is returned in multiple situations: when trying to initialize an
|
||||||
|
/// already-initialized session, or when using the wrong context handle in a context-bound
|
||||||
|
/// session
|
||||||
|
SessionStateError = 102,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResultCode ERROR_CONTEXT_ERROR = // 0xD8A0A066
|
const ResultCode ERROR_STATE_ERROR = // 0xD8A0A066
|
||||||
ResultCode(ErrCodes::InvalidContext, ErrorModule::HTTP, ErrorSummary::InvalidState,
|
ResultCode(ErrCodes::SessionStateError, ErrorModule::HTTP, ErrorSummary::InvalidState,
|
||||||
ErrorLevel::Permanent);
|
ErrorLevel::Permanent);
|
||||||
|
|
||||||
void HTTP_C::Initialize(Kernel::HLERequestContext& ctx) {
|
void HTTP_C::Initialize(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx, 0x1, 1, 4);
|
IPC::RequestParser rp(ctx, 0x1, 1, 4);
|
||||||
const u32 shmem_size = rp.Pop<u32>();
|
const u32 shmem_size = rp.Pop<u32>();
|
||||||
rp.PopPID();
|
u32 pid = rp.PopPID();
|
||||||
shared_memory = rp.PopObject<Kernel::SharedMemory>();
|
shared_memory = rp.PopObject<Kernel::SharedMemory>();
|
||||||
if (shared_memory) {
|
if (shared_memory) {
|
||||||
shared_memory->name = "HTTP_C:shared_memory";
|
shared_memory->name = "HTTP_C:shared_memory";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG_WARNING(Service_HTTP, "(STUBBED) called, shared memory size: {} pid: {}", shmem_size, pid);
|
||||||
|
|
||||||
|
auto* session_data = GetSessionData(ctx.Session());
|
||||||
|
ASSERT(session_data);
|
||||||
|
|
||||||
|
if (session_data->initialized) {
|
||||||
|
LOG_ERROR(Service_HTTP, "Tried to initialize an already initialized session");
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
|
rb.Push(ERROR_STATE_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
session_data->initialized = true;
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
// This returns 0xd8a0a046 if no network connection is available.
|
// This returns 0xd8a0a046 if no network connection is available.
|
||||||
// Just assume we are always connected.
|
// Just assume we are always connected.
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
LOG_WARNING(Service_HTTP, "(STUBBED) called, shared memory size: {}", shmem_size);
|
void HTTP_C::InitializeConnectionSession(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx, 0x8, 1, 2);
|
||||||
|
const Context::Handle context_handle = rp.Pop<u32>();
|
||||||
|
u32 pid = rp.PopPID();
|
||||||
|
|
||||||
|
auto* session_data = GetSessionData(ctx.Session());
|
||||||
|
ASSERT(session_data);
|
||||||
|
|
||||||
|
if (session_data->initialized) {
|
||||||
|
LOG_ERROR(Service_HTTP, "Tried to initialize an already initialized session");
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
|
rb.Push(ERROR_STATE_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(Subv): Check that the input PID matches the PID that created the context.
|
||||||
|
auto itr = contexts.find(context_handle);
|
||||||
|
if (itr == contexts.end()) {
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
|
rb.Push(ResultCode(ErrCodes::ContextNotFound, ErrorModule::HTTP, ErrorSummary::InvalidState,
|
||||||
|
ErrorLevel::Permanent));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
session_data->initialized = true;
|
||||||
|
// Bind the context to the current session.
|
||||||
|
session_data->current_http_context = context_handle;
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
LOG_DEBUG(Service_HTTP, "called, context_id={} pid={}", context_handle, pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) {
|
void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -50,11 +102,46 @@ void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) {
|
||||||
LOG_DEBUG(Service_HTTP, "called, url_size={}, url={}, method={}", url_size, url,
|
LOG_DEBUG(Service_HTTP, "called, url_size={}, url={}, method={}", url_size, url,
|
||||||
static_cast<u32>(method));
|
static_cast<u32>(method));
|
||||||
|
|
||||||
|
auto* session_data = GetSessionData(ctx.Session());
|
||||||
|
ASSERT(session_data);
|
||||||
|
|
||||||
|
if (!session_data->initialized) {
|
||||||
|
LOG_ERROR(Service_HTTP, "Tried to create a context on an uninitialized session");
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
|
rb.Push(ERROR_STATE_ERROR);
|
||||||
|
rb.PushMappedBuffer(buffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This command can only be called without a bound session.
|
||||||
|
if (session_data->current_http_context != boost::none) {
|
||||||
|
LOG_ERROR(Service_HTTP, "Command called with a bound context");
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
|
rb.Push(ResultCode(ErrorDescription::NotImplemented, ErrorModule::HTTP,
|
||||||
|
ErrorSummary::Internal, ErrorLevel::Permanent));
|
||||||
|
rb.PushMappedBuffer(buffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr size_t MaxConcurrentHTTPContexts = 8;
|
||||||
|
if (session_data->num_http_contexts >= MaxConcurrentHTTPContexts) {
|
||||||
|
// There can only be 8 HTTP contexts open at the same time for any particular session.
|
||||||
|
LOG_ERROR(Service_HTTP, "Tried to open too many HTTP contexts");
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
|
rb.Push(ResultCode(ErrCodes::TooManyContexts, ErrorModule::HTTP, ErrorSummary::InvalidState,
|
||||||
|
ErrorLevel::Permanent));
|
||||||
|
rb.PushMappedBuffer(buffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (method == RequestMethod::None || static_cast<u32>(method) >= TotalRequestMethods) {
|
if (method == RequestMethod::None || static_cast<u32>(method) >= TotalRequestMethods) {
|
||||||
LOG_ERROR(Service_HTTP, "invalid request method={}", static_cast<u32>(method));
|
LOG_ERROR(Service_HTTP, "invalid request method={}", static_cast<u32>(method));
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
rb.Push(ERROR_CONTEXT_ERROR);
|
rb.Push(ResultCode(ErrCodes::InvalidRequestMethod, ErrorModule::HTTP,
|
||||||
|
ErrorSummary::InvalidState, ErrorLevel::Permanent));
|
||||||
rb.PushMappedBuffer(buffer);
|
rb.PushMappedBuffer(buffer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -67,6 +154,8 @@ void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) {
|
||||||
contexts[context_counter].socket_buffer_size = 0;
|
contexts[context_counter].socket_buffer_size = 0;
|
||||||
contexts[context_counter].handle = context_counter;
|
contexts[context_counter].handle = context_counter;
|
||||||
|
|
||||||
|
session_data->num_http_contexts++;
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
rb.Push<u32>(context_counter);
|
rb.Push<u32>(context_counter);
|
||||||
|
@ -80,10 +169,24 @@ void HTTP_C::CloseContext(Kernel::HLERequestContext& ctx) {
|
||||||
|
|
||||||
LOG_WARNING(Service_HTTP, "(STUBBED) called, handle={}", context_handle);
|
LOG_WARNING(Service_HTTP, "(STUBBED) called, handle={}", context_handle);
|
||||||
|
|
||||||
|
auto* session_data = GetSessionData(ctx.Session());
|
||||||
|
ASSERT(session_data);
|
||||||
|
|
||||||
|
if (!session_data->initialized) {
|
||||||
|
LOG_ERROR(Service_HTTP, "Tried to close a context on an uninitialized session");
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
|
rb.Push(ERROR_STATE_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_MSG(session_data->current_http_context == boost::none,
|
||||||
|
"Unimplemented CloseContext on context-bound session");
|
||||||
|
|
||||||
auto itr = contexts.find(context_handle);
|
auto itr = contexts.find(context_handle);
|
||||||
if (itr == contexts.end()) {
|
if (itr == contexts.end()) {
|
||||||
|
// The real HTTP module just silently fails in this case.
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
rb.Push(ERROR_CONTEXT_ERROR);
|
rb.Push(RESULT_SUCCESS);
|
||||||
LOG_ERROR(Service_HTTP, "called, context {} not found", context_handle);
|
LOG_ERROR(Service_HTTP, "called, context {} not found", context_handle);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -91,7 +194,10 @@ void HTTP_C::CloseContext(Kernel::HLERequestContext& ctx) {
|
||||||
// TODO(Subv): What happens if you try to close a context that's currently being used?
|
// TODO(Subv): What happens if you try to close a context that's currently being used?
|
||||||
ASSERT(itr->second.state == RequestState::NotStarted);
|
ASSERT(itr->second.state == RequestState::NotStarted);
|
||||||
|
|
||||||
|
// TODO(Subv): Make sure that only the session that created the context can close it.
|
||||||
|
|
||||||
contexts.erase(itr);
|
contexts.erase(itr);
|
||||||
|
session_data->num_http_contexts--;
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
@ -108,19 +214,56 @@ void HTTP_C::AddRequestHeader(Kernel::HLERequestContext& ctx) {
|
||||||
// Copy the name_buffer into a string without the \0 at the end
|
// Copy the name_buffer into a string without the \0 at the end
|
||||||
const std::string name(name_buffer.begin(), name_buffer.end() - 1);
|
const std::string name(name_buffer.begin(), name_buffer.end() - 1);
|
||||||
|
|
||||||
// Copy thr value_buffer into a string without the \0 at the end
|
// Copy the value_buffer into a string without the \0 at the end
|
||||||
std::string value(value_size - 1, '\0');
|
std::string value(value_size - 1, '\0');
|
||||||
value_buffer.Read(&value[0], 0, value_size - 1);
|
value_buffer.Read(&value[0], 0, value_size - 1);
|
||||||
|
|
||||||
auto itr = contexts.find(context_handle);
|
auto* session_data = GetSessionData(ctx.Session());
|
||||||
if (itr == contexts.end()) {
|
ASSERT(session_data);
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
|
||||||
rb.Push(ERROR_CONTEXT_ERROR);
|
if (!session_data->initialized) {
|
||||||
LOG_ERROR(Service_HTTP, "called, context {} not found", context_handle);
|
LOG_ERROR(Service_HTTP, "Tried to add a request header on an uninitialized session");
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
|
rb.Push(ERROR_STATE_ERROR);
|
||||||
|
rb.PushMappedBuffer(value_buffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This command can only be called with a bound context
|
||||||
|
if (session_data->current_http_context == boost::none) {
|
||||||
|
LOG_ERROR(Service_HTTP, "Command called without a bound context");
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
|
rb.Push(ResultCode(ErrorDescription::NotImplemented, ErrorModule::HTTP,
|
||||||
|
ErrorSummary::Internal, ErrorLevel::Permanent));
|
||||||
|
rb.PushMappedBuffer(value_buffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session_data->current_http_context != context_handle) {
|
||||||
|
LOG_ERROR(Service_HTTP,
|
||||||
|
"Tried to add a request header on a mismatched session input context={} session "
|
||||||
|
"context={}",
|
||||||
|
context_handle, session_data->current_http_context.get());
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
|
rb.Push(ERROR_STATE_ERROR);
|
||||||
|
rb.PushMappedBuffer(value_buffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto itr = contexts.find(context_handle);
|
||||||
|
ASSERT(itr != contexts.end());
|
||||||
|
|
||||||
|
if (itr->second.state != RequestState::NotStarted) {
|
||||||
|
LOG_ERROR(Service_HTTP,
|
||||||
|
"Tried to add a request header on a context that has already been started.");
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
|
rb.Push(ResultCode(ErrCodes::InvalidRequestState, ErrorModule::HTTP,
|
||||||
|
ErrorSummary::InvalidState, ErrorLevel::Permanent));
|
||||||
|
rb.PushMappedBuffer(value_buffer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ASSERT(itr->second.state == RequestState::NotStarted);
|
|
||||||
ASSERT(std::find_if(itr->second.headers.begin(), itr->second.headers.end(),
|
ASSERT(std::find_if(itr->second.headers.begin(), itr->second.headers.end(),
|
||||||
[&name](const Context::RequestHeader& m) -> bool {
|
[&name](const Context::RequestHeader& m) -> bool {
|
||||||
return m.name == name;
|
return m.name == name;
|
||||||
|
@ -132,7 +275,7 @@ void HTTP_C::AddRequestHeader(Kernel::HLERequestContext& ctx) {
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
rb.PushMappedBuffer(value_buffer);
|
rb.PushMappedBuffer(value_buffer);
|
||||||
|
|
||||||
LOG_WARNING(Service_HTTP, "called, name={}, value={}, context_handle={}", name, value,
|
LOG_DEBUG(Service_HTTP, "called, name={}, value={}, context_handle={}", name, value,
|
||||||
context_handle);
|
context_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,7 +288,7 @@ HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) {
|
||||||
{0x00050040, nullptr, "GetRequestState"},
|
{0x00050040, nullptr, "GetRequestState"},
|
||||||
{0x00060040, nullptr, "GetDownloadSizeState"},
|
{0x00060040, nullptr, "GetDownloadSizeState"},
|
||||||
{0x00070040, nullptr, "GetRequestError"},
|
{0x00070040, nullptr, "GetRequestError"},
|
||||||
{0x00080042, nullptr, "InitializeConnectionSession"},
|
{0x00080042, &HTTP_C::InitializeConnectionSession, "InitializeConnectionSession"},
|
||||||
{0x00090040, nullptr, "BeginRequest"},
|
{0x00090040, nullptr, "BeginRequest"},
|
||||||
{0x000A0040, nullptr, "BeginRequestAsync"},
|
{0x000A0040, nullptr, "BeginRequestAsync"},
|
||||||
{0x000B0082, nullptr, "ReceiveData"},
|
{0x000B0082, nullptr, "ReceiveData"},
|
||||||
|
|
|
@ -41,7 +41,8 @@ enum class RequestState : u8 {
|
||||||
/// There can only be at most one client certificate context attached to an HTTP context at any
|
/// There can only be at most one client certificate context attached to an HTTP context at any
|
||||||
/// given time.
|
/// given time.
|
||||||
struct ClientCertContext {
|
struct ClientCertContext {
|
||||||
u32 handle;
|
using Handle = u32;
|
||||||
|
Handle handle;
|
||||||
std::vector<u8> certificate;
|
std::vector<u8> certificate;
|
||||||
std::vector<u8> private_key;
|
std::vector<u8> private_key;
|
||||||
};
|
};
|
||||||
|
@ -51,17 +52,21 @@ struct ClientCertContext {
|
||||||
/// it, but the chain may contain an arbitrary number of certificates in it.
|
/// it, but the chain may contain an arbitrary number of certificates in it.
|
||||||
struct RootCertChain {
|
struct RootCertChain {
|
||||||
struct RootCACert {
|
struct RootCACert {
|
||||||
u32 handle;
|
using Handle = u32;
|
||||||
|
Handle handle;
|
||||||
std::vector<u8> certificate;
|
std::vector<u8> certificate;
|
||||||
};
|
};
|
||||||
|
|
||||||
u32 handle;
|
using Handle = u32;
|
||||||
|
Handle handle;
|
||||||
std::vector<RootCACert> certificates;
|
std::vector<RootCACert> certificates;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents an HTTP context.
|
/// Represents an HTTP context.
|
||||||
class Context final {
|
class Context final {
|
||||||
public:
|
public:
|
||||||
|
using Handle = u32;
|
||||||
|
|
||||||
Context() = default;
|
Context() = default;
|
||||||
Context(const Context&) = delete;
|
Context(const Context&) = delete;
|
||||||
Context& operator=(const Context&) = delete;
|
Context& operator=(const Context&) = delete;
|
||||||
|
@ -99,7 +104,7 @@ public:
|
||||||
std::weak_ptr<RootCertChain> root_ca_chain;
|
std::weak_ptr<RootCertChain> root_ca_chain;
|
||||||
};
|
};
|
||||||
|
|
||||||
u32 handle;
|
Handle handle;
|
||||||
std::string url;
|
std::string url;
|
||||||
RequestMethod method;
|
RequestMethod method;
|
||||||
RequestState state = RequestState::NotStarted;
|
RequestState state = RequestState::NotStarted;
|
||||||
|
@ -111,7 +116,22 @@ public:
|
||||||
std::vector<PostData> post_data;
|
std::vector<PostData> post_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HTTP_C final : public ServiceFramework<HTTP_C> {
|
struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase {
|
||||||
|
/// The HTTP context that is currently bound to this session, this can be empty if no context
|
||||||
|
/// has been bound. Certain commands can only be called on a session with a bound context.
|
||||||
|
boost::optional<Context::Handle> current_http_context;
|
||||||
|
|
||||||
|
/// Number of HTTP contexts that are currently opened in this session.
|
||||||
|
u32 num_http_contexts = 0;
|
||||||
|
/// Number of ClientCert contexts that are currently opened in this session.
|
||||||
|
u32 num_client_certs = 0;
|
||||||
|
|
||||||
|
/// Whether this session has been initialized in some way, be it via Initialize or
|
||||||
|
/// InitializeConnectionSession.
|
||||||
|
bool initialized = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HTTP_C final : public ServiceFramework<HTTP_C, SessionData> {
|
||||||
public:
|
public:
|
||||||
HTTP_C();
|
HTTP_C();
|
||||||
|
|
||||||
|
@ -151,6 +171,17 @@ private:
|
||||||
*/
|
*/
|
||||||
void CloseContext(Kernel::HLERequestContext& ctx);
|
void CloseContext(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP_C::InitializeConnectionSession service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : HTTP context handle
|
||||||
|
* 2 : 0x20, processID translate-header for the ARM11-kernel
|
||||||
|
* 3 : processID set by the ARM11-kernel
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void InitializeConnectionSession(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP_C::AddRequestHeader service function
|
* HTTP_C::AddRequestHeader service function
|
||||||
* Inputs:
|
* Inputs:
|
||||||
|
@ -168,11 +199,17 @@ private:
|
||||||
|
|
||||||
Kernel::SharedPtr<Kernel::SharedMemory> shared_memory = nullptr;
|
Kernel::SharedPtr<Kernel::SharedMemory> shared_memory = nullptr;
|
||||||
|
|
||||||
std::unordered_map<u32, Context> contexts;
|
/// The next handle number to use when a new HTTP context is created.
|
||||||
u32 context_counter = 0;
|
Context::Handle context_counter = 0;
|
||||||
|
|
||||||
std::unordered_map<u32, ClientCertContext> client_certs;
|
/// The next handle number to use when a new ClientCert context is created.
|
||||||
u32 client_certs_counter = 0;
|
ClientCertContext::Handle client_certs_counter = 0;
|
||||||
|
|
||||||
|
/// Global list of HTTP contexts currently opened.
|
||||||
|
std::unordered_map<Context::Handle, Context> contexts;
|
||||||
|
|
||||||
|
/// Global list of ClientCert contexts currently opened.
|
||||||
|
std::unordered_map<ClientCertContext::Handle, ClientCertContext> client_certs;
|
||||||
};
|
};
|
||||||
|
|
||||||
void InstallInterfaces(SM::ServiceManager& service_manager);
|
void InstallInterfaces(SM::ServiceManager& service_manager);
|
||||||
|
|
Loading…
Reference in a new issue