using MemoryError = Exception<WAUTHN_ERROR_OUT_OF_MEMORY>;
using Cancelled = Exception<WAUTHN_ERROR_CANCELED>;
using Timeout = Exception<WAUTHN_ERROR_TIMED_OUT>;
+using Uncontactable = Exception<WAUTHN_ERROR_CONNECTION_REFUSED>;
} // namespace Exception
#define THROW_ENCODING(...) ERROR_LOGGED_THROW(::Exception::EncodingFailed, __VA_ARGS__)
#define THROW_MEMORY() ERROR_LOGGED_THROW(::Exception::MemoryError, "Memory error")
#define THROW_TIMEOUT(...) ERROR_LOGGED_THROW(::Exception::Timeout, __VA_ARGS__)
+#define THROW_UNCONTACTABLE() \
+ ERROR_LOGGED_THROW(::Exception::Uncontactable, "Authenticator is uncontactable")
#define THROW_CANCELLED() DEBUG_LOGGED_THROW(::Exception::Cancelled, "Operation cancelled")
THROW_UNKNOWN("Creating libwebsocket context failed");
}
+ m_uncontactable = false;
m_connection = m_ws->ClientConnect(m_context, url);
if (!m_connection)
DisconnectOnError();
case LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED:
case LWS_CALLBACK_WSI_CREATE:
case LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION:
- case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
case LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH:
if (m_state != State::DISCONNECTED) {
LogError("Unexpected event");
}
break;
+ case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
+ if (m_state != State::DISCONNECTED) {
+ LogError("Unexpected event");
+ m_state = State::FAILED;
+ return true;
+ }
+ if (HTTP_STATUS_GONE == m_ws->HttpClientResponse(wsi)) {
+ LogError("Uncontactable authenticactor");
+ m_state = State::FAILED;
+ m_uncontactable = true;
+ return true;
+ }
+ break;
+
case LWS_CALLBACK_VHOST_CERT_AGING:
// Was seen happening as soon as just after LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED,
// as well as after the connection is fully connected. Therefore ignore this event
throw ExceptionTunnelClosed{};
} else if (m_state != State::CONNECTED) {
DestroyContext(lock);
+ if (m_uncontactable)
+ THROW_UNCONTACTABLE();
THROW_INVALID_STATE("Invalid tunnel state");
}
}
size_t m_writtenBytesNum = 0;
State m_state = State::DISCONNECTED;
bool m_cancelled = false;
+ bool m_uncontactable = false;
/*
* Protects m_cancelled and m_context.
lws_context_destroy(Context2Native(context));
}
+unsigned int Websockets::HttpClientResponse(Lws *wsi) noexcept
+{
+ return lws_http_client_http_response(Lws2Native(wsi));
+}
+
int Websockets::FidoCallback(struct lws *wsi,
enum lws_callback_reasons reason,
void * /*user*/,
*/
virtual void ContextDestroy(LwsContext *context) noexcept = 0;
+ /*
+ * @brief Returns the last server response code, eg, 200 for client http connections. If there
+ * is no valid response, it will return 0.
+ *
+ * @param[in] wsi The connection to get the response code from
+ */
+ virtual unsigned int HttpClientResponse(Lws *wsi) noexcept = 0;
+
/*
* @brief Sets the listener of websocket events
*
int WriteBinary(Lws *wsi, unsigned char *buf, size_t len) noexcept override;
void SetTimer(Lws *wsi, lws_usec_t usecs) noexcept override;
void ContextDestroy(LwsContext *context) noexcept override;
+ unsigned int HttpClientResponse(Lws *wsi) noexcept override;
void SetListener(IWebsocketsListener *listener) noexcept override { m_listener = listener; }
}
}
+unsigned int MockedSockets::HttpClientResponse(Lws *lws) noexcept
+{
+ auto mockedLws = Lws2Mocked(lws);
+ if (mockedLws->m_url == UncontactableTestUrl())
+ return HTTP_STATUS_GONE;
+
+ return HTTP_STATUS_OK;
+}
+
/*
* Tests below are executed using fully mocked websockets implementation only (MockedSockets)
*/
void ContextDestroy(LwsContext *lwsContext) noexcept override;
+ unsigned int HttpClientResponse(Lws *lws) noexcept override;
+
void SetListener(IWebsocketsListener *listener) noexcept override { m_listener = listener; }
IWebsocketsListener *m_listener = nullptr;
using Exception::Cancelled;
using Exception::InvalidParam;
using Exception::InvalidState;
+using Exception::Uncontactable;
using Exception::Unknown;
/*
EXPECT_NO_THROW(
tunnel.Connect(TestUrl(), ExtraHttpHeader{"unexpected_name:", "unexpected_value"}));
}
+
+TYPED_TEST(TunnelTypedTests, UncontactableAuthenticator)
+{
+ Tunnel tunnel(std::make_shared<TypeParam>());
+
+ EXPECT_THROW(tunnel.Connect(UncontactableTestUrl()), Uncontactable);
+}
+
+TYPED_TEST(TunnelTypedTests, UncontactableAuthenticator2)
+{
+ static unsigned int responseCode = HTTP_STATUS_OK;
+
+ struct TestSockets : public TypeParam {
+ unsigned int HttpClientResponse(Lws *) noexcept override { return responseCode; }
+ };
+
+ const std::string test = "sdfjisdjkfhdfjkghsdfkghdgkjhd";
+ std::vector<uint8_t> in;
+ std::vector<uint8_t> out;
+ out.assign(test.c_str(), test.c_str() + test.size());
+
+ Tunnel tunnel(std::make_shared<TestSockets>());
+ responseCode = HTTP_STATUS_GONE;
+ ASSERT_THROW(tunnel.Connect(TestUrl()), Uncontactable);
+
+ responseCode = HTTP_STATUS_OK;
+ ASSERT_NO_THROW(tunnel.Connect(TestUrl()));
+ responseCode = HTTP_STATUS_GONE;
+ ASSERT_NO_THROW(tunnel.WriteBinary(out));
+ ASSERT_NO_THROW(in = tunnel.ReadBinary());
+ EXPECT_EQ(in, out);
+
+ EXPECT_NO_THROW(tunnel.Disconnect());
+}
static const std::string HEADERS_TEST_URL = TestUrl() + "/headers/";
return HEADERS_TEST_URL;
}
+
+const std::string &UncontactableTestUrl()
+{
+ static const std::string UNCONTACTABLE_TEST_URL = TestUrl() + "/uncontactable/";
+ return UNCONTACTABLE_TEST_URL;
+}
const std::string &TestUrl();
const std::string &HeadersTestUrl();
+const std::string &UncontactableTestUrl();
constexpr inline char TEST_HEADER_NAME[] = "name:";
constexpr inline char TEST_HEADER_VALUE[] = "value";
if "name" not in request_headers or request_headers["name"] != "value":
logging.error("Required header is missing")
return HTTPStatus.BAD_REQUEST, [], b'Invalid header\n'
+ elif path.startswith("/uncontactable"):
+ logging.error("Uncontactable authenticator")
+ return HTTPStatus.GONE, [], b'Uncontactable authenticator\n'
def get_host_ip():
# find host ip by sdb port 26101