forked from acouzens/open5gs
Follow-up on #1744
This commit is contained in:
parent
775520d7a8
commit
5520cb65a5
|
@ -190,7 +190,7 @@ int ogs_dbi_update_imeisv(char *supi, char *imeisv)
|
|||
}
|
||||
|
||||
int ogs_dbi_update_mme(char *supi, char *mme_host, char *mme_realm,
|
||||
bool mme_ispurged)
|
||||
bool purge_flag)
|
||||
{
|
||||
int rv = OGS_OK;
|
||||
bson_t *query = NULL;
|
||||
|
@ -216,7 +216,7 @@ int ogs_dbi_update_mme(char *supi, char *mme_host, char *mme_realm,
|
|||
"mme_host", BCON_UTF8(mme_host),
|
||||
"mme_realm", BCON_UTF8(mme_realm),
|
||||
"mme_timestamp", BCON_INT64(ogs_time_now()),
|
||||
"mme_ispurged", BCON_BOOL(mme_ispurged),
|
||||
"purge_flag", BCON_BOOL(purge_flag),
|
||||
"}");
|
||||
if (!mongoc_collection_update(ogs_mongoc()->collection.subscriber,
|
||||
MONGOC_UPDATE_UPSERT, query, update, NULL, &error)) {
|
||||
|
@ -377,11 +377,9 @@ int ogs_dbi_subscription_data(char *supi,
|
|||
} else if (!strcmp(key, "imsi") &&
|
||||
BSON_ITER_HOLDS_UTF8(&iter)) {
|
||||
utf8 = bson_iter_utf8(&iter, &length);
|
||||
subscription_data->imsi = ogs_calloc(1, ogs_min(length,
|
||||
OGS_MAX_IMSI_BCD_LEN)+1);
|
||||
subscription_data->imsi =
|
||||
ogs_strndup(utf8, ogs_min(length, OGS_MAX_IMSI_BCD_LEN) + 1);
|
||||
ogs_assert(subscription_data->imsi);
|
||||
ogs_cpystrn((char*)subscription_data->imsi,
|
||||
utf8, ogs_min(length, OGS_MAX_IMSI_BCD_LEN)+1);
|
||||
} else if (!strcmp(key, "access_restriction_data") &&
|
||||
BSON_ITER_HOLDS_INT32(&iter)) {
|
||||
subscription_data->access_restriction_data =
|
||||
|
@ -705,22 +703,18 @@ int ogs_dbi_subscription_data(char *supi,
|
|||
} else if (!strcmp(key, "mme_host") &&
|
||||
BSON_ITER_HOLDS_UTF8(&iter)) {
|
||||
utf8 = bson_iter_utf8(&iter, &length);
|
||||
subscription_data->mme_host = ogs_calloc(1, ogs_min(length,
|
||||
OGS_MAX_FQDN_LEN)+1);
|
||||
subscription_data->mme_host =
|
||||
ogs_strndup(utf8, ogs_min(length, OGS_MAX_FQDN_LEN) + 1);
|
||||
ogs_assert(subscription_data->mme_host);
|
||||
ogs_cpystrn((char*)subscription_data->mme_host,
|
||||
utf8, ogs_min(length, OGS_MAX_FQDN_LEN)+1);
|
||||
} else if (!strcmp(key, "mme_realm") &&
|
||||
BSON_ITER_HOLDS_UTF8(&iter)) {
|
||||
utf8 = bson_iter_utf8(&iter, &length);
|
||||
subscription_data->mme_realm = ogs_calloc(1, ogs_min(length,
|
||||
OGS_MAX_FQDN_LEN)+1);
|
||||
subscription_data->mme_realm =
|
||||
ogs_strndup(utf8, ogs_min(length, OGS_MAX_FQDN_LEN) + 1);
|
||||
ogs_assert(subscription_data->mme_realm);
|
||||
ogs_cpystrn((char*)subscription_data->mme_realm,
|
||||
utf8, ogs_min(length, OGS_MAX_FQDN_LEN)+1);
|
||||
} else if (!strcmp(key, "mme_ispurged") &&
|
||||
} else if (!strcmp(key, "purge_flag") &&
|
||||
BSON_ITER_HOLDS_BOOL(&iter)) {
|
||||
subscription_data->mme_ispurged = bson_iter_bool(&iter);
|
||||
subscription_data->purge_flag = bson_iter_bool(&iter);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ int ogs_dbi_update_sqn(char *supi, uint64_t sqn);
|
|||
int ogs_dbi_increment_sqn(char *supi);
|
||||
int ogs_dbi_update_imeisv(char *supi, char *imeisv);
|
||||
int ogs_dbi_update_mme(char *supi, char *mme_host, char *mme_realm,
|
||||
bool mme_ispurged);
|
||||
bool purge_flag);
|
||||
|
||||
int ogs_dbi_subscription_data(char *supi,
|
||||
ogs_subscription_data_t *subscription_data);
|
||||
|
|
|
@ -696,7 +696,7 @@ typedef struct ogs_subscription_data_s {
|
|||
|
||||
char *mme_host;
|
||||
char *mme_realm;
|
||||
bool mme_ispurged;
|
||||
bool purge_flag;
|
||||
} ogs_subscription_data_t;
|
||||
|
||||
void ogs_subscription_data_free(ogs_subscription_data_t *subscription_data);
|
||||
|
|
|
@ -397,7 +397,7 @@ int hss_db_update_imeisv(char *imsi_bcd, char *imeisv)
|
|||
}
|
||||
|
||||
int hss_db_update_mme(char *imsi_bcd, char *mme_host, char *mme_realm,
|
||||
bool mme_ispurged)
|
||||
bool purge_flag)
|
||||
{
|
||||
int rv;
|
||||
char *supi = NULL;
|
||||
|
@ -408,7 +408,7 @@ int hss_db_update_mme(char *imsi_bcd, char *mme_host, char *mme_realm,
|
|||
supi = ogs_msprintf("%s-%s", OGS_ID_SUPI_TYPE_IMSI, imsi_bcd);
|
||||
ogs_assert(supi);
|
||||
|
||||
rv = ogs_dbi_update_mme(supi, mme_host, mme_realm, mme_ispurged);
|
||||
rv = ogs_dbi_update_mme(supi, mme_host, mme_realm, purge_flag);
|
||||
|
||||
ogs_free(supi);
|
||||
ogs_thread_mutex_unlock(&self.db_lock);
|
||||
|
|
|
@ -64,7 +64,7 @@ int hss_db_update_sqn(char *imsi_bcd, uint8_t *rand, uint64_t sqn);
|
|||
int hss_db_increment_sqn(char *imsi_bcd);
|
||||
int hss_db_update_imeisv(char *imsi_bcd, char *imeisv);
|
||||
int hss_db_update_mme(char *imsi_bcd, char *mme_host, char *mme_realm,
|
||||
bool mme_ispurged);
|
||||
bool purge_flag);
|
||||
|
||||
int hss_db_subscription_data(
|
||||
char *imsi_bcd, ogs_subscription_data_t *subscription_data);
|
||||
|
|
|
@ -740,11 +740,11 @@ static int hss_ogs_diam_s6a_ulr_cb( struct msg **msg, struct avp *avp,
|
|||
struct avp *avpch1;
|
||||
union avp_value val;
|
||||
|
||||
char imsi_bcd[OGS_MAX_IMSI_BCD_LEN+1];
|
||||
char *imsi_bcd = NULL;
|
||||
char imeisv_bcd[OGS_MAX_IMEISV_BCD_LEN+1];
|
||||
|
||||
char mme_host[OGS_MAX_FQDN_LEN+1];
|
||||
char mme_realm[OGS_MAX_FQDN_LEN+1];
|
||||
char *mme_host = NULL;
|
||||
char *mme_realm = NULL;
|
||||
|
||||
int rv;
|
||||
uint32_t result_code = 0;
|
||||
|
@ -764,12 +764,22 @@ static int hss_ogs_diam_s6a_ulr_cb( struct msg **msg, struct avp *avp,
|
|||
ogs_assert(ret == 0);
|
||||
ans = *msg;
|
||||
|
||||
/* Get User-Name AVP */
|
||||
ret = fd_msg_search_avp(qry, ogs_diam_user_name, &avp);
|
||||
ogs_assert(ret == 0);
|
||||
ret = fd_msg_avp_hdr(avp, &hdr);
|
||||
ogs_assert(ret == 0);
|
||||
ogs_cpystrn(imsi_bcd, (char*)hdr->avp_value->os.data,
|
||||
ogs_min(hdr->avp_value->os.len, OGS_MAX_IMSI_BCD_LEN)+1);
|
||||
if (avp) {
|
||||
ret = fd_msg_avp_hdr(avp, &hdr);
|
||||
ogs_assert(ret == 0);
|
||||
|
||||
imsi_bcd = ogs_strndup(
|
||||
(char*)hdr->avp_value->os.data,
|
||||
ogs_min(hdr->avp_value->os.len, OGS_MAX_IMSI_BCD_LEN) + 1);
|
||||
ogs_assert(imsi_bcd);
|
||||
} else {
|
||||
ogs_error("no_User-Name");
|
||||
result_code = OGS_DIAM_MISSING_AVP;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rv = hss_db_subscription_data(imsi_bcd, &subscription_data);
|
||||
if (rv != OGS_OK) {
|
||||
|
@ -778,26 +788,49 @@ static int hss_ogs_diam_s6a_ulr_cb( struct msg **msg, struct avp *avp,
|
|||
goto out;
|
||||
}
|
||||
|
||||
/* Get Origin-Host */
|
||||
ret = fd_msg_search_avp(qry, ogs_diam_origin_host, &avp);
|
||||
ogs_assert(ret == 0);
|
||||
ret = fd_msg_avp_hdr(avp, &hdr);
|
||||
ogs_assert(ret == 0);
|
||||
ogs_cpystrn(mme_host, (char*)hdr->avp_value->os.data,
|
||||
ogs_min(hdr->avp_value->os.len, OGS_MAX_FQDN_LEN)+1);
|
||||
if (avp) {
|
||||
ret = fd_msg_avp_hdr(avp, &hdr);
|
||||
ogs_assert(ret == 0);
|
||||
|
||||
mme_host = ogs_strndup(
|
||||
(char*)hdr->avp_value->os.data,
|
||||
ogs_min(hdr->avp_value->os.len, OGS_MAX_FQDN_LEN) + 1);
|
||||
ogs_assert(mme_host);
|
||||
} else {
|
||||
ogs_error("no_Origin-Host");
|
||||
result_code = OGS_DIAM_MISSING_AVP;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Get Origin-Realm */
|
||||
ret = fd_msg_search_avp(qry, ogs_diam_origin_realm, &avp);
|
||||
ogs_assert(ret == 0);
|
||||
ret = fd_msg_avp_hdr(avp, &hdr);
|
||||
ogs_assert(ret == 0);
|
||||
ogs_cpystrn(mme_realm, (char*)hdr->avp_value->os.data,
|
||||
ogs_min(hdr->avp_value->os.len, OGS_MAX_FQDN_LEN)+1);
|
||||
|
||||
if (avp) {
|
||||
ret = fd_msg_avp_hdr(avp, &hdr);
|
||||
ogs_assert(ret == 0);
|
||||
|
||||
mme_realm = ogs_strndup(
|
||||
(char*)hdr->avp_value->os.data,
|
||||
ogs_min(hdr->avp_value->os.len, OGS_MAX_FQDN_LEN) + 1);
|
||||
ogs_assert(mme_realm);
|
||||
} else {
|
||||
ogs_error("no_Origin-Realm");
|
||||
result_code = OGS_DIAM_MISSING_AVP;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ogs_assert(mme_host);
|
||||
ogs_assert(mme_realm);
|
||||
|
||||
/* If UE is not purged at MME, determine if the MME sending the ULR
|
||||
* is different from the one that was last used. if so, send CLR.
|
||||
*/
|
||||
if (subscription_data.mme_host != NULL &&
|
||||
subscription_data.mme_realm != NULL) {
|
||||
if (!subscription_data.mme_ispurged) {
|
||||
if (!subscription_data.purge_flag) {
|
||||
if (strcmp(subscription_data.mme_host, mme_host) ||
|
||||
strcmp(subscription_data.mme_realm, mme_realm)) {
|
||||
hss_s6a_send_clr(imsi_bcd, subscription_data.mme_host,
|
||||
|
@ -926,6 +959,13 @@ static int hss_ogs_diam_s6a_ulr_cb( struct msg **msg, struct avp *avp,
|
|||
|
||||
ogs_subscription_data_free(&subscription_data);
|
||||
|
||||
if (imsi_bcd)
|
||||
ogs_free(imsi_bcd);
|
||||
if (mme_host)
|
||||
ogs_free(mme_host);
|
||||
if (mme_realm)
|
||||
ogs_free(mme_realm);
|
||||
|
||||
return 0;
|
||||
|
||||
out:
|
||||
|
@ -951,6 +991,13 @@ out:
|
|||
|
||||
ogs_subscription_data_free(&subscription_data);
|
||||
|
||||
if (imsi_bcd)
|
||||
ogs_free(imsi_bcd);
|
||||
if (mme_host)
|
||||
ogs_free(mme_host);
|
||||
if (mme_realm)
|
||||
ogs_free(mme_realm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1277,7 +1324,7 @@ int hss_s6a_send_idr(char *imsi_bcd, uint32_t idr_flags, uint32_t subdatamask)
|
|||
return OGS_ERROR;
|
||||
}
|
||||
|
||||
if (subscription_data.mme_ispurged) {
|
||||
if (subscription_data.purge_flag) {
|
||||
ogs_error(" [%s] UE Purged at MME. Cannot send IDR.", imsi_bcd);
|
||||
return OGS_ERROR;
|
||||
}
|
||||
|
|
|
@ -13,9 +13,6 @@ const Profile = new Schema({
|
|||
|
||||
msisdn: [ String ],
|
||||
imeisv: [ String ],
|
||||
mme_host: [ String ],
|
||||
mme_realm: [ String ],
|
||||
mme_ispurged: [ Boolean ],
|
||||
|
||||
security: {
|
||||
k: String,
|
||||
|
|
|
@ -15,7 +15,7 @@ const Subscriber = new Schema({
|
|||
imeisv: [ String ],
|
||||
mme_host: [ String ],
|
||||
mme_realm: [ String ],
|
||||
mme_ispurged: [ Boolean ],
|
||||
purge_flag: [ Boolean ],
|
||||
|
||||
security: {
|
||||
k: String,
|
||||
|
|
|
@ -99,6 +99,12 @@ const Profile = styled.div`
|
|||
margin: 12px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.sectionbody {
|
||||
display: flex;
|
||||
}
|
||||
.sectioncolumn {
|
||||
flex: 1;
|
||||
}
|
||||
.body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -194,94 +200,100 @@ const View = ({ visible, disableOnClickOutside, profile, onEdit, onDelete, onHid
|
|||
<div className="header">
|
||||
Profile Configuration
|
||||
</div>
|
||||
{(msisdn_list.length !== 0 || (imeisv && imeisv.length !== 0)) &&
|
||||
<div className="body">
|
||||
<div className="left">
|
||||
<PhoneIcon/>
|
||||
</div>
|
||||
<div className="right">
|
||||
{msisdn_list.map((msisdn, index) =>
|
||||
<div key={index} className="data">
|
||||
{msisdn}
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>MSISDN</span>
|
||||
<div className="sectionbody">
|
||||
<div className="sectioncolumn">
|
||||
{(msisdn_list.length !== 0 || (imeisv && imeisv.length !== 0)) &&
|
||||
<div className="body">
|
||||
<div className="left">
|
||||
<PhoneIcon/>
|
||||
</div>
|
||||
)}
|
||||
{imeisv && imeisv.length !== 0 &&
|
||||
<div className="right">
|
||||
{msisdn_list.map((msisdn, index) =>
|
||||
<div key={index} className="data">
|
||||
{msisdn}
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>MSISDN</span>
|
||||
</div>
|
||||
)}
|
||||
{imeisv && imeisv.length !== 0 &&
|
||||
<div className="data">
|
||||
{imeisv}
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>IMEISV</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div className="body">
|
||||
<div className="left">
|
||||
<SecurityIcon/>
|
||||
</div>
|
||||
<div className="right">
|
||||
<div className="data">
|
||||
{imeisv}
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>IMEISV</span>
|
||||
{security.k}
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>K</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div className="body">
|
||||
<div className="left">
|
||||
<SecurityIcon/>
|
||||
</div>
|
||||
<div className="right">
|
||||
<div className="data">
|
||||
{security.k}
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>K</span>
|
||||
</div>
|
||||
{security.opc &&
|
||||
<div className="data">
|
||||
{security.opc}
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>OPc</span>
|
||||
{security.opc &&
|
||||
<div className="data">
|
||||
{security.opc}
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>OPc</span>
|
||||
</div>
|
||||
}
|
||||
{security.op &&
|
||||
<div className="data">
|
||||
{security.op}
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>OP</span>
|
||||
</div>
|
||||
}
|
||||
<div className="data">
|
||||
{security.amf}
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>AMF</span>
|
||||
</div>
|
||||
{security.sqn &&
|
||||
<div className="data">
|
||||
{security.sqn}
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>SQN</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{security.op &&
|
||||
<div className="data">
|
||||
{security.op}
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>OP</span>
|
||||
</div>
|
||||
}
|
||||
<div className="data">
|
||||
{security.amf}
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>AMF</span>
|
||||
</div>
|
||||
{security.sqn &&
|
||||
<div className="data">
|
||||
{security.sqn}
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>SQN</span>
|
||||
</div>
|
||||
<div className="sectioncolumn">
|
||||
<div className="body">
|
||||
<div className="left">
|
||||
<PdnIcon/>
|
||||
</div>
|
||||
<div className="right">
|
||||
<div className="data">
|
||||
{ambr['downlink'] === undefined ? "unlimited" :
|
||||
ambr.downlink['value'] === undefined ? "unlimited" :
|
||||
ambr.downlink.value
|
||||
} {ambr['downlink'] === undefined ? "unlimited" :
|
||||
ambr.downlink['value'] === undefined ? "" :
|
||||
ambr.downlink['unit'] === undefined ? "bps" :
|
||||
ambr.downlink.unit === 0 ? "bps" :
|
||||
ambr.downlink.unit === 1 ? "Kbps" :
|
||||
ambr.downlink.unit === 2 ? "Mbps" :
|
||||
ambr.downlink.unit === 3 ? "Gbps" :
|
||||
ambr.downlink.unit === 4 ? "Tbps" :
|
||||
"Unknown Unit" }
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>UL</span>
|
||||
</div>
|
||||
<div className="data">
|
||||
{ambr['uplink'] === undefined ? "unlimited" :
|
||||
ambr.uplink['value'] === undefined ? "unlimited" :
|
||||
ambr.uplink.value
|
||||
} {ambr['uplink'] === undefined ? "unlimited" :
|
||||
ambr.uplink['value'] === undefined ? "" :
|
||||
ambr.uplink['unit'] === undefined ? "bps" :
|
||||
ambr.uplink.unit === 0 ? "bps" :
|
||||
ambr.uplink.unit === 1 ? "Kbps" :
|
||||
ambr.uplink.unit === 2 ? "Mbps" :
|
||||
ambr.uplink.unit === 3 ? "Gbps" :
|
||||
ambr.uplink.unit === 4 ? "Tbps" :
|
||||
"Unknown Unit" }
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>DL</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="body">
|
||||
<div className="left">
|
||||
<PdnIcon/>
|
||||
</div>
|
||||
<div className="right">
|
||||
<div className="data">
|
||||
{ambr['downlink'] === undefined ? "unlimited" :
|
||||
ambr.downlink['value'] === undefined ? "unlimited" :
|
||||
ambr.downlink.value
|
||||
} {ambr['downlink'] === undefined ? "unlimited" :
|
||||
ambr.downlink['value'] === undefined ? "" :
|
||||
ambr.downlink['unit'] === undefined ? "bps" :
|
||||
ambr.downlink.unit === 0 ? "bps" :
|
||||
ambr.downlink.unit === 1 ? "Kbps" :
|
||||
ambr.downlink.unit === 2 ? "Mbps" :
|
||||
ambr.downlink.unit === 3 ? "Gbps" :
|
||||
ambr.downlink.unit === 4 ? "Tbps" :
|
||||
"Unknown Unit" }
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>UL</span>
|
||||
</div>
|
||||
<div className="data">
|
||||
{ambr['uplink'] === undefined ? "unlimited" :
|
||||
ambr.uplink['value'] === undefined ? "unlimited" :
|
||||
ambr.uplink.value
|
||||
} {ambr['uplink'] === undefined ? "unlimited" :
|
||||
ambr.uplink['value'] === undefined ? "" :
|
||||
ambr.uplink['unit'] === undefined ? "bps" :
|
||||
ambr.uplink.unit === 0 ? "bps" :
|
||||
ambr.uplink.unit === 1 ? "Kbps" :
|
||||
ambr.uplink.unit === 2 ? "Mbps" :
|
||||
ambr.uplink.unit === 3 ? "Gbps" :
|
||||
ambr.uplink.unit === 4 ? "Tbps" :
|
||||
"Unknown Unit" }
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>DL</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -172,7 +172,7 @@ const View = ({ visible, disableOnClickOutside, subscriber, onEdit, onDelete, on
|
|||
const imeisv = (subscriber || {}).imeisv;
|
||||
const mme_host = (subscriber || {}).mme_host;
|
||||
const mme_realm = (subscriber || {}).mme_realm;
|
||||
const mme_ispurged = (subscriber || {}).mme_ispurged;
|
||||
const purge_flag = (subscriber || {}).purge_flag;
|
||||
const security = ((subscriber || {}).security || {});
|
||||
const ambr = ((subscriber || {}).ambr || {});
|
||||
const slice_list = ((subscriber || {}).slice || []);
|
||||
|
@ -278,7 +278,7 @@ const View = ({ visible, disableOnClickOutside, subscriber, onEdit, onDelete, on
|
|||
</div>
|
||||
}
|
||||
<div className="data">
|
||||
{mme_ispurged === true ? ( "Purged" ) : ( "Not Purged" )}
|
||||
{purge_flag === true ? ( "Purged" ) : ( "Not Purged" )}
|
||||
<span style={{color:oc.gray[5]}}><KeyboardControlIcon/>UE is Purged at MME</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue