Commit 497f7bc2 authored by yangjian's avatar yangjian
Browse files

Modify udr database

parent 2cc79369
# REST API Server for Nudr_DataRepository API OpenAPI file
## Overview
This API Server was generated by the [OpenAPI Generator](https://openapi-generator.tech) project.
It uses the [Pistache](https://github.com/oktal/pistache) Framework.
## Files organization
The Pistache C++ REST server generator creates three folders:
- `api`: This folder contains the handlers for each method specified in the OpenAPI definition. Every handler extracts
the path and body parameters (if any) from the requests and tries to parse and possibly validate them.
Once this step is completed, the main API class calls the corresponding abstract method that should be implemented
by the developer (a basic implementation is provided under the `impl` folder)
- `impl`: As written above, the implementation folder contains, for each API, the corresponding implementation class,
which extends the main API class and implements the abstract methods.
Every method receives the path and body parameters as constant reference variables and a reference to the response
object, that should be filled with the right response and sent at the end of the method with the command:
response.send(returnCode, responseBody, [mimeType])
- `model`: This folder contains the corresponding class for every object schema found in the OpenAPI specification.
The main folder contains also a file with a main that can be used to start the server.
Of course, is you should customize this file based on your needs
## Installation
First of all, you need to download and install the libraries listed [here](#libraries-required).
Once the libraries are installed, in order to compile and run the server please follow the steps below:
```bash
cd scripts
./build_udr
```
Once compiled run the server:
```bash
cd build
./udr
```
## Libraries required
- [pistache](http://pistache.io/quickstart): Please download the `Pistache` project and
put the "include" folder and the generated "*.so" file into external/include/ and external/lib/ after compilation
- [JSON for Modern C++](https://github.com/nlohmann/json/#integration): Please download the `json.hpp` file and
put it under the external/include/nlohmann folder
## Namespaces
org.openapitools.server.api
org.openapitools.server.model
# OpenXG-UDR
OpenXG-UDR Arch
├── build: Build directory, contains targets and object files generated by compilation of network functions.
├── scripts: Directory containing scripts for building network functions.
├── ext: Directory containing spdlog and pistache and the JSON library.
└── UDR: Directory containing object files generated by compilation of UDR network function.
├── etc: Directory containing the configuration file to be deployed for UDR.
└── src: Source files of UDR.
├── udr_app: UDR Procedures handling logic.
├── common: Common definitions for 3GPP specifications.
├── model: UDR service.
├── api: UDR service.
└── impl: UDR service.
## Download source code from Gitlab
git clone http://git.opensource5g.org/openxg/openxg-udr.git
cd openxg-udr/
git checkout master
## install dependencies
cd ./build/scripts
sudo ./build_udr -I
## build UDR
sudo ./build_udr -c -b Debug -j
## launch UDR
sudo ./build/UDR/udr -c etc/udr.conf -o
\ No newline at end of file
......@@ -41,7 +41,18 @@ void AMF3GPPAccessRegistrationDocumentApiImpl::amf_context3gpp(const std::string
void AMF3GPPAccessRegistrationDocumentApiImpl::create_amf_context3gpp(const std::string &ueId, Amf3GppAccessRegistration &amf3GppAccessRegistration, Pistache::Http::ResponseWriter &response) {
MYSQL_RES *res = NULL;
MYSQL_ROW row;
const std::string select_AMF3GPPAccessRegistration = "select * from Amf3GppAccessRegistration WHERE ueid='"+ueId+"'";
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
const std::string select_AMF3GPPAccessRegistration = "select * from Amf3GppAccessRegistration WHERE ueid='"+tmp_ueId+"'";
std::string query;
nlohmann::json j;
......@@ -121,11 +132,11 @@ void AMF3GPPAccessRegistrationDocumentApiImpl::create_amf_context3gpp(const std:
query += ",guami='"+j.dump()+"'";
to_json(j,amf3GppAccessRegistration.getRatType());
query += ",ratType='"+j.dump()+"'";
query += " where ueid='"+ueId+"'";
query += " where ueid='"+tmp_ueId+"'";
}
else
{
query="insert into Amf3GppAccessRegistration set ueid='"+ueId+"'"+ \
query="insert into Amf3GppAccessRegistration set ueid='"+tmp_ueId+"'"+ \
",amfInstanceId='"+amf3GppAccessRegistration.getAmfInstanceId()+"'"+ \
(amf3GppAccessRegistration.supportedFeaturesIsSet()?",supportedFeatures='"+amf3GppAccessRegistration.getSupportedFeatures()+"'":"")+ \
(amf3GppAccessRegistration.purgeFlagIsSet()?(amf3GppAccessRegistration.isPurgeFlag()?",purgeFlag=1":",purgeFlag=0"):"")+ \
......@@ -211,8 +222,18 @@ void AMF3GPPAccessRegistrationDocumentApiImpl::query_amf_context3gpp(const std::
nlohmann::json j;
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
Amf3GppAccessRegistration amf3gppaccessregistration;
const std::string query = "select * from Amf3GppAccessRegistration WHERE ueid='"+ueId+"'";
const std::string query = "select * from Amf3GppAccessRegistration WHERE ueid='"+tmp_ueId+"'";
if (mysql_real_query(mysql_WitcommUDRDB,query.c_str(), (unsigned long)query.size()))
{
......
......@@ -33,8 +33,18 @@ void AccessAndMobilitySubscriptionDataDocumentApiImpl::query_am_data(const std::
nlohmann::json j;
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
AccessAndMobilitySubscriptionData accessandmobilitysubscriptiondata;
const std::string query = "select * from AccessAndMobilitySubscriptionData WHERE ueid='"+ueId+"' and servingPlmnid='"+servingPlmnId+"'";
const std::string query = "select * from AccessAndMobilitySubscriptionData WHERE ueid='"+tmp_ueId+"' and servingPlmnid='"+servingPlmnId+"'";
if (mysql_real_query(mysql_WitcommUDRDB,query.c_str(), (unsigned long)query.size()))
{
......
......@@ -30,7 +30,17 @@ void AuthenticationStatusDocumentApiImpl::create_authentication_status(const std
//response.send(Pistache::Http::Code::Ok, "create_authentication_status\n");
MYSQL_RES *res = NULL;
MYSQL_ROW row;
const std::string select_AuthenticationStatus = "select * from AuthenticationStatus WHERE ueid='"+ueId+"'";
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
const std::string select_AuthenticationStatus = "select * from AuthenticationStatus WHERE ueid='"+tmp_ueId+"'";
std::string query;
nlohmann::json j;
......@@ -56,11 +66,11 @@ void AuthenticationStatusDocumentApiImpl::create_authentication_status(const std
(authEvent.authRemovalIndIsSet()?(authEvent.isAuthRemovalInd()?",authRemovalInd=1":",authRemovalInd=0"):"");
// to_json(j,authEvent.getAuthType());
// query += ",authType='"+j.dump()+"'";
query += " where ueid='"+ueId+"'";
query += " where ueid='"+tmp_ueId+"'";
}
else
{
query="insert into AuthenticationStatus set ueid='"+ueId+"'"+ \
query="insert into AuthenticationStatus set ueid='"+tmp_ueId+"'"+ \
",nfInstanceId='"+authEvent.getNfInstanceId()+"'"+ \
",success="+(authEvent.isSuccess()?"1":"0")+ \
",timeStamp='"+authEvent.getTimeStamp()+"'"+ \
......@@ -86,8 +96,17 @@ void AuthenticationStatusDocumentApiImpl::create_authentication_status(const std
}
void AuthenticationStatusDocumentApiImpl::delete_authentication_status(const std::string &ueId, Pistache::Http::ResponseWriter &response) {
//response.send(Pistache::Http::Code::Ok, "delete_authentication_status\n");
const std::string query = "DELETE from AuthenticationStatus WHERE ueid='"+ueId+"'";
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
const std::string query = "DELETE from AuthenticationStatus WHERE ueid='"+tmp_ueId+"'";
if (mysql_real_query(mysql_WitcommUDRDB,query.c_str(), (unsigned long)query.size()))
{
......@@ -105,9 +124,18 @@ void AuthenticationStatusDocumentApiImpl::query_authentication_status(const std:
MYSQL_FIELD* field = nullptr;
nlohmann::json j;
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
AuthEvent authenticationstatus;
const std::string query = "select * from AuthenticationStatus WHERE ueid='"+ueId+"'";
const std::string query = "select * from AuthenticationStatus WHERE ueid='"+tmp_ueId+"'";
if (mysql_real_query(mysql_WitcommUDRDB,query.c_str(), (unsigned long)query.size()))
{
......
......@@ -34,7 +34,16 @@ void AuthenticationSubscriptionDocumentApiImpl::modify_authentication_subscripti
MYSQL_RES *res = NULL;
MYSQL_ROW row;
const std::string select_Authenticationsubscription = "select * from AuthenticationSubscription WHERE ueid='"+ueId+"'";
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
const std::string select_Authenticationsubscription = "select * from AuthenticationSubscription WHERE ueid='"+tmp_ueId+"'";
std::string query;
nlohmann::json j,tmp_j;
......@@ -48,14 +57,14 @@ void AuthenticationSubscriptionDocumentApiImpl::modify_authentication_subscripti
if (mysql_real_query(mysql_WitcommUDRDB,select_Authenticationsubscription.c_str(), (unsigned long)select_Authenticationsubscription.size()))
{
Logger::udr_server().error("mysql_real_query failure!SQL(%s)",select_Authenticationsubscription);
Logger::udr_server().error("mysql_real_query failure!SQL(%s)",select_Authenticationsubscription.c_str());
return;
}
res = mysql_store_result(mysql_WitcommUDRDB);
if(res == NULL)
{
Logger::udr_server().error("mysql_store_result failure!SQL(%s)",select_Authenticationsubscription);
Logger::udr_server().error("mysql_store_result failure!SQL(%s)",select_Authenticationsubscription.c_str());
return;
}
if (mysql_num_rows(res))
......@@ -65,11 +74,11 @@ void AuthenticationSubscriptionDocumentApiImpl::modify_authentication_subscripti
to_json(sequencenumber_j,sequencenumber);
query += sequencenumber_j.dump()+"'";
query += " where ueid='"+ueId+"'";
query += " where ueid='"+tmp_ueId+"'";
}
else
{
Logger::udr_server().error("AuthenticationSubscription no data!");
Logger::udr_server().error("AuthenticationSubscription no data!SQL(%s)",select_Authenticationsubscription.c_str());
}
mysql_free_result(res);
......@@ -77,7 +86,7 @@ void AuthenticationSubscriptionDocumentApiImpl::modify_authentication_subscripti
// Logger::udr_server().debug("modify content: %s",query.c_str());
if (mysql_real_query(mysql_WitcommUDRDB,query.c_str(), (unsigned long)query.size()))
{
Logger::udr_server().error("update mysql failure!");
Logger::udr_server().error("update mysql failure!SQL(%s)",query.c_str());
return;
}
}
......@@ -100,21 +109,30 @@ void AuthenticationSubscriptionDocumentApiImpl::read_authentication_subscription
MYSQL_FIELD* field = nullptr;
nlohmann::json j;
std::string tmp_ueId = "s";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
AuthenticationSubscription authenticationsubscription;
const std::string query = "select * from AuthenticationSubscription WHERE ueid='"+ueId+"'";
const std::string query = "select * from AuthenticationSubscription WHERE ueid='"+tmp_ueId+"'";
if (mysql_real_query(mysql_WitcommUDRDB,query.c_str(), (unsigned long)query.size()))
{
Logger::udr_server().error("mysql_real_query failure!");
Logger::udr_server().error("mysql_real_query failure!SQL(%s)",query.c_str());
return;
}
res = mysql_store_result(mysql_WitcommUDRDB);
if(res == NULL)
{
Logger::udr_server().error("mysql_store_result failure!");
Logger::udr_server().error("mysql_store_result failure!SQL(%s)",query.c_str());
return;
}
......@@ -196,7 +214,7 @@ void AuthenticationSubscriptionDocumentApiImpl::read_authentication_subscription
}
else
{
Logger::udr_server().error("AuthenticationSubscription no data!");
Logger::udr_server().error("AuthenticationSubscription no data!SQL(%s)",query.c_str());
}
mysql_free_result(res);
......
......@@ -34,10 +34,20 @@ void SDMSubscriptionDocumentApiImpl::querysdm_subscription(const std::string &ue
MYSQL_ROW row;
MYSQL_FIELD* field = nullptr;
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
nlohmann::json j;
SdmSubscription SdmSubscriptions;
const std::string query = "SELECT * from SdmSubscriptions WHERE ueid='"+ueId+"' AND subsId="+subsId;
const std::string query = "SELECT * from SdmSubscriptions WHERE ueid='"+tmp_ueId+"' AND subsId="+subsId;
if (mysql_real_query(mysql_WitcommUDRDB,query.c_str(), (unsigned long)query.size()))
{
......@@ -151,9 +161,19 @@ void SDMSubscriptionDocumentApiImpl::removesdm_subscriptions(const std::string &
nlohmann::json j;
ProblemDetails problemdetails;
const std::string select_query = "SELECT * from SdmSubscriptions WHERE ueid='"+ueId+"' AND subsId="+subsId;
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
const std::string select_query = "SELECT * from SdmSubscriptions WHERE ueid='"+tmp_ueId+"' AND subsId="+subsId;
const std::string query = "DELETE from SdmSubscriptions WHERE ueid='"+ueId+"' AND subsId="+subsId;
const std::string query = "DELETE from SdmSubscriptions WHERE ueid='"+tmp_ueId+"' AND subsId="+subsId;
if (mysql_real_query(mysql_WitcommUDRDB,select_query.c_str(), (unsigned long)select_query.size()))
{
......@@ -196,7 +216,18 @@ void SDMSubscriptionDocumentApiImpl::removesdm_subscriptions(const std::string &
void SDMSubscriptionDocumentApiImpl::updatesdmsubscriptions(const std::string &ueId, const std::string &subsId, SdmSubscription &sdmSubscription, Pistache::Http::ResponseWriter &response) {
MYSQL_RES *res = NULL;
MYSQL_ROW row;
const std::string select_query = "SELECT * from SdmSubscriptions WHERE ueid='"+ueId+"' AND subsId="+subsId;
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
const std::string select_query = "SELECT * from SdmSubscriptions WHERE ueid='"+tmp_ueId+"' AND subsId="+subsId;
std::string query;
nlohmann::json j;
ProblemDetails problemdetails;
......@@ -254,7 +285,7 @@ void SDMSubscriptionDocumentApiImpl::updatesdmsubscriptions(const std::string &u
query += ",monitoredResourceUris='"+MonitoredResourceUris_json.dump()+"'";
query += " where ueid='"+ueId+"' AND subsId="+subsId;
query += " where ueid='"+tmp_ueId+"' AND subsId="+subsId;
}
else
{
......
......@@ -31,9 +31,19 @@ void SDMSubscriptionsCollectionApiImpl::create_sdm_subscriptions(const std::stri
MYSQL_ROW row;
nlohmann::json j;
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
int32_t subsId = 0;
int32_t count = 0;
std::string query = "SELECT subsId from SdmSubscriptions WHERE ueid='"+ueId+"'";
std::string query = "SELECT subsId from SdmSubscriptions WHERE ueid='"+tmp_ueId+"'";
if (mysql_real_query(mysql_WitcommUDRDB,query.c_str(), (unsigned long)query.size()))
{
Logger::udr_server().error("mysql_real_query failure!SQL(%s)",query.c_str());
......@@ -59,7 +69,7 @@ void SDMSubscriptionsCollectionApiImpl::create_sdm_subscriptions(const std::stri
//****** add query *******
query="insert into SdmSubscriptions set ueid='"+ueId+"'"+ \
query="insert into SdmSubscriptions set ueid='"+tmp_ueId+"'"+ \
",nfInstanceId='"+sdmSubscription.getNfInstanceId()+"'"+ \
(sdmSubscription.implicitUnsubscribeIsSet()?(sdmSubscription.isImplicitUnsubscribe()?",implicitUnsubscribe=1":",implicitUnsubscribe=0"):"")+ \
(sdmSubscription.expiresIsSet()?",expires='"+sdmSubscription.getExpires()+"'":"")+ \
......@@ -127,7 +137,17 @@ void SDMSubscriptionsCollectionApiImpl::querysdmsubscriptions(const std::string
nlohmann::json j,tmp;
const std::string query = "SELECT * from SdmSubscriptions WHERE ueid='"+ueId+"'";
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
const std::string query = "SELECT * from SdmSubscriptions WHERE ueid='"+tmp_ueId+"'";
if (mysql_real_query(mysql_WitcommUDRDB,query.c_str(), (unsigned long)query.size()))
{
......
......@@ -29,7 +29,18 @@ SMFRegistrationDocumentApiImpl::SMFRegistrationDocumentApiImpl(std::shared_ptr<P
void SMFRegistrationDocumentApiImpl::create_smf_context_non3gpp(const std::string &ueId, const int32_t &pduSessionId, const SmfRegistration &smfRegistration, Pistache::Http::ResponseWriter &response) {
MYSQL_RES *res = NULL;
MYSQL_ROW row;
const std::string select_SmfRegistration = "SELECT * from SmfRegistrations WHERE ueid='"+ueId+"' AND subpduSessionId="+std::to_string(pduSessionId);
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
const std::string select_SmfRegistration = "SELECT * from SmfRegistrations WHERE ueid='"+tmp_ueId+"' AND subpduSessionId="+std::to_string(pduSessionId);
std::string query;
nlohmann::json j;
......@@ -74,11 +85,11 @@ void SMFRegistrationDocumentApiImpl::create_smf_context_non3gpp(const std::strin
query += ",singleNssai='"+j.dump()+"'";
to_json(j,smfRegistration.getPlmnId());
query += ",plmnId='"+j.dump()+"'";
query += " where ueid='"+ueId+"' AND subpduSessionId="+std::to_string(pduSessionId);
query += " where ueid='"+tmp_ueId+"' AND subpduSessionId="+std::to_string(pduSessionId);
}
else
{
query="insert into SmfRegistrations set ueid='"+ueId+"'"+ \
query="insert into SmfRegistrations set ueid='"+tmp_ueId+"'"+ \
",subpduSessionId="+std::to_string(pduSessionId)+ \
",pduSessionId="+std::to_string(smfRegistration.getPduSessionId())+ \
",smfInstanceId='"+smfRegistration.getSmfInstanceId()+"'"+ \
......@@ -124,7 +135,17 @@ void SMFRegistrationDocumentApiImpl::create_smf_context_non3gpp(const std::strin
Logger::udr_server().debug("SmfRegistration PUT - json:\n\"%s\"",out.c_str());
}
void SMFRegistrationDocumentApiImpl::delete_smf_context(const std::string &ueId, const int32_t &pduSessionId, Pistache::Http::ResponseWriter &response) {
const std::string query = "DELETE from SmfRegistrations WHERE ueid='"+ueId+"' AND subpduSessionId="+std::to_string(pduSessionId);
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
const std::string query = "DELETE from SmfRegistrations WHERE ueid='"+tmp_ueId+"' AND subpduSessionId="+std::to_string(pduSessionId);
if (mysql_real_query(mysql_WitcommUDRDB,query.c_str(), (unsigned long)query.size()))
{
......@@ -140,10 +161,20 @@ void SMFRegistrationDocumentApiImpl::query_smf_registration(const std::string &u
MYSQL_ROW row;
MYSQL_FIELD* field = nullptr;
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
nlohmann::json j;
SmfRegistration smfregistration;
const std::string query = "SELECT * from SmfRegistrations WHERE ueid='"+ueId+"' AND subpduSessionId="+std::to_string(pduSessionId);
const std::string query = "SELECT * from SmfRegistrations WHERE ueid='"+tmp_ueId+"' AND subpduSessionId="+std::to_string(pduSessionId);
if (mysql_real_query(mysql_WitcommUDRDB,query.c_str(), (unsigned long)query.size()))
{
......
......@@ -31,11 +31,21 @@ void SMFRegistrationsCollectionApiImpl::query_smf_reg_list(const std::string &ue
MYSQL_ROW row;
MYSQL_FIELD* field = nullptr;
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
std::vector<std::string> fields;
nlohmann::json j,tmp;
const std::string query = "SELECT * from SmfRegistrations WHERE ueid='"+ueId+"'";
const std::string query = "SELECT * from SmfRegistrations WHERE ueid='"+tmp_ueId+"'";
if (mysql_real_query(mysql_WitcommUDRDB,query.c_str(), (unsigned long)query.size()))
{
......
......@@ -33,8 +33,18 @@ void SMFSelectionSubscriptionDataDocumentApiImpl::query_smf_select_data(const st
nlohmann::json j;
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
SmfSelectionSubscriptionData smfselectionsubscriptiondata;
const std::string query = "select * from SmfSelectionSubscriptionData WHERE ueid='"+ueId+"' and servingPlmnid='"+servingPlmnId+"'";
const std::string query = "select * from SmfSelectionSubscriptionData WHERE ueid='"+tmp_ueId+"' and servingPlmnid='"+servingPlmnId+"'";
if (mysql_real_query(mysql_WitcommUDRDB,query.c_str(), (unsigned long)query.size()))
{
......
......@@ -35,8 +35,18 @@ void SessionManagementSubscriptionDataApiImpl::query_sm_data(const std::string &
nlohmann::json j;
std::string tmp_ueId = "";
if(ueId.size() == 15)
{
tmp_ueId = "imsi-"+ueId;
}
else
{
tmp_ueId = ueId;
}
SessionManagementSubscriptionData sessionmanagementsubscriptiondata;
const std::string query = "select * from SessionManagementSubscriptionData WHERE ueid='"+ueId+"' and servingPlmnid='"+servingPlmnId+"'";
const std::string query = "select * from SessionManagementSubscriptionData WHERE ueid='"+tmp_ueId+"' and servingPlmnid='"+servingPlmnId+"'";
if (mysql_real_query(mysql_WitcommUDRDB,query.c_str(), (unsigned long)query.size()))
{
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment