2020-05-22 13:18:41 +00:00
/*
2023-02-18 02:13:50 +00:00
* Copyright ( c ) 2013 - 2023 , The PurpleI2P Project
2020-05-22 13:18:41 +00:00
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
2013-11-24 23:10:27 +00:00
# ifndef LEASE_SET_H__
# define LEASE_SET_H__
# include <inttypes.h>
2013-11-29 12:52:09 +00:00
# include <string.h>
2014-01-01 23:19:03 +00:00
# include <vector>
2016-05-29 20:35:57 +00:00
# include <set>
2016-02-09 15:46:27 +00:00
# include <memory>
2013-12-20 03:05:45 +00:00
# include "Identity.h"
2016-09-03 15:46:47 +00:00
# include "Timestamp.h"
2019-02-06 18:36:03 +00:00
# include "I2PEndian.h"
2019-05-14 15:42:25 +00:00
# include "Blinding.h"
2013-11-24 23:10:27 +00:00
namespace i2p
{
2014-07-29 17:44:54 +00:00
namespace tunnel
{
2018-01-06 03:48:51 +00:00
class InboundTunnel ;
2014-07-29 17:44:54 +00:00
}
2013-11-24 23:10:27 +00:00
namespace data
2018-01-06 03:48:51 +00:00
{
2016-02-29 02:43:18 +00:00
const int LEASE_ENDDATE_THRESHOLD = 51000 ; // in milliseconds
2013-11-24 23:10:27 +00:00
struct Lease
{
2015-03-23 16:55:42 +00:00
IdentHash tunnelGateway ;
2013-11-24 23:10:27 +00:00
uint32_t tunnelID ;
2016-02-09 20:27:23 +00:00
uint64_t endDate ; // 0 means invalid
2019-04-10 16:04:19 +00:00
bool isUpdated ; // transient
2016-09-03 15:46:47 +00:00
/* return true if this lease expires within t millisecond + fudge factor */
bool ExpiresWithin ( const uint64_t t , const uint64_t fudge = 1000 ) const {
auto expire = i2p : : util : : GetMillisecondsSinceEpoch ( ) ;
if ( fudge ) expire + = rand ( ) % fudge ;
2017-04-09 12:52:42 +00:00
if ( endDate < expire ) return true ;
return ( endDate - expire ) < t ;
2016-09-03 15:46:47 +00:00
}
2018-01-06 03:48:51 +00:00
} ;
2016-02-09 20:27:23 +00:00
struct LeaseCmp
{
bool operator ( ) ( std : : shared_ptr < const Lease > l1 , std : : shared_ptr < const Lease > l2 ) const
2018-01-06 04:01:44 +00:00
{
2016-02-09 20:27:23 +00:00
if ( l1 - > tunnelID ! = l2 - > tunnelID )
2018-01-06 03:48:51 +00:00
return l1 - > tunnelID < l2 - > tunnelID ;
2016-02-09 20:27:23 +00:00
else
2018-01-06 03:48:51 +00:00
return l1 - > tunnelGateway < l2 - > tunnelGateway ;
2016-02-09 20:27:23 +00:00
} ;
2018-01-06 03:48:51 +00:00
} ;
2013-11-24 23:10:27 +00:00
2020-03-01 10:25:50 +00:00
typedef std : : function < bool ( const Lease & l ) > LeaseInspectFunc ;
2018-01-06 03:48:51 +00:00
2016-05-25 18:17:34 +00:00
const size_t MAX_LS_BUFFER_SIZE = 3072 ;
const size_t LEASE_SIZE = 44 ; // 32 + 4 + 8
2020-03-01 10:25:50 +00:00
const size_t LEASE2_SIZE = 40 ; // 32 + 4 + 4
2018-01-06 03:48:51 +00:00
const uint8_t MAX_NUM_LEASES = 16 ;
2019-01-02 19:19:10 +00:00
const uint8_t NETDB_STORE_TYPE_LEASESET = 1 ;
2013-11-24 23:10:27 +00:00
class LeaseSet : public RoutingDestination
{
public :
2016-02-08 00:45:06 +00:00
LeaseSet ( const uint8_t * buf , size_t len , bool storeLeases = true ) ;
2019-01-14 18:49:27 +00:00
virtual ~ LeaseSet ( ) { delete [ ] m_EncryptionKey ; delete [ ] m_Buffer ; } ;
2019-01-17 00:00:17 +00:00
virtual void Update ( const uint8_t * buf , size_t len , bool verifySignature = true ) ;
2020-04-29 21:11:48 +00:00
virtual bool IsNewer ( const uint8_t * buf , size_t len ) const ;
2018-01-06 03:48:51 +00:00
void PopulateLeases ( ) ; // from buffer
2014-10-03 19:08:41 +00:00
2014-07-29 18:31:55 +00:00
const uint8_t * GetBuffer ( ) const { return m_Buffer ; } ;
2018-01-06 03:48:51 +00:00
size_t GetBufferLen ( ) const { return m_BufferLen ; } ;
2015-04-08 14:34:16 +00:00
bool IsValid ( ) const { return m_IsValid ; } ;
2016-02-11 03:51:08 +00:00
const std : : vector < std : : shared_ptr < const Lease > > GetNonExpiredLeases ( bool withThreshold = true ) const ;
2020-03-01 10:25:50 +00:00
const std : : vector < std : : shared_ptr < const Lease > > GetNonExpiredLeasesExcluding ( LeaseInspectFunc exclude , bool withThreshold = true ) const ;
2014-01-15 01:57:33 +00:00
bool HasExpiredLeases ( ) const ;
2016-02-08 00:45:06 +00:00
bool IsExpired ( ) const ;
2016-02-10 03:42:01 +00:00
bool IsEmpty ( ) const { return m_Leases . empty ( ) ; } ;
2016-02-08 00:45:06 +00:00
uint64_t GetExpirationTime ( ) const { return m_ExpirationTime ; } ;
2016-09-03 15:46:47 +00:00
bool ExpiresSoon ( const uint64_t dlt = 1000 * 5 , const uint64_t fudge = 0 ) const ;
2018-01-06 03:48:51 +00:00
bool operator = = ( const LeaseSet & other ) const
{ return m_BufferLen = = other . m_BufferLen & & ! memcmp ( m_Buffer , other . m_Buffer , m_BufferLen ) ; } ;
2019-01-02 19:19:10 +00:00
virtual uint8_t GetStoreType ( ) const { return NETDB_STORE_TYPE_LEASESET ; } ;
2019-02-26 21:20:24 +00:00
virtual uint32_t GetPublishedTimestamp ( ) const { return 0 ; } ; // should be set for LeaseSet2 only
2020-03-01 10:25:50 +00:00
virtual std : : shared_ptr < const i2p : : crypto : : Verifier > GetTransientVerifier ( ) const { return nullptr ; } ;
2019-08-07 19:43:03 +00:00
virtual bool IsPublishedEncrypted ( ) const { return false ; } ;
2019-02-06 18:36:03 +00:00
2016-02-09 15:46:27 +00:00
// implements RoutingDestination
2017-11-02 18:50:57 +00:00
std : : shared_ptr < const IdentityEx > GetIdentity ( ) const { return m_Identity ; } ;
2021-08-31 22:51:40 +00:00
void Encrypt ( const uint8_t * data , uint8_t * encrypted ) const ;
2013-11-27 01:59:25 +00:00
bool IsDestination ( ) const { return true ; } ;
2014-07-22 00:14:11 +00:00
2018-12-21 20:00:03 +00:00
protected :
2019-01-09 17:47:47 +00:00
void UpdateLeasesBegin ( ) ;
void UpdateLeasesEnd ( ) ;
2019-01-02 20:40:48 +00:00
void UpdateLease ( const Lease & lease , uint64_t ts ) ;
2018-12-21 20:00:03 +00:00
// called from LeaseSet2
2019-01-09 17:47:47 +00:00
LeaseSet ( bool storeLeases ) ;
2018-12-21 20:00:03 +00:00
void SetBuffer ( const uint8_t * buf , size_t len ) ;
2020-03-01 10:25:50 +00:00
void SetBufferLen ( size_t len ) ;
2018-12-21 20:00:03 +00:00
void SetIdentity ( std : : shared_ptr < const IdentityEx > identity ) { m_Identity = identity ; } ;
void SetExpirationTime ( uint64_t t ) { m_ExpirationTime = t ; } ;
void SetIsValid ( bool isValid ) { m_IsValid = isValid ; } ;
2019-01-09 17:47:47 +00:00
bool IsStoreLeases ( ) const { return m_StoreLeases ; } ;
2018-12-21 20:00:03 +00:00
2014-07-22 00:14:11 +00:00
private :
2018-01-25 15:09:34 +00:00
void ReadFromBuffer ( bool readIdentity = true , bool verifySignature = true ) ;
2020-04-29 21:11:48 +00:00
virtual uint64_t ExtractExpirationTimestamp ( const uint8_t * buf , size_t len ) const ; // returns max expiration time
2018-01-06 03:48:51 +00:00
2013-11-24 23:10:27 +00:00
private :
2016-02-08 00:45:06 +00:00
bool m_IsValid , m_StoreLeases ; // we don't need to store leases for floodfill
2016-02-09 20:27:23 +00:00
std : : set < std : : shared_ptr < Lease > , LeaseCmp > m_Leases ;
2016-02-08 00:45:06 +00:00
uint64_t m_ExpirationTime ; // in milliseconds
2015-11-03 14:15:49 +00:00
std : : shared_ptr < const IdentityEx > m_Identity ;
2019-01-14 18:49:27 +00:00
uint8_t * m_EncryptionKey ;
2015-04-08 13:39:02 +00:00
uint8_t * m_Buffer ;
2014-07-29 17:44:54 +00:00
size_t m_BufferLen ;
2018-01-06 03:48:51 +00:00
} ;
2016-05-25 18:17:34 +00:00
2018-01-24 15:16:51 +00:00
/**
2022-05-20 16:56:05 +00:00
* validate lease set buffer signature and extract expiration timestamp
* @ returns true if the leaseset is well formed and signature is valid
2018-01-24 15:16:51 +00:00
*/
bool LeaseSetBufferValidate ( const uint8_t * ptr , size_t sz , uint64_t & expires ) ;
2019-01-02 19:19:10 +00:00
const uint8_t NETDB_STORE_TYPE_STANDARD_LEASESET2 = 3 ;
2019-01-08 16:26:50 +00:00
const uint8_t NETDB_STORE_TYPE_ENCRYPTED_LEASESET2 = 5 ;
2019-01-02 19:19:10 +00:00
const uint8_t NETDB_STORE_TYPE_META_LEASESET2 = 7 ;
2019-02-12 19:56:39 +00:00
const uint16_t LEASESET2_FLAG_OFFLINE_KEYS = 0x0001 ;
2020-03-01 10:25:50 +00:00
const uint16_t LEASESET2_FLAG_UNPUBLISHED_LEASESET = 0x0002 ;
2019-08-07 19:43:03 +00:00
const uint16_t LEASESET2_FLAG_PUBLISHED_ENCRYPTED = 0x0004 ;
2019-07-16 15:48:30 +00:00
2018-12-21 20:00:03 +00:00
class LeaseSet2 : public LeaseSet
{
public :
2023-02-18 02:13:50 +00:00
LeaseSet2 ( uint8_t storeType ) : LeaseSet ( true ) , m_StoreType ( storeType ) { } ; // for update
2020-02-12 16:09:20 +00:00
LeaseSet2 ( uint8_t storeType , const uint8_t * buf , size_t len , bool storeLeases = true , CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL ) ;
LeaseSet2 ( const uint8_t * buf , size_t len , std : : shared_ptr < const BlindedPublicKey > key , const uint8_t * secret = nullptr , CryptoKeyType preferredCrypto = CRYPTO_KEY_TYPE_ELGAMAL ) ; // store type 5, called from local netdb only
2018-12-26 20:27:32 +00:00
uint8_t GetStoreType ( ) const { return m_StoreType ; } ;
2019-02-26 21:20:24 +00:00
uint32_t GetPublishedTimestamp ( ) const { return m_PublishedTimestamp ; } ;
2019-07-16 15:48:30 +00:00
bool IsPublic ( ) const { return m_IsPublic ; } ;
2019-08-07 19:43:03 +00:00
bool IsPublishedEncrypted ( ) const { return m_IsPublishedEncrypted ; } ;
2019-02-06 18:36:03 +00:00
std : : shared_ptr < const i2p : : crypto : : Verifier > GetTransientVerifier ( ) const { return m_TransientVerifier ; } ;
2019-01-17 00:00:17 +00:00
void Update ( const uint8_t * buf , size_t len , bool verifySignature ) ;
2020-04-29 21:11:48 +00:00
bool IsNewer ( const uint8_t * buf , size_t len ) const ;
2019-02-06 18:36:03 +00:00
2019-01-09 17:47:47 +00:00
// implements RoutingDestination
2021-08-31 22:51:40 +00:00
void Encrypt ( const uint8_t * data , uint8_t * encrypted ) const ;
2019-12-19 20:59:15 +00:00
CryptoKeyType GetEncryptionType ( ) const { return m_EncryptionType ; } ;
2019-01-09 17:47:47 +00:00
2018-12-26 20:27:32 +00:00
private :
2019-02-01 17:55:13 +00:00
void ReadFromBuffer ( const uint8_t * buf , size_t len , bool readIdentity = true , bool verifySignature = true ) ;
2019-06-06 16:33:33 +00:00
void ReadFromBufferEncrypted ( const uint8_t * buf , size_t len , std : : shared_ptr < const BlindedPublicKey > key , const uint8_t * secret ) ;
2019-01-02 19:19:10 +00:00
size_t ReadStandardLS2TypeSpecificPart ( const uint8_t * buf , size_t len ) ;
size_t ReadMetaLS2TypeSpecificPart ( const uint8_t * buf , size_t len ) ;
2018-12-21 20:00:03 +00:00
2019-01-08 16:26:50 +00:00
template < typename Verifier >
bool VerifySignature ( Verifier & verifier , const uint8_t * buf , size_t len , size_t signatureOffset ) ;
2020-04-29 21:11:48 +00:00
uint64_t ExtractExpirationTimestamp ( const uint8_t * buf , size_t len ) const ;
uint64_t ExtractPublishedTimestamp ( const uint8_t * buf , size_t len , uint64_t & expiration ) const ;
2019-06-05 19:57:20 +00:00
size_t ExtractClientAuthData ( const uint8_t * buf , size_t len , const uint8_t * secret , const uint8_t * subcredential , uint8_t * authCookie ) const ; // subcredential is subcredential + timestamp, return length of autData without flag
2019-01-14 18:49:27 +00:00
2018-12-21 20:00:03 +00:00
private :
2020-03-01 10:25:50 +00:00
uint8_t m_StoreType ;
2019-02-27 20:52:47 +00:00
uint32_t m_PublishedTimestamp = 0 ;
2019-08-07 19:43:03 +00:00
bool m_IsPublic = true , m_IsPublishedEncrypted = false ;
2019-02-06 18:36:03 +00:00
std : : shared_ptr < i2p : : crypto : : Verifier > m_TransientVerifier ;
2020-02-12 16:09:20 +00:00
CryptoKeyType m_EncryptionType ;
2019-01-09 17:47:47 +00:00
std : : shared_ptr < i2p : : crypto : : CryptoKeyEncryptor > m_Encryptor ; // for standardLS2
2018-12-21 20:00:03 +00:00
} ;
2020-03-01 10:25:50 +00:00
// also called from Streaming.cpp
2019-02-06 18:36:03 +00:00
template < typename Verifier >
std : : shared_ptr < i2p : : crypto : : Verifier > ProcessOfflineSignature ( const Verifier & verifier , const uint8_t * buf , size_t len , size_t & offset )
{
if ( offset + 6 > = len ) return nullptr ;
const uint8_t * signedData = buf + offset ;
uint32_t expiresTimestamp = bufbe32toh ( buf + offset ) ; offset + = 4 ; // expires timestamp
if ( expiresTimestamp < i2p : : util : : GetSecondsSinceEpoch ( ) ) return nullptr ;
uint16_t keyType = bufbe16toh ( buf + offset ) ; offset + = 2 ;
std : : shared_ptr < i2p : : crypto : : Verifier > transientVerifier ( i2p : : data : : IdentityEx : : CreateVerifier ( keyType ) ) ;
if ( ! transientVerifier ) return nullptr ;
auto keyLen = transientVerifier - > GetPublicKeyLen ( ) ;
if ( offset + keyLen > = len ) return nullptr ;
transientVerifier - > SetPublicKey ( buf + offset ) ; offset + = keyLen ;
if ( offset + verifier - > GetSignatureLen ( ) > = len ) return nullptr ;
if ( ! verifier - > Verify ( signedData , keyLen + 6 , buf + offset ) ) return nullptr ;
2020-03-01 10:25:50 +00:00
offset + = verifier - > GetSignatureLen ( ) ;
2019-02-06 18:36:03 +00:00
return transientVerifier ;
}
//------------------------------------------------------------------------------------
2016-05-25 18:17:34 +00:00
class LocalLeaseSet
{
public :
LocalLeaseSet ( std : : shared_ptr < const IdentityEx > identity , const uint8_t * encryptionPublicKey , std : : vector < std : : shared_ptr < i2p : : tunnel : : InboundTunnel > > tunnels ) ;
2016-05-30 16:56:42 +00:00
LocalLeaseSet ( std : : shared_ptr < const IdentityEx > identity , const uint8_t * buf , size_t len ) ;
2019-01-10 16:52:34 +00:00
virtual ~ LocalLeaseSet ( ) { delete [ ] m_Buffer ; } ;
2016-05-25 18:17:34 +00:00
2019-01-10 16:52:34 +00:00
virtual uint8_t * GetBuffer ( ) const { return m_Buffer ; } ;
uint8_t * GetSignature ( ) { return GetBuffer ( ) + GetBufferLen ( ) - GetSignatureLen ( ) ; } ;
virtual size_t GetBufferLen ( ) const { return m_BufferLen ; } ;
2016-05-25 18:17:34 +00:00
size_t GetSignatureLen ( ) const { return m_Identity - > GetSignatureLen ( ) ; } ;
2018-01-06 03:48:51 +00:00
uint8_t * GetLeases ( ) { return m_Leases ; } ;
2016-05-25 18:17:34 +00:00
const IdentHash & GetIdentHash ( ) const { return m_Identity - > GetIdentHash ( ) ; } ;
2019-04-09 13:21:38 +00:00
std : : shared_ptr < const IdentityEx > GetIdentity ( ) const { return m_Identity ; } ;
2016-05-25 19:10:28 +00:00
bool IsExpired ( ) const ;
2016-05-30 16:56:42 +00:00
uint64_t GetExpirationTime ( ) const { return m_ExpirationTime ; } ;
void SetExpirationTime ( uint64_t expirationTime ) { m_ExpirationTime = expirationTime ; } ;
2018-01-06 03:48:51 +00:00
bool operator = = ( const LeaseSet & other ) const
2019-01-10 16:52:34 +00:00
{ return GetBufferLen ( ) = = other . GetBufferLen ( ) & & ! memcmp ( GetBuffer ( ) , other . GetBuffer ( ) , GetBufferLen ( ) ) ; } ;
2016-05-25 19:10:28 +00:00
2019-01-09 19:51:47 +00:00
virtual uint8_t GetStoreType ( ) const { return NETDB_STORE_TYPE_LEASESET ; } ;
2019-04-09 19:36:10 +00:00
virtual const IdentHash & GetStoreHash ( ) const { return GetIdentHash ( ) ; } ; // differ from ident hash for encrypted LeaseSet2
2019-04-10 16:04:19 +00:00
virtual std : : shared_ptr < const LocalLeaseSet > GetInnerLeaseSet ( ) const { return nullptr ; } ; // non-null for encrypted LeaseSet2
2019-01-09 19:51:47 +00:00
2016-05-25 18:17:34 +00:00
private :
2018-01-06 03:48:51 +00:00
2016-05-25 19:10:28 +00:00
uint64_t m_ExpirationTime ; // in milliseconds
2016-05-25 18:17:34 +00:00
std : : shared_ptr < const IdentityEx > m_Identity ;
2016-05-29 20:35:57 +00:00
uint8_t * m_Buffer , * m_Leases ;
2016-05-25 18:17:34 +00:00
size_t m_BufferLen ;
2018-01-06 03:48:51 +00:00
} ;
2019-01-09 19:51:47 +00:00
class LocalLeaseSet2 : public LocalLeaseSet
{
public :
2020-03-18 22:03:03 +00:00
struct KeySection
{
uint16_t keyType , keyLen ;
const uint8_t * encryptionPublicKey ;
2020-03-01 10:25:50 +00:00
} ;
2020-03-18 22:03:03 +00:00
typedef std : : vector < KeySection > KeySections ;
2020-03-01 10:25:50 +00:00
LocalLeaseSet2 ( uint8_t storeType , const i2p : : data : : PrivateKeys & keys ,
const KeySections & encryptionKeys ,
2021-04-01 17:37:21 +00:00
const std : : vector < std : : shared_ptr < i2p : : tunnel : : InboundTunnel > > & tunnels ,
2019-08-07 19:43:03 +00:00
bool isPublic , bool isPublishedEncrypted = false ) ;
2020-03-01 10:25:50 +00:00
LocalLeaseSet2 ( uint8_t storeType , std : : shared_ptr < const IdentityEx > identity , const uint8_t * buf , size_t len ) ; // from I2CP
2019-01-10 16:52:34 +00:00
virtual ~ LocalLeaseSet2 ( ) { delete [ ] m_Buffer ; } ;
2020-03-01 10:25:50 +00:00
2019-01-10 16:52:34 +00:00
uint8_t * GetBuffer ( ) const { return m_Buffer + 1 ; } ;
size_t GetBufferLen ( ) const { return m_BufferLen ; } ;
2019-01-09 19:51:47 +00:00
2019-01-10 16:52:34 +00:00
uint8_t GetStoreType ( ) const { return m_Buffer [ 0 ] ; } ;
2019-01-09 19:51:47 +00:00
2019-04-10 16:04:19 +00:00
protected :
LocalLeaseSet2 ( std : : shared_ptr < const IdentityEx > identity ) : LocalLeaseSet ( identity , nullptr , 0 ) , m_Buffer ( nullptr ) , m_BufferLen ( 0 ) { } ; // called from LocalEncryptedLeaseSet2
protected :
2019-01-09 19:51:47 +00:00
2019-01-10 16:52:34 +00:00
uint8_t * m_Buffer ; // 1 byte store type + actual buffer
size_t m_BufferLen ;
2019-04-10 16:04:19 +00:00
} ;
2019-07-12 19:37:32 +00:00
2019-07-16 20:31:17 +00:00
const int ENCRYPTED_LEASESET_AUTH_TYPE_NONE = 0 ;
const int ENCRYPTED_LEASESET_AUTH_TYPE_DH = 1 ;
const int ENCRYPTED_LEASESET_AUTH_TYPE_PSK = 2 ;
2019-04-10 16:04:19 +00:00
2020-03-01 10:25:50 +00:00
typedef i2p : : data : : Tag < 32 > AuthPublicKey ;
2019-07-12 19:37:32 +00:00
2019-07-16 20:31:17 +00:00
class LocalEncryptedLeaseSet2 : public LocalLeaseSet2
{
2019-07-12 19:37:32 +00:00
public :
2020-03-01 10:25:50 +00:00
LocalEncryptedLeaseSet2 ( std : : shared_ptr < const LocalLeaseSet2 > ls , const i2p : : data : : PrivateKeys & keys , int authType = ENCRYPTED_LEASESET_AUTH_TYPE_NONE , std : : shared_ptr < std : : vector < AuthPublicKey > > authKeys = nullptr ) ;
2019-04-10 16:04:19 +00:00
2019-04-11 18:06:53 +00:00
LocalEncryptedLeaseSet2 ( std : : shared_ptr < const IdentityEx > identity , const uint8_t * buf , size_t len ) ; // from I2CP
2019-04-10 16:04:19 +00:00
const IdentHash & GetStoreHash ( ) const { return m_StoreHash ; } ;
std : : shared_ptr < const LocalLeaseSet > GetInnerLeaseSet ( ) const { return m_InnerLeaseSet ; } ;
2019-07-12 19:37:32 +00:00
private :
2019-08-26 00:51:15 +00:00
void CreateClientAuthData ( const uint8_t * subcredential , int authType , std : : shared_ptr < std : : vector < AuthPublicKey > > authKeys , const uint8_t * authCookie , uint8_t * authData ) const ;
2019-07-12 19:37:32 +00:00
2019-04-10 16:04:19 +00:00
private :
IdentHash m_StoreHash ;
std : : shared_ptr < const LocalLeaseSet2 > m_InnerLeaseSet ;
2019-01-09 19:51:47 +00:00
} ;
2018-01-06 03:48:51 +00:00
}
}
2013-11-24 23:10:27 +00:00
# endif