mirror of
https://github.com/PurpleI2P/i2pd.git
synced 2025-01-23 05:06:31 +00:00
generic encryption for RoutingDestination
This commit is contained in:
parent
9fa67b0e0a
commit
81658d2ff9
@ -11,11 +11,9 @@ namespace crypto
|
|||||||
memcpy (m_PublicKey, pub, 256);
|
memcpy (m_PublicKey, pub, 256);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted)
|
void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx)
|
||||||
{
|
{
|
||||||
BN_CTX * ctx = BN_CTX_new ();
|
|
||||||
ElGamalEncrypt (m_PublicKey, data, encrypted, ctx, true);
|
ElGamalEncrypt (m_PublicKey, data, encrypted, ctx, true);
|
||||||
BN_CTX_free (ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ElGamalDecryptor::ElGamalDecryptor (const uint8_t * priv)
|
ElGamalDecryptor::ElGamalDecryptor (const uint8_t * priv)
|
||||||
@ -23,11 +21,9 @@ namespace crypto
|
|||||||
memcpy (m_PrivateKey, priv, 256);
|
memcpy (m_PrivateKey, priv, 256);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ElGamalDecryptor::Decrypt (const uint8_t * encrypted, uint8_t * data)
|
bool ElGamalDecryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx)
|
||||||
{
|
{
|
||||||
BN_CTX * ctx = BN_CTX_new ();
|
return ElGamalDecrypt (m_PrivateKey, encrypted, data, ctx, true);
|
||||||
ElGamalDecrypt (m_PrivateKey, encrypted, data, ctx, true);
|
|
||||||
BN_CTX_free (ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ECIESP256Encryptor::ECIESP256Encryptor (const uint8_t * pub)
|
ECIESP256Encryptor::ECIESP256Encryptor (const uint8_t * pub)
|
||||||
@ -47,14 +43,10 @@ namespace crypto
|
|||||||
if (m_PublicKey) EC_POINT_free (m_PublicKey);
|
if (m_PublicKey) EC_POINT_free (m_PublicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ECIESP256Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted)
|
void ECIESP256Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx)
|
||||||
{
|
{
|
||||||
if (m_Curve && m_PublicKey)
|
if (m_Curve && m_PublicKey)
|
||||||
{
|
|
||||||
BN_CTX * ctx = BN_CTX_new ();
|
|
||||||
ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted, ctx);
|
ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted, ctx);
|
||||||
BN_CTX_free (ctx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ECIESP256Decryptor::ECIESP256Decryptor (const uint8_t * priv)
|
ECIESP256Decryptor::ECIESP256Decryptor (const uint8_t * priv)
|
||||||
@ -69,14 +61,11 @@ namespace crypto
|
|||||||
if (m_PrivateKey) BN_free (m_PrivateKey);
|
if (m_PrivateKey) BN_free (m_PrivateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ECIESP256Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data)
|
bool ECIESP256Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx)
|
||||||
{
|
{
|
||||||
if (m_Curve && m_PrivateKey)
|
if (m_Curve && m_PrivateKey)
|
||||||
{
|
return ECIESDecrypt (m_Curve, m_PrivateKey, encrypted, data, ctx);
|
||||||
BN_CTX * ctx = BN_CTX_new ();
|
return false;;
|
||||||
ECIESDecrypt (m_Curve, m_PrivateKey, encrypted, data, ctx);
|
|
||||||
BN_CTX_free (ctx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreateECIESP256RandomKeys (uint8_t * priv, uint8_t * pub)
|
void CreateECIESP256RandomKeys (uint8_t * priv, uint8_t * pub)
|
||||||
|
@ -13,7 +13,7 @@ namespace crypto
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
virtual ~CryptoKeyEncryptor () {};
|
virtual ~CryptoKeyEncryptor () {};
|
||||||
virtual void Encrypt (const uint8_t * data, uint8_t * encrypted); // 222 bytes data, 512 bytes encrypted
|
virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) = 0; // 222 bytes data, 512 bytes encrypted
|
||||||
};
|
};
|
||||||
|
|
||||||
class CryptoKeyDecryptor
|
class CryptoKeyDecryptor
|
||||||
@ -21,40 +21,40 @@ namespace crypto
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
virtual ~CryptoKeyDecryptor () {};
|
virtual ~CryptoKeyDecryptor () {};
|
||||||
virtual void Decrypt (const uint8_t * encrypted, uint8_t * data); // 512 bytes encrypted, 222 bytes data
|
virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) = 0; // 512 bytes encrypted, 222 bytes data
|
||||||
};
|
};
|
||||||
|
|
||||||
class ElGamalEncryptor // for destination
|
class ElGamalEncryptor: public CryptoKeyEncryptor // for destination
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
ElGamalEncryptor (const uint8_t * pub);
|
ElGamalEncryptor (const uint8_t * pub);
|
||||||
void Encrypt (const uint8_t * data, uint8_t * encrypted);
|
void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
uint8_t m_PublicKey[256];
|
uint8_t m_PublicKey[256];
|
||||||
};
|
};
|
||||||
|
|
||||||
class ElGamalDecryptor // for destination
|
class ElGamalDecryptor: public CryptoKeyDecryptor // for destination
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
ElGamalDecryptor (const uint8_t * priv);
|
ElGamalDecryptor (const uint8_t * priv);
|
||||||
void Decrypt (const uint8_t * encrypted, uint8_t * data);
|
bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
uint8_t m_PrivateKey[256];
|
uint8_t m_PrivateKey[256];
|
||||||
};
|
};
|
||||||
|
|
||||||
class ECIESP256Encryptor
|
class ECIESP256Encryptor: public CryptoKeyEncryptor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
ECIESP256Encryptor (const uint8_t * pub);
|
ECIESP256Encryptor (const uint8_t * pub);
|
||||||
~ECIESP256Encryptor ();
|
~ECIESP256Encryptor ();
|
||||||
void Encrypt (const uint8_t * data, uint8_t * encrypted);
|
void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
@ -63,13 +63,13 @@ namespace crypto
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class ECIESP256Decryptor
|
class ECIESP256Decryptor: public CryptoKeyDecryptor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
ECIESP256Decryptor (const uint8_t * priv);
|
ECIESP256Decryptor (const uint8_t * priv);
|
||||||
~ECIESP256Decryptor ();
|
~ECIESP256Decryptor ();
|
||||||
void Decrypt (const uint8_t * encrypted, uint8_t * data);
|
bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "Crypto.h"
|
#include "Crypto.h"
|
||||||
#include "CryptoKey.h"
|
|
||||||
#include "I2PEndian.h"
|
#include "I2PEndian.h"
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include "Identity.h"
|
#include "Identity.h"
|
||||||
@ -443,6 +442,23 @@ namespace data
|
|||||||
m_Verifier = nullptr;
|
m_Verifier = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<i2p::crypto::CryptoKeyEncryptor> IdentityEx::CreateEncryptor (const uint8_t * key) const
|
||||||
|
{
|
||||||
|
if (!key) key = GetEncryptionPublicKey (); // use publicKey
|
||||||
|
switch (GetCryptoKeyType ())
|
||||||
|
{
|
||||||
|
case CRYPTO_KEY_TYPE_ELGAMAL:
|
||||||
|
return std::make_shared<i2p::crypto::ElGamalEncryptor>(key);
|
||||||
|
break;
|
||||||
|
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC:
|
||||||
|
return std::make_shared<i2p::crypto::ECIESP256Encryptor>(key);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)GetCryptoKeyType ());
|
||||||
|
};
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
PrivateKeys& PrivateKeys::operator=(const Keys& keys)
|
PrivateKeys& PrivateKeys::operator=(const Keys& keys)
|
||||||
{
|
{
|
||||||
m_Public = std::make_shared<IdentityEx>(Identity (keys));
|
m_Public = std::make_shared<IdentityEx>(Identity (keys));
|
||||||
@ -568,6 +584,23 @@ namespace data
|
|||||||
return nullptr; // TODO: implement me
|
return nullptr; // TODO: implement me
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<i2p::crypto::CryptoKeyDecryptor> PrivateKeys::CreateDecryptor (const uint8_t * key) const
|
||||||
|
{
|
||||||
|
if (!key) key = m_PrivateKey; // use privateKey
|
||||||
|
switch (m_Public->GetCryptoKeyType ())
|
||||||
|
{
|
||||||
|
case CRYPTO_KEY_TYPE_ELGAMAL:
|
||||||
|
return std::make_shared<i2p::crypto::ElGamalDecryptor>(key);
|
||||||
|
break;
|
||||||
|
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC:
|
||||||
|
return std::make_shared<i2p::crypto::ECIESP256Decryptor>(key);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)m_Public->GetCryptoKeyType ());
|
||||||
|
};
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
PrivateKeys PrivateKeys::CreateRandomKeys (SigningKeyType type, CryptoKeyType cryptoType)
|
PrivateKeys PrivateKeys::CreateRandomKeys (SigningKeyType type, CryptoKeyType cryptoType)
|
||||||
{
|
{
|
||||||
if (type != SIGNING_KEY_TYPE_DSA_SHA1)
|
if (type != SIGNING_KEY_TYPE_DSA_SHA1)
|
||||||
@ -627,7 +660,7 @@ namespace data
|
|||||||
case CRYPTO_KEY_TYPE_ELGAMAL:
|
case CRYPTO_KEY_TYPE_ELGAMAL:
|
||||||
i2p::crypto::GenerateElGamalKeyPair(priv, pub);
|
i2p::crypto::GenerateElGamalKeyPair(priv, pub);
|
||||||
break;
|
break;
|
||||||
case CRYPTO_KEY_TYPE_ECICS_P256_SHA256_AES256CBC:
|
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC:
|
||||||
i2p::crypto::CreateECIESP256RandomKeys (priv, pub);
|
i2p::crypto::CreateECIESP256RandomKeys (priv, pub);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include "Base.h"
|
#include "Base.h"
|
||||||
#include "Signature.h"
|
#include "Signature.h"
|
||||||
|
#include "CryptoKey.h"
|
||||||
|
|
||||||
namespace i2p
|
namespace i2p
|
||||||
{
|
{
|
||||||
@ -52,7 +53,7 @@ namespace data
|
|||||||
const size_t DEFAULT_IDENTITY_SIZE = sizeof (Identity); // 387 bytes
|
const size_t DEFAULT_IDENTITY_SIZE = sizeof (Identity); // 387 bytes
|
||||||
|
|
||||||
const uint16_t CRYPTO_KEY_TYPE_ELGAMAL = 0;
|
const uint16_t CRYPTO_KEY_TYPE_ELGAMAL = 0;
|
||||||
const uint16_t CRYPTO_KEY_TYPE_ECICS_P256_SHA256_AES256CBC = 65280; // TODO: change to actual code
|
const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC = 65280; // TODO: change to actual code
|
||||||
|
|
||||||
const uint16_t SIGNING_KEY_TYPE_DSA_SHA1 = 0;
|
const uint16_t SIGNING_KEY_TYPE_DSA_SHA1 = 0;
|
||||||
const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA256_P256 = 1;
|
const uint16_t SIGNING_KEY_TYPE_ECDSA_SHA256_P256 = 1;
|
||||||
@ -88,11 +89,12 @@ namespace data
|
|||||||
size_t ToBuffer (uint8_t * buf, size_t len) const;
|
size_t ToBuffer (uint8_t * buf, size_t len) const;
|
||||||
size_t FromBase64(const std::string& s);
|
size_t FromBase64(const std::string& s);
|
||||||
std::string ToBase64 () const;
|
std::string ToBase64 () const;
|
||||||
const Identity& GetStandardIdentity () const { return m_StandardIdentity; };
|
const Identity& GetStandardIdentity () const { return m_StandardIdentity; };
|
||||||
|
|
||||||
const IdentHash& GetIdentHash () const { return m_IdentHash; };
|
const IdentHash& GetIdentHash () const { return m_IdentHash; };
|
||||||
const uint8_t * GetEncryptionPublicKey () const { return m_StandardIdentity.publicKey; };
|
const uint8_t * GetEncryptionPublicKey () const { return m_StandardIdentity.publicKey; };
|
||||||
uint8_t * GetEncryptionPublicKeyBuffer () { return m_StandardIdentity.publicKey; };
|
uint8_t * GetEncryptionPublicKeyBuffer () { return m_StandardIdentity.publicKey; };
|
||||||
|
std::shared_ptr<i2p::crypto::CryptoKeyEncryptor> CreateEncryptor (const uint8_t * key) const;
|
||||||
size_t GetFullLen () const { return m_ExtendedLen + DEFAULT_IDENTITY_SIZE; };
|
size_t GetFullLen () const { return m_ExtendedLen + DEFAULT_IDENTITY_SIZE; };
|
||||||
size_t GetSigningPublicKeyLen () const;
|
size_t GetSigningPublicKeyLen () const;
|
||||||
size_t GetSigningPrivateKeyLen () const;
|
size_t GetSigningPrivateKeyLen () const;
|
||||||
@ -136,7 +138,7 @@ namespace data
|
|||||||
const uint8_t * GetPrivateKey () const { return m_PrivateKey; };
|
const uint8_t * GetPrivateKey () const { return m_PrivateKey; };
|
||||||
const uint8_t * GetSigningPrivateKey () const { return m_SigningPrivateKey; };
|
const uint8_t * GetSigningPrivateKey () const { return m_SigningPrivateKey; };
|
||||||
uint8_t * GetPadding();
|
uint8_t * GetPadding();
|
||||||
void RecalculateIdentHash(uint8_t * buf=nullptr) { m_Public->RecalculateIdentHash(buf); }
|
void RecalculateIdentHash(uint8_t * buf=nullptr) { m_Public->RecalculateIdentHash(buf); }
|
||||||
void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
|
void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
|
||||||
|
|
||||||
size_t GetFullLen () const { return m_Public->GetFullLen () + 256 + m_Public->GetSigningPrivateKeyLen (); };
|
size_t GetFullLen () const { return m_Public->GetFullLen () + 256 + m_Public->GetSigningPrivateKeyLen (); };
|
||||||
@ -146,6 +148,8 @@ namespace data
|
|||||||
size_t FromBase64(const std::string& s);
|
size_t FromBase64(const std::string& s);
|
||||||
std::string ToBase64 () const;
|
std::string ToBase64 () const;
|
||||||
|
|
||||||
|
std::shared_ptr<i2p::crypto::CryptoKeyDecryptor> CreateDecryptor (const uint8_t * key) const;
|
||||||
|
|
||||||
static PrivateKeys CreateRandomKeys (SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1, CryptoKeyType cryptoType = CRYPTO_KEY_TYPE_ELGAMAL);
|
static PrivateKeys CreateRandomKeys (SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1, CryptoKeyType cryptoType = CRYPTO_KEY_TYPE_ELGAMAL);
|
||||||
static void GenerateCryptoKeyPair (CryptoKeyType type, uint8_t * priv, uint8_t * pub); // priv and pub are 256 bytes long
|
static void GenerateCryptoKeyPair (CryptoKeyType type, uint8_t * priv, uint8_t * pub); // priv and pub are 256 bytes long
|
||||||
|
|
||||||
@ -187,7 +191,8 @@ namespace data
|
|||||||
virtual ~RoutingDestination () {};
|
virtual ~RoutingDestination () {};
|
||||||
|
|
||||||
virtual std::shared_ptr<const IdentityEx> GetIdentity () const = 0;
|
virtual std::shared_ptr<const IdentityEx> GetIdentity () const = 0;
|
||||||
virtual const uint8_t * GetEncryptionPublicKey () const = 0;
|
virtual const uint8_t * GetEncryptionPublicKey () const = 0; // deprecated
|
||||||
|
virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) = 0; // encrypt data for
|
||||||
virtual bool IsDestination () const = 0; // for garlic
|
virtual bool IsDestination () const = 0; // for garlic
|
||||||
|
|
||||||
const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); };
|
const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); };
|
||||||
|
@ -208,6 +208,13 @@ namespace data
|
|||||||
return ts > m_ExpirationTime;
|
return ts > m_ExpirationTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LeaseSet::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx)
|
||||||
|
{
|
||||||
|
auto encryptor = m_Identity->CreateEncryptor (m_EncryptionKey);
|
||||||
|
if (encryptor)
|
||||||
|
encryptor->Encrypt (data, encrypted, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
LocalLeaseSet::LocalLeaseSet (std::shared_ptr<const IdentityEx> identity, const uint8_t * encryptionPublicKey, std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels):
|
LocalLeaseSet::LocalLeaseSet (std::shared_ptr<const IdentityEx> identity, const uint8_t * encryptionPublicKey, std::vector<std::shared_ptr<i2p::tunnel::InboundTunnel> > tunnels):
|
||||||
m_ExpirationTime (0), m_Identity (identity)
|
m_ExpirationTime (0), m_Identity (identity)
|
||||||
{
|
{
|
||||||
|
@ -76,7 +76,8 @@ namespace data
|
|||||||
|
|
||||||
// implements RoutingDestination
|
// implements RoutingDestination
|
||||||
std::shared_ptr<const IdentityEx> GetIdentity () const { return m_Identity; };
|
std::shared_ptr<const IdentityEx> GetIdentity () const { return m_Identity; };
|
||||||
const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionKey; };
|
const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionKey; }; // deprecated
|
||||||
|
void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx);
|
||||||
bool IsDestination () const { return true; };
|
bool IsDestination () const { return true; };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -828,5 +828,11 @@ namespace data
|
|||||||
m_Profile = GetRouterProfile (GetIdentHash ());
|
m_Profile = GetRouterProfile (GetIdentHash ());
|
||||||
return m_Profile;
|
return m_Profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RouterInfo::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx)
|
||||||
|
{
|
||||||
|
// TODO: we always assume ElGamal for RouterInfo, might change later
|
||||||
|
i2p::crypto::ElGamalEncrypt (m_RouterIdentity->GetEncryptionPublicKey (), data, encrypted, ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,12 +181,14 @@ namespace data
|
|||||||
void DeleteBuffer () { delete[] m_Buffer; m_Buffer = nullptr; };
|
void DeleteBuffer () { delete[] m_Buffer; m_Buffer = nullptr; };
|
||||||
bool IsNewer (const uint8_t * buf, size_t len) const;
|
bool IsNewer (const uint8_t * buf, size_t len) const;
|
||||||
|
|
||||||
/** return true if we are in a router family and the signature is valid */
|
/** return true if we are in a router family and the signature is valid */
|
||||||
bool IsFamily(const std::string & fam) const;
|
bool IsFamily(const std::string & fam) const;
|
||||||
|
|
||||||
// implements RoutingDestination
|
// implements RoutingDestination
|
||||||
std::shared_ptr<const IdentityEx> GetIdentity () const { return m_RouterIdentity; };
|
std::shared_ptr<const IdentityEx> GetIdentity () const { return m_RouterIdentity; };
|
||||||
const uint8_t * GetEncryptionPublicKey () const { return m_RouterIdentity->GetStandardIdentity ().publicKey; };
|
const uint8_t * GetEncryptionPublicKey () const { return m_RouterIdentity->GetStandardIdentity ().publicKey; }; // deprecated
|
||||||
|
void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx);
|
||||||
|
|
||||||
bool IsDestination () const { return false; };
|
bool IsDestination () const { return false; };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user