Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
O
OpenXG-NRF
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenXG
OpenXG-NRF
Commits
d426b2e3
Commit
d426b2e3
authored
Dec 11, 2020
by
Tien-Thinh Nguyen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
update UpdateNF operation
parent
a9e2a792
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
400 additions
and
19 deletions
+400
-19
src/api-server/impl/NFInstanceIDDocumentApiImpl.cpp
src/api-server/impl/NFInstanceIDDocumentApiImpl.cpp
+1
-1
src/common/nrf.h
src/common/nrf.h
+14
-0
src/nrf_app/nrf_app.cpp
src/nrf_app/nrf_app.cpp
+72
-12
src/nrf_app/nrf_profile.cpp
src/nrf_app/nrf_profile.cpp
+196
-5
src/nrf_app/nrf_profile.hpp
src/nrf_app/nrf_profile.hpp
+117
-1
No files found.
src/api-server/impl/NFInstanceIDDocumentApiImpl.cpp
View file @
d426b2e3
...
...
@@ -49,7 +49,7 @@ void NFInstanceIDDocumentApiImpl::register_nf_instance(
const
Pistache
::
Optional
<
Pistache
::
Http
::
Header
::
Raw
>
&
contentEncoding
,
Pistache
::
Http
::
ResponseWriter
&
response
)
{
Logger
::
nrf_sbi
().
info
(
"Got a request to register an NF instance, Instance ID: %s"
,
"Got a request to register an NF instance
/Update an NF instance
, Instance ID: %s"
,
nfInstanceID
.
c_str
());
NFProfile
nf_profile
=
nFProfile
;
...
...
src/common/nrf.h
View file @
d426b2e3
...
...
@@ -112,4 +112,18 @@ typedef struct amf_info_s {
std
::
vector
<
guami_t
>
guami_list
;
}
amf_info_t
;
typedef
struct
dnn_smf_info_item_s
{
std
::
string
dnn
;
}
dnn_smf_info_item_t
;
typedef
struct
snssai_smf_info_item_s
{
snssai_t
snssai
;
std
::
vector
<
dnn_smf_info_item_t
>
dnn_smf_info_list
;
}
snssai_smf_info_item_t
;
typedef
struct
smf_info_s
{
std
::
vector
<
snssai_smf_info_item_t
>
snssai_smf_info_list
;
}
smf_info_t
;
#endif
src/nrf_app/nrf_app.cpp
View file @
d426b2e3
...
...
@@ -50,10 +50,12 @@ nrf_app::nrf_app(const std::string &config_file) {
void
nrf_app
::
handle_register_nf_instance
(
const
std
::
string
&
nf_instance_id
,
const
oai
::
nrf
::
model
::
NFProfile
&
nf_profile
,
int
&
http_code
,
const
uint8_t
http_version
,
oai
::
nrf
::
model
::
ProblemDetails
&
problem_details
)
{
const
uint8_t
http_version
,
oai
::
nrf
::
model
::
ProblemDetails
&
problem_details
)
{
Logger
::
nrf_app
().
info
(
"Handle NF Instance Registration (HTTP version %d)"
,
http_version
);
Logger
::
nrf_app
().
info
(
"Handle Register NF Instance/Update NF Instance (HTTP version %d)"
,
http_version
);
if
(
!
api_conv
::
validate_uuid
(
nf_instance_id
))
{
http_code
=
HTTP_STATUS_CODE_400_BAD_REQUEST
;
...
...
@@ -91,19 +93,41 @@ void nrf_app::handle_register_nf_instance(
//convert to nrf_profile
if
(
api_conv
::
profile_api_to_amf_profile
(
nf_profile
,
sn
))
{
//set default value for hearbeattimer
sn
.
get
()
->
set_nf_heartBeat_timer
(
HEART_BEAT_TIMER
);
if
(
is_profile_exist
(
nf_instance_id
))
http_code
=
HTTP_STATUS_CODE_200_OK
;
else
http_code
=
HTTP_STATUS_CODE_201_CREATED
;
//add to the DB
add_nf_profile
(
nf_instance_id
,
sn
);
http_code
=
HTTP_STATUS_CODE_201_CREATED
;
Logger
::
nrf_app
().
debug
(
"Added NF Profile to the DB"
);
if
(
nf_profile
.
getNfType
().
compare
(
"AMF"
)
==
0
)
std
::
static_pointer_cast
<
amf_profile
>
(
sn
).
get
()
->
display
();
Logger
::
nrf_app
().
debug
(
"Added/Updated NF Profile to the DB"
);
switch
(
type
)
{
case
NF_TYPE_AMF
:
{
std
::
static_pointer_cast
<
amf_profile
>
(
sn
).
get
()
->
display
();
}
break
;
case
NF_TYPE_SMF
:
{
std
::
static_pointer_cast
<
smf_profile
>
(
sn
).
get
()
->
display
();
}
break
;
default:
{
sn
.
get
()
->
display
();
}
}
}
else
{
//error
Logger
::
nrf_app
().
warn
(
"Cannot convert a NF profile generated from OpenAPI to an AMF profile (profile ID %s)"
,
nf_instance_id
.
c_str
());
http_code
=
HTTP_STATUS_CODE_412_PRECONDITION_FAILED
;
http_code
=
HTTP_STATUS_CODE_400_BAD_REQUEST
;
problem_details
.
setCause
(
protocol_application_error_e2str
[
MANDATORY_IE_INCORRECT
]);
}
}
//------------------------------------------------------------------------------
...
...
@@ -145,7 +169,42 @@ void nrf_app::handle_update_nf_instance(const std::string &nf_instance_id,
}
}
case
PATCH_OP_ADD
:
{
switch
(
sn
.
get
()
->
get_nf_type
())
{
case
NF_TYPE_AMF
:
{
Logger
::
nrf_app
().
debug
(
"Update a AMF profile"
);
if
(
std
::
static_pointer_cast
<
amf_profile
>
(
sn
)
->
add_profile_info
(
path
,
p
.
getValue
()))
update_nf_profile
(
nf_instance_id
,
sn
);
}
break
;
case
NF_TYPE_SMF
:
{
}
break
;
default:
{
Logger
::
nrf_app
().
warn
(
"Unknown NF type!"
);
}
}
}
case
PATCH_OP_REMOVE
:
{
switch
(
sn
.
get
()
->
get_nf_type
())
{
case
NF_TYPE_AMF
:
{
Logger
::
nrf_app
().
debug
(
"Update a AMF profile"
);
if
(
std
::
static_pointer_cast
<
amf_profile
>
(
sn
)
->
remove_profile_info
(
path
))
update_nf_profile
(
nf_instance_id
,
sn
);
}
break
;
case
NF_TYPE_SMF
:
{
}
break
;
default:
{
Logger
::
nrf_app
().
warn
(
"Unknown NF type!"
);
}
}
}
break
;
default:
{
...
...
@@ -230,7 +289,7 @@ std::shared_ptr<nrf_profile> nrf_app::find_nf_profile(
Logger
::
nrf_app
().
info
(
"Find a NF profile with ID %s"
,
profile_id
.
c_str
());
std
::
unique
_lock
lock
(
m_instance_id2nrf_profile
);
std
::
shared
_lock
lock
(
m_instance_id2nrf_profile
);
if
(
instance_id2nrf_profile
.
count
(
profile_id
)
>
0
)
{
return
instance_id2nrf_profile
.
at
(
profile_id
);
}
else
{
...
...
@@ -245,7 +304,7 @@ bool nrf_app::find_nf_profile(const std::string &profile_id,
std
::
shared_ptr
<
nrf_profile
>
&
p
)
const
{
Logger
::
nrf_app
().
info
(
"Find a NF profile with ID %s"
,
profile_id
.
c_str
());
std
::
unique
_lock
lock
(
m_instance_id2nrf_profile
);
std
::
shared
_lock
lock
(
m_instance_id2nrf_profile
);
if
(
instance_id2nrf_profile
.
count
(
profile_id
)
>
0
)
{
p
=
instance_id2nrf_profile
.
at
(
profile_id
);
return
true
;
...
...
@@ -260,6 +319,7 @@ bool nrf_app::find_nf_profile(const std::string &profile_id,
void
nrf_app
::
find_nf_profiles
(
const
nf_type_t
&
nf_type
,
std
::
vector
<
std
::
shared_ptr
<
nrf_profile
>>
&
profiles
)
const
{
std
::
shared_lock
lock
(
m_instance_id2nrf_profile
);
for
(
auto
profile
:
instance_id2nrf_profile
)
{
if
(
profile
.
second
.
get
()
->
get_nf_type
()
==
nf_type
)
{
profiles
.
push_back
(
profile
.
second
);
...
...
@@ -272,7 +332,7 @@ bool nrf_app::is_profile_exist(const std::string &profile_id) const {
Logger
::
nrf_app
().
info
(
"Check if a profile with this ID %d exist"
,
profile_id
.
c_str
());
std
::
unique
_lock
lock
(
m_instance_id2nrf_profile
);
std
::
shared
_lock
lock
(
m_instance_id2nrf_profile
);
if
(
instance_id2nrf_profile
.
count
(
profile_id
)
>
0
)
{
return
true
;
}
else
{
...
...
src/nrf_app/nrf_profile.cpp
View file @
d426b2e3
...
...
@@ -28,8 +28,13 @@
*/
#include "nrf_profile.hpp"
#include "logger.hpp"
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include "api_conversions.hpp"
#include "logger.hpp"
#include "string.hpp"
using
namespace
std
;
using
namespace
oai
::
nrf
::
app
;
...
...
@@ -156,6 +161,16 @@ void nrf_profile::get_nf_ipv4_addresses(std::vector<struct in_addr> &a) const {
a
=
ipv4_addresses
;
}
//------------------------------------------------------------------------------
void
nrf_profile
::
set_json_data
(
const
nlohmann
::
json
&
data
)
{
json_data
=
data
;
}
//------------------------------------------------------------------------------
void
nrf_profile
::
get_json_data
(
nlohmann
::
json
&
data
)
const
{
data
=
json_data
;
}
//------------------------------------------------------------------------------
void
nrf_profile
::
display
()
{
...
...
@@ -180,6 +195,11 @@ void nrf_profile::display() {
for
(
auto
address
:
ipv4_addresses
)
{
Logger
::
nrf_app
().
debug
(
"............IPv4 Addr: %s"
,
inet_ntoa
(
address
));
}
if
(
!
json_data
.
empty
())
{
Logger
::
nrf_app
().
debug
(
"............Json Data: %s"
,
json_data
.
dump
().
c_str
());
}
}
bool
nrf_profile
::
replace_profile_info
(
const
std
::
string
&
path
,
...
...
@@ -191,14 +211,12 @@ bool nrf_profile::replace_profile_info(const std::string &path,
nf_instance_name
=
value
;
return
true
;
}
if
(
path
.
compare
(
"nfStatus"
)
==
0
)
{
nf_status
=
value
;
return
true
;
}
if
(
path
.
compare
(
"nfStatus"
)
==
0
)
{
nf_status
=
value
;
return
true
;
}
if
(
path
.
compare
(
"nfType"
)
==
0
)
{
nf_type
=
api_conv
::
string_to_nf_type
(
value
);
return
true
;
...
...
@@ -235,6 +253,118 @@ bool nrf_profile::replace_profile_info(const std::string &path,
return
false
;
}
//------------------------------------------------------------------------------
bool
nrf_profile
::
add_profile_info
(
const
std
::
string
&
path
,
const
std
::
string
&
value
)
{
Logger
::
nrf_app
().
debug
(
"Add an array element (value, array member), or a new member (value, member): %s, %s"
,
value
.
c_str
(),
path
.
c_str
());
if
(
path
.
compare
(
"ipv4Addresses"
)
==
0
)
{
std
::
string
address
=
value
;
struct
in_addr
addr4
=
{
};
unsigned
char
buf_in_addr
[
sizeof
(
struct
in_addr
)];
if
(
inet_pton
(
AF_INET
,
util
::
trim
(
address
).
c_str
(),
buf_in_addr
)
==
1
)
{
memcpy
(
&
addr4
,
buf_in_addr
,
sizeof
(
struct
in_addr
));
}
else
{
Logger
::
nrf_app
().
warn
(
"Address conversion: Bad value %s"
,
util
::
trim
(
address
).
c_str
());
}
Logger
::
nrf_app
().
debug
(
"Added IPv4 Addr: %s"
,
address
.
c_str
());
ipv4_addresses
.
push_back
(
addr4
);
return
true
;
}
if
(
path
.
compare
(
"snssais"
)
==
0
)
{
Logger
::
nrf_app
().
info
(
"Does not support this operation for snssais"
);
return
false
;
}
if
((
path
.
compare
(
"nfInstanceName"
)
==
0
)
or
(
path
.
compare
(
"nfStatus"
)
==
0
)
or
(
path
.
compare
(
"nfType"
)
==
0
)
or
(
path
.
compare
(
"heartBeatTimer"
)
==
0
)
or
(
path
.
compare
(
"priority"
)
==
0
)
or
(
path
.
compare
(
"capacity"
)
==
0
))
{
Logger
::
nrf_app
().
info
(
"Does not support this operation, member (%s) exit!"
,
path
.
c_str
());
return
false
;
}
//add new member
json_data
[
path
]
=
value
;
return
true
;
}
//------------------------------------------------------------------------------
bool
nrf_profile
::
remove_profile_info
(
const
std
::
string
&
path
)
{
Logger
::
nrf_app
().
debug
(
"Remove an array element or a member: %s"
,
path
.
c_str
());
if
(
path
.
compare
(
"nfInstanceName"
)
==
0
)
{
nf_instance_name
=
""
;
return
true
;
}
if
(
path
.
compare
(
"nfStatus"
)
==
0
)
{
nf_status
=
""
;
return
true
;
}
if
(
path
.
compare
(
"nfType"
)
==
0
)
{
nf_type
=
NF_TYPE_UNKNOWN
;
return
true
;
}
if
(
path
.
compare
(
"heartBeatTimer"
)
==
0
)
{
heartBeat_timer
=
0
;
return
true
;
}
if
(
path
.
compare
(
"priority"
)
==
0
)
{
priority
=
0
;
return
true
;
}
if
(
path
.
compare
(
"capacity"
)
==
0
)
{
priority
=
0
;
return
true
;
}
//path: e.g., /ipv4Addresses/4
if
(
path
.
find
(
"ipv4Addresses"
)
!=
std
::
string
::
npos
)
{
std
::
vector
<
std
::
string
>
parts
;
boost
::
split
(
parts
,
path
,
boost
::
is_any_of
(
"/"
),
boost
::
token_compress_on
);
if
(
parts
.
size
()
!=
2
)
{
Logger
::
nrf_app
().
warn
(
"Bad value for path: %s "
,
path
.
c_str
());
return
false
;
}
//get and check index
uint32_t
index
=
0
;
try
{
index
=
std
::
stoi
(
parts
.
at
(
1
));
}
catch
(
const
std
::
exception
&
err
)
{
Logger
::
nrf_app
().
warn
(
"Bad value for path: %s "
,
path
.
c_str
());
return
false
;
}
if
(
index
>=
ipv4_addresses
.
size
())
{
Logger
::
nrf_app
().
warn
(
"Bad value for path: %s "
,
path
.
c_str
());
return
false
;
}
else
{
Logger
::
nrf_app
().
debug
(
"Removed IPv4 Addr: %s"
,
inet_ntoa
(
ipv4_addresses
[
index
]));
ipv4_addresses
.
erase
(
ipv4_addresses
.
begin
()
+
index
);
return
true
;
}
}
if
(
path
.
find
(
"snssais"
)
!=
std
::
string
::
npos
)
{
Logger
::
nrf_app
().
info
(
"Does not support this operation for snssais"
);
return
false
;
}
Logger
::
nrf_app
().
debug
(
"Member (%s) not found!"
,
path
.
c_str
());
return
false
;
}
//------------------------------------------------------------------------------
void
amf_profile
::
add_amf_info
(
const
amf_info_t
&
info
)
{
amf_info
=
info
;
...
...
@@ -263,9 +393,70 @@ void amf_profile::display() {
}
//------------------------------------------------------------------------------
bool
amf_profile
::
replace_profile_info
(
const
std
::
string
&
path
,
const
std
::
string
&
value
)
{
nrf_profile
::
replace_profile_info
(
path
,
value
);
//TODO with AMF part
}
//------------------------------------------------------------------------------
bool
amf_profile
::
add_profile_info
(
const
std
::
string
&
path
,
const
std
::
string
&
value
)
{
nrf_profile
::
add_profile_info
(
path
,
value
);
//TODO with AMF part
}
//------------------------------------------------------------------------------
bool
amf_profile
::
remove_profile_info
(
const
std
::
string
&
path
)
{
nrf_profile
::
remove_profile_info
(
path
);
//TODO with AMF part
}
//------------------------------------------------------------------------------
void
smf_profile
::
add_smf_info
(
const
smf_info_t
&
info
)
{
smf_info
=
info
;
}
//------------------------------------------------------------------------------
void
smf_profile
::
get_smf_info
(
smf_info_t
&
infos
)
const
{
infos
=
smf_info
;
}
//------------------------------------------------------------------------------
void
smf_profile
::
display
()
{
nrf_profile
::
display
();
/*
for (auto g : amf_info.guami_list) {
Logger::nrf_app().debug("............AMF GUAMI, AMF_ID: %s",
g.amf_id.c_str());
Logger::nrf_app().debug("....................., PLMN (MCC: %s, MNC: %s)",
g.plmn.mcc.c_str(), g.plmn.mnc.c_str());
}
*/
}
//------------------------------------------------------------------------------
bool
smf_profile
::
add_profile_info
(
const
std
::
string
&
path
,
const
std
::
string
&
value
)
{
nrf_profile
::
add_profile_info
(
path
,
value
);
//TODO with SMF part
}
bool
smf_profile
::
replace_profile_info
(
const
std
::
string
&
path
,
const
std
::
string
&
value
)
{
nrf_profile
::
replace_profile_info
(
path
,
value
);
//TODO with SMF part
}
//------------------------------------------------------------------------------
bool
smf_profile
::
remove_profile_info
(
const
std
::
string
&
path
)
{
nrf_profile
::
remove_profile_info
(
path
);
//TODO with SMF part
}
src/nrf_app/nrf_profile.hpp
View file @
d426b2e3
...
...
@@ -37,6 +37,7 @@
#include <memory>
#include <utility>
#include <vector>
#include <nlohmann/json.hpp>
#include "nrf.h"
#include "nrf.h"
...
...
@@ -60,6 +61,7 @@ class nrf_profile : public std::enable_shared_from_this<nrf_profile> {
capacity
(
0
)
{
nf_instance_name
=
""
;
nf_status
=
""
;
json_data
=
{
};
}
nrf_profile
(
const
nf_type_t
type
)
:
...
...
@@ -71,6 +73,7 @@ class nrf_profile : public std::enable_shared_from_this<nrf_profile> {
capacity
(
0
)
{
nf_instance_name
=
""
;
nf_status
=
""
;
json_data
=
{
};
}
nrf_profile
(
const
std
::
string
&
id
)
...
...
@@ -84,6 +87,7 @@ class nrf_profile : public std::enable_shared_from_this<nrf_profile> {
nf_type
(
NF_TYPE_UNKNOWN
)
{
nf_instance_name
=
""
;
nf_status
=
""
;
json_data
=
{
};
}
nrf_profile
(
nrf_profile
&
b
)
=
delete
;
...
...
@@ -262,6 +266,20 @@ class nrf_profile : public std::enable_shared_from_this<nrf_profile> {
*/
void
get_nf_ipv4_addresses
(
std
::
vector
<
struct
in_addr
>
&
a
)
const
;
/*
* Set json data
* @param [const nlohmann::json &] data: Json data to be set
* @return void
*/
void
set_json_data
(
const
nlohmann
::
json
&
data
);
/*
* Get json data
* @param [nlohmann::json &] data: Store json data
* @return void
*/
void
get_json_data
(
nlohmann
::
json
&
data
)
const
;
/*
* Print related-information for NF profile
* @param void
...
...
@@ -273,10 +291,26 @@ class nrf_profile : public std::enable_shared_from_this<nrf_profile> {
* Update a new value for a member of NF profile
* @param [const std::string &] path: member name
* @param [const std::string &] value: new value
* @return
void
* @return
true if success, otherwise false
*/
bool
replace_profile_info
(
const
std
::
string
&
path
,
const
std
::
string
&
value
);
/*
* Add a new value for a member of NF profile
* @param [const std::string &] path: member name
* @param [const std::string &] value: new value
* @return true if success, otherwise false
*/
bool
add_profile_info
(
const
std
::
string
&
path
,
const
std
::
string
&
value
);
/*
* Remove value of a member of NF profile
* @param [const std::string &] path: member name
* @param [const std::string &] value: new value
* @return true if success, otherwise false
*/
bool
remove_profile_info
(
const
std
::
string
&
path
);
protected:
//From NFProfile (Section 6.1.6.2.2@3GPP TS 29.510 V16.0.0 (2019-06))
std
::
string
nf_instance_id
;
...
...
@@ -288,6 +322,7 @@ class nrf_profile : public std::enable_shared_from_this<nrf_profile> {
std
::
vector
<
struct
in_addr
>
ipv4_addresses
;
uint16_t
priority
;
uint16_t
capacity
;
nlohmann
::
json
json_data
;
//store extra json data
/*
std::vector<PlmnId> m_PlmnList;
...
...
@@ -402,10 +437,91 @@ class amf_profile : public nrf_profile {
*/
bool
replace_profile_info
(
const
std
::
string
&
path
,
const
std
::
string
&
value
);
/*
* Add a new value for a member of NF profile
* @param [const std::string &] path: member name
* @param [const std::string &] value: new value
* @return true if success, otherwise false
*/
bool
add_profile_info
(
const
std
::
string
&
path
,
const
std
::
string
&
value
);
/*
* Remove value of a member of NF profile
* @param [const std::string &] path: member name
* @param [const std::string &] value: new value
* @return true if success, otherwise false
*/
bool
remove_profile_info
(
const
std
::
string
&
path
);
private:
amf_info_t
amf_info
;
};
class
smf_profile
:
public
nrf_profile
{
public:
smf_profile
()
:
nrf_profile
(
NF_TYPE_SMF
)
{
smf_info
=
{
};
}
smf_profile
(
const
std
::
string
&
id
)
:
nrf_profile
(
id
)
{
nf_type
=
NF_TYPE_SMF
;
smf_info
=
{
};
}
smf_profile
(
smf_profile
&
b
)
=
delete
;
/*
* Add a SMF info
* @param [const smf_info_t &] info: SMF info
* @return void
*/
void
add_smf_info
(
const
smf_info_t
&
info
);
/*
* Get list of SMF infos a SMF info
* @param [const smf_info_t &] info: SMF info
* @return void
*/
void
get_smf_info
(
smf_info_t
&
infos
)
const
;
/*
* Print related-information for a SMF profile
* @param void
* @return void:
*/
void
display
();
/*
* Update a new value for a member of SMF profile
* @param [const std::string &] path: member name
* @param [const std::string &] value: new value
* @return void
*/
bool
replace_profile_info
(
const
std
::
string
&
path
,
const
std
::
string
&
value
);
/*
* Add a new value for a member of NF profile
* @param [const std::string &] path: member name
* @param [const std::string &] value: new value
* @return true if success, otherwise false
*/
bool
add_profile_info
(
const
std
::
string
&
path
,
const
std
::
string
&
value
);
/*
* Remove value of a member of NF profile
* @param [const std::string &] path: member name
* @param [const std::string &] value: new value
* @return true if success, otherwise false
*/
bool
remove_profile_info
(
const
std
::
string
&
path
);
private:
smf_info_t
smf_info
;
};
}
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment