${CMAKE_CURRENT_SOURCE_DIR}/handshake.cpp
${CMAKE_CURRENT_SOURCE_DIR}/encrypted_tunnel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/base64.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/cbor_parsing.cpp
)
SET(WEBAUTHN_BLE_SOURCES ${WEBAUTHN_BLE_SOURCES} PARENT_SCOPE)
* limitations under the License
*/
-#include "cbor.h"
#include "cbor_encoding.h"
#include "crypto/random.h"
#include "exception.h"
fidoUri = "FIDO:/" + DigitEncode(buffer);
}
+void Container::CloseContainer(const CborEncoder &mapEncoder) noexcept
+{
+ assert(m_appendingToChild);
+
+ CborError err = cbor_encoder_close_container(&m_encoder, &mapEncoder);
+ if (err != CborNoError)
+ TRY_LOG_ERROR("cbor_encoder_close_container() failed with " << static_cast<int>(err));
+
+ m_appendingToChild = false;
+}
+
+void Container::AppendTextStringZ(const char *value)
+{
+ assert(!m_appendingToChild);
+
+ CborError err;
+ err = cbor_encode_text_stringz(&m_encoder, value);
+ if (err != CborNoError)
+ THROW_ENCODING("cbor_encode_text_stringz() failed with " << static_cast<int>(err));
+}
+
+void Container::AppendTextString(const std::string_view& text)
+{
+ assert(!m_appendingToChild);
+
+ CborError err;
+ err = cbor_encode_text_string(&m_encoder, text.data(), text.size());
+ if (err != CborNoError)
+ THROW_ENCODING("cbor_encode_text_string() failed with " << static_cast<int>(err));
+}
+
+void Container::AppendInt64(int64_t value)
+{
+ assert(!m_appendingToChild);
+
+ CborError err = cbor_encode_int(&m_encoder, value);
+ if (err != CborNoError)
+ THROW_ENCODING("cbor_encode_int() failed with " << static_cast<int>(err));
+}
+
+void Container::AppendByteString(const Buffer &value)
+{
+ assert(!m_appendingToChild);
+
+ CborError err = cbor_encode_byte_string(&m_encoder, value.data(), value.size());
+ if (err != CborNoError)
+ THROW_ENCODING("cbor_encode_byte_string() failed with " << static_cast<int>(err));
+}
+
+void Container::AppendByteString(const wauthn_const_buffer_s &value)
+{
+ assert(!m_appendingToChild);
+ assert(value.data);
+
+ CborError err = cbor_encode_byte_string(&m_encoder, value.data, value.size);
+ if (err != CborNoError)
+ THROW_ENCODING("cbor_encode_byte_string() failed with " << static_cast<int>(err));
+}
+
+void Container::AppendBoolean(bool value)
+{
+ assert(!m_appendingToChild);
+
+ CborError err = cbor_encode_boolean(&m_encoder, value);
+ if (err != CborNoError)
+ THROW_ENCODING("cbor_encode_boolean() failed with " << static_cast<int>(err));
+}
+
+Container Container::OpenArray(size_t elements)
+{
+ assert(!m_appendingToChild);
+
+ CborEncoder arrayEncoder;
+ CborError err = cbor_encoder_create_array(&m_encoder, &arrayEncoder, elements);
+ if (err != CborNoError)
+ THROW_ENCODING("cbor_encoder_create_array() failed with " << static_cast<int>(err));
+
+ m_appendingToChild = true;
+ return Container(std::move(arrayEncoder), this);
+}
+
+SortedMap Container::OpenMap(size_t elements)
+{
+ assert(!m_appendingToChild);
+
+ CborEncoder mapEncoder;
+ CborError err = cbor_encoder_create_map(&m_encoder, &mapEncoder, elements);
+ if (err != CborNoError)
+ THROW_ENCODING("cbor_encoder_create_map() failed with " << static_cast<int>(err));
+
+ m_appendingToChild = true;
+ return SortedMap(std::move(mapEncoder), this);
+}
+
+Container::~Container()
+{
+ assert(!m_appendingToChild);
+
+ if (m_parent)
+ m_parent->CloseContainer(m_encoder);
+}
+
+void SortedMap::AppendByteStringAt(const Key &key, const Buffer &value)
+{
+ AddKey(key);
+ AppendByteString(value);
+}
+
+void SortedMap::AppendByteStringAt(const Key &key, const wauthn_const_buffer_s &value)
+{
+ AddKey(key);
+ AppendByteString(value);
+}
+
+void SortedMap::AppendTextStringZAt(const Key &key, const char *value)
+{
+ assert(value);
+
+ AddKey(key);
+ AppendTextStringZ(value);
+}
+
+void SortedMap::AppendOptionalTextStringZAt(const Key &key, const char *value)
+{
+ if (!value)
+ return;
+
+ AppendTextStringZAt(key, value);
+}
+
+void SortedMap::AppendInt64At(const Key &key, int64_t value)
+{
+ AddKey(key);
+ AppendInt64(value);
+}
+
+void SortedMap::AppendBooleanAt(const Key &key, bool value)
+{
+ AddKey(key);
+ AppendBoolean(value);
+}
+
+Container SortedMap::OpenArrayAt(const Key &key, size_t elements)
+{
+ AddKey(key);
+ return OpenArray(elements);
+}
+
+SortedMap SortedMap::OpenMapAt(const Key &key, size_t elements)
+{
+ AddKey(key);
+ return OpenMap(elements);
+}
+
+void SortedMap::AddKey(const Key &key)
+{
+ if (std::holds_alternative<int64_t>(key)) {
+ AppendInt64(std::get<int64_t>(key));
+ } else if (std::holds_alternative<std::string_view>(key)) {
+ AppendTextString(std::get<std::string_view>(key));
+ } else {
+ THROW_UNKNOWN("Unexpected variant type");
+ }
+}
+
+Encoder Encoder::Create(uint8_t *output, size_t size)
+{
+ assert(output);
+ CborEncoder encoder;
+ cbor_encoder_init(&encoder, output, size, 0);
+ return Encoder(std::move(encoder), output);
+}
+
+size_t Encoder::GetBufferSize() const { return cbor_encoder_get_buffer_size(&m_encoder, m_output); }
+
} // namespace CborEncoding
#pragma once
+#include "cbor.h"
#include "common.h"
#include "crypto/common.h"
#include <string>
+#include <variant>
+#include <webauthn-types.h>
namespace CborEncoding {
virtual bool getGrease() const;
};
+class SortedMap;
+
+class Container {
+public:
+ Container(CborEncoder &&encoder, Container *parent = nullptr)
+ : m_encoder(std::move(encoder)), m_parent(parent)
+ {
+ }
+
+ Container(const Container &) = delete;
+ Container(Container &&) = delete;
+ Container &operator=(const Container &) = delete;
+ Container &operator=(Container &&) = delete;
+
+ void AppendTextStringZ(const char *value);
+ void AppendTextString(const std::string_view& text);
+ void AppendInt64(int64_t value);
+ void AppendByteString(const Buffer &value);
+ void AppendByteString(const wauthn_const_buffer_s &value);
+ void AppendBoolean(bool value);
+ Container OpenArray(size_t elements);
+ SortedMap OpenMap(size_t elements);
+
+ virtual ~Container();
+
+private:
+ void CloseContainer(const CborEncoder &containerEncoder) noexcept;
+
+protected:
+ CborEncoder m_encoder;
+
+private:
+ bool m_appendingToChild = false;
+ Container *m_parent;
+};
+
+typedef std::variant<int64_t, std::string_view> Key;
+
+class SortedMap : protected Container {
+public:
+ using Container::Container;
+
+ void AppendByteStringAt(const Key &key, const Buffer &value);
+ void AppendByteStringAt(const Key &key, const wauthn_const_buffer_s &value);
+ void AppendTextStringZAt(const Key &key, const char *value);
+ void AppendOptionalTextStringZAt(const Key &key, const char *value);
+ void AppendInt64At(const Key &key, int64_t value);
+ void AppendBooleanAt(const Key &key, bool value);
+ Container OpenArrayAt(const Key &key, size_t elements);
+ SortedMap OpenMapAt(const Key &key, size_t elements);
+
+private:
+ void AddKey(const Key &key);
+};
+
+class Encoder : public Container {
+public:
+ static Encoder Create(uint8_t *output, size_t size);
+ size_t GetBufferSize() const;
+
+private:
+ Encoder(CborEncoder &&encoder, uint8_t *output)
+ : Container(std::move(encoder)), m_output(output)
+ {
+ }
+
+ uint8_t *m_output;
+};
+
} // namespace CborEncoding
--- /dev/null
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd. 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 "cbor_parsing.h"
+#include "exception.h"
+
+#include <utility>
+
+namespace {
+constexpr auto CBOR_FLAGS = CborValidateShortestIntegrals | CborValidateNoIndeterminateLength |
+ CborValidateMapIsSorted | CborValidateNoTags | CborValidateMapKeysAreUnique |
+ CborValidateCompleteData;
+} // namespace
+
+namespace CborParsing {
+
+const Key CURRENT_KEY;
+
+Container::Container(Container &&other)
+: m_it(std::move(other.m_it)),
+ m_length(other.m_length),
+ m_parsingChild(std::exchange(other.m_parsingChild, false)),
+ m_parent(std::exchange(other.m_parent, nullptr))
+{
+}
+
+Container &Container::operator=(Container &&other)
+{
+ if (this != &other) {
+ m_parent = std::exchange(other.m_parent, nullptr);
+ m_it = std::move(other.m_it);
+ m_length = other.m_length;
+ m_parsingChild = std::exchange(other.m_parsingChild, false);
+ }
+ return *this;
+}
+
+Container::~Container()
+{
+ assert(!m_parsingChild);
+
+ if (!m_parent)
+ return;
+
+ // Skip remaining unparsed values before leaving the container
+ CborError err;
+ while (!cbor_value_at_end(&m_it)) {
+ err = cbor_value_advance(&m_it);
+ if (err != CborNoError)
+ TRY_LOG_ERROR("cbor_value_advance failed with " << static_cast<int>(err));
+ }
+
+ m_parent->LeaveContainer(m_it);
+}
+
+SortedMap Container::EnterMap()
+{
+ assert(!m_parsingChild);
+
+ CborType type = cbor_value_get_type(&m_it);
+ if (type != CborMapType)
+ THROW_UNKNOWN("Expected map type, got " << static_cast<int>(type));
+
+ size_t length;
+ auto err = cbor_value_get_map_length(&m_it, &length);
+ if (err != CborNoError)
+ THROW_UNKNOWN("cbor_value_get_map_length failed with " << static_cast<int>(err));
+
+ CborValue mapIt;
+ err = cbor_value_enter_container(&m_it, &mapIt);
+ if (err != CborNoError)
+ THROW_UNKNOWN("cbor_value_enter_container failed with " << static_cast<int>(err));
+
+ m_parsingChild = true;
+ return SortedMap(std::move(mapIt), this, length);
+}
+
+Container Container::EnterArray()
+{
+ assert(!m_parsingChild);
+
+ CborType type = cbor_value_get_type(&m_it);
+ if (type != CborArrayType)
+ THROW_UNKNOWN("Expected array type, got " << static_cast<int>(type));
+
+ size_t length;
+ auto err = cbor_value_get_array_length(&m_it, &length);
+ if (err != CborNoError)
+ THROW_UNKNOWN("cbor_value_get_array_length failed with " << static_cast<int>(err));
+
+ CborValue arrayIt;
+ err = cbor_value_enter_container(&m_it, &arrayIt);
+ if (err != CborNoError)
+ THROW_UNKNOWN("cbor_value_enter_container failed with " << static_cast<int>(err));
+
+ m_parsingChild = true;
+ return Container(std::move(arrayIt), this, length);
+}
+
+void Container::LeaveContainer(CborValue &childIt) noexcept
+{
+ assert(m_parsingChild);
+
+ // set parent iterator to point to the next element after the child container
+ auto err = cbor_value_leave_container(&m_it, &childIt);
+ if (err != CborNoError)
+ TRY_LOG_ERROR("cbor_value_leave_container failed with " << static_cast<int>(err));
+
+ m_parsingChild = false;
+}
+
+void Container::Advance()
+{
+ assert(!m_parsingChild);
+
+ auto err = cbor_value_advance(&m_it);
+ if (err != CborNoError)
+ THROW_UNKNOWN("cbor_value_advance failed with " << static_cast<int>(err));
+}
+
+int64_t Container::GetInt64()
+{
+ assert(!m_parsingChild);
+
+ if (!cbor_value_is_integer(&m_it))
+ THROW_UNKNOWN("Value is not an integer");
+
+ int64_t value;
+ auto err = cbor_value_get_int64_checked(&m_it, &value);
+ if (err != CborNoError)
+ THROW_UNKNOWN("cbor_value_get_int64_checked failed with " << static_cast<int>(err));
+
+ // skip to the next value
+ Advance();
+ return value;
+}
+
+std::string Container::GetTextString()
+{
+ assert(!m_parsingChild);
+
+ if (!cbor_value_is_text_string(&m_it))
+ THROW_UNKNOWN("Value is not a text string");
+
+ size_t length;
+ auto err = cbor_value_get_string_length(&m_it, &length);
+ if (err != CborNoError)
+ THROW_UNKNOWN("cbor_value_get_string_length failed with " << static_cast<int>(err));
+
+ std::string value(length, '\0');
+
+ // It advances the m_it too
+ err = cbor_value_copy_text_string(&m_it, value.data(), &length, &m_it);
+ if (err != CborNoError)
+ THROW_UNKNOWN("cbor_value_copy_text_string failed with " << static_cast<int>(err));
+
+ return value;
+}
+
+bool Container::GetBoolean()
+{
+ assert(!m_parsingChild);
+
+ if (!cbor_value_is_boolean(&m_it))
+ THROW_UNKNOWN("Value is not a boolean");
+
+ bool value;
+ auto err = cbor_value_get_boolean(&m_it, &value);
+ if (err != CborNoError)
+ THROW_UNKNOWN("cbor_value_get_boolean failed with " << static_cast<int>(err));
+
+ Advance();
+ return value;
+}
+
+uint64_t Container::GetUint64()
+{
+ assert(!m_parsingChild);
+
+ if (!cbor_value_is_unsigned_integer(&m_it))
+ THROW_UNKNOWN("Value is not an unsigned integer");
+
+ uint64_t value;
+ auto err = cbor_value_get_uint64(&m_it, &value);
+ if (err != CborNoError)
+ THROW_UNKNOWN("cbor_value_get_uint64 failed with " << static_cast<int>(err));
+
+ Advance();
+ return value;
+}
+
+Buffer Container::GetByteString()
+{
+ assert(!m_parsingChild);
+
+ if (!cbor_value_is_byte_string(&m_it))
+ THROW_UNKNOWN("Value is not a byte string");
+
+ size_t length;
+ auto err = cbor_value_get_string_length(&m_it, &length);
+ if (err != CborNoError)
+ THROW_UNKNOWN("cbor_value_get_string_length failed with " << static_cast<int>(err));
+
+ Buffer value(length);
+
+ // It advances the m_it too
+ err = cbor_value_copy_byte_string(&m_it, value.data(), &length, &m_it);
+ if (err != CborNoError)
+ THROW_UNKNOWN("cbor_value_copy_byte_string failed with " << static_cast<int>(err));
+
+ return value;
+}
+
+bool SortedMap::LocateKey(const Key &key)
+{
+ assert(!m_parsingChild);
+
+ auto backup = m_it;
+ for (;;) {
+ if (cbor_value_at_end(&m_it))
+ return false;
+
+ if (std::holds_alternative<int64_t>(key)) {
+ auto keyInt = std::get<int64_t>(key);
+ auto tmpKey = GetInt64(); // skips key
+
+ if (tmpKey > keyInt)
+ break; // bigger key found
+
+ if (tmpKey == keyInt)
+ return true;
+ } else if (std::holds_alternative<std::string_view>(key)) {
+ auto &keyStr = std::get<std::string_view>(key);
+ auto tmpKey = GetTextString(); // skips key
+
+ if (tmpKey.size() > keyStr.size() ||
+ (tmpKey.size() == keyStr.size() && tmpKey > keyStr))
+ break; // bigger key found
+
+ if (tmpKey == keyStr)
+ return true;
+ } else if (std::holds_alternative<std::monostate>(key)) {
+ Advance(); // skip key
+ return true;
+ } else {
+ THROW_UNKNOWN("Unexpeceted variant type");
+ }
+
+ // skip value
+ Advance();
+ }
+ m_it = backup; // restore the iterator if key is not found
+ return false;
+}
+
+std::optional<SortedMap> SortedMap::EnterMapAt(const Key &key)
+{
+ if (!LocateKey(key))
+ return std::nullopt;
+
+ return EnterMap();
+}
+
+std::optional<Container> SortedMap::EnterArrayAt(const Key &key)
+{
+ if (!LocateKey(key))
+ return std::nullopt;
+
+ return EnterArray();
+}
+
+std::optional<int64_t> SortedMap::GetInt64At(const Key &key)
+{
+ if (!LocateKey(key))
+ return std::nullopt;
+
+ return GetInt64();
+}
+
+std::optional<std::string> SortedMap::GetTextStringAt(const Key &key)
+{
+ if (!LocateKey(key))
+ return std::nullopt;
+
+ return GetTextString();
+}
+
+std::optional<Buffer> SortedMap::GetByteStringAt(const Key &key)
+{
+ if (!LocateKey(key))
+ return std::nullopt;
+
+ return GetByteString();
+}
+
+std::optional<bool> SortedMap::GetBooleanAt(const Key &key)
+{
+ if (!LocateKey(key))
+ return std::nullopt;
+
+ return GetBoolean();
+}
+
+std::optional<uint64_t> SortedMap::GetUint64At(const Key &key)
+{
+ if (!LocateKey(key))
+ return std::nullopt;
+
+ return GetUint64();
+}
+
+KeyCopy SortedMap::PeekKey()
+{
+ assert(!m_parsingChild);
+
+ if (cbor_value_at_end(&m_it))
+ THROW_UNKNOWN("No more keys");
+
+ CborError err;
+ if (cbor_value_is_integer(&m_it)) {
+ int64_t value;
+ err = cbor_value_get_int64_checked(&m_it, &value);
+ if (err != CborNoError)
+ THROW_UNKNOWN("cbor_value_get_int64_checked failed with " << static_cast<int>(err));
+
+ return value;
+ } else if (cbor_value_is_text_string(&m_it)) {
+ size_t length;
+ err = cbor_value_get_string_length(&m_it, &length);
+ if (err != CborNoError)
+ THROW_UNKNOWN("cbor_value_get_string_length failed with " << static_cast<int>(err));
+
+ std::string value(length, '\0');
+ auto backup = m_it;
+ err = cbor_value_copy_text_string(&m_it, value.data(), &length, &m_it);
+ if (err != CborNoError)
+ THROW_UNKNOWN("cbor_value_copy_text_string failed with " << static_cast<int>(err));
+
+ m_it = backup;
+ return value;
+ }
+
+ THROW_UNKNOWN("Unexpected key type");
+}
+
+Parser::Parser(std::unique_ptr<CborParser> &&parser, CborValue &&root)
+: Container(std::move(root)), m_parser(std::move(parser))
+{
+}
+
+Parser Parser::Create(const uint8_t *input, size_t size)
+{
+ assert(input);
+
+ // root gets a pointer to parser so we can't move it
+ auto parser = std::make_unique<CborParser>();
+ CborValue root;
+
+ auto err = cbor_parser_init(input, size, 0, parser.get(), &root);
+ if (err != CborNoError)
+ THROW_UNKNOWN("cbor_parser_init failed with " << static_cast<int>(err));
+
+ err = cbor_value_validate(&root, CBOR_FLAGS);
+ if (err != CborNoError)
+ THROW_UNKNOWN("CBOR validation failed with " << static_cast<int>(err));
+
+ return Parser(std::move(parser), std::move(root));
+}
+
+} // namespace CborParsing
--- /dev/null
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd. 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
+ */
+
+#pragma once
+
+#include "cbor.h"
+#include "common.h"
+
+#include <memory>
+#include <optional>
+#include <variant>
+
+namespace CborParsing {
+
+class SortedMap;
+
+class Container {
+public:
+ Container(Container &&other);
+ Container &operator=(Container &&other);
+ virtual ~Container();
+
+ SortedMap EnterMap();
+ Container EnterArray();
+
+ int64_t GetInt64();
+ std::string GetTextString();
+ Buffer GetByteString();
+ bool GetBoolean();
+ uint64_t GetUint64();
+
+ size_t Length() const { return m_length; }
+
+protected:
+ Container(CborValue &&it, Container *parent = nullptr, size_t length = 0)
+ : m_it(std::move(it)), m_length(length), m_parent(parent)
+ {
+ }
+
+ void Advance();
+
+private:
+ Container(const Container &) = delete;
+ Container &operator=(const Container &) = delete;
+ void LeaveContainer(CborValue &childIt) noexcept;
+
+protected:
+ CborValue m_it;
+ size_t m_length = 0;
+ bool m_parsingChild = false;
+
+private:
+ Container *m_parent;
+};
+
+typedef std::variant<std::monostate, int64_t, std::string_view> Key;
+typedef std::variant<int64_t, std::string> KeyCopy;
+extern const Key CURRENT_KEY;
+
+class SortedMap : protected Container {
+private:
+ bool LocateKey(const Key &key);
+
+public:
+ using Container::Container;
+
+ std::optional<SortedMap> EnterMapAt(const Key &key);
+ std::optional<Container> EnterArrayAt(const Key &key);
+
+ std::optional<int64_t> GetInt64At(const Key &key);
+ std::optional<std::string> GetTextStringAt(const Key &key);
+ std::optional<Buffer> GetByteStringAt(const Key &key);
+ std::optional<bool> GetBooleanAt(const Key &key);
+ std::optional<uint64_t> GetUint64At(const Key &key);
+
+ KeyCopy PeekKey();
+
+ size_t Length() const { return m_length; }
+};
+
+class Parser : public Container {
+public:
+ static Parser Create(const uint8_t *input, size_t size);
+
+private:
+ Parser(std::unique_ptr<CborParser> &&parser, CborValue &&root);
+
+ std::unique_ptr<CborParser> m_parser;
+};
+
+} // namespace CborParsing
#include <cstddef>
#include <string_view>
#include <utility> // for std::move
+#include <vector>
constexpr size_t QR_SECRET_LEN = 16;
constexpr size_t BLUETOOTH_ADVERT_LEN = 16;
T m_cleanupFn;
};
+typedef std::vector<uint8_t> Buffer;
typedef std::basic_string_view<uint8_t> BufferView;
#define THROW_UNKNOWN(...) LOGGED_THROW(Exception::Unknown, __VA_ARGS__)
#define THROW_INVALID_STATE(...) LOGGED_THROW(Exception::InvalidState, __VA_ARGS__)
#define THROW_CANCELLED() LOGGED_THROW(Exception::Cancelled, "Operation cancelled")
+#define THROW_ENCODING(...) LOGGED_THROW(Exception::EncodingFailed, __VA_ARGS__)
} catch (...) { \
}
-#define LOGGED_THROW(exceptionType, msg) \
- do { \
- std::ostringstream message; \
- message << msg; \
- TRY_LOG_ERROR("Throwing exception: " << message.str()); \
- throw exceptionType(message.str()); \
+#define LOGGED_THROW(exceptionType, msg) \
+ do { \
+ std::ostringstream oss; \
+ oss << msg; \
+ TRY_LOG_ERROR("Throwing exception: " << oss.str()); \
+ throw exceptionType(oss.str()); \
} while (false)