diff --git a/libi2pd/NetDb.cpp b/libi2pd/NetDb.cpp index 6357d7c3..6349b1cc 100644 --- a/libi2pd/NetDb.cpp +++ b/libi2pd/NetDb.cpp @@ -478,7 +478,6 @@ namespace data if (r->GetRouterIdentity () && !r->IsUnreachable () && r->HasValidAddresses ()) { r->DeleteBuffer (); - r->ClearProperties (); // properties are not used for regular routers if (m_RouterInfos.emplace (r->GetIdentHash (), r).second) { if (r->IsFloodfill () && r->IsEligibleFloodfill ()) diff --git a/libi2pd/RouterContext.cpp b/libi2pd/RouterContext.cpp index cca38cf0..14067739 100644 --- a/libi2pd/RouterContext.cpp +++ b/libi2pd/RouterContext.cpp @@ -157,7 +157,7 @@ namespace i2p if (addressCaps) routerInfo.SetUnreachableAddressesTransportCaps (addressCaps); - routerInfo.SetCaps (caps); // caps + L + routerInfo.UpdateCaps (caps); // caps + L routerInfo.SetProperty ("netId", std::to_string (m_NetID)); routerInfo.SetProperty ("router.version", I2P_VERSION); routerInfo.CreateBuffer (m_Keys); @@ -349,10 +349,10 @@ namespace i2p { m_IsFloodfill = floodfill; if (floodfill) - m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eFloodfill); + m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () | i2p::data::RouterInfo::eFloodfill); else { - m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eFloodfill); + m_RouterInfo.UpdateCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eFloodfill); // we don't publish number of routers and leaseset for non-floodfill m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_LEASESETS); m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_ROUTERS); @@ -414,7 +414,7 @@ namespace i2p // no break here, extra + high means 'X' case high : caps |= i2p::data::RouterInfo::eHighBandwidth; break; } - m_RouterInfo.SetCaps (caps); + m_RouterInfo.UpdateCaps (caps); UpdateRouterInfo (); m_BandwidthLimit = limit; } @@ -469,7 +469,7 @@ namespace i2p caps |= i2p::data::RouterInfo::eUnreachable; if (v6 || !SupportsV6 ()) caps &= ~i2p::data::RouterInfo::eFloodfill; // can't be floodfill - m_RouterInfo.SetCaps (caps); + m_RouterInfo.UpdateCaps (caps); } uint16_t port = 0; // delete previous introducers @@ -501,7 +501,7 @@ namespace i2p caps |= i2p::data::RouterInfo::eReachable; if (m_IsFloodfill) caps |= i2p::data::RouterInfo::eFloodfill; - m_RouterInfo.SetCaps (caps); + m_RouterInfo.UpdateCaps (caps); } uint16_t port = 0; // delete previous introducers diff --git a/libi2pd/RouterContext.h b/libi2pd/RouterContext.h index 55fb7bf8..a3970605 100644 --- a/libi2pd/RouterContext.h +++ b/libi2pd/RouterContext.h @@ -67,7 +67,7 @@ namespace garlic void Init (); const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; - i2p::data::RouterInfo& GetRouterInfo () { return m_RouterInfo; }; + i2p::data::LocalRouterInfo& GetRouterInfo () { return m_RouterInfo; }; std::shared_ptr GetSharedRouterInfo () { return std::shared_ptr (&m_RouterInfo, diff --git a/libi2pd/RouterInfo.cpp b/libi2pd/RouterInfo.cpp index b4a92bbd..766f07b4 100644 --- a/libi2pd/RouterInfo.cpp +++ b/libi2pd/RouterInfo.cpp @@ -97,7 +97,6 @@ namespace data m_ReachableTransports = 0; m_Caps = 0; // don't clean up m_Addresses, it will be replaced in ReadFromStream - m_Properties.clear (); // copy buffer UpdateBuffer (buf, len); // skip identity @@ -411,7 +410,7 @@ namespace data r += ReadString (value, 255, s); s.seekg (1, std::ios_base::cur); r++; // ; if (!s) return; - m_Properties[key] = value; + SetProperty (key, value); // extract caps if (!strcmp (key, "caps")) @@ -529,243 +528,6 @@ namespace data return caps; } - void RouterInfo::UpdateCapsProperty () - { - std::string caps; - if (m_Caps & eFloodfill) - { - if (m_Caps & eExtraBandwidth) caps += (m_Caps & eHighBandwidth) ? - CAPS_FLAG_EXTRA_BANDWIDTH2 : // 'X' - CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P' - else - caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O' - caps += CAPS_FLAG_FLOODFILL; // floodfill - } - else - { - if (m_Caps & eExtraBandwidth) - caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_EXTRA_BANDWIDTH2 /* 'X' */ : CAPS_FLAG_EXTRA_BANDWIDTH1; /*'P' */ - else - caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH3 /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth - } - if (m_Caps & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden - if (m_Caps & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable - if (m_Caps & eUnreachable) caps += CAPS_FLAG_UNREACHABLE; // unreachable - - SetProperty ("caps", caps); - } - - void RouterInfo::WriteToStream (std::ostream& s) const - { - uint64_t ts = htobe64 (m_Timestamp); - s.write ((const char *)&ts, sizeof (ts)); - - // addresses - uint8_t numAddresses = m_Addresses->size (); - s.write ((char *)&numAddresses, sizeof (numAddresses)); - for (const auto& addr_ptr : *m_Addresses) - { - const Address& address = *addr_ptr; - // calculate cost - uint8_t cost = 0x7f; - if (address.transportStyle == eTransportNTCP) - cost = address.published ? COST_NTCP2_PUBLISHED : COST_NTCP2_NON_PUBLISHED; - else if (address.transportStyle == eTransportSSU) - cost = address.published ? COST_SSU_DIRECT : COST_SSU_THROUGH_INTRODUCERS; - s.write ((const char *)&cost, sizeof (cost)); - s.write ((const char *)&address.date, sizeof (address.date)); - std::stringstream properties; - bool isPublished = false; - if (address.transportStyle == eTransportNTCP) - { - if (address.IsNTCP2 ()) - { - WriteString ("NTCP2", s); - if (address.IsPublishedNTCP2 () && !address.host.is_unspecified () && address.port) - isPublished = true; - else - { - WriteString ("caps", properties); - properties << '='; - std::string caps; - if (address.IsV4 ()) caps += CAPS_FLAG_V4; - if (address.IsV6 ()) caps += CAPS_FLAG_V6; - if (caps.empty ()) caps += CAPS_FLAG_V4; - WriteString (caps, properties); - properties << ';'; - } - } - else - continue; // don't write NTCP address - } - else if (address.transportStyle == eTransportSSU) - { - WriteString ("SSU", s); - // caps - WriteString ("caps", properties); - properties << '='; - std::string caps; - if (address.IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING; - if (address.host.is_v4 ()) - { - if (address.published) - { - isPublished = true; - if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; - } - else - caps += CAPS_FLAG_V4; - } - else if (address.host.is_v6 ()) - { - if (address.published) - { - isPublished = true; - if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; - } - else - caps += CAPS_FLAG_V6; - } - else - { - if (address.IsV4 ()) caps += CAPS_FLAG_V4; - if (address.IsV6 ()) caps += CAPS_FLAG_V6; - if (caps.empty ()) caps += CAPS_FLAG_V4; - } - WriteString (caps, properties); - properties << ';'; - } - else - WriteString ("", s); - - if (isPublished) - { - WriteString ("host", properties); - properties << '='; - WriteString (address.host.to_string (), properties); - properties << ';'; - } - if (address.transportStyle == eTransportSSU) - { - // write introducers if any - if (!address.ssu->introducers.empty()) - { - int i = 0; - for (const auto& introducer: address.ssu->introducers) - { - if (introducer.iExp) // expiration is specified - { - WriteString ("iexp" + boost::lexical_cast(i), properties); - properties << '='; - WriteString (boost::lexical_cast(introducer.iExp), properties); - properties << ';'; - } - i++; - } - i = 0; - for (const auto& introducer: address.ssu->introducers) - { - WriteString ("ihost" + boost::lexical_cast(i), properties); - properties << '='; - WriteString (introducer.iHost.to_string (), properties); - properties << ';'; - i++; - } - i = 0; - for (const auto& introducer: address.ssu->introducers) - { - WriteString ("ikey" + boost::lexical_cast(i), properties); - properties << '='; - char value[64]; - size_t l = ByteStreamToBase64 (introducer.iKey, 32, value, 64); - value[l] = 0; - WriteString (value, properties); - properties << ';'; - i++; - } - i = 0; - for (const auto& introducer: address.ssu->introducers) - { - WriteString ("iport" + boost::lexical_cast(i), properties); - properties << '='; - WriteString (boost::lexical_cast(introducer.iPort), properties); - properties << ';'; - i++; - } - i = 0; - for (const auto& introducer: address.ssu->introducers) - { - WriteString ("itag" + boost::lexical_cast(i), properties); - properties << '='; - WriteString (boost::lexical_cast(introducer.iTag), properties); - properties << ';'; - i++; - } - } - // write intro key - WriteString ("key", properties); - properties << '='; - char value[64]; - size_t l = ByteStreamToBase64 (address.ssu->key, 32, value, 64); - value[l] = 0; - WriteString (value, properties); - properties << ';'; - // write mtu - if (address.ssu->mtu) - { - WriteString ("mtu", properties); - properties << '='; - WriteString (boost::lexical_cast(address.ssu->mtu), properties); - properties << ';'; - } - } - - if (address.IsNTCP2 () && isPublished) - { - // publish i for NTCP2 - WriteString ("i", properties); properties << '='; - WriteString (address.ntcp2->iv.ToBase64 (), properties); properties << ';'; - } - - if (isPublished || address.ssu) - { - WriteString ("port", properties); - properties << '='; - WriteString (boost::lexical_cast(address.port), properties); - properties << ';'; - } - if (address.IsNTCP2 ()) - { - // publish s and v for NTCP2 - WriteString ("s", properties); properties << '='; - WriteString (address.ntcp2->staticKey.ToBase64 (), properties); properties << ';'; - WriteString ("v", properties); properties << '='; - WriteString ("2", properties); properties << ';'; - } - - uint16_t size = htobe16 (properties.str ().size ()); - s.write ((char *)&size, sizeof (size)); - s.write (properties.str ().c_str (), properties.str ().size ()); - } - - // peers - uint8_t numPeers = 0; - s.write ((char *)&numPeers, sizeof (numPeers)); - - // properties - std::stringstream properties; - for (const auto& p : m_Properties) - { - WriteString (p.first, properties); - properties << '='; - WriteString (p.second, properties); - properties << ';'; - } - uint16_t size = htobe16 (properties.str ().size ()); - s.write ((char *)&size, sizeof (size)); - s.write (properties.str ().c_str (), properties.str ().size ()); - } - bool RouterInfo::IsNewer (const uint8_t * buf, size_t len) const { if (!m_RouterIdentity) return false; @@ -819,12 +581,6 @@ namespace data return l+1; } - void RouterInfo::WriteString (const std::string& str, std::ostream& s) const - { - uint8_t len = str.size (); - s.write ((char *)&len, 1); - s.write (str.c_str (), len); - } void RouterInfo::AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu) { @@ -911,37 +667,6 @@ namespace data return false; } - void RouterInfo::SetCaps (uint8_t caps) - { - m_Caps = caps; - UpdateCapsProperty (); - } - - void RouterInfo::SetCaps (const char * caps) - { - SetProperty ("caps", caps); - m_Caps = 0; - ExtractCaps (caps); - } - - void RouterInfo::SetProperty (const std::string& key, const std::string& value) - { - m_Properties[key] = value; - } - - void RouterInfo::DeleteProperty (const std::string& key) - { - m_Properties.erase (key); - } - - std::string RouterInfo::GetProperty (const std::string& key) const - { - auto it = m_Properties.find (key); - if (it != m_Properties.end ()) - return it->second; - return ""; - } - bool RouterInfo::IsSSU (bool v4only) const { if (v4only) @@ -1267,5 +992,275 @@ namespace data else LogPrint (eLogError, "RouterInfo: Our RouterInfo is too long ", len + signatureLen); } + + void LocalRouterInfo::UpdateCaps (uint8_t caps) + { + SetCaps (caps); + UpdateCapsProperty (); + } + + void LocalRouterInfo::UpdateCapsProperty () + { + std::string caps; + uint8_t c = GetCaps (); + if (c & eFloodfill) + { + if (c & eExtraBandwidth) caps += (c & eHighBandwidth) ? + CAPS_FLAG_EXTRA_BANDWIDTH2 : // 'X' + CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P' + else + caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O' + caps += CAPS_FLAG_FLOODFILL; // floodfill + } + else + { + if (c & eExtraBandwidth) + caps += (c & eHighBandwidth) ? CAPS_FLAG_EXTRA_BANDWIDTH2 /* 'X' */ : CAPS_FLAG_EXTRA_BANDWIDTH1; /*'P' */ + else + caps += (c & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH3 /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth + } + if (c & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden + if (c & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable + if (c & eUnreachable) caps += CAPS_FLAG_UNREACHABLE; // unreachable + + SetProperty ("caps", caps); + } + + void LocalRouterInfo::WriteToStream (std::ostream& s) const + { + uint64_t ts = htobe64 (GetTimestamp ()); + s.write ((const char *)&ts, sizeof (ts)); + + // addresses + const Addresses& addresses = GetAddresses (); + uint8_t numAddresses = addresses.size (); + s.write ((char *)&numAddresses, sizeof (numAddresses)); + for (const auto& addr_ptr : addresses) + { + const Address& address = *addr_ptr; + // calculate cost + uint8_t cost = 0x7f; + if (address.transportStyle == eTransportNTCP) + cost = address.published ? COST_NTCP2_PUBLISHED : COST_NTCP2_NON_PUBLISHED; + else if (address.transportStyle == eTransportSSU) + cost = address.published ? COST_SSU_DIRECT : COST_SSU_THROUGH_INTRODUCERS; + s.write ((const char *)&cost, sizeof (cost)); + s.write ((const char *)&address.date, sizeof (address.date)); + std::stringstream properties; + bool isPublished = false; + if (address.transportStyle == eTransportNTCP) + { + if (address.IsNTCP2 ()) + { + WriteString ("NTCP2", s); + if (address.IsPublishedNTCP2 () && !address.host.is_unspecified () && address.port) + isPublished = true; + else + { + WriteString ("caps", properties); + properties << '='; + std::string caps; + if (address.IsV4 ()) caps += CAPS_FLAG_V4; + if (address.IsV6 ()) caps += CAPS_FLAG_V6; + if (caps.empty ()) caps += CAPS_FLAG_V4; + WriteString (caps, properties); + properties << ';'; + } + } + else + continue; // don't write NTCP address + } + else if (address.transportStyle == eTransportSSU) + { + WriteString ("SSU", s); + // caps + WriteString ("caps", properties); + properties << '='; + std::string caps; + if (address.IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING; + if (address.host.is_v4 ()) + { + if (address.published) + { + isPublished = true; + if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; + } + else + caps += CAPS_FLAG_V4; + } + else if (address.host.is_v6 ()) + { + if (address.published) + { + isPublished = true; + if (address.IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; + } + else + caps += CAPS_FLAG_V6; + } + else + { + if (address.IsV4 ()) caps += CAPS_FLAG_V4; + if (address.IsV6 ()) caps += CAPS_FLAG_V6; + if (caps.empty ()) caps += CAPS_FLAG_V4; + } + WriteString (caps, properties); + properties << ';'; + } + else + WriteString ("", s); + + if (isPublished) + { + WriteString ("host", properties); + properties << '='; + WriteString (address.host.to_string (), properties); + properties << ';'; + } + if (address.transportStyle == eTransportSSU) + { + // write introducers if any + if (!address.ssu->introducers.empty()) + { + int i = 0; + for (const auto& introducer: address.ssu->introducers) + { + if (introducer.iExp) // expiration is specified + { + WriteString ("iexp" + boost::lexical_cast(i), properties); + properties << '='; + WriteString (boost::lexical_cast(introducer.iExp), properties); + properties << ';'; + } + i++; + } + i = 0; + for (const auto& introducer: address.ssu->introducers) + { + WriteString ("ihost" + boost::lexical_cast(i), properties); + properties << '='; + WriteString (introducer.iHost.to_string (), properties); + properties << ';'; + i++; + } + i = 0; + for (const auto& introducer: address.ssu->introducers) + { + WriteString ("ikey" + boost::lexical_cast(i), properties); + properties << '='; + char value[64]; + size_t l = ByteStreamToBase64 (introducer.iKey, 32, value, 64); + value[l] = 0; + WriteString (value, properties); + properties << ';'; + i++; + } + i = 0; + for (const auto& introducer: address.ssu->introducers) + { + WriteString ("iport" + boost::lexical_cast(i), properties); + properties << '='; + WriteString (boost::lexical_cast(introducer.iPort), properties); + properties << ';'; + i++; + } + i = 0; + for (const auto& introducer: address.ssu->introducers) + { + WriteString ("itag" + boost::lexical_cast(i), properties); + properties << '='; + WriteString (boost::lexical_cast(introducer.iTag), properties); + properties << ';'; + i++; + } + } + // write intro key + WriteString ("key", properties); + properties << '='; + char value[64]; + size_t l = ByteStreamToBase64 (address.ssu->key, 32, value, 64); + value[l] = 0; + WriteString (value, properties); + properties << ';'; + // write mtu + if (address.ssu->mtu) + { + WriteString ("mtu", properties); + properties << '='; + WriteString (boost::lexical_cast(address.ssu->mtu), properties); + properties << ';'; + } + } + + if (address.IsNTCP2 () && isPublished) + { + // publish i for NTCP2 + WriteString ("i", properties); properties << '='; + WriteString (address.ntcp2->iv.ToBase64 (), properties); properties << ';'; + } + + if (isPublished || address.ssu) + { + WriteString ("port", properties); + properties << '='; + WriteString (boost::lexical_cast(address.port), properties); + properties << ';'; + } + if (address.IsNTCP2 ()) + { + // publish s and v for NTCP2 + WriteString ("s", properties); properties << '='; + WriteString (address.ntcp2->staticKey.ToBase64 (), properties); properties << ';'; + WriteString ("v", properties); properties << '='; + WriteString ("2", properties); properties << ';'; + } + + uint16_t size = htobe16 (properties.str ().size ()); + s.write ((char *)&size, sizeof (size)); + s.write (properties.str ().c_str (), properties.str ().size ()); + } + + // peers + uint8_t numPeers = 0; + s.write ((char *)&numPeers, sizeof (numPeers)); + + // properties + std::stringstream properties; + for (const auto& p : m_Properties) + { + WriteString (p.first, properties); + properties << '='; + WriteString (p.second, properties); + properties << ';'; + } + uint16_t size = htobe16 (properties.str ().size ()); + s.write ((char *)&size, sizeof (size)); + s.write (properties.str ().c_str (), properties.str ().size ()); + } + + void LocalRouterInfo::SetProperty (const std::string& key, const std::string& value) + { + m_Properties[key] = value; + } + + void LocalRouterInfo::DeleteProperty (const std::string& key) + { + m_Properties.erase (key); + } + + std::string LocalRouterInfo::GetProperty (const std::string& key) const + { + auto it = m_Properties.find (key); + if (it != m_Properties.end ()) + return it->second; + return ""; + } + + void LocalRouterInfo::WriteString (const std::string& str, std::ostream& s) const + { + uint8_t len = str.size (); + s.write ((char *)&len, 1); + s.write (str.c_str (), len); + } } } diff --git a/libi2pd/RouterInfo.h b/libi2pd/RouterInfo.h index bde7f305..f2f91395 100644 --- a/libi2pd/RouterInfo.h +++ b/libi2pd/RouterInfo.h @@ -181,6 +181,7 @@ namespace data std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); }; uint64_t GetTimestamp () const { return m_Timestamp; }; int GetVersion () const { return m_Version; }; + virtual void SetProperty (const std::string& key, const std::string& value) {}; Addresses& GetAddresses () { return *m_Addresses; }; // should be called for local RI only, otherwise must return shared_ptr std::shared_ptr GetNTCP2AddressWithStaticKey (const uint8_t * key) const; std::shared_ptr GetPublishedNTCP2V4Address () const; @@ -194,10 +195,6 @@ namespace data const boost::asio::ip::address& host = boost::asio::ip::address(), int port = 0, uint8_t caps = 0); bool AddIntroducer (const Introducer& introducer); bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); - void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only - void DeleteProperty (const std::string& key); // called from RouterContext only - std::string GetProperty (const std::string& key) const; // called from RouterContext only - void ClearProperties () { m_Properties.clear (); }; void SetUnreachableAddressesTransportCaps (uint8_t transports); // bitmask of AddressCaps void UpdateSupportedTransports (); bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; }; @@ -229,8 +226,7 @@ namespace data bool IsIntroducer (bool v4) const; uint8_t GetCaps () const { return m_Caps; }; - void SetCaps (uint8_t caps); - void SetCaps (const char * caps); + void SetCaps (uint8_t caps) { m_Caps = caps; }; void SetUnreachable (bool unreachable) { m_IsUnreachable = unreachable; }; bool IsUnreachable () const { return m_IsUnreachable; }; @@ -266,7 +262,7 @@ namespace data void UpdateBuffer (const uint8_t * buf, size_t len); void SetBufferLen (size_t len) { m_BufferLen = len; }; void RefreshTimestamp (); - void WriteToStream (std::ostream& s) const; + const Addresses& GetAddresses () const { return *m_Addresses; }; private: @@ -275,12 +271,10 @@ namespace data void ReadFromStream (std::istream& s); void ReadFromBuffer (bool verifySignature); size_t ReadString (char* str, size_t len, std::istream& s) const; - void WriteString (const std::string& str, std::ostream& s) const; void ExtractCaps (const char * value); uint8_t ExtractAddressCaps (const char * value) const; template std::shared_ptr GetAddress (Filter filter) const; - void UpdateCapsProperty (); private: @@ -290,7 +284,6 @@ namespace data size_t m_BufferLen; uint64_t m_Timestamp; boost::shared_ptr m_Addresses; // TODO: use std::shared_ptr and std::atomic_store for gcc >= 4.9 - std::map m_Properties; bool m_IsUpdated, m_IsUnreachable; CompatibleTransports m_SupportedTransports, m_ReachableTransports; uint8_t m_Caps; @@ -304,6 +297,22 @@ namespace data LocalRouterInfo () = default; void CreateBuffer (const PrivateKeys& privateKeys); + void UpdateCaps (uint8_t caps); + + void SetProperty (const std::string& key, const std::string& value) override; + void DeleteProperty (const std::string& key); + std::string GetProperty (const std::string& key) const; + void ClearProperties () { m_Properties.clear (); }; + + private: + + void WriteToStream (std::ostream& s) const; + void UpdateCapsProperty (); + void WriteString (const std::string& str, std::ostream& s) const; + + private: + + std::map m_Properties; }; } }