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
53243dc3
Commit
53243dc3
authored
Oct 01, 2021
by
Raphael Defosseux
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'httpv2_support' into 'develop'
httpv2 support See merge request oai/cn5g/oai-cn5g-nrf!19
parents
c5e1f9b8
b0f69dc4
Changes
14
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
791 additions
and
23 deletions
+791
-23
src/api-server/CMakeLists.txt
src/api-server/CMakeLists.txt
+1
-0
src/api-server/nrf-http2-server.cpp
src/api-server/nrf-http2-server.cpp
+586
-0
src/api-server/nrf-http2-server.h
src/api-server/nrf-http2-server.h
+92
-0
src/common/nrf.h
src/common/nrf.h
+3
-1
src/common/utils/conversions.cpp
src/common/utils/conversions.cpp
+12
-0
src/common/utils/string.cpp
src/common/utils/string.cpp
+19
-0
src/common/utils/string.hpp
src/common/utils/string.hpp
+2
-0
src/nrf_app/nrf_app.cpp
src/nrf_app/nrf_app.cpp
+19
-9
src/nrf_app/nrf_app.hpp
src/nrf_app/nrf_app.hpp
+1
-1
src/nrf_app/nrf_client.cpp
src/nrf_app/nrf_client.cpp
+19
-8
src/nrf_app/nrf_client.hpp
src/nrf_app/nrf_client.hpp
+3
-3
src/nrf_app/nrf_subscription.cpp
src/nrf_app/nrf_subscription.cpp
+10
-0
src/nrf_app/nrf_subscription.hpp
src/nrf_app/nrf_subscription.hpp
+13
-0
src/oai-nrf/main.cpp
src/oai-nrf/main.cpp
+11
-1
No files found.
src/api-server/CMakeLists.txt
View file @
53243dc3
...
...
@@ -32,6 +32,7 @@ include_directories(${SRC_TOP_DIR}/../build/ext/spdlog/include)
file
(
GLOB NRF_API_SERVER_src_files
${
NRF_API_SERVER_DIR
}
/nrf-api-server.cpp
${
NRF_API_SERVER_DIR
}
/nrf-http2-server.cpp
${
NRF_API_SERVER_DIR
}
/model/*.cpp
${
NRF_API_SERVER_DIR
}
/api/*.cpp
${
NRF_API_SERVER_DIR
}
/impl/*.cpp
...
...
src/api-server/nrf-http2-server.cpp
0 → 100644
View file @
53243dc3
/*
* 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
*/
/*! \file nrf_http2-server.cpp
\brief
\author Tien-Thinh NGUYEN
\company Eurecom
\date 2020
\email: tien-thinh.nguyen@eurecom.fr
*/
#include "nrf-http2-server.h"
#include <boost/algorithm/string.hpp>
#include <boost/thread.hpp>
#include <boost/thread/future.hpp>
#include <regex>
#include <nlohmann/json.hpp>
#include <string>
#include "string.hpp"
#include "logger.hpp"
#include "nrf_config.hpp"
#include "3gpp_29.500.h"
#include "mime_parser.hpp"
using
namespace
nghttp2
::
asio_http2
;
using
namespace
nghttp2
::
asio_http2
::
server
;
using
namespace
oai
::
nrf
::
model
;
extern
nrf_config
nrf_cfg
;
//------------------------------------------------------------------------------
void
nrf_http2_server
::
start
()
{
boost
::
system
::
error_code
ec
;
Logger
::
nrf_app
().
info
(
"HTTP2 server started"
);
std
::
string
nfInstanceID
=
{};
std
::
string
subscriptionID
=
{};
SubscriptionData
subscriptionData
=
{};
// NF Instances (Store)
server
.
handle
(
NNRF_NFM_BASE
+
nrf_cfg
.
sbi_api_version
+
"/nf-instances"
,
[
&
](
const
request
&
request
,
const
response
&
response
)
{
request
.
on_data
([
&
](
const
uint8_t
*
data
,
std
::
size_t
len
)
{
std
::
string
msg
((
char
*
)
data
,
len
);
try
{
// Retrieves a collection of NF Instances
if
(
request
.
method
().
compare
(
"GET"
)
==
0
)
{
std
::
string
split_query
=
request
.
uri
().
raw_query
;
// Parse query paramaters
std
::
string
nfType
=
util
::
get_query_param
(
split_query
,
"nf-type"
);
std
::
string
limit_nfs
=
util
::
get_query_param
(
split_query
.
c_str
(),
"limit"
);
Logger
::
nrf_sbi
().
debug
(
"/nnrf-nfm/ query params - nfType: %s, limit_nfs: %s, "
,
nfType
.
c_str
(),
limit_nfs
.
c_str
());
this
->
get_nf_instances_handler
(
nfType
,
limit_nfs
,
response
);
}
}
catch
(
nlohmann
::
detail
::
exception
&
e
)
{
Logger
::
nrf_sbi
().
warn
(
"Can not parse the json data (error: %s)!"
,
e
.
what
());
response
.
write_head
(
http_status_code_e
::
HTTP_STATUS_CODE_400_BAD_REQUEST
);
response
.
end
();
return
;
}
});
});
// NF Instances ID (Document)
server
.
handle
(
NNRF_NFM_BASE
+
nrf_cfg
.
sbi_api_version
+
NNRF_NFM_NF_INSTANCES
,
[
&
](
const
request
&
request
,
const
response
&
response
)
{
request
.
on_data
([
&
](
const
uint8_t
*
data
,
std
::
size_t
len
)
{
std
::
string
msg
((
char
*
)
data
,
len
);
NFProfile
nFProfile
;
try
{
// Register a new NF Instance
if
(
request
.
method
().
compare
(
"PUT"
)
==
0
&&
len
>
0
)
{
nlohmann
::
json
::
parse
(
msg
.
c_str
()).
get_to
(
nFProfile
);
this
->
register_nf_instance_handler
(
nFProfile
,
response
);
}
// Read the profile of a given NF Instance
if
(
request
.
method
().
compare
(
"GET"
)
==
0
)
{
std
::
vector
<
std
::
string
>
split_result
;
boost
::
split
(
split_result
,
request
.
uri
().
path
,
boost
::
is_any_of
(
"/"
));
if
(
split_result
.
size
()
==
5
)
{
nfInstanceID
=
split_result
[
split_result
.
size
()
-
1
].
c_str
();
this
->
get_nf_instance_handler
(
nfInstanceID
,
response
);
}
}
// Update NF Instance profile
if
(
request
.
method
().
compare
(
"PATCH"
)
==
0
&&
len
>
0
)
{
std
::
vector
<
PatchItem
>
patchItem
;
nlohmann
::
json
::
parse
(
msg
.
c_str
()).
get_to
(
patchItem
);
std
::
vector
<
std
::
string
>
split_result
;
boost
::
split
(
split_result
,
request
.
uri
().
path
,
boost
::
is_any_of
(
"/"
));
nfInstanceID
=
split_result
[
split_result
.
size
()
-
1
].
c_str
();
this
->
update_instance_handler
(
nfInstanceID
,
patchItem
,
response
);
}
// Deregisters a given NF Instance
if
(
request
.
method
().
compare
(
"DELETE"
)
==
0
)
{
std
::
vector
<
std
::
string
>
split_result
;
boost
::
split
(
split_result
,
request
.
uri
().
path
,
boost
::
is_any_of
(
"/"
));
nfInstanceID
=
split_result
[
split_result
.
size
()
-
1
].
c_str
();
this
->
deregister_nf_instance_handler
(
nfInstanceID
,
response
);
}
}
catch
(
nlohmann
::
detail
::
exception
&
e
)
{
Logger
::
nrf_sbi
().
warn
(
"Can not parse the json data (error: %s)!"
,
e
.
what
());
response
.
write_head
(
http_status_code_e
::
HTTP_STATUS_CODE_400_BAD_REQUEST
);
response
.
end
();
return
;
}
});
});
// Subscriptions (Collection & ID Document)
server
.
handle
(
NNRF_NFM_BASE
+
nrf_cfg
.
sbi_api_version
+
NNRF_NFM_STATUS_SUBSCRIBE_URL
,
[
&
](
const
request
&
request
,
const
response
&
response
)
{
request
.
on_data
([
&
](
const
uint8_t
*
data
,
std
::
size_t
len
)
{
std
::
string
msg
((
char
*
)
data
,
len
);
try
{
// Create a new subscription
if
(
request
.
method
().
compare
(
"POST"
)
==
0
&&
len
>
0
)
{
nlohmann
::
json
::
parse
(
msg
.
c_str
()).
get_to
(
subscriptionData
);
this
->
create_subscription_handler
(
subscriptionData
,
response
);
}
// Updates a subscription
if
(
request
.
method
().
compare
(
"PATCH"
)
==
0
&&
len
>
0
)
{
std
::
vector
<
PatchItem
>
patchItem
;
nlohmann
::
json
::
parse
(
msg
.
c_str
()).
get_to
(
patchItem
);
std
::
vector
<
std
::
string
>
split_result
;
boost
::
split
(
split_result
,
request
.
uri
().
path
,
boost
::
is_any_of
(
"/"
));
subscriptionID
=
split_result
[
split_result
.
size
()
-
1
].
c_str
();
this
->
update_subscription_handler
(
subscriptionID
,
patchItem
,
response
);
}
// Delete a subscription
if
(
request
.
method
().
compare
(
"DELETE"
)
==
0
)
{
std
::
vector
<
std
::
string
>
split_result
;
boost
::
split
(
split_result
,
request
.
uri
().
path
,
boost
::
is_any_of
(
"/"
));
subscriptionID
=
split_result
[
split_result
.
size
()
-
1
].
c_str
();
this
->
remove_subscription_handler
(
subscriptionID
,
response
);
}
}
catch
(
nlohmann
::
detail
::
exception
&
e
)
{
Logger
::
nrf_sbi
().
warn
(
"Can not parse the json data (error: %s)!"
,
e
.
what
());
response
.
write_head
(
http_status_code_e
::
HTTP_STATUS_CODE_400_BAD_REQUEST
);
response
.
end
();
return
;
}
});
});
// NF Discovery (Store)
server
.
handle
(
NNRF_DISC_BASE
+
nrf_cfg
.
sbi_api_version
+
"/nf-instances"
,
[
&
](
const
request
&
request
,
const
response
&
response
)
{
request
.
on_data
([
&
](
const
uint8_t
*
data
,
std
::
size_t
len
)
{
std
::
string
msg
((
char
*
)
data
,
len
);
try
{
// Search a collection of NF Instances
if
(
request
.
method
().
compare
(
"GET"
)
==
0
)
{
std
::
string
split_query
=
request
.
uri
().
raw_query
;
// Parse query paramaters
std
::
string
nfTypeTarget
=
util
::
get_query_param
(
split_query
,
"target-nf-type"
);
std
::
string
nfTypeReq
=
util
::
get_query_param
(
split_query
.
c_str
(),
"requester-nf-type"
);
std
::
string
requester_nf_instance_id
=
util
::
get_query_param
(
split_query
.
c_str
(),
"requester-nf-instance-id"
);
std
::
string
limit_nfs
=
util
::
get_query_param
(
split_query
.
c_str
(),
"limit"
);
// TODO: other query parameters
Logger
::
nrf_sbi
().
debug
(
"/nnrf-disc/ query params - nfTypeTarget: %s, nfTypeReq: %s, "
"requester-nf-instance-id: %s, limit_nfs %s"
,
nfTypeTarget
.
c_str
(),
nfTypeReq
.
c_str
(),
requester_nf_instance_id
.
c_str
(),
limit_nfs
.
c_str
());
this
->
search_nf_instances_handler
(
nfTypeTarget
,
nfTypeReq
,
requester_nf_instance_id
,
limit_nfs
,
response
);
}
}
catch
(
nlohmann
::
detail
::
exception
&
e
)
{
Logger
::
nrf_sbi
().
warn
(
"Can not parse the json data (error: %s)!"
,
e
.
what
());
response
.
write_head
(
http_status_code_e
::
HTTP_STATUS_CODE_400_BAD_REQUEST
);
response
.
end
();
return
;
}
});
});
if
(
server
.
listen_and_serve
(
ec
,
m_address
,
std
::
to_string
(
m_port
)))
{
std
::
cerr
<<
"HTTP Server error: "
<<
ec
.
message
()
<<
std
::
endl
;
}
}
void
nrf_http2_server
::
register_nf_instance_handler
(
const
NFProfile
&
NFProfiledata
,
const
response
&
response
)
{
std
::
string
nfInstanceID
=
{};
nfInstanceID
=
NFProfiledata
.
getNfInstanceId
();
Logger
::
nrf_sbi
().
info
(
"Got a request to register an NF instance/Update an NF instance, "
"Instance ID: %s"
,
nfInstanceID
.
c_str
());
int
http_code
=
0
;
ProblemDetails
problem_details
=
{};
nlohmann
::
json
json_data
=
{};
std
::
string
content_type
=
"application/json"
;
std
::
string
json_format
;
m_nrf_app
->
handle_register_nf_instance
(
nfInstanceID
,
NFProfiledata
,
http_code
,
2
,
problem_details
);
if
((
http_code
!=
HTTP_STATUS_CODE_200_OK
)
and
(
http_code
!=
HTTP_STATUS_CODE_201_CREATED
)
and
(
http_code
!=
HTTP_STATUS_CODE_202_ACCEPTED
))
{
to_json
(
json_data
,
problem_details
);
content_type
=
"application/problem+json"
;
}
else
{
std
::
shared_ptr
<
nrf_profile
>
profile
=
m_nrf_app
->
find_nf_profile
(
nfInstanceID
);
if
(
profile
.
get
()
!=
nullptr
)
{
profile
.
get
()
->
to_json
(
json_data
);
}
}
header_map
h
;
h
.
emplace
(
"location"
,
header_value
{
m_address
+
NNRF_NFM_BASE
+
nrf_cfg
.
sbi_api_version
+
"/nf-instances/"
+
nfInstanceID
});
h
.
emplace
(
"content-type"
,
header_value
{
content_type
});
response
.
write_head
(
http_code
,
h
);
response
.
end
(
json_data
.
dump
().
c_str
());
};
void
nrf_http2_server
::
get_nf_instance_handler
(
const
std
::
string
&
nfInstanceID
,
const
response
&
response
)
{
Logger
::
nrf_sbi
().
info
(
"Got a request to retrieve the profile of a given NF Instance, Instance "
"ID: %s"
,
nfInstanceID
.
c_str
());
int
http_code
=
0
;
ProblemDetails
problem_details
=
{};
nlohmann
::
json
json_data
=
{};
std
::
string
content_type
=
"application/json"
;
std
::
shared_ptr
<
nrf_profile
>
profile
=
{};
m_nrf_app
->
handle_get_nf_instance
(
nfInstanceID
,
profile
,
http_code
,
2
,
problem_details
);
if
(
http_code
!=
HTTP_STATUS_CODE_200_OK
)
{
to_json
(
json_data
,
problem_details
);
content_type
=
"application/problem+json"
;
}
else
{
profile
.
get
()
->
to_json
(
json_data
);
}
header_map
h
;
h
.
emplace
(
"location"
,
header_value
{
m_address
+
NNRF_NFM_BASE
+
nrf_cfg
.
sbi_api_version
+
"/nf-instances/"
+
nfInstanceID
});
h
.
emplace
(
"content-type"
,
header_value
{
content_type
});
response
.
write_head
(
http_code
,
h
);
response
.
end
(
json_data
.
dump
().
c_str
());
}
void
nrf_http2_server
::
get_nf_instances_handler
(
const
std
::
string
&
nf_type
,
const
std
::
string
&
limit_nfs
,
const
response
&
response
)
{
Logger
::
nrf_sbi
().
info
(
"Got a request to retrieve a collection of NF Instances"
);
std
::
string
nfType
=
{};
if
(
!
nf_type
.
empty
())
{
nfType
=
nf_type
;
Logger
::
nrf_sbi
().
debug
(
"
\t
NF type: %s"
,
nfType
.
c_str
());
}
uint32_t
limit_Nfs
=
0
;
if
(
!
limit_nfs
.
empty
())
{
limit_Nfs
=
stoi
(
limit_nfs
);
Logger
::
nrf_sbi
().
debug
(
"
\t
Maximum number of NFProfiles to be returned in the response: %d"
,
limit_Nfs
);
}
int
http_code
=
0
;
ProblemDetails
problem_details
=
{};
nlohmann
::
json
json_data
=
{};
std
::
string
content_type
=
"application/json"
;
std
::
shared_ptr
<
nrf_profile
>
profile
=
{};
std
::
vector
<
std
::
string
>
uris
=
{};
m_nrf_app
->
handle_get_nf_instances
(
nfType
,
uris
,
limit_Nfs
,
http_code
,
2
,
problem_details
);
if
(
http_code
!=
HTTP_STATUS_CODE_200_OK
)
{
to_json
(
json_data
,
problem_details
);
content_type
=
"application/problem+json"
;
}
else
{
profile
.
get
()
->
to_json
(
json_data
);
}
header_map
h
;
h
.
emplace
(
"location"
,
header_value
{
m_address
+
NNRF_NFM_BASE
+
nrf_cfg
.
sbi_api_version
+
"/nf-instances/"
});
h
.
emplace
(
"content-type"
,
header_value
{
content_type
});
response
.
write_head
(
http_code
,
h
);
response
.
end
();
}
void
nrf_http2_server
::
update_instance_handler
(
const
std
::
string
&
nfInstanceID
,
const
std
::
vector
<
PatchItem
>&
patchItem
,
const
response
&
response
)
{
Logger
::
nrf_sbi
().
info
(
""
);
Logger
::
nrf_sbi
().
info
(
"Got a request to update an NF instance, Instance ID: %s"
,
nfInstanceID
.
c_str
());
int
http_code
=
0
;
ProblemDetails
problem_details
=
{};
m_nrf_app
->
handle_update_nf_instance
(
nfInstanceID
,
patchItem
,
http_code
,
2
,
problem_details
);
nlohmann
::
json
json_data
=
{};
std
::
string
content_type
=
"application/json"
;
std
::
shared_ptr
<
nrf_profile
>
profile
=
m_nrf_app
->
find_nf_profile
(
nfInstanceID
);
if
((
http_code
!=
HTTP_STATUS_CODE_200_OK
)
and
(
http_code
!=
HTTP_STATUS_CODE_204_NO_CONTENT
))
{
to_json
(
json_data
,
problem_details
);
content_type
=
"application/problem+json"
;
}
else
if
(
http_code
==
HTTP_STATUS_CODE_200_OK
)
{
if
(
profile
.
get
()
!=
nullptr
)
// convert the profile to Json
profile
.
get
()
->
to_json
(
json_data
);
}
Logger
::
nrf_sbi
().
debug
(
"Json data: %s"
,
json_data
.
dump
().
c_str
());
header_map
h
;
h
.
emplace
(
"location"
,
header_value
{
m_address
+
NNRF_NFM_BASE
+
nrf_cfg
.
sbi_api_version
+
"/nf-instances/"
+
nfInstanceID
});
h
.
emplace
(
"content-type"
,
header_value
{
content_type
});
response
.
write_head
(
http_code
,
h
);
response
.
end
(
json_data
.
dump
().
c_str
());
}
void
nrf_http2_server
::
deregister_nf_instance_handler
(
const
std
::
string
&
nfInstanceID
,
const
response
&
response
)
{
Logger
::
nrf_sbi
().
info
(
"Got a request to de-register a given NF Instance, Instance ID: %s"
,
nfInstanceID
.
c_str
());
int
http_code
=
0
;
ProblemDetails
problem_details
=
{};
nlohmann
::
json
json_data
=
{};
std
::
string
content_type
=
"application/json"
;
m_nrf_app
->
handle_deregister_nf_instance
(
nfInstanceID
,
http_code
,
2
,
problem_details
);
header_map
h
;
h
.
emplace
(
"location"
,
header_value
{
m_address
+
NNRF_NFM_BASE
+
nrf_cfg
.
sbi_api_version
+
"/nf-instances/"
+
nfInstanceID
});
h
.
emplace
(
"content-type"
,
header_value
{
content_type
});
response
.
write_head
(
http_code
,
h
);
response
.
end
();
};
void
nrf_http2_server
::
create_subscription_handler
(
const
SubscriptionData
&
subscriptionData
,
const
response
&
response
)
{
Logger
::
nrf_sbi
().
info
(
"Got a request to create a new subscription"
);
int
http_code
=
0
;
ProblemDetails
problem_details
=
{};
std
::
string
sub_id
;
nlohmann
::
json
json_sub
=
{};
nlohmann
::
json
json_data
=
{};
std
::
string
content_type
=
"application/json"
;
Logger
::
nrf_sbi
().
debug
(
"Subscription data %s"
,
json_sub
.
dump
().
c_str
());
m_nrf_app
->
handle_create_subscription
(
subscriptionData
,
sub_id
,
http_code
,
2
,
problem_details
);
if
(
http_code
!=
HTTP_STATUS_CODE_201_CREATED
)
{
to_json
(
json_data
,
problem_details
);
content_type
=
"application/problem+json"
;
}
else
{
to_json
(
json_data
,
subscriptionData
);
json_data
[
"subscriptionId"
]
=
sub_id
;
}
header_map
h
;
h
.
emplace
(
"location"
,
header_value
{
m_address
+
NNRF_NFM_BASE
+
nrf_cfg
.
sbi_api_version
+
NNRF_NFM_STATUS_SUBSCRIBE_URL
});
h
.
emplace
(
"content-type"
,
header_value
{
content_type
});
response
.
write_head
(
http_code
,
h
);
response
.
end
(
json_data
.
dump
().
c_str
());
};
void
nrf_http2_server
::
update_subscription_handler
(
const
std
::
string
&
subscriptionID
,
const
std
::
vector
<
PatchItem
>&
patchItem
,
const
response
&
response
)
{
Logger
::
nrf_sbi
().
info
(
"Got a request to update of subscription to NF instances, subscription "
"ID %s"
,
subscriptionID
.
c_str
());
int
http_code
=
0
;
ProblemDetails
problem_details
=
{};
nlohmann
::
json
json_data
=
{};
std
::
string
content_type
=
"application/json"
;
m_nrf_app
->
handle_update_subscription
(
subscriptionID
,
patchItem
,
http_code
,
1
,
problem_details
);
// TODO: (section 5.2.2.5.6, Update of Subscription to NF Instances,
// 3GPP TS 29.510 V16.0.0 (2019-06)) if the NRF accepts the extension
// of the lifetime of the subscription, but it assigns a validity time
// different than the value suggested by the NF Service Consumer, a
// "200 OK" response code shall be returned
header_map
h
;
h
.
emplace
(
"content-type"
,
header_value
{
content_type
});
if
(
http_code
!=
HTTP_STATUS_CODE_204_NO_CONTENT
)
{
to_json
(
json_data
,
problem_details
);
response
.
write_head
(
http_code
,
h
);
response
.
end
(
json_data
.
dump
().
c_str
());
}
else
{
response
.
write_head
(
http_code
,
h
);
response
.
end
();
}
}
void
nrf_http2_server
::
remove_subscription_handler
(
const
std
::
string
&
subscriptionID
,
const
response
&
response
)
{
Logger
::
nrf_sbi
().
info
(
"Got a request to remove an existing subscription, subscription ID %s"
,
subscriptionID
.
c_str
());
int
http_code
=
0
;
ProblemDetails
problem_details
=
{};
nlohmann
::
json
json_data
=
{};
std
::
string
content_type
=
"application/json"
;
m_nrf_app
->
handle_remove_subscription
(
subscriptionID
,
http_code
,
2
,
problem_details
);
header_map
h
;
h
.
emplace
(
"content-type"
,
header_value
{
content_type
});
if
(
http_code
!=
HTTP_STATUS_CODE_204_NO_CONTENT
)
{
to_json
(
json_data
,
problem_details
);
response
.
write_head
(
http_code
,
h
);
response
.
end
(
json_data
.
dump
().
c_str
());
}
else
{
response
.
write_head
(
http_code
,
h
);
response
.
end
();
}
}
void
nrf_http2_server
::
search_nf_instances_handler
(
const
std
::
string
&
target_nf_type
,
const
std
::
string
&
requester_nf_type
,
const
std
::
string
&
requester_nf_instance_id
,
const
std
::
string
&
limit_nfs
,
const
response
&
response
)
{
Logger
::
nrf_sbi
().
info
(
"Got a request to discover the set of NF instances that satisfies a "
"number of input query parameters"
);
std
::
string
target_nfType
=
{};
if
(
!
target_nf_type
.
empty
())
{
target_nfType
=
target_nf_type
;
Logger
::
nrf_sbi
().
debug
(
"
\t
Target NF type: %s"
,
target_nfType
.
c_str
());
}
std
::
string
requester_nfType
=
{};
if
(
!
requester_nf_type
.
empty
())
{
requester_nfType
=
requester_nf_type
;
Logger
::
nrf_sbi
().
debug
(
"
\t
Requested NF type: %s"
,
requester_nfType
.
c_str
());
}
std
::
string
requester_nfInstance_id
=
{};
if
(
!
requester_nf_instance_id
.
empty
())
{
requester_nfInstance_id
=
requester_nf_instance_id
;
Logger
::
nrf_sbi
().
debug
(
"
\t
Requested NF instance id: %s"
,
requester_nf_instance_id
.
c_str
());
}
uint32_t
limit_Nfs
=
0
;
if
(
!
limit_nfs
.
empty
())
{
limit_Nfs
=
stoi
(
limit_nfs
);
Logger
::
nrf_sbi
().
debug
(
"
\t
Maximum number of NFProfiles to be returned in the response: %d"
,
limit_Nfs
);
}
// TODO: other query parameters
int
http_code
=
0
;
ProblemDetails
problem_details
=
{};
std
::
string
search_id
=
{};
m_nrf_app
->
handle_search_nf_instances
(
target_nfType
,
requester_nfType
,
requester_nfInstance_id
,
limit_Nfs
,
search_id
,
http_code
,
2
,
problem_details
);
nlohmann
::
json
json_data
=
{};
std
::
string
content_type
=
"application/json"
;
std
::
shared_ptr
<
nrf_search_result
>
search_result
=
{};
m_nrf_app
->
find_search_result
(
search_id
,
search_result
);
if
(
http_code
!=
HTTP_STATUS_CODE_200_OK
)
{
to_json
(
json_data
,
problem_details
);
content_type
=
"application/problem+json"
;
}
else
{
// convert the profile to Json
if
(
search_result
!=
nullptr
)
search_result
.
get
()
->
to_json
(
json_data
,
limit_Nfs
);
}
// TODO: applying client restrictions in terms of the number of
// instances to be returned (i.e. "limit" or "max-
// payload-size" query parameters) .
Logger
::
nrf_sbi
().
debug
(
"Json data: %s"
,
json_data
.
dump
().
c_str
());
header_map
h
;
h
.
emplace
(
"content-type"
,
header_value
{
content_type
});
response
.
write_head
(
http_code
,
h
);
response
.
end
(
json_data
.
dump
().
c_str
());
}
void
nrf_http2_server
::
access_token_request_handler
(
const
SubscriptionData
&
subscriptionData
,
const
response
&
response
)
{}
//------------------------------------------------------------------------------
void
nrf_http2_server
::
stop
()
{
server
.
stop
();
}
src/api-server/nrf-http2-server.h
0 → 100644
View file @
53243dc3
/*
* 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
*/
/*! \file smf_http2-server.h
\brief
\author Tien-Thinh NGUYEN
\company Eurecom
\date 2020
\email: tien-thinh.nguyen@eurecom.fr
*/
#ifndef FILE_NRF_HTTP2_SERVER_SEEN
#define FILE_NRF_HTTP2_SERVER_SEEN
#include "conversions.hpp"
//#include "nrf.h"
#include "nrf_app.hpp"
#include "uint_generator.hpp"
#include <nghttp2/asio_http2_server.h>
using
namespace
nghttp2
::
asio_http2
;
using
namespace
nghttp2
::
asio_http2
::
server
;
using
namespace
oai
::
nrf
::
model
;
using
namespace
oai
::
nrf
::
app
;
class
nrf_http2_server
{
public:
nrf_http2_server
(
std
::
string
addr
,
uint32_t
port
,
nrf_app
*
nrf_app_inst
)
:
m_address
(
addr
),
m_port
(
port
),
server
(),
m_nrf_app
(
nrf_app_inst
)
{}
void
start
();
void
init
(
size_t
thr
)
{}
void
register_nf_instance_handler
(
const
NFProfile
&
NFProfiledata
,
const
response
&
response
);
void
deregister_nf_instance_handler
(
const
std
::
string
&
nfInstanceID
,
const
response
&
response
);
void
get_nf_instance_handler
(
const
std
::
string
&
nfInstanceID
,
const
response
&
response
);
void
get_nf_instances_handler
(
const
std
::
string
&
nf_type
,
const
std
::
string
&
limit_nfs
,
const
response
&
response
);
void
update_instance_handler
(
const
std
::
string
&
nfInstanceID
,
const
std
::
vector
<
PatchItem
>&
patchItem
,
const
response
&
response
);
void
create_subscription_handler
(
const
SubscriptionData
&
subscriptionData
,
const
response
&
response
);
void
update_subscription_handler
(
const
std
::
string
&
subscriptionID
,
const
std
::
vector
<
PatchItem
>&
patchItem
,
const
response
&
response
);
void
remove_subscription_handler
(
const
std
::
string
&
subscriptionID
,
const
response
&
response
);
void
search_nf_instances_handler
(
const
std
::
string
&
target_nf_type
,
const
std
::
string
&
requester_nf_type
,
const
std
::
string
&
requester_nf_instance_id
,
const
std
::
string
&
limit_nfs
,
const
response
&
response
);
void
access_token_request_handler
(
const
SubscriptionData
&
subscriptionData
,
const
response
&
response
);
void
stop
();
private:
util
::
uint_generator
<
uint32_t
>
m_promise_id_generator
;
std
::
string
m_address
;
uint32_t
m_port
;
http2
server
;
nrf_app
*
m_nrf_app
;
protected:
static
uint64_t
generate_promise_id
()
{
return
util
::
uint_uid_generator
<
uint64_t
>::
get_instance
().
get_uid
();
}
};
#endif
src/common/nrf.h
View file @
53243dc3
...
...
@@ -77,7 +77,9 @@ typedef uint32_t evsub_id_t;
#define UNASSIGNED_EVSUB_ID ((evsub_id_t) 0x00000000)
#define NNRF_NFM_BASE "/nnrf-nfm/"
#define NNRF_NFM_NF_INSTANCE "/nf-instances/"
#define NNRF_DISC_BASE "/nnrf-disc/"
#define NNRF_NFM_NF_INSTANCES "/nf-instances/"
#define NNRF_NFM_STATUS_SUBSCRIBE_URL "/subscriptions"
#define NF_CURL_TIMEOUT_MS 1000L
...
...
src/common/utils/conversions.cpp
View file @
53243dc3
...
...
@@ -92,3 +92,15 @@ int conv::ascii_to_hex(uint8_t* dst, const char* h) {
dst
[
i
++
]
=
(
high
<<
4
)
|
low
;
}
}
std
::
string
conv
::
toString
(
const
struct
in_addr
&
inaddr
)
{
std
::
string
s
=
{};
char
str
[
INET6_ADDRSTRLEN
]
=
{};
if
(
inet_ntop
(
AF_INET
,
(
const
void
*
)
&
inaddr
,
str
,
INET6_ADDRSTRLEN
)
==
NULL
)
{
s
.
append
(
"Error in_addr"
);
}
else
{
s
.
append
(
str
);
}
return
s
;
}
\ No newline at end of file
src/common/utils/string.cpp
View file @
53243dc3
...
...
@@ -19,12 +19,14 @@
* contact@openairinterface.org
*/
#include "string.hpp"
#include <iostream>
#include <algorithm>
#include <functional>
#include <cctype>
#include <locale>
#include <stdarg.h>
#include <regex>
template
<
class
T
>
class
Buffer
{
...
...
@@ -86,3 +88,20 @@ std::string& util::rtrim(std::string& s) {
std
::
string
&
util
::
trim
(
std
::
string
&
s
)
{
return
util
::
ltrim
(
util
::
rtrim
(
s
));
}
// extract query param from given querystring
std
::
string
query_param_tmp
;
//
std
::
string
util
::
get_query_param
(
std
::
string
querystring
,
std
::
string
param
)
{
std
::
regex
reList
(
"([^=]*)=([^&]*)&?"
);
query_param_tmp
.
clear
();
std
::
for_each
(
std
::
sregex_iterator
(
querystring
.
begin
(),
querystring
.
end
(),
reList
),
std
::
sregex_iterator
(),
[
param
](
std
::
smatch
match
)
{
if
(
match
[
1
]
==
param
)
{
query_param_tmp
=
match
[
2
].
str
().
c_str
();
return
;
}
});
return
query_param_tmp
;
}
\ No newline at end of file
src/common/utils/string.hpp
View file @
53243dc3
...
...
@@ -39,5 +39,7 @@ std::string& ltrim(std::string& s);
std
::
string
&
rtrim
(
std
::
string
&
s
);
// trim from both ends
std
::
string
&
trim
(
std
::
string
&
s
);
// extract query param from given querystring
std
::
string
get_query_param
(
std
::
string
querystring
,
std
::
string
param
);
}
// namespace util
#endif
src/nrf_app/nrf_app.cpp
View file @
53243dc3
...
...
@@ -436,7 +436,7 @@ void nrf_app::handle_create_subscription(
// generate a subscription ID
generate_ev_subscription_id
(
evsub_id
);
ss
.
get
()
->
set_subscription_id
(
evsub_id
);
ss
.
get
()
->
set_http_version
(
http_version
);
// subscribe to NF status registered
// subscribe_nf_status(evsub_id); // from nrf_app
// subscribe to NF status
...
...
@@ -475,7 +475,8 @@ void nrf_app::handle_create_subscription(
for
(
auto
p
:
profiles
)
{
// send notifications
nrf_client_inst
->
notify_subscribed_event
(
p
,
NOTIFICATION_TYPE_NF_REGISTERED
,
notification_uris
);
p
,
NOTIFICATION_TYPE_NF_REGISTERED
,
notification_uris
,
http_version
);
}
}
...
...
@@ -1029,12 +1030,15 @@ void nrf_app::handle_nf_status_registered(const std::string& profile_id) {
find_nf_profile
(
profile_id
,
profile
);
if
(
profile
.
get
()
!=
nullptr
)
{
std
::
vector
<
std
::
string
>
notification_uris
=
{};
uint8_t
httpVersion
=
1
;
get_subscription_list
(
profile_id
,
NOTIFICATION_TYPE_NF_REGISTERED
,
notification_uris
);
profile_id
,
NOTIFICATION_TYPE_NF_REGISTERED
,
notification_uris
,
httpVersion
);
// send notifications
if
(
notification_uris
.
size
()
>
0
)
nrf_client_inst
->
notify_subscribed_event
(
profile
,
NOTIFICATION_TYPE_NF_REGISTERED
,
notification_uris
);
profile
,
NOTIFICATION_TYPE_NF_REGISTERED
,
notification_uris
,
httpVersion
);
else
Logger
::
nrf_app
().
debug
(
"
\t
No subscription found"
);
...
...
@@ -1060,13 +1064,14 @@ void nrf_app::handle_nf_status_deregistered(
p
.
get
()
->
get_nf_instance_id
().
c_str
());
std
::
vector
<
std
::
string
>
notification_uris
=
{};
uint8_t
http_version
=
1
;
get_subscription_list
(
p
.
get
()
->
get_nf_instance_id
(),
NOTIFICATION_TYPE_NF_DEREGISTERED
,
notification_uris
);
notification_uris
,
http_version
);
// send notifications
if
(
notification_uris
.
size
()
>
0
)
nrf_client_inst
->
notify_subscribed_event
(
p
,
NOTIFICATION_TYPE_NF_DEREGISTERED
,
notification_uris
);
p
,
NOTIFICATION_TYPE_NF_DEREGISTERED
,
notification_uris
,
http_version
);
else
Logger
::
nrf_app
().
debug
(
"
\t
No subscription found"
);
}
...
...
@@ -1089,14 +1094,17 @@ void nrf_app::handle_nf_status_profile_changed(const std::string& profile_id) {
find_nf_profile
(
profile_id
,
profile
);
if
(
profile
.
get
()
!=
nullptr
)
{
std
::
vector
<
std
::
string
>
notification_uris
=
{};
uint8_t
http_version
=
1
;
get_subscription_list
(
profile_id
,
NOTIFICATION_TYPE_NF_PROFILE_CHANGED
,
notification_uris
);
profile_id
,
NOTIFICATION_TYPE_NF_PROFILE_CHANGED
,
notification_uris
,
http_version
);
// Notification data includes NF profile (other alternative, includes
// profile_changes)
// send notifications
if
(
notification_uris
.
size
()
>
0
)
nrf_client_inst
->
notify_subscribed_event
(
profile
,
NOTIFICATION_TYPE_NF_PROFILE_CHANGED
,
notification_uris
);
profile
,
NOTIFICATION_TYPE_NF_PROFILE_CHANGED
,
notification_uris
,
http_version
);
else
Logger
::
nrf_app
().
debug
(
"
\t
No subscription found"
);
}
else
{
...
...
@@ -1108,7 +1116,7 @@ void nrf_app::handle_nf_status_profile_changed(const std::string& profile_id) {
//------------------------------------------------------------------------------
void
nrf_app
::
get_subscription_list
(
const
std
::
string
&
profile_id
,
const
uint8_t
&
notification_type
,
std
::
vector
<
std
::
string
>&
uris
)
const
{
std
::
vector
<
std
::
string
>&
uris
,
uint8_t
&
http_version
)
const
{
Logger
::
nrf_app
().
info
(
"
\t
Get the list of subscriptions related to this profile, profile id %s"
,
profile_id
.
c_str
());
...
...
@@ -1127,6 +1135,8 @@ void nrf_app::get_subscription_list(
std
::
string
uri
;
s
.
second
.
get
()
->
get_notification_uri
(
uri
);
http_version
=
s
.
second
.
get
()
->
get_http_version
();
// check notification event type
bool
match_notif_type
=
false
;
for
(
auto
i
:
s
.
second
.
get
()
->
get_notif_events
())
{
...
...
src/nrf_app/nrf_app.hpp
View file @
53243dc3
...
...
@@ -409,7 +409,7 @@ class nrf_app {
*/
void
get_subscription_list
(
const
std
::
string
&
profile_id
,
const
uint8_t
&
notification_type
,
std
::
vector
<
std
::
string
>&
uris
)
const
;
std
::
vector
<
std
::
string
>&
uris
,
uint8_t
&
http_version
)
const
;
/*
* Verify whether the requester is allowed to discover the NF services
...
...
src/nrf_app/nrf_client.cpp
View file @
53243dc3
...
...
@@ -88,8 +88,8 @@ nrf_client::~nrf_client() {
//------------------------------------------------------------------------------
CURL
*
nrf_client
::
curl_create_handle
(
const
std
::
string
&
uri
,
const
std
::
string
&
data
,
std
::
string
&
response_data
)
{
const
std
::
string
&
uri
,
const
std
::
string
&
data
,
std
::
string
&
response_data
,
uint8_t
http_version
)
{
// create handle for a curl request
CURL
*
curl
=
curl_easy_init
();
...
...
@@ -106,17 +106,26 @@ CURL* nrf_client::curl_create_handle(
curl_easy_setopt
(
curl
,
CURLOPT_FOLLOWLOCATION
,
1L
);
curl_easy_setopt
(
curl
,
CURLOPT_POSTFIELDSIZE
,
data
.
length
());
curl_easy_setopt
(
curl
,
CURLOPT_POSTFIELDS
,
data
.
c_str
());
if
(
http_version
==
2
)
{
curl_easy_setopt
(
curl
,
CURLOPT_VERBOSE
,
1L
);
// curl_easy_setopt(curl, CURLOPT_PORT, 8080);
// We use a self-signed test server, skip verification during debugging
curl_easy_setopt
(
curl
,
CURLOPT_SSL_VERIFYPEER
,
0L
);
curl_easy_setopt
(
curl
,
CURLOPT_SSL_VERIFYHOST
,
0L
);
curl_easy_setopt
(
curl
,
CURLOPT_HTTP_VERSION
,
CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE
);
}
}
return
curl
;
}
//------------------------------------------------------------------------------
void
nrf_client
::
send_curl_multi
(
const
std
::
string
&
uri
,
const
std
::
string
&
data
,
std
::
string
&
response_data
)
{
const
std
::
string
&
uri
,
const
std
::
string
&
data
,
std
::
string
&
response_data
,
uint8_t
http_version
)
{
// create a new handle and add to the multi handle
// the curl will actually be sent in perform_curl_multi
CURL
*
tmp
=
curl_create_handle
(
uri
,
data
,
response_data
);
CURL
*
tmp
=
curl_create_handle
(
uri
,
data
,
response_data
,
http_version
);
curl_multi_add_handle
(
curl_multi
,
tmp
);
handles
.
push_back
(
tmp
);
}
...
...
@@ -209,9 +218,11 @@ void nrf_client::curl_release_handles() {
//------------------------------------------------------------------------------
void
nrf_client
::
notify_subscribed_event
(
const
std
::
shared_ptr
<
nrf_profile
>&
profile
,
const
uint8_t
&
event_type
,
const
std
::
vector
<
std
::
string
>&
uris
)
{
const
std
::
vector
<
std
::
string
>&
uris
,
uint8_t
http_version
)
{
Logger
::
nrf_app
().
debug
(
"Send notification for the subscribed event to the subscriptions"
);
"Send notification for the subscribed event to the subscriptions (HTTP "
"VERSION %d)"
,
http_version
);
std
::
map
<
std
::
string
,
std
::
string
>
responses
=
{};
// Fill the json part
...
...
@@ -259,7 +270,7 @@ void nrf_client::notify_subscribed_event(
for
(
auto
uri
:
uris
)
{
responses
[
uri
]
=
""
;
std
::
unique_ptr
<
std
::
string
>
httpData
(
new
std
::
string
());
send_curl_multi
(
uri
,
body
,
responses
[
uri
]);
send_curl_multi
(
uri
,
body
,
responses
[
uri
]
,
http_version
);
}
perform_curl_multi
(
...
...
src/nrf_app/nrf_client.hpp
View file @
53243dc3
...
...
@@ -64,7 +64,7 @@ class nrf_client {
*/
void
notify_subscribed_event
(
const
std
::
shared_ptr
<
nrf_profile
>&
profile
,
const
uint8_t
&
event_type
,
const
std
::
vector
<
std
::
string
>&
uris
);
const
std
::
vector
<
std
::
string
>&
uris
,
uint8_t
http_version
);
/*
* Create Curl handle for multi curl
...
...
@@ -75,7 +75,7 @@ class nrf_client {
*/
CURL
*
curl_create_handle
(
const
std
::
string
&
uri
,
const
std
::
string
&
data
,
std
::
string
&
response_data
);
std
::
string
&
response_data
,
uint8_t
http_version
);
/*
* Prepare to send a request using curl multi
...
...
@@ -86,7 +86,7 @@ class nrf_client {
*/
void
send_curl_multi
(
const
std
::
string
&
uri
,
const
std
::
string
&
data
,
std
::
string
&
response_data
);
std
::
string
&
response_data
,
uint8_t
http_version
);
/*
* Perform curl multi to actually process the available data
...
...
src/nrf_app/nrf_subscription.cpp
View file @
53243dc3
...
...
@@ -115,6 +115,16 @@ boost::posix_time::ptime nrf_subscription::get_validity_time() const {
return
validity_time
;
}
//------------------------------------------------------------------------------
void
nrf_subscription
::
set_http_version
(
const
uint8_t
&
httpVersion
)
{
http_version
=
httpVersion
;
}
//------------------------------------------------------------------------------
uint8_t
nrf_subscription
::
get_http_version
()
const
{
return
http_version
;
}
//------------------------------------------------------------------------------
void
nrf_subscription
::
display
()
{
Logger
::
nrf_app
().
debug
(
"Subscription information"
);
...
...
src/nrf_app/nrf_subscription.hpp
View file @
53243dc3
...
...
@@ -157,7 +157,19 @@ class nrf_subscription {
* @return [boost::posix_time::ptime &] validity time
*/
boost
::
posix_time
::
ptime
get_validity_time
()
const
;
/*
* Set the http_version
* @param [uint8_t&]: http_version: http_version
* @return void
*/
void
set_http_version
(
const
uint8_t
&
http_version
);
/*
* Get the http_version
* @param [void]
* @return http_version
*/
uint8_t
get_http_version
()
const
;
/*
* Subscribe to be notified when a new NF registered to the NRF
* @param void
...
...
@@ -188,6 +200,7 @@ class nrf_subscription {
nrf_event
&
m_event_sub
;
bs2
::
connection
ev_connection
;
boost
::
posix_time
::
ptime
validity_time
;
uint8_t
http_version
=
1
;
};
}
// namespace app
}
// namespace nrf
...
...
src/oai-nrf/main.cpp
View file @
53243dc3
...
...
@@ -16,10 +16,12 @@
#include "logger.hpp"
#include "nrf-api-server.h"
#include "nrf-http2-server.h"
#include "nrf_app.hpp"
#include "nrf_client.hpp"
#include "options.hpp"
#include "pid_file.hpp"
#include "conversions.hpp"
#include "pistache/endpoint.h"
#include "pistache/http.h"
...
...
@@ -39,6 +41,7 @@ using namespace std;
nrf_app
*
nrf_app_inst
=
nullptr
;
nrf_config
nrf_cfg
;
NRFApiServer
*
api_server
=
nullptr
;
nrf_http2_server
*
nrf_api_server_2
=
nullptr
;
//------------------------------------------------------------------------------
void
my_app_signal_handler
(
int
s
)
{
...
...
@@ -112,7 +115,14 @@ int main(int argc, char** argv) {
api_server
=
new
NRFApiServer
(
addr
,
nrf_app_inst
);
api_server
->
init
(
2
);
std
::
thread
nrf_manager
(
&
NRFApiServer
::
start
,
api_server
);
// NRF NGHTTP API server (HTTP2)
nrf_api_server_2
=
new
nrf_http2_server
(
conv
::
toString
(
nrf_cfg
.
sbi
.
addr4
),
nrf_cfg
.
sbi_http2_port
,
nrf_app_inst
);
std
::
thread
nrf_http2_manager
(
&
nrf_http2_server
::
start
,
nrf_api_server_2
);
nrf_manager
.
join
();
nrf_http2_manager
.
join
();
FILE
*
fp
=
NULL
;
std
::
string
filename
=
fmt
::
format
(
"/tmp/nrf_{}.status"
,
getpid
());
...
...
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