Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
O
OpenXG-AMF
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
1
Issues
1
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-AMF
Commits
8a310228
Commit
8a310228
authored
Jan 31, 2022
by
Tien-Thinh Nguyen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Validate NSSAIs
parent
13c9b4e5
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
310 additions
and
24 deletions
+310
-24
src/amf-app/amf_n1.cpp
src/amf-app/amf_n1.cpp
+99
-13
src/amf-app/amf_n1.hpp
src/amf-app/amf_n1.hpp
+4
-4
src/amf-app/amf_n2.cpp
src/amf-app/amf_n2.cpp
+5
-5
src/itti/msgs/itti_msg_n11.hpp
src/itti/msgs/itti_msg_n11.hpp
+0
-1
src/sbi/amf_server/model/Nssai.cpp
src/sbi/amf_server/model/Nssai.cpp
+100
-0
src/sbi/amf_server/model/Nssai.h
src/sbi/amf_server/model/Nssai.h
+101
-0
src/utils/3gpp_conversions.hpp
src/utils/3gpp_conversions.hpp
+1
-1
No files found.
src/amf-app/amf_n1.cpp
View file @
8a310228
...
...
@@ -73,7 +73,7 @@ extern "C" {
#include "dynamic_memory_check.h"
}
using
namespace
oai
::
amf
::
model
;
//
using namespace oai::amf::model;
using
namespace
nas
;
using
namespace
amf_application
;
using
namespace
config
;
...
...
@@ -1436,8 +1436,9 @@ void amf_n1::run_registration_procedure(std::shared_ptr<nas_context>& nc) {
}
nc
.
get
()
->
ngKsi
=
ngksi
;
}
// TODO: If AMF can't handle this UE, reroute the Registration Request to a
// new AMF Get UE's slice selection subscription from UDM if not available
// If current AMF can't handle this UE, reroute the Registration Request to
// a target AMF
if
(
reroute_registration_request
(
nc
))
{
return
;
}
...
...
@@ -3708,30 +3709,58 @@ bool amf_n1::reroute_registration_request(std::shared_ptr<nas_context>& nc) {
Logger
::
amf_n1
().
debug
(
"Registration with AMF re-allocation, Reroute Registration Request to "
"the target AMF"
);
nssai_t
nssai
=
{};
// Get NSSAI from UDM
oai
::
amf
::
model
::
Nssai
nssai
=
{};
if
(
!
get_slice_selection_subscription_data
(
nc
,
nssai
))
{
Logger
::
amf_n1
().
debug
(
"Could not get the Slice Selection Subscription Data from UDM"
);
return
false
;
}
// Check that AMF can process the Requested NSSAIs or not
if
(
check_requested_nssai
(
nc
,
nssai
))
{
Logger
::
amf_n1
().
debug
(
"Current AMF can handle all the Subscribed NSSAIs, do not need to "
"perform AMF Re-allocation procedure"
);
return
false
;
}
// Process NS selection to select the appropriate AMF
oai
::
amf
::
model
::
SliceInfoForRegistration
slice_info
=
{};
oai
::
amf
::
model
::
AuthorizedNetworkSliceInfo
authorized_network_slice_info
=
{};
std
::
vector
<
SubscribedSnssai
>
subscribed_snssais
;
for
(
auto
n
:
nssai
.
getDefaultSingleNssais
())
{
SubscribedSnssai
subscribed_snssai
=
{};
subscribed_snssai
.
setSubscribedSnssai
(
n
);
subscribed_snssai
.
setDefaultIndication
(
true
);
subscribed_snssais
.
push_back
(
subscribed_snssai
);
}
slice_info
.
setSubscribedNssai
(
subscribed_snssais
);
// TODO: To be verified
// slice_info.setRequestedNssai(nssai.getDefaultSingleNssais());
if
(
!
get_network_slice_selection
(
nc
,
amf_app_inst
->
get_nf_instance
(),
slice_info
,
authorized_network_slice_info
))
{
Logger
::
amf_n1
().
debug
(
"Could not get the Network Slice Selection information from NSSF"
);
return
false
;
}
// if get_target_amf();
// Find the appropriate target AMF
std
::
string
target_amf
=
{};
if
(
get_target_amf
(
nc
,
target_amf
,
authorized_network_slice_info
))
{
Logger
::
amf_n1
().
debug
(
"Target AMF %s"
,
target_amf
.
c_str
());
// Send N1MessageNotify to the Target AMF
send_n1_message_notity
(
nc
,
target_amf
);
return
true
;
}
else
{
Logger
::
amf_n1
().
debug
(
"Could not find an appropriate target AMF to handle UE"
);
return
false
;
}
return
true
;
...
...
@@ -3739,15 +3768,64 @@ bool amf_n1::reroute_registration_request(std::shared_ptr<nas_context>& nc) {
//------------------------------------------------------------------------------
bool
amf_n1
::
check_requested_nssai
(
const
std
::
shared_ptr
<
nas_context
>&
nc
,
const
nssai_t
&
nssai
)
const
{
//
TODO
const
std
::
shared_ptr
<
nas_context
>&
nc
,
oai
::
amf
::
model
::
Nssai
&
nssai
)
{
//
Check if the AMF can serve all the subscribed S-NSSAIs
return
true
;
std
::
shared_ptr
<
ue_context
>
uc
=
{};
if
(
!
find_ue_context
(
nc
.
get
()
->
ran_ue_ngap_id
,
nc
.
get
()
->
amf_ue_ngap_id
,
uc
))
{
Logger
::
amf_n1
().
warn
(
"Cannot find the UE context"
);
return
false
;
}
bool
result
=
false
;
for
(
auto
p
:
amf_cfg
.
plmn_list
)
{
// Check PLMN/TAC
if
((
uc
.
get
()
->
tai
.
mcc
.
compare
(
p
.
mcc
)
!=
0
)
or
(
uc
.
get
()
->
tai
.
mnc
.
compare
(
p
.
mnc
)
!=
0
)
or
(
uc
.
get
()
->
tai
.
tac
!=
p
.
tac
))
{
continue
;
}
result
=
true
;
// Each S-NSSAI in the Default Single NSSAIs must be in the AMF's Slice List
for
(
auto
n
:
nssai
.
getDefaultSingleNssais
())
{
bool
found_nssai
=
false
;
for
(
auto
s
:
p
.
slice_list
)
{
uint8_t
sst
=
0
;
try
{
sst
=
std
::
stoi
(
s
.
sST
);
}
catch
(
const
std
::
exception
&
err
)
{
Logger
::
amf_n1
().
warn
(
"Invalid SST"
);
continue
;
}
if
(
sst
==
n
.
getSst
())
{
if
((
n
.
sdIsSet
()
and
(
n
.
getSd
().
compare
(
s
.
sD
)
==
0
))
or
(
!
n
.
sdIsSet
()
and
s
.
sD
.
empty
()))
{
found_nssai
=
true
;
Logger
::
amf_n1
().
debug
(
"Found S-NSSAI (SST %d, SD %s)"
,
sst
,
n
.
getSd
().
c_str
());
break
;
}
}
}
if
(
!
found_nssai
)
{
result
=
false
;
break
;
}
}
}
return
result
;
}
//------------------------------------------------------------------------------
bool
amf_n1
::
get_slice_selection_subscription_data
(
const
std
::
shared_ptr
<
nas_context
>&
nc
,
nssai_t
&
nssai
)
{
const
std
::
shared_ptr
<
nas_context
>&
nc
,
oai
::
amf
::
model
::
Nssai
&
nssai
)
{
// TODO: UDM selection (from NRF or configuration file)
if
(
amf_cfg
.
support_features
.
enable_external_udm
)
{
std
::
shared_ptr
<
ue_context
>
uc
=
{};
...
...
@@ -3792,9 +3870,12 @@ bool amf_n1::get_slice_selection_subscription_data(
assert
(
f
.
has_value
());
assert
(
!
f
.
has_exception
());
// Wait for the result from UDM
nlohmann
::
json
nssai
=
f
.
get
();
if
(
!
nssai
.
empty
())
{
Logger
::
ngap
().
debug
(
"Got NSSAI from UDM: %s"
,
nssai
.
dump
().
c_str
());
nlohmann
::
json
nssai_json
=
f
.
get
();
if
(
!
nssai_json
.
empty
())
{
Logger
::
ngap
().
debug
(
"Got NSSAI from UDM: %s"
,
nssai_json
.
dump
().
c_str
());
from_json
(
nssai_json
,
nssai
);
return
true
;
}
else
{
return
false
;
}
...
...
@@ -3812,7 +3893,7 @@ bool amf_n1::get_slice_selection_subscription_data(
//------------------------------------------------------------------------------
bool
amf_n1
::
get_slice_selection_subscription_data_from_conf_file
(
const
std
::
shared_ptr
<
nas_context
>&
nc
,
nssai_t
&
nssai
)
{
const
std
::
shared_ptr
<
nas_context
>&
nc
,
oai
::
amf
::
model
::
Nssai
&
nssai
)
{
// TODO:
return
true
;
}
...
...
@@ -3911,6 +3992,11 @@ bool amf_n1::get_target_amf(
const
oai
::
amf
::
model
::
AuthorizedNetworkSliceInfo
&
authorized_network_slice_info
)
{
// Get Target AMF from AuthorizedNetworkSliceInfo
std
::
string
target_amf_set
=
{};
if
(
authorized_network_slice_info
.
targetAmfSetIsSet
())
{
target_amf_set
=
authorized_network_slice_info
.
getTargetAmfSet
();
}
return
true
;
}
...
...
src/amf-app/amf_n1.hpp
View file @
8a310228
...
...
@@ -38,7 +38,6 @@
#include "3gpp_ts24501.hpp"
#include "3gpp_29.503.h"
#include "3gpp_29.531.h"
#include "amf.hpp"
#include "amf_statistics.hpp"
#include "bstrlib.h"
...
...
@@ -52,6 +51,7 @@
#include "itti.hpp"
#include "SliceInfoForRegistration.h"
#include "AuthorizedNetworkSliceInfo.h"
#include "Nssai.h"
namespace
amf_application
{
...
...
@@ -223,11 +223,11 @@ class amf_n1 {
bool
reroute_registration_request
(
std
::
shared_ptr
<
nas_context
>&
nc
);
bool
get_slice_selection_subscription_data
(
const
std
::
shared_ptr
<
nas_context
>&
nc
,
nssai_t
&
nssai
);
const
std
::
shared_ptr
<
nas_context
>&
nc
,
oai
::
amf
::
model
::
Nssai
&
nssai
);
bool
get_slice_selection_subscription_data_from_conf_file
(
const
std
::
shared_ptr
<
nas_context
>&
nc
,
nssai_t
&
nssai
);
const
std
::
shared_ptr
<
nas_context
>&
nc
,
oai
::
amf
::
model
::
Nssai
&
nssai
);
bool
check_requested_nssai
(
const
std
::
shared_ptr
<
nas_context
>&
nc
,
const
nssai_t
&
nssai
)
const
;
const
std
::
shared_ptr
<
nas_context
>&
nc
,
oai
::
amf
::
model
::
Nssai
&
nssai
)
;
bool
get_network_slice_selection
(
const
std
::
shared_ptr
<
nas_context
>&
nc
,
const
std
::
string
&
nf_instance_id
,
const
oai
::
amf
::
model
::
SliceInfoForRegistration
&
slice_info
,
...
...
src/amf-app/amf_n2.cpp
View file @
8a310228
...
...
@@ -1411,8 +1411,8 @@ bool amf_n2::handle_itti_message(itti_handover_required& itti_msg) {
GlobalgNBId
*
TargetGlobalgNBId
=
new
GlobalgNBId
();
itti_msg
.
handoverReq
->
getGlobalRanNodeId
(
TargetGlobalgNBId
);
PlmnId
*
plmn
=
new
PlmnId
();
GNB_ID
*
gnbid
=
new
GNB_ID
();
ngap
::
PlmnId
*
plmn
=
new
ngap
::
PlmnId
();
GNB_ID
*
gnbid
=
new
GNB_ID
();
TargetGlobalgNBId
->
getGlobalgNBId
(
plmn
,
gnbid
);
std
::
string
mcc
=
{};
std
::
string
mnc
=
{};
...
...
@@ -1426,8 +1426,8 @@ bool amf_n2::handle_itti_message(itti_handover_required& itti_msg) {
TAI
*
tai
=
new
TAI
();
itti_msg
.
handoverReq
->
getTAI
(
tai
);
PlmnId
*
plmnOfTAI
=
new
PlmnId
();
TAC
*
tac
=
new
TAC
();
ngap
::
PlmnId
*
plmnOfTAI
=
new
ngap
::
PlmnId
();
TAC
*
tac
=
new
TAC
();
tai
->
getTAI
(
plmnOfTAI
,
tac
);
string
mccOfselectTAI
=
{};
string
mncOfselectTAI
=
{};
...
...
@@ -1478,7 +1478,7 @@ bool amf_n2::handle_itti_message(itti_handover_required& itti_msg) {
guami
.
regionID
=
amf_cfg
.
guami
.
regionID
;
guami
.
AmfSetID
=
amf_cfg
.
guami
.
AmfSetID
;
guami
.
AmfPointer
=
amf_cfg
.
guami
.
AmfPointer
;
PlmnId
*
m_plmnId
=
new
PlmnId
();
ngap
::
PlmnId
*
m_plmnId
=
new
ngap
::
PlmnId
();
AMFRegionID
*
m_aMFRegionID
=
new
AMFRegionID
();
AMFSetID
*
m_aMFSetID
=
new
AMFSetID
();
AMFPointer
*
m_aMFPointer
=
new
AMFPointer
();
...
...
src/itti/msgs/itti_msg_n11.hpp
View file @
8a310228
...
...
@@ -28,7 +28,6 @@
#include "amf_profile.hpp"
#include "bstrlib.h"
#include "itti_msg.hpp"
#include "3gpp_29.531.h"
#include "SliceInfoForRegistration.h"
class
itti_msg_n11
:
public
itti_msg
{
...
...
src/sbi/amf_server/model/Nssai.cpp
0 → 100644
View file @
8a310228
/*
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the
* License at
*
* http://www.openairinterface.org/?page_id=698
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*-------------------------------------------------------------------------------
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
/**
* Nudm_SDM
* Nudm Subscriber Data Management Service. � 2019, 3GPP Organizational Partners
* (ARIB, ATIS, CCSA, ETSI, TSDSI, TTA, TTC). All rights reserved.
*
* The version of the OpenAPI document: 2.1.0.alpha-1
*
*
* NOTE: This class is auto generated by OpenAPI Generator
* (https://openapi-generator.tech). https://openapi-generator.tech Do not edit
* the class manually.
*/
#include "Nssai.h"
namespace
oai
{
namespace
amf
{
namespace
model
{
Nssai
::
Nssai
()
{
m_SupportedFeatures
=
""
;
m_SupportedFeaturesIsSet
=
false
;
m_SingleNssaisIsSet
=
false
;
}
Nssai
::~
Nssai
()
{}
void
Nssai
::
validate
()
{
// TODO: implement validation
}
void
to_json
(
nlohmann
::
json
&
j
,
const
Nssai
&
o
)
{
j
=
nlohmann
::
json
();
if
(
o
.
supportedFeaturesIsSet
())
j
[
"supportedFeatures"
]
=
o
.
m_SupportedFeatures
;
j
[
"defaultSingleNssais"
]
=
o
.
m_DefaultSingleNssais
;
if
(
o
.
singleNssaisIsSet
())
j
[
"singleNssais"
]
=
o
.
m_SingleNssais
;
}
void
from_json
(
const
nlohmann
::
json
&
j
,
Nssai
&
o
)
{
if
(
j
.
find
(
"supportedFeatures"
)
!=
j
.
end
())
{
j
.
at
(
"supportedFeatures"
).
get_to
(
o
.
m_SupportedFeatures
);
o
.
m_SupportedFeaturesIsSet
=
true
;
}
j
.
at
(
"defaultSingleNssais"
).
get_to
(
o
.
m_DefaultSingleNssais
);
if
(
j
.
find
(
"singleNssais"
)
!=
j
.
end
())
{
j
.
at
(
"singleNssais"
).
get_to
(
o
.
m_SingleNssais
);
o
.
m_SingleNssaisIsSet
=
true
;
}
}
std
::
string
Nssai
::
getSupportedFeatures
()
const
{
return
m_SupportedFeatures
;
}
void
Nssai
::
setSupportedFeatures
(
std
::
string
const
&
value
)
{
m_SupportedFeatures
=
value
;
m_SupportedFeaturesIsSet
=
true
;
}
bool
Nssai
::
supportedFeaturesIsSet
()
const
{
return
m_SupportedFeaturesIsSet
;
}
void
Nssai
::
unsetSupportedFeatures
()
{
m_SupportedFeaturesIsSet
=
false
;
}
std
::
vector
<
Snssai
>&
Nssai
::
getDefaultSingleNssais
()
{
return
m_DefaultSingleNssais
;
}
std
::
vector
<
Snssai
>&
Nssai
::
getSingleNssais
()
{
return
m_SingleNssais
;
}
bool
Nssai
::
singleNssaisIsSet
()
const
{
return
m_SingleNssaisIsSet
;
}
void
Nssai
::
unsetSingleNssais
()
{
m_SingleNssaisIsSet
=
false
;
}
}
// namespace model
}
// namespace amf
}
// namespace oai
src/
common/3gpp_29.531
.h
→
src/
sbi/amf_server/model/Nssai
.h
View file @
8a310228
...
...
@@ -4,8 +4,8 @@
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this
*file except in compliance with the License. You may obtain a copy of the
*License at
*
file except in compliance with the License. You may obtain a copy of the
*
License at
*
* http://www.openairinterface.org/?page_id=698
*
...
...
@@ -18,78 +18,84 @@
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
/*! \file 3gpp_29.531.h
\brief
\author Tien Thinh NGUYEN
\company Eurecom
\email: tien-thinh.nguyen@eurecom.fr
/**
* Nudm_SDM
* Nudm Subscriber Data Management Service. � 2019, 3GPP Organizational Partners
* (ARIB, ATIS, CCSA, ETSI, TSDSI, TTA, TTC). All rights reserved.
*
* The version of the OpenAPI document: 2.1.0.alpha-1
*
*
* NOTE: This class is auto generated by OpenAPI Generator
* (https://openapi-generator.tech). https://openapi-generator.tech Do not edit
* the class manually.
*/
/*
* Nssai.h
*
*
*/
#ifndef FILE_3GPP_29_531_SEEN
#define FILE_3GPP_29_531_SEEN
#include <stdint.h>
#ifndef Nssai_H_
#define Nssai_H_
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
#include "3gpp_23.003.h"
typedef
struct
subscribed_snssai_s
{
snssai_t
subscribed_snssai
;
}
subscribed_snssai_t
;
#include "Snssai.h"
using
namespace
oai
::
amf
::
model
;
namespace
oai
{
namespace
amf
{
namespace
model
{
typedef
struct
nsi_information_s
{
std
::
string
nrf_id
;
// TODO: nsiId
// TODO: nrfNfMgtUri
// TODO: nrfAccessTokenUri
}
nsi_information_t
;
/// <summary>
///
/// </summary>
class
Nssai
{
public:
Nssai
();
virtual
~
Nssai
();
typedef
struct
allowed_snssai_s
{
snssai_t
allowed_snssai
;
std
::
vector
<
nsi_information_t
>
nsi_information_list
;
snssai_t
mapped_home_snssai
;
}
allowed_snssai_t
;
void
validate
();
typedef
struct
allowed_nssai_s
{
std
::
vector
<
allowed_snssai_t
>
allowed_snssai_list
;
std
::
string
access_type
;
}
allowed_nssai_t
;
/////////////////////////////////////////////
/// Nssai members
typedef
struct
mapping_of_snssai_s
{
snssai_t
serving_snssai
;
snssai_t
home_snssai
;
}
mapping_of_snssai_t
;
/// <summary>
///
/// </summary>
std
::
string
getSupportedFeatures
()
const
;
void
setSupportedFeatures
(
std
::
string
const
&
value
);
bool
supportedFeaturesIsSet
()
const
;
void
unsetSupportedFeatures
();
/// <summary>
///
/// </summary>
std
::
vector
<
Snssai
>&
getDefaultSingleNssais
();
/// <summary>
///
/// </summary>
std
::
vector
<
Snssai
>&
getSingleNssais
();
bool
singleNssaisIsSet
()
const
;
void
unsetSingleNssais
();
typedef
struct
slice_info_for_registration_s
{
std
::
vector
<
subscribed_snssai_t
>
subscribed_nssai
;
std
::
vector
<
allowed_nssai_t
>
allowed_nssai_current_access
;
// allowed_nssai_other_access
std
::
vector
<
snssai_t
>
sNssai_for_mapping
;
std
::
vector
<
snssai_t
>
requested_nssai
;
bool
default_configured_snssai_ind
;
std
::
vector
<
mapping_of_snssai_t
>
mapping_of_nssai
;
bool
request_mapping
;
}
slice_info_for_registration_t
;
friend
void
to_json
(
nlohmann
::
json
&
j
,
const
Nssai
&
o
);
friend
void
from_json
(
const
nlohmann
::
json
&
j
,
Nssai
&
o
);
typedef
struct
configured_snssai_s
{
s
nssai_t
configured_snssai
;
snssai_t
mapped_home_snssai
;
}
configured_snssai_t
;
protected:
s
td
::
string
m_SupportedFeatures
;
bool
m_SupportedFeaturesIsSet
;
std
::
vector
<
Snssai
>
m_DefaultSingleNssais
;
typedef
struct
authorized_network_slice_info_s
{
std
::
vector
<
allowed_nssai_t
>
allowed_nssai_list
;
std
::
vector
<
configured_snssai_t
>
configured_nssai
;
std
::
string
target_amf_set
;
std
::
vector
<
std
::
string
>
candidate_amf_list
;
std
::
vector
<
snssai_t
>
rejected_nssai_in_plmn
;
std
::
vector
<
snssai_t
>
rejected_nssai_in_ta
;
nsi_information_t
nsi_information
;
// SupportedFeatures
std
::
string
nrf_amf_set
;
std
::
string
nrf_amf_set_nf_mgt_uri
;
std
::
string
nrf_amf_set_access_token_uri
;
std
::
vector
<
Snssai
>
m_SingleNssais
;
bool
m_SingleNssaisIsSet
;
};
}
authorized_network_slice_info_t
;
}
// namespace model
}
// namespace amf
}
// namespace oai
#endif
#endif
/* Nssai_H_ */
src/utils/3gpp_conversions.hpp
View file @
8a310228
...
...
@@ -34,7 +34,7 @@
#include "3gpp_29.518.h"
using
namespace
amf_application
;
using
namespace
oai
::
amf
::
model
;
//
using namespace oai::amf::model;
namespace
xgpp_conv
{
/*
...
...
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