mirror of
https://github.com/espressif/openthread.git
synced 2026-07-29 15:17:47 +00:00
[netdiag] add support for Vendor App URL TLV (#9816)
This commit contains the following: - Adds support for the Vendor App URL TLV. - Includes new public OT APIs and configurations to get and set the TLV value. - Enables use of the TLV in the CLI `networkdiagnostic` command. - Updates `test-020-net-diag-vendor-info.py` to validate the new TLV.
This commit is contained in:
@@ -53,7 +53,7 @@ extern "C" {
|
||||
* @note This number versions both OpenThread platform and user APIs.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_API_VERSION (395)
|
||||
#define OPENTHREAD_API_VERSION (396)
|
||||
|
||||
/**
|
||||
* @addtogroup api-instance
|
||||
|
||||
@@ -95,13 +95,14 @@ enum
|
||||
OT_NETWORK_DIAGNOSTIC_TLV_ANSWER = 32, ///< Answer TLV
|
||||
OT_NETWORK_DIAGNOSTIC_TLV_QUERY_ID = 33, ///< Query ID TLV
|
||||
OT_NETWORK_DIAGNOSTIC_TLV_MLE_COUNTERS = 34, ///< MLE Counters TLV
|
||||
|
||||
OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_APP_URL = 35, ///< Vendor App URL TLV
|
||||
};
|
||||
|
||||
#define OT_NETWORK_DIAGNOSTIC_MAX_VENDOR_NAME_TLV_LENGTH 32 ///< Max length of Vendor Name TLV.
|
||||
#define OT_NETWORK_DIAGNOSTIC_MAX_VENDOR_MODEL_TLV_LENGTH 32 ///< Max length of Vendor Model TLV.
|
||||
#define OT_NETWORK_DIAGNOSTIC_MAX_VENDOR_SW_VERSION_TLV_LENGTH 16 ///< Max length of Vendor SW Version TLV.
|
||||
#define OT_NETWORK_DIAGNOSTIC_MAX_THREAD_STACK_VERSION_TLV_LENGTH 64 ///< Max length of Thread Stack Version TLV.
|
||||
#define OT_NETWORK_DIAGNOSTIC_MAX_VENDOR_APP_URL_TLV_LENGTH 96 ///< Max length of Vendor App URL TLV.
|
||||
|
||||
typedef uint16_t otNetworkDiagIterator; ///< Used to iterate through Network Diagnostic TLV.
|
||||
|
||||
@@ -294,6 +295,7 @@ typedef struct otNetworkDiagTlv
|
||||
char mVendorModel[OT_NETWORK_DIAGNOSTIC_MAX_VENDOR_MODEL_TLV_LENGTH + 1];
|
||||
char mVendorSwVersion[OT_NETWORK_DIAGNOSTIC_MAX_VENDOR_SW_VERSION_TLV_LENGTH + 1];
|
||||
char mThreadStackVersion[OT_NETWORK_DIAGNOSTIC_MAX_THREAD_STACK_VERSION_TLV_LENGTH + 1];
|
||||
char mVendorAppUrl[OT_NETWORK_DIAGNOSTIC_MAX_VENDOR_APP_URL_TLV_LENGTH + 1];
|
||||
struct
|
||||
{
|
||||
uint8_t mCount;
|
||||
@@ -428,6 +430,16 @@ const char *otThreadGetVendorModel(otInstance *aInstance);
|
||||
*/
|
||||
const char *otThreadGetVendorSwVersion(otInstance *aInstance);
|
||||
|
||||
/**
|
||||
* Get the vendor app URL string.
|
||||
*
|
||||
* @param[in] aInstance A pointer to an OpenThread instance.
|
||||
*
|
||||
* @returns The vendor app URL string.
|
||||
*
|
||||
*/
|
||||
const char *otThreadGetVendorAppUrl(otInstance *aInstance);
|
||||
|
||||
/**
|
||||
* Set the vendor name string.
|
||||
*
|
||||
@@ -479,6 +491,23 @@ otError otThreadSetVendorModel(otInstance *aInstance, const char *aVendorModel);
|
||||
*/
|
||||
otError otThreadSetVendorSwVersion(otInstance *aInstance, const char *aVendorSwVersion);
|
||||
|
||||
/**
|
||||
* Set the vendor app URL string.
|
||||
*
|
||||
* Requires `OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE`.
|
||||
*
|
||||
* @p aVendorAppUrl should be UTF8 with max length of 64 chars (`MAX_VENDOR_APPL_URL_TLV_LENGTH`). Maximum length
|
||||
* does not include the null `\0` character.
|
||||
*
|
||||
* @param[in] aInstance A pointer to an OpenThread instance.
|
||||
* @param[in] aVendorAppUrl The vendor app URL string.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Successfully set the vendor app URL string.
|
||||
* @retval OT_ERROR_INVALID_ARGS @p aVendorAppUrl is not valid (too long or not UTF8).
|
||||
*
|
||||
*/
|
||||
otError otThreadSetVendorAppUrl(otInstance *aInstance, const char *aVendorAppUrl);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*
|
||||
|
||||
+37
-3
@@ -7682,7 +7682,7 @@ template <> otError Interpreter::Process<Cmd("vendor")>(Arg aArgs[])
|
||||
error = ProcessGet(aArgs, otThreadGetVendorName);
|
||||
#else
|
||||
/**
|
||||
* @cli vendor name (name)
|
||||
* @cli vendor name (set)
|
||||
* @code
|
||||
* vendor name nest
|
||||
* Done
|
||||
@@ -7712,7 +7712,7 @@ template <> otError Interpreter::Process<Cmd("vendor")>(Arg aArgs[])
|
||||
error = ProcessGet(aArgs, otThreadGetVendorModel);
|
||||
#else
|
||||
/**
|
||||
* @cli vendor model (name)
|
||||
* @cli vendor model (set)
|
||||
* @code
|
||||
* vendor model Hub\ Max
|
||||
* Done
|
||||
@@ -7742,7 +7742,7 @@ template <> otError Interpreter::Process<Cmd("vendor")>(Arg aArgs[])
|
||||
error = ProcessGet(aArgs, otThreadGetVendorSwVersion);
|
||||
#else
|
||||
/**
|
||||
* @cli vendor swversion (version)
|
||||
* @cli vendor swversion (set)
|
||||
* @code
|
||||
* vendor swversion Marble3.5.1
|
||||
* Done
|
||||
@@ -7754,6 +7754,36 @@ template <> otError Interpreter::Process<Cmd("vendor")>(Arg aArgs[])
|
||||
error = ProcessGetSet(aArgs, otThreadGetVendorSwVersion, otThreadSetVendorSwVersion);
|
||||
#endif
|
||||
}
|
||||
/**
|
||||
* @cli vendor appurl
|
||||
* @code
|
||||
* vendor appurl
|
||||
* http://www.example.com
|
||||
* Done
|
||||
* @endcode
|
||||
* @par api_copy
|
||||
* #otThreadGetVendorAppUrl
|
||||
*/
|
||||
else if (aArgs[0] == "appurl")
|
||||
{
|
||||
aArgs++;
|
||||
|
||||
#if !OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE
|
||||
error = ProcessGet(aArgs, otThreadGetVendorAppUrl);
|
||||
#else
|
||||
/**
|
||||
* @cli vendor appurl (set)
|
||||
* @code
|
||||
* vendor appurl http://www.example.com
|
||||
* Done
|
||||
* @endcode
|
||||
* @par api_copy
|
||||
* #otThreadSetVendorAppUrl
|
||||
* @cparam vendor appurl @ca{url}
|
||||
*/
|
||||
error = ProcessGetSet(aArgs, otThreadGetVendorAppUrl, otThreadSetVendorAppUrl);
|
||||
#endif
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
@@ -7829,6 +7859,7 @@ template <> otError Interpreter::Process<Cmd("networkdiagnostic")>(Arg aArgs[])
|
||||
* - `28`: Thread Stack Version TLV (version identifier as UTF-8 string for Thread stack codebase/commit/version)
|
||||
* - `29`: Child TLV
|
||||
* - `34`: MLE Counters TLV
|
||||
* - `35`: Vendor App URL TLV
|
||||
* @par
|
||||
* Sends a network diagnostic request to retrieve specified Type Length Values (TLVs)
|
||||
* for the specified addresses(es).
|
||||
@@ -7996,6 +8027,9 @@ void Interpreter::HandleDiagnosticGetResponse(otError aError,
|
||||
case OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_SW_VERSION:
|
||||
OutputLine("Vendor SW Version: %s", diagTlv.mData.mVendorSwVersion);
|
||||
break;
|
||||
case OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_APP_URL:
|
||||
OutputLine("Vendor App URL: %s", diagTlv.mData.mVendorAppUrl);
|
||||
break;
|
||||
case OT_NETWORK_DIAGNOSTIC_TLV_THREAD_STACK_VERSION:
|
||||
OutputLine("Thread Stack Version: %s", diagTlv.mData.mThreadStackVersion);
|
||||
break;
|
||||
|
||||
@@ -89,6 +89,11 @@ const char *otThreadGetVendorSwVersion(otInstance *aInstance)
|
||||
return AsCoreType(aInstance).Get<NetworkDiagnostic::Server>().GetVendorSwVersion();
|
||||
}
|
||||
|
||||
const char *otThreadGetVendorAppUrl(otInstance *aInstance)
|
||||
{
|
||||
return AsCoreType(aInstance).Get<NetworkDiagnostic::Server>().GetVendorAppUrl();
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE
|
||||
otError otThreadSetVendorName(otInstance *aInstance, const char *aVendorName)
|
||||
{
|
||||
@@ -104,4 +109,9 @@ otError otThreadSetVendorSwVersion(otInstance *aInstance, const char *aVendorSwV
|
||||
{
|
||||
return AsCoreType(aInstance).Get<NetworkDiagnostic::Server>().SetVendorSwVersion(aVendorSwVersion);
|
||||
}
|
||||
|
||||
otError otThreadSetVendorAppUrl(otInstance *aInstance, const char *aVendorAppUrl)
|
||||
{
|
||||
return AsCoreType(aInstance).Get<NetworkDiagnostic::Server>().SetVendorAppUrl(aVendorAppUrl);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -75,6 +75,16 @@
|
||||
#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_SW_VERSION ""
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_NET_DIAG_VENDOR_APP_URL
|
||||
*
|
||||
* Specifies the default Vendor App URL string.
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_NET_DIAG_VENDOR_APP_URL
|
||||
#define OPENTHREAD_CONFIG_NET_DIAG_VENDOR_APP_URL ""
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE
|
||||
*
|
||||
|
||||
@@ -61,6 +61,7 @@ namespace NetworkDiagnostic {
|
||||
const char Server::kVendorName[] = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_NAME;
|
||||
const char Server::kVendorModel[] = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_MODEL;
|
||||
const char Server::kVendorSwVersion[] = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_SW_VERSION;
|
||||
const char Server::kVendorAppUrl[] = OPENTHREAD_CONFIG_NET_DIAG_VENDOR_APP_URL;
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------
|
||||
// Server
|
||||
@@ -71,11 +72,13 @@ Server::Server(Instance &aInstance)
|
||||
static_assert(sizeof(kVendorName) <= sizeof(VendorNameTlv::StringType), "VENDOR_NAME is too long");
|
||||
static_assert(sizeof(kVendorModel) <= sizeof(VendorModelTlv::StringType), "VENDOR_MODEL is too long");
|
||||
static_assert(sizeof(kVendorSwVersion) <= sizeof(VendorSwVersionTlv::StringType), "VENDOR_SW_VERSION is too long");
|
||||
static_assert(sizeof(kVendorAppUrl) <= sizeof(VendorAppUrlTlv::StringType), "VENDOR_APP_URL is too long");
|
||||
|
||||
#if OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE
|
||||
memcpy(mVendorName, kVendorName, sizeof(kVendorName));
|
||||
memcpy(mVendorModel, kVendorModel, sizeof(kVendorModel));
|
||||
memcpy(mVendorSwVersion, kVendorSwVersion, sizeof(kVendorSwVersion));
|
||||
memcpy(mVendorAppUrl, kVendorAppUrl, sizeof(kVendorAppUrl));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -96,6 +99,11 @@ Error Server::SetVendorSwVersion(const char *aVendorSwVersion)
|
||||
return StringCopy(mVendorSwVersion, aVendorSwVersion, kStringCheckUtf8Encoding);
|
||||
}
|
||||
|
||||
Error Server::SetVendorAppUrl(const char *aVendorAppUrl)
|
||||
{
|
||||
return StringCopy(mVendorAppUrl, aVendorAppUrl, kStringCheckUtf8Encoding);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void Server::PrepareMessageInfoForDest(const Ip6::Address &aDestination, Tmf::MessageInfo &aMessageInfo) const
|
||||
@@ -329,6 +337,10 @@ Error Server::AppendDiagTlv(uint8_t aTlvType, Message &aMessage)
|
||||
error = Tlv::Append<VendorSwVersionTlv>(aMessage, GetVendorSwVersion());
|
||||
break;
|
||||
|
||||
case Tlv::kVendorAppUrl:
|
||||
error = Tlv::Append<VendorAppUrlTlv>(aMessage, GetVendorAppUrl());
|
||||
break;
|
||||
|
||||
case Tlv::kThreadStackVersion:
|
||||
error = Tlv::Append<ThreadStackVersionTlv>(aMessage, otGetVersionString());
|
||||
break;
|
||||
@@ -1225,6 +1237,10 @@ Error Client::GetNextDiagTlv(const Coap::Message &aMessage, Iterator &aIterator,
|
||||
SuccessOrExit(error = Tlv::Read<VendorSwVersionTlv>(aMessage, offset, aTlvInfo.mData.mVendorSwVersion));
|
||||
break;
|
||||
|
||||
case Tlv::kVendorAppUrl:
|
||||
SuccessOrExit(error = Tlv::Read<VendorAppUrlTlv>(aMessage, offset, aTlvInfo.mData.mVendorAppUrl));
|
||||
break;
|
||||
|
||||
case Tlv::kThreadStackVersion:
|
||||
SuccessOrExit(error =
|
||||
Tlv::Read<ThreadStackVersionTlv>(aMessage, offset, aTlvInfo.mData.mThreadStackVersion));
|
||||
|
||||
@@ -141,10 +141,30 @@ public:
|
||||
*/
|
||||
Error SetVendorSwVersion(const char *aVendorSwVersion);
|
||||
|
||||
/**
|
||||
* Returns the vendor app URL string.
|
||||
*
|
||||
* @returns the vendor app URL string.
|
||||
*
|
||||
*/
|
||||
const char *GetVendorAppUrl(void) const { return mVendorAppUrl; }
|
||||
|
||||
/**
|
||||
* Sets the vendor app URL string.
|
||||
*
|
||||
* @param[in] aVendorAppUrl The vendor app URL string
|
||||
*
|
||||
* @retval kErrorNone Successfully set the vendor app URL.
|
||||
* @retval kErrorInvalidArgs @p aVendorAppUrl is not valid (too long or not UTF8).
|
||||
*
|
||||
*/
|
||||
Error SetVendorAppUrl(const char *aVendorAppUrl);
|
||||
|
||||
#else
|
||||
const char *GetVendorName(void) const { return kVendorName; }
|
||||
const char *GetVendorModel(void) const { return kVendorModel; }
|
||||
const char *GetVendorSwVersion(void) const { return kVendorSwVersion; }
|
||||
const char *GetVendorAppUrl(void) const { return kVendorAppUrl; }
|
||||
#endif // OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE
|
||||
|
||||
private:
|
||||
@@ -173,6 +193,7 @@ private:
|
||||
static const char kVendorName[];
|
||||
static const char kVendorModel[];
|
||||
static const char kVendorSwVersion[];
|
||||
static const char kVendorAppUrl[];
|
||||
|
||||
Error AppendDiagTlv(uint8_t aTlvType, Message &aMessage);
|
||||
Error AppendIp6AddressList(Message &aMessage);
|
||||
@@ -211,6 +232,7 @@ private:
|
||||
VendorNameTlv::StringType mVendorName;
|
||||
VendorModelTlv::StringType mVendorModel;
|
||||
VendorSwVersionTlv::StringType mVendorSwVersion;
|
||||
VendorAppUrlTlv::StringType mVendorAppUrl;
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_FTD
|
||||
|
||||
@@ -96,6 +96,7 @@ public:
|
||||
kAnswer = OT_NETWORK_DIAGNOSTIC_TLV_ANSWER,
|
||||
kQueryId = OT_NETWORK_DIAGNOSTIC_TLV_QUERY_ID,
|
||||
kMleCounters = OT_NETWORK_DIAGNOSTIC_TLV_MLE_COUNTERS,
|
||||
kVendorAppUrl = OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_APP_URL,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -122,6 +123,12 @@ public:
|
||||
*/
|
||||
static constexpr uint8_t kMaxThreadStackVersionLength = OT_NETWORK_DIAGNOSTIC_MAX_THREAD_STACK_VERSION_TLV_LENGTH;
|
||||
|
||||
/**
|
||||
* Maximum length of Vendor SW Version TLV.
|
||||
*
|
||||
*/
|
||||
static constexpr uint8_t kMaxVendorAppUrlLength = OT_NETWORK_DIAGNOSTIC_MAX_VENDOR_APP_URL_TLV_LENGTH;
|
||||
|
||||
/**
|
||||
* Returns the Type value.
|
||||
*
|
||||
@@ -236,6 +243,12 @@ typedef StringTlvInfo<Tlv::kVendorSwVersion, Tlv::kMaxVendorSwVersionLength> Ven
|
||||
*/
|
||||
typedef StringTlvInfo<Tlv::kThreadStackVersion, Tlv::kMaxThreadStackVersionLength> ThreadStackVersionTlv;
|
||||
|
||||
/**
|
||||
* Defines Vendor App URL TLV constants and types.
|
||||
*
|
||||
*/
|
||||
typedef StringTlvInfo<Tlv::kVendorAppUrl, Tlv::kMaxVendorAppUrlLength> VendorAppUrlTlv;
|
||||
|
||||
/**
|
||||
* Defines Child IPv6 Address List TLV constants and types.
|
||||
*
|
||||
|
||||
@@ -378,6 +378,12 @@ class Node(object):
|
||||
def set_vendor_sw_version(self, version):
|
||||
return self._cli_no_output('vendor swversion', version)
|
||||
|
||||
def get_vendor_app_url(self):
|
||||
return self._cli_single_output('vendor appurl')
|
||||
|
||||
def set_vendor_app_url(self, url):
|
||||
return self._cli_no_output('vendor appurl', url)
|
||||
|
||||
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# netdata
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ VENDOR_MODEL_TLV = 26
|
||||
VENDOR_SW_VERSION_TLV = 27
|
||||
THREAD_STACK_VERSION_TLV = 28
|
||||
MLE_COUNTERS_TLV = 34
|
||||
VENDOR_APP_URL = 35
|
||||
|
||||
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Check setting vendor name, model, ans sw version
|
||||
@@ -76,10 +77,12 @@ MLE_COUNTERS_TLV = 34
|
||||
r1.set_vendor_name('nest')
|
||||
r1.set_vendor_model('marble')
|
||||
r1.set_vendor_sw_version('ot-1.3.1')
|
||||
r1.set_vendor_app_url('https://example.com/vendor-app')
|
||||
|
||||
verify(r1.get_vendor_name() == 'nest')
|
||||
verify(r1.get_vendor_model() == 'marble')
|
||||
verify(r1.get_vendor_sw_version() == 'ot-1.3.1')
|
||||
verify(r1.get_vendor_app_url() == 'https://example.com/vendor-app')
|
||||
|
||||
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Check invalid names (too long)
|
||||
@@ -126,6 +129,8 @@ except cli.CliError as e:
|
||||
|
||||
verify(errored)
|
||||
|
||||
r2.set_vendor_app_url("https://example.com/vendor-app")
|
||||
|
||||
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Perform net diag query
|
||||
|
||||
@@ -153,6 +158,13 @@ verify(len(result) == 2)
|
||||
verify(result[1].startswith("Vendor SW Version:"))
|
||||
verify(result[1].split(':')[1].strip() == r1.get_vendor_sw_version())
|
||||
|
||||
# Get vendor app URL (TLV 35)
|
||||
|
||||
result = r2.cli('networkdiagnostic get', r1_rloc, VENDOR_APP_URL)
|
||||
verify(len(result) == 2)
|
||||
verify(result[1].startswith("Vendor App URL:"))
|
||||
verify(result[1].split(':', 1)[1].strip() == r1.get_vendor_app_url())
|
||||
|
||||
# Get thread stack version (TLV 30)
|
||||
|
||||
result = r2.cli('networkdiagnostic get', r1_rloc, THREAD_STACK_VERSION_TLV)
|
||||
@@ -163,8 +175,8 @@ verify(r1.get_version().startswith(result[1].split(':', 1)[1].strip()))
|
||||
# Get all three TLVs (now from `r1`)
|
||||
|
||||
result = r1.cli('networkdiagnostic get', r2_rloc, VENDOR_NAME_TLV, VENDOR_MODEL_TLV, VENDOR_SW_VERSION_TLV,
|
||||
THREAD_STACK_VERSION_TLV)
|
||||
verify(len(result) == 5)
|
||||
THREAD_STACK_VERSION_TLV, VENDOR_APP_URL)
|
||||
verify(len(result) == 6)
|
||||
for line in result[1:]:
|
||||
if line.startswith("Vendor Name:"):
|
||||
verify(line.split(':')[1].strip() == r2.get_vendor_name())
|
||||
@@ -172,6 +184,8 @@ for line in result[1:]:
|
||||
verify(line.split(':')[1].strip() == r2.get_vendor_model())
|
||||
elif line.startswith("Vendor SW Version:"):
|
||||
verify(line.split(':')[1].strip() == r2.get_vendor_sw_version())
|
||||
elif line.startswith("Vendor App URL:"):
|
||||
verify(line.split(':', 1)[1].strip() == r2.get_vendor_app_url())
|
||||
elif line.startswith("Thread Stack Version:"):
|
||||
verify(r2.get_version().startswith(line.split(':', 1)[1].strip()))
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user