Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
O
OpenXG-SMF-Simple
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
CommunityXG
OpenXG-SMF-Simple
Commits
251022eb
Commit
251022eb
authored
May 05, 2020
by
Tien-Thinh Nguyen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
finalize PDU Session Modification procedure (step 2 and 3)
parent
0d1c5fd6
Changes
3
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
393 additions
and
154 deletions
+393
-154
src/smf_app/smf_context.cpp
src/smf_app/smf_context.cpp
+79
-146
src/smf_app/smf_n1_n2.cpp
src/smf_app/smf_n1_n2.cpp
+105
-3
src/test/amf_client/amf-client.cpp
src/test/amf_client/amf-client.cpp
+209
-5
No files found.
src/smf_app/smf_context.cpp
View file @
251022eb
This diff is collapsed.
Click to expand it.
src/smf_app/smf_n1_n2.cpp
View file @
251022eb
...
...
@@ -57,6 +57,9 @@ extern "C" {
#include "Ngap_PDUSessionResourceReleaseCommandTransfer.h"
#include "dynamic_memory_check.h"
#include "Ngap_PDUSessionResourceReleaseResponseTransfer.h"
#include "Ngap_QosFlowAddOrModifyResponseItem.h"
#include "Ngap_QosFlowAddOrModifyResponseList.h"
}
#define BUF_LEN 512
...
...
@@ -1330,6 +1333,105 @@ void smf_n1_n2::create_n2_sm_information(pdu_session_msg &msg,
}
break
;
case
n2_sm_info_type_e
:
:
PDU_RES_MOD_RSP
:
{
//PDU Session Resource Modify Response Transfer IE
//for testing purpose
Logger
::
smf_app
().
debug
(
"[Create N2 SM Information] NGAP PDU Session Resource Modify Response Transfer"
);
//struct Ngap_UPTransportLayerInformation *dL_NGU_UP_TNLInformation; /* OPTIONAL */
//struct Ngap_UPTransportLayerInformation *uL_NGU_UP_TNLInformation; /* OPTIONAL */
//struct Ngap_QosFlowAddOrModifyResponseList *qosFlowAddOrModifyResponseList; /* OPTIONAL */
//struct Ngap_QosFlowPerTNLInformationList *additionalDLQosFlowPerTNLInformation; /* OPTIONAL */
// struct Ngap_QosFlowListWithCause *qosFlowFailedToAddOrModifyList; /* OPTIONAL */
Ngap_PDUSessionResourceModifyResponseTransfer_t
*
ngap_resource_response_transfer
=
nullptr
;
ngap_resource_response_transfer
=
(
Ngap_PDUSessionResourceModifyResponseTransfer_t
*
)
calloc
(
1
,
sizeof
(
Ngap_PDUSessionResourceModifyResponseTransfer_t
));
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
=
(
Ngap_UPTransportLayerInformation
*
)
calloc
(
1
,
sizeof
(
Ngap_UPTransportLayerInformation
));
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
present
=
Ngap_UPTransportLayerInformation_PR_gTPTunnel
;
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
choice
.
gTPTunnel
=
(
Ngap_GTPTunnel_t
*
)
calloc
(
1
,
sizeof
(
Ngap_GTPTunnel_t
));
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
choice
.
gTPTunnel
->
transportLayerAddress
.
size
=
4
;
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
choice
.
gTPTunnel
->
transportLayerAddress
.
buf
=
(
uint8_t
*
)
calloc
(
4
,
sizeof
(
uint8_t
));
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
choice
.
gTPTunnel
->
transportLayerAddress
.
buf
[
0
]
=
0xc0
;
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
choice
.
gTPTunnel
->
transportLayerAddress
.
buf
[
1
]
=
0xa8
;
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
choice
.
gTPTunnel
->
transportLayerAddress
.
buf
[
2
]
=
0xf8
;
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
choice
.
gTPTunnel
->
transportLayerAddress
.
buf
[
3
]
=
0x9f
;
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
choice
.
gTPTunnel
->
transportLayerAddress
.
bits_unused
=
0
;
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
choice
.
gTPTunnel
->
gTP_TEID
.
size
=
4
;
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
choice
.
gTPTunnel
->
gTP_TEID
.
buf
=
(
uint8_t
*
)
calloc
(
4
,
sizeof
(
uint8_t
));
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
choice
.
gTPTunnel
->
gTP_TEID
.
buf
[
0
]
=
0x00
;
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
choice
.
gTPTunnel
->
gTP_TEID
.
buf
[
1
]
=
0x00
;
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
choice
.
gTPTunnel
->
gTP_TEID
.
buf
[
2
]
=
0x00
;
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
choice
.
gTPTunnel
->
gTP_TEID
.
buf
[
3
]
=
0x01
;
ngap_resource_response_transfer
->
qosFlowAddOrModifyResponseList
=
(
Ngap_QosFlowAddOrModifyResponseList_t
*
)
calloc
(
1
,
sizeof
(
Ngap_QosFlowAddOrModifyResponseList_t
));
Ngap_QosFlowAddOrModifyResponseItem
*
qosFlowAddOrModifyResponseItem
=
nullptr
;
qosFlowAddOrModifyResponseItem
=
(
Ngap_QosFlowAddOrModifyResponseItem
*
)
calloc
(
1
,
sizeof
(
Ngap_QosFlowAddOrModifyResponseItem
));
qosFlowAddOrModifyResponseItem
->
qosFlowIdentifier
=
60
;
ASN_SEQUENCE_ADD
(
&
ngap_resource_response_transfer
->
qosFlowAddOrModifyResponseList
->
list
,
qosFlowAddOrModifyResponseItem
);
//encode
size_t
buffer_size
=
512
;
char
*
buffer
=
(
char
*
)
calloc
(
1
,
buffer_size
);
asn_enc_rval_t
er
=
aper_encode_to_buffer
(
&
asn_DEF_Ngap_PDUSessionResourceModifyResponseTransfer
,
nullptr
,
ngap_resource_response_transfer
,
(
void
*
)
buffer
,
buffer_size
);
if
(
er
.
encoded
<
0
)
{
Logger
::
smf_app
().
warn
(
"[Create N2 SM Information] NGAP PDU Session Resource Modify Response Transfer encode failed, er.encoded: %d"
,
er
.
encoded
);
return
;
}
Logger
::
smf_app
().
debug
(
"N2 SM buffer data: "
);
for
(
int
i
=
0
;
i
<
er
.
encoded
;
i
++
)
printf
(
"%02x "
,
(
char
)
buffer
[
i
]);
Logger
::
smf_app
().
debug
(
" (%d bytes)
\n
"
,
er
.
encoded
);
std
::
string
ngap_message
((
char
*
)
buffer
,
er
.
encoded
);
ngap_msg_str
=
ngap_message
;
//free memory
free_wrapper
(
(
void
**
)
&
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
choice
.
gTPTunnel
->
transportLayerAddress
.
buf
);
free_wrapper
(
(
void
**
)
&
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
choice
.
gTPTunnel
->
gTP_TEID
.
buf
);
free_wrapper
(
(
void
**
)
&
ngap_resource_response_transfer
->
dL_NGU_UP_TNLInformation
->
choice
.
gTPTunnel
);
free_wrapper
((
void
**
)
&
qosFlowAddOrModifyResponseItem
);
free_wrapper
((
void
**
)
&
ngap_resource_response_transfer
->
qosFlowAddOrModifyResponseList
);
free_wrapper
((
void
**
)
&
ngap_resource_response_transfer
);
free_wrapper
((
void
**
)
&
buffer
);
}
break
;
//PDU Session Resource Release Command Transfer
case
n2_sm_info_type_e
:
:
PDU_RES_REL_CMD
:
{
//PDU Session Resource Release Command Transfer IE
...
...
@@ -1513,7 +1615,7 @@ int smf_n1_n2::decode_n2_sm_information(
free_wrapper
((
void
**
)
&
data
);
if
(
rc
.
code
!=
RC_OK
)
{
Logger
::
smf_ap
i_server
().
warn
(
"asn_decode failed with code %d"
,
rc
.
code
);
Logger
::
smf_ap
p
().
warn
(
"asn_decode failed with code %d"
,
rc
.
code
);
return
RETURNerror
;
}
return
RETURNok
;
...
...
@@ -1542,7 +1644,7 @@ int smf_n1_n2::decode_n2_sm_information(
free_wrapper
((
void
**
)
&
data
);
if
(
rc
.
code
!=
RC_OK
)
{
Logger
::
smf_ap
i_server
().
warn
(
"asn_decode failed with code %d"
,
rc
.
code
);
Logger
::
smf_ap
p
().
warn
(
"asn_decode failed with code %d"
,
rc
.
code
);
return
RETURNerror
;
}
...
...
@@ -1572,7 +1674,7 @@ int smf_n1_n2::decode_n2_sm_information(
free_wrapper
((
void
**
)
&
data
);
if
(
rc
.
code
!=
RC_OK
)
{
Logger
::
smf_ap
i_server
().
warn
(
"asn_decode failed with code %d"
,
rc
.
code
);
Logger
::
smf_ap
p
().
warn
(
"asn_decode failed with code %d"
,
rc
.
code
);
return
RETURNerror
;
}
...
...
src/test/amf_client/amf-client.cpp
View file @
251022eb
...
...
@@ -290,10 +290,10 @@ void send_pdu_session_update_sm_context_establishment(
ENCODE_U8
(
buffer
,
0x00
,
size
);
ENCODE_U8
(
buffer
+
size
,
0x03
,
size
);
ENCODE_U8
(
buffer
+
size
,
0xe0
,
size
);
ENCODE_U8
(
buffer
+
size
,
0xac
,
size
);
//uPTransportLayerInformation IP Addr 172.1
0.5.1
: 172.
ENCODE_U8
(
buffer
+
size
,
0x
0a
,
size
);
//10
ENCODE_U8
(
buffer
+
size
,
0x0
5
,
size
);
//.5
ENCODE_U8
(
buffer
+
size
,
0x
01
,
size
);
//.1
ENCODE_U8
(
buffer
+
size
,
0xac
,
size
);
//uPTransportLayerInformation IP Addr 172.1
6.3.103
: 172.
ENCODE_U8
(
buffer
+
size
,
0x
10
,
size
);
//16
ENCODE_U8
(
buffer
+
size
,
0x0
3
,
size
);
//.3
ENCODE_U8
(
buffer
+
size
,
0x
67
,
size
);
//.103
ENCODE_U8
(
buffer
+
size
,
0x00
,
size
);
//gTP_TEID 00 00 00 01: 00
ENCODE_U8
(
buffer
+
size
,
0x00
,
size
);
//00
ENCODE_U8
(
buffer
+
size
,
0x00
,
size
);
//00
...
...
@@ -496,6 +496,207 @@ void send_pdu_session_modification_request_step1(std::string smf_ip_address) {
free
(
buffer
);
}
//------------------------------------------------------------------------------
void
send_pdu_session_modification_request_step2
(
std
::
string
smf_ip_address
)
{
std
::
cout
<<
"[AMF N11] PDU Session Modification procedure (SM Context Update, step 2)"
<<
std
::
endl
;
nlohmann
::
json
pdu_session_modification
;
//encode PDU Session Resource Modify Response Transfer IE
size_t
buffer_size
=
128
;
char
*
buffer
=
(
char
*
)
calloc
(
1
,
buffer_size
);
int
size
=
0
;
//ENCODE_U8(buffer, 0x00, size);
ENCODE_U8
(
buffer
+
size
,
0x50
,
size
);
ENCODE_U8
(
buffer
+
size
,
0x03
,
size
);
ENCODE_U8
(
buffer
+
size
,
0xe0
,
size
);
//Id dL_NGU_UP_TNLInformation
ENCODE_U8
(
buffer
+
size
,
0xac
,
size
);
//Transport Layer Address 172.16.3.101: 172
ENCODE_U8
(
buffer
+
size
,
0x10
,
size
);
//Transport Layer Address 172.16.3.101: 16
ENCODE_U8
(
buffer
+
size
,
0x03
,
size
);
//Transport Layer Address 172.16.3.101: 3
ENCODE_U8
(
buffer
+
size
,
0x65
,
size
);
//Transport Layer Address 172.16.3.101: 101
ENCODE_U8
(
buffer
+
size
,
0x00
,
size
);
//Gtp-teid: 01000000
ENCODE_U8
(
buffer
+
size
,
0x00
,
size
);
//Gtp-teid: 01000000
ENCODE_U8
(
buffer
+
size
,
0x00
,
size
);
//Gtp-teid: 01000000
ENCODE_U8
(
buffer
+
size
,
0x01
,
size
);
//Gtp-teid: 01000000
ENCODE_U8
(
buffer
+
size
,
0x00
,
size
);
//QoSFlowAddorModifyResponseList
ENCODE_U8
(
buffer
+
size
,
0x78
,
size
);
//60: QFI
/*
struct Ngap_UPTransportLayerInformation *dL_NGU_UP_TNLInformation;
struct Ngap_UPTransportLayerInformation *uL_NGU_UP_TNLInformation;
struct Ngap_QosFlowAddOrModifyResponseList *qosFlowAddOrModifyResponseList;
struct Ngap_QosFlowPerTNLInformationList *additionalDLQosFlowPerTNLInformation;
struct Ngap_QosFlowListWithCause *qosFlowFailedToAddOrModifyList;
struct Ngap_ProtocolExtensionContainer *iE_Extensions;
*/
std
::
cout
<<
"Buffer: "
<<
std
::
endl
;
for
(
int
i
=
0
;
i
<
size
;
i
++
)
{
printf
(
"%02x "
,
buffer
[
i
]);
}
std
::
cout
<<
"Buffer: "
<<
std
::
endl
;
std
::
string
url
=
std
::
string
(
"http://"
);
url
.
append
(
smf_ip_address
);
url
.
append
(
std
::
string
(
"/nsmf-pdusession/v2/sm-contexts/1/modify"
));
//Fill the json part
pdu_session_modification
[
"n2SmInfoType"
]
=
"PDU_RES_MOD_RSP"
;
//"PDU_RES_SETUP_RSP"
pdu_session_modification
[
"n2SmInfo"
][
"contentId"
]
=
"n2SmMsg"
;
//NGAP
std
::
string
body
;
std
::
string
boundary
=
"----Boundary"
;
std
::
string
json_part
=
pdu_session_modification
.
dump
();
std
::
string
n2_msg
(
reinterpret_cast
<
const
char
*>
(
buffer
),
size
);
create_multipart_related_content
(
body
,
json_part
,
boundary
,
n2_msg
,
multipart_related_content_part_e
::
NGAP
);
unsigned
char
*
data
=
(
unsigned
char
*
)
malloc
(
body
.
length
()
+
1
);
memset
(
data
,
0
,
body
.
length
()
+
1
);
memcpy
((
void
*
)
data
,
(
void
*
)
body
.
c_str
(),
body
.
length
());
curl_global_init
(
CURL_GLOBAL_ALL
);
CURL
*
curl
=
curl
=
curl_easy_init
();
if
(
curl
)
{
CURLcode
res
=
{
};
struct
curl_slist
*
headers
=
nullptr
;
//headers = curl_slist_append(headers, "charsets: utf-8");
headers
=
curl_slist_append
(
headers
,
"content-type: multipart/related; boundary=----Boundary"
);
curl_easy_setopt
(
curl
,
CURLOPT_HTTPHEADER
,
headers
);
curl_easy_setopt
(
curl
,
CURLOPT_URL
,
url
.
c_str
());
curl_easy_setopt
(
curl
,
CURLOPT_HTTPGET
,
1
);
curl_easy_setopt
(
curl
,
CURLOPT_TIMEOUT_MS
,
100L
);
//curl_easy_setopt(curl, CURLOPT_INTERFACE, "eno1:amf"); //hardcoded
// Response information.
long
httpCode
=
{
0
};
std
::
unique_ptr
<
std
::
string
>
httpData
(
new
std
::
string
());
curl_easy_setopt
(
curl
,
CURLOPT_WRITEFUNCTION
,
&
callback
);
curl_easy_setopt
(
curl
,
CURLOPT_WRITEDATA
,
httpData
.
get
());
curl_easy_setopt
(
curl
,
CURLOPT_POSTFIELDSIZE
,
body
.
length
());
curl_easy_setopt
(
curl
,
CURLOPT_POSTFIELDS
,
data
);
res
=
curl_easy_perform
(
curl
);
curl_easy_getinfo
(
curl
,
CURLINFO_RESPONSE_CODE
,
&
httpCode
);
//get cause from the response
nlohmann
::
json
response_data
;
try
{
response_data
=
nlohmann
::
json
::
parse
(
*
httpData
.
get
());
}
catch
(
nlohmann
::
json
::
exception
&
e
)
{
std
::
cout
<<
"Could not get json data from the response"
<<
std
::
endl
;
}
std
::
cout
<<
"[AMF N11] PDU Session Modification procedure (step 2), response from SMF, Http Code "
<<
httpCode
<<
std
::
endl
;
curl_easy_cleanup
(
curl
);
}
curl_global_cleanup
();
free
(
buffer
);
}
//------------------------------------------------------------------------------
void
send_pdu_session_modification_complete
(
std
::
string
smf_ip_address
)
{
std
::
cout
<<
"[AMF N11] PDU Session Modification Complete (Update SM Context): N1 SM - PDU Session Modification Complete"
<<
std
::
endl
;
nlohmann
::
json
pdu_session_modification_complete
;
//encode PDU Session Modification Complete
size_t
buffer_size
=
128
;
char
*
buffer
=
(
char
*
)
calloc
(
1
,
buffer_size
);
int
size
=
0
;
ENCODE_U8
(
buffer
,
0x2e
,
size
);
//ExtendedProtocolDiscriminator
ENCODE_U8
(
buffer
+
size
,
0x01
,
size
);
//PDUSessionIdentity
ENCODE_U8
(
buffer
+
size
,
0x01
,
size
);
//ProcedureTransactionIdentity
ENCODE_U8
(
buffer
+
size
,
0xcc
,
size
);
//MessageType
ENCODE_U8
(
buffer
+
size
,
0x00
,
size
);
//presence
ENCODE_U8
(
buffer
+
size
,
0x00
,
size
);
//Extended protocol configuration options
std
::
cout
<<
"Buffer: "
<<
std
::
endl
;
for
(
int
i
=
0
;
i
<
size
;
i
++
)
{
printf
(
"%02x "
,
buffer
[
i
]);
}
std
::
cout
<<
"Buffer: "
<<
std
::
endl
;
std
::
string
url
=
std
::
string
(
"http://"
);
url
.
append
(
smf_ip_address
);
url
.
append
(
std
::
string
(
"/nsmf-pdusession/v2/sm-contexts/1/modify"
));
//Fill the json part
pdu_session_modification_complete
[
"n1SmMsg"
][
"contentId"
]
=
"n1SmMsg"
;
// NAS
std
::
string
body
;
std
::
string
boundary
=
"----Boundary"
;
std
::
string
json_part
=
pdu_session_modification_complete
.
dump
();
std
::
string
n1_msg
(
reinterpret_cast
<
const
char
*>
(
buffer
),
size
);
create_multipart_related_content
(
body
,
json_part
,
boundary
,
n1_msg
,
multipart_related_content_part_e
::
NAS
);
unsigned
char
*
data
=
(
unsigned
char
*
)
malloc
(
body
.
length
()
+
1
);
memset
(
data
,
0
,
body
.
length
()
+
1
);
memcpy
((
void
*
)
data
,
(
void
*
)
body
.
c_str
(),
body
.
length
());
curl_global_init
(
CURL_GLOBAL_ALL
);
CURL
*
curl
=
curl
=
curl_easy_init
();
if
(
curl
)
{
CURLcode
res
=
{
};
struct
curl_slist
*
headers
=
nullptr
;
//headers = curl_slist_append(headers, "charsets: utf-8");
headers
=
curl_slist_append
(
headers
,
"content-type: multipart/related; boundary=----Boundary"
);
curl_easy_setopt
(
curl
,
CURLOPT_HTTPHEADER
,
headers
);
curl_easy_setopt
(
curl
,
CURLOPT_URL
,
url
.
c_str
());
curl_easy_setopt
(
curl
,
CURLOPT_HTTPGET
,
1
);
curl_easy_setopt
(
curl
,
CURLOPT_TIMEOUT_MS
,
100L
);
//curl_easy_setopt(curl, CURLOPT_INTERFACE, "eno1:amf"); //hardcoded
// Response information.
long
httpCode
=
{
0
};
std
::
unique_ptr
<
std
::
string
>
httpData
(
new
std
::
string
());
curl_easy_setopt
(
curl
,
CURLOPT_WRITEFUNCTION
,
&
callback
);
curl_easy_setopt
(
curl
,
CURLOPT_WRITEDATA
,
httpData
.
get
());
curl_easy_setopt
(
curl
,
CURLOPT_POSTFIELDSIZE
,
body
.
length
());
curl_easy_setopt
(
curl
,
CURLOPT_POSTFIELDS
,
data
);
res
=
curl_easy_perform
(
curl
);
curl_easy_getinfo
(
curl
,
CURLINFO_RESPONSE_CODE
,
&
httpCode
);
//get cause from the response
nlohmann
::
json
response_data
;
try
{
response_data
=
nlohmann
::
json
::
parse
(
*
httpData
.
get
());
}
catch
(
nlohmann
::
json
::
exception
&
e
)
{
std
::
cout
<<
"Could not get json data from the response"
<<
std
::
endl
;
}
std
::
cout
<<
"[AMF N11] PDU Session Modification Complete, response from SMF, Http Code "
<<
httpCode
<<
std
::
endl
;
curl_easy_cleanup
(
curl
);
}
curl_global_cleanup
();
free
(
buffer
);
}
//------------------------------------------------------------------------------
void
send_pdu_session_release_request
(
std
::
string
smf_ip_address
)
{
...
...
@@ -1054,6 +1255,9 @@ int main(int argc, char *argv[]) {
usleep
(
200000
);
//PDU Session Modification
send_pdu_session_modification_request_step1
(
smf_ip_address
);
send_pdu_session_modification_request_step2
(
smf_ip_address
);
send_pdu_session_modification_complete
(
smf_ip_address
);
//PDU Session Release procedure
send_pdu_session_release_request
(
smf_ip_address
);
usleep
(
200000
);
...
...
@@ -1063,7 +1267,7 @@ int main(int argc, char *argv[]) {
usleep
(
200000
);
//Release SM context
send_release_sm_context_request
(
smf_ip_address
);
//
send_release_sm_context_request(smf_ip_address);
return
0
;
}
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