diff --git a/.gitmodules b/.gitmodules index ef40847..9b5c83e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "subprojects/open5gs"] - path = subprojects/open5gs - url = https://github.com/open5gs/open5gs.git [submodule "subprojects/rt-common-shared"] path = subprojects/rt-common-shared url = https://github.com/5G-MAG/rt-common-shared.git diff --git a/README.md b/README.md index 010f86a..4aa4e52 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,24 @@ -# 5GMS Application Function - -This repository holds the 5GMS Application Function implementation for the 5G-MAG Reference Tools. -Note that currently this implementation only supports downlink media streaming. +

5GMS Application Function

+

+ Under Development + Version + License +

## Introduction The 5GMS Application Function (AF) is a Network Function that forms part of the 5G Media Services framework as defined in ETSI TS 126.501. +Additional information can be found at: https://5g-mag.github.io/Getting-Started/pages/5g-media-streaming/ + ### 5GMS Downlink Application Function A 5GMSd Application Function (AF), which can be deployed in the 5G Core Network or in an External Data Network, is responsible for managing the 5GMSd System. The AF is a logical function which embodies the control plane aspects of the system, including provisioning, configuration, and reporting, among others. A 5GMSd Application Provider provisions 5GMS functions using a RESTful HTTP-based provisioning interface at reference point M1d. Another RESTful HTTP-based configuration and reporting interface is exposed to UE-based 5GMSd Clients at reference point M5d. -#### Specifications - -A list of specification related to this repository is available in the [Standards Wiki](https://github.com/5G-MAG/Standards/wiki/5G-Downlink-Media-Streaming-Architecture-(5GMSd):-Relevant-Specifications). - -#### About the implementation +### About the implementation This AF uses the [Open5GS](https://open5gs.org/) framework to implement the network function. -A list of currently supported features is available [here](https://github.com/5G-MAG/rt-5gms-application-function/wiki/Feature-Matrix). +A list of currently supported features is available [here](https://5g-mag.github.io/Getting-Started/pages/5g-media-streaming/usage/application-function/features-af.html). ## Install dependencies @@ -84,11 +84,49 @@ specify an alternative configuration file. For example: The source example configuration file can be found in `~/rt-5gms-application-function/src/5gmsaf/msaf.yaml`. -Also see the [Configuring the Application Function](https://github.com/5G-MAG/rt-5gms-application-function/wiki/Configuring-the-Application-Function) wiki page for details on configuration. +Also see the [Configuring the Application Function](https://5g-mag.github.io/Getting-Started/pages/5g-media-streaming/usage/application-function/configuration-5GMSAF.html) page for details on configuration. ## Testing -See the section on [Testing](https://github.com/5G-MAG/rt-5gms-application-function/wiki/Developing-and-Contributing#testing) in the wiki. +Follow the [Testing as a Local User](https://5g-mag.github.io/Getting-Started/pages/5g-media-streaming/usage/application-function/installation-local-user-5GMSAF.html) page for setting up a test environment without requiring full +system installation. + +### Testing: M1 Interface + +The details of these tests change with different versions of the 5GMSd Application Function. + +If you are testing the v1.2.x versions then please visit the [Testing the M1 Interface on v1.2.0](https://5g-mag.github.io/Getting-Started/pages/5g-media-streaming/usage/application-function/testing-m1-v120.html) page. + +If you are testing the M1 interface on 5GMSd Application Function v1.3.0 to v1.4.0 then please visit the +[Testing the M1 Interface on v1.3.0](https://5g-mag.github.io/Getting-Started/pages/5g-media-streaming/usage/application-function/testing-m1-v130.html) page. + +For testing the M1 interface on 5GMSd Application Function v1.4.1 or later, then please visit the +[Testing the M1 Interface on v1.4.1](https://5g-mag.github.io/Getting-Started/pages/5g-media-streaming/usage/application-function/testing-m1-v141.html) page. + +### Testing the M3 Interface + +Depending on which version of the 5GMSd Application Function you wish to test, the commands to test the interface at reference point M3 change. + +If you wish to test 5GMSd Application Function v1.1.x then please see the [Testing the M3 Interface on v1.1.0](https://5g-mag.github.io/Getting-Started/pages/5g-media-streaming/usage/application-function/testing-m3-v110.html) page. + +For versions after v1.1.x (i.e. v1.2.0 and above) please use the [Testing the M3 Interface on v1.2.0](https://5g-mag.github.io/Getting-Started/pages/5g-media-streaming/usage/application-function/testing-m3-v120.html) page. + +### Testing: M5 Interface + +The details of these tests change with different versions of the 5GMSd Application Function. + +If you are testing versions up to v1.1.x then please visit the [Testing: M5 Interface on v1.0.0](https://5g-mag.github.io/Getting-Started/pages/5g-media-streaming/usage/application-function/testing-m5-v100.html) +page. + +If you are testing the M5 interface on 5GMSd Application Function v1.2.x please visit the +[Testing the M5 Interface on v1.2.0](https://5g-mag.github.io/Getting-Started/pages/5g-media-streaming/usage/application-function/testing-m5-v120.html) page. + +If you are testing the M5 interface on 5GMSd Application Function v1.3.0 or later please visit the +[Testing the M5 Interface on v1.3.0](https://5g-mag.github.io/Getting-Started/pages/5g-media-streaming/usage/application-function/testing-m5-v130.html) page. + +### Testing with Postman + +For detailed instructions on how to use the Postman Collection please refer to this [documentation](https://5g-mag.github.io/Getting-Started/pages/5g-media-streaming/usage/application-function/testing-postman.html). ## Development @@ -97,6 +135,6 @@ the [Gitflow workflow](https://www.atlassian.com/git/tutorials/comparing-workflo `development` branch of this project serves as an integration branch for new features. Consequently, please make sure to switch to the `development` branch before starting the implementation of a new feature. -## Support +## Acknowledgements The reference implementation of the Network Assistance and Dynamic Policies features was funded by the UK Government through the [REASON](https://reason-open-networks.ac.uk/) project. diff --git a/meson.build b/meson.build index 45f5451..1ef2911 100644 --- a/meson.build +++ b/meson.build @@ -9,16 +9,14 @@ # Meson module fs and its functions like fs.hash_file require atleast meson 0.59.0 project('rt-5gms-application-function', 'c', - version : '1.4.0', + version : '1.4.1', license : '5G-MAG Public', - meson_version : '>= 0.59.0', + meson_version : '>= 0.63.0', default_options : [ 'c_std=gnu89', ], ) -sh_cmd = find_program('sh') -patch_open5gs_result = run_command([sh_cmd, '-c', '"$MESON_SOURCE_ROOT/subprojects/patch_open5gs.sh" open5gs'], check: true, capture: false) open5gs_project=subproject('open5gs',required:true) svc_consumers_project=subproject('rt-5gc-service-consumers',required:true) diff --git a/src/5gmsaf/5G_APIs-overrides/TS26512_M5_DynamicPolicies.yaml b/src/5gmsaf/5G_APIs-overrides/TS26512_M5_DynamicPolicies.yaml new file mode 100644 index 0000000..f62643e --- /dev/null +++ b/src/5gmsaf/5G_APIs-overrides/TS26512_M5_DynamicPolicies.yaml @@ -0,0 +1,161 @@ +openapi: 3.0.0 +info: + title: M5_DynamicPolicies + version: 2.0.2 + description: | + 5GMS AF M5 Dynamic Policy API + © 2023, 3GPP Organizational Partners (ARIB, ATIS, CCSA, ETSI, TSDSI, TTA, TTC). + All rights reserved. +tags: + - name: M5_DynamicPolicies + description: '5G Media Streaming: Media Session Handling (M5) APIs: Dynamic Policies' +externalDocs: + description: 'TS 26.512 V17.6.0; 5G Media Streaming (5GMS); Protocols' + url: 'https://www.3gpp.org/ftp/Specs/archive/26_series/26.512/' +servers: + - url: '{apiRoot}/3gpp-m5/v2' + variables: + apiRoot: + default: https://example.com + description: See 3GPP TS 29.512 clause 6.1. +paths: + /dynamic-policies: + post: + operationId: createDynamicPolicy + summary: 'Create (and optionally upload) a new Dynamic Policy resource' + requestBody: + description: 'An optional JSON representation of a Dynamic Policy resource' + content: + application/json: + schema: + $ref: '#/components/schemas/DynamicPolicy' + responses: + '201': + description: 'Created Dynamic Policy Resource' + content: + application/json: + schema: + $ref: '#/components/schemas/DynamicPolicy' + headers: + Location: + description: 'The URL of the newly created Dynamic Policy resource' + required: true + schema: + $ref: 'TS26512_CommonData.yaml#/components/schemas/AbsoluteUrl' + '400': + description: 'Bad Request' + '401': + description: 'Unauthorized' + + /dynamic-policies/{dynamicPolicyId}: + parameters: + - name: dynamicPolicyId + description: 'The resource identifier of a Dynamic Policy resource' + in: path + required: true + schema: + $ref: 'TS26512_CommonData.yaml#/components/schemas/ResourceId' + get: + operationId: retrieveDynamicPolicy + summary: 'Retrieve an existing Dynamic Policy resource' + responses: + '200': + description: 'Success' + content: + application/json: + schema: + $ref: '#/components/schemas/DynamicPolicy' + '400': + description: 'Bad Request' + '401': + description: 'Unauthorized' + '404': + description: 'Not Found' + put: + operationId: updateDynamicPolicy + summary: 'Update an existing Dynamic Policy resource' + requestBody: + description: 'A replacement JSON representation of a Dynamic Policy resource' + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DynamicPolicy' + responses: + '400': + description: 'Bad Request' + '401': + description: 'Unauthorized' + '404': + description: 'Not found' + patch: + operationId: patchDynamicPolicy + summary: 'Patch an existing Dynamic Policy resource' + requestBody: + description: 'A JSON patch to a Dynamic Policy resource' + required: true + content: + application/merge-patch+json: + schema: + $ref: '#/components/schemas/DynamicPolicy' + application/json-patch+json: + schema: + $ref: '#/components/schemas/DynamicPolicy' + responses: + '200': + description: 'Patched Dynamic Policy' + content: + application/json: + schema: + $ref: '#/components/schemas/DynamicPolicy' + '204': + description: 'Patched Dynamic Policy' + '400': + description: 'Bad Request' + '401': + description: 'Unauthorized' + '404': + description: 'Not found' + delete: + operationId: destroyDynamicPolicy + summary: 'Destroy an existing Dynamic Policy resource' + responses: + '204': + description: 'Destroyed Dynamic Policy' + '400': + description: 'Bad Request' + '401': + description: 'Unauthorized' + '404': + description: 'Not Found' +components: + schemas: + DynamicPolicy: + description: "A representation of a Dynamic Policy resource." + type: object + required: + - dynamicPolicyId + - policyTemplateId + - serviceDataFlowDescriptions + - provisioningSessionId + properties: + dynamicPolicyId: + readOnly: true + allOf: + - $ref: 'TS26512_CommonData.yaml#/components/schemas/ResourceId' + policyTemplateId: + $ref: 'TS26512_CommonData.yaml#/components/schemas/ResourceId' + serviceDataFlowDescriptions: + type: array + items: + $ref: 'TS26512_CommonData.yaml#/components/schemas/ServiceDataFlowDescription' + mediaType: + $ref: 'TS29514_Npcf_PolicyAuthorization.yaml#/components/schemas/MediaType' + provisioningSessionId: + $ref: 'TS26512_CommonData.yaml#/components/schemas/ResourceId' + qosSpecification: + $ref: 'TS26512_CommonData.yaml#/components/schemas/M5QoSSpecification' + enforcementMethod: + type: string + enforcementBitRate: + type: integer diff --git a/src/5gmsaf/app.c b/src/5gmsaf/app.c index 75f07cd..dfc0e96 100644 --- a/src/5gmsaf/app.c +++ b/src/5gmsaf/app.c @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include "ogs-app.h" diff --git a/src/5gmsaf/application-server-context.c b/src/5gmsaf/application-server-context.c index 19e606e..b182400 100644 --- a/src/5gmsaf/application-server-context.c +++ b/src/5gmsaf/application-server-context.c @@ -1,12 +1,13 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Authors: Dev Audsin + * David Waring + * Copyright: (C) 2022-2024 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include "ogs-core.h" #include "ogs-sbi.h" @@ -48,7 +49,7 @@ msaf_application_server_state_set_on_post( msaf_provisioning_session_t *provisio msaf_as = ogs_list_first(&msaf_self()->config.applicationServers_list); ogs_assert(msaf_as); - ogs_list_for_each(&msaf_self()->application_server_states, as_state){ + ogs_list_for_each(&msaf_self()->application_server_states, as_state) { if (as_state->application_server == msaf_as) { msaf_application_server_state_ref_node_t *as_state_ref; @@ -90,7 +91,7 @@ msaf_application_server_state_update( msaf_provisioning_session_t *provisioning_ { msaf_application_server_state_ref_node_t *as_state_ref; - ogs_list_for_each(&provisioning_session->application_server_states, as_state_ref){ + ogs_list_for_each(&provisioning_session->application_server_states, as_state_ref) { resource_id_node_t *chc; msaf_application_server_state_node_t *as_state = as_state_ref->as_state; ogs_list_t *certs = msaf_retrieve_certificates_from_map(provisioning_session); @@ -188,11 +189,11 @@ msaf_application_server_add(char *canonical_hostname, char *url_path_prefix_form void msaf_application_server_state_log(ogs_list_t *list, const char* list_name) { resource_id_node_t *state_node; - if(!list || (ogs_list_count(list) == 0)){ + if (!list || (ogs_list_count(list) == 0)) { ogs_debug("%s is empty",list_name); } else{ int i = 1; - ogs_list_for_each(list, state_node){ + ogs_list_for_each(list, state_node) { ogs_debug("%s[%d]: %s\n", list_name, i, state_node->state); i++; } @@ -274,26 +275,26 @@ void next_action_for_application_server(msaf_application_server_state_node_t *as cJSON_Delete(json); cJSON_free(data); - } else if (ogs_list_first(&as_state->delete_content_hosting_configurations) != NULL) { + } else if (ogs_list_first(&as_state->delete_content_hosting_configurations) != NULL) { char *component; resource_id_node_t *delete_chc = ogs_list_first(&as_state->delete_content_hosting_configurations); ogs_debug("M3 client: Sending DELETE method for Content Hosting Configuration [%s] to the Application Server [%s]", delete_chc->state, as_state->application_server->canonicalHostname); component = ogs_msprintf("content-hosting-configurations/%s", delete_chc->state); m3_client_as_state_requests(as_state, NULL, NULL, NULL, (char *)OGS_SBI_HTTP_METHOD_DELETE, component); ogs_free(component); - } else if (ogs_list_first(&as_state->delete_certificates) != NULL) { + } else if (ogs_list_first(&as_state->delete_certificates) != NULL) { char *component; resource_id_node_t *delete_cert = ogs_list_first(&as_state->delete_certificates); ogs_debug("M3 client: Sending DELETE method for certificate [%s] to the Application Server [%s]", delete_cert->state, as_state->application_server->canonicalHostname); component = ogs_msprintf("certificates/%s", delete_cert->state); m3_client_as_state_requests(as_state, NULL, NULL, NULL, (char *)OGS_SBI_HTTP_METHOD_DELETE, component); ogs_free(component); - } else if(ogs_list_first(&as_state->purge_content_hosting_cache) != NULL){ + } else if (ogs_list_first(&as_state->purge_content_hosting_cache) != NULL) { purge_resource_id_node_t *purge_chc = ogs_list_first(&as_state->purge_content_hosting_cache); ogs_assert(purge_chc); ogs_assert(purge_chc->provisioning_session_id); char *component = ogs_msprintf("content-hosting-configurations/%s/purge", purge_chc->provisioning_session_id); - if(purge_chc->purge_regex) { + if (purge_chc->purge_regex) { ogs_debug("M3 client: Sending cache purge operation for resource [%s] to the Application Server", purge_chc->provisioning_session_id); m3_client_as_state_requests(as_state, purge_chc, "application/x-www-form-urlencoded", purge_chc->purge_regex, OGS_SBI_HTTP_METHOD_POST, component); } else { diff --git a/src/5gmsaf/application-server-context.h b/src/5gmsaf/application-server-context.h index 9990c75..5427ebe 100644 --- a/src/5gmsaf/application-server-context.h +++ b/src/5gmsaf/application-server-context.h @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef MSAF_APPLICATION_SERVER_H #define MSAF_APPLICATION_SERVER_H diff --git a/src/5gmsaf/certmgr.c b/src/5gmsaf/certmgr.c index 6a20f3d..90a8074 100644 --- a/src/5gmsaf/certmgr.c +++ b/src/5gmsaf/certmgr.c @@ -1,7 +1,8 @@ /* * License: 5G-MAG Public License (v1.0) - * Authors: Dev Audsin & David Waring - * Copyright: (C) 2023 British Broadcasting Corporation + * Authors: Dev Audsin + * David Waring + * Copyright: (C) 2023-2024 British Broadcasting Corporation * * For full license terms please see the LICENSE file distributed with this * program. If this file is missing then the license can be retrieved from @@ -41,7 +42,7 @@ int server_cert_delete(const char *certid) out = ogs_proc_stdout(current); ogs_assert(out); - while(fgets(buf, OGS_HUGE_LEN, out)) { + while (fgets(buf, OGS_HUGE_LEN, out)) { printf("%s", buf); } ret = ogs_proc_join(current, &out_return_code); @@ -84,9 +85,9 @@ msaf_certificate_t *server_cert_retrieve(const char *certid) cert = ogs_calloc(1, 4096); cert_reserved = 4096; - while(fgets(buf, OGS_HUGE_LEN, out)) { + while (fgets(buf, OGS_HUGE_LEN, out)) { cert_size += strlen (buf); - if(cert_size > cert_reserved - 1) { + if (cert_size > cert_reserved - 1) { cert_reserved +=4096; cert = ogs_realloc(cert,cert_reserved); } @@ -98,7 +99,7 @@ msaf_certificate_t *server_cert_retrieve(const char *certid) ogs_assert(ret == 0); ogs_free(current); - if(out_return_code == 0 || out_return_code == 4 || out_return_code == 8){ + if (out_return_code == 0 || out_return_code == 4 || out_return_code == 8) { msaf_certificate = msaf_certificate_populate(certid, cert, out_return_code); ogs_assert(msaf_certificate); } @@ -136,9 +137,9 @@ msaf_certificate_t *server_cert_get_servercert(const char *certid) cert = ogs_calloc(1, 4096); cert_reserved = 4096; - while(fgets(buf, OGS_HUGE_LEN, out)) { + while (fgets(buf, OGS_HUGE_LEN, out)) { cert_size += strlen (buf); - if(cert_size > cert_reserved - 1) { + if (cert_size > cert_reserved - 1) { cert_reserved +=4096; cert = ogs_realloc(cert,cert_reserved); } @@ -150,7 +151,7 @@ msaf_certificate_t *server_cert_get_servercert(const char *certid) ogs_assert(ret == 0); ogs_free(current); - if(!out_return_code){ + if (!out_return_code) { msaf_certificate = msaf_certificate_populate(certid, cert, out_return_code); ogs_assert(msaf_certificate); } @@ -244,9 +245,9 @@ msaf_certificate_t *server_cert_new(const char *operation, const char *common_na cert = ogs_calloc(1, 4096); cert_reserved = 4096; - while(fgets(buf, OGS_HUGE_LEN, out)) { + while (fgets(buf, OGS_HUGE_LEN, out)) { cert_size += strlen (buf); - if(cert_size > cert_reserved - 1) { + if (cert_size > cert_reserved - 1) { cert_reserved =+ 4096; cert = ogs_realloc(cert,cert_reserved); } @@ -289,7 +290,7 @@ char *check_in_cert_list(const char *canonical_domain_name) out = ogs_proc_stdout(current); ogs_assert(out); - while(fgets(buf, OGS_HUGE_LEN, out)) { + while (fgets(buf, OGS_HUGE_LEN, out)) { ogs_debug("buf=\"%s\", canonical_domain_name=\"%s\"", buf, canonical_domain_name); if (str_match(buf, canonical_domain_name)) { diff --git a/src/5gmsaf/certmgr.h b/src/5gmsaf/certmgr.h index be3e327..9ff9220 100644 --- a/src/5gmsaf/certmgr.h +++ b/src/5gmsaf/certmgr.h @@ -1,6 +1,6 @@ /* * License: 5G-MAG Public License (v1.0) - * Author: Dev Audsin + * Author: Dev Audsin * Copyright: (C) 2022 British Broadcasting Corporation * * For full license terms please see the LICENSE file distributed with this diff --git a/src/5gmsaf/consumption-report-configuration.c b/src/5gmsaf/consumption-report-configuration.c index 96cf77b..4ab48da 100644 --- a/src/5gmsaf/consumption-report-configuration.c +++ b/src/5gmsaf/consumption-report-configuration.c @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: David Waring -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: David Waring + * Copyright: (C) 2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include "ogs-core.h" @@ -81,7 +81,7 @@ bool msaf_consumption_report_configuration_deregister(msaf_provisioning_session_ ogs_free(session->httpMetadata.consumptionReportingConfiguration.hash); session->httpMetadata.consumptionReportingConfiguration.hash = NULL; } - + session->httpMetadata.consumptionReportingConfiguration.received = 0; msaf_sai_cache_clear(session->sai_cache); @@ -126,7 +126,7 @@ char *msaf_consumption_report_configuration_body(msaf_provisioning_session_t *se { cJSON *json; char *body; - + ogs_assert(session); if (!session->consumptionReportingConfiguration) return NULL; diff --git a/src/5gmsaf/consumption-report-configuration.h b/src/5gmsaf/consumption-report-configuration.h index c3d4d6d..e0f5937 100644 --- a/src/5gmsaf/consumption-report-configuration.h +++ b/src/5gmsaf/consumption-report-configuration.h @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: David Waring -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: David Waring + * Copyright: (C) 2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef MSAF_CONSUMPTION_REPORT_CONFIGURATION_H #define MSAF_CONSUMPTION_REPORT_CONFIGURATION_H diff --git a/src/5gmsaf/context.c b/src/5gmsaf/context.c index b01d23f..a8cab7c 100644 --- a/src/5gmsaf/context.c +++ b/src/5gmsaf/context.c @@ -1,12 +1,13 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022-2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Authors: Dev Audsin + * David Waring + * Copyright: (C) 2022-2024 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include #include @@ -106,27 +107,27 @@ void msaf_context_final(void) if (self->config.server_response_cache_control) { - ogs_free(self->config.server_response_cache_control); + ogs_free(self->config.server_response_cache_control); } - + if (self->config.certificateManager) ogs_free(self->config.certificateManager); msaf_network_assistance_delivery_boost_free(); - if(self->config.offerNetworkAssistance){ + if (self->config.offerNetworkAssistance) { //msaf_na_policy_template_remove_all(); - msaf_network_assistance_session_remove_all_pcf_app_session(); + msaf_network_assistance_session_remove_all_pcf_app_session(); msaf_network_assistance_session_remove_all(); msaf_pcf_session_remove_all(); - bsf_terminate(); - pcf_service_consumer_final(); - //msaf_network_assistance_session_remove_all(); - //pcf_terminate(); + bsf_terminate(); + pcf_service_consumer_final(); + //msaf_network_assistance_session_remove_all(); + //pcf_terminate(); } - + msaf_pcf_cache_free(self->pcf_cache); - + if (self->config.data_collection_dir) ogs_free(self->config.data_collection_dir); @@ -177,7 +178,7 @@ int msaf_context_parse_config(void) const char *msaf_key = ogs_yaml_iter_key(&msaf_iter); ogs_assert(msaf_key); if (!strcmp(msaf_key, "open5gsIntegration")) { - self->config.open5gsIntegration_flag = ogs_yaml_iter_bool(&msaf_iter); + self->config.open5gsIntegration_flag = ogs_yaml_iter_bool(&msaf_iter); } else if (!strcmp(msaf_key, "certificateManager")) { self->config.certificateManager = msaf_strdup(ogs_yaml_iter_value(&msaf_iter)); } else if (!strcmp(msaf_key, "applicationServers")) { @@ -231,6 +232,7 @@ int msaf_context_parse_config(void) int m1_content_hosting_configurations_response_max_age = SERVER_RESPONSE_MAX_AGE; int m1_server_certificates_response_max_age = SERVER_RESPONSE_MAX_AGE; int m1_content_protocols_response_max_age = M1_CONTENT_PROTOCOLS_RESPONSE_MAX_AGE; + int m1_metrics_reporting_response_max_age = SERVER_RESPONSE_MAX_AGE; int m1_consumption_reporting_response_max_age = SERVER_RESPONSE_MAX_AGE; int m5_service_access_information_response_max_age = SERVER_RESPONSE_MAX_AGE; while (ogs_yaml_iter_next(&cc_iter)) { @@ -244,6 +246,8 @@ int msaf_context_parse_config(void) m1_content_hosting_configurations_response_max_age = ascii_to_long(ogs_yaml_iter_value(&cc_iter)); } else if (!strcmp(cc_key, "m1ContentProtocols")) { m1_content_protocols_response_max_age = ascii_to_long(ogs_yaml_iter_value(&cc_iter)); + } else if (!strcmp(cc_key, "m1MetricsReportingConfiguration")){ + m1_metrics_reporting_response_max_age = ascii_to_long(ogs_yaml_iter_value(&cc_iter)); } else if (!strcmp(cc_key, "m1ConsumptionReportingConfiguration")) { m1_consumption_reporting_response_max_age = ascii_to_long(ogs_yaml_iter_value(&cc_iter)); } else if (!strcmp(cc_key, "m5ServiceAccessInformation")) { @@ -252,16 +256,16 @@ int msaf_context_parse_config(void) } msaf_server_response_cache_control_set_from_config( m1_provisioning_session_response_max_age, m1_content_hosting_configurations_response_max_age, - m1_server_certificates_response_max_age, m1_content_protocols_response_max_age, + m1_server_certificates_response_max_age, m1_content_protocols_response_max_age, m1_metrics_reporting_response_max_age, m1_consumption_reporting_response_max_age, m5_service_access_information_response_max_age); - - } else if ((!strcmp(msaf_key, "sbi") && self->config.open5gsIntegration_flag)) { + + } else if ((!strcmp(msaf_key, "sbi") && self->config.open5gsIntegration_flag)) { /* handle config in sbi library */ - } else if (!strcmp(msaf_key, "sbi") || !strcmp(msaf_key, "m1") || !strcmp(msaf_key, "m5") || !strcmp(msaf_key, "maf")) { - + } else if (!strcmp(msaf_key, "sbi") || !strcmp(msaf_key, "m1") || !strcmp(msaf_key, "m5") || !strcmp(msaf_key, "maf")) { + ogs_list_t list, list6; ogs_socknode_t *node = NULL, *node6 = NULL; @@ -273,7 +277,7 @@ int msaf_context_parse_config(void) const char *hostname[OGS_MAX_NUM_OF_HOSTNAME]; int num_of_advertise = 0; const char *advertise[OGS_MAX_NUM_OF_HOSTNAME]; - + uint16_t port = 0; const char *dev = NULL; ogs_sockaddr_t *addr = NULL; @@ -365,10 +369,10 @@ int msaf_context_parse_config(void) } else ogs_warn("unknown key `%s`", sbi_key); } - - if (port == 0){ + + if (port == 0) { ogs_warn("Specify the [%s] port, otherwise a random port will be used", msaf_key); - } + } addr = NULL; for (i = 0; i < num; i++) { @@ -414,12 +418,12 @@ int msaf_context_parse_config(void) break; } } - if(!matches) { + if (!matches) { server = ogs_sbih1_server_add( node->addr, is_option ? &option : NULL); ogs_assert(server); - - + + if (addr && ogs_app()->parameter.no_ipv4 == 0) ogs_sbi_server_set_advertise( server, AF_INET, addr); @@ -436,21 +440,21 @@ int msaf_context_parse_config(void) } } } else if (!strcmp(msaf_key, "m1")) { - if(self->config.servers[MSAF_SVR_M1].ipv4){ + if (self->config.servers[MSAF_SVR_M1].ipv4) { ogs_freeaddrinfo(self->config.servers[MSAF_SVR_M1].ipv4); self->config.servers[MSAF_SVR_M1].ipv4 = NULL; } ogs_assert(OGS_OK == ogs_copyaddrinfo(&self->config.servers[MSAF_SVR_M1].ipv4, server->node.addr)); self->config.servers[MSAF_SVR_M1].server_v4 = server; } else if (!strcmp(msaf_key, "m5")) { - if(self->config.servers[MSAF_SVR_M5].ipv4){ + if (self->config.servers[MSAF_SVR_M5].ipv4) { ogs_freeaddrinfo(self->config.servers[MSAF_SVR_M5].ipv4); self->config.servers[MSAF_SVR_M5].ipv4 = NULL; } ogs_assert(OGS_OK == ogs_copyaddrinfo(&self->config.servers[MSAF_SVR_M5].ipv4, server->node.addr)); self->config.servers[MSAF_SVR_M5].server_v4 = server; } else if (!strcmp(msaf_key, "maf")) { - if(self->config.servers[MSAF_SVR_MSAF].ipv4){ + if (self->config.servers[MSAF_SVR_MSAF].ipv4) { ogs_freeaddrinfo(self->config.servers[MSAF_SVR_MSAF].ipv4); self->config.servers[MSAF_SVR_MSAF].ipv4 = NULL; } @@ -470,11 +474,11 @@ int msaf_context_parse_config(void) break; } } - if(!matches) { + if (!matches) { server = ogs_sbih1_server_add( node->addr, is_option ? &option : NULL); ogs_assert(server); - + if (addr && ogs_app()->parameter.no_ipv6 == 0) ogs_sbi_server_set_advertise( server, AF_INET6, addr); @@ -491,21 +495,21 @@ int msaf_context_parse_config(void) } } } else if (!strcmp(msaf_key, "m1")) { - if(self->config.servers[MSAF_SVR_M1].ipv6){ + if (self->config.servers[MSAF_SVR_M1].ipv6) { ogs_freeaddrinfo(self->config.servers[MSAF_SVR_M1].ipv6); self->config.servers[MSAF_SVR_M1].ipv6 = NULL; } ogs_assert(OGS_OK == ogs_copyaddrinfo(&self->config.servers[MSAF_SVR_M1].ipv6, server->node.addr)); self->config.servers[MSAF_SVR_M1].server_v6 = server; } else if (!strcmp(msaf_key, "m5")) { - if(self->config.servers[MSAF_SVR_M5].ipv6){ + if (self->config.servers[MSAF_SVR_M5].ipv6) { ogs_freeaddrinfo(self->config.servers[MSAF_SVR_M5].ipv6); self->config.servers[MSAF_SVR_M5].ipv6 = NULL; } ogs_assert(OGS_OK == ogs_copyaddrinfo(&self->config.servers[MSAF_SVR_M5].ipv6, server->node.addr)); self->config.servers[MSAF_SVR_M5].server_v6 = server; } else if (!strcmp(msaf_key, "maf")) { - if(self->config.servers[MSAF_SVR_MSAF].ipv6){ + if (self->config.servers[MSAF_SVR_MSAF].ipv6) { ogs_freeaddrinfo(self->config.servers[MSAF_SVR_MSAF].ipv6); self->config.servers[MSAF_SVR_MSAF].ipv6 = NULL; } @@ -521,7 +525,7 @@ int msaf_context_parse_config(void) ogs_socknode_remove_all(&list6); } while (ogs_yaml_iter_type(&sbi_array) == YAML_SEQUENCE_NODE); - + /* handle config in sbi library */ } else if (!strcmp(msaf_key, "service_name")) { /* handle config in sbi library */ @@ -531,7 +535,7 @@ int msaf_context_parse_config(void) self->config.data_collection_dir = msaf_strdup(ogs_yaml_iter_value(&msaf_iter)); } else if (!strcmp(msaf_key, "offerNetworkAssistance")) { self->config.offerNetworkAssistance = ogs_yaml_iter_bool(&msaf_iter); - msaf_context_network_assistance_session_init(); + msaf_context_network_assistance_session_init(); } else if (!strcmp(msaf_key, "networkAssistance")) { ogs_yaml_iter_t na_iter, na_array; ogs_yaml_iter_recurse(&msaf_iter, &na_array); @@ -545,17 +549,17 @@ int msaf_context_parse_config(void) break; } else ogs_assert_if_reached(); - - //int delivery_boost_min_dl_bit_rate; - uint64_t delivery_boost_min_dl_bit_rate; - int delivery_boost_period; + + //int delivery_boost_min_dl_bit_rate; + uint64_t delivery_boost_min_dl_bit_rate; + int delivery_boost_period; while (ogs_yaml_iter_next(&na_iter)) { const char *na_key = ogs_yaml_iter_key(&na_iter); ogs_assert(na_key); if (!strcmp(na_key, "deliveryBoost")) { - ogs_info("deliveryBoost"); - ogs_yaml_iter_t db_iter, db_array; + ogs_info("deliveryBoost"); + ogs_yaml_iter_t db_iter, db_array; ogs_yaml_iter_recurse(&na_iter, &db_array); if (ogs_yaml_iter_type(&db_array) == YAML_MAPPING_NODE) { memcpy(&db_iter, &db_array, sizeof(ogs_yaml_iter_t)); @@ -573,28 +577,28 @@ int msaf_context_parse_config(void) ogs_assert(db_key); if (!strcmp(db_key, "minDlBitRate")) { ogs_info("deliveryBoost.minDlBitRate"); - - delivery_boost_min_dl_bit_rate = ogs_sbi_bitrate_from_string((char*)ogs_yaml_iter_value(&db_iter)); /* cast safe as ogs_sbi_bitrate_from_string doesn't alter the string */ - - ogs_info("delivery_boost_min_dl_bit_rate: %ld", delivery_boost_min_dl_bit_rate); - /* - delivery_boost_min_dl_bit_rate = atoi(ogs_yaml_iter_value(&db_iter)); - ogs_info("delivery_boost_min_dl_bit_rate: %d", delivery_boost_min_dl_bit_rate); - */ + + delivery_boost_min_dl_bit_rate = ogs_sbi_bitrate_from_string((char*)ogs_yaml_iter_value(&db_iter)); /* cast safe as ogs_sbi_bitrate_from_string doesn't alter the string */ + + ogs_info("delivery_boost_min_dl_bit_rate: %ld", delivery_boost_min_dl_bit_rate); + /* + delivery_boost_min_dl_bit_rate = atoi(ogs_yaml_iter_value(&db_iter)); + ogs_info("delivery_boost_min_dl_bit_rate: %d", delivery_boost_min_dl_bit_rate); + */ } - if (!strcmp(db_key, "boostPeriod")) { + if (!strcmp(db_key, "boostPeriod")) { ogs_info("deliveryBoost.boostPeriod"); delivery_boost_period = atoi(ogs_yaml_iter_value(&db_iter)); - ogs_info("delivery_boost_period: %d", delivery_boost_period); + ogs_info("delivery_boost_period: %d", delivery_boost_period); } } - msaf_network_assistance_delivery_boost_set_from_config( delivery_boost_min_dl_bit_rate, delivery_boost_period); + msaf_network_assistance_delivery_boost_set_from_config( delivery_boost_min_dl_bit_rate, delivery_boost_period); } } } - - else { + + else { ogs_warn("unknown key `%s`", msaf_key); } } @@ -634,11 +638,11 @@ static void msaf_context_network_assistance_session_init(void) ogs_list_init(&self->delete_pcf_app_sessions); } -static int check_for_network_assistance_support(void){ +static int check_for_network_assistance_support(void) { - if(self->config.offerNetworkAssistance && !self->config.open5gsIntegration_flag) { + if (self->config.offerNetworkAssistance && !self->config.open5gsIntegration_flag) { ogs_info("msaf.open5gsIntegration must be true if msaf.offerNetworkAssistance is true. For network assistance set both \"offerNetworkAssistance: true\" and \"open5gsIntegration: true\" in the configuration file"); - return OGS_ERROR; + return OGS_ERROR; } return OGS_OK; @@ -658,18 +662,18 @@ static void msaf_context_application_server_state_certificates_remove_all(void) resource_id_node_t *delete_certificate, *node = NULL; ogs_debug("Removing all upload certificates"); - ogs_list_for_each_safe(&as_state->upload_certificates, next_node, upload_certificate){ + ogs_list_for_each_safe(&as_state->upload_certificates, next_node, upload_certificate) { if (upload_certificate->state) ogs_free(upload_certificate->state); ogs_list_remove(&as_state->upload_certificates, upload_certificate); - if(upload_certificate) + if (upload_certificate) ogs_free(upload_certificate); } if (as_state->current_certificates) { ogs_debug("Removing all current certificates"); - ogs_list_for_each_safe(as_state->current_certificates, next, certificate){ + ogs_list_for_each_safe(as_state->current_certificates, next, certificate) { if (certificate->state) ogs_free(certificate->state); ogs_list_remove(as_state->current_certificates, certificate); @@ -680,7 +684,7 @@ static void msaf_context_application_server_state_certificates_remove_all(void) } } - ogs_list_for_each_safe(&as_state->delete_certificates, node, delete_certificate){ + ogs_list_for_each_safe(&as_state->delete_certificates, node, delete_certificate) { if (delete_certificate->state) ogs_free(delete_certificate->state); ogs_list_remove(&as_state->delete_certificates, delete_certificate); @@ -699,8 +703,8 @@ static void msaf_context_application_server_state_content_hosting_configuration_ resource_id_node_t *upload_content_hosting_configuration = NULL; resource_id_node_t *delete_content_hosting_configuration, *node = NULL; - ogs_list_for_each_safe(&as_state->upload_content_hosting_configurations, next, upload_content_hosting_configuration){ - if(upload_content_hosting_configuration->state) + ogs_list_for_each_safe(&as_state->upload_content_hosting_configurations, next, upload_content_hosting_configuration) { + if (upload_content_hosting_configuration->state) ogs_free(upload_content_hosting_configuration->state); ogs_list_remove(&as_state->upload_content_hosting_configurations, upload_content_hosting_configuration); ogs_free(upload_content_hosting_configuration); @@ -708,7 +712,7 @@ static void msaf_context_application_server_state_content_hosting_configuration_ } if (as_state->current_content_hosting_configurations) { - ogs_list_for_each_safe(as_state->current_content_hosting_configurations, next, content_hosting_configuration){ + ogs_list_for_each_safe(as_state->current_content_hosting_configurations, next, content_hosting_configuration) { ogs_free(content_hosting_configuration->state); ogs_list_remove(as_state->current_content_hosting_configurations, content_hosting_configuration); ogs_free(content_hosting_configuration); @@ -716,7 +720,7 @@ static void msaf_context_application_server_state_content_hosting_configuration_ } } - ogs_list_for_each_safe(&as_state->delete_content_hosting_configurations, node, delete_content_hosting_configuration){ + ogs_list_for_each_safe(&as_state->delete_content_hosting_configurations, node, delete_content_hosting_configuration) { ogs_free(delete_content_hosting_configuration->state); ogs_list_remove(&as_state->delete_content_hosting_configurations, delete_content_hosting_configuration); ogs_free(delete_content_hosting_configuration); @@ -732,7 +736,7 @@ static void msaf_context_application_server_state_assigned_provisioning_sessions assigned_provisioning_sessions_node_t *provisioning_session_resource; assigned_provisioning_sessions_node_t *provisioning_session_node = NULL; - ogs_list_for_each_safe(&as_state->assigned_provisioning_sessions, provisioning_session_node, provisioning_session_resource){ + ogs_list_for_each_safe(&as_state->assigned_provisioning_sessions, provisioning_session_node, provisioning_session_resource) { ogs_list_remove(&as_state->assigned_provisioning_sessions, provisioning_session_resource); ogs_free(provisioning_session_resource); @@ -747,19 +751,19 @@ static void msaf_context_application_server_state_remove_all(void) { ogs_list_for_each_safe(&self->application_server_states, as_state_node, as_state) { ogs_list_remove(&self->application_server_states, as_state); - if(as_state->current_certificates) + if (as_state->current_certificates) ogs_free(as_state->current_certificates); - if(as_state->current_content_hosting_configurations) + if (as_state->current_content_hosting_configurations) ogs_free(as_state->current_content_hosting_configurations); ogs_free (as_state); } } -static void msaf_context_server_sockaddr_remove(void){ +static void msaf_context_server_sockaddr_remove(void) { int i; for (i=0; iconfig.servers[i].ipv4) ogs_freeaddrinfo(self->config.servers[i].ipv4); - if(self->config.servers[i].ipv6) ogs_freeaddrinfo(self->config.servers[i].ipv6); + if (self->config.servers[i].ipv4) ogs_freeaddrinfo(self->config.servers[i].ipv4); + if (self->config.servers[i].ipv6) ogs_freeaddrinfo(self->config.servers[i].ipv6); } } @@ -795,7 +799,7 @@ msaf_context_provisioning_session_free(msaf_provisioning_session_t *provisioning static void safe_ogs_free(void *memory) { - if(memory) + if (memory) ogs_free(memory); } @@ -804,8 +808,8 @@ int msaf_context_server_name_set(void) { ogs_sbi_server_t *server = NULL; ogs_list_for_each(&ogs_sbi_self()->server_list, server) { - - ogs_sockaddr_t *advertise = NULL; + + ogs_sockaddr_t *advertise = NULL; int res = 0; advertise = server->advertise; @@ -815,7 +819,7 @@ int msaf_context_server_name_set(void) { res = getnameinfo(&advertise->sa, ogs_sockaddr_len(advertise), self->server_name, sizeof(self->server_name), NULL, 0, NI_NAMEREQD); - if(res) { + if (res) { ogs_debug("Unable to retrieve server name: %d\n", res); continue; } else { diff --git a/src/5gmsaf/context.h b/src/5gmsaf/context.h index b4a41b9..3e574fa 100644 --- a/src/5gmsaf/context.h +++ b/src/5gmsaf/context.h @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022-2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022-2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef MSAF_CONTEXT_H #define MSAF_CONTEXT_H diff --git a/src/5gmsaf/data-collection.c b/src/5gmsaf/data-collection.c index d35bce8..ee685b8 100644 --- a/src/5gmsaf/data-collection.c +++ b/src/5gmsaf/data-collection.c @@ -1,11 +1,11 @@ /* -License: 5G-MAG Public License (v1.0) -Author: David Waring -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + * License: 5G-MAG Public License (v1.0) + * Author: David Waring + * Copyright: (C) 2023-2024 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view */ #include @@ -19,6 +19,7 @@ program. If this file is missing then the license can be retrieved from #include "ogs-core.h" #include "context.h" +#include "utilities.h" #include "data-collection.h" @@ -41,7 +42,7 @@ bool msaf_data_collection_store(const char *provisioning_session_id, const char size_t body_len; fd = open_data_store_file(provisioning_session_id, report_class, client_id, session_id, report_time, format); - + if (fd < 0) { return false; } @@ -73,7 +74,7 @@ static bool ensure_directory(const char *path) } } else { /* path doesn't exist so ensure parent directory is present and try to create wanted directory */ - char *path_copy = ogs_strdup(path); + char *path_copy = msaf_strdup(path); if (ensure_directory(dirname(path_copy)) && !mkdir(path, 0755)) { ret = true; } diff --git a/src/5gmsaf/data-collection.h b/src/5gmsaf/data-collection.h index c47aa1a..cd6e38e 100644 --- a/src/5gmsaf/data-collection.h +++ b/src/5gmsaf/data-collection.h @@ -1,11 +1,11 @@ /* -License: 5G-MAG Public License (v1.0) -Author: David Waring -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + * License: 5G-MAG Public License (v1.0) + * Author: David Waring + * Copyright: (C) 2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view */ #ifndef DATA_COLLECTION_H diff --git a/src/5gmsaf/dynamic-policy.c b/src/5gmsaf/dynamic-policy.c index a625259..2fa6496 100644 --- a/src/5gmsaf/dynamic-policy.c +++ b/src/5gmsaf/dynamic-policy.c @@ -1,12 +1,13 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Authors: Dev Audsin + * David Waring + * Copyright: (C) 2023-2024 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include "utilities.h" #include "dynamic-policy.h" @@ -33,7 +34,7 @@ typedef struct app_session_change_cb_data_s { static msaf_dynamic_policy_t *msaf_dynamic_policy_init(void); static void msaf_dynamic_policy_remove(msaf_dynamic_policy_t *msaf_dynamic_policy); static void ue_connection_details_free(ue_network_identifier_t *ue_connection); -static char *set_max_bit_rate_compliant_with_policy_template(char *policy_template_m1_qos_bit_rate, char *m5_qos_bit_rate); +static char *set_max_bit_rate_compliant_with_policy_template(char *policy_template_m1_qos_bit_rate, char *m5_qos_bit_rate); static int calculate_max_bit_rate_for_enforcement(char *policy_template_m1_qos_bit_rate, char *m5_qos_bit_rate); static bool app_session_change_callback(pcf_app_session_t *app_session, void *user_data); static bool app_session_notification_callback(pcf_app_session_t *app_session, const OpenAPI_events_notification_t *notifications, void *user_data); @@ -85,13 +86,13 @@ int msaf_dynamic_policy_create(cJSON *dynamicPolicy, msaf_event_t *e) dynamic_policy = msaf_api_dynamic_policy_parseRequestFromJSON(dynamicPolicy, &reason); - if(!dynamic_policy) { - ogs_error("Dynamic Policy Badly formed JSON: [%s]", reason); + if (!dynamic_policy) { + ogs_error("Dynamic Policy Badly formed JSON: [%s]", reason); return 0; } dyn_policy = msaf_dynamic_policy_init(); - if(!dyn_policy) return 0; + if (!dyn_policy) return 0; dyn_policy->DynamicPolicy = dynamic_policy; ogs_assert(dyn_policy->DynamicPolicy); @@ -111,18 +112,18 @@ int msaf_dynamic_policy_create(cJSON *dynamicPolicy, msaf_event_t *e) service_data_flow_description = (msaf_api_service_data_flow_description_t *)node->data; /* Not Implemented Yet */ - if(service_data_flow_description->domain_name) { - ogs_error("Service Data Flow Descriptions specified using a domain name are not yet supported by this implementation"); + if (service_data_flow_description->domain_name) { + ogs_error("Service Data Flow Descriptions specified using a domain name are not yet supported by this implementation"); msaf_dynamic_policy_remove(dyn_policy); - return 0; - } + return 0; + } /* Validate SDF */ - if (service_data_flow_description->flow_description && service_data_flow_description->domain_name) { - ogs_error("Validation of service data flow description failed: Only one of flowDescription or domainName may be present"); + if (service_data_flow_description->flow_description && service_data_flow_description->domain_name) { + ogs_error("Validation of service data flow description failed: Only one of flowDescription or domainName may be present"); msaf_dynamic_policy_remove(dyn_policy); return 0; - } + } if (!service_data_flow_description->flow_description && !service_data_flow_description->domain_name) { ogs_error("Validation of service data flow description failed: flowDescription or domainName must be present"); @@ -130,7 +131,7 @@ int msaf_dynamic_policy_create(cJSON *dynamicPolicy, msaf_event_t *e) return 0; } - if (service_data_flow_description->flow_description) { + if (service_data_flow_description->flow_description) { if (!service_data_flow_description->flow_description->direction) { ogs_error("Mandatory direction property missing"); @@ -181,8 +182,8 @@ int msaf_dynamic_policy_create(cJSON *dynamicPolicy, msaf_event_t *e) return 0; } - msaf_policy_template = msaf_provisioning_session_get_policy_template_by_id(dynamic_policy->provisioning_session_id, dynamic_policy->policy_template_id); - if (!msaf_policy_template) { + msaf_policy_template = msaf_provisioning_session_get_policy_template_by_id(dynamic_policy->provisioning_session_id, dynamic_policy->policy_template_id); + if (!msaf_policy_template) { ogs_error("Cannot find policy template %s in provisioning session %s", dynamic_policy->policy_template_id, dynamic_policy->provisioning_session_id); msaf_dynamic_policy_remove(dyn_policy); return 0; @@ -193,16 +194,16 @@ int msaf_dynamic_policy_create(cJSON *dynamicPolicy, msaf_event_t *e) ogs_error("Unable to convert policy to MediaComponent"); msaf_dynamic_policy_remove(dyn_policy); return 0; - } + } dynamic_policy_set_enforcement_bit_rate(msaf_policy_template, dynamic_policy); - /* - dynamic_policy->is_enforcement_bit_rate = true; - if (!dynamic_policy->qos_specification) { - dynamic_policy->enforcement_bit_rate = ogs_sbi_bitrate_from_string(msaf_policy_template->policy_template->qo_s_specification->max_auth_btr_dl?msaf_policy_template->policy_template->qo_s_specification->max_auth_btr_dl: msaf_policy_template->policy_template->qo_s_specification->max_btr_dl); + /* + dynamic_policy->is_enforcement_bit_rate = true; + if (!dynamic_policy->qos_specification) { + dynamic_policy->enforcement_bit_rate = ogs_sbi_bitrate_from_string(msaf_policy_template->policy_template->qo_s_specification->max_auth_btr_dl?msaf_policy_template->policy_template->qo_s_specification->max_auth_btr_dl: msaf_policy_template->policy_template->qo_s_specification->max_btr_dl); } else { - dynamic_policy->enforcement_bit_rate = calculate_max_bit_rate_for_enforcement(msaf_policy_template->policy_template->qo_s_specification->max_auth_btr_dl?msaf_policy_template->policy_template->qo_s_specification->max_auth_btr_dl: msaf_policy_template->policy_template->qo_s_specification->max_btr_dl, dynamic_policy->qos_specification->mar_bw_dl_bit_rate); + dynamic_policy->enforcement_bit_rate = calculate_max_bit_rate_for_enforcement(msaf_policy_template->policy_template->qo_s_specification->max_auth_btr_dl?msaf_policy_template->policy_template->qo_s_specification->max_auth_btr_dl: msaf_policy_template->policy_template->qo_s_specification->max_btr_dl, dynamic_policy->qos_specification->mar_bw_dl_bit_rate); } - */ + */ pcf_address = msaf_pcf_cache_find(msaf_self()->pcf_cache, ue_connection->address); if (pcf_address) { @@ -212,7 +213,7 @@ int msaf_dynamic_policy_create(cJSON *dynamicPolicy, msaf_event_t *e) } ue_connection_details_free(ue_connection); } - } + } } else { ogs_error("Must have a serviceDataFlowDescriptions"); msaf_dynamic_policy_remove(dyn_policy); @@ -230,30 +231,30 @@ int msaf_dynamic_policy_update_pcf(msaf_dynamic_policy_t *msaf_dynamic_policy, m ogs_assert(dynamic_policy); msaf_policy_template = msaf_provisioning_session_get_policy_template_by_id(dynamic_policy->provisioning_session_id, dynamic_policy->policy_template_id); - if(!msaf_policy_template) return 0; + if (!msaf_policy_template) return 0; dynamic_policy_set_enforcement_bit_rate(msaf_policy_template, dynamic_policy); /* - if(!dynamic_policy->qos_specification) { + if (!dynamic_policy->qos_specification) { dynamic_policy->enforcement_bit_rate = ogs_sbi_bitrate_from_string(msaf_policy_template->policy_template->qo_s_specification->max_btr_dl); } else { dynamic_policy->enforcement_bit_rate = calculate_max_bit_rate_for_enforcement(msaf_policy_template->policy_template->qo_s_specification->max_btr_dl, dynamic_policy->qos_specification->mar_bw_dl_bit_rate); - } - */ + } + */ media_comps = update_media_component(msaf_policy_template->policy_template->qo_s_specification, dynamic_policy->qos_specification, dynamic_policy->media_type?dynamic_policy->media_type: OpenAPI_media_type_VIDEO); if (msaf_dynamic_policy->pcf_app_session) { - if(!pcf_session_update_app_session(msaf_dynamic_policy->pcf_app_session, media_comps)) { + if (!pcf_session_update_app_session(msaf_dynamic_policy->pcf_app_session, media_comps)) { ogs_error("Unable to send dynamic policy update request to the PCF"); - return 0; + return 0; } } else { ogs_error("The dynamic policy has no associated App Session"); - return 0; + return 0; } update_dynamic_policy_context(msaf_dynamic_policy, dynamic_policy); @@ -266,8 +267,8 @@ cJSON *msaf_dynamic_policy_get_json(const char *dynamic_policy_id) msaf_dynamic_policy_t *dynamic_policy = NULL; dynamic_policy = msaf_dynamic_policy_find_by_dynamicPolicyId(dynamic_policy_id); - - if(dynamic_policy) + + if (dynamic_policy) return msaf_api_dynamic_policy_convertResponseToJSON(dynamic_policy->DynamicPolicy); return NULL; @@ -282,48 +283,48 @@ msaf_dynamic_policy_find_by_dynamicPolicyId(const char *dynamicPolicyId) void msaf_dynamic_policy_delete_by_id(const char *dynamic_policy_id, msaf_event_t *delete_event) { - + msaf_dynamic_policy_t *msaf_dynamic_policy; if (!msaf_self()->dynamic_policies) return; msaf_dynamic_policy = msaf_dynamic_policy_find_by_dynamicPolicyId(dynamic_policy_id); - if(msaf_dynamic_policy) { + if (msaf_dynamic_policy) { add_delete_event_metadata_to_dynamic_policy_context(msaf_dynamic_policy, delete_event); pcf_app_session_free(msaf_dynamic_policy->pcf_app_session); } -} +} void msaf_context_dynamic_policy_free(msaf_dynamic_policy_t *dynamic_policy) { - msaf_dynamic_policy_remove(dynamic_policy); + msaf_dynamic_policy_remove(dynamic_policy); } /*Private functions */ -static msaf_dynamic_policy_t *msaf_dynamic_policy_init(void){ +static msaf_dynamic_policy_t *msaf_dynamic_policy_init(void) { msaf_dynamic_policy_t *dyn_policy; dyn_policy = ogs_calloc(1, sizeof(msaf_dynamic_policy_t)); - if(!dyn_policy) return NULL; + if (!dyn_policy) return NULL; return dyn_policy; } static void update_dynamic_policy_context(msaf_dynamic_policy_t *msaf_dynamic_policy, msaf_api_dynamic_policy_t *dynamic_policy) { - - cJSON *dynamic_policy_json; + + cJSON *dynamic_policy_json; char *dynamic_policy_to_hash; - + dynamic_policy_json = msaf_api_dynamic_policy_convertResponseToJSON(dynamic_policy); - if(dynamic_policy_json) { + if (dynamic_policy_json) { dynamic_policy_to_hash = cJSON_Print(dynamic_policy_json); - if(msaf_dynamic_policy->hash) ogs_free(msaf_dynamic_policy->hash); + if (msaf_dynamic_policy->hash) ogs_free(msaf_dynamic_policy->hash); msaf_dynamic_policy->hash = calculate_hash(dynamic_policy_to_hash); msaf_dynamic_policy->dynamic_policy_created = time(NULL); - if(msaf_dynamic_policy->DynamicPolicy) + if (msaf_dynamic_policy->DynamicPolicy) msaf_api_dynamic_policy_free(msaf_dynamic_policy->DynamicPolicy); msaf_dynamic_policy->DynamicPolicy = dynamic_policy; @@ -331,7 +332,7 @@ static void update_dynamic_policy_context(msaf_dynamic_policy_t *msaf_dynamic_po cJSON_free(dynamic_policy_to_hash); } else { - ogs_error("Error converting the Dynamic Policy to JSON"); + ogs_error("Error converting the Dynamic Policy to JSON"); } } @@ -347,32 +348,32 @@ static OpenAPI_list_t *populate_media_component(msaf_api_m1_qo_s_specification_t MediaComponentList = OpenAPI_list_create(); ogs_assert(MediaComponentList); - if(!requested_qos) { - if(m1_qos->max_auth_btr_dl) { + if (!requested_qos) { + if (m1_qos->max_auth_btr_dl) { mar_bw_dl_bit_rate = m1_qos->max_auth_btr_dl; - } else if(m1_qos->max_btr_dl) { + } else if (m1_qos->max_btr_dl) { mar_bw_dl_bit_rate = m1_qos->max_btr_dl; - } + } - if(m1_qos->max_auth_btr_ul) { + if (m1_qos->max_auth_btr_ul) { mar_bw_ul_bit_rate = m1_qos->max_auth_btr_ul; - } else if(m1_qos->max_btr_ul) { + } else if (m1_qos->max_btr_ul) { mar_bw_ul_bit_rate = m1_qos->max_btr_ul; - } + } } else { - - if(m1_qos->max_auth_btr_dl) { + + if (m1_qos->max_auth_btr_dl) { mar_bw_dl_bit_rate = set_max_bit_rate_compliant_with_policy_template(m1_qos->max_auth_btr_dl, requested_qos->mar_bw_dl_bit_rate); - } else if(m1_qos->max_btr_dl) { + } else if (m1_qos->max_btr_dl) { mar_bw_dl_bit_rate = set_max_bit_rate_compliant_with_policy_template(m1_qos->max_btr_dl, requested_qos->mar_bw_dl_bit_rate); - } - - if(m1_qos->max_auth_btr_ul) { + } + + if (m1_qos->max_auth_btr_ul) { mar_bw_ul_bit_rate = set_max_bit_rate_compliant_with_policy_template(m1_qos->max_auth_btr_ul, requested_qos->mar_bw_ul_bit_rate); - } else if(m1_qos->max_btr_ul) { + } else if (m1_qos->max_btr_ul) { mar_bw_ul_bit_rate = set_max_bit_rate_compliant_with_policy_template(m1_qos->max_btr_ul, requested_qos->mar_bw_ul_bit_rate); - } + } } if (flow_description->src_ip || flow_description->src_port !=0 || flow_description->protocol != IPPROTO_IP || @@ -424,7 +425,7 @@ static OpenAPI_list_t *populate_media_component(msaf_api_m1_qo_s_specification_t ogs_free(ue_port); ogs_free(remote_port); - media_sub_comp = OpenAPI_media_sub_component_create(OpenAPI_af_sig_protocol_NULL, + media_sub_comp = OpenAPI_media_sub_component_create(OpenAPI_af_sig_protocol_NULL, NULL, 0, flow_descs, OpenAPI_flow_status_ENABLED, mar_bw_dl_bit_rate, mar_bw_ul_bit_rate, @@ -472,30 +473,30 @@ static OpenAPI_list_t *update_media_component(msaf_api_m1_qo_s_specification_t * media_comps = OpenAPI_list_create(); ogs_assert(media_comps); - if(!requested_qos) { - if(m1_qos->max_auth_btr_dl) { + if (!requested_qos) { + if (m1_qos->max_auth_btr_dl) { mar_bw_dl_bit_rate = m1_qos->max_auth_btr_dl; - } else if(m1_qos->max_btr_dl) { + } else if (m1_qos->max_btr_dl) { mar_bw_dl_bit_rate = m1_qos->max_btr_dl; } - if(m1_qos->max_auth_btr_ul) { + if (m1_qos->max_auth_btr_ul) { mar_bw_ul_bit_rate = m1_qos->max_auth_btr_ul; - } else if(m1_qos->max_btr_ul) { + } else if (m1_qos->max_btr_ul) { mar_bw_ul_bit_rate = m1_qos->max_btr_ul; } } else { - if(m1_qos->max_auth_btr_dl) { + if (m1_qos->max_auth_btr_dl) { mar_bw_dl_bit_rate = set_max_bit_rate_compliant_with_policy_template(m1_qos->max_auth_btr_dl, requested_qos->mar_bw_dl_bit_rate); - } else if(m1_qos->max_btr_dl) { + } else if (m1_qos->max_btr_dl) { mar_bw_dl_bit_rate = set_max_bit_rate_compliant_with_policy_template(m1_qos->max_btr_dl, requested_qos->mar_bw_dl_bit_rate); } - if(m1_qos->max_auth_btr_ul) { + if (m1_qos->max_auth_btr_ul) { mar_bw_ul_bit_rate = set_max_bit_rate_compliant_with_policy_template(m1_qos->max_auth_btr_ul, requested_qos->mar_bw_ul_bit_rate); - } else if(m1_qos->max_btr_ul) { + } else if (m1_qos->max_btr_ul) { mar_bw_ul_bit_rate = set_max_bit_rate_compliant_with_policy_template(m1_qos->max_btr_ul, requested_qos->mar_bw_ul_bit_rate); } } @@ -567,9 +568,9 @@ static char *set_max_bit_rate_compliant_with_policy_template(char *policy_templa qos_bit_rate_m5 = ogs_sbi_bitrate_from_string(m5_qos_bit_rate); qos_bit_rate_m1 = ogs_sbi_bitrate_from_string(policy_template_m1_qos_bit_rate); - if(qos_bit_rate_m5 > qos_bit_rate_m1) + if (qos_bit_rate_m5 > qos_bit_rate_m1) return policy_template_m1_qos_bit_rate; - return m5_qos_bit_rate; + return m5_qos_bit_rate; } static int calculate_max_bit_rate_for_enforcement(char *policy_template_m1_qos_bit_rate, char *m5_qos_bit_rate) { @@ -578,7 +579,7 @@ static int calculate_max_bit_rate_for_enforcement(char *policy_template_m1_qos_b qos_bit_rate_m5 = ogs_sbi_bitrate_from_string(m5_qos_bit_rate); qos_bit_rate_m1 = ogs_sbi_bitrate_from_string(policy_template_m1_qos_bit_rate); - if(qos_bit_rate_m5 > qos_bit_rate_m1) + if (qos_bit_rate_m5 > qos_bit_rate_m1) return qos_bit_rate_m1; return qos_bit_rate_m5; } @@ -587,7 +588,7 @@ static void dynamic_policy_set_enforcement_bit_rate(msaf_policy_template_node_t { dynamic_policy->is_enforcement_bit_rate = true; - if(!dynamic_policy->qos_specification) { + if (!dynamic_policy->qos_specification) { dynamic_policy->enforcement_bit_rate = ogs_sbi_bitrate_from_string(msaf_policy_template->policy_template->qo_s_specification->max_auth_btr_dl?msaf_policy_template->policy_template->qo_s_specification->max_auth_btr_dl: msaf_policy_template->policy_template->qo_s_specification->max_btr_dl); } else { dynamic_policy->enforcement_bit_rate = calculate_max_bit_rate_for_enforcement(msaf_policy_template->policy_template->qo_s_specification->max_auth_btr_dl?msaf_policy_template->policy_template->qo_s_specification->max_auth_btr_dl: msaf_policy_template->policy_template->qo_s_specification->max_btr_dl, dynamic_policy->qos_specification->mar_bw_dl_bit_rate); @@ -602,9 +603,9 @@ static void create_dynamic_policy_app_session(const ogs_sockaddr_t *pcf_address, ue_network_identifier_t *ue_net = NULL; pcf_session = msaf_pcf_session_new(pcf_address); - - if(!pcf_session) { - ogs_assert(true == nf_server_send_error(dynamic_policy->metadata->create_event->h.sbi.data, 401, 0, dynamic_policy->metadata->create_event->message, "Failed to create dynamic policy.", "Unable to establish connection with the PCF." , NULL, dynamic_policy->metadata->create_event->nf_server_interface_metadata, dynamic_policy->metadata->create_event->app_meta)); + + if (!pcf_session) { + ogs_assert(true == nf_server_send_error(dynamic_policy->metadata->create_event->h.sbi.data, 401, 0, dynamic_policy->metadata->create_event->message, "Failed to create dynamic policy.", "Unable to establish connection with the PCF." , NULL, dynamic_policy->metadata->create_event->nf_server_interface_metadata, dynamic_policy->metadata->create_event->app_meta)); } ue_net = copy_ue_network_connection_identifier(ue_connection); @@ -635,10 +636,10 @@ static ue_network_identifier_t *copy_ue_network_connection_identifier(const ue_n ue_net_connection_copy = ogs_calloc(1, sizeof(ue_network_identifier_t)); if (ue_net_connection_copy) { if (ue_net_connection->address) ogs_copyaddrinfo(&ue_net_connection_copy->address, ue_net_connection->address); - if (ue_net_connection->supi) ue_net_connection_copy->supi = ogs_strdup(ue_net_connection->supi); - if (ue_net_connection->gpsi) ue_net_connection_copy->gpsi = ogs_strdup(ue_net_connection->gpsi); - if (ue_net_connection->dnn) ue_net_connection_copy->dnn = ogs_strdup(ue_net_connection->dnn); - if (ue_net_connection->ip_domain) ue_net_connection_copy->ip_domain = ogs_strdup(ue_net_connection->ip_domain); + ue_net_connection_copy->supi = msaf_strdup(ue_net_connection->supi); + ue_net_connection_copy->gpsi = msaf_strdup(ue_net_connection->gpsi); + ue_net_connection_copy->dnn = msaf_strdup(ue_net_connection->dnn); + ue_net_connection_copy->ip_domain = msaf_strdup(ue_net_connection->ip_domain); } return ue_net_connection_copy; } @@ -654,20 +655,20 @@ static void free_ue_network_connection_identifier(ue_network_identifier_t *ue_ne ogs_free(ue_net_connection); } -static void msaf_dynamic_policy_remove(msaf_dynamic_policy_t *msaf_dynamic_policy){ - - if(!msaf_dynamic_policy) return; +static void msaf_dynamic_policy_remove(msaf_dynamic_policy_t *msaf_dynamic_policy) { - if(msaf_dynamic_policy->dynamicPolicyId) { + if (!msaf_dynamic_policy) return; + + if (msaf_dynamic_policy->dynamicPolicyId) { ogs_free(msaf_dynamic_policy->dynamicPolicyId); msaf_dynamic_policy->dynamicPolicyId = NULL; } - if(msaf_dynamic_policy->DynamicPolicy) msaf_api_dynamic_policy_free(msaf_dynamic_policy->DynamicPolicy); - if(msaf_dynamic_policy->hash) ogs_free(msaf_dynamic_policy->hash); - if(msaf_dynamic_policy->metadata){ - if(msaf_dynamic_policy->metadata->create_event) msaf_event_free(msaf_dynamic_policy->metadata->create_event); - if(msaf_dynamic_policy->metadata->delete_event) msaf_event_free(msaf_dynamic_policy->metadata->delete_event); + if (msaf_dynamic_policy->DynamicPolicy) msaf_api_dynamic_policy_free(msaf_dynamic_policy->DynamicPolicy); + if (msaf_dynamic_policy->hash) ogs_free(msaf_dynamic_policy->hash); + if (msaf_dynamic_policy->metadata) { + if (msaf_dynamic_policy->metadata->create_event) msaf_event_free(msaf_dynamic_policy->metadata->create_event); + if (msaf_dynamic_policy->metadata->delete_event) msaf_event_free(msaf_dynamic_policy->metadata->delete_event); ogs_free(msaf_dynamic_policy->metadata); } @@ -676,24 +677,24 @@ static void msaf_dynamic_policy_remove(msaf_dynamic_policy_t *msaf_dynamic_polic } static void ue_connection_details_free(ue_network_identifier_t *ue_connection) { - if(ue_connection->address) ogs_freeaddrinfo(ue_connection->address); - if(ue_connection->ip_domain) ogs_free(ue_connection->ip_domain); + if (ue_connection->address) ogs_freeaddrinfo(ue_connection->address); + if (ue_connection->ip_domain) ogs_free(ue_connection->ip_domain); ogs_free(ue_connection); } -static bool app_session_change_callback(pcf_app_session_t *app_session, void *data){ +static bool app_session_change_callback(pcf_app_session_t *app_session, void *data) { msaf_dynamic_policy_t *dynamic_policy; ogs_debug("change callback(app_session=%p, data=%p)", app_session, data); dynamic_policy = (msaf_dynamic_policy_t *)data; - if(!app_session){ + if (!app_session) { - if(dynamic_policy->metadata->create_event) + if (dynamic_policy->metadata->create_event) { - ogs_assert(true == nf_server_send_error(dynamic_policy->metadata->create_event->h.sbi.data, 401, 0, dynamic_policy->metadata->create_event->message, "Failed to create dynamic policy.", "Unable to establish connection with the PCF." , NULL, dynamic_policy->metadata->create_event->nf_server_interface_metadata, dynamic_policy->metadata->create_event->app_meta)); + ogs_assert(true == nf_server_send_error(dynamic_policy->metadata->create_event->h.sbi.data, 401, 0, dynamic_policy->metadata->create_event->message, "Failed to create dynamic policy.", "Unable to establish connection with the PCF." , NULL, dynamic_policy->metadata->create_event->nf_server_interface_metadata, dynamic_policy->metadata->create_event->app_meta)); msaf_dynamic_policy_remove(dynamic_policy); return true; } @@ -702,7 +703,7 @@ static bool app_session_change_callback(pcf_app_session_t *app_session, void *da return true; } - if(app_session && dynamic_policy->metadata->create_event){ + if (app_session && dynamic_policy->metadata->create_event) { dynamic_policy->pcf_app_session = app_session; create_msaf_dynamic_policy_and_send_response(dynamic_policy); return true; @@ -751,7 +752,7 @@ free_ogs_hash_dynamic_policy(void *rec, const void *key, int klen, const void *v return 1; } -static bool create_msaf_dynamic_policy_and_send_response(msaf_dynamic_policy_t *dyn_policy){ +static bool create_msaf_dynamic_policy_and_send_response(msaf_dynamic_policy_t *dyn_policy) { ogs_uuid_t uuid; char id[OGS_UUID_FORMATTED_LENGTH + 1]; ogs_sbi_response_t *response; @@ -775,14 +776,14 @@ static bool create_msaf_dynamic_policy_and_send_response(msaf_dynamic_policy_t * dyn_policy->dynamic_policy_created = time(NULL); dynamic_policy = msaf_api_dynamic_policy_convertResponseToJSON(dyn_policy->DynamicPolicy); - if(dynamic_policy) { + if (dynamic_policy) { dynamic_policy_to_hash = cJSON_Print(dynamic_policy); dyn_policy->hash = calculate_hash(dynamic_policy_to_hash); } else { ogs_error("Error converting Dynamic Policy to JSON"); - ogs_free(dyn_policy->DynamicPolicy->dynamic_policy_id); - ogs_free(dyn_policy->dynamicPolicyId); - return 0; + ogs_free(dyn_policy->DynamicPolicy->dynamic_policy_id); + ogs_free(dyn_policy->dynamicPolicyId); + return 0; } ogs_hash_set(msaf_self()->dynamic_policies, msaf_strdup(id), OGS_HASH_KEY_STRING, dyn_policy); @@ -798,7 +799,7 @@ static bool create_msaf_dynamic_policy_and_send_response(msaf_dynamic_policy_t * nf_server_populate_response(response, response_body?strlen(response_body):0, msaf_strdup(response_body), response_code); ogs_assert(true == ogs_sbi_server_send_response(dyn_policy->metadata->create_event->h.sbi.data, response)); - if(dyn_policy->metadata->create_event) + if (dyn_policy->metadata->create_event) { msaf_event_free(dyn_policy->metadata->create_event); dyn_policy->metadata->create_event = NULL; @@ -821,7 +822,7 @@ static bool app_session_notification_callback(pcf_app_session_t *app_session, co return true; } -static bool bsf_retrieve_pcf_binding_callback(OpenAPI_pcf_binding_t *pcf_binding, void *data){ +static bool bsf_retrieve_pcf_binding_callback(OpenAPI_pcf_binding_t *pcf_binding, void *data) { int rv; int valid_time = 50; ogs_time_t expires; @@ -835,20 +836,20 @@ static bool bsf_retrieve_pcf_binding_callback(OpenAPI_pcf_binding_t *pcf_binding ogs_assert(ue_address); - if(pcf_binding){ + if (pcf_binding) { const ogs_sockaddr_t *pcf_address; expires = ogs_time_now() + ogs_time_from_sec(valid_time); rv = msaf_pcf_cache_add(msaf_self()->pcf_cache, ue_address, (const OpenAPI_pcf_binding_t *)pcf_binding, expires); OpenAPI_pcf_binding_free(pcf_binding); - if (rv != true){ + if (rv != true) { ogs_error("Adding PCF Binding to the cache failed"); retrieve_pcf_binding_cb_data_free(retrieve_pcf_binding_cb_data); return false; } pcf_address = msaf_pcf_cache_find(msaf_self()->pcf_cache, retrieve_pcf_binding_cb_data->ue_connection->address); - if(pcf_address){ + if (pcf_address) { create_dynamic_policy_app_session(pcf_address, retrieve_pcf_binding_cb_data->ue_connection, retrieve_pcf_binding_cb_data->media_component, retrieve_pcf_binding_cb_data->dyn_policy); retrieve_pcf_binding_cb_data_free(retrieve_pcf_binding_cb_data); return true; @@ -930,7 +931,7 @@ static char *flow_description_protocol_to_string(int protocol) static char *flow_description_port(int port) { - if (port == 0) return ogs_strdup(""); + if (port == 0) return msaf_strdup(""); return ogs_msprintf(" %d", port); } diff --git a/src/5gmsaf/dynamic-policy.h b/src/5gmsaf/dynamic-policy.h index 33cba2b..4b9ddd6 100644 --- a/src/5gmsaf/dynamic-policy.h +++ b/src/5gmsaf/dynamic-policy.h @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022-2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022-2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef MSAF_DYN_POLICY_H #define MSAF_DYN_POLICY_H diff --git a/src/5gmsaf/event.c b/src/5gmsaf/event.c index e8c5646..8abc82b 100644 --- a/src/5gmsaf/event.c +++ b/src/5gmsaf/event.c @@ -1,11 +1,11 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022-2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022-2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view */ #include "context.h" @@ -44,7 +44,7 @@ int check_event_addresses(msaf_event_t *e, ogs_sockaddr_t *sockaddr_v4, ogs_sock (sockaddr_v6 && ogs_sockaddr_is_equal(server->node.addr, sockaddr_v6)) ) { return 1; - } + } } return 0; @@ -66,7 +66,7 @@ msaf_event_t *populate_msaf_event_with_metadata(msaf_event_t *e, const nf_server if (rv != OGS_OK) { ogs_error("ogs_sbi_parse_header() failed"); } - + event->nf_server_interface_metadata = nf_server_interface_metadata; event->app_meta = msaf_app_metadata(); diff --git a/src/5gmsaf/event.h b/src/5gmsaf/event.h index 3a1972e..78fb361 100644 --- a/src/5gmsaf/event.h +++ b/src/5gmsaf/event.h @@ -1,11 +1,11 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022-2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022-2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view */ #ifndef MSAF_EVENT_H diff --git a/src/5gmsaf/generator-5gmsaf b/src/5gmsaf/generator-5gmsaf index 832c80a..09d79c2 100755 --- a/src/5gmsaf/generator-5gmsaf +++ b/src/5gmsaf/generator-5gmsaf @@ -29,7 +29,7 @@ scriptdir=`cd "$scriptdir"; pwd` # Command line option defaults default_branch='REL-17' -default_apis="TS26512_M1_ProvisioningSessions TS26512_M1_ContentHostingProvisioning TS26512_M1_ServerCertificatesProvisioning TS26512_M1_ContentProtocolsDiscovery TS26512_M1_ConsumptionReportingProvisioning TS26512_M1_PolicyTemplatesProvisioning TS26512_M1_MetricsReportingProvisioning M3_ContentHostingProvisioning M3_ServerCertificatesProvisioning TS26512_M5_ServiceAccessInformation TS26512_M5_ConsumptionReporting TS26512_M5_NetworkAssistance TS26512_M5_DynamicPolicies Maf_Management" +default_apis="TS26512_M1_ProvisioningSessions TS26512_M1_ContentHostingProvisioning TS26512_M1_ServerCertificatesProvisioning TS26512_M1_ContentProtocolsDiscovery TS26512_M1_ConsumptionReportingProvisioning TS26512_M1_PolicyTemplatesProvisioning TS26512_M1_MetricsReportingProvisioning M3_ContentHostingProvisioning M3_ServerCertificatesProvisioning TS26512_M5_ServiceAccessInformation TS26512_M5_ConsumptionReporting TS26512_M5_NetworkAssistance TS26512_M5_DynamicPolicies Maf_Management TS26512_M5_MetricsReporting" # Parse command line arguments ARGS=`getopt -n "$scriptname" -o 'a:b:hM:' -l 'api:,branch:,help,model-deps:' -s sh -- "$@"` @@ -118,7 +118,7 @@ destdir=`realpath -m "$scriptdir/openapi"` openapi_gen_dir=`realpath "$scriptdir/../../subprojects/open5gs/lib/sbi/support/r17-20230301-openapitools-6.4.0/openapi-generator"` sed "s@^templateDir:.*@templateDir: \"${openapi_gen_dir}/templates\"@" $openapi_gen_dir/config.yaml > $scriptdir/config.yaml -cp "${scriptdir}/openapi-templates/"*.mustache "${openapi_gen_dir}/templates/" +cp "${scriptdir}/../../subprojects/rt-common-shared/open5gs-tools/openapi-generator-templates/c/"*.mustache "${openapi_gen_dir}/templates/" cat >> $scriptdir/config.yaml << EOF importMappings: set: set @@ -141,7 +141,9 @@ if [ ! -d "$scriptdir/openapi" ]; then mkdir "$scriptdir/openapi" fi -if ! "$scriptdir/../../subprojects/rt-common-shared/5gms/scripts/generate_openapi" -a "${APIS}" -b "${BRANCH}" -c "$scriptdir/config.yaml" -l c -d "$scriptdir/openapi" -g 6.4.0 -y "$scriptdir/fix_openapi_yaml.py" -p "MSAF_API"; then +rt_common_shared="$scriptdir/../../subprojects/rt-common-shared" + +if ! "$rt_common_shared/open5gs-tools/scripts/generate_openapi" -a "${APIS}" -b "${BRANCH}" -o "$scriptdir/openapi-extra-yaml:$scriptdir/5G_APIs-overrides:$rt_common_shared/5gms/5G_APIs-overrides" -c "$scriptdir/config.yaml" -l c -d "$scriptdir/openapi" -g 6.4.0 -y "$scriptdir/fix_openapi_yaml.py" -p "MSAF_API"; then echo "Error: Failed to generate OpenAPI model" 1>&2 exit 1 fi diff --git a/src/5gmsaf/hash.c b/src/5gmsaf/hash.c index 427371b..69fafeb 100644 --- a/src/5gmsaf/hash.c +++ b/src/5gmsaf/hash.c @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include "hash.h" diff --git a/src/5gmsaf/hash.h b/src/5gmsaf/hash.h index 60f76df..9435655 100644 --- a/src/5gmsaf/hash.h +++ b/src/5gmsaf/hash.h @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef MSAF_HASH_H #define MSAF_HASH_H diff --git a/src/5gmsaf/headers.c b/src/5gmsaf/headers.c index abd9163..73b0407 100644 --- a/src/5gmsaf/headers.c +++ b/src/5gmsaf/headers.c @@ -1,6 +1,6 @@ /* * License: 5G-MAG Public License (v1.0) - * Author: David Waring + * Author: David Waring * Copyright: (C) 2023 British Broadcasting Corporation * * For full license terms please see the LICENSE file distributed with this @@ -122,7 +122,7 @@ static int _hash_do_callback(void *rec, const void *key, int key_len, const void int nf_headers_do(nf_headers_t *headers, nf_headers_do_callback_fn_t *fn, void *user_data) { hdrs_hash_do_data_t data = {fn, headers, user_data}; - + return ogs_hash_do(_hash_do_callback, &data, headers->hdrs); } diff --git a/src/5gmsaf/headers.h b/src/5gmsaf/headers.h index 71da4bf..d5eee9f 100644 --- a/src/5gmsaf/headers.h +++ b/src/5gmsaf/headers.h @@ -1,6 +1,6 @@ /* * License: 5G-MAG Public License (v1.0) - * Author: David Waring + * Author: David Waring * Copyright: (C) 2023 British Broadcasting Corporation * * For full license terms please see the LICENSE file distributed with this diff --git a/src/5gmsaf/init.c b/src/5gmsaf/init.c index 79ef927..7e0f807 100644 --- a/src/5gmsaf/init.c +++ b/src/5gmsaf/init.c @@ -1,12 +1,13 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022-2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Authors: Dev Audsin + * David Waring + * Copyright: (C) 2022-2024 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include "bsf-service-consumer.h" @@ -128,7 +129,7 @@ static void msaf_main(void *data) int rv; ogs_fsm_init(&msaf_sm, msaf_state_initial, msaf_state_final, 0); - + for ( ;; ) { ogs_pollset_poll(ogs_app()->pollset, ogs_timer_mgr_next(ogs_app()->timer_mgr)); @@ -155,20 +156,20 @@ static void msaf_main(void *data) } } done: - + ogs_fsm_fini(&msaf_sm, 0); - + } static int msaf_set_time(void) { - if(ogs_env_set("TZ", "GMT") != OGS_OK) + if (ogs_env_set("TZ", "GMT") != OGS_OK) { ogs_error("Failed to set TZ to GMT"); return OGS_ERROR; } - if(ogs_env_set("LC_TIME", "C") != OGS_OK) + if (ogs_env_set("LC_TIME", "C") != OGS_OK) { ogs_error("Failed to set LC_TIME to C"); return OGS_ERROR; diff --git a/src/5gmsaf/init.h b/src/5gmsaf/init.h index 13ce361..46a4954 100644 --- a/src/5gmsaf/init.h +++ b/src/5gmsaf/init.h @@ -1,11 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Copyright: (C) 2022 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef MSAF_INIT_H #define MSAF_INIT_H diff --git a/src/5gmsaf/local.c b/src/5gmsaf/local.c index eeb6466..27f46a8 100644 --- a/src/5gmsaf/local.c +++ b/src/5gmsaf/local.c @@ -1,12 +1,13 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Authors: Dev Audsin + * David Waring + * Copyright: (C) 2023-2024 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include "ogs-proto.h" #include "ogs-sbi.h" @@ -29,62 +30,62 @@ bool local_process_event(msaf_event_t *e) switch (e->h.id) { case MSAF_EVENT_SBI_LOCAL: { - if(e->local_id == MSAF_LOCAL_EVENT_POLICY_TEMPLATE_STATE_CHANGE) { - msaf_policy_template_change_state_event_data_t *msaf_policy_template_change_state_event_data; - msaf_provisioning_session_t *provisioning_session; - msaf_provisioning_session_t *provisioning_sess; - msaf_policy_template_node_t *msaf_policy_template_node; - msaf_policy_template_node_t *msaf_policy_template; - const char *policy_template_id; - const char *provisioning_session_id; + if (e->local_id == MSAF_LOCAL_EVENT_POLICY_TEMPLATE_STATE_CHANGE) { + msaf_policy_template_change_state_event_data_t *msaf_policy_template_change_state_event_data; + msaf_provisioning_session_t *provisioning_session; + msaf_provisioning_session_t *provisioning_sess; + msaf_policy_template_node_t *msaf_policy_template_node; + msaf_policy_template_node_t *msaf_policy_template; + const char *policy_template_id; + const char *provisioning_session_id; msaf_policy_template_change_state_event_data = (msaf_policy_template_change_state_event_data_t *)e->data; - provisioning_session = msaf_policy_template_change_state_event_data->provisioning_session; + provisioning_session = msaf_policy_template_change_state_event_data->provisioning_session; msaf_policy_template_node = msaf_policy_template_change_state_event_data->policy_template_node; - policy_template_id = msaf_policy_template_node->policy_template->policy_template_id; - - provisioning_session_id = provisioning_session->provisioningSessionId; - - provisioning_sess = msaf_provisioning_session_find_by_provisioningSessionId(provisioning_session_id); - if(provisioning_sess) { - msaf_policy_template = msaf_provisioning_session_find_policy_template_by_id(provisioning_session, policy_template_id); - if(msaf_policy_template && (msaf_policy_template_node == msaf_policy_template)) { - - if(msaf_policy_template_set_state(msaf_policy_template->policy_template, msaf_policy_template_change_state_event_data->new_state, provisioning_sess)) { - ogs_info("msaf_policy_template->policy_template->state: %d", msaf_policy_template->policy_template->state); - msaf_policy_template->last_modified = time(NULL); - if(msaf_policy_template->hash) ogs_free(msaf_policy_template->hash); + policy_template_id = msaf_policy_template_node->policy_template->policy_template_id; + + provisioning_session_id = provisioning_session->provisioningSessionId; + + provisioning_sess = msaf_provisioning_session_find_by_provisioningSessionId(provisioning_session_id); + if (provisioning_sess) { + msaf_policy_template = msaf_provisioning_session_find_policy_template_by_id(provisioning_session, policy_template_id); + if (msaf_policy_template && (msaf_policy_template_node == msaf_policy_template)) { + + if (msaf_policy_template_set_state(msaf_policy_template->policy_template, msaf_policy_template_change_state_event_data->new_state, provisioning_sess)) { + ogs_info("msaf_policy_template->policy_template->state: %d", msaf_policy_template->policy_template->state); + msaf_policy_template->last_modified = time(NULL); + if (msaf_policy_template->hash) ogs_free(msaf_policy_template->hash); msaf_policy_template->hash = calculate_policy_template_hash(msaf_policy_template->policy_template); - //MVP: going straight to READY state from PENDING - if(msaf_policy_template->policy_template->state == msaf_api_policy_template_STATE_PENDING) { - ogs_debug("MVP: set to msaf_api_policy_template_STATE_READY"); - msaf_provisioning_session_send_policy_template_state_change_event(provisioning_sess, msaf_policy_template, msaf_api_policy_template_STATE_READY, NULL, NULL); - } - ogs_info("msaf_policy_template->policy_template->state: %d", msaf_policy_template->policy_template->state); - - if(msaf_policy_template_change_state_event_data->callback) { - msaf_policy_template_change_state_event_data->callback(msaf_policy_template_change_state_event_data->provisioning_session, msaf_policy_template_change_state_event_data->policy_template_node, msaf_policy_template_change_state_event_data->new_state, msaf_policy_template_change_state_event_data->callback_user_data); - - } - - } - - } else { - ogs_error("Policy template not found"); - policy_template_state_change_local_event_data_free(e); + //MVP: going straight to READY state from PENDING + if (msaf_policy_template->policy_template->state == msaf_api_policy_template_STATE_VAL_PENDING) { + ogs_debug("MVP: set to msaf_api_policy_template_STATE_READY"); + msaf_provisioning_session_send_policy_template_state_change_event(provisioning_sess, msaf_policy_template, msaf_api_policy_template_STATE_VAL_READY, NULL, NULL); + } + ogs_info("msaf_policy_template->policy_template->state: %d", msaf_policy_template->policy_template->state); + + if (msaf_policy_template_change_state_event_data->callback) { + msaf_policy_template_change_state_event_data->callback(msaf_policy_template_change_state_event_data->provisioning_session, msaf_policy_template_change_state_event_data->policy_template_node, msaf_policy_template_change_state_event_data->new_state, msaf_policy_template_change_state_event_data->callback_user_data); + + } + + } + + } else { + ogs_error("Policy template not found"); + policy_template_state_change_local_event_data_free(e); return false; - - } - } + } + + } ogs_debug("taking event for OGS_EVENT_SBI_LOCAL"); - policy_template_state_change_local_event_data_free(e); - return true; + policy_template_state_change_local_event_data_free(e); + return true; + + } - } - //break; //DEFAULT //END @@ -92,16 +93,16 @@ bool local_process_event(msaf_event_t *e) //ogs_debug("end OGS_EVENT_SBI_LOCAL"); //break; } - break; + break; default: break; } - return false; + return false; } static void policy_template_state_change_local_event_data_free(msaf_event_t *e) { - if(e->data) ogs_free(e->data); + if (e->data) ogs_free(e->data); } diff --git a/src/5gmsaf/local.h b/src/5gmsaf/local.h index 146555e..552f0b0 100644 --- a/src/5gmsaf/local.h +++ b/src/5gmsaf/local.h @@ -1,11 +1,11 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view */ #ifndef MSAF_LOCAL_SM_H diff --git a/src/5gmsaf/meson.build b/src/5gmsaf/meson.build index b59a3f3..a56a097 100644 --- a/src/5gmsaf/meson.build +++ b/src/5gmsaf/meson.build @@ -34,6 +34,8 @@ libmsaf_dist_sources = files(''' application-server-context.c certmgr.c certmgr.h + metrics-reporting-configuration.c + metrics-reporting-configuration.h consumption-report-configuration.c consumption-report-configuration.h context.c diff --git a/src/5gmsaf/metrics-reporting-configuration.c b/src/5gmsaf/metrics-reporting-configuration.c new file mode 100644 index 0000000..4cad218 --- /dev/null +++ b/src/5gmsaf/metrics-reporting-configuration.c @@ -0,0 +1,151 @@ +/* + * License: 5G-MAG Public License (v1.0) + * Author: Vuk Stojkovic + * Copyright: (C) 2023-2024 Fraunhofer FOKUS + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ + +#include "ogs-core.h" +#include "hash.h" +#include "utilities.h" +#include "provisioning-session.h" +#include "metrics-reporting-configuration.h" + + +ogs_hash_t * msaf_metrics_reporting_map(void) +{ + ogs_hash_t *metrics_reporting_map = ogs_hash_make(); + return metrics_reporting_map; +} + +static char *calculate_metrics_reporting_configuration_hash(msaf_api_metrics_reporting_configuration_t *metrics_reporting_configuration) +{ + if (!metrics_reporting_configuration) { + ogs_error("Metrics object not found."); + return NULL; + } + + cJSON *metrics_configuration_json = msaf_api_metrics_reporting_configuration_convertResponseToJSON(metrics_reporting_configuration); + + if (!metrics_configuration_json) { + ogs_error("Conversion to JSON failed."); + return NULL; + } + + char *metrics_configuration_to_hash = cJSON_PrintUnformatted(metrics_configuration_json); + + if (!metrics_configuration_to_hash) { + cJSON_Delete(metrics_configuration_json); + return NULL; + } + + char *metrics_configuration_hashed = calculate_hash(metrics_configuration_to_hash); + + cJSON_free(metrics_configuration_to_hash); + cJSON_Delete(metrics_configuration_json); + + return metrics_configuration_hashed; +} + +msaf_metrics_reporting_configuration_t* process_and_map_metrics_reporting_configuration(msaf_provisioning_session_t *provisioning_session, msaf_api_metrics_reporting_configuration_t *parsed_config) +{ + + ogs_assert(provisioning_session); + ogs_assert(parsed_config); + + ogs_uuid_t uuid; + ogs_uuid_get(&uuid); + char new_id[OGS_UUID_FORMATTED_LENGTH + 1]; + ogs_uuid_format(new_id, &uuid); + + if (parsed_config->metrics_reporting_configuration_id != NULL) { + ogs_free(parsed_config->metrics_reporting_configuration_id); + } + parsed_config->metrics_reporting_configuration_id = msaf_strdup(new_id); + + msaf_metrics_reporting_configuration_t *msaf_metrics_config = ogs_calloc(1, sizeof(msaf_metrics_reporting_configuration_t)); + + if (!msaf_metrics_config) { + ogs_error("Failed to allocate msaf_metrics_reporting_configuration"); + return NULL; + } + + msaf_metrics_config->config = parsed_config; + msaf_metrics_config->etag = calculate_metrics_reporting_configuration_hash(msaf_metrics_config->config); + msaf_metrics_config->receivedTime = time(NULL); + + if (provisioning_session->metrics_reporting_map == NULL) { + provisioning_session->metrics_reporting_map = msaf_metrics_reporting_map(); + } + + char *hashKey = msaf_strdup(msaf_metrics_config->config->metrics_reporting_configuration_id); + ogs_hash_set(provisioning_session->metrics_reporting_map, hashKey, OGS_HASH_KEY_STRING, msaf_metrics_config); + + return msaf_metrics_config; +} + +msaf_metrics_reporting_configuration_t* msaf_metrics_reporting_configuration_retrieve(const msaf_provisioning_session_t *provisioning_session, const char *metrics_configuration_id) { + if (!provisioning_session || !metrics_configuration_id) { + return NULL; + } + return (msaf_metrics_reporting_configuration_t*)ogs_hash_get(provisioning_session->metrics_reporting_map, metrics_configuration_id, OGS_HASH_KEY_STRING); +} + +int msaf_delete_metrics_configuration(msaf_provisioning_session_t *provisioning_session, const char *metrics_configuration_id) { + + msaf_metrics_reporting_configuration_t *metrics_config = msaf_metrics_reporting_configuration_retrieve(provisioning_session, metrics_configuration_id); + + if (!metrics_config) return -1; + + msaf_metrics_reporting_configuration_free(metrics_config); + + ogs_hash_set(provisioning_session->metrics_reporting_map, msaf_strdup(metrics_configuration_id), OGS_HASH_KEY_STRING, NULL); + + return 0; +} + +void msaf_metrics_reporting_configuration_free(msaf_metrics_reporting_configuration_t *metrics_config) +{ + if (!metrics_config) return; + + if (metrics_config->config) { + msaf_api_metrics_reporting_configuration_free(metrics_config->config); + metrics_config->config = NULL; + } + + if (metrics_config->etag) { + ogs_free(metrics_config->etag); + } + + ogs_free(metrics_config); +} + +int update_metrics_configuration(msaf_metrics_reporting_configuration_t *existing_metrics_config, msaf_api_metrics_reporting_configuration_t *updated_config) { + + if (!existing_metrics_config || !updated_config) { + ogs_error("Null pointers passed"); + return -1; + } + if (existing_metrics_config->config->metrics_reporting_configuration_id) { + if (updated_config->metrics_reporting_configuration_id) { + ogs_free(updated_config->metrics_reporting_configuration_id); + } + updated_config->metrics_reporting_configuration_id = msaf_strdup(existing_metrics_config->config->metrics_reporting_configuration_id); + } + + msaf_api_metrics_reporting_configuration_free(existing_metrics_config->config); + existing_metrics_config->config = updated_config; + + if (existing_metrics_config->etag) { + ogs_free(existing_metrics_config->etag); + } + existing_metrics_config->etag = calculate_metrics_reporting_configuration_hash(updated_config); + existing_metrics_config->receivedTime = time(NULL); + + return 0; +} + + diff --git a/src/5gmsaf/metrics-reporting-configuration.h b/src/5gmsaf/metrics-reporting-configuration.h new file mode 100644 index 0000000..699aba1 --- /dev/null +++ b/src/5gmsaf/metrics-reporting-configuration.h @@ -0,0 +1,30 @@ +/* + * License: 5G-MAG Public License (v1.0) + * Author: Vuk Stojkovic + * Copyright: (C) 2023-2024 Fraunhofer Fokus + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ + +#ifndef MSAF_METRICS_REPORTING_CONFIGURATION_H +#define MSAF_METRICS_REPORTING_CONFIGURATION_H +#include "openapi/model/msaf_api_metrics_reporting_configuration.h" + + +typedef struct msaf_metrics_reporting_configuration_s { + msaf_api_metrics_reporting_configuration_t *config; + char *etag; + time_t receivedTime; +} msaf_metrics_reporting_configuration_t; + +extern ogs_hash_t *msaf_metrics_reporting_map(); +extern msaf_metrics_reporting_configuration_t* process_and_map_metrics_reporting_configuration(msaf_provisioning_session_t *provisioning_session, msaf_api_metrics_reporting_configuration_t *parsed_config); +extern msaf_metrics_reporting_configuration_t* msaf_metrics_reporting_configuration_retrieve(const msaf_provisioning_session_t *provisioning_session, const char *metrics_configuration_id); +extern void msaf_metrics_reporting_configuration_free(msaf_metrics_reporting_configuration_t *metrics_config); +extern cJSON *msaf_metrics_reporting_configuration_convertToJSON(const msaf_metrics_reporting_configuration_t *msaf_metrics_reporting_configuration); +extern int msaf_delete_metrics_configuration(msaf_provisioning_session_t *provisioning_session, const char *metrics_configuration_id); +int update_metrics_configuration(msaf_metrics_reporting_configuration_t *existing_metrics_config, msaf_api_metrics_reporting_configuration_t *updated_config); + +#endif //MSAF_METRICS_REPORTING_CONFIGURATION_H diff --git a/src/5gmsaf/msaf-fsm.c b/src/5gmsaf/msaf-fsm.c index 7870a78..85dddae 100644 --- a/src/5gmsaf/msaf-fsm.c +++ b/src/5gmsaf/msaf-fsm.c @@ -1,12 +1,13 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Authors: Dev Audsin + * David Waring + * Copyright: (C) 2023-2024 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include "msaf-sm.h" #include "msaf-m1-sm.h" @@ -17,8 +18,8 @@ program. If this file is missing then the license can be retrieved from void msaf_fsm_init(void) { ogs_fsm_init(&msaf_self()->msaf_fsm.msaf_m1_sm, msaf_m1_state_initial, msaf_m1_state_final, 0); - ogs_fsm_init(&msaf_self()->msaf_fsm.msaf_m5_sm, msaf_m5_state_initial, msaf_m5_state_final, 0); - if(msaf_self()->config.servers[MSAF_SVR_MSAF].ipv4 || msaf_self()->config.servers[MSAF_SVR_MSAF].ipv6) { + ogs_fsm_init(&msaf_self()->msaf_fsm.msaf_m5_sm, msaf_m5_state_initial, msaf_m5_state_final, 0); + if (msaf_self()->config.servers[MSAF_SVR_MSAF].ipv4 || msaf_self()->config.servers[MSAF_SVR_MSAF].ipv6) { ogs_fsm_init(&msaf_self()->msaf_fsm.msaf_maf_mgmt_sm, msaf_maf_mgmt_state_initial, msaf_maf_mgmt_state_final, 0); } } @@ -26,7 +27,7 @@ void msaf_fsm_init(void) { void msaf_fsm_fini(void) { ogs_fsm_fini(&msaf_self()->msaf_fsm.msaf_m1_sm, 0); ogs_fsm_fini(&msaf_self()->msaf_fsm.msaf_m5_sm, 0); - if(msaf_self()->config.servers[MSAF_SVR_MSAF].ipv4 || msaf_self()->config.servers[MSAF_SVR_MSAF].ipv6) { + if (msaf_self()->config.servers[MSAF_SVR_MSAF].ipv4 || msaf_self()->config.servers[MSAF_SVR_MSAF].ipv6) { ogs_fsm_fini(&msaf_self()->msaf_fsm.msaf_maf_mgmt_sm, 0); } } diff --git a/src/5gmsaf/msaf-fsm.h b/src/5gmsaf/msaf-fsm.h index 428051a..62e1c71 100644 --- a/src/5gmsaf/msaf-fsm.h +++ b/src/5gmsaf/msaf-fsm.h @@ -1,11 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Copyright: (C) 2022 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef MSAF_FSM_H #define MSAF_FSM_H diff --git a/src/5gmsaf/msaf-m1-sm.c b/src/5gmsaf/msaf-m1-sm.c index f236879..71ff961 100644 --- a/src/5gmsaf/msaf-m1-sm.c +++ b/src/5gmsaf/msaf-m1-sm.c @@ -1,7 +1,9 @@ /* * License: 5G-MAG Public License (v1.0) - * Author: Dev Audsin - * Copyright: (C) 2023 British Broadcasting Corporation + * Authors: Dev Audsin + * David Waring + * Vuk Stojkovic + * Copyright: (C) 2023-2024 British Broadcasting Corporation * * For full license terms please see the LICENSE file distributed with this * program. If this file is missing then the license can be retrieved from @@ -22,12 +24,14 @@ #include "msaf-version.h" #include "msaf-sm.h" #include "utilities.h" +#include "metrics-reporting-configuration.h" #include "consumption-report-configuration.h" #include "provisioning-session.h" #include "ContentProtocolsDiscovery_body.h" #include "openapi/api/TS26512_M1_ProvisioningSessionsAPI-info.h" #include "openapi/api/TS26512_M1_ServerCertificatesProvisioningAPI-info.h" #include "openapi/api/TS26512_M1_ContentHostingProvisioningAPI-info.h" +#include "openapi/api/TS26512_M1_MetricsReportingProvisioningAPI-info.h" #include "openapi/api/TS26512_M1_ConsumptionReportingProvisioningAPI-info.h" #include "openapi/api/M3_ServerCertificatesProvisioningAPI-info.h" #include "openapi/api/M3_ContentHostingProvisioningAPI-info.h" @@ -35,6 +39,7 @@ #include "openapi/api/TS26512_M1_PolicyTemplatesProvisioningAPI-info.h" #include "openapi/api/Maf_ManagementAPI-info.h" #include "openapi/model/msaf_api_content_hosting_configuration.h" +#include "openapi/model/msaf_api_metrics_reporting_configuration.h" #include "openapi/model/msaf_api_consumption_reporting_configuration.h" #include "msaf-m1-sm.h" @@ -87,6 +92,12 @@ maf_management_api_metadata = { MAF_MANAGEMENT_API_VERSION }; +static const nf_server_interface_metadata_t + m1_metricsreportingprovisioning_api_metadata = { + M1_METRICSREPORTINGPROVISIONING_API_NAME, + M1_METRICSREPORTINGPROVISIONING_API_VERSION +}; + static void _policy_template_extra_validation(msaf_api_policy_template_t **policy_template, const char **parse_err); static void _policy_template_remove_read_only(msaf_api_policy_template_t *policy_template); @@ -119,6 +130,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) static const nf_server_interface_metadata_t *m1_contenthostingprovisioning_api = &m1_contenthostingprovisioning_api_metadata; static const nf_server_interface_metadata_t *m1_contentprotocolsdiscovery_api = &m1_contentprotocolsdiscovery_api_metadata; static const nf_server_interface_metadata_t *m1_servercertificatesprovisioning_api = &m1_servercertificatesprovisioning_api_metadata; + static const nf_server_interface_metadata_t *m1_metricsreportingprovisioning_api = &m1_metricsreportingprovisioning_api_metadata; static const nf_server_interface_metadata_t *m1_consumptionreportingprovisioning_api = &m1_consumptionreportingprovisioning_api_metadata; static const nf_server_interface_metadata_t *m3_contenthostingprovisioning_api = &m3_contenthostingprovisioning_api_metatdata; static const nf_server_interface_metadata_t *m1_policytemplatesprovisioning_api = &m1_policytemplatesprovisioning_api_metadata; @@ -143,7 +155,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_assert(stream); message = e->message; - SWITCH(message->h.service.name) + SWITCH(message->h.service.name) CASE("3gpp-m1") if (strcmp(message->h.api.version, "v2") != 0) { char *error; @@ -174,7 +186,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) hi; hi = ogs_hash_next(hi)) { if (!ogs_strcasecmp(ogs_hash_this_key(hi), OGS_SBI_CONTENT_TYPE)) { if (ogs_strcasecmp(ogs_hash_this_val(hi), "application/x-www-form-urlencoded")) { - char *err = NULL; + char *err; const char *type; type = (const char *)ogs_hash_this_val(hi); err = ogs_msprintf( "Unsupported Media Type: received type: %s, should have been application/x-www-form-urlencoded", type); @@ -190,7 +202,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) } } msaf_provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId(message->h.resource.component[1]); - if(msaf_provisioning_session) { + if (msaf_provisioning_session) { // process the POST body purge_resource_id_node_t *purge_cache; msaf_application_server_state_ref_node_t *as_state_ref; @@ -202,8 +214,8 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_list_for_each(&msaf_provisioning_session->application_server_states, as_state_ref) { msaf_application_server_state_node_t *as_state = as_state_ref->as_state; if (as_state->application_server && as_state->application_server->canonicalHostname) { - ogs_list_for_each(&as_state->assigned_provisioning_sessions,assigned_provisioning_sessions_resource){ - if(assigned_provisioning_sessions_resource->assigned_provisioning_session == msaf_provisioning_session) { + ogs_list_for_each(&as_state->assigned_provisioning_sessions,assigned_provisioning_sessions_resource) { + if (assigned_provisioning_sessions_resource->assigned_provisioning_session == msaf_provisioning_session) { purge_cache = ogs_calloc(1, sizeof(purge_resource_id_node_t)); ogs_assert(purge_cache); @@ -211,7 +223,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) purge_cache->m1_purge_info = m1_purge_info; m1_purge_info->refs++; - if(request->http.content) + if (request->http.content) purge_cache->purge_regex = msaf_strdup(request->http.content); else purge_cache->purge_regex = NULL; @@ -221,7 +233,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_list_add(&as_state->purge_content_hosting_cache, purge_cache); } else { - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning Session [%s] is not assigned to an Application Server", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 500, 3, message, "Provisioning session is not assigned to an Application Server.", err, NULL, m1_contenthostingprovisioning_api, app_meta)); @@ -229,7 +241,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) } } } else { - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning Session [%s] : Unable to get information about Application Server", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 500, 3, message, "Unable to get information about Application Server", err, NULL, m1_contenthostingprovisioning_api, app_meta)); @@ -248,13 +260,19 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_assert(true == ogs_sbi_server_send_response(stream, response)); } } else { - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning session [%s] does not exist.", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Provisioning session does not exist.", err, NULL, m1_contenthostingprovisioning_api, app_meta)); ogs_free(err); } + } else { + char *err; + err = ogs_msprintf("POST for sub-resource [%s] with a sub-resource identifier for Provisioning Session [%s] does not exist.", message->h.resource.component[2], message->h.resource.component[1]); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Provisioning session does not exist.", err, NULL, m1_contenthostingprovisioning_api, app_meta)); + ogs_free(err); } } else if (message->h.resource.component[1] && message->h.resource.component[2] && @@ -266,6 +284,9 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) CASE("consumption-reporting-configuration") api = m1_consumptionreportingprovisioning_api; break; + CASE("metrics-reporting-configurations") + api = m1_metricsreportingprovisioning_api; + break; CASE("content-hosting-configuration") api = m1_contenthostingprovisioning_api; break; @@ -280,16 +301,21 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) msaf_provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId(message->h.resource.component[1]); if (!msaf_provisioning_session) { - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning session [%s] does not exist.", message->h.resource.component[1]); ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Provisioning session does not exist.", err, NULL, api, app_meta)); + ogs_assert(true == nf_server_send_error(stream, 404, 3, message, + "Provisioning session does not exist.", err, NULL, + api?api:m1_provisioningsession_api, app_meta)); ogs_free(err); } else if (!api) { - char *err = NULL; - err = ogs_msprintf("Unknown sub resource [%s] for provisioning session [%s]", message->h.resource.component[2], message->h.resource.component[1]); + char *err; + err = ogs_msprintf("Unknown sub resource [%s] for provisioning session [%s]", + message->h.resource.component[2], message->h.resource.component[1]); ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Provisioning session does not exist.", err, NULL, m1_provisioningsession_api, app_meta)); + ogs_assert(true == nf_server_send_error(stream, 404, 3, message, + "Provisioning session does not exist.", err, NULL, + m1_provisioningsession_api, app_meta)); ogs_free(err); } else if (api == m1_contenthostingprovisioning_api) { // process the POST body @@ -307,7 +333,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) } if (!content_hosting_config) { - char *err = NULL; + char *err; err = ogs_msprintf("Unable to parse Content Hosting Configuration as JSON for the Provisioning Session [%s].", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 400, 2, message, "Bad Content Hosting Configuration.", err, NULL, m1_contenthostingprovisioning_api, app_meta)); @@ -317,17 +343,15 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) rv = msaf_distribution_create(content_hosting_config, msaf_provisioning_session, &reason); content_hosting_config = NULL; - + if (rv) { - + ogs_debug("Content Hosting Configuration created successfully"); if (msaf_application_server_state_set_on_post(msaf_provisioning_session)) { chc = msaf_get_content_hosting_configuration_by_provisioning_session_id( message->h.resource.component[1]); if (chc != NULL) { char *text; - msaf_provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId( - message->h.resource.component[1]); response = nf_server_new_response(request->h.uri, "application/json", msaf_provisioning_session->httpMetadata.contentHostingConfiguration.received, msaf_provisioning_session->httpMetadata.contentHostingConfiguration.hash, @@ -340,21 +364,21 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) response = NULL; cJSON_Delete(chc); } else { - char *err = NULL; + char *err; err = ogs_msprintf("Unable to retrieve the Content Hosting Configuration for the Provisioning Session [%s].", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 2, message, "Unable to retrieve the Content Hosting Configuration.", err, NULL, m1_contenthostingprovisioning_api, app_meta)); ogs_free(err); } } else { - char *err = NULL; + char *err; err = ogs_msprintf("Unable to retrieve certificate for the Provisioning Session [%s].", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 500, 2, message, "Internal Server Error.", err, NULL, m1_contenthostingprovisioning_api, app_meta)); ogs_free(err); } } else { - char *err = NULL; + char *err; err = ogs_msprintf("Creation of the Content Hosting Configuration failed for the Provisioning Session [%s]: %s", message->h.resource.component[1], reason); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 400, 2, message, "Creation of the Content Hosting Configuration failed.", err, NULL, m1_contenthostingprovisioning_api, app_meta)); @@ -438,7 +462,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_hash_set(msaf_provisioning_session->certificate_map, msaf_strdup(csr_cert->id), OGS_HASH_KEY_STRING, msaf_strdup(csr_cert->id)); ogs_sbi_response_t *response; location = ogs_msprintf("%s/%s", request->h.uri, csr_cert->id); - if(csr_cert->cache_control_max_age){ + if (csr_cert->cache_control_max_age) { m1_server_certificates_response_max_age = csr_cert->cache_control_max_age; } else { m1_server_certificates_response_max_age = msaf_self()->config.server_response_cache_control->m1_server_certificates_response_max_age; @@ -461,7 +485,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) char *location; ogs_hash_set(msaf_provisioning_session->certificate_map, msaf_strdup(cert), OGS_HASH_KEY_STRING, cert); - + location = ogs_msprintf("%s/%s", request->h.uri, cert); response = nf_server_new_response(location, NULL, 0, NULL, 0, NULL, m1_servercertificatesprovisioning_api, app_meta); nf_server_populate_response(response, 0, NULL, 200); @@ -475,9 +499,9 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) char *location; new_cert = server_cert_new("newcert", canonical_domain_name, NULL); ogs_hash_set(msaf_provisioning_session->certificate_map, msaf_strdup(new_cert->id), OGS_HASH_KEY_STRING, msaf_strdup(new_cert->id)); - + location = ogs_msprintf("%s/%s", request->h.uri, new_cert->id); - if(new_cert->cache_control_max_age){ + if (new_cert->cache_control_max_age) { m1_server_certificates_response_max_age = new_cert->cache_control_max_age; } else { m1_server_certificates_response_max_age = msaf_self()->config.server_response_cache_control->m1_server_certificates_response_max_age; @@ -523,14 +547,73 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) msaf_api_consumption_reporting_configuration_free(report_config); } else { ogs_sbi_response_t *response; - - response = nf_server_new_response(NULL, NULL, 0, NULL, 0, NULL, api, app_meta); + + response = nf_server_new_response(message->h.uri, NULL, 0, NULL, 0, NULL, api, app_meta); ogs_assert(response); - nf_server_populate_response(response, 0, NULL, 204); + nf_server_populate_response(response, 0, NULL, 201); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); } } - } else if (api == m1_policytemplatesprovisioning_api) { + } else if (api == m1_metricsreportingprovisioning_api) { + + cJSON *json; + const char *parse_err = NULL; + + ogs_debug("POST metrics-reporting-configuration"); + json = cJSON_Parse(request->http.content); + + if (!json) { + char *err; + err = ogs_msprintf("Bad MetricsReportingConfiguration for provisioning session [%s]", message->h.resource.component[1]); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 400, 2, message, "Bad request.", err, NULL, api,app_meta)); + ogs_free(err); + } else { + + msaf_api_metrics_reporting_configuration_t *metrics_config; + metrics_config = msaf_api_metrics_reporting_configuration_parseRequestFromJSON(json, &parse_err); + + cJSON_Delete(json); + + if (!metrics_config) { + char *err; + err = ogs_msprintf("Bad MetricsReportingConfiguration for provisioning session [%s]: %s", message->h.resource.component[1], parse_err); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 400, 2, message, "Bad request.", err, NULL, api,app_meta)); + ogs_free(err); + } else { + msaf_metrics_reporting_configuration_t *msaf_new_metrics_config; + msaf_new_metrics_config = process_and_map_metrics_reporting_configuration(msaf_provisioning_session,metrics_config); + + ogs_debug(" Metrics Reporting Configuration ID: %s", msaf_new_metrics_config ? msaf_new_metrics_config->config->metrics_reporting_configuration_id : "null"); + + if (msaf_new_metrics_config) { + + if (msaf_provisioning_session->sai_cache) { + msaf_sai_cache_clear(msaf_provisioning_session->sai_cache); + ogs_debug("SAI cache cleared for provisioning session [%s]", message->h.resource.component[1]); + } + + ogs_sbi_response_t *response; + + char *location = ogs_msprintf("%s/%s", request->h.uri, msaf_new_metrics_config->config->metrics_reporting_configuration_id); + + response = nf_server_new_response(location, NULL, 0, NULL, 0, NULL, api, app_meta); + ogs_assert(response); + nf_server_populate_response(response, 0, NULL, 201); + ogs_assert(true == ogs_sbi_server_send_response(stream, response)); + ogs_free(location); + } else { + char *err; + err = ogs_msprintf("Failed to create MetricsReportingConfiguration for provisioning session [%s]", message->h.resource.component[1]); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 500, 2, message, "Internal Server Error.", err,NULL, api, app_meta)); + ogs_free(err); + } + } + } + } + else if (api == m1_policytemplatesprovisioning_api) { cJSON *policy_template = NULL; msaf_api_policy_template_t *policy_temp = NULL; char *pol_temp; @@ -560,16 +643,17 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) location = ogs_msprintf("%s/%s", request->h.uri, msaf_policy_template->policy_template->policy_template_id); - //response = nf_server_new_response(location, NULL, msaf_policy_template->last_modified, msaf_policy_template->hash, msaf_self()->config.server_response_cache_control->m1_provisioning_session_response_max_age, NULL, m1_policytemplatesprovisioning_api, app_meta); + /* If returning the updated policy template in a 200 response, this should use: + * response = nf_server_new_response(location, NULL, msaf_policy_template->last_modified, msaf_policy_template->hash, msaf_self()->config.server_response_cache_control->m1_provisioning_session_response_max_age, NULL, api, app_meta); + */ response = nf_server_new_response(location, NULL, 0, NULL, 0, NULL, api, app_meta); nf_server_populate_response(response, 0, NULL, 201); ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); - - ogs_free(location); + ogs_free(location); } else { - char *err = NULL; + char *err; err = ogs_msprintf("Problem adding the policy template to the provisioning session [%s].", message->h.resource.component[1]); ogs_error("%s",err); ogs_assert(true == nf_server_send_error(stream, 404, 2, message, "Problem adding the policy template.", err, NULL, api, app_meta)); @@ -578,33 +662,33 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_info("policy template id: %s", policy_temp->policy_template_id); } else { - char *err = NULL; + char *err; err = ogs_msprintf("Problem parsing Policy template JSON: %s", parse_err); ogs_error("%s",err); ogs_assert(true == nf_server_send_error(stream, 400, 2, message, "Problem parsing Policy template JSON.", err, NULL, api, app_meta)); ogs_free(err); } - if (policy_template) cJSON_Delete(policy_template); + if (policy_template) cJSON_Delete(policy_template); if (pol_temp) cJSON_free(pol_temp); } - } else if (message->h.resource.component[1] && !message->h.resource.component[2]){ + } else if (message->h.resource.component[1] && !message->h.resource.component[2]) { msaf_provisioning_session_t *msaf_provisioning_session; msaf_provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId(message->h.resource.component[1]); - if(msaf_provisioning_session) { - char *err = NULL; + if (msaf_provisioning_session) { + char *err; err = ogs_msprintf("Method [%s] not allowed for [%s].", message->h.method, message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 405, 1, message, "Method not allowed.", err, NULL, m1_provisioningsession_api, app_meta)); ogs_free(err); } else { - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning session [%s] does not exist.", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 1, message, "Provisioning session does not exist.", err, NULL, m1_provisioningsession_api, app_meta)); ogs_free(err); - } + } } else { cJSON *entry; @@ -662,7 +746,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) } asp_id = entry->valuestring; } - + msaf_provisioning_session = msaf_provisioning_session_create(provisioning_session_type, asp_id, external_app_id); provisioning_session = msaf_provisioning_session_get_json(msaf_provisioning_session->provisioningSessionId); if (provisioning_session != NULL) { @@ -693,9 +777,9 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) break; CASE(OGS_SBI_HTTP_METHOD_GET) - + if (message->h.resource.component[1] && message->h.resource.component[2] && message->h.resource.component[3] && !message->h.resource.component[4]) { - msaf_provisioning_session_t *msaf_provisioning_session; + msaf_provisioning_session_t *msaf_provisioning_session; const nf_server_interface_metadata_t *api = NULL; SWITCH(message->h.resource.component[2]) @@ -705,116 +789,160 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) CASE("certificates") api = m1_servercertificatesprovisioning_api; break; + CASE("metrics-reporting-configurations") + api = m1_metricsreportingprovisioning_api; + break; DEFAULT END - msaf_provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId(message->h.resource.component[1]); + msaf_provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId( + message->h.resource.component[1]); if (!msaf_provisioning_session) { - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning session [%s] is not available.", message->h.resource.component[1]); ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 2, message, "Provisioning session does not exists.", err, NULL, api, app_meta)); + ogs_assert(true == nf_server_send_error(stream, 404, 2, message, + "Provisioning session does not exists.", err, NULL, + api?api:m1_provisioningsession_api, app_meta)); ogs_free(err); } else if (!api) { - char *err = NULL; - err = ogs_msprintf("Unknown sub-resource [%s] for provisioning session [%s].", message->h.resource.component[2], message->h.resource.component[1]); + char *err; + err = ogs_msprintf("Unknown sub-resource [%s] for provisioning session [%s].", + message->h.resource.component[2], message->h.resource.component[1]); ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 2, message, "Unknown provisioning session sub-resource.", err, NULL, m1_provisioningsession_api, app_meta)); + ogs_assert(true == nf_server_send_error(stream, 404, 2, message, + "Unknown provisioning session sub-resource.", err, NULL, + m1_provisioningsession_api, app_meta)); ogs_free(err); } else if (api == m1_policytemplatesprovisioning_api) { - msaf_provisioning_session_t *msaf_provisioning_session; - msaf_provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId(message->h.resource.component[1]); - if (msaf_provisioning_session) { - msaf_policy_template_node_t *msaf_policy_template; - msaf_policy_template = msaf_provisioning_session_find_policy_template_by_id(msaf_provisioning_session, message->h.resource.component[3]); - if(msaf_policy_template) { - cJSON *policy_template; - char *policy_template_body; - - policy_template = msaf_policy_template_convertToJSON(msaf_policy_template->policy_template); - policy_template_body = cJSON_Print(policy_template); - - response = nf_server_new_response(NULL, "application/json", msaf_policy_template->last_modified, msaf_policy_template->hash, msaf_self()->config.server_response_cache_control->m1_provisioning_session_response_max_age, NULL, m1_policytemplatesprovisioning_api, app_meta); - nf_server_populate_response(response, strlen(policy_template_body), policy_template_body, 200); - ogs_assert(true == ogs_sbi_server_send_response(stream, response)); - response = NULL; - - cJSON_Delete(policy_template); + msaf_policy_template_node_t *msaf_policy_template; + msaf_policy_template = msaf_provisioning_session_find_policy_template_by_id( + msaf_provisioning_session, message->h.resource.component[3]); + if (msaf_policy_template) { + cJSON *policy_template; + char *policy_template_body; + + policy_template = msaf_policy_template_convertToJSON(msaf_policy_template->policy_template); + policy_template_body = cJSON_Print(policy_template); + + response = nf_server_new_response(NULL, "application/json", msaf_policy_template->last_modified, + msaf_policy_template->hash, + msaf_self()->config.server_response_cache_control-> + m1_provisioning_session_response_max_age, + NULL, m1_policytemplatesprovisioning_api, app_meta); + nf_server_populate_response(response, strlen(policy_template_body), policy_template_body, 200); + ogs_assert(true == ogs_sbi_server_send_response(stream, response)); + response = NULL; + + cJSON_Delete(policy_template); + } else { + char *err; + err = ogs_msprintf("Provisioning session [%s] has no policy template [%s].", + message->h.resource.component[1], message->h.resource.component[3]); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 404, 3, message, + "Policy template does not exists.", err, NULL, api, + app_meta)); + ogs_free(err); + } + } else if (api == m1_servercertificatesprovisioning_api) { + msaf_certificate_t *cert; + ogs_sbi_response_t *response; + const char *provisioning_session_cert; + + provisioning_session_cert = ogs_hash_get(msaf_provisioning_session->certificate_map, message->h.resource.component[3], OGS_HASH_KEY_STRING); + if (!provisioning_session_cert) { + char *err; + err = ogs_msprintf("Certificate [%s] not found in provisioning session [%s]", message->h.resource.component[3], message->h.resource.component[1]); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Certificate not found.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); + ogs_free(err); + break; + } + cert = server_cert_retrieve(message->h.resource.component[3]); + if (!cert) { + char *err; + err = ogs_msprintf("Certificate [%s] management problem", message->h.resource.component[3]); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 500, 3, message, "Certificate management problem.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); + ogs_free(err); + break; + } + if (!cert->return_code) { + int m1_server_certificates_response_max_age; + if (cert->cache_control_max_age) { + m1_server_certificates_response_max_age = cert->cache_control_max_age; } else { - char *err = NULL; - err = ogs_msprintf("Provisioning session [%s] has no policy template [%s].", message->h.resource.component[1], message->h.resource.component[3]); - ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Policy template does not exists.", err, NULL, m1_policytemplatesprovisioning_api, app_meta)); - ogs_free(err); + m1_server_certificates_response_max_age = msaf_self()->config.server_response_cache_control->m1_server_certificates_response_max_age; } + response = nf_server_new_response(NULL, "application/x-pem-file", cert->last_modified, cert->server_certificate_hash, m1_server_certificates_response_max_age, NULL, m1_servercertificatesprovisioning_api, app_meta); + nf_server_populate_response(response, strlen(cert->certificate), msaf_strdup(cert->certificate), 200); + ogs_assert(response); + ogs_assert(true == ogs_sbi_server_send_response(stream, response)); + } else if (cert->return_code == 4) { + char *err; + err = ogs_msprintf("Certificate [%s] does not exists.", cert->id); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Certificate does not exists.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); + ogs_free(err); + } else if (cert->return_code == 8) { + ogs_sbi_response_t *response; + response = nf_server_new_response(NULL, NULL, 0, NULL, 0, NULL, m1_servercertificatesprovisioning_api, app_meta); + nf_server_populate_response(response, 0, NULL, 204); + ogs_assert(response); + ogs_assert(true == ogs_sbi_server_send_response(stream, response)); + } else { + char *err; + err = ogs_msprintf("Certificate [%s] management problem.", cert->id); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 500, 3, message, "Certificate management problem.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); + ogs_free(err); + } + msaf_certificate_free(cert); + } + else if (api == m1_metricsreportingprovisioning_api) { - } + ogs_debug("GET metrics-reporting-configuration"); - } else if (api == m1_servercertificatesprovisioning_api) { - msaf_provisioning_session_t *msaf_provisioning_session; - msaf_provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId(message->h.resource.component[1]); - if (msaf_provisioning_session) { - msaf_certificate_t *cert; - ogs_sbi_response_t *response; - const char *provisioning_session_cert; - provisioning_session_cert = ogs_hash_get(msaf_provisioning_session->certificate_map, message->h.resource.component[3], OGS_HASH_KEY_STRING); - if(!provisioning_session_cert) { - char *err = NULL; - err = ogs_msprintf("Certificate [%s] not found in provisioning session [%s]", message->h.resource.component[3], message->h.resource.component[1]); - ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Certificate not found.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); - ogs_free(err); - break; - } - cert = server_cert_retrieve(message->h.resource.component[3]); - if (!cert) { - char *err = NULL; - err = ogs_msprintf("Certificate [%s] management problem", message->h.resource.component[3]); - ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 500, 3, message, "Certificate management problem.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); - ogs_free(err); - break; - } + msaf_metrics_reporting_configuration_t *metricsReportingConfiguration; + metricsReportingConfiguration = msaf_metrics_reporting_configuration_retrieve(msaf_provisioning_session, message->h.resource.component[3]); - if(!cert->return_code) { - int m1_server_certificates_response_max_age; - if(cert->cache_control_max_age){ - m1_server_certificates_response_max_age = cert->cache_control_max_age; - } else { - m1_server_certificates_response_max_age = msaf_self()->config.server_response_cache_control->m1_server_certificates_response_max_age; + if (!metricsReportingConfiguration) { + char *err = ogs_msprintf("Metrics Reporting Configuration [%s] not found", message->h.resource.component[3]); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Metrics Reporting Configuration not found.", err, NULL, m1_metricsreportingprovisioning_api, app_meta)); + ogs_free(err); + } + else { + cJSON *mrc_json_data = msaf_api_metrics_reporting_configuration_convertResponseToJSON(metricsReportingConfiguration->config); + if (mrc_json_data) { + char *metrics_response_body = cJSON_Print(mrc_json_data); + if(metrics_response_body) { + ogs_debug("Retrieved Metrics Reporting Configuration:\n%s", metrics_response_body); + ogs_sbi_response_t *response; + response = nf_server_new_response(NULL, "application/json", + metricsReportingConfiguration->receivedTime, + metricsReportingConfiguration->etag, + msaf_self()->config.server_response_cache_control->m1_metrics_reporting_response_max_age, + NULL, m1_metricsreportingprovisioning_api, app_meta); + nf_server_populate_response(response, strlen(metrics_response_body), metrics_response_body, 200); + ogs_assert(true == ogs_sbi_server_send_response(stream, response)); + response= NULL; + cJSON_Delete(mrc_json_data); + } + else { + char *err = ogs_msprintf("Failed to generate JSON string for Metrics Reporting Configuration"); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 500, 3, message, "Internal Server Error", err, NULL, m1_metricsreportingprovisioning_api, app_meta)); + ogs_free(err); } - response = nf_server_new_response(NULL, "application/x-pem-file", cert->last_modified, cert->server_certificate_hash, m1_server_certificates_response_max_age, NULL, m1_servercertificatesprovisioning_api, app_meta); - nf_server_populate_response(response, strlen(cert->certificate), msaf_strdup(cert->certificate), 200); - ogs_assert(response); - ogs_assert(true == ogs_sbi_server_send_response(stream, response)); - } else if(cert->return_code == 4){ - char *err = NULL; - err = ogs_msprintf("Certificate [%s] does not exists.", cert->id); - ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Certificate does not exists.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); - ogs_free(err); - } else if(cert->return_code == 8){ - ogs_sbi_response_t *response; - response = nf_server_new_response(NULL, NULL, 0, NULL, 0, NULL, m1_servercertificatesprovisioning_api, app_meta); - nf_server_populate_response(response, 0, NULL, 204); - ogs_assert(response); - ogs_assert(true == ogs_sbi_server_send_response(stream, response)); } else { - char *err = NULL; - err = ogs_msprintf("Certificate [%s] management problem.", cert->id); + char *err = ogs_msprintf("Failed to convert Metrics Reporting Configuration to JSON"); ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 500, 3, message, "Certificate management problem.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); + ogs_assert(true == nf_server_send_error(stream, 500, 3, message, "Internal Server Error", err, NULL, m1_metricsreportingprovisioning_api, app_meta)); ogs_free(err); } - msaf_certificate_free(cert); - - } else { - char *err = NULL; - err = ogs_msprintf("Provisioning session [%s] is not available.", message->h.resource.component[1]); - ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Provisioning session does not exists.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); - ogs_free(err); } } } else if (message->h.resource.component[1] && message->h.resource.component[2] && !message->h.resource.component[3]) { @@ -836,16 +964,21 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) msaf_provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId(message->h.resource.component[1]); if (!msaf_provisioning_session) { - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning session [%s] is not available.", message->h.resource.component[1]); ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 2, message, "Provisioning session does not exists.", err, NULL, api, app_meta)); + ogs_assert(true == nf_server_send_error(stream, 404, 2, message, + "Provisioning session does not exists.", err, NULL, + api?api:m1_provisioningsession_api, app_meta)); ogs_free(err); } else if (!api) { - char *err = NULL; - err = ogs_msprintf("Unknown sub-resource [%s] for provisioning session [%s].", message->h.resource.component[2], message->h.resource.component[1]); + char *err; + err = ogs_msprintf("Unknown sub-resource [%s] for provisioning session [%s].", + message->h.resource.component[2], message->h.resource.component[1]); ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 2, message, "Unknown provisioning session sub-resource.", err, NULL, m1_provisioningsession_api, app_meta)); + ogs_assert(true == nf_server_send_error(stream, 404, 2, message, + "Unknown provisioning session sub-resource.", err, NULL, + m1_provisioningsession_api, app_meta)); ogs_free(err); } else if (api == m1_contenthostingprovisioning_api) { cJSON *chc; @@ -862,7 +995,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) cJSON_Delete(chc); } else { - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning Session [%s]: Unable to retrieve the Content Hosting Configuration", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 2, message, "Unable to retrieve the Content Hosting Configuration.", err, NULL, m1_contenthostingprovisioning_api, app_meta)); @@ -884,7 +1017,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) body = msaf_consumption_report_configuration_body(msaf_provisioning_session); if (!body) { - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning Session [%s]: Unable to retrieve the Consumption Reporting Configuration", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 2, message, "Unable to retrieve the Consumption Reporting Configuration.", err, NULL, m1_contenthostingprovisioning_api, app_meta)); @@ -919,7 +1052,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); } else { - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning Session [%s] is not available.", message->h.resource.component[1]); ogs_error("%s", err); @@ -941,6 +1074,9 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) CASE("consumption-reporting-configuration") api = m1_consumptionreportingprovisioning_api; break; + CASE("metrics-reporting-configurations") + api = m1_metricsreportingprovisioning_api; + break; CASE("content-hosting-configuration") api = m1_contenthostingprovisioning_api; break; @@ -955,18 +1091,23 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) msaf_provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId(message->h.resource.component[1]); if (!msaf_provisioning_session) { - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning Session [%s] is not available.", message->h.resource.component[1]); ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 2, message, "Provisioning session does not exists.", err, NULL, api, app_meta)); + ogs_assert(true == nf_server_send_error(stream, 404, 2, message, + "Provisioning session does not exists.", err, NULL, + api?api:m1_provisioningsession_api, app_meta)); ogs_free(err); } else if (!api) { - char *err = NULL; - err = ogs_msprintf("Unknown sub-resource [%s] for provisioning Session [%s].", message->h.resource.component[2], message->h.resource.component[1]); + char *err; + err = ogs_msprintf("Unknown sub-resource [%s] for provisioning Session [%s].", + message->h.resource.component[2], message->h.resource.component[1]); ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 2, message, "Unknown provisioning session sub-resource.", err, NULL, m1_provisioningsession_api, app_meta)); + ogs_assert(true == nf_server_send_error(stream, 404, 2, message, + "Unknown provisioning session sub-resource.", err, NULL, + m1_provisioningsession_api, app_meta)); ogs_free(err); } else if (api == m1_contenthostingprovisioning_api) { if (!message->h.resource.component[3]) { @@ -977,7 +1118,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) cJSON *content_hosting_config = cJSON_Parse(request->http.content); if (!content_hosting_config) { - char *err = NULL; + char *err; err = ogs_msprintf("While updating the Content Hosting Configuration for the Provisioning Session [%s], Failure parsing ContentHostingConfiguration JSON.",message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 400, 2, message, "Bad ContentHosting Configuration JSON.", err, NULL, m1_contenthostingprovisioning_api, app_meta)); @@ -991,7 +1132,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) cJSON_free(txt); } - if(msaf_provisioning_session->contentHostingConfiguration) { + if (msaf_provisioning_session->contentHostingConfiguration) { msaf_api_content_hosting_configuration_free(msaf_provisioning_session->contentHostingConfiguration); msaf_provisioning_session->contentHostingConfiguration = NULL; msaf_sai_cache_clear(msaf_provisioning_session->sai_cache); @@ -999,7 +1140,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) rv = msaf_distribution_create(content_hosting_config, msaf_provisioning_session, &reason); content_hosting_config = NULL; - if (rv){ + if (rv) { msaf_application_server_state_update(msaf_provisioning_session); ogs_debug("Content Hosting Configuration updated successfully"); @@ -1012,14 +1153,14 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); } else { - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning Session [%s]: Update to Content Hosting Configuration failed: %s", message->h.resource.component[1], reason); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 400, 2, message, "Failed to update the contentHostingConfiguration.", err, NULL, m1_contenthostingprovisioning_api, app_meta)); ogs_free(err); } } else { - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning Session [%s]: " "Unknown Content Hosting Configuration sub-resource [%s].", message->h.resource.component[1], @@ -1031,13 +1172,75 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) ); ogs_free(err); } + } else if (api == m1_metricsreportingprovisioning_api) { + if (message->h.resource.component[3] && !message->h.resource.component[4]) { + + cJSON *json; + const char *parse_err = NULL; + char *metrics_reporting_configuration_id = message->h.resource.component[3]; + + ogs_debug("UPDATE metrics-reporting-configuration"); + + msaf_metrics_reporting_configuration_t *metrics_configuration = msaf_metrics_reporting_configuration_retrieve(msaf_provisioning_session, metrics_reporting_configuration_id); + + if (!metrics_configuration) { + char *err = ogs_msprintf("Metrics Reporting Configuration [%s] not found", metrics_reporting_configuration_id); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Metrics Reporting Configuration not found.", err, NULL, api, app_meta)); + ogs_free(err); + return; + } + else{ + json = cJSON_Parse(request->http.content); + if (!json) { + char *err = ogs_msprintf("Bad request body for updating Metrics Reporting Configuration [%s]", metrics_reporting_configuration_id); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 400, 3, message, "Bad request.", err, NULL, api, app_meta)); + ogs_free(err); + } + else { + msaf_api_metrics_reporting_configuration_t *updated_config = msaf_api_metrics_reporting_configuration_parseRequestFromJSON(json, &parse_err); + cJSON_Delete(json); + + if (!updated_config) { + char *err = ogs_msprintf("Unable to parse updated Metrics Reporting Configuration: %s", parse_err); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 400, 3, message, "Bad request.", err, NULL, api, app_meta)); + ogs_free(err); + } else { + int result = update_metrics_configuration(metrics_configuration, updated_config); + if (result == 0) { + + if (msaf_provisioning_session->sai_cache) { + msaf_sai_cache_clear(msaf_provisioning_session->sai_cache); + ogs_debug("SAI cache cleared for provisioning session [%s]", message->h.resource.component[1]); + } + + ogs_sbi_response_t *response = nf_server_new_response(NULL, NULL, 0, NULL, 0, NULL, api, app_meta); + ogs_assert(response); + nf_server_populate_response(response, 0, NULL, 200); + ogs_assert(true == ogs_sbi_server_send_response(stream, response)); + } else { + char *err = ogs_msprintf("Failed to update Metrics Reporting Configuration [%s]", metrics_reporting_configuration_id); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 500, 3, message, "Internal Server Error.", err, NULL, api, app_meta)); + ogs_free(err); + } + } + } + } + } else { + char *err = ogs_msprintf("Unrecognised Metrics Reporting Configurations operation for provisioning session [%s]", message->h.resource.component[1]); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 400, 2, message, "Bad request.", err, NULL, api, app_meta)); + ogs_free(err); + } } else if (api == m1_servercertificatesprovisioning_api) { if (message->h.resource.component[3] && !message->h.resource.component[4]) { char *cert_id; char *cert; int rv; ogs_sbi_response_t *response; - msaf_provisioning_session_t *msaf_provisioning_session; { ogs_hash_index_t *hi; @@ -1045,7 +1248,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) hi; hi = ogs_hash_next(hi)) { if (!ogs_strcasecmp(ogs_hash_this_key(hi), OGS_SBI_CONTENT_TYPE)) { if (ogs_strcasecmp(ogs_hash_this_val(hi), "application/x-pem-file")) { - char *err = NULL; + char *err; const char *type; type = ogs_hash_this_val(hi); err = ogs_msprintf( "Unsupported Media Type: received type: %s, should have been application/x-pem-file", type); @@ -1062,63 +1265,52 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) } } - msaf_provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId(message->h.resource.component[1]); - - if(msaf_provisioning_session) { - const char *provisioning_session_cert; - provisioning_session_cert = ogs_hash_get(msaf_provisioning_session->certificate_map, message->h.resource.component[3], OGS_HASH_KEY_STRING); - cert_id = message->h.resource.component[3]; - cert = msaf_strdup(request->http.content); - rv = server_cert_set(cert_id, cert); - // response = ogs_sbi_response_new(); + const char *provisioning_session_cert; + provisioning_session_cert = ogs_hash_get(msaf_provisioning_session->certificate_map, message->h.resource.component[3], OGS_HASH_KEY_STRING); + cert_id = message->h.resource.component[3]; + cert = msaf_strdup(request->http.content); + rv = server_cert_set(cert_id, cert); + // response = ogs_sbi_response_new(); - if (rv == 0 && provisioning_session_cert){ - response = nf_server_new_response(NULL, NULL, 0, NULL, 0, NULL, m1_servercertificatesprovisioning_api, app_meta); - nf_server_populate_response(response, 0, NULL, 204); - ogs_assert(response); - ogs_assert(true == ogs_sbi_server_send_response(stream, response)); - } else if (rv == 3 && provisioning_session_cert ) { + if (rv == 0 && provisioning_session_cert) { + response = nf_server_new_response(NULL, NULL, 0, NULL, 0, NULL, m1_servercertificatesprovisioning_api, app_meta); + nf_server_populate_response(response, 0, NULL, 204); + ogs_assert(response); + ogs_assert(true == ogs_sbi_server_send_response(stream, response)); + } else if (rv == 3 && provisioning_session_cert ) { - char *err = NULL; - err = ogs_msprintf("A server certificate with id [%s] already exist", cert_id); - ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 403, 3, message, "A server certificate already exist.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); - ogs_free(err); - } else if(rv == 4 || ! provisioning_session_cert) { - char *err = NULL; - err = ogs_msprintf("Server certificate with id [%s] does not exist", cert_id); - ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Server certificate does not exist.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); - ogs_free(err); - } else if(rv == 5) { - char *err = NULL; - err = ogs_msprintf("CSR was never generated for this certificate Id [%s]", cert_id); - ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 400, 3, message, "CSR was never generated for the certificate.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); - ogs_free(err); - } else if(rv == 6) { - char *err = NULL; - err = ogs_msprintf("The public certificate [%s] provided does not match the key", cert_id); - ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 400, 3, message, "The public certificate provided does not match the key.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); - ogs_free(err); - } else { - char *err = NULL; - err = ogs_msprintf("There was a certificate management problem for the certificate id [%s].", cert_id); - ogs_error("%s", err); + char *err; + err = ogs_msprintf("A server certificate with id [%s] already exist", cert_id); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 403, 3, message, "A server certificate already exist.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); + ogs_free(err); + } else if (rv == 4 || ! provisioning_session_cert) { + char *err; + err = ogs_msprintf("Server certificate with id [%s] does not exist", cert_id); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Server certificate does not exist.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); + ogs_free(err); + } else if (rv == 5) { + char *err; + err = ogs_msprintf("CSR was never generated for this certificate Id [%s]", cert_id); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 400, 3, message, "CSR was never generated for the certificate.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); + ogs_free(err); + } else if (rv == 6) { + char *err; + err = ogs_msprintf("The public certificate [%s] provided does not match the key", cert_id); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 400, 3, message, "The public certificate provided does not match the key.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); + ogs_free(err); + } else { + char *err; + err = ogs_msprintf("There was a certificate management problem for the certificate id [%s].", cert_id); + ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 500, 3, message, "There was a certificate management problem.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); - ogs_free(err); - } - ogs_free(cert); + ogs_assert(true == nf_server_send_error(stream, 500, 3, message, "There was a certificate management problem.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); + ogs_free(err); } - - } else { - char *err = NULL; - err = ogs_msprintf("[%s]: Resource not found.", message->h.method); - ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 1, message, "Resource not found.", err, NULL, m1_provisioningsession_api, app_meta)); - ogs_free(err); + ogs_free(cert); } } else if (api == m1_consumptionreportingprovisioning_api) { cJSON *json; @@ -1127,7 +1319,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) json = cJSON_Parse(request->http.content); if (!json) { - char *err = NULL; + char *err; err = ogs_msprintf("Bad request body while updating ConsumptionReportingConfiguration for Provisioining Session [%s].", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 400, 2, message, "Bad request.", err, NULL, api, app_meta)); @@ -1138,14 +1330,14 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) config = msaf_consumption_report_configuration_parseJSON(json, &parse_err); if (!config) { - char *err = NULL; + char *err; err = ogs_msprintf("Bad request body while updating ConsumptionReportingConfiguration for Provisioining Session [%s]: %s", message->h.resource.component[1], parse_err); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 400, 2, message, "Bad request.", err, NULL, api, app_meta)); ogs_free(err); } else { if (!msaf_consumption_report_configuration_update(msaf_provisioning_session, config)) { - char *err = NULL; + char *err; err = ogs_msprintf("No ConsumptionReportingConfiguration for Provisioining Session [%s].", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 2, message, "Not found.", err, NULL, api, app_meta)); @@ -1161,88 +1353,77 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) cJSON_Delete(json); } } else if (api == m1_policytemplatesprovisioning_api) { - ogs_sbi_response_t *response; - msaf_provisioning_session_t *msaf_provisioning_session; - msaf_api_policy_template_t *policy_template; + ogs_sbi_response_t *response; + msaf_api_policy_template_t *policy_template; - if(!check_http_content_type(request->http,"application/json")){ - ogs_assert(true == nf_server_send_error(stream, 415, 3, message, "Unsupported Media Type.", "Expected content type: application/json", NULL, m1_policytemplatesprovisioning_api, app_meta)); + if (!check_http_content_type(request->http,"application/json")) { + ogs_assert(true == nf_server_send_error(stream, 415, 3, message, "Unsupported Media Type.", + "Expected content type: application/json", NULL, api, + app_meta)); ogs_sbi_message_free(message); ogs_free(message); - return; + return; } - if(!request->http.content) { - ogs_assert(true == nf_server_send_error(stream, 400, 3, message, "Bad request.", "Request has no content", NULL, m1_policytemplatesprovisioning_api, app_meta)); + if (!request->http.content) { + ogs_assert(true == nf_server_send_error(stream, 400, 3, message, "Bad request.", + "Request has no content", NULL, api, app_meta)); ogs_sbi_message_free(message); ogs_free(message); return; - - } - - msaf_provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId(message->h.resource.component[1]); - - if(msaf_provisioning_session) { - msaf_policy_template_node_t *msaf_policy_template; - msaf_policy_template = msaf_provisioning_session_find_policy_template_by_id(msaf_provisioning_session, message->h.resource.component[3]); - if(msaf_policy_template) { - cJSON *policy_template_received; - const char *parse_err; - - policy_template_received = cJSON_Parse(request->http.content); - - policy_template = msaf_policy_template_parseFromJSON(policy_template_received, &parse_err); - cJSON_Delete(policy_template_received); - - _policy_template_extra_validation(&policy_template, &parse_err); - - if (!policy_template) { - char *err = ogs_msprintf("Updating policy template: Could not parse request body as JSON: %s", parse_err); - ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 400, 3, message, "Updating policy template failed.", - err, NULL, m1_policytemplatesprovisioning_api, app_meta)); - ogs_free(err); - break; - } + } - /* validation passed, remove read-only fields if present */ - _policy_template_remove_read_only(policy_template); + msaf_policy_template_node_t *msaf_policy_template; + msaf_policy_template = msaf_provisioning_session_find_policy_template_by_id(msaf_provisioning_session, message->h.resource.component[3]); + if (msaf_policy_template) { + cJSON *policy_template_received; + const char *parse_err; - /* update policy template */ - if(msaf_provisioning_session_update_policy_template(msaf_provisioning_session, msaf_policy_template, policy_template)) { - - response = nf_server_new_response(NULL, NULL, 0, NULL, 0, NULL, m1_policytemplatesprovisioning_api, app_meta); - nf_server_populate_response(response, 0, NULL, 204); - ogs_assert(response); - ogs_assert(true == ogs_sbi_server_send_response(stream, response)); - - } else { - const char *err = ogs_msprintf("Internal server error while updating policy template [%s]", message->h.resource.component[3]); - ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 400, 3, message, "Updating policy template failed.", - err, NULL, m1_policytemplatesprovisioning_api, app_meta)); - } - + policy_template_received = cJSON_Parse(request->http.content); - } else { - - char *err = NULL; - err = ogs_msprintf("Provisioning session [%s] has no policy template [%s].", message->h.resource.component[1], message->h.resource.component[3]); + policy_template = msaf_policy_template_parseFromJSON(policy_template_received, &parse_err); + cJSON_Delete(policy_template_received); + + _policy_template_extra_validation(&policy_template, &parse_err); + + if (!policy_template) { + char *err = ogs_msprintf("Updating policy template: Could not parse request body as JSON: %s", parse_err); ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Policy template does not exists.", err, NULL, m1_policytemplatesprovisioning_api, app_meta)); + ogs_assert(true == nf_server_send_error(stream, 400, 3, message, + "Updating policy template failed.", err, NULL, api, + app_meta)); ogs_free(err); - - } + break; + } - } + /* validation passed, remove read-only fields if present */ + _policy_template_remove_read_only(policy_template); - } + /* update policy template */ + if (msaf_provisioning_session_update_policy_template(msaf_provisioning_session, + msaf_policy_template, policy_template)) { + response = nf_server_new_response(NULL, NULL, 0, NULL, 0, NULL, api, app_meta); + nf_server_populate_response(response, 0, NULL, 204); + ogs_assert(response); + ogs_assert(true == ogs_sbi_server_send_response(stream, response)); + } else { + char *err = ogs_msprintf("Internal server error while updating policy template [%s]", + message->h.resource.component[3]); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 400, 3, message, + "Updating policy template failed.", err, NULL, api, + app_meta)); + ogs_free(err); + } + } + } } else { - char *err = NULL; + char *err; err = ogs_msprintf("[%s]: Resource not found.", message->h.method); ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 1, message, "Resource not found.", err, NULL, m1_provisioningsession_api, app_meta)); + ogs_assert(true == nf_server_send_error(stream, 404, 1, message, "Resource not found.", err, NULL, + m1_provisioningsession_api, app_meta)); ogs_free(err); } break; @@ -1259,6 +1440,9 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) CASE("consumption-reporting-configuration") api = m1_consumptionreportingprovisioning_api; break; + CASE("metrics-reporting-configurations") + api = m1_metricsreportingprovisioning_api; + break; CASE("content-hosting-configuration") api = m1_contenthostingprovisioning_api; break; @@ -1273,25 +1457,30 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId(message->h.resource.component[1]); if (!provisioning_session) { - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning Session [%s] is not available.", message->h.resource.component[1]); ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 2, message, "Provisioning session does not exists.", err, NULL, api, app_meta)); + ogs_assert(true == nf_server_send_error(stream, 404, 2, message, + "Provisioning session does not exists.", err, NULL, + api?api:m1_provisioningsession_api, app_meta)); ogs_free(err); } else if (!api) { - char *err = NULL; - err = ogs_msprintf("Unknown sub-resource [%s] for provisioning Session [%s].", message->h.resource.component[2], message->h.resource.component[1]); + char *err; + err = ogs_msprintf("Unknown sub-resource [%s] for provisioning Session [%s].", + message->h.resource.component[2], message->h.resource.component[1]); ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 2, message, "Unknown provisioning session sub-resource.", err, NULL, m1_provisioningsession_api, app_meta)); + ogs_assert(true == nf_server_send_error(stream, 404, 2, message, + "Unknown provisioning session sub-resource.", err, NULL, + m1_provisioningsession_api, app_meta)); ogs_free(err); } else if (api == m1_contenthostingprovisioning_api) { /* Delete ContentHostingConfiguration operations */ if (!message->h.resource.component[3]) { /* Delete the ContentHostingConfiguration */ ogs_sbi_response_t *response; - if(provisioning_session && provisioning_session->contentHostingConfiguration) { + if (provisioning_session->contentHostingConfiguration) { msaf_delete_content_hosting_configuration(message->h.resource.component[1]); msaf_api_content_hosting_configuration_free(provisioning_session->contentHostingConfiguration); provisioning_session->contentHostingConfiguration = NULL; @@ -1300,7 +1489,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) nf_server_populate_response(response, 0, NULL, 204); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); } else { - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning Session [%s] has no Content Hosting Configuration.", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 2, message, "Content Hosting Configuration does not exist.", err, NULL, api, app_meta)); @@ -1308,7 +1497,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) } } else { /* Delete the ContentHostingConfiguration with extra field - undefined operation */ - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning Session [%s]: Unknown ContentHostingConfiguration operation.", message->h.resource.component[1]); ogs_error("%s", err); @@ -1319,7 +1508,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) if (message->h.resource.component[3]) { if (message->h.resource.component[4]) { /* Delete certificate with extra field - undefined operation */ - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning session [%s]: Certificate [%s]: Unknown delete operation.", message->h.resource.component[1], message->h.resource.component[3]); ogs_error("%s", err); @@ -1330,20 +1519,20 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_sbi_response_t *response; int rv; rv = server_cert_delete(message->h.resource.component[3]); - if ((rv == 0) || (rv == 8)){ + if ((rv == 0) || (rv == 8)) { response = nf_server_new_response(NULL, NULL, 0, NULL, 0, NULL, m1_servercertificatesprovisioning_api, app_meta); nf_server_populate_response(response, 0, NULL, 204); ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); msaf_provisioning_session_certificate_hash_remove(message->h.resource.component[1], message->h.resource.component[3]); } else if (rv == 4 ) { - char *err = NULL; + char *err; err = ogs_msprintf("Certificate [%s] does not exist.", message->h.resource.component[3]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Certificate does not exist.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); ogs_free(err); } else { - char *err = NULL; + char *err; err = ogs_msprintf("Certificate management problem for certificate [%s].", message->h.resource.component[3]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 500, 3, message, "Certificate management problem.", err, NULL, api, app_meta)); @@ -1352,37 +1541,87 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) } } else { /* Delete certificate without certificate id - undefined operation */ - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning session [%s]: Unknown Certificate Management operation.", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 400, 2, message, "Bad request", err, NULL, api, app_meta)); ogs_free(err); } } else if (api == m1_policytemplatesprovisioning_api) { - if (message->h.resource.component[3]) { - if (!message->h.resource.component[4]) { - ogs_sbi_response_t *response; - msaf_provisioning_session_t *provisioning_session = NULL; - provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId(message->h.resource.component[1]); - if (provisioning_session) { - if (msaf_provisioning_session_delete_policy_template_by_id(provisioning_session, message->h.resource.component[3])) { - response = nf_server_new_response(NULL, NULL, 0, NULL, 0, NULL, m1_policytemplatesprovisioning_api, app_meta); - nf_server_populate_response(response, 0, NULL, 204); - ogs_assert(response); - ogs_assert(true == ogs_sbi_server_send_response(stream, response)); - } else { - char *err = NULL; - err = ogs_msprintf("Provisioning session [%s]: Policy template [%s] does not exist.", - message->h.resource.component[1], message->h.resource.component[3]); + if (message->h.resource.component[3]) { + if (!message->h.resource.component[4]) { + ogs_sbi_response_t *response; + if (msaf_provisioning_session_delete_policy_template_by_id(provisioning_session, + message->h.resource.component[3]) + ) { + response = nf_server_new_response(NULL, NULL, 0, NULL, 0, NULL, m1_policytemplatesprovisioning_api, app_meta); + nf_server_populate_response(response, 0, NULL, 204); + ogs_assert(response); + ogs_assert(true == ogs_sbi_server_send_response(stream, response)); + } else { + char *err; + err = ogs_msprintf("Provisioning session [%s]: Policy template [%s] does not exist.", + message->h.resource.component[1], message->h.resource.component[3]); ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Policy template does not exist.", err, NULL, m1_policytemplatesprovisioning_api, app_meta)); + ogs_assert(true == nf_server_send_error(stream, 404, 3, message, + "Policy template does not exist.", err, NULL, + api, app_meta)); ogs_free(err); + } + } else { + char *err; + err = ogs_msprintf("Provisioning session [%s]: Policy template [%s]: Request for deletion " + "of sub-resource [%s] not recognised.", + message->h.resource.component[1], message->h.resource.component[3], + message->h.resource.component[4]); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 404, 4, message, + "Policy template does not exist.", err, NULL, + api, app_meta)); + ogs_free(err); + } + } else { + char *err; + err = ogs_msprintf("Provisioning session [%s]: Cannot perform delete on Policy templates " + "without a template id.", message->h.resource.component[1]); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 404, 2, message, + "Policy template does not exist.", err, NULL, + api, app_meta)); + ogs_free(err); + } + } else if(api == m1_metricsreportingprovisioning_api) { + if (message->h.resource.component[3] && !message->h.resource.component[4]) { + if (msaf_delete_metrics_configuration(provisioning_session, message->h.resource.component[3]) == 0) { - } - } - } - } - } else if (api == m1_consumptionreportingprovisioning_api) { + if (provisioning_session->sai_cache) { + msaf_sai_cache_clear(provisioning_session->sai_cache); + ogs_debug("SAI cache cleared for provisioning session [%s]", message->h.resource.component[1]); + } + + ogs_sbi_response_t *response; + response = nf_server_new_response(NULL, "application/json", 0, NULL, 0, NULL, m1_metricsreportingprovisioning_api, app_meta); + nf_server_populate_response(response, 0, NULL, 204); + ogs_assert(response); + ogs_assert(true == ogs_sbi_server_send_response(stream, response)); + } else { + char *err = NULL; + err = ogs_msprintf("Provisioning session [%s]: Metrics Reporting Configuration [%s] does not exist.", message->h.resource.component[1], message->h.resource.component[3]); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Metrics Reporting Configuration does not exist.", err, NULL, m1_metricsreportingprovisioning_api, app_meta)); + ogs_free(err); + } + } else { + char *err; + err = ogs_msprintf("Provisioning session [%s]: Policy template operation not recognised.", + message->h.resource.component[1]); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 404, 2, message, + "Metrics Reporting Configuration operation does not exist.", + err, NULL, api, app_meta)); + ogs_free(err); + } + } else if (api == m1_consumptionreportingprovisioning_api) { if (!message->h.resource.component[3]) { /* Delete consumption reporting configuration */ if (msaf_consumption_report_configuration_deregister(provisioning_session)) { @@ -1394,7 +1633,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_assert(true == ogs_sbi_server_send_response(stream, response)); } else { /* Failed to delete consumption reporting configuration - no configuration to delete */ - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning session [%s]: Content Reporting Configuration not found.", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 2, message, "Not Found", err, NULL, api, app_meta)); @@ -1402,7 +1641,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) } } else { /* Delete ConsumptionReportingConfiguration sub-resource - undefined operation */ - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning session [%s]: Unknown Consumption Reporting Configuration operation.", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 400, 2, message, "Bad request", err, NULL, api, app_meta)); @@ -1416,7 +1655,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId(message->h.resource.component[1]); if (!provisioning_session || provisioning_session->marked_for_deletion) { - char *err = NULL; + char *err; err = ogs_msprintf("Provisioning Session [%s] is not available.", message->h.resource.component[1]); ogs_error("%s", err); @@ -1438,7 +1677,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) msaf_provisioning_session_hash_remove(message->h.resource.component[1]); } } else { - char *err = NULL; + char *err; err = ogs_msprintf("[%s]: Resource not found.", message->h.method); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 1, message, "Resource not found.", err, NULL, m1_provisioningsession_api, app_meta)); @@ -1448,9 +1687,8 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) break; CASE(OGS_SBI_HTTP_METHOD_OPTIONS) - if (!strcmp(message->h.resource.component[0],"provisioning-sessions")){ + if (!strcmp(message->h.resource.component[0],"provisioning-sessions")) { ogs_sbi_response_t *response; - char *methods = NULL; if (message->h.resource.component[1]) { msaf_provisioning_session_t *provisioning_session = NULL; @@ -1458,19 +1696,21 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) if (provisioning_session) { if (message->h.resource.component[2]) { - if (!strcmp(message->h.resource.component[2],"policy-templates")) { + if (!strcmp(message->h.resource.component[2],"policy-templates")) { if (message->h.resource.component[3]) { msaf_policy_template_node_t *msaf_policy_template; msaf_policy_template = msaf_provisioning_session_find_policy_template_by_id(provisioning_session, message->h.resource.component[3]); - if(msaf_policy_template) { - methods = ogs_msprintf("%s, %s, %s, %s",OGS_SBI_HTTP_METHOD_GET, OGS_SBI_HTTP_METHOD_PUT, OGS_SBI_HTTP_METHOD_DELETE, OGS_SBI_HTTP_METHOD_OPTIONS); + if (msaf_policy_template) { + static const char methods[] = OGS_SBI_HTTP_METHOD_GET ", " + OGS_SBI_HTTP_METHOD_PUT ", " + OGS_SBI_HTTP_METHOD_DELETE ", " + OGS_SBI_HTTP_METHOD_OPTIONS; response = nf_server_new_response(request->h.uri, NULL, 0, NULL, 0, methods, m1_policytemplatesprovisioning_api, app_meta); nf_server_populate_response(response, 0, NULL, 204); ogs_assert(response); - ogs_assert(true == ogs_sbi_server_send_response(stream, response)); - - } else { - char *err = NULL; + ogs_assert(true == ogs_sbi_server_send_response(stream, response)); + } else { + char *err; err = ogs_msprintf("Policy template [%s] does not exists", message->h.resource.component[3]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 500, 3, message, "Problem obtaining the specified policy template.", err, NULL, m1_policytemplatesprovisioning_api, app_meta)); @@ -1478,27 +1718,31 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) break; } - } else { - methods = ogs_msprintf("%s, %s",OGS_SBI_HTTP_METHOD_POST, OGS_SBI_HTTP_METHOD_OPTIONS); + } else { + static const char methods[] = OGS_SBI_HTTP_METHOD_POST ", " + OGS_SBI_HTTP_METHOD_OPTIONS; response = nf_server_new_response(request->h.uri, NULL, 0, NULL, 0, methods, m1_policytemplatesprovisioning_api, app_meta); nf_server_populate_response(response, 0, NULL, 204); ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); } - } else if (!strcmp(message->h.resource.component[2],"certificates")) { + } else if (!strcmp(message->h.resource.component[2],"certificates")) { if (message->h.resource.component[3]) { msaf_certificate_t *cert; cert = server_cert_retrieve(message->h.resource.component[3]); - if(cert){ - methods = ogs_msprintf("%s, %s, %s, %s",OGS_SBI_HTTP_METHOD_GET, OGS_SBI_HTTP_METHOD_PUT, OGS_SBI_HTTP_METHOD_DELETE, OGS_SBI_HTTP_METHOD_OPTIONS); + if (cert) { + static const char methods[] = OGS_SBI_HTTP_METHOD_GET ", " + OGS_SBI_HTTP_METHOD_PUT ", " + OGS_SBI_HTTP_METHOD_DELETE ", " + OGS_SBI_HTTP_METHOD_OPTIONS; response = nf_server_new_response(request->h.uri, NULL, 0, NULL, 0, methods, m1_servercertificatesprovisioning_api, app_meta); nf_server_populate_response(response, 0, NULL, 204); ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); msaf_certificate_free(cert); } else { - char *err = NULL; + char *err; err = ogs_msprintf("Certificate [%s] management problem", message->h.resource.component[3]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 500, 3, message, "Certificate management problem.", err, NULL, m1_servercertificatesprovisioning_api, app_meta)); @@ -1506,63 +1750,87 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) break; } } else { - methods = ogs_msprintf("%s",OGS_SBI_HTTP_METHOD_POST); + static const char methods[] = OGS_SBI_HTTP_METHOD_POST; response = nf_server_new_response(request->h.uri, NULL, 0, NULL, 0, methods, m1_servercertificatesprovisioning_api, app_meta); nf_server_populate_response(response, 0, NULL, 204); ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); } + } else if (!strcmp(message->h.resource.component[2], "metrics-reporting-configurations")) { + if (message->h.resource.component[3]) { + msaf_metrics_reporting_configuration_t *metrics_configuration = msaf_metrics_reporting_configuration_retrieve(provisioning_session, message->h.resource.component[3]); + + if (!metrics_configuration) { + char *err = ogs_msprintf("Metrics Reporting Configuration [%s] does not exist", message->h.resource.component[3]); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, 404, 3, message, "Metrics Reporting Configuration does not exist.", err, NULL, m1_metricsreportingprovisioning_api, app_meta)); + ogs_free(err); + } else { + static const char methods[] = OGS_SBI_HTTP_METHOD_GET ", " + OGS_SBI_HTTP_METHOD_PUT ", " + OGS_SBI_HTTP_METHOD_DELETE ", " + OGS_SBI_HTTP_METHOD_OPTIONS; + ogs_sbi_response_t *response = nf_server_new_response(request->h.uri, NULL, 0, NULL, 0, methods, m1_metricsreportingprovisioning_api, app_meta); + ogs_assert(response); + nf_server_populate_response(response, 0, NULL, 204); + ogs_assert(true == ogs_sbi_server_send_response(stream, response)); + } + } else { + static const char methods[] = OGS_SBI_HTTP_METHOD_POST ", " + OGS_SBI_HTTP_METHOD_OPTIONS; + ogs_sbi_response_t *response = nf_server_new_response(request->h.uri, NULL, 0, NULL, 0, methods, m1_metricsreportingprovisioning_api, app_meta); + ogs_assert(response); + nf_server_populate_response(response, 0, NULL, 204); + ogs_assert(true == ogs_sbi_server_send_response(stream, response)); + } } else if (!strcmp(message->h.resource.component[2],"content-hosting-configuration")) { - methods = ogs_msprintf("%s, %s, %s, %s, %s",OGS_SBI_HTTP_METHOD_POST, OGS_SBI_HTTP_METHOD_GET, OGS_SBI_HTTP_METHOD_PUT, OGS_SBI_HTTP_METHOD_DELETE, OGS_SBI_HTTP_METHOD_OPTIONS); + static const char methods[] = OGS_SBI_HTTP_METHOD_POST ", " + OGS_SBI_HTTP_METHOD_GET ", " + OGS_SBI_HTTP_METHOD_PUT ", " + OGS_SBI_HTTP_METHOD_DELETE ", " + OGS_SBI_HTTP_METHOD_OPTIONS; response = nf_server_new_response(request->h.uri, NULL, 0, NULL, 0, methods, m1_contenthostingprovisioning_api, app_meta); nf_server_populate_response(response, 0, NULL, 204); ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); } else if (!strcmp(message->h.resource.component[2],"consumption-reporting-configuration")) { - methods = ogs_msprintf("%s, %s, %s, %s, %s", OGS_SBI_HTTP_METHOD_POST, - OGS_SBI_HTTP_METHOD_GET, OGS_SBI_HTTP_METHOD_PUT, - OGS_SBI_HTTP_METHOD_DELETE, OGS_SBI_HTTP_METHOD_OPTIONS); + static const char methods[] = OGS_SBI_HTTP_METHOD_POST ", " + OGS_SBI_HTTP_METHOD_GET ", " + OGS_SBI_HTTP_METHOD_PUT ", " + OGS_SBI_HTTP_METHOD_DELETE ", " + OGS_SBI_HTTP_METHOD_OPTIONS; response = nf_server_new_response(request->h.uri, NULL, 0, NULL, 0, methods, m1_consumptionreportingprovisioning_api, app_meta); nf_server_populate_response(response, 0, NULL, 204); ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); } else if (!strcmp(message->h.resource.component[2],"protocols")) { - methods = ogs_msprintf("%s, %s", OGS_SBI_HTTP_METHOD_GET, OGS_SBI_HTTP_METHOD_OPTIONS); + static const char methods[] = OGS_SBI_HTTP_METHOD_GET ", " OGS_SBI_HTTP_METHOD_OPTIONS; response = nf_server_new_response(request->h.uri, NULL, 0, NULL, 0, methods, m1_contentprotocolsdiscovery_api, app_meta); nf_server_populate_response(response, 0, NULL, 204); ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); - } else { - char *err = NULL; + char *err; err = ogs_msprintf("Method [%s]: Target [%s] not yet supported.", message->h.method, message->h.resource.component[2]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 2, message, "Target not yet supported.", err, NULL, NULL, app_meta)); ogs_free(err); } } else { - methods = ogs_msprintf("%s, %s, %s", OGS_SBI_HTTP_METHOD_GET, OGS_SBI_HTTP_METHOD_DELETE, OGS_SBI_HTTP_METHOD_OPTIONS); + static const char methods[] = OGS_SBI_HTTP_METHOD_GET ", " OGS_SBI_HTTP_METHOD_DELETE ", " OGS_SBI_HTTP_METHOD_OPTIONS; response = nf_server_new_response(request->h.uri, NULL, 0, NULL, 0, methods, m1_provisioningsession_api, app_meta); nf_server_populate_response(response, 0, NULL, 204); ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); - } - /* - nf_server_populate_response(response, 0, NULL, 204); - ogs_assert(response); - ogs_assert(true == ogs_sbi_server_send_response(stream, response)); - - if(methods) ogs_free(methods); - */ } else { - char *err = NULL; + char *err; int number_of_components = 0; const nf_server_interface_metadata_t *interface = NULL; - if (message->h.resource.component[2]){ + if (message->h.resource.component[2]) { if (!strcmp(message->h.resource.component[2],"certificates")) { number_of_components = 2; if (message->h.resource.component[3]) { @@ -1574,14 +1842,13 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) interface = m1_contenthostingprovisioning_api; } - } else if (message->h.resource.component[0]){ - if (!strcmp(message->h.resource.component[0],"provisioning-sessions")){ + } else if (message->h.resource.component[0]) { + if (!strcmp(message->h.resource.component[0],"provisioning-sessions")) { number_of_components = 0; if (message->h.resource.component[1]) { number_of_components = 1; } interface = m1_provisioningsession_api; - } } err = ogs_msprintf("Method [%s]: [%s] - Provisioning Session [%s] does not exist.", message->h.method, message->h.resource.component[2], message->h.resource.component[1]); @@ -1591,16 +1858,14 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) } } else { - methods = ogs_msprintf("%s, %s",OGS_SBI_HTTP_METHOD_POST, OGS_SBI_HTTP_METHOD_OPTIONS); + static const char methods[] = OGS_SBI_HTTP_METHOD_POST ", " OGS_SBI_HTTP_METHOD_OPTIONS; response = nf_server_new_response(request->h.uri, NULL, 0, NULL, 0, methods, m1_provisioningsession_api, app_meta); nf_server_populate_response(response, 0, NULL, 204); ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); - } - if(methods) ogs_free(methods); } else { - char *err = NULL; + char *err; err = ogs_msprintf("Method [%s]: Target [%s] not yet supported.", message->h.method, message->h.resource.component[0]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 0, message, "Target not yet supported.", err, NULL, m1_provisioningsession_api, app_meta)); @@ -1615,23 +1880,23 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) break; DEFAULT - char *err = NULL; + char *err; err = ogs_msprintf("Invalid resource name [%s]", message->h.resource.component[0]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_BAD_REQUEST, 0, message, "Invalid resource name", err, NULL, NULL, app_meta)); ogs_free(err); END break; - + CASE("5gmag-rt-management") if (strcmp(message->h.api.version, "v1") != 0) { char *error; error = ogs_msprintf("Version [%s] not supported", message->h.api.version); ogs_error("%s", error); - ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_BAD_REQUEST, 1, NULL, "Not supported version", error, NULL, maf_management_api, app_meta)); + ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_BAD_REQUEST, 1, NULL, "Not supported version", error, NULL, maf_management_api, app_meta)); ogs_free(error); break; - } + } if (!message->h.resource.component[0]) { const char *error = "Resource required for Management interface"; ogs_error("%s", error); @@ -1643,29 +1908,29 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) CASE("provisioning-sessions") SWITCH(message->h.method) - CASE(OGS_SBI_HTTP_METHOD_GET) + CASE(OGS_SBI_HTTP_METHOD_GET) char *provisioning_sessions = NULL; ogs_sbi_response_t *response; provisioning_sessions = enumerate_provisioning_sessions(); - if(provisioning_sessions) { + if (provisioning_sessions) { response = nf_server_new_response(NULL, "application/json", 0, NULL, msaf_self()->config.server_response_cache_control->m1_provisioning_session_response_max_age, NULL, maf_management_api, app_meta); - + nf_server_populate_response(response, strlen(provisioning_sessions), provisioning_sessions, 200); ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); break; } else { - ogs_error("Internal Server Error."); - ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_INTERNAL_SERVER_ERROR, 0, message, "Internal Server Error.", message->h.method, NULL, maf_management_api, app_meta)); + ogs_error("Internal Server Error."); + ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_INTERNAL_SERVER_ERROR, 0, message, "Internal Server Error.", message->h.method, NULL, maf_management_api, app_meta)); } DEFAULT - ogs_error("Invalid HTTP method [%s]", message->h.method); + ogs_error("Invalid HTTP method [%s]", message->h.method); ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_FORBIDDEN, 0, message, "Invalid HTTP method.", message->h.method, NULL, maf_management_api, app_meta)); END break; DEFAULT - char *err = NULL; + char *err; err = ogs_msprintf("Invalid resource name [%s]", message->h.resource.component[0]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_BAD_REQUEST, 0, message, "Invalid resource name", err, NULL, NULL, app_meta)); @@ -1729,7 +1994,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) for (hi = ogs_hash_first(request->http.headers); hi; hi = ogs_hash_next(hi)) { if (!ogs_strcasecmp(ogs_hash_this_key(hi), OGS_SBI_CONTENT_TYPE)) { if (ogs_strcasecmp(ogs_hash_this_val(hi), "application/json")) { - char *err = NULL; + char *err; const char *type; type = ogs_hash_this_val(hi); err = ogs_msprintf( "Unsupported Media Type: received type: %s, should have been application/x-www-form-urlencoded", type); @@ -1755,21 +2020,21 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) } - ogs_list_for_each_safe(&as_state->purge_content_hosting_cache, next, content_hosting_cache){ + ogs_list_for_each_safe(&as_state->purge_content_hosting_cache, next, content_hosting_cache) { if (purge_node->purge_regex) { - if(!strcmp(content_hosting_cache->provisioning_session_id, purge_node->provisioning_session_id) && !strcmp(content_hosting_cache->purge_regex, purge_node->purge_regex)) + if (!strcmp(content_hosting_cache->provisioning_session_id, purge_node->provisioning_session_id) && !strcmp(content_hosting_cache->purge_regex, purge_node->purge_regex)) break; - } else if(!strcmp(content_hosting_cache->provisioning_session_id, purge_node->provisioning_session_id)) { + } else if (!strcmp(content_hosting_cache->provisioning_session_id, purge_node->provisioning_session_id)) { break; } } - if(content_hosting_cache){ + if (content_hosting_cache) { ogs_list_remove(&as_state->purge_content_hosting_cache, content_hosting_cache); ogs_debug("M1 List Purge refs: %d, Event Purge node refs: %d ", content_hosting_cache->m1_purge_info->refs, purge_node->m1_purge_info->refs); purge_node->m1_purge_info->refs--; ogs_debug(" After decrement, M1 List Purge refs: %d, Event Purge node refs: %d ", content_hosting_cache->m1_purge_info->refs, purge_node->m1_purge_info->refs); - if(!purge_node->m1_purge_info->refs){ + if (!purge_node->m1_purge_info->refs) { // send M1 response with total from purge_node->m1_purge_info->purged_entries_total // ogs_free(purge_node->m1_purge_info); ogs_sbi_response_t *response; @@ -1784,9 +2049,9 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(purge_node->m1_purge_info->m1_stream, response)); - if(content_hosting_cache->m1_purge_info) ogs_free(content_hosting_cache->m1_purge_info); + if (content_hosting_cache->m1_purge_info) ogs_free(content_hosting_cache->m1_purge_info); if (content_hosting_cache->provisioning_session_id) ogs_free(content_hosting_cache->provisioning_session_id); - if(content_hosting_cache->purge_regex) ogs_free(content_hosting_cache->purge_regex); + if (content_hosting_cache->purge_regex) ogs_free(content_hosting_cache->purge_regex); ogs_free(content_hosting_cache); } msaf_application_server_state_log(&as_state->purge_content_hosting_cache, "Purge Content Hosting Cache list"); @@ -1795,11 +2060,11 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) } - if((response->status == 400) || (response->status == 404) || (response->status == 413) || (response->status == 414) || (response->status == 415) || (response->status == 422) || (response->status == 500) || (response->status == 503)) { + if ((response->status == 400) || (response->status == 404) || (response->status == 413) || (response->status == 414) || (response->status == 415) || (response->status == 422) || (response->status == 500) || (response->status == 503)) { char *error; purge_resource_id_node_t *content_hosting_cache, *next = NULL; cJSON *purge_cache_err = NULL; - if(response->http.content){ + if (response->http.content) { purge_cache_err = cJSON_Parse(response->http.content); char *txt = cJSON_Print(purge_cache_err); ogs_debug("Parsed JSON: %s", txt); @@ -1837,25 +2102,25 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) response->status, 3, &purge_node->m1_purge_info->m1_message, "Problem occured during cache purge", error, purge_cache_err, m1_contenthostingprovisioning_api, app_meta)); ogs_free(error); - ogs_list_for_each_safe(&as_state->purge_content_hosting_cache, next, content_hosting_cache){ + ogs_list_for_each_safe(&as_state->purge_content_hosting_cache, next, content_hosting_cache) { if (purge_node->purge_regex) { - if(!strcmp(content_hosting_cache->provisioning_session_id, purge_node->provisioning_session_id) && !strcmp(content_hosting_cache->purge_regex, purge_node->purge_regex)) { + if (!strcmp(content_hosting_cache->provisioning_session_id, purge_node->provisioning_session_id) && !strcmp(content_hosting_cache->purge_regex, purge_node->purge_regex)) { ogs_list_remove(&as_state->purge_content_hosting_cache, content_hosting_cache); ogs_debug("M1 List Purge refs: %d, Event Purge node refs: %d ", content_hosting_cache->m1_purge_info->refs, purge_node->m1_purge_info->refs); - if(content_hosting_cache->m1_purge_info) ogs_free(content_hosting_cache->m1_purge_info); + if (content_hosting_cache->m1_purge_info) ogs_free(content_hosting_cache->m1_purge_info); if (content_hosting_cache->provisioning_session_id) ogs_free(content_hosting_cache->provisioning_session_id); - if(content_hosting_cache->purge_regex) ogs_free(content_hosting_cache->purge_regex); + if (content_hosting_cache->purge_regex) ogs_free(content_hosting_cache->purge_regex); ogs_free(content_hosting_cache); } - } else if(!strcmp(content_hosting_cache->provisioning_session_id, purge_node->provisioning_session_id)) { + } else if (!strcmp(content_hosting_cache->provisioning_session_id, purge_node->provisioning_session_id)) { ogs_list_remove(&as_state->purge_content_hosting_cache, content_hosting_cache); ogs_debug("M1 List Purge refs: %d, Event Purge node refs: %d ", content_hosting_cache->m1_purge_info->refs, purge_node->m1_purge_info->refs); - if(content_hosting_cache->m1_purge_info) ogs_free(content_hosting_cache->m1_purge_info); + if (content_hosting_cache->m1_purge_info) ogs_free(content_hosting_cache->m1_purge_info); if (content_hosting_cache->provisioning_session_id) ogs_free(content_hosting_cache->provisioning_session_id); - if(content_hosting_cache->purge_regex) ogs_free(content_hosting_cache->purge_regex); + if (content_hosting_cache->purge_regex) ogs_free(content_hosting_cache->purge_regex); ogs_free(content_hosting_cache); } @@ -1881,10 +2146,10 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) resource_id_node_t *content_hosting_configuration; ogs_list_for_each(&as_state->upload_content_hosting_configurations,content_hosting_configuration) { - if(!strcmp(content_hosting_configuration->state, message->h.resource.component[1])) + if (!strcmp(content_hosting_configuration->state, message->h.resource.component[1])) break; } - if(content_hosting_configuration) { + if (content_hosting_configuration) { ogs_debug("Removing %s from upload_content_hosting_configurations", content_hosting_configuration->state); ogs_list_remove(&as_state->upload_content_hosting_configurations, content_hosting_configuration); @@ -1893,36 +2158,36 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) } } - if(response->status == 405){ + if (response->status == 405) { ogs_error("Content Hosting Configuration resource already exist at the specified path\n"); } - if(response->status == 413){ + if (response->status == 413) { ogs_error("Payload too large\n"); } - if(response->status == 414){ + if (response->status == 414) { ogs_error("URI too long\n"); } - if(response->status == 415){ + if (response->status == 415) { ogs_error("Unsupported media type\n"); } - if(response->status == 500){ + if (response->status == 500) { ogs_error("Internal server error\n"); } - if(response->status == 503){ + if (response->status == 503) { ogs_error("Service unavailable\n"); } next_action_for_application_server(as_state); break; CASE(OGS_SBI_HTTP_METHOD_PUT) - if(response->status == 200 || response->status == 204) { + if (response->status == 200 || response->status == 204) { ogs_debug("[%s] Method [%s] with Response [%d] recieved for Content Hosting Configuration [%s]", message->h.resource.component[0], message->h.method, response->status, message->h.resource.component[1]); resource_id_node_t *content_hosting_configuration; - ogs_list_for_each(&as_state->upload_content_hosting_configurations,content_hosting_configuration){ - if(!strcmp(content_hosting_configuration->state, message->h.resource.component[1])) + ogs_list_for_each(&as_state->upload_content_hosting_configurations,content_hosting_configuration) { + if (!strcmp(content_hosting_configuration->state, message->h.resource.component[1])) break; } - if(content_hosting_configuration) { + if (content_hosting_configuration) { ogs_debug("Removing %s from upload_content_hosting_configurations", content_hosting_configuration->state); ogs_free(content_hosting_configuration->state); @@ -1931,44 +2196,44 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) } } - if(response->status == 404){ + if (response->status == 404) { ogs_error("Not Found\n"); } - if(response->status == 413){ + if (response->status == 413) { ogs_error("Payload too large\n"); } - if(response->status == 414){ + if (response->status == 414) { ogs_error("URI too long\n"); } - if(response->status == 415){ + if (response->status == 415) { ogs_error("Unsupported Media Type\n"); } - if(response->status == 500){ + if (response->status == 500) { ogs_error("Internal Server Error\n"); } - if(response->status == 503){ + if (response->status == 503) { ogs_error("Service Unavailable\n"); } next_action_for_application_server(as_state); break; CASE(OGS_SBI_HTTP_METHOD_DELETE) - if(response->status == 204) { + if (response->status == 204) { ogs_debug("[%s] Method [%s] with Response [%d] recieved for Content Hosting Configuration [%s]", message->h.resource.component[0], message->h.method, response->status,message->h.resource.component[1]); resource_id_node_t *content_hosting_configuration = NULL, *next = NULL; resource_id_node_t *delete_content_hosting_configuration, *node = NULL; - if(as_state->current_content_hosting_configurations) { + if (as_state->current_content_hosting_configurations) { - ogs_list_for_each_safe(as_state->current_content_hosting_configurations, next, content_hosting_configuration){ + ogs_list_for_each_safe(as_state->current_content_hosting_configurations, next, content_hosting_configuration) { - if(!strcmp(content_hosting_configuration->state, message->h.resource.component[1])) + if (!strcmp(content_hosting_configuration->state, message->h.resource.component[1])) break; } } - if(content_hosting_configuration) { + if (content_hosting_configuration) { msaf_application_server_state_log(as_state->current_content_hosting_configurations, "Current Content Hosting Configurations"); @@ -1995,22 +2260,22 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) } } - if(response->status == 404){ + if (response->status == 404) { ogs_error("Not Found\n"); } - if(response->status == 413){ + if (response->status == 413) { ogs_error("Payload too large\n"); } - if(response->status == 414){ + if (response->status == 414) { ogs_error("URI too long\n"); } - if(response->status == 415){ + if (response->status == 415) { ogs_error("Unsupported Media Type\n"); } - if(response->status == 500){ + if (response->status == 500) { ogs_error("Internal Server Error\n"); } - if(response->status == 503){ + if (response->status == 503) { ogs_error("Service Unavailable\n"); } next_action_for_application_server(as_state); @@ -2028,7 +2293,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) SWITCH(message->h.method) CASE(OGS_SBI_HTTP_METHOD_GET) - if(response->status == 200) { + if (response->status == 200) { ogs_debug("[%s] Method [%s] with Response [%d] for Content Hosting Configuration operation [%s]", message->h.resource.component[0], message->h.method, response->status, message->h.resource.component[1]); @@ -2071,7 +2336,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) response->http.content); } } - if (response->status == 500){ + if (response->status == 500) { ogs_error("Received Internal Server error\n"); } if (response->status == 503) { @@ -2080,7 +2345,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) next_action_for_application_server(as_state); break; DEFAULT - char *err = NULL; + char *err; err = ogs_msprintf( "Unknown M3 Content Hosting Configuration operation [%s] with method [%s]", message->h.resource.component[1], message->h.method); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_BAD_REQUEST, 0, message, "Unknown M3 Content Hosting Configuration operation", err, NULL, NULL, app_meta)); @@ -2102,18 +2367,18 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) if (message->h.resource.component[1]) { SWITCH(message->h.method) CASE(OGS_SBI_HTTP_METHOD_POST) - if(response->status == 201) { + if (response->status == 201) { ogs_debug("[%s] Method [%s] with Response [%d] recieved for certificate [%s]", message->h.resource.component[0], message->h.method, response->status, message->h.resource.component[1]); resource_id_node_t *certificate; //Iterate upload_certs and find match strcmp resource component 0 - ogs_list_for_each(&as_state->upload_certificates,certificate){ - if(!strcmp(certificate->state, message->h.resource.component[1])) + ogs_list_for_each(&as_state->upload_certificates,certificate) { + if (!strcmp(certificate->state, message->h.resource.component[1])) break; } - if(certificate) { + if (certificate) { ogs_debug("Removing certificate [%s] from upload_certificates", certificate->state); @@ -2125,28 +2390,28 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) // ogs_free(upload_cert_id); } } - if(response->status == 405){ + if (response->status == 405) { ogs_error("Server Certificate resource already exist at the specified path\n"); } - if(response->status == 413){ + if (response->status == 413) { ogs_error("Payload too large\n"); } - if(response->status == 414){ + if (response->status == 414) { ogs_error("URI too long\n"); } - if(response->status == 415){ + if (response->status == 415) { ogs_error("Unsupported media type\n"); } - if(response->status == 500){ + if (response->status == 500) { ogs_error("Internal server error\n"); } - if(response->status == 503){ + if (response->status == 503) { ogs_error("Service unavailable\n"); } next_action_for_application_server(as_state); break; CASE(OGS_SBI_HTTP_METHOD_PUT) - if(response->status == 200 || response->status == 204) { + if (response->status == 200 || response->status == 204) { ogs_debug("[%s] Method [%s] with Response [%d] recieved for certificate [%s]", message->h.resource.component[0], message->h.method, response->status,message->h.resource.component[1]); @@ -2155,13 +2420,13 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) msaf_application_server_state_log(&as_state->upload_certificates, "Upload Certificates"); //Iterate upload_certs and find match strcmp resource component 0 - ogs_list_for_each(&as_state->upload_certificates,certificate){ + ogs_list_for_each(&as_state->upload_certificates,certificate) { - if(!strcmp(certificate->state, message->h.resource.component[1])) + if (!strcmp(certificate->state, message->h.resource.component[1])) break; } - if(!certificate){ + if (!certificate) { ogs_debug("Certificate %s not found in upload certificates", message->h.resource.component[1]); } else { ogs_debug("Removing certificate [%s] from upload_certificates", certificate->state); @@ -2171,43 +2436,43 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_free(certificate); } } - if(response->status == 404){ + if (response->status == 404) { ogs_error("Not Found\n"); } - if(response->status == 413){ + if (response->status == 413) { ogs_error("Payload too large\n"); } - if(response->status == 414){ + if (response->status == 414) { ogs_error("URI too long\n"); } - if(response->status == 415){ + if (response->status == 415) { ogs_error("Unsupported Media Type\n"); } - if(response->status == 500){ + if (response->status == 500) { ogs_error("Internal Server Error\n"); } - if(response->status == 503){ + if (response->status == 503) { ogs_error("Service Unavailable\n"); } next_action_for_application_server(as_state); break; CASE(OGS_SBI_HTTP_METHOD_DELETE) - if(response->status == 204) { + if (response->status == 204) { ogs_debug("[%s] Method [%s] with Response [%d] recieved for Certificate [%s]", message->h.resource.component[0], message->h.method, response->status,message->h.resource.component[1]); resource_id_node_t *certificate = NULL, *next = NULL; resource_id_node_t *delete_certificate = NULL, *node = NULL; - if(as_state->current_certificates) { - ogs_list_for_each_safe(as_state->current_certificates, next, certificate){ + if (as_state->current_certificates) { + ogs_list_for_each_safe(as_state->current_certificates, next, certificate) { - if(!strcmp(certificate->state, message->h.resource.component[1])) + if (!strcmp(certificate->state, message->h.resource.component[1])) break; } } - if(certificate) { + if (certificate) { msaf_application_server_state_log(as_state->current_certificates, "Current Certificates"); @@ -2220,9 +2485,9 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) } - ogs_list_for_each_safe(&as_state->delete_certificates, node, delete_certificate){ + ogs_list_for_each_safe(&as_state->delete_certificates, node, delete_certificate) { - if(!strcmp(delete_certificate->state, message->h.resource.component[1])) { + if (!strcmp(delete_certificate->state, message->h.resource.component[1])) { msaf_application_server_state_log(&as_state->delete_certificates, "Delete Certificates"); ogs_debug("Destroying Certificate: %s", delete_certificate->state); @@ -2234,22 +2499,22 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) } } } - if(response->status == 404){ + if (response->status == 404) { ogs_error("Not Found\n"); } - if(response->status == 413){ + if (response->status == 413) { ogs_error("Payload too large\n"); } - if(response->status == 414){ + if (response->status == 414) { ogs_error("URI too long\n"); } - if(response->status == 415){ + if (response->status == 415) { ogs_error("Unsupported Media Type\n"); } - if(response->status == 500){ + if (response->status == 500) { ogs_error("Internal Server Error\n"); } - if(response->status == 503){ + if (response->status == 503) { ogs_error("Service Unavailable\n"); } next_action_for_application_server(as_state); @@ -2267,7 +2532,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) SWITCH(message->h.method) CASE(OGS_SBI_HTTP_METHOD_GET) - if(response->status == 200) { + if (response->status == 200) { ogs_debug("[%s] Method [%s] with Response [%d] received", message->h.resource.component[0], message->h.method, response->status); @@ -2312,7 +2577,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) response->http.content); } } - if (response->status == 500){ + if (response->status == 500) { ogs_error("Received Internal Server error"); } if (response->status == 503) { @@ -2321,7 +2586,7 @@ void msaf_m1_state_functional(ogs_fsm_t *s, msaf_event_t *e) next_action_for_application_server(as_state); break; DEFAULT - char *err = NULL; + char *err; err = ogs_msprintf( "Unsupported M3 Certificate operation [%s] with method [%s]", message->h.resource.component[1], message->h.method); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_BAD_REQUEST, 0, message, "Unknown M3 Certificate operation", err, NULL, NULL, app_meta)); diff --git a/src/5gmsaf/msaf-m1-sm.h b/src/5gmsaf/msaf-m1-sm.h index 1bace9a..a3ee976 100644 --- a/src/5gmsaf/msaf-m1-sm.h +++ b/src/5gmsaf/msaf-m1-sm.h @@ -1,11 +1,11 @@ /* -License: 5G-MAG Public License (v1.0) -Author: David Waring -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + * License: 5G-MAG Public License (v1.0) + * Author: David Waring + * Copyright: (C) 2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view */ #ifndef MSAF_M1_SM_H diff --git a/src/5gmsaf/msaf-m5-sm.c b/src/5gmsaf/msaf-m5-sm.c index 1a8ae05..7d64840 100644 --- a/src/5gmsaf/msaf-m5-sm.c +++ b/src/5gmsaf/msaf-m5-sm.c @@ -1,7 +1,9 @@ /* * License: 5G-MAG Public License (v1.0) - * Author: Dev Audsin - * Copyright: (C) 2023 British Broadcasting Corporation + * Authors: Dev Audsin + * David Waring + * Vuk Stojkovic + * Copyright: (C) 2023-2024 British Broadcasting Corporation * * For full license terms please see the LICENSE file distributed with this * program. If this file is missing then the license can be retrieved from @@ -25,6 +27,7 @@ #include "hash.h" #include "timer.h" #include "openapi/api/TS26512_M5_ServiceAccessInformationAPI-info.h" +#include "openapi/api/TS26512_M5_MetricsReportingAPI-info.h" #include "openapi/api/TS26512_M5_ConsumptionReportingAPI-info.h" #include "openapi/api/TS26512_M5_NetworkAssistanceAPI-info.h" #include "openapi/model/msaf_api_consumption_report.h" @@ -45,6 +48,12 @@ m5_consumptionreporting_api_metadata = { M5_CONSUMPTIONREPORTING_API_VERSION }; +static const nf_server_interface_metadata_t +m5_metricsreporting_api_metadata = { + M5_METRICSREPORTING_API_NAME, + M5_METRICSREPORTING_API_VERSION +}; + static const nf_server_interface_metadata_t m5_networkassistance_api_metadata = { M5_NETWORKASSISTANCE_API_NAME, @@ -58,7 +67,7 @@ m5_dynamicpolicy_api_metadata = { }; -static bool +static bool is_dynamic_policy_create_request_valid(ogs_sbi_request_t *request, ogs_sbi_stream_t *stream, ogs_sbi_message_t *message, const nf_server_interface_metadata_t *m5_dynamicpolicy_api, const nf_server_app_metadata_t *app_meta); @@ -90,6 +99,7 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) msaf_sm_debug(e); static const nf_server_interface_metadata_t *m5_serviceaccessinformation_api = &m5_serviceaccessinformation_api_metadata; + static const nf_server_interface_metadata_t *m5_metricsreporting_api = &m5_metricsreporting_api_metadata; static const nf_server_interface_metadata_t *m5_consumptionreporting_api = &m5_consumptionreporting_api_metadata; static const nf_server_interface_metadata_t *m5_networkassistance_api = &m5_networkassistance_api_metadata; static const nf_server_interface_metadata_t *m5_dynamicpolicy_api = &m5_dynamicpolicy_api_metadata; @@ -105,24 +115,24 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) case OGS_FSM_EXIT_SIG: break; - - case MSAF_EVENT_DELIVERY_BOOST_TIMER: + + case MSAF_EVENT_DELIVERY_BOOST_TIMER: ogs_assert(e); switch(e->h.timer_id) { case MSAF_TIMER_DELIVERY_BOOST: - { - msaf_network_assistance_session_t *na_sess = NULL; - na_sess = e->network_assistance_session; - ogs_assert(na_sess); - ogs_info("MSAF_EVENT_DELIVERY_BOOST_TIMER: MSAF_TIMER_DELIVERY_BOOST"); - - msaf_nw_assistance_session_update_pcf_on_timeout(na_sess); - } - break; + { + msaf_network_assistance_session_t *na_sess = NULL; + na_sess = e->network_assistance_session; + ogs_assert(na_sess); + ogs_info("MSAF_EVENT_DELIVERY_BOOST_TIMER: MSAF_TIMER_DELIVERY_BOOST"); + + msaf_nw_assistance_session_update_pcf_on_timeout(na_sess); + } + break; default: ogs_error("Invalid timer for event %s", msaf_event_get_name(e)); break; - + } break; @@ -133,7 +143,7 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_assert(stream); message = e->message; - SWITCH(message->h.service.name) + SWITCH(message->h.service.name) CASE("3gpp-m5") if (strcmp(message->h.api.version, "v2") != 0) { char *error; @@ -144,22 +154,22 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) break; } SWITCH(message->h.resource.component[0]) - CASE("dynamic-policies") + CASE("dynamic-policies") SWITCH(message->h.method) - CASE(OGS_SBI_HTTP_METHOD_DELETE) - if(message->h.resource.component[1] && !message->h.resource.component[2]) + CASE(OGS_SBI_HTTP_METHOD_DELETE) + if (message->h.resource.component[1] && !message->h.resource.component[2]) { msaf_dynamic_policy_t *msaf_dynamic_policy = NULL; - msaf_dynamic_policy = msaf_dynamic_policy_find_by_dynamicPolicyId(message->h.resource.component[1]); - if(!msaf_dynamic_policy) { - char *err = NULL; + msaf_dynamic_policy = msaf_dynamic_policy_find_by_dynamicPolicyId(message->h.resource.component[1]); + if (!msaf_dynamic_policy) { + char *err = NULL; err = ogs_msprintf("The AF has no dynamic policy with id [%s].", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 1, message, "Deleting dynamic policy failed.", err, NULL, m5_dynamicpolicy_api, app_meta)); ogs_free(err); - break; + break; } dynamic_policy_event = (msaf_event_t*)populate_msaf_event_with_metadata(e, m5_dynamicpolicy_api, app_meta); @@ -170,39 +180,39 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) break; - CASE(OGS_SBI_HTTP_METHOD_GET) - if (message->h.resource.component[1] && !message->h.resource.component[2]) - { - ogs_sbi_response_t *response; - cJSON *dynamic_policy = NULL; - int response_code = 200; - msaf_dynamic_policy_t *msaf_dynamic_policy; + CASE(OGS_SBI_HTTP_METHOD_GET) + if (message->h.resource.component[1] && !message->h.resource.component[2]) + { + ogs_sbi_response_t *response; + cJSON *dynamic_policy = NULL; + int response_code = 200; + msaf_dynamic_policy_t *msaf_dynamic_policy; char *body; char *hash; dynamic_policy = msaf_dynamic_policy_get_json(message->h.resource.component[1]); - - if(!dynamic_policy) { - const char *err = "Dynamic policy not found"; + + if (!dynamic_policy) { + const char *err = "Dynamic policy not found"; ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 1, message, "Retrieving dynamic policy failed.", err, NULL, m5_dynamicpolicy_api, app_meta)); - ogs_sbi_message_free(message); + ogs_sbi_message_free(message); ogs_free(message); return; - } + } - body = cJSON_Print(dynamic_policy); + body = cJSON_Print(dynamic_policy); hash= calculate_hash(body); - msaf_dynamic_policy = msaf_dynamic_policy_find_by_dynamicPolicyId((const char *)message->h.resource.component[1]); + msaf_dynamic_policy = msaf_dynamic_policy_find_by_dynamicPolicyId((const char *)message->h.resource.component[1]); response = nf_server_new_response(request->h.uri, "application/json", msaf_dynamic_policy->dynamic_policy_created, hash, msaf_self()->config.server_response_cache_control->m5_service_access_information_response_max_age, NULL, m5_dynamicpolicy_api, app_meta); ogs_assert(response); - nf_server_populate_response(response, strlen(body), ogs_strdup(body), response_code); + nf_server_populate_response(response, strlen(body), msaf_strdup(body), response_code); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); ogs_free(hash); @@ -210,11 +220,11 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) cJSON_free(body); - - } - break; - CASE(OGS_SBI_HTTP_METHOD_POST) - { + + } + break; + CASE(OGS_SBI_HTTP_METHOD_POST) + { cJSON *dynamic_policy; if (!is_dynamic_policy_create_request_valid (request, stream, message, m5_dynamicpolicy_api, app_meta)) return; @@ -225,7 +235,7 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) dynamic_policy_event = (msaf_event_t*)populate_msaf_event_with_metadata(e, m5_dynamicpolicy_api, app_meta); - if(!msaf_dynamic_policy_create(dynamic_policy, dynamic_policy_event)) { + if (!msaf_dynamic_policy_create(dynamic_policy, dynamic_policy_event)) { const char *err = "Problem in obtaining the information required to create the Dynamic Policy"; ogs_error("%s", err); @@ -236,79 +246,79 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) cJSON_Delete(dynamic_policy); } - break; - CASE(OGS_SBI_HTTP_METHOD_PUT) - if (message->h.resource.component[1] && !message->h.resource.component[2]) - { + break; + CASE(OGS_SBI_HTTP_METHOD_PUT) + if (message->h.resource.component[1] && !message->h.resource.component[2]) + { msaf_api_dynamic_policy_t *dynamic_policy; msaf_dynamic_policy_t *msaf_dynamic_policy = NULL; - cJSON *dynamic_policy_received; - const char *reason; + cJSON *dynamic_policy_received; + const char *reason; - if(!check_http_content_type(request->http,"application/json")){ + if (!check_http_content_type(request->http,"application/json")) { ogs_assert(true == nf_server_send_error(stream, 415, 1, message, "Unsupported Media Type.", "Expected content type: application/json", NULL, m5_dynamicpolicy_api, app_meta)); ogs_sbi_message_free(message); ogs_free(message); return; } - if(!request->http.content) { + if (!request->http.content) { ogs_assert(true == nf_server_send_error(stream, 400, 1, message, "Bad request.", "Request has no content", NULL, m5_dynamicpolicy_api, app_meta)); ogs_sbi_message_free(message); ogs_free(message); - return; + return; } - dynamic_policy_received = cJSON_Parse(request->http.content); + dynamic_policy_received = cJSON_Parse(request->http.content); dynamic_policy = msaf_api_dynamic_policy_parseRequestFromJSON(dynamic_policy_received, &reason); if (!dynamic_policy) { - char *err; - err = ogs_msprintf("Badly formed Dynamic Policy [%s]", reason); + char *err; + err = ogs_msprintf("Badly formed Dynamic Policy [%s]", reason); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_BAD_REQUEST, 1, message, "Malformed request body", err, NULL, m5_dynamicpolicy_api, app_meta)); ogs_free(err); break; } - /* - if(strcmp(message->h.resource.component[1], dynamic_policy->dynamic_policy_id)){ + /* + if (strcmp(message->h.resource.component[1], dynamic_policy->dynamic_policy_id)) { const char *err = "Updating dynamic policy: The path component and the JSON body have mismatching Dynamic policy id"; ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 400, 1, message, "Updating dynamic policy failed.", err, NULL, m5_dynamicpolicy_api, app_meta)); break; - } - */ + } + */ - msaf_dynamic_policy = msaf_dynamic_policy_find_by_dynamicPolicyId(message->h.resource.component[1]); - if(!msaf_dynamic_policy) { - const char *err = "Updating dynamic policy: Dynamic policy not found"; + msaf_dynamic_policy = msaf_dynamic_policy_find_by_dynamicPolicyId(message->h.resource.component[1]); + if (!msaf_dynamic_policy) { + const char *err = "Updating dynamic policy: Dynamic policy not found"; ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 1, message, "Updating dynamic policy failed.", err, NULL, m5_dynamicpolicy_api, app_meta)); break; - - } - - if(dynamic_policy->dynamic_policy_id) ogs_free(dynamic_policy->dynamic_policy_id); - dynamic_policy->dynamic_policy_id = msaf_strdup(message->h.resource.component[1]); - - if(!msaf_dynamic_policy_update_pcf(msaf_dynamic_policy, dynamic_policy)) { - const char *err = "Updating dynamic policy: Dynamic policy not found"; + + } + + if (dynamic_policy->dynamic_policy_id) ogs_free(dynamic_policy->dynamic_policy_id); + dynamic_policy->dynamic_policy_id = msaf_strdup(message->h.resource.component[1]); + + if (!msaf_dynamic_policy_update_pcf(msaf_dynamic_policy, dynamic_policy)) { + const char *err = "Updating dynamic policy: Dynamic policy not found"; ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 1, message, "Updating dynamic policy failed.", err, NULL, m5_dynamicpolicy_api, app_meta)); break; - } else { + } else { ogs_sbi_response_t *response; int response_code = 200; char *body; - char *hash; - cJSON *dyn_policy; + char *hash; + cJSON *dyn_policy; - dyn_policy = msaf_api_dynamic_policy_convertResponseToJSON((const msaf_api_dynamic_policy_t *)msaf_dynamic_policy->DynamicPolicy); + dyn_policy = msaf_api_dynamic_policy_convertResponseToJSON((const msaf_api_dynamic_policy_t *)msaf_dynamic_policy->DynamicPolicy); body = cJSON_Print(dyn_policy); hash= calculate_hash(body); @@ -318,30 +328,30 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) 0, NULL, m5_dynamicpolicy_api, app_meta); ogs_assert(response); - nf_server_populate_response(response, strlen(body), ogs_strdup(body), response_code); + nf_server_populate_response(response, strlen(body), msaf_strdup(body), response_code); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); ogs_free(hash); cJSON_Delete(dyn_policy); cJSON_free(body); - - } + } - cJSON_Delete(dynamic_policy_received); + cJSON_Delete(dynamic_policy_received); - - } - break; + + + } + break; CASE(OGS_SBI_HTTP_METHOD_OPTIONS) - if (message->h.resource.component[1] && !message->h.resource.component[2]) { - msaf_dynamic_policy_t *msaf_dynamic_policy; - ogs_sbi_response_t *response; - char *methods = NULL; - msaf_dynamic_policy = msaf_dynamic_policy_find_by_dynamicPolicyId(message->h.resource.component[1]); - if(!msaf_dynamic_policy) { + if (message->h.resource.component[1] && !message->h.resource.component[2]) { + msaf_dynamic_policy_t *msaf_dynamic_policy; + ogs_sbi_response_t *response; + char *methods = NULL; + msaf_dynamic_policy = msaf_dynamic_policy_find_by_dynamicPolicyId(message->h.resource.component[1]); + if (!msaf_dynamic_policy) { const char *err = "OPTIONS: Dynamic policy not found"; ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 1, message, "Dynamic policy failed.", @@ -349,119 +359,119 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) break; } - methods = ogs_msprintf("%s, %s, %s, %s",OGS_SBI_HTTP_METHOD_GET, OGS_SBI_HTTP_METHOD_PUT, OGS_SBI_HTTP_METHOD_DELETE, OGS_SBI_HTTP_METHOD_OPTIONS); - response = nf_server_new_response(request->h.uri, NULL, 0, NULL, 0, methods, m5_dynamicpolicy_api, app_meta); + methods = ogs_msprintf("%s, %s, %s, %s",OGS_SBI_HTTP_METHOD_GET, OGS_SBI_HTTP_METHOD_PUT, OGS_SBI_HTTP_METHOD_DELETE, OGS_SBI_HTTP_METHOD_OPTIONS); + response = nf_server_new_response(request->h.uri, NULL, 0, NULL, 0, methods, m5_dynamicpolicy_api, app_meta); nf_server_populate_response(response, 0, NULL, 204); ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); - if(methods) ogs_free(methods); - } else { - - ogs_sbi_response_t *response; - char *methods = NULL; + if (methods) ogs_free(methods); + } else { + + ogs_sbi_response_t *response; + char *methods = NULL; - methods = ogs_msprintf("%s, %s",OGS_SBI_HTTP_METHOD_POST, OGS_SBI_HTTP_METHOD_OPTIONS); + methods = ogs_msprintf("%s, %s",OGS_SBI_HTTP_METHOD_POST, OGS_SBI_HTTP_METHOD_OPTIONS); response = nf_server_new_response(request->h.uri, NULL, 0, NULL, 0, methods, m5_dynamicpolicy_api, app_meta); nf_server_populate_response(response, 0, NULL, 204); ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); - if(methods) ogs_free(methods); + if (methods) ogs_free(methods); } - break; - - DEFAULT + break; + + DEFAULT ogs_error("Invalid HTTP method [%s]", message->h.method); ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_FORBIDDEN, 1, message, "Invalid HTTP method.", message->h.method, NULL, NULL, app_meta)); END break; - - CASE("network-assistance") + + CASE("network-assistance") SWITCH(message->h.method) - CASE(OGS_SBI_HTTP_METHOD_DELETE) - if(message->h.resource.component[1]) + CASE(OGS_SBI_HTTP_METHOD_DELETE) + if (message->h.resource.component[1]) { - msaf_network_assistance_session_t *na_sess = NULL; - na_sess = msaf_network_assistance_session_retrieve((const char *)message->h.resource.component[1]); - if(na_sess){ - ogs_sbi_response_t *response; + msaf_network_assistance_session_t *na_sess = NULL; + na_sess = msaf_network_assistance_session_retrieve((const char *)message->h.resource.component[1]); + if (na_sess) { + ogs_sbi_response_t *response; response = nf_server_new_response(NULL, NULL, 0, NULL, 0, NULL, m5_networkassistance_api, app_meta); nf_server_populate_response(response, 0, NULL, 204); ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); msaf_network_assistance_session_delete_by_session_id((const char *)message->h.resource.component[1]); - } else { - char *err = NULL; + } else { + char *err = NULL; err = ogs_msprintf("The AF has no network assistance session with id [%s].", message->h.resource.component[1]); ogs_error("%s", err); - ogs_assert(true == nf_server_send_error(stream, 404, 0, message, "Unable to retrieve the Network Assistance Session", err, NULL, m5_networkassistance_api, app_meta)); + ogs_assert(true == nf_server_send_error(stream, 404, 0, message, "Unable to retrieve the Network Assistance Session", err, NULL, m5_networkassistance_api, app_meta)); ogs_free(err); - } + } - } else { - ogs_assert(true == nf_server_send_error(stream, 400, 0, message, "Unable to retrieve the Network Assistance Session", "Session Id not present in the request", NULL, m5_networkassistance_api, app_meta)); - - } - break; + } else { + ogs_assert(true == nf_server_send_error(stream, 400, 0, message, "Unable to retrieve the Network Assistance Session", "Session Id not present in the request", NULL, m5_networkassistance_api, app_meta)); + } + break; - CASE(OGS_SBI_HTTP_METHOD_GET) - if(message->h.resource.component[1]) - { - cJSON *network_assistance_sess; - network_assistance_sess = msaf_network_assistance_session_get_json((const char *)message->h.resource.component[1]); - if(network_assistance_sess) { - msaf_network_assistance_session_t *na_sess = NULL; - ogs_sbi_response_t *response; - int response_code = 200; - char *body; - char *hash; + CASE(OGS_SBI_HTTP_METHOD_GET) + if (message->h.resource.component[1]) + { + + cJSON *network_assistance_sess; + network_assistance_sess = msaf_network_assistance_session_get_json((const char *)message->h.resource.component[1]); + if (network_assistance_sess) { + msaf_network_assistance_session_t *na_sess = NULL; + ogs_sbi_response_t *response; + int response_code = 200; + char *body; + char *hash; - body = cJSON_Print(network_assistance_sess); - hash= calculate_hash(body); + body = cJSON_Print(network_assistance_sess); + hash= calculate_hash(body); - na_sess = msaf_network_assistance_session_retrieve((const char *)message->h.resource.component[1]); + na_sess = msaf_network_assistance_session_retrieve((const char *)message->h.resource.component[1]); - response = nf_server_new_response(request->h.uri, "application/json", + response = nf_server_new_response(request->h.uri, "application/json", na_sess->na_sess_created, hash, - msaf_self()->config.server_response_cache_control->m5_service_access_information_response_max_age, + msaf_self()->config.server_response_cache_control->m5_service_access_information_response_max_age, NULL, m5_networkassistance_api, app_meta); - ogs_assert(response); - nf_server_populate_response(response, strlen(body), ogs_strdup(body), response_code); + ogs_assert(response); + nf_server_populate_response(response, strlen(body), msaf_strdup(body), response_code); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); - ogs_free(hash); - - cJSON_Delete(network_assistance_sess); - cJSON_free(body); + ogs_free(hash); + + cJSON_Delete(network_assistance_sess); + cJSON_free(body); - } else { + } else { char *err = NULL; err = ogs_msprintf("The AF has no network assistance session with id [%s].", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 0, message, "Unable to retrieve the Network Assistance Session", err, NULL, m5_networkassistance_api, app_meta)); - ogs_free(err); - } + ogs_free(err); + } + + } else { - } else { - ogs_assert(true == nf_server_send_error(stream, 400, 0, message, "Unable to retrieve the Network Assistance Session", "Session Id not present in the request", NULL, m5_networkassistance_api, app_meta)); - } - break; + } + break; - CASE(OGS_SBI_HTTP_METHOD_PUT) - if(message->h.resource.component[1] && !message->h.resource.component[2]) + CASE(OGS_SBI_HTTP_METHOD_PUT) + if (message->h.resource.component[1] && !message->h.resource.component[2]) { msaf_network_assistance_session_t *na_sess; - cJSON *network_assistance_sess; - msaf_api_network_assistance_session_t *nas; + cJSON *network_assistance_sess; + msaf_api_network_assistance_session_t *nas; - if(!check_http_content_type(request->http,"application/json")){ + if (!check_http_content_type(request->http,"application/json")) { ogs_assert(true == nf_server_send_error(stream, 415, 3, message, "Unsupported Media Type.", "Expected content type: application/json", NULL, m5_networkassistance_api, app_meta)); ogs_sbi_message_free(message); ogs_free(message); @@ -476,15 +486,15 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_assert(true == nf_server_send_error(stream, 400, 0, message, "Creation of the Network Assistance Session failed.", err, NULL, m5_networkassistance_api, app_meta)); break; } - - nas = msaf_api_network_assistance_session_parseRequestFromJSON(network_assistance_sess, NULL); - if(nas->na_session_id) { - ogs_free(nas->na_session_id); - nas->na_session_id = message->h.resource.component[1]; - } + + nas = msaf_api_network_assistance_session_parseRequestFromJSON(network_assistance_sess, NULL); + if (nas->na_session_id) { + ogs_free(nas->na_session_id); + nas->na_session_id = message->h.resource.component[1]; + } na_sess = msaf_network_assistance_session_retrieve((const char *)message->h.resource.component[1]); - if(!na_sess){ + if (!na_sess) { char *err = NULL; err = ogs_msprintf("The AF has no network assistance session with id [%s].", message->h.resource.component[1]); ogs_error("%s", err); @@ -494,20 +504,20 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) break; } - if(!msaf_nw_assistance_session_update(na_sess, nas)) { - const char *err = "Updating dynamic policy: Unable to communicate withe the PCF"; + if (!msaf_nw_assistance_session_update(na_sess, nas)) { + const char *err = "Updating dynamic policy: Unable to communicate withe the PCF"; ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 1, message, "Updating dynamic policy failed.", err, NULL, m5_networkassistance_api, app_meta)); break; - } else { + } else { ogs_sbi_response_t *response; int response_code = 200; char *body; - char *hash; - cJSON *nw_assist_session; + char *hash; + cJSON *nw_assist_session; - nw_assist_session = msaf_api_network_assistance_session_convertResponseToJSON((const msaf_api_network_assistance_session_t *)na_sess->NetworkAssistanceSession); + nw_assist_session = msaf_api_network_assistance_session_convertResponseToJSON((const msaf_api_network_assistance_session_t *)na_sess->NetworkAssistanceSession); body = cJSON_Print(nw_assist_session); hash = calculate_hash(body); @@ -517,95 +527,95 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) 0, NULL, m5_networkassistance_api, app_meta); ogs_assert(response); - nf_server_populate_response(response, strlen(body), ogs_strdup(body), response_code); + nf_server_populate_response(response, strlen(body), msaf_strdup(body), response_code); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); ogs_free(hash); cJSON_Delete(nw_assist_session); cJSON_free(body); - } + } + + cJSON_Delete(network_assistance_sess); - cJSON_Delete(network_assistance_sess); - - } - break; + } + break; CASE(OGS_SBI_HTTP_METHOD_POST) - if(message->h.resource.component[1] && !strcmp(message->h.resource.component[2], "boost-request")) + if (message->h.resource.component[1] && !strcmp(message->h.resource.component[2], "boost-request")) { - msaf_network_assistance_session_t *na_sess; - na_sess = msaf_network_assistance_session_retrieve((const char *)message->h.resource.component[1]); - if(!na_sess){ - char *err = NULL; + msaf_network_assistance_session_t *na_sess; + na_sess = msaf_network_assistance_session_retrieve((const char *)message->h.resource.component[1]); + if (!na_sess) { + char *err = NULL; err = ogs_msprintf("The AF has no network assistance session with id [%s].", message->h.resource.component[1]); ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 404, 0, message, "Unable to retrieve the Network Assistance Session", err, NULL, m5_networkassistance_api, app_meta)); ogs_free(err); - } - if(!is_ue_allowed_to_request_delivery_boost(na_sess)) { - char *reason = NULL; - msaf_api_operation_success_response_t *operation_success_response; - cJSON *op_success_response; - char *success_response; - ogs_sbi_response_t *response; + } + if (!is_ue_allowed_to_request_delivery_boost(na_sess)) { + char *reason = NULL; + msaf_api_operation_success_response_t *operation_success_response; + cJSON *op_success_response; + char *success_response; + ogs_sbi_response_t *response; int response_code = 200; reason = ogs_msprintf("The AF has an active delivery boost for the network assistance session [%s].", message->h.resource.component[1]); ogs_debug("%s", reason); - - operation_success_response = msaf_api_operation_success_response_create(reason, 0); - op_success_response = msaf_api_operation_success_response_convertResponseToJSON(operation_success_response); - success_response = cJSON_Print(op_success_response); - response = nf_server_new_response(NULL, "application/json", 0, NULL, 0, NULL, m5_networkassistance_api, app_meta); + operation_success_response = msaf_api_operation_success_response_create(reason, 0); + op_success_response = msaf_api_operation_success_response_convertResponseToJSON(operation_success_response); + success_response = cJSON_Print(op_success_response); + + response = nf_server_new_response(NULL, "application/json", 0, NULL, 0, NULL, m5_networkassistance_api, app_meta); ogs_assert(response); - nf_server_populate_response(response, strlen(success_response), ogs_strdup(success_response), response_code); + nf_server_populate_response(response, strlen(success_response), msaf_strdup(success_response), response_code); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); - cJSON_Delete(op_success_response); - cJSON_free(success_response); - //ogs_free(reason); - msaf_api_operation_success_response_free(operation_success_response); - break; + cJSON_Delete(op_success_response); + cJSON_free(success_response); + //ogs_free(reason); + msaf_api_operation_success_response_free(operation_success_response); + break; - } + } - nw_assist_event = (msaf_event_t*)populate_msaf_event_with_metadata(e, m5_networkassistance_api, app_meta); - msaf_nw_assistance_session_delivery_boost_update(na_sess, nw_assist_event); + nw_assist_event = (msaf_event_t*)populate_msaf_event_with_metadata(e, m5_networkassistance_api, app_meta); + msaf_nw_assistance_session_delivery_boost_update(na_sess, nw_assist_event); - } else { - - cJSON *network_assistance_sess; - cJSON *service_data_flow_descriptions = NULL; - cJSON *policy_template_id = NULL; - cJSON *requested_qos = NULL; - cJSON *provisioning_session_id = NULL; + } else { - if(!check_http_content_type(request->http,"application/json")){ - ogs_assert(true == nf_server_send_error(stream, 415, 3, message, "Unsupported Media Type.", "Expected content type: application/json", NULL, m5_networkassistance_api, app_meta)); + cJSON *network_assistance_sess; + cJSON *service_data_flow_descriptions = NULL; + cJSON *policy_template_id = NULL; + cJSON *requested_qos = NULL; + cJSON *provisioning_session_id = NULL; + + if (!check_http_content_type(request->http,"application/json")) { + ogs_assert(true == nf_server_send_error(stream, 415, 3, message, "Unsupported Media Type.", "Expected content type: application/json", NULL, m5_networkassistance_api, app_meta)); ogs_sbi_message_free(message); ogs_free(message); - } + } ogs_debug("Request body: %s", request->http.content); network_assistance_sess = cJSON_Parse(request->http.content); - if (!network_assistance_sess) { + if (!network_assistance_sess) { const char *err = "networkAssistanceSession: Could not parse request body as JSON"; ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 400, 0, message, "Creation of the Network Assistance Session failed.", err, NULL, m5_networkassistance_api, app_meta)); break; - } + } - service_data_flow_descriptions = cJSON_GetObjectItemCaseSensitive(network_assistance_sess, "serviceDataFlowDescriptions"); + service_data_flow_descriptions = cJSON_GetObjectItemCaseSensitive(network_assistance_sess, "serviceDataFlowDescriptions"); if (!service_data_flow_descriptions) { const char *err = "createNetworkAssistanceSession: \"serviceDataFlowDescriptions\" not present"; ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 400, 0, message, "Creation of the Network Assistance Session failed.", err, NULL, m5_networkassistance_api, app_meta)); - cJSON_Delete(network_assistance_sess); + cJSON_Delete(network_assistance_sess); break; } if (!cJSON_IsArray(service_data_flow_descriptions)) { @@ -613,31 +623,31 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 400, 0, message, "Creation of the Network Assistance Session failed.", err, NULL, m5_networkassistance_api, app_meta)); cJSON_Delete(network_assistance_sess); - break; + break; } - provisioning_session_id = cJSON_GetObjectItemCaseSensitive(network_assistance_sess, "provisioningSessionId"); - if(!provisioning_session_id) { - const char *err = "createNetworkAssistanceSession: \"provisioningSessionId\" is not present"; + provisioning_session_id = cJSON_GetObjectItemCaseSensitive(network_assistance_sess, "provisioningSessionId"); + if (!provisioning_session_id) { + const char *err = "createNetworkAssistanceSession: \"provisioningSessionId\" is not present"; ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 400, 0, message, "Creation of the Network Assistance Session failed.", err, NULL, m5_networkassistance_api, app_meta)); cJSON_Delete(network_assistance_sess); break; - - } - if (!cJSON_IsString(provisioning_session_id)) { - const char *err = "createNetworkAssistanceSession: \"provisioningSessionId\" is not a string"; + + } + if (!cJSON_IsString(provisioning_session_id)) { + const char *err = "createNetworkAssistanceSession: \"provisioningSessionId\" is not a string"; ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 400, 0, message, "Creation of the Network Assistance Session failed.", err, NULL, m5_networkassistance_api, app_meta)); cJSON_Delete(network_assistance_sess); break; - - } + + } policy_template_id = cJSON_GetObjectItemCaseSensitive(network_assistance_sess, "policyTemplateId"); if (!policy_template_id) { ogs_debug("createNetworkAssistanceSession: \"policyTemplateId\" is not present"); - + } if (!policy_template_id && cJSON_IsString(policy_template_id)) { ogs_debug("createNetworkAssistanceSession: \"policyTemplateId\" is not a string"); @@ -647,10 +657,10 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) if (!requested_qos) { ogs_debug("createNetworkAssistanceSession: \"requestedQoS\" is not present"); } - - nw_assist_event = (msaf_event_t*)populate_msaf_event_with_metadata(e, m5_networkassistance_api, app_meta); - if(!msaf_nw_assistance_session_create(network_assistance_sess, nw_assist_event)) { + nw_assist_event = (msaf_event_t*)populate_msaf_event_with_metadata(e, m5_networkassistance_api, app_meta); + + if (!msaf_nw_assistance_session_create(network_assistance_sess, nw_assist_event)) { const char *err = "Problem in obtaining the information required to create the Network Assitance Session"; ogs_error("%s", err); @@ -658,18 +668,18 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) cJSON_Delete(network_assistance_sess); break; - } + } - cJSON_Delete(network_assistance_sess); - } - break; - DEFAULT + cJSON_Delete(network_assistance_sess); + } + break; + DEFAULT ogs_error("Invalid HTTP method [%s]", message->h.method); ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_FORBIDDEN, 1, message, "Invalid HTTP method.", message->h.method, NULL, NULL, app_meta)); END break; - + CASE("service-access-information") SWITCH(message->h.method) CASE(OGS_SBI_HTTP_METHOD_GET) @@ -718,7 +728,7 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_sbi_response_t *response; response = nf_server_new_response(NULL, "application/json", ogs_time_sec(sai_entry->generated)+1, sai_entry->hash, msaf_self()->config.server_response_cache_control->m5_service_access_information_response_max_age, NULL, m5_serviceaccessinformation_api, app_meta); ogs_assert(response); - nf_server_populate_response(response, response_body?strlen(response_body):0, ogs_strdup(response_body), response_code); + nf_server_populate_response(response, response_body?strlen(response_body):0, msaf_strdup(response_body), response_code); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); } break; @@ -739,6 +749,106 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_FORBIDDEN, 1, message, "Invalid HTTP method.", message->h.method, NULL, NULL, app_meta)); END break; + + CASE("metrics-reporting") + SWITCH(message->h.method) + CASE(OGS_SBI_HTTP_METHOD_POST) + if(message->h.resource.component[1] && message->h.resource.component[2] && !message->h.resource.component[3]){ + + msaf_provisioning_session_t *provisioning_session; + provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId(message->h.resource.component[1]); + + if(provisioning_session){ + if (ogs_hash_count(provisioning_session->metrics_reporting_map) == 0) { + char *err; + err = ogs_msprintf("No MetricsReportingConfiguration for Provisioning Session [%s]", message->h.resource.component[1]); + ogs_error("%s", err); + ogs_assert(true==nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_NOT_FOUND, 1, message, "Not found", err, NULL, m5_metricsreporting_api, app_meta)); + ogs_free(err); + } else { + const char *content_type = "application/xml"; + ogs_hash_index_t *hi; + for (hi = ogs_hash_first(request->http.headers); hi; hi = ogs_hash_next(hi)) { + if (!ogs_strcasecmp(ogs_hash_this_key(hi), OGS_SBI_CONTENT_TYPE)) { + content_type = ogs_hash_this_val(hi); + break; + } + } + + SWITCH(content_type) + CASE("application/xml") + + /* This will parse relevant information from incoming XML */ + char *parseXmlField(const char *xmlString, const char *fieldName) { + char *startTag = malloc(strlen(fieldName) + 3); + char *endTag = "\""; + sprintf(startTag, "%s=\"", fieldName); + char *startPosition = strstr(xmlString, startTag), *endPosition, *fieldValue = NULL; + if (startPosition && (endPosition = strstr(startPosition += strlen(startTag), endTag))) { + fieldValue = strndup(startPosition, endPosition - startPosition); + } + free(startTag); + return fieldValue; + } + if (request->http.content) { + char *reportTime = parseXmlField(request->http.content, "reportTime"); + char *recordingSessionId = parseXmlField(request->http.content, "recordingSessionId"); + char *clientId = parseXmlField(request->http.content, "clientID"); + + if (msaf_data_collection_store(message->h.resource.component[1], "metrics_reports", clientId, recordingSessionId, reportTime, "xml", request->http.content)) { + ogs_sbi_response_t *response; + response = nf_server_new_response(request->h.uri, NULL, 0, NULL, 0, NULL,m5_metricsreporting_api, app_meta); + ogs_assert(response); + nf_server_populate_response(response, 0, NULL, 204); + ogs_assert(true == ogs_sbi_server_send_response(stream, response)); + } else { + char *err; + err = ogs_msprintf( "Failed to store Metrics Report for provisioning session [%s]", message->h.resource.component[1]); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_INTERNAL_SERVER_ERROR, 1, message, "Data storage error", err, NULL, m5_metricsreporting_api, app_meta)); + ogs_free(err); + } + break; + DEFAULT + char *err; + err = ogs_msprintf( "Unrecognised content type for Metrics Report for Provisioning Session [%s]", message->h.resource.component[1]); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, 1, message, "Unsupported Media Type",err, NULL, m5_metricsreporting_api, app_meta)); + ogs_free(err); + END + } + } + } else{ + char *err; + err = ogs_msprintf("Provisioning session [%s] not found for Metrics Report", message->h.resource.component[1]); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_NOT_FOUND, 1, message, "Not Found", err, NULL, m5_metricsreporting_api, app_meta)); + ogs_free(err); + } + } else { + ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_NOT_FOUND, 0, message, "No Metrics ID Found", NULL, NULL, m5_metricsreporting_api, app_meta)); + } + break; + CASE(OGS_SBI_HTTP_METHOD_OPTIONS) + if (message->h.resource.component[1] && message->h.resource.component[2] && !message->h.resource.component[3]) { + ogs_sbi_response_t *response; + response = nf_server_new_response(request->h.uri, NULL, 0, NULL, 0, OGS_SBI_HTTP_METHOD_POST ", " OGS_SBI_HTTP_METHOD_OPTIONS, m5_metricsreporting_api, app_meta); + ogs_assert(response); + nf_server_populate_response(response, 0, NULL, 204); + ogs_assert(true == ogs_sbi_server_send_response(stream, response)); + } else { + ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_NOT_FOUND, 2, message, "No Metrics ID Found", message->h.method, NULL, m5_metricsreporting_api, app_meta)); + } + DEFAULT + char *err; + err = ogs_msprintf("Method [%s] not implemented for M5 Metrics Reporting API", message->h.method); + ogs_error("%s", err); + ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_MEHTOD_NOT_ALLOWED, 1, message, "Method Not Allowed", err, NULL, m5_metricsreporting_api, app_meta)); + ogs_free(err); + END + break; + DEFAULT + CASE("consumption-reporting") SWITCH(message->h.method) CASE(OGS_SBI_HTTP_METHOD_POST) @@ -843,6 +953,7 @@ void msaf_m5_state_functional(ogs_fsm_t *s, msaf_event_t *e) } break; CASE(OGS_SBI_HTTP_METHOD_OPTIONS) + if (message->h.resource.component[1] && !message->h.resource.component[2]) { ogs_sbi_response_t *response; response = nf_server_new_response(request->h.uri, NULL, 0, NULL, 0, @@ -904,7 +1015,7 @@ static bool is_dynamic_policy_create_request_valid(ogs_sbi_request_t *request, o cJSON *policy_template_id = NULL; cJSON *provisioning_session_id = NULL; - if(!check_http_content_type(request->http,"application/json")){ + if (!check_http_content_type(request->http,"application/json")) { ogs_assert(true == nf_server_send_error(stream, 415, 3, message, "Unsupported Media Type.", "Expected content type: application/json", NULL, m5_dynamicpolicy_api, app_meta)); ogs_sbi_message_free(message); @@ -938,7 +1049,7 @@ static bool is_dynamic_policy_create_request_valid(ogs_sbi_request_t *request, o } provisioning_session_id = cJSON_GetObjectItemCaseSensitive(dynamic_policy, "provisioningSessionId"); - if(!provisioning_session_id) { + if (!provisioning_session_id) { const char *err = "createDynamicPolicy: \"provisioningSessionId\" is not present"; ogs_error("%s", err); ogs_assert(true == nf_server_send_error(stream, 400, 0, message, "Creation of the Dynamic Policy failed.", err, NULL, m5_dynamicpolicy_api, app_meta)); @@ -972,7 +1083,7 @@ static bool is_dynamic_policy_create_request_valid(ogs_sbi_request_t *request, o return 0; } - if(dynamic_policy) { + if (dynamic_policy) { cJSON_Delete(dynamic_policy); dynamic_policy = NULL; } diff --git a/src/5gmsaf/msaf-m5-sm.h b/src/5gmsaf/msaf-m5-sm.h index 26400a8..552e888 100644 --- a/src/5gmsaf/msaf-m5-sm.h +++ b/src/5gmsaf/msaf-m5-sm.h @@ -1,11 +1,11 @@ /* -License: 5G-MAG Public License (v1.0) -Author: David Waring -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + * License: 5G-MAG Public License (v1.0) + * Author: David Waring + * Copyright: (C) 2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view */ #ifndef MSAF_M5_SM_H diff --git a/src/5gmsaf/msaf-mgmt-sm.c b/src/5gmsaf/msaf-mgmt-sm.c index 9f2ea08..6baadab 100644 --- a/src/5gmsaf/msaf-mgmt-sm.c +++ b/src/5gmsaf/msaf-mgmt-sm.c @@ -1,6 +1,6 @@ /* * License: 5G-MAG Public License (v1.0) - * Author: Dev Audsin + * Author: Dev Audsin * Copyright: (C) 2023 British Broadcasting Corporation * * For full license terms please see the LICENSE file distributed with this @@ -17,7 +17,7 @@ #include "msaf-version.h" #include "msaf-sm.h" #include "openapi/api/Maf_ManagementAPI-info.h" - + #include "msaf-mgmt-sm.h" static const nf_server_interface_metadata_t @@ -75,7 +75,7 @@ void msaf_maf_mgmt_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_fsm_dispatch(&msaf_self()->msaf_fsm.msaf_m1_sm, e); message = NULL; break; - + DEFAULT ogs_error("Resource [%s] not found.", message->h.service.name); ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_NOT_FOUND, 0, message, "Not Found.", message->h.service.name, NULL, maf_management_api, app_meta)); diff --git a/src/5gmsaf/msaf-mgmt-sm.h b/src/5gmsaf/msaf-mgmt-sm.h index c3f0469..7adbbf8 100644 --- a/src/5gmsaf/msaf-mgmt-sm.h +++ b/src/5gmsaf/msaf-mgmt-sm.h @@ -1,11 +1,11 @@ /* -License: 5G-MAG Public License (v1.0) -Author: David Waring -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + * License: 5G-MAG Public License (v1.0) + * Author: David Waring + * Copyright: (C) 2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view */ #ifndef MSAF_MGMT_SM_H diff --git a/src/5gmsaf/msaf-sm.c b/src/5gmsaf/msaf-sm.c index 321d398..6d822a1 100644 --- a/src/5gmsaf/msaf-sm.c +++ b/src/5gmsaf/msaf-sm.c @@ -1,7 +1,8 @@ /* * License: 5G-MAG Public License (v1.0) - * Author: Dev Audsin - * Copyright: (C) 2022-2023 British Broadcasting Corporation + * Authors: Dev Audsin + * David Waring + * Copyright: (C) 2022-2024 British Broadcasting Corporation * * For full license terms please see the LICENSE file distributed with this * program. If this file is missing then the license can be retrieved from @@ -81,7 +82,7 @@ void msaf_state_functional(ogs_fsm_t *s, msaf_event_t *e) stream = e->h.sbi.data; ogs_assert(stream); - if (!strcmp(request->h.method, OGS_SBI_HTTP_METHOD_OPTIONS) && !strcmp(request->h.uri, "*")){ + if (!strcmp(request->h.method, OGS_SBI_HTTP_METHOD_OPTIONS) && !strcmp(request->h.uri, "*")) { char *methods = NULL; methods = ogs_msprintf("%s, %s, %s, %s, %s",OGS_SBI_HTTP_METHOD_POST, OGS_SBI_HTTP_METHOD_GET, OGS_SBI_HTTP_METHOD_PUT, OGS_SBI_HTTP_METHOD_DELETE, OGS_SBI_HTTP_METHOD_OPTIONS); response = nf_server_new_response(NULL, NULL, 0, NULL, 0, methods, NULL, app_meta); @@ -138,7 +139,7 @@ void msaf_state_functional(ogs_fsm_t *s, msaf_event_t *e) break; CASE("3gpp-m1") - if(check_event_addresses(e, msaf_self()->config.servers[MSAF_SVR_M1].ipv4, msaf_self()->config.servers[MSAF_SVR_M1].ipv6)){ + if (check_event_addresses(e, msaf_self()->config.servers[MSAF_SVR_M1].ipv4, msaf_self()->config.servers[MSAF_SVR_M1].ipv6)) { e->message = message; message = NULL; ogs_fsm_dispatch(&msaf_self()->msaf_fsm.msaf_m1_sm, e); @@ -149,9 +150,9 @@ void msaf_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_free(error); } break; - + CASE("5gmag-rt-management") - if(check_event_addresses(e, msaf_self()->config.servers[MSAF_SVR_MSAF].ipv4, msaf_self()->config.servers[MSAF_SVR_MSAF].ipv6)){ + if (check_event_addresses(e, msaf_self()->config.servers[MSAF_SVR_MSAF].ipv4, msaf_self()->config.servers[MSAF_SVR_MSAF].ipv6)) { e->message = message; message = NULL; ogs_fsm_dispatch(&msaf_self()->msaf_fsm.msaf_maf_mgmt_sm, e); @@ -162,10 +163,10 @@ void msaf_state_functional(ogs_fsm_t *s, msaf_event_t *e) ogs_assert(true == nf_server_send_error(stream, OGS_SBI_HTTP_STATUS_NOT_FOUND, 1, NULL, "Not Found.", error, NULL, NULL, app_meta)); ogs_free(error); } - break; + break; CASE("3gpp-m5") - if(check_event_addresses(e, msaf_self()->config.servers[MSAF_SVR_M5].ipv4, msaf_self()->config.servers[MSAF_SVR_M5].ipv6)){ + if (check_event_addresses(e, msaf_self()->config.servers[MSAF_SVR_M5].ipv4, msaf_self()->config.servers[MSAF_SVR_M5].ipv6)) { e->message = message; message = NULL; ogs_fsm_dispatch(&msaf_self()->msaf_fsm.msaf_m5_sm, e); @@ -198,7 +199,7 @@ void msaf_state_functional(ogs_fsm_t *s, msaf_event_t *e) message->res_status = response->status; SWITCH(message->h.service.name) - + CASE("3gpp-m3") /* M1 state machine handles M3 responses */ e->message = message; @@ -210,13 +211,13 @@ void msaf_state_functional(ogs_fsm_t *s, msaf_event_t *e) SWITCH(message->h.resource.component[0]) CASE(OGS_SBI_RESOURCE_NAME_NF_INSTANCES) - cJSON *nf_profile; + cJSON *nf_profile; OpenAPI_nf_profile_t *nfprofile; nf_instance = e->h.sbi.data; ogs_assert(nf_instance); - if (response->http.content_length && response->http.content){ + if (response->http.content_length && response->http.content) { ogs_debug( "response: %s", response->http.content); nf_profile = cJSON_Parse(response->http.content); nfprofile = OpenAPI_nf_profile_parseFromJSON(nf_profile); @@ -233,7 +234,7 @@ void msaf_state_functional(ogs_fsm_t *s, msaf_event_t *e) e->h.sbi.message = message; //message = NULL; ogs_fsm_dispatch(&nf_instance->sm, e); - ogs_sbi_response_free(response); + ogs_sbi_response_free(response); break; CASE(OGS_SBI_RESOURCE_NAME_SUBSCRIPTIONS) @@ -281,13 +282,13 @@ void msaf_state_functional(ogs_fsm_t *s, msaf_event_t *e) break; case MSAF_EVENT_DELIVERY_BOOST_TIMER: - ogs_assert(e); + ogs_assert(e); //e->message = message; //message = NULL; ogs_fsm_dispatch(&msaf_self()->msaf_fsm.msaf_m5_sm, e); - break; + break; - case OGS_EVENT_SBI_TIMER: + case OGS_EVENT_SBI_TIMER: ogs_assert(e); switch(e->h.timer_id) { diff --git a/src/5gmsaf/msaf-sm.h b/src/5gmsaf/msaf-sm.h index abef9eb..9c5cacf 100644 --- a/src/5gmsaf/msaf-sm.h +++ b/src/5gmsaf/msaf-sm.h @@ -1,11 +1,11 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022-2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022-2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view */ #ifndef MSAF_SM_H diff --git a/src/5gmsaf/msaf.yaml.in b/src/5gmsaf/msaf.yaml.in index d63e936..b04a75d 100644 --- a/src/5gmsaf/msaf.yaml.in +++ b/src/5gmsaf/msaf.yaml.in @@ -189,6 +189,7 @@ msaf: serverResponseCacheControl: - maxAge: 60 m1ProvisioningSessions: 60 + m1MetricsReportingConfiguration: 60 m1ContentHostingConfigurations: 60 m1ServerCertificates: 60 m1ContentProtocols: 86400 diff --git a/src/5gmsaf/network-assistance-delivery-boost.c b/src/5gmsaf/network-assistance-delivery-boost.c index c33ca5b..c1116a4 100644 --- a/src/5gmsaf/network-assistance-delivery-boost.c +++ b/src/5gmsaf/network-assistance-delivery-boost.c @@ -1,12 +1,13 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Authors: Dev Audsin + * David Waring + * Copyright: (C) 2023-2024 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include "context.h" #include "network-assistance-delivery-boost.h" @@ -35,12 +36,12 @@ void msaf_network_assistance_delivery_boost_free(void) { } int is_ue_allowed_to_request_delivery_boost(msaf_network_assistance_session_t *na_sess) { - - if(na_sess->active_delivery_boost) - return 0; + + if (na_sess->active_delivery_boost) + return 0; //Placeholder to implement any further restrictions here - + return 1; } diff --git a/src/5gmsaf/network-assistance-delivery-boost.h b/src/5gmsaf/network-assistance-delivery-boost.h index 91fbcc5..5530eab 100644 --- a/src/5gmsaf/network-assistance-delivery-boost.h +++ b/src/5gmsaf/network-assistance-delivery-boost.h @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef MSAF_NETWORK_ASSISTANCE_DELIVERY_BOOST_H #define MSAF_NETWORK_ASSISTANCE_DELIVERY_BOOST_H @@ -20,7 +20,7 @@ program. If this file is missing then the license can be retrieved from extern "C" { #endif -typedef struct msaf_network_assistance_session_s msaf_network_assistance_session_t; +typedef struct msaf_network_assistance_session_s msaf_network_assistance_session_t; typedef struct msaf_network_assistance_delivery_boost_s { uint64_t delivery_boost_min_dl_bit_rate; diff --git a/src/5gmsaf/network-assistance-session.c b/src/5gmsaf/network-assistance-session.c index 9610540..650406f 100644 --- a/src/5gmsaf/network-assistance-session.c +++ b/src/5gmsaf/network-assistance-session.c @@ -1,12 +1,13 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Authors: Dev Audsin + * David Waring + * Copyright: (C) 2023-2024 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include "utilities.h" #include "network-assistance-session.h" @@ -59,7 +60,7 @@ int msaf_nw_assistance_session_create(cJSON *network_assistance_sess, msaf_event OpenAPI_list_t *media_component = NULL; nas = msaf_api_network_assistance_session_parseRequestFromJSON(network_assistance_sess, NULL); - if(!nas) return 0; + if (!nas) return 0; na_sess = msaf_network_assistance_session_init(); ogs_assert(na_sess); @@ -75,26 +76,26 @@ int msaf_nw_assistance_session_create(cJSON *network_assistance_sess, msaf_event service_data_flow_description = (msaf_api_service_data_flow_description_t *)node->data; - if(service_data_flow_description->domain_name) { - ogs_debug("Service Data Flow Descriptions specified using a domain name are not yet supported by this implementation"); + if (service_data_flow_description->domain_name) { + ogs_debug("Service Data Flow Descriptions specified using a domain name are not yet supported by this implementation"); msaf_network_assistance_session_remove(na_sess); - return 0; - } - + return 0; + } + - if(service_data_flow_description->flow_description && service_data_flow_description->domain_name) { - ogs_error("Validation of service data flow description failed: Only one of flowDescription or domainName may be present"); + if (service_data_flow_description->flow_description && service_data_flow_description->domain_name) { + ogs_error("Validation of service data flow description failed: Only one of flowDescription or domainName may be present"); msaf_network_assistance_session_remove(na_sess); return 0; - } + } - if(!service_data_flow_description->flow_description && !service_data_flow_description->domain_name) { + if (!service_data_flow_description->flow_description && !service_data_flow_description->domain_name) { ogs_error("Validation of service data flow description failed: flowDescription or domainName must be present"); msaf_network_assistance_session_remove(na_sess); return 0; } - if(service_data_flow_description->flow_description){ + if (service_data_flow_description->flow_description) { if (!service_data_flow_description->flow_description->direction) { ogs_error("Validation of service data flow description failed: no flowDescription.direction present"); @@ -148,7 +149,7 @@ int msaf_nw_assistance_session_create(cJSON *network_assistance_sess, msaf_event } ue_connection_details_free(ue_connection); } - } + } } return 1; @@ -164,7 +165,7 @@ int msaf_nw_assistance_session_update(msaf_network_assistance_session_t *msaf_ne ogs_assert(network_assistance_session); if (!msaf_network_assistance_session->pcf_app_session) { - ogs_error("The Network Assistance Session has no associated App Session"); + ogs_error("The Network Assistance Session has no associated App Session"); return 0; } @@ -173,18 +174,18 @@ int msaf_nw_assistance_session_update(msaf_network_assistance_session_t *msaf_ne service_data_flow_description = (msaf_api_service_data_flow_description_t *)node->data; - if(service_data_flow_description->domain_name) { + if (service_data_flow_description->domain_name) { ogs_debug("Service Data Flow Descriptions specified using a domain name are not yet supported by this implementation"); return 0; } - if(service_data_flow_description->flow_description && service_data_flow_description->domain_name) { + if (service_data_flow_description->flow_description && service_data_flow_description->domain_name) { ogs_error("Validation of service data flow description failed: Exactly one of flowDescription or domainName must be present"); return 0; } - if(service_data_flow_description->flow_description){ + if (service_data_flow_description->flow_description) { if (!service_data_flow_description->flow_description->direction) { ogs_error("The Network Assistance Session has direction in flow description"); @@ -193,18 +194,18 @@ int msaf_nw_assistance_session_update(msaf_network_assistance_session_t *msaf_ne media_component = populate_media_component(network_assistance_session->policy_template_id, service_data_flow_description->flow_description, network_assistance_session->requested_qo_s, network_assistance_session->media_type?network_assistance_session->media_type: OpenAPI_media_type_VIDEO); - if(!pcf_session_update_app_session(msaf_network_assistance_session->pcf_app_session, media_component)) { + if (!pcf_session_update_app_session(msaf_network_assistance_session->pcf_app_session, media_component)) { ogs_error("Unable to send update request to the PCF"); return 0; } - } - } + } + } } update_msaf_network_assistance_session_context(msaf_network_assistance_session, network_assistance_session); return 1; } -void msaf_nw_assistance_session_delivery_boost_update(msaf_network_assistance_session_t *na_sess, msaf_event_t *e){ +void msaf_nw_assistance_session_delivery_boost_update(msaf_network_assistance_session_t *na_sess, msaf_event_t *e) { OpenAPI_list_t *media_comps; char *mir_bw_dl_bit_rate = NULL; @@ -221,7 +222,7 @@ void msaf_nw_assistance_session_delivery_boost_update(msaf_network_assistance_se add_delivery_boost_event_metadata_to_na_sess_context(na_sess, e); - if(!pcf_session_update_app_session(na_sess->pcf_app_session, media_comps)) { + if (!pcf_session_update_app_session(na_sess->pcf_app_session, media_comps)) { ogs_error("Unable to send update request to the PCF"); ogs_assert(true == nf_server_send_error(e->h.sbi.data, 401, 0, e->message, "Creation of delivery boost failed.", "Unable to send update request to the PCF" , NULL, e->nf_server_interface_metadata, e->app_meta)); } @@ -235,7 +236,7 @@ void msaf_nw_assistance_session_delivery_boost_update(msaf_network_assistance_se } -void msaf_nw_assistance_session_update_pcf_on_timeout(msaf_network_assistance_session_t *na_sess){ +void msaf_nw_assistance_session_update_pcf_on_timeout(msaf_network_assistance_session_t *na_sess) { OpenAPI_list_t *media_comps; char *mir_bw_dl_bit_rate; @@ -252,7 +253,7 @@ void msaf_nw_assistance_session_update_pcf_on_timeout(msaf_network_assistance_se rv = pcf_session_update_app_session(na_sess->pcf_app_session, media_comps); - if(!rv){ + if (!rv) { ogs_error("Unable to send update request to the PCF"); } else { na_sess->active_delivery_boost = false; @@ -273,10 +274,10 @@ msaf_network_assistance_session_t *msaf_network_assistance_session_retrieve(cons msaf_network_assistance_session_t *na_sess = NULL; ogs_list_for_each(&msaf_self()->network_assistance_sessions, na_sess) { - if(!strcmp(na_sess->naSessionId, na_session_id)) + if (!strcmp(na_sess->naSessionId, na_session_id)) break; } - if(na_sess) + if (na_sess) return na_sess; return NULL; @@ -288,10 +289,10 @@ cJSON *msaf_network_assistance_session_get_json(const char *na_session_id) msaf_network_assistance_session_t *na_sess = NULL; ogs_list_for_each(&msaf_self()->network_assistance_sessions, na_sess) { - if(!strcmp(na_sess->naSessionId, na_session_id)) + if (!strcmp(na_sess->naSessionId, na_session_id)) break; } - if(na_sess) + if (na_sess) return msaf_api_network_assistance_session_convertResponseToJSON(na_sess->NetworkAssistanceSession); return NULL; @@ -335,7 +336,7 @@ ue_network_identifier_t *populate_ue_connection_details(msaf_api_service_data_fl void msaf_network_assistance_session_remove_all_pcf_app_session(void) { msaf_pcf_app_session_t *msaf_pcf_app_session = NULL, *next = NULL; - ogs_list_for_each_safe(&msaf_self()->delete_pcf_app_sessions, next, msaf_pcf_app_session){ + ogs_list_for_each_safe(&msaf_self()->delete_pcf_app_sessions, next, msaf_pcf_app_session) { ogs_list_remove(&msaf_self()->delete_pcf_app_sessions, msaf_pcf_app_session); ogs_free(msaf_pcf_app_session); } @@ -372,7 +373,7 @@ void msaf_network_assistance_session_remove_all() { msaf_network_assistance_session_t *msaf_network_assistance_session = NULL, *next = NULL; - ogs_list_for_each_safe(&msaf_self()->network_assistance_sessions, next, msaf_network_assistance_session){ + ogs_list_for_each_safe(&msaf_self()->network_assistance_sessions, next, msaf_network_assistance_session) { ogs_list_remove(&msaf_self()->network_assistance_sessions, msaf_network_assistance_session); msaf_network_assistance_session_remove(msaf_network_assistance_session); } @@ -382,8 +383,8 @@ void msaf_network_assistance_session_delete_by_session_id(const char *na_sess_id { msaf_network_assistance_session_t *msaf_network_assistance_session = NULL, *next = NULL; - ogs_list_for_each_safe(&msaf_self()->network_assistance_sessions, next, msaf_network_assistance_session){ - if(!strcmp(msaf_network_assistance_session->naSessionId, na_sess_id)) { + ogs_list_for_each_safe(&msaf_self()->network_assistance_sessions, next, msaf_network_assistance_session) { + if (!strcmp(msaf_network_assistance_session->naSessionId, na_sess_id)) { ogs_list_remove(&msaf_self()->network_assistance_sessions, msaf_network_assistance_session); msaf_network_assistance_session_add_to_delete_list(msaf_network_assistance_session->pcf_app_session); msaf_network_assistance_session_remove(msaf_network_assistance_session); @@ -394,7 +395,7 @@ void msaf_network_assistance_session_delete_by_session_id(const char *na_sess_id /*Private functions */ -static msaf_network_assistance_session_t *msaf_network_assistance_session_init(void){ +static msaf_network_assistance_session_t *msaf_network_assistance_session_init(void) { msaf_network_assistance_session_t *na_sess; na_sess = ogs_calloc(1, sizeof(msaf_network_assistance_session_t)); @@ -427,7 +428,7 @@ static OpenAPI_list_t *populate_media_component(char *policy_template_id, msaf_a char *remote_addr; char *remote_port; - if(!strcmp(flow_description->direction, "UPLINK")){ + if (!strcmp(flow_description->direction, "UPLINK")) { remote_addr = flow_description->dst_ip?flow_description->dst_ip:"any"; remote_port = flow_description_port(flow_description->dst_port); @@ -436,7 +437,7 @@ static OpenAPI_list_t *populate_media_component(char *policy_template_id, msaf_a } - if(!strcmp(flow_description->direction, "DOWNLINK")){ + if (!strcmp(flow_description->direction, "DOWNLINK")) { remote_addr = flow_description->src_ip?flow_description->src_ip:"any"; remote_port = flow_description_port(flow_description->src_port); @@ -512,8 +513,8 @@ static void msaf_network_assistance_session_add_to_delete_list(pcf_app_session_t static void msaf_network_assistance_session_remove_from_delete_list(void) { msaf_pcf_app_session_t *msaf_pcf_app_session = NULL, *next = NULL; - ogs_list_for_each_safe(&msaf_self()->delete_pcf_app_sessions, next, msaf_pcf_app_session){ - if(!msaf_pcf_app_session->pcf_app_session){ + ogs_list_for_each_safe(&msaf_self()->delete_pcf_app_sessions, next, msaf_pcf_app_session) { + if (!msaf_pcf_app_session->pcf_app_session) { ogs_list_remove(&msaf_self()->delete_pcf_app_sessions, msaf_pcf_app_session); ogs_free(msaf_pcf_app_session); } @@ -524,8 +525,8 @@ static void msaf_network_assistance_session_remove_from_delete_list(void) static void msaf_pcf_app_session_free(void) { msaf_pcf_app_session_t *msaf_pcf_app_session; - ogs_list_for_each(&msaf_self()->delete_pcf_app_sessions, msaf_pcf_app_session){ - if(msaf_pcf_app_session->pcf_app_session) + ogs_list_for_each(&msaf_self()->delete_pcf_app_sessions, msaf_pcf_app_session) { + if (msaf_pcf_app_session->pcf_app_session) { pcf_app_session_free(msaf_pcf_app_session->pcf_app_session); msaf_pcf_app_session->pcf_app_session = NULL; @@ -572,10 +573,10 @@ static ue_network_identifier_t *copy_ue_network_connection_identifier(const ue_n ue_net_connection_copy = ogs_calloc(1, sizeof(ue_network_identifier_t)); if (ue_net_connection_copy) { if (ue_net_connection->address) ogs_copyaddrinfo(&ue_net_connection_copy->address, ue_net_connection->address); - if (ue_net_connection->supi) ue_net_connection_copy->supi = ogs_strdup(ue_net_connection->supi); - if (ue_net_connection->gpsi) ue_net_connection_copy->gpsi = ogs_strdup(ue_net_connection->gpsi); - if (ue_net_connection->dnn) ue_net_connection_copy->dnn = ogs_strdup(ue_net_connection->dnn); - if (ue_net_connection->ip_domain) ue_net_connection_copy->ip_domain = ogs_strdup(ue_net_connection->ip_domain); + ue_net_connection_copy->supi = msaf_strdup(ue_net_connection->supi); + ue_net_connection_copy->gpsi = msaf_strdup(ue_net_connection->gpsi); + ue_net_connection_copy->dnn = msaf_strdup(ue_net_connection->dnn); + ue_net_connection_copy->ip_domain = msaf_strdup(ue_net_connection->ip_domain); } return ue_net_connection_copy; } @@ -591,21 +592,21 @@ static void free_ue_network_connection_identifier(ue_network_identifier_t *ue_ne ogs_free(ue_net_connection); } -static void msaf_network_assistance_session_remove(msaf_network_assistance_session_t *msaf_network_assistance_session){ +static void msaf_network_assistance_session_remove(msaf_network_assistance_session_t *msaf_network_assistance_session) { ogs_assert(msaf_network_assistance_session); - if(msaf_network_assistance_session->naSessionId) { + if (msaf_network_assistance_session->naSessionId) { ogs_free(msaf_network_assistance_session->naSessionId); msaf_network_assistance_session->naSessionId = NULL; } - if(msaf_network_assistance_session->NetworkAssistanceSession) msaf_api_network_assistance_session_free(msaf_network_assistance_session->NetworkAssistanceSession); - if(msaf_network_assistance_session->metadata){ - if(msaf_network_assistance_session->metadata->create_event) msaf_event_free(msaf_network_assistance_session->metadata->create_event); - if(msaf_network_assistance_session->metadata->delivery_boost) msaf_event_free(msaf_network_assistance_session->metadata->delivery_boost); + if (msaf_network_assistance_session->NetworkAssistanceSession) msaf_api_network_assistance_session_free(msaf_network_assistance_session->NetworkAssistanceSession); + if (msaf_network_assistance_session->metadata) { + if (msaf_network_assistance_session->metadata->create_event) msaf_event_free(msaf_network_assistance_session->metadata->create_event); + if (msaf_network_assistance_session->metadata->delivery_boost) msaf_event_free(msaf_network_assistance_session->metadata->delivery_boost); ogs_free(msaf_network_assistance_session->metadata); } - if(msaf_network_assistance_session->delivery_boost_timer) + if (msaf_network_assistance_session->delivery_boost_timer) ogs_timer_delete(msaf_network_assistance_session->delivery_boost_timer); ogs_free(msaf_network_assistance_session); @@ -613,22 +614,22 @@ static void msaf_network_assistance_session_remove(msaf_network_assistance_sessi } static void ue_connection_details_free(ue_network_identifier_t *ue_connection) { - if(ue_connection->address) ogs_freeaddrinfo(ue_connection->address); - if(ue_connection->ip_domain) ogs_free(ue_connection->ip_domain); + if (ue_connection->address) ogs_freeaddrinfo(ue_connection->address); + if (ue_connection->ip_domain) ogs_free(ue_connection->ip_domain); ogs_free(ue_connection); } -static bool app_session_change_callback(pcf_app_session_t *app_session, void *data){ +static bool app_session_change_callback(pcf_app_session_t *app_session, void *data) { msaf_network_assistance_session_t *na_sess; ogs_debug("change callback(app_session=%p, data=%p)", app_session, data); na_sess = (msaf_network_assistance_session_t *)data; - if(!app_session){ + if (!app_session) { - if(na_sess->metadata->create_event) + if (na_sess->metadata->create_event) { /* ogs_assert(true == nf_server_send_error(na_sess->create_event->h.sbi.data, 401, 0, na_sess->create_event->message, "Creation of the Network Assistance Session failed.", "PCF App Session creation failed" , NULL, na_sess->create_event->local.nf_server_interface_metadata, na_sess->create_event->local.app_meta)); @@ -638,7 +639,7 @@ static bool app_session_change_callback(pcf_app_session_t *app_session, void *da } - if(na_sess->metadata->delivery_boost){ + if (na_sess->metadata->delivery_boost) { delivery_boost_send_response(na_sess); return false; } @@ -648,13 +649,13 @@ static bool app_session_change_callback(pcf_app_session_t *app_session, void *da return false; } - if(app_session && na_sess->metadata->create_event){ + if (app_session && na_sess->metadata->create_event) { na_sess->pcf_app_session = app_session; create_msaf_na_sess_and_send_response(na_sess); return true; } - if(app_session && na_sess->metadata->delivery_boost) { + if (app_session && na_sess->metadata->delivery_boost) { ogs_info("Callback from PCF Update"); activate_delivery_boost_and_send_response(na_sess); return true; @@ -683,15 +684,15 @@ static void activate_delivery_boost_and_send_response(msaf_network_assistance_se response = nf_server_new_response(NULL, "application/json", 0, NULL, cache_control_max_age, NULL, na_sess->metadata->delivery_boost->nf_server_interface_metadata, na_sess->metadata->delivery_boost->app_meta); ogs_assert(response); - nf_server_populate_response(response, strlen(success_response), ogs_strdup(success_response), response_code); + nf_server_populate_response(response, strlen(success_response), msaf_strdup(success_response), response_code); ogs_assert(true == ogs_sbi_server_send_response(na_sess->metadata->delivery_boost->h.sbi.data, response)); - if(!na_sess->delivery_boost_timer) na_sess->delivery_boost_timer = ogs_timer_add(ogs_app()->timer_mgr, msaf_timer_delivery_boost, na_sess); + if (!na_sess->delivery_boost_timer) na_sess->delivery_boost_timer = ogs_timer_add(ogs_app()->timer_mgr, msaf_timer_delivery_boost, na_sess); if (na_sess->delivery_boost_timer) { ogs_timer_start(na_sess->delivery_boost_timer, ogs_time_from_sec(msaf_self()->config.network_assistance_delivery_boost->delivery_boost_period)); } - if(na_sess->metadata->delivery_boost) + if (na_sess->metadata->delivery_boost) { msaf_event_free(na_sess->metadata->delivery_boost); na_sess->metadata->delivery_boost = NULL; @@ -724,7 +725,7 @@ static void delivery_boost_send_response(msaf_network_assistance_session_t *na_s response = nf_server_new_response(NULL, "application/json", 0, NULL, 0, NULL, na_sess->metadata->delivery_boost->nf_server_interface_metadata, na_sess->metadata->delivery_boost->app_meta); ogs_assert(response); - nf_server_populate_response(response, strlen(success_response), ogs_strdup(success_response), response_code); + nf_server_populate_response(response, strlen(success_response), msaf_strdup(success_response), response_code); ogs_assert(true == ogs_sbi_server_send_response(na_sess->metadata->delivery_boost->h.sbi.data, response)); cJSON_Delete(op_success_response); @@ -734,7 +735,7 @@ static void delivery_boost_send_response(msaf_network_assistance_session_t *na_s } -static bool create_msaf_na_sess_and_send_response(msaf_network_assistance_session_t *na_sess){ +static bool create_msaf_na_sess_and_send_response(msaf_network_assistance_session_t *na_sess) { ogs_uuid_t uuid; char id[OGS_UUID_FORMATTED_LENGTH + 1]; ogs_sbi_response_t *response; @@ -763,7 +764,7 @@ static bool create_msaf_na_sess_and_send_response(msaf_network_assistance_sessio nf_server_populate_response(response, response_body?strlen(response_body):0, msaf_strdup(response_body), response_code); ogs_assert(true == ogs_sbi_server_send_response(na_sess->metadata->create_event->h.sbi.data, response)); - if(na_sess->metadata->create_event) + if (na_sess->metadata->create_event) { msaf_event_free(na_sess->metadata->create_event); na_sess->metadata->create_event = NULL; @@ -802,7 +803,7 @@ static bool app_session_notification_callback(pcf_app_session_t *app_session, co return true; } -static bool bsf_retrieve_pcf_binding_callback(OpenAPI_pcf_binding_t *pcf_binding, void *data){ +static bool bsf_retrieve_pcf_binding_callback(OpenAPI_pcf_binding_t *pcf_binding, void *data) { int rv; int valid_time = 50; ogs_time_t expires; @@ -816,20 +817,20 @@ static bool bsf_retrieve_pcf_binding_callback(OpenAPI_pcf_binding_t *pcf_binding ogs_assert(ue_address); - if(pcf_binding){ + if (pcf_binding) { const ogs_sockaddr_t *pcf_address; expires = ogs_time_now() + ogs_time_from_sec(valid_time); rv = msaf_pcf_cache_add(msaf_self()->pcf_cache, ue_address, (const OpenAPI_pcf_binding_t *)pcf_binding, expires); OpenAPI_pcf_binding_free(pcf_binding); - if (rv != true){ + if (rv != true) { ogs_error("Adding PCF Binding to the cache failed"); retrieve_pcf_binding_cb_data_free(retrieve_pcf_binding_cb_data); return false; } pcf_address = msaf_pcf_cache_find(msaf_self()->pcf_cache, retrieve_pcf_binding_cb_data->ue_connection->address); - if(pcf_address){ + if (pcf_address) { create_pcf_app_session(pcf_address, retrieve_pcf_binding_cb_data->ue_connection, retrieve_pcf_binding_cb_data->media_component, retrieve_pcf_binding_cb_data->na_sess); retrieve_pcf_binding_cb_data_free(retrieve_pcf_binding_cb_data); return true; @@ -878,12 +879,12 @@ static void retrieve_pcf_binding_cb_data_free(retrieve_pcf_binding_cb_data_t *cb static void add_create_event_metadata_to_na_sess_context(msaf_network_assistance_session_t *na_sess, msaf_event_t *e) { - //if(na_sess->metadata->create_event) msaf_event_free(na_sess->metadata->create_event); + //if (na_sess->metadata->create_event) msaf_event_free(na_sess->metadata->create_event); na_sess->metadata = ogs_calloc(1, sizeof(msaf_network_assistance_session_internal_metadata_t)); na_sess->metadata->create_event = e; } -static void add_delivery_boost_event_metadata_to_na_sess_context(msaf_network_assistance_session_t *na_sess, msaf_event_t *e){ +static void add_delivery_boost_event_metadata_to_na_sess_context(msaf_network_assistance_session_t *na_sess, msaf_event_t *e) { na_sess->metadata->delivery_boost = e; } @@ -909,7 +910,7 @@ static char *flow_description_protocol_to_string(int protocol) static char *flow_description_port(int port) { - if (port == 0) return ogs_strdup(""); + if (port == 0) return msaf_strdup(""); return ogs_msprintf(" %d", port); } diff --git a/src/5gmsaf/network-assistance-session.h b/src/5gmsaf/network-assistance-session.h index daabdce..705f545 100644 --- a/src/5gmsaf/network-assistance-session.h +++ b/src/5gmsaf/network-assistance-session.h @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef MSAF_NETWORK_ASSISTANCE_SESSION_H #define MSAF_NETWORK_ASSISTANCE_SESSION_H @@ -31,11 +31,11 @@ typedef struct msaf_event_s msaf_event_t; typedef struct msaf_network_assistance_session_internal_metadata_s { msaf_event_t *create_event; - msaf_event_t *delivery_boost; + msaf_event_t *delivery_boost; } msaf_network_assistance_session_internal_metadata_t; typedef struct msaf_network_assistance_session_s { - ogs_lnode_t node; + ogs_lnode_t node; char *naSessionId; msaf_network_assistance_session_internal_metadata_t *metadata; msaf_api_network_assistance_session_t *NetworkAssistanceSession; diff --git a/src/5gmsaf/openapi-extra-yaml/Maf_Management.yaml b/src/5gmsaf/openapi-extra-yaml/Maf_Management.yaml new file mode 100644 index 0000000..6e24007 --- /dev/null +++ b/src/5gmsaf/openapi-extra-yaml/Maf_Management.yaml @@ -0,0 +1,43 @@ +openapi: 3.0.0 +info: + title: Maf_Management + version: 1.0.0 + description: | + 5GMS AF Maf_Management API + Copyright © 2023 British Broadcasting Corporation. + All rights reserved. +tags: + - name: Maf_Management + description: '5G Media Streaming: AF Management API' +externalDocs: + description: '5G-MAG Reference Tools - 5GMS Application Function' + url: 'https://github.com/5G-MAG/rt-common-shared/tree/main/5gms/5G_APIs-overrides' +servers: + - url: '{apiRoot}/5gmag-rt-management/v1' + variables: + apiRoot: + default: https://example.com + description: See https://github.com/5G-MAG/rt-5gms-application-function/. +paths: + /provisioning-sessions: + get: + operationId: enumerateProvisioningSessions + summary: 'Retrieve a list of current Provisioning Sessions.' + responses: + '200': + description: 'Success' + content: + application/json: + schema: + type: array + description: 'A list of current Provisioning Session resource identifiers.' + items: + $ref: 'TS26512_CommonData.yaml#/components/schemas/ResourceId' + '500': + # Internal Server Error + $ref: 'TS29571_CommonData.yaml#/components/responses/500' + '503': + # Service Unavailable + $ref: 'TS29571_CommonData.yaml#/components/responses/503' + default: + $ref: 'TS29571_CommonData.yaml#/components/responses/default' diff --git a/src/5gmsaf/pcf-cache.c b/src/5gmsaf/pcf-cache.c index 5d259e4..2880a13 100644 --- a/src/5gmsaf/pcf-cache.c +++ b/src/5gmsaf/pcf-cache.c @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: David Waring -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: David Waring + * Copyright: (C) 2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include "ogs-core.h" #include "ogs-sbi.h" @@ -68,7 +68,7 @@ bool msaf_pcf_cache_add(msaf_pcf_cache_t *cache, const ogs_sockaddr_t *ue_addres const ogs_sockaddr_t *msaf_pcf_cache_find(msaf_pcf_cache_t *cache, const ogs_sockaddr_t *ue_address) { msaf_pcf_cache_entry_t *entry; - + entry = ogs_hash_get(cache, ue_address, sizeof(*ue_address)); if (!entry) return NULL; diff --git a/src/5gmsaf/pcf-cache.h b/src/5gmsaf/pcf-cache.h index 948409f..8ee5a18 100644 --- a/src/5gmsaf/pcf-cache.h +++ b/src/5gmsaf/pcf-cache.h @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: David Waring -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: David Waring + * Copyright: (C) 2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef MSAF_PCF_CACHE_H #define MSAF_PCF_CACHE_H diff --git a/src/5gmsaf/pcf-session.c b/src/5gmsaf/pcf-session.c index ab517df..eb84720 100644 --- a/src/5gmsaf/pcf-session.c +++ b/src/5gmsaf/pcf-session.c @@ -1,12 +1,13 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Authors: Dev Audsin + * David Waring + * Copyright: (C) 2023-2024 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include "context.h" #include "pcf-session.h" @@ -15,7 +16,7 @@ static void msaf_pcf_session_remove(msaf_pcf_session_t *pcf_sess); pcf_session_t *msaf_pcf_session_new(const ogs_sockaddr_t *pcf_address) { - msaf_pcf_session_t *msaf_pcf_session; + msaf_pcf_session_t *msaf_pcf_session; msaf_pcf_session = ogs_calloc(1, sizeof(msaf_pcf_session_t)); msaf_pcf_session->pcf_session = pcf_session_new(pcf_address); ogs_list_add(&msaf_self()->pcf_sessions, msaf_pcf_session); @@ -26,8 +27,8 @@ void msaf_pcf_session_remove_all() { msaf_pcf_session_t *msaf_pcf_session = NULL, *next = NULL; - ogs_list_for_each_safe(&msaf_self()->pcf_sessions, next, msaf_pcf_session){ - ogs_list_remove(&msaf_self()->pcf_sessions, msaf_pcf_session); + ogs_list_for_each_safe(&msaf_self()->pcf_sessions, next, msaf_pcf_session) { + ogs_list_remove(&msaf_self()->pcf_sessions, msaf_pcf_session); msaf_pcf_session_remove(msaf_pcf_session); } } diff --git a/src/5gmsaf/pcf-session.h b/src/5gmsaf/pcf-session.h index c2ca4c5..1f7a60a 100644 --- a/src/5gmsaf/pcf-session.h +++ b/src/5gmsaf/pcf-session.h @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef MSAF_PCF_SESSION_H #define MSAF_PCF_SESSION_H @@ -19,7 +19,7 @@ extern "C" { #endif typedef struct msaf_pcf_session_s { - ogs_lnode_t node; + ogs_lnode_t node; pcf_session_t *pcf_session; } msaf_pcf_session_t; diff --git a/src/5gmsaf/policy-template.c b/src/5gmsaf/policy-template.c index 81dea2e..d10003d 100644 --- a/src/5gmsaf/policy-template.c +++ b/src/5gmsaf/policy-template.c @@ -1,11 +1,13 @@ /* -License: 5G-MAG Public License (v1.0) -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Authors: Dev Audsin + * David Waring + * Copyright: (C) 2023-2024 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include "context.h" #include "policy-template.h" @@ -25,8 +27,8 @@ msaf_api_policy_template_t *msaf_policy_template_parseFromJSON(cJSON *policy_tem void msaf_policy_template_set_id(msaf_api_policy_template_t *policy_template, const char *policy_template_id) { ogs_assert(policy_template); - if(policy_template->policy_template_id) ogs_free(policy_template->policy_template_id); - policy_template->policy_template_id = policy_template_id?ogs_strdup(policy_template_id):NULL; + if (policy_template->policy_template_id) ogs_free(policy_template->policy_template_id); + policy_template->policy_template_id = msaf_strdup(policy_template_id); } char *calculate_policy_template_hash(msaf_api_policy_template_t *policy_template) @@ -63,49 +65,49 @@ bool msaf_policy_template_set_state(msaf_api_policy_template_t *policy_template, ogs_assert(policy_template); ogs_assert(provisioning_session); - if(policy_template->state == msaf_api_policy_template_STATE_NULL) { - if(new_state == msaf_api_policy_template_STATE_NULL) return false; + if (policy_template->state == msaf_api_policy_template_STATE_NULL) { + if (new_state == msaf_api_policy_template_STATE_NULL) return false; - if(new_state == msaf_api_policy_template_STATE_PENDING) { - policy_template->state = msaf_api_policy_template_STATE_PENDING; + if (new_state == msaf_api_policy_template_STATE_VAL_PENDING) { + policy_template->state = msaf_api_policy_template_STATE_VAL_PENDING; msaf_policy_template_set_state_reason(policy_template, NULL, NULL, NULL, NULL, NULL, NULL, NULL); return true; } - if(new_state == msaf_api_policy_template_STATE_READY || new_state == msaf_api_policy_template_STATE_INVALID || new_state == msaf_api_policy_template_STATE_SUSPENDED) { - ogs_error("Invalid state change"); + if (new_state == msaf_api_policy_template_STATE_VAL_READY || new_state == msaf_api_policy_template_STATE_VAL_INVALID || new_state == msaf_api_policy_template_STATE_VAL_SUSPENDED) { + ogs_error("Invalid state change"); return false; - } + } } - if(policy_template->state == msaf_api_policy_template_STATE_PENDING) { + if (policy_template->state == msaf_api_policy_template_STATE_VAL_PENDING) { - if(new_state == msaf_api_policy_template_STATE_NULL) { + if (new_state == msaf_api_policy_template_STATE_NULL) { policy_template->state = msaf_api_policy_template_STATE_NULL; - msaf_policy_template_set_state_reason(policy_template, NULL, NULL, NULL, NULL, NULL, NULL, NULL); - return true; + msaf_policy_template_set_state_reason(policy_template, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + return true; } - if(new_state == msaf_api_policy_template_STATE_PENDING) return false; + if (new_state == msaf_api_policy_template_STATE_VAL_PENDING) return false; - if(new_state == msaf_api_policy_template_STATE_READY) { - if(provisioning_session->sai_cache) + if (new_state == msaf_api_policy_template_STATE_VAL_READY) { + if (provisioning_session->sai_cache) msaf_sai_cache_clear(provisioning_session->sai_cache); - policy_template->state = msaf_api_policy_template_STATE_READY; + policy_template->state = msaf_api_policy_template_STATE_VAL_READY; msaf_policy_template_set_state_reason(policy_template, NULL, NULL, NULL, NULL, NULL, NULL, NULL); - return true; + return true; } - - if(new_state == msaf_api_policy_template_STATE_INVALID) { - char *detail = "Policy template state transitioned from PENDING to INVALID."; - char *title = "Provider decision."; - policy_template->state = msaf_api_policy_template_STATE_INVALID; - msaf_policy_template_set_state_reason(policy_template, NULL, msaf_strdup(detail), NULL, NULL, NULL, msaf_strdup(title), NULL); + + if (new_state == msaf_api_policy_template_STATE_VAL_INVALID) { + char *detail = "Policy template state transitioned from PENDING to INVALID."; + char *title = "Provider decision."; + policy_template->state = msaf_api_policy_template_STATE_VAL_INVALID; + msaf_policy_template_set_state_reason(policy_template, NULL, msaf_strdup(detail), NULL, NULL, NULL, msaf_strdup(title), NULL); return true; } - - if(new_state == msaf_api_policy_template_STATE_SUSPENDED) { + + if (new_state == msaf_api_policy_template_STATE_VAL_SUSPENDED) { ogs_error("Invalid state change"); return false; } @@ -113,84 +115,84 @@ bool msaf_policy_template_set_state(msaf_api_policy_template_t *policy_template, } - if(policy_template->state == msaf_api_policy_template_STATE_READY) { + if (policy_template->state == msaf_api_policy_template_STATE_VAL_READY) { - if(new_state == msaf_api_policy_template_STATE_NULL) { - if (provisioning_session->sai_cache) + if (new_state == msaf_api_policy_template_STATE_NULL) { + if (provisioning_session->sai_cache) msaf_sai_cache_clear(provisioning_session->sai_cache); policy_template->state = msaf_api_policy_template_STATE_NULL; msaf_policy_template_set_state_reason(policy_template, NULL, NULL, NULL, NULL, NULL, NULL, NULL); return true; - } + } - if(new_state == msaf_api_policy_template_STATE_PENDING) { - if(provisioning_session->sai_cache) + if (new_state == msaf_api_policy_template_STATE_VAL_PENDING) { + if (provisioning_session->sai_cache) msaf_sai_cache_clear(provisioning_session->sai_cache); - policy_template->state = msaf_api_policy_template_STATE_PENDING; + policy_template->state = msaf_api_policy_template_STATE_VAL_PENDING; msaf_policy_template_set_state_reason(policy_template, NULL, NULL, NULL, NULL, NULL, NULL, NULL); return true; } - - if(new_state == msaf_api_policy_template_STATE_READY) return false; - - if(new_state == msaf_api_policy_template_STATE_INVALID) { + + if (new_state == msaf_api_policy_template_STATE_VAL_READY) return false; + + if (new_state == msaf_api_policy_template_STATE_VAL_INVALID) { ogs_error("Invalid state change"); return false; } - - if(new_state == msaf_api_policy_template_STATE_SUSPENDED) { - char *detail = "Policy template state transitioned from READY to SUSPENDED."; - char *title = "Operator Decision."; - if (provisioning_session->sai_cache) - msaf_sai_cache_clear(provisioning_session->sai_cache); - policy_template->state = msaf_api_policy_template_STATE_SUSPENDED; - msaf_policy_template_set_state_reason(policy_template, NULL, msaf_strdup(detail), NULL, NULL, NULL, msaf_strdup(title), NULL); + + if (new_state == msaf_api_policy_template_STATE_VAL_SUSPENDED) { + char *detail = "Policy template state transitioned from READY to SUSPENDED."; + char *title = "Operator Decision."; + if (provisioning_session->sai_cache) + msaf_sai_cache_clear(provisioning_session->sai_cache); + policy_template->state = msaf_api_policy_template_STATE_VAL_SUSPENDED; + msaf_policy_template_set_state_reason(policy_template, NULL, msaf_strdup(detail), NULL, NULL, NULL, msaf_strdup(title), NULL); return true; } } - if(policy_template->state == msaf_api_policy_template_STATE_INVALID) { - - if(new_state == msaf_api_policy_template_STATE_NULL) { + if (policy_template->state == msaf_api_policy_template_STATE_VAL_INVALID) { + + if (new_state == msaf_api_policy_template_STATE_NULL) { policy_template->state = msaf_api_policy_template_STATE_NULL; msaf_policy_template_set_state_reason(policy_template, NULL, NULL, NULL, NULL, NULL, NULL, NULL); - return true; + return true; } - if(new_state == msaf_api_policy_template_STATE_PENDING) { - policy_template->state = msaf_api_policy_template_STATE_PENDING; + if (new_state == msaf_api_policy_template_STATE_VAL_PENDING) { + policy_template->state = msaf_api_policy_template_STATE_VAL_PENDING; msaf_policy_template_set_state_reason(policy_template, NULL, NULL, NULL, NULL, NULL, NULL, NULL); - return true; + return true; } - if(new_state == msaf_api_policy_template_STATE_READY || new_state == msaf_api_policy_template_STATE_SUSPENDED) { + if (new_state == msaf_api_policy_template_STATE_VAL_READY || new_state == msaf_api_policy_template_STATE_VAL_SUSPENDED) { ogs_error("Invalid state change"); return false; } - if(new_state == msaf_api_policy_template_STATE_INVALID) return false; + if (new_state == msaf_api_policy_template_STATE_VAL_INVALID) return false; } - if(policy_template->state == msaf_api_policy_template_STATE_SUSPENDED) { - - if(new_state == msaf_api_policy_template_STATE_NULL) { + if (policy_template->state == msaf_api_policy_template_STATE_VAL_SUSPENDED) { + + if (new_state == msaf_api_policy_template_STATE_NULL) { policy_template->state = msaf_api_policy_template_STATE_NULL; msaf_policy_template_set_state_reason(policy_template, NULL, NULL, NULL, NULL, NULL, NULL, NULL); - return true; + return true; } - if(new_state == msaf_api_policy_template_STATE_PENDING) { - policy_template->state = msaf_api_policy_template_STATE_PENDING; + if (new_state == msaf_api_policy_template_STATE_VAL_PENDING) { + policy_template->state = msaf_api_policy_template_STATE_VAL_PENDING; msaf_policy_template_set_state_reason(policy_template, NULL, NULL, NULL, NULL, NULL, NULL, NULL); - return true; + return true; } - if(new_state == msaf_api_policy_template_STATE_READY || (new_state == msaf_api_policy_template_STATE_INVALID)) { + if (new_state == msaf_api_policy_template_STATE_VAL_READY || (new_state == msaf_api_policy_template_STATE_VAL_INVALID)) { ogs_error("Invalid state change"); return false; } - if(new_state == msaf_api_policy_template_STATE_SUSPENDED) return false; + if (new_state == msaf_api_policy_template_STATE_VAL_SUSPENDED) return false; } return false; @@ -217,7 +219,7 @@ OpenAPI_list_t *get_id_of_policy_templates_in_ready_state(ogs_hash_t *policy_tem for (hi = ogs_hash_first(policy_templates); hi; hi = ogs_hash_next(hi)) { policy_template_node = (msaf_policy_template_node_t *)ogs_hash_this_val(hi); - if (policy_template_node->policy_template->state == msaf_api_policy_template_STATE_READY) { + if (policy_template_node->policy_template->state == msaf_api_policy_template_STATE_VAL_READY) { OpenAPI_list_add(valid_policy_template_ids, msaf_strdup(policy_template_node->policy_template->policy_template_id)); } } @@ -235,7 +237,7 @@ OpenAPI_list_t *get_external_reference_of_policy_templates_in_ready_state(ogs_ha for (hi = ogs_hash_first(policy_templates); hi; hi = ogs_hash_next(hi)) { policy_template_node = (msaf_policy_template_node_t *)ogs_hash_this_val(hi); - if (policy_template_node->policy_template->state == msaf_api_policy_template_STATE_READY) { + if (policy_template_node->policy_template->state == msaf_api_policy_template_STATE_VAL_READY) { OpenAPI_list_add(external_references, msaf_strdup(policy_template_node->policy_template->external_reference)); } } @@ -275,7 +277,7 @@ void msaf_policy_template_node_free(msaf_policy_template_node_t *node) static void msaf_policy_template_set_state_reason(msaf_api_policy_template_t *policy_template, char *cause, char *detail, char *instance, char *nrf_id, char *supported_features, char *title, char *type) { - if(policy_template->state_reason) msaf_api_problem_details_free(policy_template->state_reason); + if (policy_template->state_reason) msaf_api_problem_details_free(policy_template->state_reason); policy_template->state_reason = msaf_api_problem_details_create(NULL, NULL, cause, detail, instance, NULL, nrf_id, 0, 0, supported_features, title, type); } diff --git a/src/5gmsaf/policy-template.h b/src/5gmsaf/policy-template.h index bb9c244..df9ebf0 100644 --- a/src/5gmsaf/policy-template.h +++ b/src/5gmsaf/policy-template.h @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef MSAF_POLICY_TEMPLATE_H #define MSAF_POLICY_TEMPLATE_H @@ -44,7 +44,7 @@ extern bool msaf_policy_template_clear(ogs_hash_t *policy_templates); extern void msaf_policy_template_node_free(msaf_policy_template_node_t *node); cJSON *msaf_policy_template_convert_to_json(msaf_api_policy_template_t *policy_template); - + #ifdef __cplusplus } #endif diff --git a/src/5gmsaf/provisioning-session.c b/src/5gmsaf/provisioning-session.c index e122298..816ebed 100644 --- a/src/5gmsaf/provisioning-session.c +++ b/src/5gmsaf/provisioning-session.c @@ -1,12 +1,13 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022-2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Authors: Dev Audsin + * David Waring + * Copyright: (C) 2022-2024 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include #include "application-server-context.h" @@ -15,6 +16,7 @@ program. If this file is missing then the license can be retrieved from #include "context.h" #include "utilities.h" #include "hash.h" +#include "metrics-reporting-configuration.h" #include "sai-cache.h" #include "openapi/model/msaf_api_consumption_reporting_configuration.h" @@ -115,6 +117,7 @@ msaf_provisioning_session_create(const char *provisioning_session_type, const ch msaf_provisioning_session->httpMetadata.provisioningSession.received = time(NULL); msaf_provisioning_session->httpMetadata.provisioningSession.hash = calculate_provisioning_session_hash(provisioning_session); + msaf_provisioning_session->metrics_reporting_map = msaf_metrics_reporting_map(); msaf_provisioning_session->certificate_map = msaf_certificate_map(); msaf_provisioning_session->policy_templates = msaf_policy_templates_new(); ogs_hash_set(msaf_self()->provisioningSessions_map, msaf_strdup(msaf_provisioning_session->provisioningSessionId), OGS_HASH_KEY_STRING, msaf_provisioning_session); @@ -150,10 +153,10 @@ void msaf_provisioning_session_free(msaf_provisioning_session_t *provisioning_se safe_ogs_free(provisioning_session->httpMetadata.contentHostingConfiguration.hash); msaf_consumption_report_configuration_deregister(provisioning_session); - if(provisioning_session->sai_cache) + if (provisioning_session->sai_cache) msaf_sai_cache_free(provisioning_session->sai_cache); - if(provisioning_session->policy_templates) + if (provisioning_session->policy_templates) msaf_provisioning_session_policy_template_free(provisioning_session->policy_templates); ogs_list_for_each_safe(&provisioning_session->application_server_states, next_as_state_ref, as_state_ref) { @@ -161,6 +164,16 @@ void msaf_provisioning_session_free(msaf_provisioning_session_t *provisioning_se ogs_free(as_state_ref); } + if (provisioning_session->metrics_reporting_map) { + free_ogs_hash_context_t fohc = { + (void(*)(void*))msaf_metrics_reporting_configuration_free, + provisioning_session->metrics_reporting_map + }; + ogs_hash_do(free_ogs_hash_entry, &fohc, provisioning_session->metrics_reporting_map); + ogs_hash_destroy(provisioning_session->metrics_reporting_map); + provisioning_session->metrics_reporting_map = NULL; + } + ogs_free(provisioning_session); } @@ -175,7 +188,6 @@ msaf_provisioning_session_get_json(const char *provisioning_session_id) if (msaf_provisioning_session) { msaf_api_provisioning_session_t *provisioning_session; - ogs_hash_index_t *cert_node; provisioning_session = ogs_calloc(1,sizeof(*provisioning_session)); ogs_assert(provisioning_session); @@ -185,10 +197,21 @@ msaf_provisioning_session_get_json(const char *provisioning_session_id) provisioning_session->asp_id = msaf_provisioning_session->aspId; provisioning_session->app_id = msaf_provisioning_session->appId; - provisioning_session->server_certificate_ids = (OpenAPI_set_t*)OpenAPI_list_create(); - for (cert_node=ogs_hash_first(msaf_provisioning_session->certificate_map); cert_node; cert_node=ogs_hash_next(cert_node)) { - ogs_debug("msaf_provisioning_session_get_json: Add cert %s", (const char *)ogs_hash_this_key(cert_node)); - OpenAPI_list_add(provisioning_session->server_certificate_ids, (void*)ogs_hash_this_key(cert_node)); + if (msaf_provisioning_session->certificate_map && ogs_hash_first(msaf_provisioning_session->certificate_map) != NULL) { + ogs_hash_index_t *cert_node; + provisioning_session->server_certificate_ids = (OpenAPI_set_t*)OpenAPI_list_create(); + for (cert_node=ogs_hash_first(msaf_provisioning_session->certificate_map); cert_node; cert_node=ogs_hash_next(cert_node)) { + ogs_debug("msaf_provisioning_session_get_json: Add cert %s", (const char *)ogs_hash_this_key(cert_node)); + OpenAPI_list_add(provisioning_session->server_certificate_ids, (void*)ogs_hash_this_key(cert_node)); + } + } + + if (msaf_provisioning_session->metrics_reporting_map && ogs_hash_first(msaf_provisioning_session->metrics_reporting_map) != NULL) { + ogs_hash_index_t *metrics_node; + provisioning_session->metrics_reporting_configuration_ids = (OpenAPI_set_t*)OpenAPI_list_create(); + for (metrics_node=ogs_hash_first(msaf_provisioning_session->metrics_reporting_map); metrics_node; metrics_node = ogs_hash_next(metrics_node)) { + OpenAPI_list_add(provisioning_session->metrics_reporting_configuration_ids, (void*)ogs_hash_this_key(metrics_node)); + } } if (msaf_provisioning_session->policy_templates && ogs_hash_first(msaf_provisioning_session->policy_templates) != NULL) { @@ -204,6 +227,7 @@ msaf_provisioning_session_get_json(const char *provisioning_session_id) OpenAPI_list_free(provisioning_session->server_certificate_ids); OpenAPI_list_free(provisioning_session->policy_template_ids); + OpenAPI_list_free(provisioning_session->metrics_reporting_configuration_ids); ogs_free(provisioning_session); } else { ogs_error("Unable to retrieve Provisioning Session [%s]", provisioning_session_id); @@ -255,14 +279,14 @@ msaf_delete_certificates(const char *provisioning_session_id) if (as_state->current_certificates) { resource_id_node_t *certificate, *next; - ogs_list_for_each_safe(as_state->current_certificates, next, certificate){ + ogs_list_for_each_safe(as_state->current_certificates, next, certificate) { char *cert_id; char *provisioning_session; char *current_cert_id = msaf_strdup(certificate->state); provisioning_session = strtok_r(current_cert_id,":",&cert_id); - if(!strcmp(provisioning_session, provisioning_session_id)) { + if (!strcmp(provisioning_session, provisioning_session_id)) { /* provisioning session matches */ resource_id_node_t *delete_cert; delete_cert = ogs_calloc(1, sizeof(resource_id_node_t)); @@ -271,7 +295,7 @@ msaf_delete_certificates(const char *provisioning_session_id) ogs_list_add(&as_state->delete_certificates, delete_cert); } - if(current_cert_id) + if (current_cert_id) ogs_free(current_cert_id); } } @@ -307,7 +331,7 @@ msaf_delete_content_hosting_configuration(const char *provisioning_session_id) if (as_state->current_content_hosting_configurations) { - ogs_list_for_each_safe(as_state->current_content_hosting_configurations, next, content_hosting_configuration){ + ogs_list_for_each_safe(as_state->current_content_hosting_configurations, next, content_hosting_configuration) { if (!strcmp(content_hosting_configuration->state, provisioning_session_id)) break; @@ -321,7 +345,7 @@ msaf_delete_content_hosting_configuration(const char *provisioning_session_id) } } - ogs_list_for_each_safe(&as_state->upload_content_hosting_configurations, next_node, upload_content_hosting_configuration){ + ogs_list_for_each_safe(&as_state->upload_content_hosting_configurations, next_node, upload_content_hosting_configuration) { if (!strcmp(upload_content_hosting_configuration->state, provisioning_session_id)) break; } @@ -348,7 +372,7 @@ msaf_provisioning_session_find_by_provisioningSessionId(const char *provisioning msaf_policy_template_node_t * msaf_provisioning_session_find_policy_template_by_id(msaf_provisioning_session_t *provisioning_session, const char *policy_template_id) { - if(!provisioning_session->policy_templates) return NULL; + if (!provisioning_session->policy_templates) return NULL; return (msaf_policy_template_node_t *) ogs_hash_get(provisioning_session->policy_templates, policy_template_id, OGS_HASH_KEY_STRING); } @@ -356,7 +380,7 @@ msaf_policy_template_node_t *msaf_provisioning_session_get_policy_template_by_id msaf_provisioning_session_t *provisioning_session; provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId(provisioning_session_id); - if(!provisioning_session) return NULL; + if (!provisioning_session) return NULL; return msaf_provisioning_session_find_policy_template_by_id(provisioning_session, policy_template_id); } @@ -392,7 +416,7 @@ msaf_retrieve_certificates_from_map(msaf_provisioning_session_t *provisioning_se dist_config = (msaf_api_distribution_configuration_t*)dist_config_node->data; if (dist_config->certificate_id) { const char *cert = ogs_hash_get(provisioning_session->certificate_map, dist_config->certificate_id, OGS_HASH_KEY_STRING); - if (cert){ + if (cert) { certificate = ogs_calloc(1, sizeof(resource_id_node_t)); ogs_assert(certificate); char *provisioning_session_id_plus_cert_id = ogs_msprintf("%s:%s", provisioning_session->provisioningSessionId, dist_config->certificate_id); @@ -451,7 +475,7 @@ msaf_distribution_create(cJSON *content_hosting_config, msaf_provisioning_sessio dist_config = (msaf_api_distribution_configuration_t*)dist_config_node->data; - if(dist_config->entry_point && !uri_relative_check(dist_config->entry_point->relative_path)) { + if (dist_config->entry_point && !uri_relative_check(dist_config->entry_point->relative_path)) { if (reason_ret) *reason_ret = "distributionConfiguration.entryPoint.relativePath malformed"; ogs_error("distributionConfiguration.entryPoint.relativePath malformed for Provisioning Session [%s]", provisioning_session->provisioningSessionId); cJSON_Delete(content_hosting_config); @@ -477,16 +501,16 @@ msaf_distribution_create(cJSON *content_hosting_config, msaf_provisioning_sessio protocol = "https"; } - if(dist_config->domain_name_alias){ + if (dist_config->domain_name_alias) { domain_name = dist_config->domain_name_alias; } else { domain_name = dist_config->canonical_domain_name; } - if(dist_config->base_url) ogs_free(dist_config->base_url); + if (dist_config->base_url) ogs_free(dist_config->base_url); dist_config->base_url = ogs_msprintf("%s://%s%s", protocol, domain_name, url_path); - } + } } else { ogs_error("The Content Hosting Configuration has no distributionConfigurations for Provisioning Session [%s]", provisioning_session->provisioningSessionId); } @@ -519,7 +543,7 @@ cJSON *msaf_get_content_hosting_configuration_by_provisioning_session_id(const c msaf_provisioning_session = msaf_provisioning_session_find_by_provisioningSessionId(provisioning_session_id); - if(msaf_provisioning_session && msaf_provisioning_session->contentHostingConfiguration) + if (msaf_provisioning_session && msaf_provisioning_session->contentHostingConfiguration) { content_hosting_configuration_json = msaf_api_content_hosting_configuration_convertResponseToJSON(msaf_provisioning_session->contentHostingConfiguration); } else { @@ -623,7 +647,7 @@ char *enumerate_provisioning_sessions(void) } bool msaf_provisioning_session_add_policy_template(msaf_provisioning_session_t *provisioning_session, msaf_api_policy_template_t *policy_template, time_t creation_time) { - + ogs_uuid_t uuid; char id[OGS_UUID_FORMATTED_LENGTH + 1]; msaf_policy_template_node_t *msaf_policy_template; @@ -632,13 +656,13 @@ bool msaf_provisioning_session_add_policy_template(msaf_provisioning_session_t * ogs_uuid_format(id, &uuid); msaf_policy_template_set_id(policy_template, id); - + msaf_policy_template = msaf_policy_template_populate(policy_template, creation_time); - if(!msaf_policy_template) return false; + if (!msaf_policy_template) return false; ogs_hash_set(provisioning_session->policy_templates, msaf_strdup(id), OGS_HASH_KEY_STRING, msaf_policy_template); - if(!msaf_provisioning_session_send_policy_template_state_change_event(provisioning_session, msaf_policy_template, msaf_api_policy_template_STATE_PENDING, NULL, NULL)) + if (!msaf_provisioning_session_send_policy_template_state_change_event(provisioning_session, msaf_policy_template, msaf_api_policy_template_STATE_VAL_PENDING, NULL, NULL)) return false; return true; @@ -646,8 +670,8 @@ bool msaf_provisioning_session_add_policy_template(msaf_provisioning_session_t * } bool msaf_provisioning_session_update_policy_template(msaf_provisioning_session_t *provisioning_session, msaf_policy_template_node_t *msaf_policy_template, msaf_api_policy_template_t *policy_template) { - - char *policy_template_id; + + char *policy_template_id; ogs_assert(provisioning_session); ogs_assert(msaf_policy_template); ogs_assert(policy_template); @@ -657,7 +681,7 @@ bool msaf_provisioning_session_update_policy_template(msaf_provisioning_session_ msaf_policy_template_free(msaf_policy_template->policy_template); msaf_policy_template->policy_template = policy_template; msaf_policy_template->policy_template->policy_template_id = policy_template_id; - if(!msaf_provisioning_session_send_policy_template_state_change_event(provisioning_session, msaf_policy_template, msaf_api_policy_template_STATE_PENDING, NULL, NULL)) + if (!msaf_provisioning_session_send_policy_template_state_change_event(provisioning_session, msaf_policy_template, msaf_api_policy_template_STATE_VAL_PENDING, NULL, NULL)) return false; return true; @@ -666,7 +690,7 @@ bool msaf_provisioning_session_update_policy_template(msaf_provisioning_session_ bool msaf_provisioning_session_send_policy_template_state_change_event(msaf_provisioning_session_t *provisioning_session, msaf_policy_template_node_t *policy_template, msaf_api_policy_template_state_e new_state, msaf_policy_template_state_change_callback callback, void *user_data) { msaf_event_t *event; - int rv; + int rv; ogs_assert(provisioning_session); ogs_assert(policy_template); @@ -687,9 +711,9 @@ bool msaf_provisioning_session_send_policy_template_state_change_event(msaf_prov bool msaf_provisioning_session_delete_policy_template(msaf_provisioning_session_t *provisioning_session, msaf_policy_template_node_t *policy_template) { - if(!policy_template || !policy_template->policy_template) return false; + if (!policy_template || !policy_template->policy_template) return false; - if(!msaf_provisioning_session_delete_policy_template_by_id(provisioning_session, policy_template->policy_template->policy_template_id)) + if (!msaf_provisioning_session_delete_policy_template_by_id(provisioning_session, policy_template->policy_template->policy_template_id)) return false; return true; } @@ -753,8 +777,8 @@ static msaf_policy_template_change_state_event_data_t *msaf_policy_template_chan msaf_policy_template_change_state_event_data->new_state = new_state; msaf_policy_template_change_state_event_data->callback = callback; msaf_policy_template_change_state_event_data->callback_user_data = user_data; - - return msaf_policy_template_change_state_event_data; + + return msaf_policy_template_change_state_event_data; } @@ -763,9 +787,9 @@ static void msaf_provisioning_session_policy_template_delete(msaf_provisioning_s ogs_assert(msaf_provisioning_session); ogs_assert(policy_template_node); ogs_assert(user_data); - + ogs_hash_do(free_ogs_hash_provisioning_session_policy_template, user_data, msaf_provisioning_session->policy_templates); -} +} static int free_ogs_hash_provisioning_session_policy_template(void *rec, const void *key, int klen, const void *value) @@ -898,8 +922,8 @@ static int free_ogs_hash_entry(void *rec, const void *key, int klen, const void *value) { free_ogs_hash_context_t *fohc = (free_ogs_hash_context_t*)rec; - fohc->value_free_fn((void*)value); ogs_hash_set(fohc->hash, key, klen, NULL); + fohc->value_free_fn((void*)value); ogs_free((void*)key); return 1; } diff --git a/src/5gmsaf/provisioning-session.h b/src/5gmsaf/provisioning-session.h index 64d88ea..880abff 100644 --- a/src/5gmsaf/provisioning-session.h +++ b/src/5gmsaf/provisioning-session.h @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022-2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022-2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef MSAF_PROVISIONING_SESSION_H #define MSAF_PROVISIONING_SESSION_H @@ -17,6 +17,7 @@ program. If this file is missing then the license can be retrieved from #include "openapi/model/msaf_api_provisioning_session_type.h" #include "openapi/model/msaf_api_policy_template.h" +#include "openapi/model/msaf_api_metrics_reporting_configuration.h" #ifdef __cplusplus extern "C" { @@ -51,6 +52,7 @@ typedef struct msaf_provisioning_session_s { } httpMetadata; ogs_hash_t *certificate_map; //Type: char* => n/a (just used as a set - external tool manages data) ogs_hash_t *policy_templates; /* key: policy template id, value: msaf_policy_template_node_t */ + ogs_hash_t *metrics_reporting_map; ogs_list_t application_server_states; //Type: msaf_application_server_state_ref_node_t* int marked_for_deletion; } msaf_provisioning_session_t; diff --git a/src/5gmsaf/response-cache-control.c b/src/5gmsaf/response-cache-control.c index 44a92db..d47147f 100644 --- a/src/5gmsaf/response-cache-control.c +++ b/src/5gmsaf/response-cache-control.c @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include "context.h" #include "response-cache-control.h" @@ -20,6 +20,7 @@ void msaf_server_response_cache_control_set(void) server_response_cache_control->m1_content_hosting_configurations_response_max_age = SERVER_RESPONSE_MAX_AGE; server_response_cache_control->m1_server_certificates_response_max_age = SERVER_RESPONSE_MAX_AGE; server_response_cache_control->m1_content_protocols_response_max_age = M1_CONTENT_PROTOCOLS_RESPONSE_MAX_AGE; + server_response_cache_control->m1_metrics_reporting_response_max_age = SERVER_RESPONSE_MAX_AGE; server_response_cache_control->m1_consumption_reporting_response_max_age = SERVER_RESPONSE_MAX_AGE; server_response_cache_control->m5_service_access_information_response_max_age = SERVER_RESPONSE_MAX_AGE; msaf_self()->config.server_response_cache_control = server_response_cache_control; @@ -27,13 +28,14 @@ void msaf_server_response_cache_control_set(void) void msaf_server_response_cache_control_set_from_config(int m1_provisioning_session_response_max_age, int m1_content_hosting_configurations_response_max_age, int m1_server_certificates_response_max_age, - int m1_content_protocols_response_max_age, int m1_consumption_reporting_response_max_age, + int m1_content_protocols_response_max_age, int m1_metrics_reporting_response_max_age, int m1_consumption_reporting_response_max_age, int m5_service_access_information_response_max_age) { msaf_self()->config.server_response_cache_control->m1_provisioning_session_response_max_age = m1_provisioning_session_response_max_age; msaf_self()->config.server_response_cache_control->m1_content_hosting_configurations_response_max_age = m1_content_hosting_configurations_response_max_age; msaf_self()->config.server_response_cache_control->m1_server_certificates_response_max_age = m1_server_certificates_response_max_age; msaf_self()->config.server_response_cache_control->m1_content_protocols_response_max_age = m1_content_protocols_response_max_age; + msaf_self()->config.server_response_cache_control->m1_metrics_reporting_response_max_age = m1_metrics_reporting_response_max_age; msaf_self()->config.server_response_cache_control->m1_consumption_reporting_response_max_age = m1_consumption_reporting_response_max_age; msaf_self()->config.server_response_cache_control->m5_service_access_information_response_max_age = m5_service_access_information_response_max_age; } diff --git a/src/5gmsaf/response-cache-control.h b/src/5gmsaf/response-cache-control.h index d7ad282..80ee2a4 100644 --- a/src/5gmsaf/response-cache-control.h +++ b/src/5gmsaf/response-cache-control.h @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef MSAF_RESPONSE_CACHE_CONTROL_H #define MSAF_RESPONSE_CACHE_CONTROL_H @@ -25,12 +25,13 @@ typedef struct msaf_server_response_cache_control_s { int m1_content_hosting_configurations_response_max_age; int m1_server_certificates_response_max_age; int m1_content_protocols_response_max_age; + int m1_metrics_reporting_response_max_age; int m1_consumption_reporting_response_max_age; int m5_service_access_information_response_max_age; }msaf_server_response_cache_control_t; extern void msaf_server_response_cache_control_set(void); -extern void msaf_server_response_cache_control_set_from_config(int m1_provisioning_session_response_max_age, int m1_content_hosting_configurations_response_max_age, int m1_server_certificates_response_max_age, int m1_content_protocols_response_max_age, int m1_consumption_reporting_response_max_age, int m5_service_access_information_response_max_age); +extern void msaf_server_response_cache_control_set_from_config(int m1_provisioning_session_response_max_age, int m1_content_hosting_configurations_response_max_age, int m1_server_certificates_response_max_age, int m1_content_protocols_response_max_age, int m1_metrics_reporting_response_max_age, int m1_consumption_reporting_response_max_age, int m5_service_access_information_response_max_age); #ifdef __cplusplus diff --git a/src/5gmsaf/sai-cache.c b/src/5gmsaf/sai-cache.c index 54eedc7..0998749 100644 --- a/src/5gmsaf/sai-cache.c +++ b/src/5gmsaf/sai-cache.c @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: David Waring -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: David Waring + * Copyright: (C) 2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef _GNU_SOURCE #define _GNU_SOURCE diff --git a/src/5gmsaf/sai-cache.h b/src/5gmsaf/sai-cache.h index 63d043f..545d3ae 100644 --- a/src/5gmsaf/sai-cache.h +++ b/src/5gmsaf/sai-cache.h @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: David Waring -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: David Waring + * Copyright: (C) 2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef MSAF_SAI_CACHE_H #define MSAF_SAI_CACHE_H diff --git a/src/5gmsaf/sbi-path.c b/src/5gmsaf/sbi-path.c index 1f05e3f..ed2816f 100644 --- a/src/5gmsaf/sbi-path.c +++ b/src/5gmsaf/sbi-path.c @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include "ogs-sbi.h" #include "sbi-path.h" @@ -53,10 +53,10 @@ int msaf_sbi_open(void) ogs_sbi_nf_fsm_init(nf_instance); } - /* + /* ogs_sbi_subscription_spec_add( OpenAPI_nf_type_BSF, OGS_SBI_SERVICE_NAME_NBSF_MANAGEMENT); - */ + */ if (ogs_sbi_server_start_all(server_cb) != OGS_OK) return OGS_ERROR; diff --git a/src/5gmsaf/sbi-path.h b/src/5gmsaf/sbi-path.h index f023c92..c901564 100644 --- a/src/5gmsaf/sbi-path.h +++ b/src/5gmsaf/sbi-path.h @@ -1,11 +1,11 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view */ #ifndef MSAF_SBI_PATH_H diff --git a/src/5gmsaf/server.c b/src/5gmsaf/server.c index c62ccb9..4baf5cd 100644 --- a/src/5gmsaf/server.c +++ b/src/5gmsaf/server.c @@ -1,12 +1,13 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Authors: Dev Audsin + * David Waring + * Copyright: (C) 2022-2024 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include "ogs-sbi.h" #include "server.h" @@ -23,8 +24,8 @@ static bool nf_build_content(ogs_sbi_http_message_t *http, ogs_sbi_message_t *me static char *nf_build_json(ogs_sbi_message_t *message); -ogs_sbi_response_t *nf_server_new_response(char *location, char *content_type, time_t last_modified, char *etag, - int cache_control, char *allow_methods, const nf_server_interface_metadata_t *interface, +ogs_sbi_response_t *nf_server_new_response(const char *location, const char *content_type, time_t last_modified, const char *etag, + int cache_control, const char *allow_methods, const nf_server_interface_metadata_t *interface, const nf_server_app_metadata_t *app) { ogs_sbi_response_t *response = NULL; @@ -34,29 +35,29 @@ ogs_sbi_response_t *nf_server_new_response(char *location, char *content_type, t response = ogs_sbi_response_new(); ogs_expect(response); - if(content_type) + if (content_type) { ogs_sbi_header_set(response->http.headers, "Content-Type", content_type); } - if(location) + if (location) { ogs_sbi_header_set(response->http.headers, "Location", location); } - if(last_modified) + if (last_modified) { ogs_sbi_header_set(response->http.headers, "Last-Modified", get_time(last_modified)); } - if(etag) + if (etag) { ogs_sbi_header_set(response->http.headers, "ETag", etag); } - if(cache_control) + if (cache_control) { char *response_cache_control; response_cache_control = ogs_msprintf("max-age=%d", cache_control); @@ -64,7 +65,7 @@ ogs_sbi_response_t *nf_server_new_response(char *location, char *content_type, t ogs_free(response_cache_control); } - if(allow_methods) + if (allow_methods) { ogs_sbi_header_set(response->http.headers, "Allow", allow_methods); @@ -128,7 +129,7 @@ bool nf_server_send_error(ogs_sbi_stream_t *stream, memset(&problem, 0, sizeof(problem)); - if(problem_detail) { + if (problem_detail) { problem_details = OpenAPI_problem_details_parseFromJSON(problem_detail); problem.invalid_params = problem_details->invalid_params; diff --git a/src/5gmsaf/server.h b/src/5gmsaf/server.h index 63a286c..38323d0 100644 --- a/src/5gmsaf/server.h +++ b/src/5gmsaf/server.h @@ -1,11 +1,11 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view */ #ifndef NF_SERVER_H @@ -33,10 +33,12 @@ extern bool nf_server_send_error(ogs_sbi_stream_t *stream, const char *title, const char *detail, cJSON * problem_detail, const nf_server_interface_metadata_t *interface, const nf_server_app_metadata_t *app); -extern ogs_sbi_response_t *nf_server_new_response(char *location, char *content_type, time_t last_modified, char *etag, - int cache_control, char *allow_methods, const nf_server_interface_metadata_t *interface, - const nf_server_app_metadata_t *app); -extern ogs_sbi_response_t *nf_server_populate_response(ogs_sbi_response_t *response, int content_length, char *content, int status); +extern ogs_sbi_response_t *nf_server_new_response(const char *location, const char *content_type, time_t last_modified, + const char *etag, int cache_control, const char *allow_methods, + const nf_server_interface_metadata_t *interface, + const nf_server_app_metadata_t *app); +extern ogs_sbi_response_t *nf_server_populate_response(ogs_sbi_response_t *response, int content_length, char *content, + int status); #ifdef __cplusplus } diff --git a/src/5gmsaf/service-access-information.c b/src/5gmsaf/service-access-information.c index 8514683..f4c26b4 100644 --- a/src/5gmsaf/service-access-information.c +++ b/src/5gmsaf/service-access-information.c @@ -1,12 +1,14 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022-2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Authors: Dev Audsin + * David Waring + * Vuk Stojkovic + * Copyright: (C) 2022-2024 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #include #include @@ -19,12 +21,13 @@ program. If this file is missing then the license can be retrieved from #include "provisioning-session.h" #include "sai-cache.h" #include "utilities.h" - #include "openapi/model/msaf_api_consumption_reporting_configuration.h" #include "openapi/model/msaf_api_content_hosting_configuration.h" #include "openapi/model/msaf_api_m5_media_entry_point.h" #include "openapi/model/msaf_api_provisioning_session.h" #include "openapi/model/msaf_api_service_access_information_resource.h" +#include "metrics-reporting-configuration.h" + #include "service-access-information.h" @@ -40,6 +43,7 @@ msaf_context_service_access_information_create(msaf_provisioning_session_t *prov msaf_api_service_access_information_resource_client_consumption_reporting_configuration_t *ccrc = NULL; msaf_api_service_access_information_resource_network_assistance_configuration_t *nac = NULL; OpenAPI_list_t *entry_points = NULL; + OpenAPI_list_t *cmrc_list = NULL; /* streaming entry points */ ogs_debug("Adding streams to ServiceAccessInformation [%s]", provisioning_session->provisioningSessionId); @@ -56,12 +60,12 @@ msaf_context_service_access_information_create(msaf_provisioning_session_t *prov OpenAPI_lnode_t *prof_node; m5_profiles = OpenAPI_list_create(); OpenAPI_list_for_each(dist_conf->entry_point->profiles, prof_node) { - OpenAPI_list_add(m5_profiles, ogs_strdup(prof_node->data)); + OpenAPI_list_add(m5_profiles, msaf_strdup(prof_node->data)); } } url = ogs_msprintf("%s%s", dist_conf->base_url, dist_conf->entry_point->relative_path); - m5_entry = msaf_api_m5_media_entry_point_create(url, ogs_strdup(dist_conf->entry_point->content_type), m5_profiles); + m5_entry = msaf_api_m5_media_entry_point_create(url, msaf_strdup(dist_conf->entry_point->content_type), m5_profiles); ogs_assert(m5_entry); if (!entry_points) entry_points = OpenAPI_list_create(); OpenAPI_list_add(entry_points, m5_entry); @@ -74,7 +78,7 @@ msaf_context_service_access_information_create(msaf_provisioning_session_t *prov if (provisioning_session->policy_templates) { OpenAPI_list_t *policy_templates_svr_list; OpenAPI_list_t *policy_template_bindings; - msaf_api_sdf_method_e sdf_method = msaf_api_sdf_method__5_TUPLE; + msaf_api_sdf_method_e sdf_method = msaf_api_sdf_method_VAL__5_TUPLE; OpenAPI_list_t *sdf_methods; policy_template_bindings = _policy_templates_hash_to_list_of_ready_bindings(provisioning_session->policy_templates); @@ -91,7 +95,7 @@ msaf_context_service_access_information_create(msaf_provisioning_session_t *prov sdf_methods = OpenAPI_list_create(); ogs_assert(sdf_methods); OpenAPI_list_add(sdf_methods, (void *)sdf_method); - + dpic = msaf_api_service_access_information_resource_dynamic_policy_invocation_configuration_create( policy_templates_svr_list, policy_template_bindings, sdf_methods); } else { @@ -128,6 +132,76 @@ msaf_context_service_access_information_create(msaf_provisioning_session_t *prov ogs_assert(ccrc); } + /* client metrics reporting configuration */ + if (ogs_hash_count(provisioning_session->metrics_reporting_map) > 0) { + + ogs_debug("Adding clientMetricsReporting to ServiceAccessInformation [%s]", + provisioning_session->provisioningSessionId); + + cmrc_list = OpenAPI_list_create(); + ogs_assert(cmrc_list); + + ogs_hash_index_t *hi = NULL; + void *val = NULL; + const void *key = NULL; + + for (hi = ogs_hash_first(provisioning_session->metrics_reporting_map); hi; hi = ogs_hash_next(hi)) { + + ogs_hash_this(hi, &key, NULL, &val); + + const msaf_metrics_reporting_configuration_t *metrics_config = (msaf_metrics_reporting_configuration_t *)val; + + char *server_url = ogs_msprintf("http%s://%s/3gpp-m5/v2/", is_tls?"s":"", svr_hostname); + OpenAPI_list_t *cmrc_svr_list = OpenAPI_list_create(); + ogs_assert(cmrc_svr_list); + OpenAPI_list_add(cmrc_svr_list, server_url); + + OpenAPI_list_t *url_filters = NULL; + if (metrics_config->config->url_filters) { + url_filters = OpenAPI_list_create(); + ogs_assert(url_filters); + OpenAPI_lnode_t *url_filt_node; + OpenAPI_list_for_each(metrics_config->config->url_filters, url_filt_node) { + OpenAPI_list_add(url_filters, msaf_strdup((const char*)url_filt_node->data)); + } + } + + OpenAPI_list_t *metrics = NULL; + if (metrics_config->config->metrics) { + metrics = OpenAPI_list_create(); + ogs_assert(metrics); + OpenAPI_lnode_t *metrics_node; + OpenAPI_list_for_each(metrics_config->config->metrics, metrics_node) { + OpenAPI_list_add(metrics, msaf_strdup((const char*)metrics_node->data)); + } + } + + char *scheme = msaf_strdup(metrics_config->config->scheme); + if (!scheme || strlen(scheme) == 0) { + if (scheme) ogs_free(scheme); + scheme = msaf_strdup("urn:3GPP:ns:PSS:DASH:QM10"); + } + + msaf_api_service_access_information_resource_client_metrics_reporting_configurations_inner_t *cmrc_inner = + msaf_api_service_access_information_resource_client_metrics_reporting_configurations_inner_create( + msaf_strdup(metrics_config->config->metrics_reporting_configuration_id), + cmrc_svr_list, + NULL, + scheme, + msaf_strdup(metrics_config->config->data_network_name), + !!metrics_config->config->reporting_interval, + metrics_config->config->reporting_interval?*metrics_config->config->reporting_interval:0, + metrics_config->config->sample_percentage, + url_filters, + metrics_config->config->sampling_period?*metrics_config->config->sampling_period:0, + metrics); + + if (cmrc_inner) { + OpenAPI_list_add(cmrc_list, cmrc_inner); + } + } + } + /* Network Assistance Configuration */ if (config->offerNetworkAssistance) { OpenAPI_list_t *na_svr_list; @@ -142,11 +216,11 @@ msaf_context_service_access_information_create(msaf_provisioning_session_t *prov /* Create SAI */ service_access_information = msaf_api_service_access_information_resource_create( msaf_strdup(provisioning_session->provisioningSessionId), - msaf_api_provisioning_session_type_DOWNLINK, + msaf_api_provisioning_session_type_VAL_DOWNLINK, streaming_access, ccrc /* client_consumption_reporting_configuration */, dpic /* dynamic_policy */, - NULL /* client_metrics_reporting */, + cmrc_list /* OpenAPI_list_t client_metrics_reporting */, nac /* network_assistance_configuration */, NULL /* client_edge_resources */); @@ -161,8 +235,8 @@ const msaf_sai_cache_entry_t *msaf_context_retrieve_service_access_information(c const msaf_sai_cache_entry_t *sai_entry = NULL; provisioning_session_context = msaf_provisioning_session_find_by_provisioningSessionId(provisioning_session_id); - if (provisioning_session_context == NULL){ - ogs_error("Couldn't find the Provisioning Session ID [%s]", provisioning_session_id); + if (provisioning_session_context == NULL) { + ogs_error("Couldn't find the Provisioning Session ID [%s]", provisioning_session_id); return NULL; } @@ -185,13 +259,14 @@ const msaf_sai_cache_entry_t *msaf_context_retrieve_service_access_information(c ogs_debug("Found existing SAI cache entry"); } - if (sai_entry == NULL){ + if (sai_entry == NULL) { ogs_error("The provisioning Session [%s] does not have an associated Service Access Information", provisioning_session_id); } return sai_entry; } + static OpenAPI_list_t *_policy_templates_hash_to_list_of_ready_bindings(ogs_hash_t *policy_templates) { msaf_policy_template_node_t *policy_template_node; @@ -204,7 +279,7 @@ static OpenAPI_list_t *_policy_templates_hash_to_list_of_ready_bindings(ogs_hash for (hi = ogs_hash_first(policy_templates); hi; hi = ogs_hash_next(hi)) { policy_template_node = (msaf_policy_template_node_t *)ogs_hash_this_val(hi); - if (policy_template_node->policy_template->state == msaf_api_policy_template_STATE_READY) { + if (policy_template_node->policy_template->state == msaf_api_policy_template_STATE_VAL_READY) { policy_template_binding = msaf_api_service_access_information_resource_dynamic_policy_invocation_configuration_policy_template_bindings_inner_create(msaf_strdup(policy_template_node->policy_template->external_reference), msaf_strdup(policy_template_node->policy_template->policy_template_id)); OpenAPI_list_add(policy_template_bindings, policy_template_binding); } diff --git a/src/5gmsaf/service-access-information.h b/src/5gmsaf/service-access-information.h index 0e8b215..8a411de 100644 --- a/src/5gmsaf/service-access-information.h +++ b/src/5gmsaf/service-access-information.h @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2022 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef MSAF_SERVICE_ACCESS_INFORMATION_H #define MSAF_SERVICE_ACCESS_INFORMATION_H diff --git a/src/5gmsaf/timer.c b/src/5gmsaf/timer.c index cdd948f..ac9eaa2 100644 --- a/src/5gmsaf/timer.c +++ b/src/5gmsaf/timer.c @@ -2,7 +2,8 @@ * Copyright (C) 2019-2022 by Sukchan Lee * Copyright (C) 2023 British Broadcasting Corporation * - * Authors: Sukchan Lee & Dev Audsin + * Authors: Sukchan Lee + * Dev Audsin * * This file is derived from Open5GS with additions by the BBC for 5G-MAG. * @@ -42,7 +43,7 @@ const char *msaf_timer_get_name(int timer_id) return OGS_TIMER_NAME_SBI_CLIENT_WAIT; case MSAF_TIMER_DELIVERY_BOOST: return "MSAF_TIMER_DELIVERY_BOOST"; - default: + default: break; } diff --git a/src/5gmsaf/timer.h b/src/5gmsaf/timer.h index 20da124..bfc8c01 100644 --- a/src/5gmsaf/timer.h +++ b/src/5gmsaf/timer.h @@ -1,12 +1,12 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * Copyright: (C) 2023 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef MSAF_TIMER_H #define MSAF_TIMER_H diff --git a/src/5gmsaf/utilities.c b/src/5gmsaf/utilities.c index cee9a8a..f8b0cf5 100644 --- a/src/5gmsaf/utilities.c +++ b/src/5gmsaf/utilities.c @@ -1,12 +1,13 @@ /* -License: 5G-MAG Public License (v1.0) -Author: Dev Audsin -Copyright: (C) 2022-2023 British Broadcasting Corporation - -For full license terms please see the LICENSE file distributed with this -program. If this file is missing then the license can be retrieved from -https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -*/ + * License: 5G-MAG Public License (v1.0) + * Author: Dev Audsin + * David Waring + * Copyright: (C) 2022-2024 British Broadcasting Corporation + * + * For full license terms please see the LICENSE file distributed with this + * program. If this file is missing then the license can be retrieved from + * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view + */ #ifndef _GNU_SOURCE #define _GNU_SOURCE @@ -29,7 +30,7 @@ time_t str_to_time(const char *str_time) static time_t time; struct tm tm = {0}; strptime(str_time, "%a, %d %b %Y %H:%M:%S %Z", &tm); - time = mktime(&tm); + time = mktime(&tm); return time; } @@ -39,7 +40,7 @@ const char *get_time(time_t time_epoch) static char buf[80]; /* Format and print the time, "ddd yyyy-mm-dd hh:mm:ss zzz" */ - ts = localtime(&time_epoch); + ts = localtime(&time_epoch); strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", ts); return buf; @@ -71,13 +72,13 @@ char *read_file(const char *filename) } int str_match(const char *line, const char *word_to_find) { - + char* p = strstr(line,word_to_find); if ((p==line) || (p!=NULL && !isalnum((unsigned char)p[-1]))) { p += strlen(word_to_find); if (!isalnum((unsigned char)*p)) - { + { return 1; } else { return 0; @@ -92,7 +93,7 @@ char *get_path(const char *file) char *file_dir = NULL; path = realpath(file, NULL); - if(path == NULL){ + if (path == NULL) { ogs_error("cannot find file with name[%s]: %s", file, strerror(errno)); return NULL; } @@ -128,7 +129,7 @@ long int ascii_to_long(const char *str) return ret; } -uint16_t ascii_to_uint16(const char *str) +uint16_t ascii_to_uint16(const char *str) { long int ret; ret = ascii_to_long(str); diff --git a/src/5gmsaf/utilities.h b/src/5gmsaf/utilities.h index 5e6e899..1563344 100644 --- a/src/5gmsaf/utilities.h +++ b/src/5gmsaf/utilities.h @@ -2,7 +2,7 @@ * License: 5G-MAG Public License (v1.0) * Author: Dev Audsin * Copyright: (C) 2022-2023 British Broadcasting Corporation - * + * * For full license terms please see the LICENSE file distributed with this * program. If this file is missing then the license can be retrieved from * https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view diff --git a/subprojects/.gitignore b/subprojects/.gitignore index f391ce9..02bb91c 100644 --- a/subprojects/.gitignore +++ b/subprojects/.gitignore @@ -2,4 +2,5 @@ freeDiameter libtins prometheus-client-c usrsctp +open5gs rt-5gc-service-consumers diff --git a/subprojects/open5gs b/subprojects/open5gs deleted file mode 160000 index 99f7da1..0000000 --- a/subprojects/open5gs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 99f7da154e0fc4a494b35d1e74c26d89b934d5bc diff --git a/subprojects/open5gs.wrap b/subprojects/open5gs.wrap new file mode 100644 index 0000000..3ec0744 --- /dev/null +++ b/subprojects/open5gs.wrap @@ -0,0 +1,5 @@ +[wrap-git] +directory = open5gs +url = https://github.com/5G-MAG/open5gs.git +revision = bbc-patches +diff_files = open5gs.patch diff --git a/subprojects/patch_open5gs.sh b/subprojects/packagefiles/open5gs.patch similarity index 70% rename from subprojects/patch_open5gs.sh rename to subprojects/packagefiles/open5gs.patch index 4dce785..791e476 100755 --- a/subprojects/patch_open5gs.sh +++ b/subprojects/packagefiles/open5gs.patch @@ -1,49 +1,3 @@ -#!/bin/sh -#============================================================================== -# 5G-MAG Reference Tools - Open5GS apply patches -#============================================================================== -# Author: David Waring -# License: 5G-MAG Public License (v1.0) -# Copyright: ©2022-2023 British Broadcasting Corporation -# -# For full license terms please see the LICENSE file distributed with this -# program. If this file is missing then the license can be retrieved from -# https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -#============================================================================== - -cd `dirname "$0"` - -open5gs_src=`realpath "$1"` -patch_cmd=`which patch` - -if ! grep -q '/\* rt-5gms-applicatiopn-function patch applied \*/' "$open5gs_src/lib/sbi/server.c"; then - (cd "$open5gs_src"; "$patch_cmd" -p1 <stream_list, stream); -diff --git a/lib/sbi/openapi/meson.build b/lib/sbi/openapi/meson.build -index b3a507bd3..5f6388a0f 100644 ---- a/lib/sbi/openapi/meson.build -+++ b/lib/sbi/openapi/meson.build -@@ -1370,6 +1370,7 @@ libsbi_openapi_sources = files(''' - '''.split()) - - libsbi_openapi_inc = include_directories('.') -+libsbi_openapi_model_inc = include_directories('model') - - sbi_openapi_cc_flags = ['-DOGS_SBI_COMPILATION'] - diff --git a/lib/sbi/server.c b/lib/sbi/server.c index af5cb8aad..518420c5c 100644 --- a/lib/sbi/server.c @@ -331,48 +273,3 @@ index c112f9330..53467171a 100644 void ogs_sbi_server_remove(ogs_sbi_server_t *server); void ogs_sbi_server_remove_all(void); -diff --git a/src/main.c b/src/main.c -index 329d5b108..0f993a6a6 100644 ---- a/src/main.c -+++ b/src/main.c -@@ -111,7 +111,7 @@ int main(int argc, const char *const argv[]) - bool enable_debug; - bool enable_trace; - } optarg; -- const char *argv_out[argc]; -+ const char *argv_out[argc+1]; - - memset(&optarg, 0, sizeof(optarg)); - -diff --git a/src/meson.build b/src/meson.build -index d313b6932..2e25dbd93 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -33,6 +33,8 @@ version_conf = configuration_data() - version_conf.set_quoted('OPEN5GS_VERSION', package_version) - configure_file(output : 'version.h', configuration : version_conf) - -+app_main_c = files(['main.c']) -+ - subdir('mme') - subdir('hss') - subdir('sgwc') -diff --git a/tests/common/application.c b/tests/common/application.c -index 1bf1a0528..501693a45 100644 ---- a/tests/common/application.c -+++ b/tests/common/application.c -@@ -26,8 +26,8 @@ static void run(int argc, const char *const argv[], - int rv; - bool user_config; - -- /* '-f sample-XXXX.conf -e error' is always added */ -- const char *argv_out[argc+4], *new_argv[argc+4]; -+ /* '-f sample-XXXX.conf -e error' + null is always added */ -+ const char *argv_out[argc+5], *new_argv[argc+5]; - int argc_out; - - char conf_file[OGS_MAX_FILEPATH_LEN]; -EOF -) -fi -exit 0 diff --git a/subprojects/rt-common-shared b/subprojects/rt-common-shared index cf65591..1210229 160000 --- a/subprojects/rt-common-shared +++ b/subprojects/rt-common-shared @@ -1 +1 @@ -Subproject commit cf65591eb802aa034872adc96d07e5bad39b4e05 +Subproject commit 12102299bb4775f9a8d10225d5e20c2a96422b9c diff --git a/tests/msaf/sai-cache-test.c b/tests/msaf/sai-cache-test.c index bfdf3d7..0c9a61f 100644 --- a/tests/msaf/sai-cache-test.c +++ b/tests/msaf/sai-cache-test.c @@ -65,7 +65,7 @@ static void test_sai_cache_add(abts_case *tc, void *data) nac = msaf_api_service_access_information_resource_network_assistance_configuration_create(nac_addresses); ABTS_PTR_NOTNULL(tc, nac); - sai = msaf_api_service_access_information_resource_create(ogs_strdup("Provisioning-Session-Id"), msaf_api_provisioning_session_type_DOWNLINK, streams, NULL, NULL, NULL, nac, NULL); + sai = msaf_api_service_access_information_resource_create(ogs_strdup("Provisioning-Session-Id"), msaf_api_provisioning_session_type_VAL_DOWNLINK, streams, NULL, NULL, NULL, nac, NULL); ABTS_PTR_NOTNULL(tc, sai); ABTS_TRUE(tc, msaf_sai_cache_add(cache, true, "af.example.com:443", sai)); diff --git a/tools/meson.build b/tools/meson.build index cd43e1e..2e7314b 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -6,19 +6,10 @@ # program. If this file is missing then the license can be retrieved from # https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -pymod = import('python') fs = import('fs') -python3 = pymod.find_installation('python3') - support_scripts_dir = get_option('libexecdir') / 'rt-5gms' -scripts = { - 'python3/m1_sync_config.py': 'msaf-configuration', - 'python3/m1_client_cli.py': 'm1-client', - 'python3/m1_session_cli.py': 'm1-session' -} - support_scripts = { 'bash/certmgr': 'self-signed-certmgr', 'bash/le-certmgr': 'lets-encrypt-certmgr' @@ -26,11 +17,7 @@ support_scripts = { self_signed_certmgr_runtime = support_scripts_dir / 'self-signed-certmgr' -python3_modules = [ - 'python3/lib/rt_m1_client', -] - -scripts_conf_data = configuration_data({'python_packages_dir': python3.get_install_dir()}) +scripts_conf_data = configuration_data() script_conf_options = [ 'prefix', 'bindir', 'libdir', 'libexecdir', 'localstatedir', 'sbindir', 'sysconfdir', @@ -39,21 +26,7 @@ foreach opt : script_conf_options scripts_conf_data.set(opt, get_option(opt)) endforeach -foreach src, dst : scripts - scriptfile = configure_file(input: src, configuration: scripts_conf_data, output: dst) - install_data(scriptfile, install_dir: get_option('bindir'), install_mode: 'rwxr-xr-x') -endforeach - foreach src, dst : support_scripts scriptfile = configure_file(input: src, configuration: scripts_conf_data, output: dst) install_data(scriptfile, install_dir: support_scripts_dir, install_mode: 'rwxr-xr-x') endforeach - -sh = find_program('sh') -foreach pm : python3_modules - mod_files = run_command([sh, '-c', 'cd "$MESON_SOURCE_ROOT/$MESON_SUBDIR/'+fs.parent(pm)+'"; find "' + fs.name(pm) + '" -type f -name "*.py" -print'], check: false).stdout().strip().split('\n') - foreach mod_filepath : mod_files - mod_file = files([fs.parent(pm) / mod_filepath]) - python3.install_sources(mod_file, subdir: fs.parent(mod_filepath)) - endforeach -endforeach diff --git a/tools/python3/lib/rt_m1_client/certificates/__init__.py b/tools/python3/lib/rt_m1_client/certificates/__init__.py deleted file mode 100644 index 5fd896e..0000000 --- a/tools/python3/lib/rt_m1_client/certificates/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/python3 -#============================================================================== -# 5G-MAG Reference Tools: M1 Client Certificate Signing -#============================================================================== -# -# File: rt_m1_client/certificates/__init__.py -# License: 5G-MAG Public License (v1.0) -# Author: David Waring -# Copyright: (C) 2023 British Broadcasting Corporation -# -# For full license terms please see the LICENSE file distributed with this -# program. If this file is missing then the license can be retrieved from -# https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -# -#============================================================================== -# -# M1 Session Certificate Signing Module -# ===================================== -# -# This module defines classes used by the M1 session classes to sign certificates -# -# CertificateSigner - Base class for certificate signing. -# -# LocalCACertificateSigner - A CertificateSigner that uses a locally generated CA to generate X509 certificates from CSRs -# ACMECertificateSigner - A CertificateSigner which uses an ACME service to sign the certificates -# LetsEncryptCertificateSigner - An ACMECertificateSigner preconfigured for Let's Encrypt service. -# TestLetsEncryptCertificateSigner - An ACMECertificateSigner preconfigured for Let's Encrypt staging service. -# -# DefaultCertificateSigner - The default CertificateSigner used by the M1Session class, presently LocalCACertificateSigner. -# -''' -====================================================== -5G-MAG Reference Tools: M1 Session Certificate Signing -====================================================== - -This module defines some classes that can be used by the `M1Session` class to provide certificate signing services. - -''' - -from .base import CertificateSigner -from .local_ca_cert_signer import LocalCACertificateSigner -from .acme_cert_signer import ACMECertificateSigner, LetsEncryptCertificateSigner, TestLetsEncryptCertificateSigner - -DefaultCertificateSigner = LocalCACertificateSigner - -__all__ = [ - "CertificateSigner", - "LocalCACertificateSigner", - "ACMECertificateSigner", - "LetsEncryptCertificateSigner", - "TestLetsEncryptCertificateSigner", - "DefaultCertificateSigner", - ] diff --git a/tools/python3/lib/rt_m1_client/certificates/acme_cert_signer.py b/tools/python3/lib/rt_m1_client/certificates/acme_cert_signer.py deleted file mode 100644 index 5697d4a..0000000 --- a/tools/python3/lib/rt_m1_client/certificates/acme_cert_signer.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/python3 -#============================================================================== -# 5G-MAG Reference Tools: ACME Certificate Signing -#============================================================================== -# -# File: rt_acme_cert_signer/certsigner.py -# License: 5G-MAG Public License (v1.0) -# Author: David Waring -# Copyright: (C) 2023 British Broadcasting Corporation -# -# For full license terms please see the LICENSE file distributed with this -# program. If this file is missing then the license can be retrieved from -# https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -# -#============================================================================== -# -# ACME Certificate Signing -# ======================== -# -# This module defines an rt_m1_client.certificates.CertificateSigner class that -# will use an ACME service, such as Let's Encrypt, to generate signed -# certificates. -# -# ACMECertificateSigner - ACME certificate signing class. -# -# LetsEncryptCertificateSigner - Helper function to create an ACMECertificateSigner for the live Let's Encrypt service. -# TestLetsEncryptCertificateSigner - Helper function to create an ACMECertificateSigner for the staging Let's Encrypt service. -# -''' -================================================ -5G-MAG Reference Tools: ACME Certificate Signing -================================================ - -This module defines an ACME `rt_m1_client.certificates.CertificateSigner` class -that can be used by the `rt_m1_client.certificates.M1Session` class to provide -certificate signing services which use an ACME service such as Let's Encrypt. -''' - -# Python system modules -import aiofiles -import asyncio -from cryptography.hazmat.primitives.serialization import Encoding as cryptography_Encoding, PublicFormat as cryptography_PublicFormat -import logging -import os.path -import re -from typing import Optional, List, Tuple - -# 3rd party modules -import OpenSSL - -# Local modules -from .base import CertificateSigner -from ..data_store import DataStore - -LOGGER = logging.getLogger(__name__) - -class ACMECertificateSigner(CertificateSigner): - '''ACMECertificateSigner class - - Class to perform certificate signing using an ACME certificate signing service. - - Constants - ========= - - - LetsEncryptService - URL of the Let's Encrypt live service - - LetsEncryptStagingService - URL of the Let's Encrypt staging (test) service - ''' - - LetsEncryptStagingService: str = 'https://acme-staging-v02.api.letsencrypt.org/directory' - LetsEncryptService: str = 'https://acme-v02.api.letsencrypt.org/directory' - - def __init__(self, *args, acme_service: Optional[str] = None, docroots_dir: Optional[str] = None, default_docroot_dir: Optional[str] = None, data_store: Optional[DataStore] = None, **kwargs): - '''Constructor - - :param acme_service: The URL of the ACME directory service to use for certificate signing. - :param docroots_dir: The directory that contains all the document roots for the virtual hosts, each host has a directory - whose name is the FQDN of the virtual host. - :param default_docroot_dir: The directory which is the docroot of the default virtual host. - :param data_store: The persistent data store object to use for data persistence. - ''' - errs=[] - if acme_service is None: - errs += ['acme_service is None'] - if docroots_dir is None: - errs += ['docroots_dir is None'] - if default_docroot_dir is None: - errs += ['default_docroot_dir is None'] - if len(errs) != 0: - raise RuntimeError(f'{self.__class__.__name__} instantiated without needed parameters: {", ".join(errs)}') - super().__init__(*args, data_store=data_store, **kwargs) - self.__acme_service: str = acme_service - self.__docroots: str = docroots_dir - self.__default_docroot: str = default_docroot_dir - - async def asyncInit(self): - '''Asynchronous object initialisation - - Derived classes should override this if they have object initialisation to do that requires async operations. - - This async method must return self. - ''' - return self - - async def signCertificate(self, csr: str, *args, **kwargs) -> Optional[str]: - '''Sign a CSR in PEM format and return the public X509 Certificate in PEM format - - :param str csr: A CSR in PEM format. - :param str domain_name_alias: Optional domain name to add to the subjectAltNames in the final certificate. - - :return: a public X509 certificate in PEM format. - - This will use the *csr* as a guideline for talking to the ACME server. The *domain_name_alias* will be used for the common name and first SAN, if the common name or SANs from the *csr* are not private IPs or localhost references then they will also be included. - ''' - x509req: OpenSSL.crypto.X509Req = OpenSSL.crypto.load_certificate_request(OpenSSL.crypto.FILETYPE_PEM, csr.encode('utf-8')) - - # Send request to ACME server - acmeReqBytes: bytes = OpenSSL.crypto.dump_certificate_request(OpenSSL.crypto.FILETYPE_PEM, x509req) - async with aiofiles.tempfile.NamedTemporaryFile('wb', delete=False) as f: - await f.write(acmeReqBytes) - common_name = x509req.get_subject().commonName - if isinstance(common_name,bytes): - common_name = common_name.decode('utf-8') - domain_docroot = os.path.join(self.__docroots, common_name) - old_umask = os.umask(0) - try: - await aiofiles.os.makedirs(domain_docroot, mode=0o755, exist_ok=True) - if not os.path.lexists(os.path.join(domain_docroot, '.well-known')): - await aiofiles.os.symlink(os.path.join(self.__default_docroot, '.well-known'), os.path.join(domain_docroot, '.well-known'), target_is_directory=True) - finally: - os.umask(old_umask) - async with aiofiles.tempfile.TemporaryDirectory() as d: - result, output = await _run_certbot_app(['certonly', '--server', self.__acme_service, '--webroot', '--webroot-path', self.__default_docroot, '--csr', f.name, '--cert-path', os.path.join(d, 'certificate.pem'), '--fullchain-path', os.path.join(d, 'fullchain.pem'), '--chain-path', os.path.join(d, 'chain.pem')]) - certdata = None - if result == 0: - async with aiofiles.open(os.path.join(d, 'fullchain.pem'), 'r') as inpem: - certdata = await inpem.read() - else: - log_error(f'certbot failed with exit code {result}: {output}') - await aiofiles.os.remove(f.name) - return certdata - -async def _run_certbot_app(cmd_args: List[str]) -> Tuple[int, bytes]: - '''Run `certbot` using the given command line arguments - - :param cmd_args: The command line arguments for `certbot`. - - :return: A tuple of the `certbot` process exit code and STDOUT from `certbot`. - ''' - LOGGER.debug('Executing: certbot %s', ' '.join(['\''+s+'\'' for s in cmd_args])) - proc = await asyncio.create_subprocess_exec('certbot', *cmd_args, stdout=asyncio.subprocess.PIPE) - await proc.wait() - LOGGER.debug('Command exited with code %i', proc.returncode) - data = await proc.stdout.read() - return (proc.returncode, data) - -async def LetsEncryptCertificateSigner(*args, docroots_dir: Optional[str] = None, default_docroot_dir: Optional[str] = None, data_store: Optional[DataStore] = None, **kwargs) -> ACMECertificateSigner: - '''Let's Encrypt ACMECertificateSigner factory function - - Creates an ACMECertificateSigner with *acme_service* set to the Let's Encrypt live service URL and other parameters passed through. - - :return: a new ACMECertificateSigner which will use Let's Encrypt. - ''' - return await ACMECertificateSigner(*args, acme_service=ACMECertificateSigner.LetsEncryptService, docroots_dir=docroots_dir, default_docroot_dir=default_docroot_dir, data_store=data_store, **kwargs) - -async def TestLetsEncryptCertificateSigner(*args, docroots_dir: Optional[str] = None, default_docroot_dir: Optional[str] = None, data_store: Optional[DataStore] = None, **kwargs) -> ACMECertificateSigner: - '''Let's Encrypt staging (test) service ACMECertificateSigner factory function - - Creates an ACMECertificateSigner with *acme_service* set to the Let's Encrypt staging service URL and other parameters passed through. - - :return: a new ACMECertificateSigner which will use Let's Encrypt staging service. - ''' - return await ACMECertificateSigner(*args, acme_service=ACMECertificateSigner.LetsEncryptStagingService, docroots_dir=docroots_dir, default_docroot_dir=default_docroot_dir, data_store=data_store, **kwargs) - diff --git a/tools/python3/lib/rt_m1_client/certificates/base.py b/tools/python3/lib/rt_m1_client/certificates/base.py deleted file mode 100644 index f748456..0000000 --- a/tools/python3/lib/rt_m1_client/certificates/base.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/python3 -#============================================================================== -# 5G-MAG Reference Tools: M1 Client Certificate Signing base class -#============================================================================== -# -# File: rt_m1_client/certificates/base.py -# License: 5G-MAG Public License (v1.0) -# Author: David Waring -# Copyright: (C) 2023 British Broadcasting Corporation -# -# For full license terms please see the LICENSE file distributed with this -# program. If this file is missing then the license can be retrieved from -# https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -# -#============================================================================== -# -# M1 Session Certificate Signing base class -# ========================================= -# -# This module defines classes used by the M1 session classes to sign certificates -# -# CertificateSigner - Base class for certificate signing. -# -''' -================================================================= -5G-MAG Reference Tools: M1 Session Certificate Signing base class -================================================================= - -This module defines some classes that can be used by the `M1Session` class to provide certificate signing services. - -''' -from typing import Optional - -from ..data_store import DataStore - -class CertificateSigner: - '''Base class for all CertificateSigner classes - ''' - def __init__(self, *args, data_store: Optional[DataStore] = None, **kwargs): - self.data_store = data_store - - def __await__(self): - '''Await method - - This allows the class to be instantiated with asynchronous initialisation, e.g.:: - cert_signer = await MyCertificateSigner() - - This will call the async method `asyncInit` to perform the asynchronous initialisation operations. - ''' - return self.asyncInit().__await__() - - async def asyncInit(self): - '''Asynchronous object initialisation - - Derived classes should override this if they have object initialisation to do that requires async operations. - - This async method must return self. - ''' - return self - - async def signCertificate(self, csr: str, *args, **kwargs) -> Optional[str]: - '''Sign a CSR in PEM format and return the public X509 Certificate in PEM format - - :param str csr: A CSR in PEM format. - - :return: a public X509 certificate in PEM format. - ''' - raise NotImplementedError('Class derived from CertificateSigner must implement this method') diff --git a/tools/python3/lib/rt_m1_client/certificates/local_ca_cert_signer.py b/tools/python3/lib/rt_m1_client/certificates/local_ca_cert_signer.py deleted file mode 100644 index ed74784..0000000 --- a/tools/python3/lib/rt_m1_client/certificates/local_ca_cert_signer.py +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/bin/python3 -#============================================================================== -# 5G-MAG Reference Tools: M1 Client Local CA Certificate Signing -#============================================================================== -# -# File: rt_m1_client/certificates/local_ca_cert_signer.py -# License: 5G-MAG Public License (v1.0) -# Author: David Waring -# Copyright: (C) 2023 British Broadcasting Corporation -# -# For full license terms please see the LICENSE file distributed with this -# program. If this file is missing then the license can be retrieved from -# https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -# -#============================================================================== -# -# M1 Session Certificate Signing using a local CA -# =============================================== -# -# This module defines classes used by the M1 session classes to sign certificates -# -# LocalCACertificateSigner - A CertificateSigner that uses a locally generated CA to generate X509 certificates from CSRs -# -''' -======================================================================= -5G-MAG Reference Tools: M1 Session Certificate Signing using a local CA -======================================================================= - -This module defines some classes that can be used by the `M1Session` class to provide certificate signing services. - -''' -from typing import Optional, Tuple, List - -import OpenSSL - -from .base import CertificateSigner -from ..data_store import DataStore - -class LocalCACertificateSigner(CertificateSigner): - '''CertificateSigner that uses a locally generated CA kept in the data store - ''' - - def __init__(self, *args, data_store: Optional[DataStore] = None, local_ca_days: int = 365, temp_ca_days: int = 1, local_cert_days: int = 30, **kwargs): - '''Constructor - - Create a CertificateSigner that uses a locally generated CA to sign certificates. - - :param DataStore data_store: The DataStore to use to persist the CA key and certificate. - :param int local_ca_days: The number of days before expiry of the local CA in the data store. - :param int temp_ca_days: The number of days for the local CA if no DataStore is provided for persistence. - :param int local_cert_days: The number of days before expiry of signed certificates. - ''' - super().__init__(self, data_store=data_store) - self.__ca_key: Optional[OpenSSL.crypto.PKey] = None - self.__ca: Optional[OpenSSL.crypto.X509] = None - self.__local_ca_days: int = local_ca_days - self.__temp_ca_days: int = temp_ca_days - self.__local_cert_days: int = local_cert_days - - async def signCertificate(self, csr: str, *args, **kwargs) -> Optional[str]: - '''Sign a CSR in PEM format and return the public X509 Certificate in PEM format - - This will generate a public certificate from the *csr*, which is signed by the locally generated CA. - The certificate will have subjectAltNames defined for the SANs in the *csr* and the commonName. The certificate will expire - in the number of days indicated by the *local_cert_days* parameter when an instance of this class was created. - - :param str csr: A CSR in PEM format. - - :return: a public X509 certificate in PEM format, or None on error. - ''' - x509req: OpenSSL.crypto.X509Req = OpenSSL.crypto.load_certificate_request(OpenSSL.crypto.FILETYPE_PEM, csr.encode('utf-8')) - # Get local CA - ca_key, ca = await self.__getLocalCA() - # Convert CSR to X509 certificate - x509 = OpenSSL.crypto.X509() - x509.set_subject(x509req.get_subject()) - x509.set_serial_number(1) - x509.gmtime_adj_notBefore(0) - x509.gmtime_adj_notAfter(self.__local_cert_days * 24 * 60 * 60) - x509.set_issuer(ca.get_subject()) - x509.set_pubkey(x509req.get_pubkey()) - # Copy any extensions we aren't replacing - for ext in x509req.get_extensions(): - if ext.get_short_name() not in [b'subjectKeyIdentifier', b'authorityKeyIdentifier', b'basicConstraints']: - x509.add_extensions([ext]) - x509.add_extensions([ - OpenSSL.crypto.X509Extension(b'subjectKeyIdentifier', False, b'hash', subject=x509), - OpenSSL.crypto.X509Extension(b'authorityKeyIdentifier', False, b'keyid, issuer', issuer=ca), - OpenSSL.crypto.X509Extension(b'basicConstraints', True, b'CA:FALSE') - ]) - x509.sign(ca_key, "sha256") - return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, x509).decode('utf-8') - - async def __makeCACert(self, key: OpenSSL.crypto.PKey, cn: str, days: int = 365) -> OpenSSL.crypto.X509: - '''Make a CA certificate - - The CA certificate will use the provided *key* for its public key (if a private key is provided the pubilc key will be - extracted). The *cn* parameter defines the common name for the CA certificate. The *days* parameter is used to set the - expiry date on the CA certificate. - - :meta private: - :param OpenSSL.crypto.PKey key: A public or private key to use for the public key of the CA certificate. - :param str cn: The commonName for the certificate subject and issuer. - :param int days: The number of days the CA certificate will be valid for. - - :return: a self signed X509 CA certificate. - :rtype: OpenSSL.crypto.X509 - ''' - ca = OpenSSL.crypto.X509() - ca_name = ca.get_subject() - # TODO: Get these values from configured values - ca_name.organizationName = '5G-MAG' - ca_name.commonName = cn - ca.set_issuer(ca_name) - # TODO: increment serial number from data-store - ca.set_serial_number(1) - ca.gmtime_adj_notBefore(0) - ca.gmtime_adj_notAfter(days*24*60*60) - ca.set_pubkey(key) - ca.add_extensions([ - OpenSSL.crypto.X509Extension(b'basicConstraints', True, b'CA:TRUE,pathlen:1'), - OpenSSL.crypto.X509Extension(b'subjectKeyIdentifier', False, b'hash', subject=ca), - OpenSSL.crypto.X509Extension(b'authorityKeyIdentifier', False, b'keyid, issuer:always', issuer=ca), - ]) - ca.sign(key, 'sha256') - return ca - - async def __getLocalCA(self) -> Tuple[OpenSSL.crypto.PKey, OpenSSL.crypto.X509]: - '''Get the locally generated CA - - This will create the locally generated CA if it doesn't already exist. - - :meta private: - :return: the CA key and CA public certificate. - :rtype: Tuple[OpenSSL.crypto.PKey, OpenSSL.crypto.X509] - ''' - if self.__ca_key is None or self.__ca is None: - if self.data_store: - ca_key_pem = await self.data_store.get('ca-private') - if ca_key_pem is not None: - self.__ca_key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, ca_key_pem) - else: - self.__ca_key = OpenSSL.crypto.PKey() - self.__ca_key.generate_key(OpenSSL.crypto.TYPE_RSA, 4096) - await self.data_store.set('ca-private', OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, self.__ca_key).decode('utf-8')) - ca_pem = await self.data_store.get('ca-public') - if ca_pem is not None: - self.__ca = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, ca_pem) - else: - self.__ca = await self.__makeCACert(self.__ca_key, '5G-MAG Reference Tools Local CA', days=self.__local_ca_days) - await self.data_store.set('ca-public', OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, self.__ca).decode('utf-8')) - else: - self.__ca_key = OpenSSL.crypto.PKey() - self.__ca_key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) - self.__ca = await self.__makeCACert(self.__ca_key, 'Temporary Demo CA', days=self.__temp_ca_days) - - return self.__ca_key, self.__ca diff --git a/tools/python3/lib/rt_m1_client/client.py b/tools/python3/lib/rt_m1_client/client.py deleted file mode 100644 index c171973..0000000 --- a/tools/python3/lib/rt_m1_client/client.py +++ /dev/null @@ -1,893 +0,0 @@ -#!/usr/bin/python3 -#============================================================================== -# 5G-MAG Reference Tools: M1 Client -#============================================================================== -# -# File: m1_client/client.py -# License: 5G-MAG Public License (v1.0) -# Author: David Waring -# Copyright: (C) 2023 British Broadcasting Corporation -# -# For full license terms please see the LICENSE file distributed with this -# program. If this file is missing then the license can be retrieved from -# https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -# -#============================================================================== -# -# M1 Client class -# =============== -# -# This module contains an M1 Client written as a Python 3 class using asyncio. -# -'''5G-MAG Reference Tools: M1 Client class -======================================= - -This class provides a simple interface for maintaining a connection to an M1 -server, converting Python types to the various M1 requests, parsing the -responses and conversion back to Python types or Exceptions when errors have -occurred. This class will ensure that the out going request headers are -formatted according to 3GPP TS 26.512. Message bodies are passed through as -is and therefore it is the responsibility of the application using this class -to format the body correctly. - -This class is not intended to maintain client state for an M1 session, that -should be performed outside of this class. -''' -import datetime -import json -import logging -from typing import Optional, Union, Tuple, Dict, Any, TypedDict, List - -import httpx - -from .exceptions import (M1ClientError, M1ServerError) -from .types import (ApplicationId, ContentHostingConfiguration, ContentProtocols, - ConsumptionReportingConfiguration, PolicyTemplate, - ProvisioningSessionType, ProvisioningSession, ResourceId) - -class TagAndDateResponse(TypedDict, total=False): - '''Response containing ETag and Last-Modified headers - ''' - ETag: str - LastModified: datetime.datetime - -class ProvisioningSessionResponse(TagAndDateResponse, total=False): - '''Response containing a provisioning session object - ''' - ProvisioningSessionId: ResourceId - ProvisioningSession: ProvisioningSession - -class ContentHostingConfigurationResponse(TagAndDateResponse, total=False): - '''Response containing a content hosting configuration - ''' - ProvisioningSessionId: ResourceId - ContentHostingConfiguration: ContentHostingConfiguration - -class ServerCertificateResponse(TagAndDateResponse, total=False): - '''Response containing a server certificate - ''' - ProvisioningSessionId: ResourceId - ServerCertificateId: ResourceId - ServerCertificate: str - -class ServerCertificateSigningRequestResponse(TagAndDateResponse, total=False): - '''Response containing a CSR for a reserved certificate - ''' - ProvisioningSessionId: ResourceId - ServerCertificateId: ResourceId - CertificateSigningRequestPEM: str - -class ContentProtocolsResponse(TagAndDateResponse, total=False): - '''Response containing a ContentProtocols object - ''' - ProvisioningSessionId: ResourceId - ContentProtocols: ContentProtocols - -class ConsumptionReportingConfigurationResponse(TagAndDateResponse, total=False): - '''Response containing a consumption reporting configuration - ''' - ProvisioningSessionId: ResourceId - ConsumptionReportingConfiguration: ConsumptionReportingConfiguration - -class PolicyTemplateResponse(TagAndDateResponse, total=False): - '''Response containing a policy template - ''' - ProvisioningSessionId: ResourceId - PolicyTemplate: PolicyTemplate - -class M1Client: - '''5G-MAG Reference Tools: M1 Client - ''' - - def __init__(self, host_address: Tuple[str,int]): - ''' - Constructor - - :param Tuple[str,int] host_address: 5GMS Application Function to connect to as a tuple of hostname/ip-addr and TCP port - number. - ''' - self.__host_address = host_address - self.__connection = None - self.__log = logging.getLogger(__name__ + '.' + self.__class__.__name__) - - # TS26512_M1_ProvisioningSession - - async def createProvisioningSession(self, provisioning_session_type: ProvisioningSessionType, - external_application_id: ApplicationId, - asp_id: Optional[ApplicationId] = None - ) -> Optional[ProvisioningSessionResponse]: - ''' - Create a provisioning session on the 5GMS Application Function - - :param ProvisioningSessionType provisioning_session_type: The provisioning session type. - :param str external_application_id: The application ID of the external application requesting the new provisioning - session. - :param Optional[str] asp_id: The Application Server Provider ID. - - :return: the ResourceId of the allocated provisioning session or None if there was an error. - - :raises M1ClientError: if there was a problem with the request - :raises M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - self.__debug('M1Client.createProvisioningSession(%r, %r, asp_id=%r)', - provisioning_session_type, external_application_id, asp_id) - send: ProvisioningSession = { - 'provisioningSessionType': provisioning_session_type, - 'appId': external_application_id - } - if asp_id is not None: - send['aspId'] = asp_id - result = await self.__do_request('POST', '/provisioning-sessions', json.dumps(send), - 'application/json') - if result['status_code'] == 201: - ret: ProvisioningSessionResponse = {'ProvisioningSessionId': result['headers']['location'].split('/')[-1]} - if len(result['body']) > 0: - ret.update(self.__tag_and_date(result)) - ret['ProvisioningSession'] = ProvisioningSession.fromJSON(result['body']) - return ret - self.__default_response(result) - return None - - async def getProvisioningSessionById(self, - provisioning_session_id: ResourceId - ) -> Optional[ProvisioningSessionResponse]: - ''' - Get a provisioning session from the 5GMS Application Function - - :param ResourceId provisioning_session_id: The provisioning session to find. - - :return: a ProvisioningSessionResponse structure if the provisioning session was found, or None if the provisioning - session was not found. - - :raises M1ClientError: if there was a problem with the request - :raises M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.__do_request('GET', - '/provisioning-sessions/' + provisioning_session_id, '', - 'application/json') - if result['status_code'] == 200: - ret: ProvisioningSessionResponse = self.__tag_and_date(result) - ret.update({ - 'ProvisioningSessionId': provisioning_session_id, - 'ProvisioningSession': ProvisioningSession.fromJSON(result['body']) - }) - return ret - if result['status_code'] == 404: - return None - self.__default_response(result) - return None - - async def destroyProvisioningSession(self, provisioning_session_id: ResourceId) -> bool: - ''' - Destroy a provisioning session on the 5GMS Application Function - - :param ResourceId provisioning_session_id: The provisioning session to find. - - :return: True if a provisioning session was deleted (or pending deletion) or False if there was no action. - - :raise M1ClientError: if there was a problem with the request - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.__do_request('DELETE', - '/provisioning-sessions/' + provisioning_session_id, '', - 'application/json') - if result['status_code'] == 204 or result['status_code'] == 202: - return True - self.__default_response(result) - return False - - # TS26512_M1_ContentHostingProvisioning - - async def createContentHostingConfiguration(self, provisioning_session_id: ResourceId, - content_hosting_configuration: ContentHostingConfiguration - ) -> Union[bool,ContentHostingConfigurationResponse]: - ''' - Create a content hosting configuration for a provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to create the content hosting configuration in. - :param ContentHostingConfiguration content_hosting_configuration: The content hosting configuration template to use. - - :return: True if the ContentHostingConfiguration was accepted but the response was empty, False if the - ContentHostingConfiguration was not accepted or a ContentHostingConfigurationResponse if the - ContentHostingConfiguration was accepted and the AF updated version returned. - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.__do_request('POST', - f'/provisioning-sessions/{provisioning_session_id}/content-hosting-configuration', - json.dumps(content_hosting_configuration), 'application/json') - if result['status_code'] == 201: - if len(result['body']) > 0: - ret: ContentHostingConfigurationResponse = self.__tag_and_date(result) - ret.update({ - 'ProvisioningSessionId': provisioning_session_id, - }) - if len(result['body']) > 0: - ret.update({ - 'ContentHostingConfiguration': ContentHostingConfiguration.fromJSON( - result['body']) - }) - return ret - return True - self.__default_response(result) - return False - - async def retrieveContentHostingConfiguration(self, provisioning_session_id: ResourceId - ) -> Optional[ContentHostingConfigurationResponse]: - ''' - Fetch the content hosting configuration for a provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to fetch the current content hosting configuration - for. - - :return: None if the provisioning session does not exist, also returns None if the - provisioning session exists but does not have a content hosting configuration, - otherwise returns a ContentHostingConfigurationResponse. - - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.__do_request('GET', - f'/provisioning-sessions/{provisioning_session_id}/content-hosting-configuration', - '', 'application/json') - if result['status_code'] == 200: - ret: ContentHostingConfigurationResponse = self.__tag_and_date(result) - ret.update({ - 'ProvisioningSessionId': provisioning_session_id, - 'ContentHostingConfiguration': ContentHostingConfiguration.fromJSON(result['body']) - }) - return ret - if result['status_code'] == 404: - return None - self.__default_response(result) - return None - - async def updateContentHostingConfiguration(self, provisioning_session_id: ResourceId, - content_hosting_configuration: ContentHostingConfiguration - ) -> bool: - ''' - Update a content hosting configuration for a provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to update the current content hosting configuration - for. - :param ContentHostingConfiguration content_hosting_configuration: The new content hosting configuration to apply. - - :return: ``True`` if the update succeeded or ``False`` if the update failed. - - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.__do_request('PUT', f'/provisioning-sessions/{provisioning_session_id}/content-hosting-configuration', - json.dumps(content_hosting_configuration), 'application/json') - if result['status_code'] == 204: - return True - if result['status_code'] == 404: - return False - self.__default_response(result) - return False - - async def patchContentHostingConfiguration(self, provisioning_session_id: ResourceId, patch: str - ) -> Union[bool,ContentHostingConfigurationResponse]: - ''' - Patch a content hosting configuration for a provisioning session using a JSON patch - - :param ResourceId provisioning_session_id: The provisioning session to update the current content hosting configuration - for. - :param str patch: The patch information in JSON patch format. - - :return: a `ContentHostingConfigurationResponse` if the patch succeeded and the new ContentHostingConfiguration was - returned, or True if the patch succeeded but no ContentHostingConfiguration was returned, or False if the - patch failed. - - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.__do_request('PATCH', - f'/provisioning-sessions/{provisioning_session_id}/content-hosting-configuration', - - patch, 'application/json-patch+json') - if result['status_code'] == 200: - if len(result['body']) > 0: - ret: ContentHostingConfigurationResponse = self.__tag_and_date(result) - ret.update({ - 'ProvisioningSessionId': provisioning_session_id, - 'ContentHostingConfiguration': ContentHostingConfiguration.fromJSON( - result['body']) - }) - return ret - return True - if result['status_code'] == 404: - return False - self.__default_response(result) - return False - - async def destroyContentHostingConfiguration(self, provisioning_session_id: ResourceId - ) -> bool: - ''' - Delete a content hosting configuration for a provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to remove the content hosting configuration for. - - :return: True if the ContentHostingConfiguration was deleted or False if the ContentHostingConfiguration did not exist. - - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.__do_request('DELETE', - f'/provisioning-sessions/{provisioning_session_id}/content-hosting-configuration', - '', 'application/json') - if result['status_code'] == 204 or result['status_code'] == 202: - return True - if result['status_code'] == 404: - return False - self.__default_response(result) - return False - - async def purgeContentHostingCache(self, provisioning_session_id: ResourceId, - filter_regex: Optional[str] = None) -> Optional[int]: - ''' - Purge cache entries for a provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to purge cache entries for. - :param Optional[str] filter_regex: Optional regular expression to match the cache entries origin URL path. - - :return: the number of purged entries, or None if no purge took place. - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - body = '' - if filter_regex is not None: - body = f'pattern={filter_regex}' - result = await self.__do_request('POST', - f'/provisioning-sessions/{provisioning_session_id}/content-hosting-configuration/purge', - body, 'application/x-www-form-urlencoded') - if result['status_code'] == 200: - return int(result['body']) - if result['status_code'] == 204: - return None - self.__default_response(result) - return None - - # TS26512_M1_ServerCertificatesProvisioning - - async def createOrReserveServerCertificate(self, provisioning_session_id: ResourceId, extra_domain_names: Optional[List[str]] = None, csr: bool = False) -> Optional[ServerCertificateSigningRequestResponse]: - '''Create or reserve a server certificate for a provisioing session - - :param ResourceId provisioning_session_id: The provisioning session to create the new certificate entry in. - :param extra_domain_names: An optional list of extra domain names to include a CSR as SubjectAltName entries. - :param bool csr: Whether to reserve a certificate and return the CSR PEM data. - - If *csr* is ``True`` then this will reserve the certificate and request the CSR PEM data be returned along side the Id - of the newly reserved certificate. The *extra_domain_names* parameter may contain a list of extra domain names to include - in the SubjectAltNames extension. - - If *csr* is ``False`` or not provided then create a new certificate and just return the new certificate Id. The - *extra_domain_names* must be an empty list or ``None``. - - :return: a `ServerCertificateSigningRequestResponse` containing the certificate id and metadata optionally with CSR PEM - data if *csr* was ``True``. - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - - url = f'/provisioning-sessions/{provisioning_session_id}/certificates' - if extra_domain_names is not None and not isinstance(extra_domain_names,list): - raise M1ServerError(reason = f'Bad parameter passed during certificate creation', status_code = 500) - if csr: - url += '?csr=true' - elif extra_domain_names is not None and len(extra_domain_names) > 0: - raise M1ClientError(reason = f'Extra domain names cannot be specified when not generating a CSR', status_code = 400) - body='' - if extra_domain_names is not None and len(extra_domain_names) > 0: - body = json.dumps(extra_domain_names) - result = await self.__do_request('POST', url, body, 'application/json') - if result['status_code'] == 200: - certificate_id = result['headers'].get('Location','').rsplit('/',1)[1] - ret: ServerCertificateSigningRequestResponse = self.__tag_and_date(result) - ret.update({ - 'ProvisioningSessionId': provisioning_session_id, - 'ServerCertificateId': certificate_id, - }) - if csr and len(result['body']) > 0: - ret.update({ - 'CertificateSigningRequestPEM': result['body'], - }) - return ret - self.__default_response(result) - return None - - async def createServerCertificate(self, provisioning_session_id: ResourceId) -> ServerCertificateResponse: - '''Create a new certificate for a provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to create the new certificate entry in. - - :return: a ServerCertificateResponse for the newly created certificate. - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.createOrReserveServerCertificate(provisioning_session_id, csr=False) - return result - - async def reserveServerCertificate(self, provisioning_session_id: ResourceId, extra_domain_names: Optional[List[str]] = None) -> ServerCertificateSigningRequestResponse: - '''Reserve a certificate for a provisioning session and get the CSR PEM - - :param ResourceId provisioning_session_id: The provisioning session to create the new certificate entry in. - :param extra_domain_names: An optional list of extra domain names to include as Subject Alt Names. - - :return: a `ServerCertificateSigningRequestResponse` containing the CSR as a PEM string plus metadata for the reserved - certificate. - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.createOrReserveServerCertificate(provisioning_session_id, extra_domain_names=extra_domain_names, csr=True) - if result is None or 'CertificateSigningRequestPEM' not in result: - raise M1ClientError(reason = f'Failed to retrieve CSR for session {provisioning_session_id}', status_code = 200) - return result - - async def uploadServerCertificate(self, provisioning_session_id: ResourceId, certificate_id: ResourceId, pem_data: str) -> bool: - '''Upload the signed public certificate for a reserved certificate - - :param ResourceId provisioning_session_id: The provisioning session the certificate was reserved for. - :param ResourceId certificate_id: The certificate Id of the reserved certificate. - :param str pem_data: A string containing the PEM data for the public certificate to upload. - - :return: ``True`` if successful or ``False`` if the certificate has already been uploaded. - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.__do_request('PUT', - f'/provisioning-sessions/{provisioning_session_id}/certificates/{certificate_id}', - pem_data, 'application/x-pem-file') - if result['status_code'] == 204: - return True - self.__default_response(result) - return False - - async def retrieveServerCertificate(self, provisioning_session_id: ResourceId, certificate_id: ResourceId) -> Optional[ServerCertificateResponse]: - '''Retrieve the public certificate for a given certificate Id - - :param ResourceId provisioning_session_id: The provisioning session for the certificate. - :param ResourceId certificate_id: The certificate Id of the certificate. - - :return: a ServerCertificateResponse containing the PEM data for the public certificate and its metadata or ``None`` - if the certificate is reserved and awaiting upload. - - :raise M1ClientError: if there was a problem with the request or the certificate was not found. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.__do_request('GET', - f'/provisioning-sessions/{provisioning_session_id}/certificates/{certificate_id}', - '', 'application/octet-stream') - if result['status_code'] == 200: - ret: ServerCertificateResponse = self.__tag_and_date(result) - ret['ProvisioningSessionId'] = provisioning_session_id - ret['ServerCertificateId'] = certificate_id - ret['ServerCertificate'] = result['body'] - return ret - if result['status_code'] == 204: - return None - if result['status_code'] == 404: - raise M1ClientError(reason="Certificate not found", status_code=404) - self.__default_response(result) - return None - - async def destroyServerCertificate(self, provisioning_session_id: ResourceId, certificate_id: ResourceId) -> bool: - '''Delete a certificate. - - :param ResourceId provisioning_session_id: The provisioning session for the certificate. - :param ResourceId certificate_id: The certificate Id of the certificate. - - :return: ``True`` if the certificate has been deleted. - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.__do_request('DELETE', - f'/provisioning-sessions/{provisioning_session_id}/certificates/{certificate_id}', - '', 'application/octet-stream') - if result['status_code'] == 204 or result['status_code'] == 202: - return True - self.__default_response(result) - return False - - # TS26512_M1_ContentProtocolsDiscovery - async def retrieveContentProtocols(self, provisioning_session_id: ResourceId) -> Optional[ContentProtocolsResponse]: - '''Get the ContentProtocols information for the provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to get the ContentProtocols for. - - :return: a `ContentProtocolsResponse` containing the ContentProtocols structure and metadata or None if the - provisioning session was not found. - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.__do_request('GET', - f'/provisioning-sessions/{provisioning_session_id}/protocols', - '', 'application/octet-stream') - if result['status_code'] == 200: - ret: ContentProtocolsResponse = self.__tag_and_date(result) - ret['ContentProtocols'] = ContentProtocols.fromJSON(result['body']) - return ret - self.__default_response(result) - return None - - # TS26512_M1_ConsumptionReportingProvisioning - async def activateConsumptionReportingConfiguration(self, provisioning_session_id: ResourceId, consumption_reporting_config: ConsumptionReportingConfiguration) -> Union[Optional[ConsumptionReportingConfigurationResponse],bool]: - '''Set the ConsumptionReportingConfiguration for the provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to set the ConsumptionReportingConfiguration for. - :param ConsumptionReportingConfiguration consumption_reporting_config: The ConsumptionReportingConfiguration to set. - - :return: `True` if the ConsumptionReportingConfiguration was set and the Application Function didn't report back the - configuration, or a `ConsumptionReportingConfigurationResponse` if the configuration was reported back, or `None` - if setting the configuration failed. - - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.__do_request('POST', - f'/provisioning-sessions/{provisioning_session_id}/consumption-reporting-configuration', - json.dumps(consumption_reporting_config), 'application/json') - if result['status_code'] == 200: - ret: ConsumptionReportingConfigurationResponse = self.__tag_and_date(result) - ret['ConsumptionReportingConfiguration'] = ConsumptionReportingConfiguration.fromJSON(result['body']) - return ret - elif result['status_code'] == 204: - return True - self.__default_response(result) - return None - - async def retrieveConsumptionReportingConfiguration(self, provisioning_session_id: ResourceId) -> Optional[ConsumptionReportingConfigurationResponse]: - '''Get the ConsumptionReportingConfiguration for the provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to get the ConsumptionReportingConfiguration for. - - :return: A `ConsumptionReportingConfigurationResponse` for the current configuration in the provisioning session. - - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.__do_request('GET', - f'/provisioning-sessions/{provisioning_session_id}/consumption-reporting-configuration', - '', 'application/octet-stream') - if result['status_code'] == 200: - ret: ConsumptionReportingConfigurationResponse = self.__tag_and_date(result) - ret['ConsumptionReportingConfiguration'] = ConsumptionReportingConfiguration.fromJSON(result['body']) - return ret - if result['status_code'] == 404: - return None - self.__default_response(result) - return None - - async def updateConsumptionReportingConfiguration(self, provisioning_session_id: ResourceId, consumption_reporting_config: ConsumptionReportingConfiguration) -> bool: - '''Modify the ConsumptionReportingConfiguration for the provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to modify the ConsumptionReportingConfiguration for. - :param ConsumptionReportingConfiguration consumption_reporting_config: The ConsumptionReportingConfiguration to apply. - - :return: `True` if the configuration was changed successfully. - - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.__do_request('PUT', - f'/provisioning-sessions/{provisioning_session_id}/consumption-reporting-configuration', - json.dumps(consumption_reporting_config), 'application/json') - if result['status_code'] == 204: - return True - self.__default_response(result) - return False - - async def patchConsumptionReportingConfiguration(self, provisioning_session_id: ResourceId, patch: str) -> ConsumptionReportingConfigurationResponse: - '''Patch the ConsumptionReportingConfiguration for the provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to modify the ConsumptionReportingConfiguration for. - :param str patch: The JSON patch to apply to the ConsumptionReportingConfiguration. - - :return: A `ConsumptionReportingConfigurationResponse` containing the new configuration after the patch is applied. - - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.__do_request('PATCH', - f'/provisioning-sessions/{provisioning_session_id}/consumption-reporting-configuration', - patch, 'application/json-patch+json') - if result['status_code'] == 200: - ret: ConsumptionReportingConfigurationResponse = self.__tag_and_date(result) - ret['ConsumptionReportingConfiguration'] = ConsumptionReportingConfiguration.fromJSON(result['body']) - return ret - self.__default_response(result) - return None - - async def destroyConsumptionReportingConfiguration(self, provisioning_session_id: ResourceId) -> bool: - '''Remove the ConsumptionReportingConfiguration from the provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to remove the ConsumptionReportingConfiguration from. - - :return: `True` if the ConsumptionReportingConfiguration was successfully removed. - - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - result = await self.__do_request('DELETE', - f'/provisioning-sessions/{provisioning_session_id}/consumption-reporting-configuration', - '', 'application/octet-stream') - if result['status_code'] == 204: - return True - self.__default_response(result) - return False - - # TS26512_M1_ContentPreparationTemplatesProvisioning - #async def createContentPreparationTemplate(self, provisioning_session_id: ResourceId, content_preparation_template: Any) -> Optional[ResourceId]: - #async def retrieveContentPreparationTemplate(self, provisioning_session_id: ResourceId, content_preparation_template_id: ResourceId) -> ContentPreparationTemplateResponse: - #async def updateContentPreparationTemplate(self, provisioning_session_id: ResourceId, content_preparation_template_id: ResourceId, content_preparation_template: Any) -> bool: - #async def patchContentPreparationTemplate(self, provisioning_session_id: ResourceId, content_preparation_template_id: ResourceId, patch: str) -> ContentPreparationTemplateResponse: - #async def destroyContentPreparationTemplate(self, provisioning_session_id: ResourceId, content_preparation_template_id: ResourceId) -> bool: - - # TS26512_M1_EdgeResourcesProvisioning - #async def createEdgeResourcesConfiguration(self, provisioning_session_id: ResourceId, edge_resource_config: EdgeResourceConfiguration) -> Optional[ResourceId]: - #async def retrieveEdgeResourcesConfiguration(self, provisioning_session_id: ResourceId, edge_resource_config_id: ResourceId) -> EdgeResourceConfigurationResponse: - #async def updateEdgeResourcesConfiguration(self, provisioning_session_id: ResourceId, edge_resource_config_id: ResourceId, edge_resource_config: EdgeResourceConfiguration) -> bool: - #async def patchEdgeResourcesConfiguration(self, provisioning_session_id: ResourceId, edge_resource_config_id: ResourceId, patch: str) -> EdgeResourceConfigurationResponse: - #async def destroyEdgeResourcesConfiguration(self, provisioning_session_id: ResourceId, edge_resource_config_id: ResourceId) -> bool: - - # TS26512_M1_EventDataProcessingProvisioning - #async def createEventDataProcessingConfiguration(self, provisioning_session_id: ResourceId, event_data_processing_config: EventDataProcessingConfiguration) -> Optional[ResourceId]: - #async def retrieveEventDataProcessingConfiguration(self, provisioning_session_id: ResourceId, event_data_processing_config_id: ResourceId) -> EventDataProcessingConfigurationResponse: - #async def updateEventDataProcessingConfiguration(self, provisioning_session_id: ResourceId, event_data_processing_config_id: ResourceId, event_data_processing_config: EventDataProcessingConfiguration) -> bool: - #async def patchEventDataProcessingConfiguration(self, provisioning_session_id: ResourceId, event_data_processing_config_id: ResourceId, patch: str) -> EventDataProcessingConfigurationResponse: - #async def destroyEventDataProcessingConfiguration(self, provisioning_session_id: ResourceId, event_data_processing_config_id: ResourceId) -> bool: - - # TS26512_M1_MetricsReportingProvisioning - #async def activateMetricsReporting(self, provisioning_session_id: ResourceId, metrics_reporting_config: MetricsReportingConfiguration) -> ResourceId: - #async def retrieveMetricsReportingConfiguration(self, provisioning_session_id: ResourceId, metrics_reporting_config_id: ResourceId) -> MetricsReportingConfigurationResponse: - #async def updateMetricsReportingConfiguration(self, provisioning_session_id: ResourceId, metrics_reporting_config_id: ResourceId, metrics_reporting_config: MetricsReportingConfiguration) -> bool: - #async def patchMetricsReportingConfiguration(self, provisioning_session_id: ResourceId, metrics_reporting_config_id: ResourceId, patch: str) -> MetricsReportingConfigurationResponse: - #sync def destroyMetricsReportingConfiguration(self, provisioning_session_id: ResourceId, metrics_reporting_config_id: ResourceId) -> bool: - - # TS26512_M1_PolicyTemplatesProvisioning - async def createPolicyTemplate(self, provisioning_session_id: ResourceId, policy_template: PolicyTemplate) -> Optional[PolicyTemplateResponse]: - '''Create a new PolicyTemplate in a provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to add the PolicyTemaplet to. - :param PolicyTemplate policy_template: The PolicyTemplate to add to the provisioning session. - :return: The PolicyTemplateResponse if successful. - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the policy template. - ''' - result = await self.__do_request('POST', - f'/provisioning-sessions/{provisioning_session_id}/policy-templates', - json.dumps(policy_template), 'application/json') - if result['status_code'] == 200: - ret: PolicyTemplateResponse = self.__tag_and_date(result) - ret['PolicyTemplate'] = PolicyTemplate.fromJSON(result['body']) - return ret - if result['status_code'] == 201 or result['status_code'] == 204: - pol_temp_id: ResourceId = result['headers'].get('location').rsplit('/',1)[-1] - return await self.retrievePolicyTemplate(provisioning_session_id, pol_temp_id) - self.__default_response(result) - return None - - async def retrievePolicyTemplate(self, provisioning_session_id: ResourceId, policy_template_id: ResourceId) -> Optional[PolicyTemplateResponse]: - '''Retrieve a PolicyTemplate for a provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to retrieve the PolicyTemplate from. - :param ResourceId policy_template_id: The PolicyTemplate Id of the PolicyTemplate to retrieve. - :return: A `PolicyTemplateResponse` which holds the `PolicyTemplate` and the caching metadata or `None` if the - `PolicyTemplate` cannot be found. - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the retrieval of the policy template. - ''' - result = await self.__do_request('GET', - f'/provisioning-sessions/{provisioning_session_id}/policy-templates/{policy_template_id}', - '', 'application/json') - if result['status_code'] == 200: - ret: PolicyTemplateResponse = self.__tag_and_date(result) - ret['PolicyTemplate'] = PolicyTemplate.fromJSON(result['body']) - return ret - if result['status_code'] == 404: - return None - self.__default_response(result) - return None - - async def updatePolicyTemplate(self, provisioning_session_id: ResourceId, policy_template_id: ResourceId, policy_template: PolicyTemplate) -> bool: - '''Update an existing PolicyTemplate in a provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to replace the PolicyTemplate in. - :param ResourceId policy_template_id: The PolicyTemplate Id of the PolicyTemplate to replace. - :param PolicyTemplate policy_template: The PolicyTemplate which will replace the existing one in the provisioning session. - :return: `True` if the update succeeded. - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the update of the policy template. - ''' - result = await self.__do_request('PUT', - f'/provisioning-sessions/{provisioning_session_id}/policy-templates/{policy_template_id}', - json.dumps(policy_template), 'application/json') - if result['status_code'] == 204: - return True - if result['status_code'] == 404: - return False - self.__default_response(result) - return False - - async def patchPolicyTemplate(self, provisioning_session_id: ResourceId, policy_template_id: ResourceId, patch: str) -> Optional[PolicyTemplateResponse]: - '''Patch a PolicyTemplate for the provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to modify the PolicyTemplate for. - :param ResourceId policy_template_id: The PolicyTemplateId for the PolicyTemplate in the provisioning session. - :param str patch: The JSON patch to apply to the ConsumptionReportingConfiguration. - - :return: A `PolicyTemplateResponse` containing the new template after the patch is applied. - - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the patching of the policy template. - ''' - result = await self.__do_request('PATCH', - f'/provisioning-sessions/{provisioning_session_id}/policy-templates/{policy_template_id}', - patch, 'application/json-patch+json') - if result['status_code'] == 200: - ret: PolicyTemplateResponse = self.__tag_and_date(result) - ret['PolicyTemplate'] = PolicyTemplate.fromJSON(result['body']) - return ret - if result['status_code'] == 404: - return None - self.__default_response(result) - return None - - async def destroyPolicyTemplate(self, provisioning_session_id: ResourceId, policy_template_id: ResourceId) -> bool: - '''Destroy a PolicyTemplate in a provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to modify the PolicyTemplate for. - :param ResourceId policy_template_id: The PolicyTemplateId for the PolicyTemplate in the provisioning session. - - :return: `True` if the PolicyTemplate was deleted or `False` if the PolicyTemplate didn't exist. - - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the deletion of the provisioning session. - ''' - result = await self.__do_request('DELETE', - f'/provisioning-sessions/{provisioning_session_id}/policy-templates/{policy_template_id}', - '', 'application/json') - if result['status_code'] == 204: - return True - if result['status_code'] == 404: - return False - self.__default_response(result) - return False - - # Private methods - - async def __do_request(self, method: str, url_suffix: str, body: Union[str,bytes], - content_type: str, headers: Optional[dict] = None) -> Dict[str,Any]: - '''Send a request to the 5GMS Application Function - - :meta private: - :param str method: The HTTP method for the request. - :param str url_suffix: The URL path suffix for the request after the protocol and version identifiers. - :param Union[str,bytes] body: The body of the request as a `str` or `bytes`. - :param str content_type: The content type to use in the ``Content-Type`` header of the request. - :param Optional[dict] headers: Extra headers to go along with the request. - :return: a `dict` with 3 entries ``status_code``, ``body`` and ``headers`` representing the HTTP response status code, - the response message body and the response headers. - :raise M1ServerError: if communication with the AF failed. - ''' - # pylint: disable=too-many-arguments - if isinstance(body, str): - body = bytes(body, 'utf-8') - req_headers = {'Content-Type': content_type} - if headers is not None: - req_headers.update(headers) - url = f'http://{self.__host_address[0]}:{self.__host_address[1]}/3gpp-m1/v2{url_suffix}' - if self.__connection is None: - self.__connection = httpx.AsyncClient(http1=True, http2=False, - headers={'User-Agent': '5GMS-AF/testing'}) - req = self.__connection.build_request(method, url, headers=req_headers, data=body) - try: - resp = await self.__connection.send(req) - except httpx.RemoteProtocolError as err: - raise M1ServerError(reason=f'Communication with the Application Function failed: {err}', status_code=500) - return {'status_code': resp.status_code, 'body': resp.text, 'headers': resp.headers} - - def __default_response(self, result: Dict[str,Any]) -> None: - '''Handle default actions for all responses from the 5GMS Application Function - - This will raise exceptions for 4XX and 5XX response codes. - - :meta private: - :param Dict[str,Any] result: The result as returned by `__do_request`. - - :raise M1ClientError: if there was a problem with the request. - :raise M1ServerError: if there was a server side issue preventing the creation of the provisioning session. - ''' - if result['status_code'] >= 400 and result['status_code'] < 500: - raise M1ClientError(reason='M1 operation failed: '+str(result['body']), - status_code=result['status_code']) - if result['status_code'] >= 500 and result['status_code'] < 600: - raise M1ServerError(reason='M1 operation failed: '+str(result['body']), - status_code=result['status_code']) - - @staticmethod - def __tag_and_date(result: Dict[str,Any]) -> TagAndDateResponse: - '''Get the response message standard metadata - - This will extract metadata from ``ETag``, ``Last-Modified`` and ``Cache-Control`` headers. - - :param Dict[str,Any] result: The result as returned by `__do_request`. - - :return: the base TagAndDateResponse structure for all response messages. - ''' - # Get ETag - ret = {'ETag': result['headers'].get('etag')} - # Get Last-Modified as a datetime.datetime - lm_dt = result['headers'].get('last-modified') - if lm_dt is not None: - try: - lm_dt = datetime.datetime.strptime(lm_dt, '%a, %d %b %Y %H:%M:%S %Z').replace( - tzinfo=datetime.timezone.utc) - except ValueError: - try: - lm_dt = datetime.datetime.strptime(lm_dt, '%A, %d-%b-%y %H:%M:%S %Z').replace( - tzinfo=datetime.timezone.utc) - except ValueError: - try: - lm_dt = datetime.datetime.strptime(lm_dt, '%a %b %d %H:%M:%S %Y').replace( - tzinfo=datetime.timezone.utc) - except ValueError: - lm_dt = None - ret['Last-Modified'] = lm_dt - # Get Cache-Control as a cache expiry time - cc = result['headers'].get('cache-control') - if cc is not None: - age = result['headers'].get('age') - if age is None: - age = 0 - else: - age = int(age) - max_age_values = [int(c[8:]) for c in [v.strip() for v in cc.split(',')] if c[:8] == 'max-age='] - if len(max_age_values) > 0: - cc = datetime.datetime.now(tz=datetime.timezone.utc)+datetime.timedelta(seconds=min(max_age_values)-age) - else: - cc = None - ret['Cache-Until'] = cc - return ret - - def __debug(self, *args, **kwargs) -> None: - '''Output a debug message - - :meta private: - :param args: Positional arguments to pass to `logger.debug()`. - :param kwargs: Keyword arguments to pass to `logger.debug()`. - ''' - self.__log.debug(*args, **kwargs) - -__all__ = [ - # Types - 'ProvisioningSessionResponse', - 'ContentHostingConfigurationResponse', - 'ConsumptionReportingConfigurationResponse', - 'ServerCertificateResponse', - 'ServerCertificateSigningRequestResponse', - 'ContentProtocolsResponse', - 'PolicyTemplateResponse', - # Classes - 'M1Client', - ] diff --git a/tools/python3/lib/rt_m1_client/configuration.py b/tools/python3/lib/rt_m1_client/configuration.py deleted file mode 100644 index 99f4497..0000000 --- a/tools/python3/lib/rt_m1_client/configuration.py +++ /dev/null @@ -1,179 +0,0 @@ -#!/usr/bin/python3 -#============================================================================== -# 5G-MAG Reference Tools: M1 Session Configuration -#============================================================================== -# -# File: rt_m1_client/configuration.py -# License: 5G-MAG Public License (v1.0) -# Author: David Waring -# Copyright: (C) 2022-2023 British Broadcasting Corporation -# -# For full license terms please see the LICENSE file distributed with this -# program. If this file is missing then the license can be retrieved from -# https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -# -#============================================================================== -# -# M1 Session Configuration class -# ============================== -# -# This module contains a configuration class which hold application -# configuration for programs which use the M1 Session class. -# -'''5G-MAG Reference Tools: M1 Session Configuration -================================================ - -''' -import configparser -import os -import os.path - -from typing import List - -class Configuration: - '''Application configuration container - - This class handles the loading and saving of the application configuration - ''' - - DEFAULT_CONFIG='''[DEFAULT] - log_dir = /var/log/rt-5gms - state_dir = /var/cache/rt-5gms - run_dir = /run/rt-5gms - - [m1-client] - log_level = info - data_store = %(state_dir)s/m1-client - m1_address = 127.0.0.23 - m1_port = 7777 - asp_id = - external_app_id = please-change-this - certificate_signing_class = rt_m1_client.certificates.DefaultCertificateSigner - ''' #: The default configuration - - def __init__(self): - '''Constructor - - Will load the previous configuration from ``/etc/rt-5gms/m1-client.conf`` if the command is run by root or - ``~/.rt-5gms/m1-client.conf`` if run by any other user. - ''' - self.__config_filename = None - if os.getuid() != 0: - self.__config_filename = os.path.expanduser(os.path.join('~', '.rt-5gms', 'm1-client.conf')) - else: - self.__config_filename = os.path.join(os.path.sep, 'etc', 'rt-5gms', 'm1-client.conf') - self.__default_config = configparser.ConfigParser() - self.__default_config.read_string(self.DEFAULT_CONFIG) - self.__config = configparser.ConfigParser() - self.__config.read_string(self.DEFAULT_CONFIG) - if os.path.exists(self.__config_filename): - self.__config.read(self.__config_filename) - - def isKey(self, key: str) -> str: - '''Does a configuration field key exist? - - This tests *key* for being a valid configuration option field key name. - - :returns: The key string if it is a valid configuration field key. - :raises: ValueError if the key string does not match a known configuration field key. - ''' - if key in self.__default_config['m1-client']: - return key - raise ValueError('Not a valid configuration option') - - def get(self, key: str, default: str = None, raw: bool = False) -> str: - '''Get a configuration value - - Retrieves the value for configuration option *key*. If the *key* does not exist the *default* will be returned. If *raw* is - ``True`` and the *key* option exists then the raw configuration (without ``%()`` interpolation) value will be returned. - - :returns: The configuration option *key* value or *default* if key does not exist. - ''' - return self.__config.get('m1-client', key, raw=raw, fallback=default) - - def set(self, key: str, value: str) -> bool: - '''Set a configuration value - - Sets the raw *value* for configuration option *key*. If *key* is not a valid configuration option then ValueError exception - will be raised. - - The configuration is saved once the *key* option has been set. - ''' - self.isKey(key) - if key in self.__default_config['DEFAULT']: - section = 'DEFAULT' - else: - section = 'm1-client' - self.__config.set(section, key, value) - self.__saveConfig() - return True - - def isDefault(self, key: str) -> bool: - '''Checks if a key contains the default configuration value - - :returns: ``True`` if the configuration value for *key* is the default value, or ``False`` otherwise. - ''' - return self.__config.get('m1-client', key) == self.__default_config.get('m1-client', key) - - def getKeys(self) -> List[str]: - '''Get a list of configuration field name keys - - :returns: A list of configuration key names. - ''' - return list(self.__default_config['m1-client'].keys()) - - def resetValue(self, key: str) -> bool: - '''Reset a configuration field to its default value - - :returns: ``True`` if the field was reset or ``False`` if the field already contained the default value. - ''' - if self.isDefault(key): - return False - return self.set(key, self.__default_config.get('m1-client', key)) - - def __saveConfig(self): - '''Save the current configuration to local storage - - :meta private-method: - - Will save the current configuration to the relevant local file. Fields with the default value will be saved as a comment. - ''' - cfgdir = os.path.dirname(self.__config_filename) - if not os.path.exists(cfgdir): - old_umask = os.umask(0) - try: - os.makedirs(cfgdir, mode=0o755) - finally: - os.umask(old_umask) - with open(self.__config_filename, 'w') as cfgout: - for section in ['DEFAULT'] + self.__config.sections(): - cfgout.write(f'[{section}]\n') - for key in self.__config[section]: - cfgvalue = self.__config.get(section, key, raw=True) - defvalue = self.__default_config.get(section, key, raw=True) - if (section == 'DEFAULT' or key not in self.__config['DEFAULT']): - if cfgvalue == defvalue: - cfgout.write('#') - cfgout.write(f'{key} = {cfgvalue}\n') - cfgout.write('\n') - - def __str__(self): - '''String representation of the configuration - - :returns: A ``str`` representing the configuration. - ''' - buf = StringIO() - self.__config.write(buf) - return buf.getvalue() - - def __repr__(self): - '''Textual represnetation of the Configuration object - - :returns: A ``str`` representation of the Configuration object. - ''' - return f'Configuration(config="{self}")' - -__all__ = [ - # Classes - 'Configuration', - ] diff --git a/tools/python3/lib/rt_m1_client/data_store.py b/tools/python3/lib/rt_m1_client/data_store.py deleted file mode 100644 index 3f4bc41..0000000 --- a/tools/python3/lib/rt_m1_client/data_store.py +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/python3 -#============================================================================== -# 5G-MAG Reference Tools: M1 Session Persistent Data Store -#============================================================================== -# -# File: rt_m1_client/data_store.py -# License: 5G-MAG Public License (v1.0) -# Author: David Waring -# Copyright: (C) 2022 British Broadcasting Corporation -# -# For full license terms please see the LICENSE file distributed with this -# program. If this file is missing then the license can be retrieved from -# https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -# -#============================================================================== -# -# M1 Session DataStore classes -# ============================ -# -# This module contains classes to implement a persistent data store for use by -# the M1Session class. -# -# There are 2 classes DataStore is the base class and JSONFileDataStore is an -# implementation which stores the persistent data objects as JSON objects. -# -'''5G-MAG Reference Tools: M1 Session DataStore classes -==================================================== - -The DataStore class provides a base class for storing persistent data using -a key string. This data can then be retrieved later so that the application can carry on where it left off. - -The JSONFileDataStore class is an implementation that stores the data being -represented in JSON notation as a set of files. -''' -import aiofiles -import aiofiles.os -import json -import logging -import os -import os.path -from typing import Any - -class DataStore: - '''DataStore base class - ''' - def __await__(self): - '''Implement ``await`` on object creation - - This allows derived `DataStore` objects to perform asynchronous tasks on object instantiation. - - For example:: - data_store = await DataStore() - - This will await the `asyncInit()` method of this object. - ''' - return self.asyncInit().__await__() - - async def asyncInit(self): - '''Asynchronous DataStore initialisation - - Implementations should override this method to perform any object initialisation requiring asynchronous operations. - - This must always return *self*. - - :return: self - ''' - return self - - async def get(self, key: str, default: Any = None) -> Any: - '''Get a persisted value by key name - - :param str key: The key name to retrieve the `DataStore` value for. - :param default: The default value to return if the key does not exist in the `DataStore`. - - :return: The value of the retrieved key or the *default* value. - ''' - raise NotImplementedError('DataStore implementation should override this method') - - async def set(self, key: str, value: Any) -> bool: - '''Store a persisted value using the key name - - :param str key: The key name to set a value for. - :param value: The value to set. - - :return: ``True`` if the value was set in the `DataStore` or ``False`` if there was a failure. - ''' - raise NotImplementedError('DataStore implementation should override this method') - -class JSONFileDataStore(DataStore): - '''JSONFileDataStore class - - This class implements a DataStore as a set of files containing JSON. - ''' - def __init__(self, data_store_dir: str): - '''Constructor - - :param str data_store_dir: The directory path to use for the JSON file data store. - - Please note that this object should be instantiated using ``await JSONFileDataStore(data_store_dir)`` as it has - asynchronous initialisation to perform. - ''' - self.__dir = data_store_dir - - async def asyncInit(self): - '''Asynchronous JSONFileDataStore initialisation - - This will ensure that the data store directory for JSON files exists during instantiation. - - :return: self - :raise RuntimeError: if the data store path already exists but is not a directory. - ''' - if not await aiofiles.os.path.exists(self.__dir): - old_umask = os.umask(0) - try: - await aiofiles.os.makedirs(self.__dir, mode=0o700) - finally: - os.umask(old_umask) - if not await aiofiles.os.path.isdir(self.__dir): - raise RuntimeError(f'{self.__dir} is not a directory') - return self - - async def get(self, key: str, default: Any = None) -> Any: - '''Get a persisted value by key name - - :param str key: The key name to retrieve the `DataStore` value for. - :param default: The default value to return if the *key* does not exist in the `DataStore`. - - :return: The value of the retrieved key or the *default* value. - ''' - json_file = os.path.join(self.__dir, f'{key}.json') - if not await aiofiles.os.path.exists(json_file) or not await aiofiles.os.path.isfile(json_file): - return default - async with aiofiles.open(json_file, mode='r') as json_in: - val = json.loads(await json_in.read()) - return val - - async def set(self, key: str, value: Any) -> bool: - '''Store a persisted value using the key name - - :param str key: The key name to set a value for. - :param value: The value to set. - - :return: ``True`` if the value was set in the `DataStore` or ``False`` if there was a failure. - ''' - json_file = os.path.join(self.__dir, f'{key}.json') - async with aiofiles.open(json_file, mode='w') as json_out: - await json_out.write(json.dumps(value)) - return True diff --git a/tools/python3/lib/rt_m1_client/exceptions.py b/tools/python3/lib/rt_m1_client/exceptions.py deleted file mode 100644 index 0b2e41c..0000000 --- a/tools/python3/lib/rt_m1_client/exceptions.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/python3 -#============================================================================== -# 5G-MAG Reference Tools: M1 Client Exceptions -#============================================================================== -# -# File: rt_m1_client/exceptions.py -# License: 5G-MAG Public License (v1.0) -# Author: David Waring -# Copyright: (C) 2023 British Broadcasting Corporation -# -# For full license terms please see the LICENSE file distributed with this -# program. If this file is missing then the license can be retrieved from -# https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -# -#============================================================================== -# -# M1 Client Exceptions -# ==================== -# -# This module defines the exceptions used by the M1 client classes -# -# M1ClientError - Exception used for indicating client request issues as -# returned by the M1 Server (5GMS Application Function). -# -# M1ServerError - Exception used for indicating M1 server errors. The request -# that generated this error may succeed if retried at a later -# time. - -'''5G-MAG Reference Tools: M1 Client Exceptions -============================================ - -This module defines some custom exceptions used by the M1 Client class. - -The M1Error exception is the superclass of the M1ClientError and M1ServerError. -This can be used as a catch all for errors reported by the M1 Server in -response to a request. - -The M1ClientError exception derives from M1Error and is used when a request -response indicates a 4XX status code. This means that there was a problem with -the request and it should not be retried without modification to correct the -issues. - -The M1ServerError exception derives from M1Error and is used when a request -response indicates a 5XX status code. This means that there was an error on the -server. The request may be retried at a later time and may then succeed. -''' -from typing import Optional - -from .types import ProblemDetail, InvalidParam - -def format_invalid_param(inv_param: InvalidParam) -> str: - ''' - Format an InvalidParams entry for display - - :param InvalidParam inv_param: The `InvalidParam` to generate a formatted string for. - - :return: a `str` containing the invalid parameter name optionally followed by the reason. - :rtype: str - ''' - ret: str = inv_param['param'] - if 'reason' in inv_param and inv_param['reason'] is not None: - ret += ' : ' + inv_param['reason'] - return ret - -class M1Error(Exception): - '''Exception base class for all M1 Exceptions - - This can be used to catch both M1ClientError and M1ServerError exceptions. - ''' - def __init__(self, reason: str, # pylint: disable=useless-super-delegation - status_code: Optional[int] = None, problem_detail: Optional[ProblemDetail] = None): - '''Constructor - - :param str reason: The reason for the error. - :param Optional[int] status_code: An optional HTTP status code to associate with the error. - :param Optional[ProblemDetail] problem_detail: An optional `ProblemDetail` to associate with the error. - ''' - super().__init__(reason, status_code, problem_detail) - - def __str__(self) -> str: - '''String representation of the error - - :return: a formatted string representation of the `M1Error`. - ''' - # If a ProblemDetail is available use it - if self.args[2] is not None: - problem = self.args[2] - ret: str = '' - if self.args[1] is not None: - ret = f'[{self.args[1]}] ' - if 'title' in problem: - ret += problem['title']+'\n' - if 'description' in problem: - ret += problem['description'] - if 'invalidParams' in problem and problem['invalidParams'] is not None: - ret += '\nInvalid Parameters:\n'+'\n'.join( - [' '+format_invalid_param(p) for p in problem['invalidParams']]) - return ret - # Else if an HTTP status code is available use "[status_code] reason" as the format - if self.args[1] is not None: - return f'[{self.args[1]}] {self.args[0]}' - # Otherwise just use the reason string - return self.args[0] - - def __repr__(self) -> str: - '''Format a `str` representation of this error - - :return: The error formatted as a constructor for this error. - ''' - return f'{self.__class__.__name__}(reason={self.args[0]!r}, status_code={self.args[1]!r}, problem_detail={self.args[2]!r})' - -class M1ClientError(M1Error): - '''Raised when there was a client side problem during M1 operations - - This error is raised when there was a problem with the client request - detected either by this class, or by the M1 server (5GMS Application - Function) responding with a 4XX response. - - The request should not be repeated in this form as it will fail again. - ''' - -class M1ServerError(M1Error): - '''Raised when there was a server side problem during M1 operations - - This represents 5XX error responses from the M1 server (5GMS Application - Function). - - The request may be repeated at a future date and may or may not work then. - ''' - -__all__ = [ - "M1Error", - "M1ClientError", - "M1ServerError", - ] diff --git a/tools/python3/lib/rt_m1_client/session.py b/tools/python3/lib/rt_m1_client/session.py deleted file mode 100644 index 4e5bbca..0000000 --- a/tools/python3/lib/rt_m1_client/session.py +++ /dev/null @@ -1,970 +0,0 @@ -#!/usr/bin/python3 -#============================================================================== -# 5G-MAG Reference Tools: M1 Session -#============================================================================== -# -# File: rt_m1_client/session.py -# License: 5G-MAG Public License (v1.0) -# Author: David Waring -# Copyright: (C) 2022-2023 British Broadcasting Corporation -# -# For full license terms please see the LICENSE file distributed with this -# program. If this file is missing then the license can be retrieved from -# https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -# -#============================================================================== -# -# M1 Session class -# =============== -# -# This module contains an M1 Session management class written in Python 3 using -# asyncio. This class uses an M1Client object to communicate with the 5GMS -# Application Function via the interface at reference point M1. -# -# The module will maintain a persistent list of provisioning sessions and assist -# in managing the resources for those provisioning sessions. -# -'''5G-MAG Reference Tools: M1 Session class -======================================== - -This class provides an interface for managing provisioning sessions on a 5GMS -Application Function. - -This class uses the M1Client class to communicate with the 5GMS Application -Function via the interface at reference point M1. -''' -import datetime -import importlib -import inspect -import logging -import re -from typing import Optional, Union, Tuple, Dict, Any, TypedDict, List, Iterable - -import OpenSSL - -from .exceptions import (M1ClientError, M1ServerError, M1Error) -from .types import (ApplicationId, ContentHostingConfiguration, ContentProtocols, ProvisioningSessionType, ProvisioningSession, - ConsumptionReportingConfiguration, ResourceId, PolicyTemplate, PROVISIONING_SESSION_TYPE_DOWNLINK) -from .client import (M1Client, ProvisioningSessionResponse, ContentHostingConfigurationResponse, ServerCertificateResponse, - ServerCertificateSigningRequestResponse, ContentProtocolsResponse, ConsumptionReportingConfigurationResponse, - PolicyTemplateResponse) -from .data_store import DataStore -from .certificates import CertificateSigner, DefaultCertificateSigner - -class M1Session: - '''M1 Session management class - =========================== - - This class is used as the top level class to manage a communication session with the 5GMS Application Function. It will - communicate using the `M1Client` class with the M1 Server (5GMS Application Function) and cache the results to improve - efficiency. It can also use a `DataStore` to provide persistence of information across different sessions, and can use a - `CertificateSigner` to perform signing of certificates when ``domainNameAlias`` is used. - ''' - - def __init__(self, host_address: Tuple[str,int], persistent_data_store: Optional[DataStore] = None, certificate_signer: Optional[Union[CertificateSigner,type,str]] = None): - '''Constructor - - :param host_address: A tuple containing the M1 server (5GMS Application Function) hostname/ip-address and TCP port number - to contact it at. - :param persistent_data_store: A `DataStore` object to use to provide persistent storage. - :param certificate_signer: A `CertificateSigner` to use when signing certificates with extra domain names. This can be either a `str` containing the full Python class name, a `CertificateSigner` class to instantiate if needed, or an instance of a `CertificateSigner` to use. If not given then ``rt_m1_client.certificates.DefaultCertificateSigner`` is used. - ''' - self.__m1_host = host_address - self.__data_store_dir = persistent_data_store - self.__cert_signer = certificate_signer - self.__m1_client = None - self.__provisioning_sessions = {} - self.__ca_key = None - self.__ca = None - self.__log = logging.getLogger(__name__ + '.' + self.__class__.__name__) - - def __await__(self): - '''``await`` provider for asynchronous instansiation. - ''' - return self.__asyncInit().__await__() - - async def __asyncInit(self): - '''Asynchronous object instantiation - - Loads previous state from the DataStore. - - :meta private: - :return: self - ''' - await self.__reloadFromDataStore() - return self - - # Provisioning Session Management - - async def provisioningSessionIds(self) -> Iterable: - '''Get the list of current known provisioning session ids - - :return: an iterable for the provisioning session ids. - ''' - return self.__provisioning_sessions.keys() - - async def provisioningSessionProtocols(self, provisioning_session_id: ResourceId) -> Optional[ContentProtocols]: - '''Get the ContentProtocols for the existing provisioning session - - :param ResourceId provisioning_session_id: The provisioning session to get `ContentProtocols` for. - :return: a `ContentProtocols` for the provisioning session or ``None`` if the `ContentProtocols` could not be found. - ''' - if provisioning_session_id not in self.__provisioning_sessions: - return None - await self.__cacheProtocols(provisioning_session_id) - return self.__provisioning_sessions[provisioning_session_id]['protocols']['contentprotocols'] - - async def provisioningSessionCertificateIds(self, provisioning_session_id: ResourceId) -> Optional[List[ResourceId]]: - '''Get the list of certificate Ids for a provisioning session - - :param ResourceId provisioning_session_id: The provisioning session id to get the certificate ids for. - :return: a list of certificate ids associated with the *provisioning_session_id* or ``None`` if they could not be found. - ''' - if provisioning_session_id not in self.__provisioning_sessions: - return None - await self.__cacheProvisioningSession(provisioning_session_id) - ps = self.__provisioning_sessions[provisioning_session_id]['provisioningsession'] - if 'certificates' not in ps: - return [] - return ps['certificates'] - - async def provisioningSessionContentHostingConfiguration(self, provisioning_session_id: ResourceId) -> Optional[ContentHostingConfiguration]: - '''Get the ContentHostingConfiguration associated with the provisioning session - - :param ResourceId provisioning_session_id: The provisioning session id to get the `ContentHostingConfiguration` for. - :return: ``None`` if the provisioning session does not exist or if there is no `ContentHostingConfiguration` associated - with the provisioning session, otherwise return the `ContentHostingConfiguration`. - ''' - if provisioning_session_id not in self.__provisioning_sessions: - return None - await self.__cacheContentHostingConfiguration(provisioning_session_id) - ps = self.__provisioning_sessions[provisioning_session_id] - chc_resp = ps['content-hosting-configuration'] - if chc_resp is None: - # Nothing got cached from the AF, probably an error, but no CHC found - return None - chc = chc_resp['ContentHostingConfiguration'] - return chc - - async def provisioningSessionDestroy(self, provisioning_session_id: ResourceId) -> Optional[bool]: - '''Destroy a provisioning session - - :param provisioning_session_id: The provisioning session id of the session to destroy. - :return: ``True`` if the provisioning session was destroyed, ``False`` if it could not be destroyed or ``None`` if the - provisioning session does not exist. - ''' - if provisioning_session_id not in self.__provisioning_sessions: - return None - await self.__connect() - result = await self.__m1_client.destroyProvisioningSession(provisioning_session_id) - if result: - del self.__provisioning_sessions[provisioning_session_id] - if self.__data_store_dir: - await self.__data_store_dir.set('provisioning_sessions', list(self.__provisioning_sessions.keys())) - return True - return False - - async def provisioningSessionCreate(self, prov_type: ProvisioningSessionType, app_id: ApplicationId, asp_id: Optional[ApplicationId] = None) -> Optional[ResourceId]: - '''Create a provisioning session - - The *prov_type* should be `rt_m1_client.types.PROVISIONING_SESSION_TYPE_DOWNLINK` or - `rt_m1_client.types.PROVISIONING_SESSION_TYPE_UPLINK`. The *app_id* is the mandatory external application id, and the - *asp_id* is the optional ASP identfier. - - :param prov_type: The provisioning session type, either `PROVISIONING_SESSION_TYPE_DOWNLINK` or - `PROVISIONING_SESSION_TYPE_UPLINK`. - :param app_id: This is the External Application Id. - :param asp_id: This is the optional Application Service Provider Id. - - :return: the provisioning session id for the new provisioning session or ``None`` if the `ProvisioningSession` could not - be created. - ''' - await self.__connect() - prov_sess_resp: Optional[ProvisioningSessionResponse] = await self.__m1_client.createProvisioningSession(prov_type, app_id, asp_id) - if prov_sess_resp is None: - self.__log.debug("provisioningSessionCreate: no response") - return None - ps_id = prov_sess_resp['ProvisioningSessionId'] - # Register the provisioning session id - self.__provisioning_sessions[ps_id] = None - # Store in the `DataStore` if available - if self.__data_store_dir: - await self.__data_store_dir.set('provisioning_sessions', list(self.__provisioning_sessions.keys())) - return ps_id - - async def provisioningSessionIdByIngestUrl(self, ingesturl: str, entrypoint: Optional[str] = None) -> Optional[ResourceId]: - ret = None - for ps_id in self.__provisioning_sessions.keys(): - await self.__cacheContentHostingConfiguration(ps_id) - ps = await self.__getProvisioningSessionCache(ps_id) - if ps is None or ps['content-hosting-configuration'] is None: - continue - if ps['content-hosting-configuration']['contenthostingconfiguration']['ingestConfiguration']['baseURL'] == ingesturl: - entry_points = [dc['entryPoint'] for dc in ps['content-hosting-configuration']['contenthostingconfiguration']['distributionConfigurations'] if 'entryPoint' in dc] - entry_point_paths = [e['relativePath'] for e in entry_points] - if (entrypoint is None and len(entry_point_paths) == 0) or (entrypoint is not None and entrypoint in entry_point_paths): - ret = ps_id - break - return ret - - # Certificates management - - async def certificateIds(self, provisioning_session_id: ResourceId) -> Optional[List[ResourceId]]: - '''Get a list of certificate Ids - - :param provisioning_session_id: The provisioning session id to retrieve certificate ids for. - :return: a list of certificate ids or ``None`` if the provisioning session doesn't exist or cannot be retrieved. - ''' - if provisioning_session_id not in self.__provisioning_sessions: - return None - await self.__cacheProvisioningSession(provisioning_session_id) - ps = self.__provisioning_sessions[provisioning_session_id]['provisioningsession'] - if 'serverCertificateIds' not in ps: - return [] - return ps['serverCertificateIds'] - - async def certificateCreate(self, provisioning_session_id: ResourceId) -> Optional[ResourceId]: - '''Create a new certificate - - This creates a new M1 Server signed certificate in the provisioning session and returns the new certificate id. - - :param provisioning_session_id: The provisioning session to create the new certificate in. - - :return: the certificate id of the new certificate or ``None`` if the provisioning session does not exist or if there was - no response from the M1 Server. - ''' - if provisioning_session_id not in self.__provisioning_sessions: - return None - await self.__connect() - cert_resp: ServerCertificateResponse = await self.__m1_client.createServerCertificate(provisioning_session_id) - if cert_resp is None: - return None - cert_id = cert_resp['ServerCertificateId'] - ps = await self.__getProvisioningSessionCache(provisioning_session_id) - if ps is not None: - if 'certificates' not in ps or ps['certificates'] is None: - # create certificates cache - ps['certificates'] = {cert_id: {k.lower(): v for k,v in cert_resp.items()}} - elif cert_id not in ps['certificates'] or ps['certificates'][cert_id] is None: - # Store new certificate info - ps['certificates'][cert_id] = {k.lower(): v for k,v in cert_resp.items()} - else: - # Update the certificate info - if cert_resp['ServerCertificate'] is None: - cert_resp['ServerCertificate'] = ps['certificates'][cert_id]['servercertificate'] - ps['certificates'][cert_id] = {k.lower(): v for k,v in cert_resp.items()} - - return cert_id - - async def certificateGet(self, provisioning_session_id: ResourceId, certificate_id: ResourceId) -> Optional[str]: - '''Retrieve a public certificate - - :param provisioning_session_id: The provisioning session id to use to look up the certificate. - :param certificate_id: The certificate id for the certificate in the provisioning session. - - :return: The PEM string for the public certificate or ``None`` if the certificate could not be found. - ''' - ret_err = None - if provisioning_session_id not in self.__provisioning_sessions: - return None - try: - await self.__cacheCertificates(provisioning_session_id) - except M1Error as err: - # This error may happen for a different certificate, so just remember it for now - ret_err = err - ps = self.__provisioning_sessions[provisioning_session_id] - # If the certificate does not exist return None - if 'certificates' not in ps or ps['certificates'] is None or certificate_id not in ps['certificates']: - return None - # If there was an error caching certificates and this certificate failed to cache then forward the exception - if ret_err is not None and ps['certificates'][certificate_id]['servercertificate'] is None: - raise ret_err - # Return the cached certificate - return ps['certificates'][certificate_id]['servercertificate'] - - async def certificateNewSigningRequest(self, provisioning_session_id: ResourceId, extra_domain_names: Optional[List[str]] = None) -> Optional[Tuple[ResourceId,str]]: - '''Create a new CSR for a provisioning session - - This reserves a new certificate in the provisioning session and returns the new certificate id and CSR PEM string. - It is the responsibility of the caller to generate a signed public certificate from the CSR and post it back to the M1 - Server using the `certificateSet` method. - - :param provisioning_session_id: The provisioning session to reserve the new certificate in. - :param extra_domain_names: An optional list of extra domain names to add as SubjectAltName entries in a CSR. - - :return: a tuple of certificate id and CSR PEM string for the new certificate or ``None`` if the provisioning session does - not exist or if there was no response from the M1 Server. - ''' - if provisioning_session_id not in self.__provisioning_sessions: - return None - await self.__connect() - cert_resp: ServerCertificateSigningRequestResponse = await self.__m1_client.reserveServerCertificate( - provisioning_session_id, extra_domain_names=extra_domain_names) - if cert_resp is None: - return None - cert_id = cert_resp['ServerCertificateId'] - ps = await self.__getProvisioningSessionCache(provisioning_session_id) - if ps is not None: - if 'certificates' not in ps or ps['certificates'] is None: - ps['certificates'] = [cert_id] - elif cert_id not in ps['certificates']: - ps['certificates'] += [cert_id] - return (cert_id,cert_resp['CertificateSigningRequestPEM']) - - async def certificateSet(self, provisioning_session_id: ResourceId, certificate_id: ResourceId, pem: str) -> Optional[bool]: - '''Set the public certificate for a reserved certificate in a provisioning session - - This is used to provide a signed public certificate to the M1 Server after reserving the certificate with - `certificateNewSigningRequest`. This can only be done once per certificate reservation, once the public certificate is set - then further updates to it are not allowed. - - :param provisioning_session_id: The provisioning session id of the provisioning session to upload the certificate to. - :param certificate_id: The certificate id in the provisioning session to upload the certificate to. - :param pem: The public certificate as a PEM string to be uploaded. - - :return: ``True`` if the certificate was set, ``False`` if it has already been set and ``None`` if the provisioning - session or certificate id was not found. - ''' - if provisioning_session_id not in self.__provisioning_sessions: - return None - await self.__connect() - return await self.__m1_client.uploadServerCertificate(provisioning_session_id, certificate_id, pem) - - # ContentHostingConfiguration methods - - async def contentHostingConfigurationCreate(self, provisioning_session: ResourceId, chc: ContentHostingConfiguration) -> bool: - '''Store a new `ContentHostingConfiguration` for a provisioning session - - :param provisioning_session: The provisioning session id of the provisioning session to set the - `ContentHostingConfiguration` in. - :param chc: The `ContentHostingConfiguration` to set in the provisioning session. - :return: ``True`` if the new `ContentHostingConfiguration` was successfully set in the provisioning session or ``False`` if - the operation failed (e.g. because there was already a `ContentHostingConfiguration` set). - ''' - if provisioning_session not in self.__provisioning_sessions: - return False - await self.__connect() - chc_resp: Union[bool,ContentHostingConfigurationResponse] = await self.__m1_client.createContentHostingConfiguration( - provisioning_session, chc) - if isinstance(chc_resp,bool): - return chc_resp - ps = await self.__getProvisioningSessionCache(provisioning_session) - if ps is not None: - ps['content-hosting-configuration'] = {k.lower(): v for k,v in chc_resp.items()} - return True - - async def contentHostingConfigurationGet(self, provisioning_session: ResourceId) -> Optional[ContentHostingConfiguration]: - '''Retrieve the `ContentHostingConfiguration` set on a provisioning session - - :param provisioning_session: The provisioning session id to retrieve the `ContentHostingConfiguration` for. - - :return: a `ContentHostingConfiguration` for the provisioning session or ``None`` if the provisioning session does not - exist or if it has no `ContentHostingConfiguration` set. - ''' - if provisioning_session not in self.__provisioning_sessions: - return None - await self.__cacheContentHostingConfiguration(provisioning_session) - ps = await self.__getProvisioningSessionCache(provisioning_session) - if ps is None or ps['content-hosting-configuration'] is None: - return None - return ContentHostingConfiguration(ps['content-hosting-configuration']['contenthostingconfiguration']) - - async def contentHostingConfigurationUpdate(self, provisioning_session: ResourceId, chc: ContentHostingConfiguration) -> bool: - '''Update the `ContentHostingConfiguration` for a provisioning session - - :param provisioning_session: The provisioning session id of the provisioning session to set the - `ContentHostingConfiguration` in. - :param chc: The `ContentHostingConfiguration` to set in the provisioning session. - :return: ``True`` if the new `ContentHostingConfiguration` was successfully set in the provisioning session or ``False`` if - the operation failed (e.g. because there was no `ContentHostingConfiguration` set). - ''' - if provisioning_session not in self.__provisioning_sessions: - return False - await self.__connect() - return await self.__m1_client.updateContentHostingConfiguration(provisioning_session, chc) - - # ConsumptionReportingConfiguration methods - - async def consumptionReportingConfigurationCreate(self, provisioning_session: ResourceId, crc: ConsumptionReportingConfiguration) -> bool: - '''Store a new `ConsumptionReportingConfiguration` for a provisioning session - - :param provisioning_session: The provisioning session id of the provisioning session to set the - `ConsumptionReportingConfiguration` in. - :param crc: The `ConsumptionReportingConfiguration` to set in the provisioning session. - :return: ``True`` if the new `ConsumptionReportingConfiguration` was successfully set in the provisioning session or - ``False`` if the operation failed (e.g. because there was already a `ConsumptionReportingConfiguration` set). - ''' - if provisioning_session not in self.__provisioning_sessions: - return False - await self.__connect() - crc_resp: Union[bool,ConsumptionReportingConfigurationResponse,None] = \ - await self.__m1_client.activateConsumptionReportingConfiguration(provisioning_session, crc) - if isinstance(crc_resp,bool): - return crc_resp - ps = await self.__getProvisioningSessionCache(provisioning_session) - if ps is not None: - ps['consumption-reporting-configuration'] = {k.lower(): v for k,v in crc_resp.items()} - return True - - async def consumptionReportingConfigurationGet(self, provisioning_session: ResourceId) -> Optional[ConsumptionReportingConfiguration]: - '''Retrieve the `ConsumptionReportingConfiguration` set on a provisioning session - - :param provisioning_session: The provisioning session id to retrieve the `ConsumptionReportingConfiguration` for. - - :return: a `ConsumptionReportingConfiguration` for the provisioning session or ``None`` if the provisioning session does not - exist or if it has no `ConsumptionReportingConfiguration` set. - ''' - if provisioning_session not in self.__provisioning_sessions: - return None - await self.__cacheConsumptionReportingConfiguration(provisioning_session) - ps = await self.__getProvisioningSessionCache(provisioning_session) - if ps is None or ps['consumption-reporting-configuration'] is None: - return None - return ConsumptionReportingConfiguration(ps['consumption-reporting-configuration']['consumptionreportingconfiguration']) - - async def consumptionReportingConfigurationUpdate(self, provisioning_session: ResourceId, crc: ConsumptionReportingConfiguration) -> bool: - '''Update the `ConsumptionReportingConfiguration` for a provisioning session - - :param provisioning_session: The provisioning session id of the provisioning session to set the - `ConsumptionReportingConfiguration` in. - :param chc: The `ConsumptionReportingConfiguration` to set in the provisioning session. - :return: ``True`` if the new `ConsumptionReportingConfiguration` was successfully set in the provisioning session or - ``False`` if the operation failed (e.g. because there was no `ConsumptionReportingConfiguration` set). - ''' - if provisioning_session not in self.__provisioning_sessions: - return False - await self.__connect() - return await self.__m1_client.updateConsumptionReportingConfiguration(provisioning_session, crc) - - async def consumptionReportingConfigurationDelete(self, provisioning_session: ResourceId) -> bool: - '''Remove the `ConsumptionReportingConfiguration` for a provisioning session - - :param provisioning_session: The provisioning session id of the provisioning session to remove the - `ConsumptionReportingConfiguration` in. - - :return: ``True`` if the `ConsumptionReportingConfiguration` was successfully removed or ``False`` if the operation failed. - ''' - if provisioning_session not in self.__provisioning_sessions: - return False - await self.__connect() - return await self.__m1_client.destroyConsumptionReportingConfiguration(provisioning_session) - - # PolicyTemplate methods - - async def policyTemplateIds(self, provisioning_session_id: ResourceId) -> Optional[List[ResourceId]]: - '''Get a list of policy template Ids - - :param provisioning_session_id: The provisioning session id to retrieve policy template ids for. - :return: a list of policy template ids or ``None`` if the provisioning session doesn't exist or cannot be retrieved. - ''' - if provisioning_session_id not in self.__provisioning_sessions: - return None - await self.__cacheProvisioningSession(provisioning_session_id) - ps = self.__provisioning_sessions[provisioning_session_id]['provisioningsession'] - if 'policyTemplateIds' not in ps: - return [] - return ps['policyTemplateIds'] - - async def policyTemplateCreate(self, provisioning_session_id: ResourceId, policy_template: PolicyTemplate) -> Optional[ResourceId]: - '''Create a new policy template - - This creates a new policy template in the provisioning session and returns the new policy template id. - - :param provisioning_session_id: The provisioning session to create the new policy template in. - :param policy_template: The policy template to create. - - :return: the policy template id of the new policy template or ``None`` if the provisioning session does not exist or if - there was no response from the M1 Server. - ''' - if provisioning_session_id not in self.__provisioning_sessions: - return None - await self.__connect() - pol_resp: Optional[PolicyTemplateResponse] = await self.__m1_client.createPolicyTemplate(provisioning_session_id, policy_template) - if pol_resp is None: - return None - pol_id = pol_resp['PolicyTemplate']['policyTemplateId'] - ps = await self.__getProvisioningSessionCache(provisioning_session_id) - if ps is not None: - if 'policyTemplates' not in ps or ps['policyTemplates'] is None: - # create policy template cache - ps['policyTemplates'] = {pol_id: {k.lower(): v for k,v in pol_resp.items()}} - elif pol_id not in ps['policyTemplates'] or ps['policyTemplates'][pol_id] is None: - # Store new policy template info - ps['policyTemplates'][pol_id] = {k.lower(): v for k,v in pol_resp.items()} - else: - # Update the policy template info - if pol_resp['PolicyTemplate'] is None: - pol_resp['PolicyTemplate'] = ps['policyTemplates'][pol_id]['policytemplate'] - ps['policyTemplates'][pol_id] = {k.lower(): v for k,v in pol_resp.items()} - - return pol_id - - async def policyTemplateGet(self, provisioning_session_id: ResourceId, policy_template_id: ResourceId) -> Optional[PolicyTemplate]: - '''Retrieve a policy template - - ''' - if provisioning_session_id not in self.__provisioning_sessions: - return None - await self.__cachePolicyTemplates(provisioning_session_id) - ps = self.__provisioning_sessions[provisioning_session_id] - if ps is None or 'policyTemplates' not in ps or ps['policyTemplates'] is None or policy_template_id not in ps['policyTemplates']: - return None - return PolicyTemplate(ps['policyTemplates'][policy_template_id]['policytemplate']) - - async def policyTemplateUpdate(self, provisioning_session_id: ResourceId, policy_template_id: ResourceId, policy_template: PolicyTemplate) -> Optional[bool]: - '''Update a policy template - - ''' - if provisioning_session_id not in self.__provisioning_sessions: - return False - await self.__connect() - return await self.__m1_client.updatePolicyTemplate(provisioning_session_id, policy_template_id, policy_template) - - async def policyTemplateDelete(self, provisioning_session_id: ResourceId, policy_template_id: ResourceId) -> bool: - '''Delete a policy template - - ''' - if provisioning_session_id not in self.__provisioning_sessions: - return False - await self.__connect() - return await self.__m1_client.destroyPolicyTemplate(provisioning_session_id, policy_template_id) - - # Convenience methods - - async def createDownlinkPullProvisioningSession(self, app_id: ApplicationId, asp_id: Optional[ApplicationId] = None) -> Optional[ResourceId]: - '''Create a downlink provisioning session - - :param app_id: The mandatory external application id for the provisioning session. - :param asp_id: The optional ASP id for the provisioning session. - :return: the new provisioning session id or ``None`` if creation failed. - ''' - return await self.provisioningSessionCreate(PROVISIONING_SESSION_TYPE_DOWNLINK, app_id, asp_id) - - async def createNewCertificate(self, provisioning_session: ResourceId, extra_domain_names: Optional[List[str]] = None) -> Optional[ResourceId]: - '''Create a new certificate - - This will create a new certificate for the provisioning session. If *domain_name_alias* is not given this will leave - creation of the certificate up to the M1 server (5GMS Application Function). If *extra_domain_names* is given, is not - ``None`` and contains at least one entry then this will reserve a certificate for the provisioning session, sign the CSR - using the local `CertificateSigner` object and set the signed public certificate for the provisioning session. - - :param provisioning_session: The provisioning session id of the provisioning session to create the certificate in. - :param extra_domain_names: An optional list of domain names to add as extra SubjectAltName entries. - :return: The certificate id of the newly created certificate or ``None`` if the certificate could not be created. - ''' - # simple case just create the certificate - if extra_domain_names is not None and isinstance(extra_domain_names, bytes): - extra_domain_names = extra_domain_names.decode('utf-8') - if extra_domain_names is not None and isinstance(extra_domain_names, str): - if len(extra_domain_names) > 0: - extra_domain_names = [extra_domain_names] - else: - extra_domain_names = None - if extra_domain_names is not None and len(extra_domain_names) == 0: - extra_domain_names = None - if extra_domain_names is None: - return await self.certificateCreate(provisioning_session) - # When domainNameAlias is used we need to use a CSR - csr: Optional[Tuple[ResourceId,str]] = await self.certificateNewSigningRequest(provisioning_session,extra_domain_names=extra_domain_names) - if csr is None: - return None - cert_id = csr[0] - csr_pem = csr[1] - cert_signer = await self.__getCertificateSigner() - cert: Optional[str] = await cert_signer.signCertificate(csr_pem) - if cert is None: - self.__log.error('Failed to generate certificate with domainNameAlias') - return None - # Send new cert to the AF - if not await self.certificateSet(provisioning_session, cert_id, cert): - self.__log.error('Failed to upload certificate with domainNameAlias') - return None - return cert_id - - async def createNewDownlinkPullStream(self, ingesturl: str, app_id: ApplicationId, entrypoints: Optional[List[str]] = None, name: Optional[str] = None, asp_id: Optional[ApplicationId] = None, ssl: bool = False, insecure: bool = True, domain_name_alias: Optional[str] = None) -> ResourceId: - '''Create a new downlink pull stream - - This will create a new provisioning session, reserve any necessary certificates (if *ssl* is requested) and set the - `ContentHostingConfiguration`. - - The provisioning session is created with the *app_id* and *asp_id* provided. - - If *ssl* is ``True`` then a certificate will be created in the new provisioning session. This certificate will use the - *domain_name_alias* if set. - - The `ContentHostingConfiguration` set in the new provisioning session is created from the *ingesturl*, *entrypoint* and - *name* and will contain a ``distributionConfiguration`` for an HTTP distribution if *insecure* is ``True`` (the default) - and an HTTPS distribution, using the new certificate, if *ssl* is ``True`` (default is no HTTPS). - - :param ingesturl: The ingest URL for the `ContentHostingConfiguration` to create. - :param app_id: The external application id for creatation of the provisioning session. - :param entrypoints: Optional list of ``distributionConfiguration.entryPoint.relativePath`` for the - `ContentHostingConfiguration`. - :param name: Optional ``name`` for the `ContentHostingConfiguration`. - :param asp_id: Optional Application Service Provider Id for creating the provisioning session. - :param ssl: If ``True`` include an HTTPS ``distributionConfiguration`` in the `ContentHostingConfiguration`. - :param insecure: If ``True`` include an HTTP ``distributionConfiguration`` in the `ContentHostingConfiguration`. - :param domain_name_alias: Optional ``domainNameAlias`` to include in the ``distributionConfiguration`` in the - `ContentHostingConfiguration`. - - :return: The provisioning session id - :raise RuntimeError: if the creation of provisioning session, certificate or content hosting configuration fails. - ''' - self.__log.debug(f'createNewDownlinkPullStream(ingesturl={ingesturl!r}, app_id={app_id!r}, entrypoints={entrypoints!r}, name={name!r}, asp_id={asp_id!r}, ssl={ssl!r}, insecure={insecure!r}, domain_name_alias={domain_name_alias!r})') - # Abort if bad parameters - if not ssl and not insecure: - raise RuntimeError('Cannot create a stream without HTTP and HTTPS distributions.') - # Create a new provisioning session - provisioning_session: ResourceId = await self.provisioningSessionCreate(PROVISIONING_SESSION_TYPE_DOWNLINK, app_id, asp_id) - if provisioning_session is None: - raise RuntimeError('Failed to create a provisioning session') - # Create an SSL certificate if requested - if ssl: - cert: Optional[ResourceId] = await self.createNewCertificate(provisioning_session, extra_domaqin_names=[domain_name_alias]) - if cert is None: - if insecure: - self.__log.warn('Failed to create hosting with HTTPS, continuing with just HTTP') - else: - raise RuntimeError('Failed to create hosting, unable to create SSL certificate') - # If no name given, generate one - if name is None: - name = self.__next_auto_stream_name() - # Build and send the ContentHostingConfiguration - chc: ContentHostingConfiguration = { - 'name': name, - 'ingestConfiguration': { - 'pull': True, - 'protocol': 'urn:3gpp:5gms:content-protocol:http-pull-ingest', - 'baseURL': ingesturl, - }, - 'distributionConfigurations': [] - } - if entrypoints is None or len(entrypoints) == 0: - entrypoints = [None] - for ep in entrypoints: - if ssl and cert is not None: - dc = {'certificateId': cert} - if domain_name_alias is not None: - dc['domainNameAlias'] = domain_name_alias - if ep is not None: - dc['entryPoint'] = {'relativePath': ep, 'contentType': await self.__pathToContentType(ep)} - chc['distributionConfigurations'] += [dc] - if insecure: - dc = {} - if domain_name_alias is not None: - dc['domainNameAlias'] = domain_name_alias - if ep is not None: - dc['entryPoint'] = {'relativePath': ep, 'contentType': await self.__pathToContentType(ep)} - chc['distributionConfigurations'] += [dc] - if not await self.contentHostingConfigurationCreate(provisioning_session, chc): - raise RuntimeError('Failed to create the content hosting configuration') - return provisioning_session - - async def setOrUpdateConsumptionReporting(self, provisioning_session: ResourceId, crc: ConsumptionReportingConfiguration) -> bool: - '''Set or update the consumption reporting parameters for a provisioning session - - :param ResourceId provisioning_session: The provisioning session to set the consumption report on. - :param ConsumptionReportingConfiguration crc: The ConsumptionReportingConfiguration to set. - - :return: ``True`` if the configuration was set or ``False`` if the setting failed. - ''' - if provisioning_session not in self.__provisioning_sessions: - return False - await self.__cacheConsumptionReportingConfiguration(provisioning_session) - ps = await self.__getProvisioningSessionCache(provisioning_session) - if ps is None or ps['consumption-reporting-configuration'] is None: - return await self.consumptionReportingConfigurationCreate(provisioning_session, crc) - return await self.consumptionReportingConfigurationUpdate(provisioning_session, crc) - - # Private data - - __file_suffix_to_mime = { - 'mpd': 'application/dash+xml', - 'm3u8': 'application/vnd.apple.mpegurl', - } - - __regex_to_mime = [ - (re.compile(r'mpd'), 'application/dash+xml'), - (re.compile(r'm3u8'), 'application/vnd.apple.mpegurl'), - (re.compile(r'dash', re.IGNORECASE), 'application/dash+xml'), - (re.compile(r'hls', re.IGNORECASE), 'application/vnd.apple.mpegurl'), - ] - - # Private methods - - async def __pathToContentType(self, path: str) -> str: - self.__log.debug(f'__pathToContentType({path!r})') - type_map = { - 'mpd': 'application/dash+xml', - 'm3u8': 'application/vnd.apple.mpegurl', - } - suffix = path.rsplit('.',1)[-1] - if suffix in self.__file_suffix_to_mime: - return self.__file_suffix_to_mime[suffix] - for regexp, ctype in self.__regex_to_mime: - if regexp.search(path) is not None: - return ctype - return 'application/octet-stream' - - async def __reloadFromDataStore(self) -> None: - '''Reload persistent information from the DataStore - - Checks the provisioning session ids retrieved from the DataStore against the M1 server and will delete any that are no - longer available. - - :meta private: - :return: None - ''' - if self.__data_store_dir is None: - return - - sessions = await self.__data_store_dir.get('provisioning_sessions'); - if sessions is None: - return - - # Check the provisioning session still exist with the AF - await self.__connect() - to_remove = [] - for prov_sess in sessions: - if await self.__m1_client.getProvisioningSessionById(prov_sess) is None: - to_remove += [prov_sess] - if len(to_remove) > 0: - for prov_sess in to_remove: - sessions.remove(prov_sess) - if self.__data_store_dir: - await self.__data_store_dir.set('provisioning_sessions', sessions) - - # Populate provisioning session resource keys - self.__provisioning_sessions = {} - for prov_sess in sessions: - self.__provisioning_sessions[prov_sess] = None - - async def __getProvisioningSessionCache(self, provisioning_session_id: ResourceId) -> Optional[dict]: - '''Find a provisioning session cache - - :meta private: - :param provisioning_session_id: The provisioning session id to get the cache for. - :return: The cache `dict` or ``None`` if the cache doesn't exist. - ''' - if provisioning_session_id not in self.__provisioning_sessions: - return None - await self.__cacheProvisioningSession(provisioning_session_id) - return self.__provisioning_sessions[provisioning_session_id] - - async def __cacheResources(self) -> None: - '''Cache the provisioning session resources lists - - Caches the provisioning session information for each known provisioning session - ''' - if len(self.__provisioning_sessions) == 0: - return - for prov_sess in self.__provisioning_sessions.keys(): - self.__cacheProvisioningSession(prov_sess) - - async def __cacheProvisioningSession(self, prov_sess: ResourceId) -> None: - '''Cache the provisioning session resource lists for a provisioning session - - Will only cache if the old cache didn't exist or has expired. - - :meta private: - :param prov_sess: The id of provisioning session to cache. - ''' - ps = self.__provisioning_sessions[prov_sess] - now = datetime.datetime.now(datetime.timezone.utc) - if ps is None or ps['cache-until'] is None or ps['cache-until'] < now: - await self.__connect() - result = await self.__m1_client.getProvisioningSessionById(prov_sess) - if result is not None: - if ps is None: - ps = {} - self.__provisioning_sessions[prov_sess] = ps - ps.update({k.lower(): v for k,v in result.items()}) - ps.update({ - 'protocols': None, - 'content-hosting-configuration': None, - 'consumption-reporting-configuration': None, - 'certificates': None, - 'policyTemplates': None, - }) - # initialise ServerCertificates cache with the available IDs - if 'serverCertificateIds' in ps['provisioningsession']: - ps['certificates'] = {k: None for k in ps['provisioningsession']['serverCertificateIds']} - # initialise PolicyTemplate cache with the available IDs - if 'policyTemplateIds' in ps['provisioningsession']: - ps['policyTemplates'] = {k: None for k in ps['provisioningsession']['policyTemplateIds']} - - async def __cacheProtocols(self, provisioning_session_id: ResourceId): - '''Cache the ContentProtocols for a provisioning session - - Will only cache if the old cache didn't exist or has expired. - - :meta private: - :param provisioning_session_id: The id of provisioning session to cache the `ContentProtocols` for. - ''' - await self.__cacheProvisioningSession(provisioning_session_id) - ps = self.__provisioning_sessions[provisioning_session_id] - now = datetime.datetime.now(datetime.timezone.utc) - if ps['protocols'] is None or ps['protocols']['cache-until'] is None or ps['protocols']['cache-until'] < now: - await self.__connect() - result = await self.__m1_client.retrieveContentProtocols(provisioning_session_id) - if result is not None: - if ps['protocols'] is None: - ps['protocols'] = {} - ps['protocols'].update({k.lower(): v for k,v in result.items()}) - - async def __cacheCertificates(self, provisioning_session_id: ResourceId): - '''Cache all public certificates for the provisioning session - - Will only cache if the old cache didn't exist or has expired. - - :meta private: - :param provisioning_session_id: The id of provisioning session to cache the public certificates for. - ''' - await self.__cacheProvisioningSession(provisioning_session_id) - ps = self.__provisioning_sessions[provisioning_session_id] - now = datetime.datetime.now(datetime.timezone.utc) - if ps['certificates'] is None: - return - ret_err = None - for cert_id,cert in list(ps['certificates'].items()): - if cert is None: - cert = {'etag': None, 'last-modified': None, 'cache-until': None, 'servercertificateid': cert_id, - 'servercertificate': None} - ps['certificates'][cert_id] = cert - if cert['cache-until'] is None or cert['cache-until'] < now: - await self.__connect() - try: - result = await self.__m1_client.retrieveServerCertificate(provisioning_session_id, cert_id) - if result is not None: - cert.update({k.lower(): v for k,v in result.items()}) - except M1Error as err: - if ret_err is None: - ret_err = err - if ret_err is not None: - raise ret_err - - async def __cacheContentHostingConfiguration(self, provisioning_session_id: ResourceId) -> None: - '''Cache the `ContentHostingConfiguration` for a provisioning session - - Will only cache if the old cache didn't exist or has expired. - - :meta private: - :param provisioning_session_id: The id of provisioning session to cache the `ContentHostingConfiguration` for. - ''' - await self.__cacheProvisioningSession(provisioning_session_id) - ps = self.__provisioning_sessions[provisioning_session_id] - now = datetime.datetime.now(datetime.timezone.utc) - chc = ps['content-hosting-configuration'] - if chc is None or chc['cache-until'] is None or chc['cache-until'] < now: - await self.__connect() - result = await self.__m1_client.retrieveContentHostingConfiguration(provisioning_session_id) - if result is not None: - if chc is None: - chc = {} - ps['content-hosting-configuration'] = chc - chc.update({k.lower(): v for k,v in result.items()}) - else: - ps['content-hosting-configuration'] = None - - async def __cacheConsumptionReportingConfiguration(self, provisioning_session_id: ResourceId) -> None: - '''Cache the `ConsumptionReportingConfiguration` for a provisioning session - - Will only cache if the old cache didn't exist or has expired. - - :meta private: - :param provisioning_session_id: The id of provisioning session to cache the `ConsumptionReportingConfiguration` for. - ''' - await self.__cacheProvisioningSession(provisioning_session_id) - ps = self.__provisioning_sessions[provisioning_session_id] - now = datetime.datetime.now(datetime.timezone.utc) - crc = ps['consumption-reporting-configuration'] - if crc is None or crc['cache-until'] is None or crc['cache-until'] < now: - await self.__connect() - result: Optional[ConsumptionReportingConfigurationResponse] = \ - await self.__m1_client.retrieveConsumptionReportingConfiguration(provisioning_session_id) - if result is not None: - if crc is None: - crc = {} - ps['consumption-reporting-configuration'] = crc - crc.update({k.lower(): v for k,v in result.items()}) - else: - ps['consumption-reporting-configuration'] = None - - async def __cachePolicyTemplates(self, provisioning_session_id: ResourceId): - '''Cache all policy templates for the provisioning session - - Will only cache if the old cache didn't exist or has expired. - - :meta private: - :param provisioning_session_id: The id of provisioning session to cache the policy templates for. - ''' - await self.__cacheProvisioningSession(provisioning_session_id) - ps = self.__provisioning_sessions[provisioning_session_id] - now = datetime.datetime.now(datetime.timezone.utc) - if ps is None or 'policyTemplates' not in ps or ps['policyTemplates'] is None: - return - ret_err = None - for pol_id,pol in list(ps['policyTemplates'].items()): - if pol is None: - pol = {'etag': None, 'last-modified': None, 'cache-until': None, 'policytemplate': None} - ps['policyTemplates'][pol_id] = pol - if pol['cache-until'] is None or pol['cache-until'] < now: - await self.__connect() - try: - result = await self.__m1_client.retrievePolicyTemplate(provisioning_session_id, pol_id) - if result is not None: - pol.update({k.lower(): v for k,v in result.items()}) - except M1Error as err: - if ret_err is None: - ret_err = err - if ret_err is not None: - raise ret_err - - async def __getCertificateSigner(self) -> CertificateSigner: - '''Get the `CertificateSigner` - - Creates the CertificateSigner object if we don't already have one. - - :meta private: - :return: a `CertificateSigner` - :raise RuntimeError: if the certificate signer requested is not derived from `CertificateSigner`. - ''' - signer_args = {} - if self.__cert_signer is None: - self.__cert_signer = 'rt_m1_client.certificates.DefaultCertificateSigner' - if isinstance(self.__cert_signer, str): - if '(' in self.__cert_signer: - self.__cert_signer, args_str = self.__cert_signer.split('(',1) - args_str = args_str[:-1] - signer_args = dict([tuple([p.strip() for p in kv.split('=')]) for kv in args_str.split(',')]) - cert_sign_cls_mod, cert_sign_cls_name = self.__cert_signer.rsplit('.', 1) - cert_sign_cls_mod = importlib.import_module(cert_sign_cls_mod) - self.__cert_signer = getattr(cert_sign_cls_mod, cert_sign_cls_name) - try: - if inspect.isclass(self.__cert_signer) and issubclass(self.__cert_signer, CertificateSigner): - self.__cert_signer = await self.__cert_signer(data_store=self.__data_store_dir, **signer_args) - except TypeError: - pass - if inspect.iscoroutinefunction(self.__cert_signer): - self.__cert_signer = await self.__cert_signer(data_store=self.__data_store_dir, **signer_args) - if not isinstance(self.__cert_signer, CertificateSigner): - raise RuntimeError('The certificate signer class given is not derived from CertificateSigner') - return self.__cert_signer - - async def __connect(self) -> None: - '''Connect to the M1Client - - :meta private: - ''' - if self.__m1_client is None: - self.__m1_client = M1Client(self.__m1_host) - - def _dump_state(self) -> None: - '''Dump the current provisioning session cache to the log - ''' - self.__log.debug(repr(self.__provisioning_sessions)) - -__all__ = [ - # Classes - 'M1Session', - ] diff --git a/tools/python3/lib/rt_m1_client/types.py b/tools/python3/lib/rt_m1_client/types.py deleted file mode 100644 index 5f9f917..0000000 --- a/tools/python3/lib/rt_m1_client/types.py +++ /dev/null @@ -1,1076 +0,0 @@ -#!/usr/bin/python3 -#============================================================================== -# 5G-MAG Reference Tools: M1 Client types -#============================================================================== -# -# File: rt_m1_client/types.py -# License: 5G-MAG Public License (v1.0) -# Author: David Waring -# Copyright: (C) 2023 British Broadcasting Corporation -# -# For full license terms please see the LICENSE file distributed with this -# program. If this file is missing then the license can be retrieved from -# https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -# -#============================================================================== -# -# M1 Client types -# =============== -# -# Defines various types from TS 29.571 and TS 26.512 used in M1 requests - -'''5G-MAG Reference Tools: M1 Client types -======================================= - -This module defines various types from TS 29.571 and TS 26.512 used in M1 requests. - -These types can be used in static type checking in Python 3. -''' -import enum -import json -import string -from typing import List, Literal, TypedDict, Union - -def wrapped_default(self, obj): - return getattr(obj.__class__, "__jsontype__", wrapped_default.default)(obj) -wrapped_default.default = json.JSONEncoder().default - -json.JSONEncoder.default = wrapped_default - -# TS 26.512 ProvisioningSession - -ApplicationId = str -ResourceId = str -Uri = str -ProvisioningSessionId = ResourceId -ProvisioningSessionType = Literal['DOWNLINK','UPLINK'] - -class ProvisioningSessionMandatory(TypedDict): - '''Mandatory fields for a `ProvisioningSession` v17.7.0 - ''' - provisioningSessionId: ProvisioningSessionId - provisioningSessionType: ProvisioningSessionType - appId: ApplicationId - -class ProvisioningSession (ProvisioningSessionMandatory, total=False): - '''A `ProvisioningSession` object as defined in TS 26.512 - ''' - aspId: ApplicationId - - @staticmethod - def fromJSON(json_str: str) -> "ProvisioningSession": - '''Create a `ProvisioningSession` from a JSON string - - :param str json_str: The JSON string to convert to a `ProvisioningSession`. - - :return: a `ProvisioningSession` holding the data from the *json_str*. - :rtype: ProvisioningSession - :raise TypeError: if there is a problem with interpretting the *json_str* as a `ProvisioningSession`. - ''' - ret: dict = json.loads(json_str) - for mandatory_field in ProvisioningSessionMandatory.__required_keys__: - if mandatory_field not in ret: - raise TypeError(f'ProvisioningSession must contain a {mandatory_field} field: {json_str}') - if ret['provisioningSessionType'] not in ProvisioningSessionType.__args__: - raise TypeError(f'ProvisioningSession.provisioningSessionType must be one of: {", ".join(ProvisioningSessionType.__args__)}: {json_str}') - - return ProvisioningSession(ret) - -PROVISIONING_SESSION_TYPE_DOWNLINK: ProvisioningSessionType = 'DOWNLINK' #: Downlink `ProvisioningSessionType`. -PROVISIONING_SESSION_TYPE_UPLINK: ProvisioningSessionType = 'UPLINK' #: Uplink `ProvisioningSessionType`. - -# TS 26.512 ContentHostingConfiguration - -class PathRewriteRule(TypedDict): - '''PathRewriteRule structure in TS 26.512 - ''' - requestPathPattern: str #: A regex to match the request path. - mappedPath: str #: The path to map in instead of the matched path. - -class CachingDirectiveMandatory(TypedDict): - '''Mandatory fields from CachingConfiguration.cachingDirectives structure in TS 26.512 - ''' - noCache: bool #: ``True`` if ``no-cache`` should be included for this directive. - -class CachingDirective(CachingDirectiveMandatory, total=False): - '''CachingConfiguration.cachingDirectives structure in TS 26.512 - ''' - statusCodeFilters: List[int] #: A list of status codes to apply this cache directive for. - maxAge: int #: A ``max-age`` to apply for this directive. - -class CachingConfigurationMandatory(TypedDict): - '''Mandatory fields from CachingConfiguration structure in TS 26.512 - ''' - urlPatternFilter: str #: A URL pattern to match for the cache configuration - -class CachingConfiguration(CachingConfigurationMandatory, total=False): - '''CachingConfiguration structure in TS 26.512 - ''' - cachingDirectives: List[CachingDirective] #: Array of cache directives for the matched URL - -class ContentProtocolDescriptorMandatory(TypedDict): - '''Mandatory fields from ContentProtocolDescriptor in TS 26.512 - ''' - termIdentifier: Uri #: A URI (usually URN) to identify an ingest protocol. - -class ContentProtocolDescriptor(ContentProtocolDescriptorMandatory, total=False): - '''ContentProtocolDescriptor structure in TS 26.512 - ''' - descriptionLocator: Uri #: A URL to documentation describing the *termIdentfier*. - -class ContentProtocols(TypedDict, total=False): - '''ContentProtocols structure in TS 26.512 - ''' - downlinkIngestProtocols: List[ContentProtocolDescriptor] #: An array of available downlink ingest protocols. - uplinkEgestProtocols: List[ContentProtocolDescriptor] #: An array of available uplink ingest protocols. - geoFencingLocatorTypes: List[Uri] #: An array of available geo-fencing location types. - - @staticmethod - def fromJSON(json_str: str) -> "ContentProtocols": - '''Create a `ContentProtocols` from a JSON string - - :param str json_str: The JSON string to convert to a `ContentProtocols`. - :return: a `ContentProtocols` containing the data from the *json_str*. - :rtype: ContentProtocols - :raise TypeError: if the *json_str* could not be interpretted as a `ContentProtocols`. - ''' - return ContentProtocols(json.loads(json_str)) - -class DistributionNetworkType(enum.Enum): - '''Enumeration DistributionNetworkType in TS 26.512 - ''' - NETWORK_EMBMS = enum.auto() #: Distribution type is via EMBMS network. - - def __jsontype__(self, **options): - return str(self) - - def __str__(self) -> str: - '''String representation of the `DistributionNetworkType`. - - :return: a `str` containing the name of the enumerated `DistributionNetworkType`. - ''' - return self.name - -class DistributionMode(enum.Enum): - '''Enumeration DistributionMode in TS 26.512 - ''' - MODE_EXCLUSIVE = enum.auto() #: Distribution mode is exclusive - MODE_HYBRID = enum.auto() #: Distribution mode is hybrid - MODE_DYNAMIC = enum.auto() #: Distribution mode is dynamic - - def __jsontype__(self, **options): - return str(self) - - def __str__(self): - '''String representation of the `DistributionMode`. - - :return: a `str` containing the name of the enumerated `DistributionMode`. - ''' - return self.name - -class M1MediaEntryPointMandatory(TypedDict, total=True): - '''Mandatory fields from M1MediaEntryPoint in TS 26.512 (v17.5.0) - ''' - relativePath: str - contentType: str - -class M1MediaEntryPoint(M1MediaEntryPointMandatory, total=False): - '''M1MediaEntryPoint in TS 26.512 (v17.5.0) - ''' - profiles: List[str] - -class DistributionConfiguration(TypedDict, total=False): - ''' - DistributionConfiguration structure in TS 26.512 - ''' - contentPreparationTemplateId: ResourceId - canonicalDomainName: str - domainNameAlias: str - baseURL: Uri - entryPoint: M1MediaEntryPoint - pathRewriteRules: List[PathRewriteRule] - cachingConfigurations: List[CachingConfiguration] - geoFencing: TypedDict('GeoFencing', {'locatorType': str, 'locators': List[str]}) - urlSignature: TypedDict('URLSignature', { - 'urlPattern': str, - 'tokenName': str, - 'passphraseName': str, - 'passphrase': str, - 'tokenExpiryName': str, - 'useIPAddress': bool - }) - certificateId: ResourceId - supplementaryDistributionNetworks: List[TypedDict('SupplementaryDistributionNetwork', { - 'distributionNetworkType': DistributionNetworkType, - 'distributionMode': DistributionMode, - })] - - @staticmethod - def format(dc: "DistributionConfiguration", indent: int = 0) -> str: - prefix = ' ' * indent - s = f"{prefix}- URL: {dc['baseURL']}" - if 'canonicalDomainName' in dc: - s += f''' -{prefix} Canonical Domain Name: {dc['canonicalDomainName']}''' - if 'contentPreparationTemplateId' in dc: - s += f''' -{prefix} Content Preparation Template: {dc['contentPreparationTemplateId']}''' - if 'certificateId' in dc: - s += f''' -{prefix} Certificate: {dc['certificateId']}''' - if 'domainNameAlias' in dc: - s += f''' -{prefix} Domain Name Alias: {dc['domainNameAlias']}''' - if 'entryPoint' in dc: - s += f''' -{prefix} Entry point: -{prefix} Relative Path: {dc['entryPoint']['relativePath']} -{prefix} Content Type: {dc['entryPoint']['contentType']}''' - if 'profiles' in dc['entryPoint']: - s += f''' -{prefix} Profiles:''' - for p in dc['entryPoint']['profiles']: - s += f''' -{prefix} - {p}''' - if 'pathRewriteRules' in dc: - s += f''' -{prefix} Path Rewrite Rules:''' - for prr in dc['pathRewriteRules']: - s += f''' -{prefix} - {prr['requestPathPattern']} => {prr['mappedPath']}''' - if 'cachingConfigurations' in dc: - s += f''' -{prefix} Caching Configurations:''' - for cc in dc['cachingConfigurations']: - s += f''' -{prefix} - URL Pattern: {cc['urlPatternFilter']}''' - if 'cachingDirectives' in cc: - cd = cc['cachingDirectives'] - s += f''' -{prefix} Directive: -{prefix} no-cache={repr(cd['noCache'])}''' - if 'maxAge' in cd: - s += f''' -{prefix} max-age={cd['maxAge']}''' - if 'statusCodeFilters' in cd: - s += f''' -{prefix} filters=[{', '.join([str(i) for i in cd['statusCodeFilters']])}]''' - if 'geoFencing' in dc: - gf = dc['geoFencing'] - s += f''' -{prefix} Geo-fencing({gf['locatorType']}):''' - for l in gf['locators']: - s += f''' -{prefix} - {l}''' - if 'urlSignature' in dc: - us = dc['urlSignature'] - s += f''' -{prefix} URL Signature: -{prefix} - Pattern: {us['urlPattern']} -{prefix} Token: {us['tokenName']} -{prefix} Passphase name: {us['passphraseName']} -{prefix} Passphase: {us['passphrase']} -{prefix} Token Expiry name: {us['tokenExpiryName']} -{prefix} Use IP Address?: {us['useIPAddress']!r}''' - if 'ipAddressName' in us: - s += f''' -{prefix} IP Address name: {us['ipAddressName']}''' - return s - -class IngestConfiguration(TypedDict, total=False): - ''' - IngestConfiguration structure from TS 26.512 - ''' - pull: bool - protocol: Uri - baseURL: Uri - - @staticmethod - def format(ic: "IngestConfiguration", indent: int = 0) -> str: - prefix = ' ' * indent - return f'''{prefix}Type: {ic['protocol']} -{prefix}Pull Ingest?: {ic['pull']!r} -{prefix}URL: {ic['baseURL']}''' - -class ContentHostingConfigurationMandatory(TypedDict): - ''' - Mandatory fields from ContentHostingConfiguration structure in TS 26.512 - ''' - name: str - ingestConfiguration: IngestConfiguration - distributionConfigurations: List[DistributionConfiguration] - -class ContentHostingConfiguration(ContentHostingConfigurationMandatory, total=False): - ''' - ContentHostingConfiguration structure in TS 26.512 - ''' - - @staticmethod - def fromJSON(chc_json: str) -> "ContentHostingConfiguration": - ''' - Generate a ContentHostingConfiguration structure from a JSON string - ''' - # parse the JSON - chc = json.loads(chc_json) - # convert enums - if 'distributionConfigurations' in chc: - for dist_conf in chc['distributionConfigurations']: - if 'supplementaryDistributionNetworks' in dist_conf: - for supp_net in dist_conf['supplementaryDistributionNetworks']: - supp_net['distributionNetworkType'] = DistributionNetworkType[ - supp_net['distributionNetworkType']] - supp_net['distributionMode'] = DistributionMode[ - supp_net['distributionMode']] - # Validate against ContentHostingConfiguration type - return ContentHostingConfiguration(chc) - - @classmethod - def format(cls, chc: "ContentHostingConfiguration") -> str: - '''Get a formatted `str` representation of a `ContentHostingConfiguration`. - - :param ContentHostingConfiguration chc: The `ContentHostingConfiguration` to format. - :return: a formatted `str` representation of the `ContentHostingConfiguration`. - ''' - return f'''Name: {chc['name']} -Ingest: -{IngestConfiguration.format(chc['ingestConfiguration'], 2)} -Distributions: -{cls.__formatDistributions(chc)} -''' - - @classmethod - def __formatDistributions(cls, chc: "ContentHostingConfiguration", indent: int = 0) -> str: - '''Format a ContentHostingConfiguration.distributionConfigurations - - :meta private: - :param ContentHostingConfiguration chc: The `ContentHostingConfiguration` to get the distributionConfigurations from. - :param int indent: The amount of spaces to indent the formatted distributionConfigurations by. - - :return: a `str` containing the distributionConfigurations as formatted text. - ''' - return '\n'.join([DistributionConfiguration.format(d, indent) for d in chc['distributionConfigurations']]) - -# TS 26.512 ConsumptionReportingConfiguration - -class ConsumptionReportingConfiguration(TypedDict, total=False): - ''' - ConsumptionReportingConfiguration structure from TS 26.512 - ''' - reportingInterval: int - samplePercentage: float - locationReporting: bool - accessReporting: bool - - @staticmethod - def fromJSON(crc_json: str) -> "ConsumptionReportingConfiguration": - '''Create a ConsumptionReportingConfiguration from a JSON string - - :param str json: The JSON string to parse into a ConsumptionReportingConfiguration structure. - - :return: The `ConsumptionReportingConfiguration` generated from the JSON string. - - :raise ValueError: If the JSON could not be parsed. - ''' - # parse the JSON - crc = json.loads(crc_json) - # validate types - if 'reportingInterval' in crc: - if not isinstance(crc['reportingInterval'], int): - raise ValueError('ConsumptionReportingConfiguration.reportingInterval must be an integer') - if crc['reportingInterval'] <= 0: - raise ValueError('ConsumptionReportingConfiguration.reportingInterval must be an integer greater than 0') - if 'samplePercentage' in crc: - if isinstance(crc['samplePercentage'], int): - crc['samplePercentage'] = float(crc['samplePercentage']) - if not isinstance(crc['samplePercentage'], float): - raise ValueError('ConsumptionReportingConfiguration.samplePercentage must be an integer or floating point number') - if crc['samplePercentage'] < 0.0 or crc['samplePercentage'] > 100.0: - raise ValueError('ConsumptionReportingConfiguration.samplePercentage must be between 0.0 and 100.0 inclusive') - if 'locationReporting' in crc: - if not isinstance(crc['locationReporting'], bool): - raise ValueError('ConsumptionReportingConfiguration.locationReporting must be a boolean') - if 'accessReporting' in crc: - if not isinstance(crc['accessReporting'], bool): - raise ValueError('ConsumptionReportingConfiguration.accessReporting must be a boolean') - # Validate against ContentHostingConfiguration type - return ConsumptionReportingConfiguration(crc) - - @classmethod - def format(cls, crc: "ConsumptionReportingConfiguration", indent: int = 0) -> str: - prefix: str = ' ' * indent - ret: str = '' - if 'reportingInterval' in crc: - ret += f"{prefix}Reporting interval: {crc['reportingInterval']}s\n" - if 'samplePercentage' in crc: - ret += f"{prefix}Sample percentage: {crc['samplePercentage']}\n" - if 'locationReporting' in crc and crc['locationReporting']: - ret += f"{prefix}With location reporting\n" - if 'accessReporting' in crc and crc['accessReporting']: - ret += f"{prefix}With access reporting\n" - if len(ret) == 0: - ret = f"{prefix}Active with no parameters set\n" - return ret - -# TS 26.512 PolicyTemplate -class PolicyTemplateState(enum.Enum): - ''' - PolicyTemplate.state enumeration from TS 26.512 - ''' - PENDING = enum.auto() # pylint: disable=invalid-name - INVALID = enum.auto() # pylint: disable=invalid-name - READY = enum.auto() # pylint: disable=invalid-name - SUSPENDED = enum.auto() # pylint: disable=invalid-name - - def __jsontype__(self, **options): - return str(self) - - def __str__(self): - return self.name - -class SnssaiMandatory(TypedDict): - ''' - Snssai structure mandatory fields from TS 29.571 - ''' - sst: int - -class Snssai(SnssaiMandatory, total=False): - ''' - Snssai structure from TS 29.571 - ''' - sd: str - - @staticmethod - def validate(snssai: "Snssai", json: str) -> bool: - # check mandatory fields presence - for mandatory_field in SnssaiMandatory.__required_keys__: - if mandatory_field not in snssai: - raise TypeError(f'Snssai must contain a {mandatory_field} field: {json}') - - # convert enums - #asc['?'] = ???(asc['?']) - - # validate substructures - if not isinstance(snssai['sst'], int): - raise ValueError(f'''Snssai.sst must be an integer''') - if snssai['sst'] < 0 or snssai['sst'] > 255: - raise ValueError(f'''Snssai.sst must be an integer between 0 and 255 inclusive: found Snssai.sst = {snssai['sst']}''') - if 'sd' in snssai: - if len(snssai['sd']) != 6 or any(c not in string.hexdigits for c in snssai['sd']): - raise ValueError(f'''Snssai.sd must be a 6 digit hexadecimal string: found Snssai.sd = {snssai['sd']}''') - return True - - @staticmethod - def format(snssai: "Snssai", indent: int = 0) -> str: - prefix: str = ' ' * indent - ret: str = f'''{prefix}Sst: {snssai['sst']}''' - if 'sd' in snssai: - ret += f"\n{prefix}Sd: {snssai['sd']}" - return ret - -class BitRate(object): - ''' - BitRate string from TS 29.571 - ''' - def __init__(self, *args, **kwargs): - if len(args) == 1 and len(kwargs) == 0: - if isinstance(args[0], bytes): - args[0] = args[0].decode('utf-8') - if isinstance(args[0], str): - self.__bitrate = self.__parseBitrateString(args[0]) - elif isinstance(args[0], int): - self.__bitrate = float(args[0]) - elif isinstance(args[0], float): - self.__bitrate = args[0] - elif isinstance(args[0], BitRate): - self.__bitrate = args[0].bitrate() - else: - raise TypeError(f'BitRate initialiser must be str, int or float: given {type(args[0]).__name__}') - elif len(args) == 0 and len(kwargs) == 1 and kwargs.keys()[0] in ['bps', 'kbps', 'mbps', 'gbps', 'tbps', 'pbps']: - k,v = kwargs.items()[0] - if k == 'bps': - self.__bitrate = float(v) - elif k == 'kbps': - self.__bitrate = v*1000.0 - elif k == 'mbps': - self.__bitrate = v*1000000.0 - elif k == 'gbps': - self.__bitrate = v*1000000000.0 - elif k == 'tbps': - self.__bitrate = v*1000000000000.0 - else: # 'pbps' - self.__bitrate = v*1000000000000000.0 - else: - raise ValueError('Only a bitrate string or one of the bitrate keywords can be used to initialise a BitRate') - - def bitrate(self): - return self.__bitrate - - def __repr__(self) -> str: - if self.__bitrate < 1000: - return f'BitRate(bps={self.__bitrate})' - if self.__bitrate < 1000000: - return f'BitRate(kbps={self.__bitrate/1000.0})' - if self.__bitrate < 1000000000: - return f'BitRate(mbps={self.__bitrate/1000000.0})' - if self.__bitrate < 1000000000000: - return f'BitRate(gbps={self.__bitrate/1000000000.0})' - if self.__bitrate < 1000000000000000: - return f'BitRate(tbps={self.__bitrate/1000000000000.0})' - return f'BitRate(pbps={self.__bitrate/1000000000000000.0})' - - def __str__(self) -> str: - if self.__bitrate < 1000: - return f'{self.__bitrate} bps' - if self.__bitrate < 1000000: - return f'{self.__bitrate/1000.0:.3f} Kbps' - if self.__bitrate < 1000000000: - return f'{self.__bitrate/1000000.0:.3f} Mbps' - if self.__bitrate < 1000000000000: - return f'{self.__bitrate/1000000000.0:.3f} Gbps' - if self.__bitrate < 1000000000000000: - return f'{self.__bitrate/1000000000000.0:.3f} Tbps' - return f'{self.__bitrate/1000000000000000.0:.3f} Pbps' - - def __jsontype__(self, **options): - return str(self) - - def __lt__(self, other: Union[int,float,"BitRate"]) -> bool: - if isinstance(other, int) or isinstance(other, float): - return self.__bitrate < other - if isinstance(other, BitRate): - return other > self.__bitrate - raise TypeError('Can only compare a BitRate to another BitRate, int or float') - - def __gt__(self, other: Union[int,float,"BitRate"]) -> bool: - if isinstance(other, int) or isinstance(other, float): - return self.__bitrate > other - if isinstance(other, BitRate): - return other < self.__bitrate - raise TypeError('Can only compare a BitRate to another BitRate, int or float') - - def __eq__(self, other: Union[int,float,"BitRate"]) -> bool: - if isinstance(other, int) or isinstance(other, float): - return self.__bitrate == other - if isinstance(other, BitRate): - return other == self.__bitrate - raise TypeError('Can only compare a BitRate to another BitRate, int or float') - - def __le__(self, other: Union[int,float,"BitRate"]) -> bool: - return not self > other - - def __ge__(self, other: Union[int,float,"BitRate"]) -> bool: - return not self < other - - def __ne__(self, other: Union[int,float,"BitRate"]) -> bool: - return not self == other - - @staticmethod - def __parseBitrateString(br: str) -> float: - val,units = (br.split(' ',1) + [None])[:2] - val = float(val) - if units not in ['bps', 'Kbps', 'Mbps', 'Gbps', 'Tbps', 'Pbps']: - raise ValueError('BitRate string must have units of bps, Kbps, Mbps, Gbps, Tbps or Pbps') - if units == 'bps': - return val - if units == 'Kbps': - return val*1000.0 - if units == 'Mbps': - return val*1000000.0 - if units == 'Gbps': - return val*1000000000.0 - if units == 'Tbps': - return val*1000000000000.0 - return val*1000000000000000.0 - -class AppSessionContext(TypedDict, total=False): - ''' - PolicyTemplate.applicationSessionContext structure in TS 26.512 - ''' - sliceInfo: Snssai - dnn: str - - @staticmethod - def fromJSON(asc_json: str) -> "AppSessionContext": - '''Create an AppSessionContext from a JSON string - - :param str json: The JSON string to parse into a AppSessionContext structure. - - :return: The `AppSessionContext` generated from the JSON string. - - :raise TypeError: If the AppSessionContext is missing mandatory fields. - :raise ValueError: If the JSON could not be parsed. - ''' - # parse the JSON - asc = json.loads(asc_json) - AppSessionContext.validate(asc, asc_json) - return AppSessionContext(asc) - - @staticmethod - def validate(asc: "AppSessionContext", json: str) -> bool: - # check mandatory fields presence - #for mandatory_field in AppSessionContextMandatory.__required_keys__: - # if mandatory_field not in pt: - # raise TypeError(f'AppSessionContext must contain a {mandatory_field} field: {json}') - - # convert enums - #asc['?'] = ???(asc['?']) - - # validate substructures - if 'sliceInfo' in asc: - Snssai.validate(asc['sliceInfo'], json) - if 'dnn' in asc: - if not isinstance(asc['dnn'], str): - raise ValueError(f'''AppSessionContext.dnn must be a string if present''') - return True - - @classmethod - def format(cls, asc: "AppSessionContext", indent: int = 0) -> str: - prefix: str = ' ' * indent - ret: str = '' - if 'sliceInfo' in asc: - ret += f'''{prefix}Slice Info: -{Snssai.format(asc['sliceInfo'], indent+2)} -''' - if 'dnn' in asc: - ret += f'''{prefix}Network Name: {asc['dnn']} -''' - return ret - -class M1QoSSpecification(TypedDict, total=False): - ''' - M1QoSSpecification object from TS 26.512 - ''' - qosReference: str - maxBtrUl: BitRate - maxBtrDl: BitRate - maxAuthBtrUl: BitRate - maxAuthBtrDl: BitRate - defPacketLossRateDl: int - defPacketLossRateUl: int - - @staticmethod - def fromJSON(qs_json: str) -> "M1QoSSpecification": - '''Create an M1QoSSpecification from a JSON string - - :param str qs_json: The JSON string to parse into a M1QoSSpecification structure. - - :return: The `M1QoSSpecification` generated from the JSON string. - - :raise TypeError: If the M1QoSSpecification is missing mandatory fields. - :raise ValueError: If the JSON could not be parsed. - ''' - # parse the JSON - try: - qs = json.loads(qs_json) - except json.JSONDecodeError as err: - raise ValueError("Unable to parse JSON as M1QoSSpecification") - M1QoSSpecification.validate(qs, qs_json) - return M1QoSSpecification(qs) - - @staticmethod - def validate(qs: "M1QoSSpecification", json: str) -> bool: - # check mandatory fields presence - #for mandatory_field in AppSessionContextMandatory.__required_keys__: - # if mandatory_field not in pt: - # raise TypeError(f'AppSessionContext must contain a {mandatory_field} field: {json}') - - # convert enums - #asc['?'] = ???(asc['?']) - - # validate substructures - for br_field in ['maxBtrUl', 'maxBtrDl', 'maxAuthBtrUl', 'maxAuthBtrDl']: - if br_field in qs: - qs[br_field] = BitRate(qs[br_field]) - for lr_field in ['defPacketLossRateDl', 'defPacketLossRateUl']: - if lr_field in qs: - if not isinstance(qs[lr_field],int): - raise ValueError(f'{lr_field} is {type(qs[lr_field])} not an integer') - if qs[lr_field] < 0: - raise ValueError(f'{lr_field} must be a positive integer') - return True - - @classmethod - def format(cls, qs: "M1QoSSpecification", indent: int = 0) -> str: - prefix: str = ' ' * indent - ret: str = '' - if 'qosReference' in qs: - ret += f'''{prefix}QoS Reference: {qs['qosReference']} -''' - if 'maxBtrUl' in qs: - ret += f'''{prefix}Maximum Uplink Bitrate: {qs['maxBtrUl']} -''' - if 'maxBtrDl' in qs: - ret += f'''{prefix}Maximum Downlink Bitrate: {qs['maxBtrDl']} -''' - if 'maxAuthBtrUl' in qs: - ret += f'''{prefix}Maximum Authorised Uplink Bitrate: {qs['maxAuthBtrUl']} -''' - if 'maxAuthBtrDl' in qs: - ret += f'''{prefix}Maximum Authorised Downlink Bitrate: {qs['maxAuthBtrDl']} -''' - if 'defPacketLossRateDl' in qs: - ret += f'''{prefix}Default Downlink Packet Loss Rate: {qs['defPacketLossRateDl']} packets/s -''' - if 'defPacketLossRateUl' in qs: - ret += f'''{prefix}Default Uplink Packet Loss Rate: {qs['defPacketLossRateUl']} packets/s -''' - return ret - -class SponsoringStatus(enum.Enum): - ''' - SponsoringStatus enumeration from TS 29.514 - ''' - SPONSOR_DISABLED = enum.auto() # pylint: disable=invalid-name - SPONSOR_ENABLED = enum.auto() # pylint: disable=invalid-name - - def __jsontype__(self, **options): - return str(self) - - def __str__(self): - return self.name - -class ChargingSpecification(TypedDict, total=False): - ''' - ChargingSpecification object from TS 26.512 - ''' - sponId: str - sponStatus: SponsoringStatus - gpsi: List[str] - - @staticmethod - def fromJSON(cs_json: str) -> "ChargingSpecification": - '''Create a ChargingSpecification from a JSON string - ''' - try: - cs = json.loads(cs_json) - except json.JSONDecodeError as err: - raise ValueError(f'Unable to parse JSON from: {cs_json}') - ChargingSpecification.validate(cs) - return ChargingSpecification(cs) - - @staticmethod - def validate(cs: dict, json: str) -> bool: - '''Validate a dict as a ChargingSpecification - ''' - # convert enums - if 'sponStatus' in cs: - cs['sponStatus'] = SponsoringStatus[cs['sponStatus']] - # check types - if 'sponId' in cs and not isinstance(cs['sponId'], str): - raise ValueError(f'Sponsor ID should be a string in ChargingSpecification: {json}') - if 'gpsi' in cs: - if not isinstance(cs['gpsi'], list): - raise ValueError(f'GPSI in ChargingSpecification should be a list when present: {json}') - for v in cs['gpsi']: - if not isinstance(v, str): - raise ValueError(f'GPSI list in ChargingSpecification should only contain strings: {json}') - return True - - @staticmethod - def format(cs: "ChargingSpecification", indent: int = 0) -> str: - '''Format a ChargingSpecification as a multiline string - ''' - prefix: str = ' ' * indent - nlprefix: str = f"\n{prefix}" - ret: str = '' - if 'sponId' in cs: - ret += f"{nlprefix}Sponsor ID: {cs['sponId']}" - if 'sponStatus' in cs: - ret += f"{nlprefix}Sponsor Status: {cs['sponStatus']}" - if 'gpsi' in cs: - ret += f"{nlprefix}GPSIs:{nlprefix} {f'{nlprefix} '.join(cs['gpsi'])}" - return ret[1:] - -class PolicyTemplateMandatory(TypedDict): - ''' - Mandatory fields from PolicyTemplate structure in TS 26.512 - ''' - externalReference: str - -class PolicyTemplate(PolicyTemplateMandatory, total=False): - ''' - PolicyTemplate structure from TS 26.512 - ''' - policyTemplateId: ResourceId - applicationSessionContext: AppSessionContext - state: PolicyTemplateState - stateReason: "ProblemDetail" - qoSSpecification: M1QoSSpecification - chargingSpecification: ChargingSpecification - - @staticmethod - def fromJSON(policy_template_json: str) -> "PolicyTemplate": - '''Create a PolicyTemplate from a JSON string - - :param str policy_template_json: The JSON string to parse into a PolicyTemplate structure. - - :return: The `PolicyTemplate` generated from the JSON string. - - :raise TypeError: If the PolicyTemplate is missing mandatory fields. - :raise ValueError: If the JSON could not be parsed. - ''' - # parse the JSON - pt = json.loads(policy_template_json) - PolicyTemplate.validate(pt, policy_template_json) - return PolicyTemplate(pt) - - @staticmethod - def validate(pt: "PolicyTemplate", policy_template_json: str) -> bool: - # check mandatory fields presence - for mandatory_field in PolicyTemplateMandatory.__required_keys__: - if mandatory_field not in pt: - raise TypeError(f'PolicyTemplate must contain a {mandatory_field} field: {policy_template_json}') - # convert enums - if 'state' in pt: - pt['state'] = PolicyTemplateState[pt['state']] - #ProblemDetail.validate(pt['stateReason']) - if 'applicationSessionContext' in pt: - AppSessionContext.validate(pt['applicationSessionContext'], policy_template_json) - if 'qoSSpecification' in pt: - M1QoSSpecification.validate(pt['qoSSpecification'], policy_template_json) - if 'chargingSpecification' in pt: - ChargingSpecification.validate(pt['chargingSpecification'], policy_template_json) - return True - - @classmethod - def format(cls, pt: "PolicyTemplate", indent: int = 0) -> str: - prefix: str = ' ' * indent - ret: str = f'''{prefix}PolicyTemplate: -{prefix} Policy Template Id: {pt['policyTemplateId']} -{prefix} State: {pt['state']} -{prefix} State Reason: -{ProblemDetail.format(pt['stateReason'],indent+4)} -{prefix} External Reference: {pt['externalReference']} -''' - if 'applicationSessionContext' in pt: - ret += f"{prefix} AppSessionContext:\n{AppSessionContext.format(pt['applicationSessionContext'], indent+4)}" - if 'qoSSpecification' in pt: - ret += f"{prefix} QoS Specification:\n{M1QoSSpecification.format(pt['qoSSpecification'], indent+4)}" - if 'chargingSpecification' in pt: - ret += f"{prefix} Charging Specification:\n{ChargingSpecification.format(pt['chargingSpecification'], indent+4)}" - return ret - -# TS 29.571 ProblemDetail -class InvalidParamMandatory(TypedDict): - ''' - Mandatory fields from InvalidParam structure in TS 29.571 - ''' - param: str - -class InvalidParam(InvalidParamMandatory, total=False): - ''' - InvalidParam structure from TS 29.571 - ''' - reason: str - - @staticmethod - def format(ip: "InvalidParam", indent: int = 0): - prefix = ' ' * indent - ret = f'''{prefix}Parameter: {ip['param']} -''' - if 'reason' in ip: - ret += f"{prefix}Reason: {ip['reason']}\n" - return ret - -class AccessTokenErrError(enum.Enum): - ''' - AccessTokenErrError enumeration from TS 29.571 - ''' - invalid_request = enum.auto() # pylint: disable=invalid-name - invalid_client = enum.auto() # pylint: disable=invalid-name - invalid_grant = enum.auto() # pylint: disable=invalid-name - unauthorized_client = enum.auto() # pylint: disable=invalid-name - unsupported_grant_type = enum.auto() # pylint: disable=invalid-name - invalid_scope = enum.auto() # pylint: disable=invalid-name - - def __jsontype__(self, **options): - return str(self) - - def __str__(self): - return self.name - -class AccessTokenErrMandatory(TypedDict): - ''' - Mandatory fields from AccessTokenErr structure in TS 29.571 - ''' - error: AccessTokenErrError - -class AccessTokenErr(AccessTokenErrMandatory, total=False): - ''' - AccessTokenErr structure in TS 29.571 - ''' - error_description: str - error_uri: str - - @staticmethod - def format(ate: "AccessTokenErr", indent: int = 0) -> str: - prefix = ' ' * indent - ret = f'''{prefix}Error: {ate['error']}''' - if 'error_description' in ate: - ret += f"\n{prefix}Description: {ate['error_description']}" - if 'error_uri' in ate: - ret += f"\n{prefix}URI: {ate['error_uri']}" - return ret - -class AccessTokenReqGrantType(enum.Enum): - ''' - AccessTokenReqGrantType enumeration in TS 29.571 - ''' - client_credentials = enum.auto() # pylint: disable=invalid-name - - def __jsontype__(self, **options): - return str(self) - - def __str__(self): - return self.name - -class AccessTokenReqMandatory(TypedDict): - ''' - Mandatory fields from AccessTokenReq structure in TS 29.571 - ''' - grant_type: AccessTokenReqGrantType - nfInstanceId: str - scope: str - -class AccessTokenReq(AccessTokenReqMandatory, total=False): - ''' - AccessTokenReq structure in TS 29.571 - ''' - nfType: str - targetNfType: str - targetNfInstanceId: str - requesterPlmn: str - requesterPlmnList: List[str] - requesterSnssaiList: List[str] - requesterFqdn: str - requesterSnpnList: List[str] - targetPlmn: str - targetSnssaiList: List[str] - targetNsiList: List[str] - targetNfSetId: str - targetNfServiceSetId: str - hnrfAccessTokenUri: str - sourceNfInstanceId: str - - @staticmethod - def format(atr: "AccessTokenReq", indent: int = 0) -> str: - prefix = ' ' * indent - ret = f'''{prefix}Grant Type: {atr['grant_type']} -{prefix}NF Instance Id: {atr['nfInstanceId']} -{prefix}Scope: {atr['scope']}''' - if 'nfType' in atr: - ret += f"\n{prefix}NF Type: {atr['nfType']}" - if 'requesterPlmn' in atr: - ret += f"\n{prefix}Requester PLMN: {atr['requesterPlmn']}" - if 'requesterPlmnList' in atr: - ret += f"\n{prefix}Requester PLMNs: {', '.join(atr['requesterPlmnList'])}" - if 'requesterSnssaiList' in atr: - ret += f"\n{prefix}Requester S-NSSAIs: {', '.join(atr['requesterSnssaiList'])}" - if 'requesterFqdn' in atr: - ret += f"\n{prefix}Requester FQDN: {atr['requesterFqdn']}" - if 'requesterSnpnList' in atr: - ret += f"\n{prefix}Requester SNPNs: {', '.join(atr['requesterSnpnList'])}" - if 'targetNfType' in atr: - ret += f"\n{prefix}Target NF Type: {atr['targetNfType']}" - if 'targetNfInstanceId' in atr: - ret += f"\n{prefix}Target NF Instance Id: {atr['targetNfInstanceId']}" - if 'targetPlmn' in atr: - ret += f"\n{prefix}Target PLMN: {atr['targetPlmn']}" - if 'targetSnssaiList' in atr: - ret += f"\n{prefix}Target S-NSSAIs: {', '.join(atr['targetSnssaiList'])}" - if 'targetNsiList' in atr: - ret += f"\n{prefix}Target NSIs: {', '.join(atr['targetNsiList'])}" - if 'targetNfSetId' in atr: - ret += f"\n{prefix}Target NF Set Id: {atr['targetNfSetId']}" - if 'targetNfServiceSetId' in atr: - ret += f"\n{prefix}Target NF Service Set Id: {atr['targetNfServiceSetId']}" - if 'hnrfAccessTokenUri' in atr: - ret += f"\n{prefix}HNRF Access Token Uri: {atr['hnrfAccessTokenUri']}" - if 'sourceNfInstanceId' in atr: - ret += f"\n{prefix}Souce NF Instance Id: {atr['sourceNfInstanceId']}" - return ret - -class ProblemDetail(TypedDict, total=False): - ''' - ProblemDetail structure in TS 29.571 - ''' - problemtype: str - title: str - status: int - detail: str - instance: str - cause: str - invalidParams: List[InvalidParam] - supportedFeatures: str - accessTokenError: AccessTokenErr - accessTokenRequest: AccessTokenReq - nrfId: str - - @staticmethod - def fromJSON(problem_detail_json: str) -> "ProblemDetail": - ''' - Generate a `ProblemDetail` structure from a JSON string - - :param str problem_detail_json: The JSON string to convert to a `ProblemDetail`. - :return: a `ProblemDetail` containing the data from the *problem_detail_json* JSON string. - ''' - prob_detail = json.loads(problem_detail_json) - # Convert enumerated type strings to their enum values - if 'accessTokenError' in prob_detail: - for ate in prob_detail['accessTokenError']: - ate['error'] = AccessTokenErrError(ate['error']) - if 'accessTokenRequest' in prob_detail: - for atr in prob_detail['accessTokenRequest']: - atr['grant_type'] = AccessTokenReqGrantType(atr['grant_type']) - return prob_detail - - @staticmethod - def format(pd: "ProblemDetail", indent: int = 0) -> str: - prefix = ' ' * indent - ret = '' - if 'problemtype' in pd: - ret += f"\n{prefix}Problem Type: {pd['problemtype']}" - if 'title' in pd: - ret += f"\n{prefix}Title: {pd['title']}" - if 'status' in pd: - ret += f"\n{prefix}Status: {pd['status']}" - if 'detail' in pd: - ret += f''' -{prefix}Detail: -{prefix} {pd['detail']}''' - if 'instance' in pd: - ret += f"\n{prefix}Instance: {pd['instance']}" - if 'cause' in pd: - ret += f"\n{prefix}Cause: {pd['cause']}" - if 'invalidParams' in pd: - ret += f''' -{prefix}Invalid Parameters: -{''.join([InvalidParam.format(p, indent+2) for p in pd['invalidParams']])}''' - if 'supportedFeatures' in pd: - ret += f"\n{prefix}Supported Features: {pd['supportedFeatures']}" - if 'accessTokenError' in pd: - ret += f''' -{prefix}Access Token Error: -{AccessTokenErr.format(pd['accessTokenError'], indent+2)}''' - if 'accessTokenRequest' in pd: - ret += f''' -{prefix}Access Token Request: -{AccessTokenReq.format(pd['accessTokenRequest'], indent+2)}''' - if 'nrfId' in pd: - ret += f"\n{prefix}NRF Id: {pd['nrfId']}" - return ret[1:] - -__all__ = [ - "ProblemDetail", - "AccessTokenErr", - "AccessTokenReq", - "InvalidParam", - "ApplicationId", - "ResourceId", - "PolicyTemplate", - "ProvisioningSessionId", - "ProvisioningSessionType", - "ProvisioningSession", - "PROVISIONING_SESSION_TYPE_DOWNLINK", - "PROVISIONING_SESSION_TYPE_UPLINK", - ] diff --git a/tools/python3/m1_client_cli.py b/tools/python3/m1_client_cli.py deleted file mode 100755 index 4f785e2..0000000 --- a/tools/python3/m1_client_cli.py +++ /dev/null @@ -1,731 +0,0 @@ -#!/usr/bin/python3 -#============================================================================== -# 5G-MAG Reference Tools: M1 Client CLI -#============================================================================== -# -# File: m1_client_cli.py -# License: 5G-MAG Public License (v1.0) -# Author: David Waring -# Copyright: (C) 2023 British Broadcasting Corporation -# -# For full license terms please see the LICENSE file distributed with this -# program. If this file is missing then the license can be retrieved from -# https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -# -#============================================================================== -# -# M1 Client CLI -# =============== -# -# This is a simple command line tool which will communicate with a 5GMS -# Application Function via the M1 interface. -# -'''5G-MAG Reference Tools: M1 Client CLI - -This provides a simple command line interface which can be used to manipulate -the configuration of a 5GMS Application Function via the M1 interface. - -Syntax: - m1-client -h - m1-client provisioning create [-h] (-d|-u) [] - m1-client provisioning show [-h] - m1-client provisioning delete [-h] - m1-client protocols [-h] - m1-client certificates create [-h] [--csr [...]] - m1-client certificates upload [-h] - m1-client certificates show [-h] [--info] - m1-client certificates delete [-h] - m1-client hosting create [-h] - m1-client hosting show [-h] - m1-client hosting update [-h] - m1-client hosting delete [-h] - m1-client hosting purge [-h] [] - m1-client consumption create [-h] [-i ] [-p ] [-l] [-a] - m1-client consumption show [-h] - m1-client consumption update [-h] [-i ] [-p ] [-l] [-a] - m1-client consumption delete [-h] - m1-client policy create [-h] - m1-client policy show [-h] - m1-client policy update [-h] - m1-client policy delete [-h] -''' - -import aiofiles -import argparse -import asyncio -import datetime -import os.path -import sys -from typing import Optional, Union - -import cryptography -import OpenSSL - -installed_packages_dir = '@python_packages_dir@' -if os.path.isdir(installed_packages_dir) and installed_packages_dir not in sys.path: - sys.path.append(installed_packages_dir) - -from rt_m1_client.client import M1Client, ProvisioningSessionResponse, ContentProtocolsResponse, ServerCertificateSigningRequestResponse, ServerCertificateResponse, ContentHostingConfigurationResponse, ConsumptionReportingConfigurationResponse, PolicyTemplateResponse -from rt_m1_client.types import PROVISIONING_SESSION_TYPE_DOWNLINK, PROVISIONING_SESSION_TYPE_UPLINK, ContentHostingConfiguration, ConsumptionReportingConfiguration, PolicyTemplate -from rt_m1_client.exceptions import M1Error - -async def cmd_provisioning_create(args: argparse.Namespace) -> int: - client = await getClient(args) - asp_id = args.asp_id - app_id = args.external_app_id - if args.downlink: - prov_type = PROVISIONING_SESSION_TYPE_DOWNLINK - else: - prov_type = PROVISIONING_SESSION_TYPE_UPLINK - prov_sess_resp: Optional[ProvisioningSessionResponse] = await client.createProvisioningSession(prov_type, app_id, asp_id) - if prov_sess_resp is None: - print('Failed to create provisioning session') - return 1 - print(f"provisioning_session_id={prov_sess_resp['ProvisioningSessionId']}") - return 0 - -async def cmd_provisioning_show(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id = args.provisioning_session_id - prov_sess_resp: Optional[ProvisioningSessionResponse] = await client.getProvisioningSessionById(provisioning_session_id) - if prov_sess_resp is None: - print('Failed to fetch provisioning session') - return 1 - print(f"{prov_sess_resp['ProvisioningSession']!r}") - return 0 - -async def cmd_provisioning_delete(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id = args.provisioning_session_id - prov_sess_resp: bool = await client.destroyProvisioningSession(provisioning_session_id) - if not prov_sess_resp: - print('Failed to delete provisioning session') - return 1 - print('Provisioning session deleted') - return 0 - -async def cmd_protocols(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id = args.provisioning_session_id - resp: Optional[ContentProtocolsResponse] = await client.retrieveContentProtocols(provisioning_session_id) - if resp is None: - print('Failed to get ContentProtocols for provisioning session') - return 1 - print(f"{resp['ContentProtocols']!r}") - return 0 - -async def cmd_certificates_create(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id = args.provisioning_session_id - csr = args.csr is not None - fqdns = args.csr - resp: Optional[ServerCertificateSigningRequestResponse] = await client.createOrReserveServerCertificate(provisioning_session_id, extra_domain_names=fqdns, csr=csr) - if resp is None: - print('Failed to create a server certificate in the provisioning session') - return 1 - print(f"certificate_id={resp['ServerCertificateId']}") - if csr: - print(resp['CertificateSigningRequestPEM']) - return 0 - - -async def cmd_certificates_upload(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id = args.provisioning_session_id - certificate_id = args.certificate_id - pem_file = args.PEM_file - async with aiofiles.open(pem_file, mode='r') as in_file: - pem = await in_file.read() - resp: bool = await client.uploadServerCertificate(provisioning_session_id, certificate_id, pem) - if not resp: - print('Failed to upload public certificate') - return 1 - print('Public certificate uploaded') - return 0 - -def format_int_hex_block(v: int, bits: int, bytes_per_line: int, indent: int = 0) -> str: - digits = int(bits/4) - h = hex(v)[2:] - if len(h) < digits: - zeros = digits - len(h) - h = '0' * zeros + h - out_prefix = ' ' * indent - line_sep = f'\n{out_prefix}' - line_digits = bytes_per_line*2 - return (out_prefix + line_sep.join([''.join([s[i:i+2]+':' for i in range(0,len(s),2)]) for s in [h[i:i+line_digits] for i in range(0,len(h),line_digits)]]))[:-1] - -def format_x509_name(name: OpenSSL.crypto.X509Name, indent: int = 0) -> str: - '''Return a human readable `str` representing the X509Name - - :param pkey: The X509Name for format as a `str`. - :param indent: The number of space characters to preceed every output line with. - - :return: a human readable version of *name*. - ''' - out_prefix = ' ' * indent - return out_prefix + ', '.join([k.decode('utf-8')+'='+v.decode('utf-8') for k,v in name.get_components()]) - -def format_x509_extension(ext: OpenSSL.crypto.X509Extension, indent: int = 0) -> str: - '''Return a human readable `str` representing the X509Extension - - :param pkey: The X509Extension for format as a `str`. - :param indent: The number of space characters to preceed every output line with. - - :return: a human readable version of *ext*. - ''' - out_prefix = ' ' * indent - ret = '' - critical: bool = ext.get_critical() - value: str = str(ext) - short_name: str = ext.get_short_name().decode('utf-8') - name: str = short_name - long_names = { - 'subjectAltName': 'X509v3 Subject Alternative Names', - 'basicConstraints': 'X509v3 Basic Constraints', - 'authorityKeyIdentifier': 'X509v3 Authority Key Identifier', - 'subjectKeyIdentifier': 'X509v3 Subject Key Identifier', - 'authorityInformationAccess': 'Authority Information Access', - 'extendedKeyUsage': 'X509v3 Extended Key Usage', - 'crlDistributionPoints': 'X509v3 CRL Distribution Points', - 'keyUsage': 'X509v3 Key Usage', - } - if short_name in long_names: - name = long_names[short_name] - ret += out_prefix+name+':' - if critical: - ret += ' critical' - ret += '\n' - ret += '\n'.join([out_prefix+' '+s for s in value.split('\n')])+'\n' - return ret - -def format_rsa_public_key(key: cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey, indent: int = 0) -> str: - out_prefix = ' ' * indent - numbers = key.public_numbers() - ret = f'''{out_prefix}RSA Public Key: ({key.key_size} bits) -{out_prefix} Exponent: {numbers.e} ({hex(numbers.e)}) -{out_prefix} Modulus: -{format_int_hex_block(numbers.n, key.key_size, 15, indent+8)}''' - return ret - -def format_dsa_public_key(key: cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey, indent: int = 0) -> str: - out_prefix = ' ' * indent - numbers = key.public_numbers() - ret = f'''{out_prefix}DSA Public Key: ({key.key_size} bits) -{out_prefix} Parameters: -{out_prefix} p: {numbers.parameter_numbers.p} -{out_prefix} q: {numbers.parameter_numbers.q} -{out_prefix} g: {numbers.parameter_numbers.g} -{out_prefix} y: -{format_int_hex_block(numbers.y, key.key_size, 15, indent=indent+8)}''' - return ret - -def format_dh_public_key(key: cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey, indent: int = 0) -> str: - out_prefix = ' ' * indent - numbers = key.public_numbers() - ret = f'''{out_prefix}Diffie-Hellman Public Key: ({key.key_size} bits) -{out_prefix} Parameters: -{out_prefix} p: {numbers.parameter_numbers.p} -{out_prefix} g: {numbers.parameter_numbers.g} -{out_prefix} q: {numbers.parameter_numbers.q} -{out_prefix} y: -{format_int_hex_block(numbers.y, key.key_size, 15, indent=indent+8)}''' - return ret - -def format_ec_public_key(key: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey, indent: int = 0) -> str: - out_prefix = ' ' * indent - numbers = key.public_numbers() - ret = f'''{out_prefix}Elliptic-Curve Public Key: ({key.key_size} bits) -{out_prefix} Curve: {numbers.curve.name} -{out_prefix} x: -{format_int_hex_block(numbers.x, key.key_size, 15, indent=indent+8)} -{out_prefix} y: -{format_int_hex_block(numbers.y, key.key_size, 15, indent=indent+8)}''' - return ret - -def format_pkey(pkey: OpenSSL.crypto.PKey, indent: int = 0) -> str: - '''Return a human readable `str` representing the PKey - - :param pkey: The PKey for format as a `str`. - :param indent: The number of space characters to preceed every output line with. - - :return: a human readable version of *pkey*. - ''' - cryptokey = pkey.to_cryptography_key() - if isinstance(cryptokey, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey): - return format_rsa_public_key(cryptokey, indent) - if isinstance(cryptokey, cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey): - return format_dsa_public_key(cryptokey, indent) - if isinstance(cryptokey, cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey): - return format_dh_public_key(cryptokey, indent) - if isinstance(cryptokey, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey): - return format_ec_public_key(cryptokey, indent) - out_prefix = ' ' * indent - ret = f'{out_prefix}{cryptokey.__class__.__name__} type public key, unable to format' - return ret - -def format_x509_pem(pem: str, indent: int = 0) -> str: - '''Return a human readable `str` representing the X509 public certificate - - :param pem: The PEM data for the public certificate. - :return: the PEM data in human readable form. - ''' - x509: OpenSSL.crypto.X509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, pem) - serial: int = x509.get_serial_number() - subject: OpenSSL.crypto.X509Name = x509.get_subject() - issuer: OpenSSL.crypto.X509Name = x509.get_issuer() - start_str: Optional[bytes] = x509.get_notBefore() - start: Optional[datetime.datetime] = None - if start_str is not None: - start = datetime.datetime.strptime(start_str.decode('utf-8'), '%Y%m%d%H%M%SZ').replace(tzinfo=datetime.timezone.utc) - end_str: Optional[bytes] = x509.get_notAfter() - end: Optional[datetime.datetime] = None - if end_str is not None: - end = datetime.datetime.strptime(end_str.decode('utf-8'), '%Y%m%d%H%M%SZ').replace(tzinfo=datetime.timezone.utc) - public_key: OpenSSL.crypto.PKey = x509.get_pubkey() - sig_alg: str = x509.get_signature_algorithm().decode('utf-8') - version: int = x509.get_version() - out_prefix = ' ' * indent - ret: str = f'''{out_prefix}Certificate: -{out_prefix} Data: -{out_prefix} Version: {1+version} ({hex(version)}) -{out_prefix} Serial Number: {serial} ({hex(serial)}) -{out_prefix} Signature Algorithm: {sig_alg} -{out_prefix} Issuer: {format_x509_name(issuer)} -''' - if start is not None or end is not None: - ret += f'{out_prefix} Validity\n' - if start is not None: - ret += f'{out_prefix} Not Before: {start:%b %d %H:%M:%S %Y %Z}\n' - if end is not None: - ret += f'{out_prefix} Not After: {end:%b %d %H:%M:%S %Y %Z}\n' - ret += f'{out_prefix} Subject: {format_x509_name(subject)}\n' - ret += f'''{out_prefix} Subject Public Key Info: -{format_pkey(public_key, indent=indent+12)} -''' - if x509.get_extension_count() > 0: - ret += f'{out_prefix} X509v3 extensions:\n' - for ext_idx in range(x509.get_extension_count()): - ext = x509.get_extension(ext_idx) - ret += f'{format_x509_extension(ext, indent=indent+12)}' - return ret - -async def cmd_certificates_show(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id = args.provisioning_session_id - certificate_id = args.certificate_id - info = args.info - resp: Optional[ServerCertificateResponse] = await client.retrieveServerCertificate(provisioning_session_id, certificate_id) - if resp is None: - print('Certificate pending upload') - return 1 - if info: - print(f'''certificate_id={resp['ServerCertificateId']} -{format_x509_pem(resp['ServerCertificate'])} -''') - else: - print(resp['ServerCertificate']) - return 0 - -async def cmd_certificates_delete(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id = args.provisioning_session_id - certificate_id = args.certificate_id - resp: bool = await client.destroyServerCertificate(provisioning_session_id, certificate_id) - if not resp: - print('Failed to delete server certificate') - return 1 - return 0 - -def format_ContentHostingConfigurationResponse(resp: ContentHostingConfigurationResponse, indent: int = 0) -> str: - ret = '' - out_prefix = ' ' * indent - if resp['ETag'] is not None: - ret += f'{out_prefix}ETag: {resp["ETag"]}\n' - if resp['Last-Modified'] is not None: - ret += f'{out_prefix}Last-Modified: {resp["Last-Modified"]:%b %d %H:%M:%S %Y %Z}\n' - if resp['Cache-Until'] is not None: - ret += f'{out_prefix}Cache-Until: {resp["Cache-Until"]:%b %d %H:%M:%S %Y %Z}\n' - ret += f'''{out_prefix}Provisioning-Session-Id: {resp["ProvisioningSessionId"]} - -{out_prefix}{resp["ContentHostingConfiguration"]!r} -''' - return ret - -async def cmd_hosting_create(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id = args.provisioning_session_id - chc_file = args.CHC_JSON_file - async with aiofiles.open(chc_file, mode='r') as chc_in: - chc = ContentHostingConfiguration.fromJSON(await chc_in.read()) - resp: Union[bool,ContentHostingConfigurationResponse] = await client.createContentHostingConfiguration(provisioning_session_id, - chc) - if isinstance(resp, dict): - print(format_ContentHostingConfigurationResponse(resp)) - return 0 - if resp: - print('ContentHostingConfiguration set for provisioning session {provisioning_session_id}') - return 0 - print('Failed to set ContentHostingConfiguration for provisioning session {provisioning_session_id}') - return 1 - -async def cmd_hosting_show(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id = args.provisioning_session_id - resp: Optional[ContentHostingConfigurationResponse] = await client.retrieveContentHostingConfiguration(provisioning_session_id) - if resp is None: - print('ContentHostingConfiguration not found for provisioning session {provisioning_session_id}') - return 1 - print(format_ContentHostingConfigurationResponse(resp)) - return 0 - -async def cmd_hosting_update(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id = args.provisioning_session_id - chc_file = args.CHC_JSON_file - async with aiofiles.open(chc_file, mode='r') as chc_in: - chc = ContentHostingConfiguration.fromJSON(await chc_in.read()) - resp: bool = await client.updateContentHostingConfiguration(provisioning_session_id, chc) - if not resp: - print(f'Failed to update ContentHostingConfiguration for provisioning session {provisioning_session_id}') - return 1 - print(f'Updated ContentHostingConfiguration for provisioning session {provisioning_session_id}') - return 0 - -async def cmd_hosting_delete(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id = args.provisioning_session_id - resp: bool = await client.destroyContentHostingConfiguration(provisioning_session_id) - if not resp: - print(f'Failed to remove ContentHostingConfiguration for provisioning session {provisioning_session_id}') - return 1 - print(f'ContentHostingConfiguration for provisioning session {provisioning_session_id} has been removed') - return 0 - -async def cmd_hosting_purge(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id: ResourceId = args.provisioning_session_id - pattern: Optional[str] = args.path_regex - resp: Optional[int] = await client.purgeContentHostingCache(provisioning_session_id, pattern) - if resp is None: - print('No entries purged') - else: - print(f'There were {resp} entries purged from the cache') - return 0 - -async def __consumptionReportingConfigurationFromArgs(args: argparse.Namespace) -> ConsumptionReportingConfiguration: - crc: ConsumptionReportingConfiguration = {} - if args.interval is not None: - crc['reportingInterval'] = args.interval - if args.percentage is not None: - crc['samplePercentage'] = args.percentage - if args.locationReport: - crc['locationReporting'] = True - if args.accessReport: - crc['accessReporting'] = True - return crc - -async def cmd_consumption_create(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id: ResourceId = args.provisioning_session_id - crc: ConsumptionReportingConfiguration = await __consumptionReportingConfigurationFromArgs(args) - resp: Union[bool, ConsumptionReportingConfigurationResponse] = await client.activateConsumptionReportingConfiguration(provisioning_session_id, crc) - if isinstance(resp, bool) and resp or isinstance(resp, ConsumptionReportingConfigurationResponse): - print('ConsumptionReportingConfiguration created') - return 0 - -async def cmd_consumption_show(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id: ResourceId = args.provisioning_session_id - try: - resp: ConsumptionReportingConfigurationResponse = await client.retrieveConsumptionReportingConfiguration(provisioning_session_id) - print(ConsumptionReportingConfiguration.format(resp['ConsumptionReportingConfiguration'])) - except M1ClientError as err: - if err.args[1] == 404: - print('No ConsumptionReportingConfiguration for provisioning session') - else: - raise err - return 0 - -async def cmd_consumption_update(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id: ResourceId = args.provisioning_session_id - crc: ConsumptionReportingConfiguration = await __consumptionReportingConfigurationFromArgs(args) - resp: bool = await client.updateConsumptionReportingConfiguration(provisioning_session_id, crc) - if resp: - print('ConsumptionReportingConfiguration updated') - else: - print('ConsumptionReportingConfiguration update failed') - return 0 - -async def cmd_consumption_delete(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id: ResourceId = args.provisioning_session_id - resp: bool = await client.destroyConsumptionReportingConfiguration(provisioning_session_id) - if resp: - print('ConsumptionReportingConfiguration deleted') - else: - print('ConsumptionReportingConfiguration failed to delete') - return 0 - -async def __policyTemplateFromArgs(args: argparse.Namespace) -> PolicyTemplate: - with open(args.policy_template,'r') as pol_file: - return PolicyTemplate.fromJSON(pol_file.read()) - -async def cmd_policy_create(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id: ResourceId = args.provisioning_session_id - policy: PolicyTemplate = await __policyTemplateFromArgs(args) - resp: Optional[PolicyTemplateResponse] = await client.createPolicyTemplate(provisioning_session_id, policy) - if resp is None: - print('PolicyTemplate creation failed: No such provisioning session') - else: - print(f'''PolicyTemplate {resp['PolicyTemplate']['policyTemplateId']} created''') - return 0 - -async def cmd_policy_show(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id: ResourceId = args.provisioning_session_id - policy_template_id: ResourceId = args.policy_template_id - resp: Optional[PolicyTemplateResponse] = await client.retrievePolicyTemplate(provisioning_session_id, policy_template_id) - if resp is None: - print(f'PolicyTemplate "{policy_template_id}" for provisioning session "{provisioning_session_id}" not found') - else: - print(f'''{PolicyTemplate.format(resp['PolicyTemplate'])}''') - return 0 - -async def cmd_policy_update(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id: ResourceId = args.provisioning_session_id - policy_template_id: ResourceId = args.policy_template_id - policy: PolicyTemplate = await __policyTemplateFromArgs(args) - resp: bool = await client.updatePolicyTemplate(provisioning_session_id, policy_template_id, policy) - if resp: - print('PolicyTemplate updated successfully') - else: - print('PolicyTemplate update failed') - return 0 - -async def cmd_policy_delete(args: argparse.Namespace) -> int: - client = await getClient(args) - provisioning_session_id: ResourceId = args.provisioning_session_id - policy_template_id: ResourceId = args.policy_template_id - resp: bool = await client.destroyPolicyTemplate(provisioning_session_id, policy_template_id) - if resp: - print('PolicyTemplate deleted') - else: - print('Failed to delete policy {policy_template_id} for provisioning session {provisioning_session_id}') - return 0 - -async def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(prog='m1-client', description='M1 Client API tool') - subparsers = parser.add_subparsers(required=True) - - # Parent parser for AF address - parent_addr = argparse.ArgumentParser(add_help=False) - parent_addr.add_argument('address', metavar='address:port', help='Address of the 5GMS AF') - - # Parent parser for AF address and provisioning session id - parent_addr_prov = argparse.ArgumentParser(parents=[parent_addr], add_help=False) - parent_addr_prov.add_argument('provisioning_session_id', metavar='provisioning-session-id', help='The provisioning session id') - - # m1-client provisioning ... - parser_provisioning = subparsers.add_parser('provisioning', help='Provisioning Session Management') - provisioning_subparsers = parser_provisioning.add_subparsers(required=True) - - # m1-client provisioning create [-h] (-d|-u) [] - parser_provisioning_create = provisioning_subparsers.add_parser('create', parents=[parent_addr], - help='Create a new provisioning session') - parser_provisioning_create.set_defaults(command=cmd_provisioning_create) - parser_provisioning_create_type = parser_provisioning_create.add_mutually_exclusive_group(required=True) - parser_provisioning_create_type.add_argument('-d', '--downlink', action='store_true', help='Provisioning session is a downlink') - parser_provisioning_create_type.add_argument('-u', '--uplink', action='store_true', help='Provisioning session is an uplink') - parser_provisioning_create.add_argument('external_app_id', metavar='external-app-id', help='The external application id') - parser_provisioning_create.add_argument('asp_id', metavar='asp-id', nargs='?', help='The Application Service Provider id') - - # m1-client provisioning show [-h] - parser_provisioning_show = provisioning_subparsers.add_parser('show', parents=[parent_addr_prov], - help='Retreive and display a provisioning session') - parser_provisioning_show.set_defaults(command=cmd_provisioning_show) - - # m1-client provisioning delete [-h] - parser_provisioning_delete = provisioning_subparsers.add_parser('delete', parents=[parent_addr_prov], - help='Delete a provisioning session') - parser_provisioning_delete.set_defaults(command=cmd_provisioning_delete) - - # m1-client protocols [-h] - parser_protocols = subparsers.add_parser('protocols', parents=[parent_addr_prov], - help='Get the ContentProtocols for a provisioning session') - parser_protocols.set_defaults(command=cmd_protocols) - - # m1-client certificates ... - parser_certificates = subparsers.add_parser('certificates', help='ServerCertificatesProvisioning API') - certificates_subparsers = parser_certificates.add_subparsers(required=True) - - # m1-client certificates create [-h] [--csr] - parser_certificates_create = certificates_subparsers.add_parser('create', parents=[parent_addr_prov], - help='Create or reserve a new certificate') - parser_certificates_create.set_defaults(command=cmd_certificates_create) - parser_certificates_create.add_argument('--csr', metavar='FQDN', nargs='*', help='Reserve a certificate and return the CSR, provide optional extra domain names') - - # m1-client certificates upload [-h] - parser_certificates_upload = certificates_subparsers.add_parser('upload', parents=[parent_addr_prov], - help='Upload a public certificate') - parser_certificates_upload.set_defaults(command=cmd_certificates_upload) - parser_certificates_upload.add_argument('certificate_id', metavar='certificate-id', help='The certificate id to upload') - parser_certificates_upload.add_argument('PEM_file', metavar='PEM-file', help='The public certificate PEM file to upload') - - # m1-client certificates show [-h] [--info] - parser_certificates_show = certificates_subparsers.add_parser('show', parents=[parent_addr_prov], - help='Display a public certificate') - parser_certificates_show.set_defaults(command=cmd_certificates_show) - parser_certificates_show.add_argument('certificate_id', metavar='certificate-id', help='The certificate id to upload') - parser_certificates_show.add_argument('-i', '--info', action='store_true', - help='Display certificate information instead of the PEM data') - - # m1-client certificates delete [-h] - parser_certificates_delete = certificates_subparsers.add_parser('delete', parents=[parent_addr_prov], - help='Delete a certificate') - parser_certificates_delete.set_defaults(command=cmd_certificates_delete) - parser_certificates_delete.add_argument('certificate_id', metavar='certificate-id', help='The certificate id to delete') - - # m1-client hosting ... - parser_hosting = subparsers.add_parser('hosting', help='ContentHostingProvisioing APIs') - hosting_subparsers = parser_hosting.add_subparsers(required=True) - - # m1-client hosting create [-h] - parser_hosting_create = hosting_subparsers.add_parser('create', parents=[parent_addr_prov], - help='Add a ContentHostingConfiguration to a provisioning session') - parser_hosting_create.set_defaults(command=cmd_hosting_create) - parser_hosting_create.add_argument('CHC_JSON_file', metavar='CHC-JSON-file', - help='Path to a ContentHostingConfiguration JSON file') - - # m1-client hosting show [-h] - parser_hosting_show = hosting_subparsers.add_parser('show', parents=[parent_addr_prov], - help='Display the ContentHostingConfiguration for a provisioning session') - parser_hosting_show.set_defaults(command=cmd_hosting_show) - - # m1-client hosting update [-h] - parser_hosting_update = hosting_subparsers.add_parser('update', parents=[parent_addr_prov], - help='Update the existing ContentHostingConfiguration in a provisioning session') - parser_hosting_update.set_defaults(command=cmd_hosting_update) - parser_hosting_update.add_argument('CHC_JSON_file', metavar='CHC-JSON-file', - help='Path to a ContentHostingConfiguration JSON file') - - # m1-client hosting delete [-h] - parser_hosting_delete = hosting_subparsers.add_parser('delete', parents=[parent_addr_prov], - help='Delete the ContentHostingConfiguration for a provisioning session') - parser_hosting_delete.set_defaults(command=cmd_hosting_delete) - - # m1-client hosting purge [-h] [] - parser_hosting_purge = hosting_subparsers.add_parser('purge', parents=[parent_addr_prov], - help='Purge the cache for a provisioning session') - parser_hosting_purge.set_defaults(command=cmd_hosting_purge) - parser_hosting_purge.add_argument('path_regex', metavar='path-regex', nargs='?', - help='Regular expression to match for entries to purge') - - # m1-client consumption ... - parser_consumption = subparsers.add_parser('consumption', help='ConsumptionReportingProvisioing APIs') - consumption_subparsers = parser_consumption.add_subparsers(required=True) - - # m1-client consumption create [-h] [-i ] [-p ] [-l] [-a] - parser_consumption_create = consumption_subparsers.add_parser('create', parents=[parent_addr_prov], - help='Activate Consumption Reporting for a provisioning session') - parser_consumption_create.set_defaults(command=cmd_consumption_create) - parser_consumption_create.add_argument('-i','--interval', type=int, nargs=1, - help='The reporting interval for consumption reporting in whole seconds') - parser_consumption_create.add_argument('-p','--percentage', type=float, nargs=1, - help='The sample percentage to request for consumption reporting') - parser_consumption_create.add_argument('-l', '--location-reporting', action='store_true', dest='location_reporting', - help='Indicates that location reporting should be requested') - parser_consumption_create.add_argument('-a', '--access-reporting', action='store_true', dest='access_reporting', - help='Indicates that access reporting should be requested') - - # m1-client consumption show [-h] - parser_consumption_show = consumption_subparsers.add_parser('show', parents=[parent_addr_prov], - help='Retrieve a ConsumptionReportingConfiguration for a provisioning session') - parser_consumption_show.set_defaults(command=cmd_consumption_show) - - # m1-client consumption update [-h] [-i ] [-p ] [-l] [-a] - parser_consumption_update = consumption_subparsers.add_parser('update', parents=[parent_addr_prov], - help='Update Consumption Reporting for a provisioning session') - parser_consumption_update.set_defaults(command=cmd_consumption_update) - parser_consumption_update.add_argument('-i','--interval', type=int, nargs=1, - help='The reporting interval for consumption reporting in whole seconds') - parser_consumption_update.add_argument('-p','--percentage', type=float, nargs=1, - help='The sample percentage to request for consumption reporting') - parser_consumption_update.add_argument('-l', '--location-reporting', action='store_true', dest='location_reporting', - help='Indicates that location reporting should be requested') - parser_consumption_update.add_argument('-a', '--access-reporting', action='store_true', dest='access_reporting', - help='Indicates that access reporting should be requested') - - # m1-client consumption delete [-h] - parser_consumption_delete = consumption_subparsers.add_parser('delete', parents=[parent_addr_prov], - help='Delete the Consumption Reporting for a provisioning session') - parser_consumption_delete.set_defaults(command=cmd_consumption_delete) - - # m1-client policy ... - parser_policy = subparsers.add_parser('policy', help='PolicyTemplateProvisioning APIs') - policy_subparsers = parser_policy.add_subparsers(required=True) - - # m1-client policy create [-h] - parser_policy_create = policy_subparsers.add_parser('create', parents=[parent_addr_prov], - help='Create a Policy Template for a provisioning session') - parser_policy_create.set_defaults(command=cmd_policy_create) - parser_policy_create.add_argument('policy_template', metavar='PolicyTemplate-JSON-file', - help='Path to a PolicyTemplate JSON file') - - # m1-client policy show [-h] - parser_policy_show = policy_subparsers.add_parser('show', parents=[parent_addr_prov], - help='Display a Policy Template from a provisioning session') - parser_policy_show.set_defaults(command=cmd_policy_show) - parser_policy_show.add_argument('policy_template_id', metavar='policy-template-id', - help='Id of the Policy Template to display from the provisioning session') - - # m1-client policy update [-h] - parser_policy_update = policy_subparsers.add_parser('update', parents=[parent_addr_prov], - help='Update a Policy Template for a provisioning session') - parser_policy_update.set_defaults(command=cmd_policy_update) - parser_policy_update.add_argument('policy_template_id', metavar='policy-template-id', - help='Id of the Policy Template to update from the provisioning session') - parser_policy_update.add_argument('policy_template', metavar='PolicyTemplate-JSON-file', - help='Path to a PolicyTemplate JSON file') - - # m1-client policy delete [-h] - parser_policy_delete = policy_subparsers.add_parser('delete', parents=[parent_addr_prov], - help='Delete a PolicyTemplate from a provisioning session') - parser_policy_delete.set_defaults(command=cmd_policy_delete) - parser_policy_delete.add_argument('policy_template_id', metavar='policy-template-id', - help='Id of the Policy Template to delete from the provisioning session') - - return parser.parse_args() - -async def getClient(args: argparse.Namespace) -> M1Client: - if not hasattr(args, 'address'): - raise RuntimeError('Attempt to connect to M1Client without an address') - (addr,port) = args.address.split(':') - port = int(port) - return M1Client((addr,port)) - -async def main(): - ''' - Async application entry point - ''' - try: - args = await parse_args() - if hasattr(args, 'command'): - return await args.command(args) - print('Command not understood') - return 1 - except M1Error as err: - print(f'Communication error: {err}') - return 2 - return 0 - -def app(): - ''' - Application entry point - ''' - return asyncio.run(main()) - -if __name__ == '__main__': - sys.exit(app()) diff --git a/tools/python3/m1_session_cli.py b/tools/python3/m1_session_cli.py deleted file mode 100755 index 70f71e8..0000000 --- a/tools/python3/m1_session_cli.py +++ /dev/null @@ -1,1154 +0,0 @@ -#!/usr/bin/python3 -#============================================================================== -# 5G-MAG Reference Tools: M1 Session CLI -#============================================================================== -# -# File: m1_session_cli.py -# License: 5G-MAG Public License (v1.0) -# Author: David Waring -# Copyright: (C) 2023 British Broadcasting Corporation -# -# For full license terms please see the LICENSE file distributed with this -# program. If this file is missing then the license can be retrieved from -# https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -# -#============================================================================== -# -# M1 Session CLI -# =============== -# -# This is a command line tool to perform operations on the 5GMS Application -# Function via the M1 interface. -# -''' -====================================== -5G-MAG Reference Tools: M1 Session CLI -====================================== - -Perform operations on the 5GMS Application Function via the interface at -reference point M1. - -Syntax: - m1-session-cli -h - m1-session-cli configure -h - m1-session-cli configure show - m1-session-cli configure set - m1-session-cli configure get - m1-session-cli list -h - m1-session-cli list [-v] - m1-session-cli new-provisioning-session -h - m1-session-cli new-provisioning-session [-e ] [-a ] - m1-session-cli new-stream [-e ] [-a ] [-n ] [--with-ssl|--ssl-only] - [] - m1-session-cli del-stream -h - m1-session-cli del-stream -p - m1-session-cli del-stream [] - m1-session-cli set-stream -h - m1-session-cli set-stream -p - m1-session-cli new-certificate -h - m1-session-cli new-certificate -p [-d ...] [--csr] - m1-session-cli show-certificate -h - m1-session-cli show-certificate -p -c - m1-session-cli set-certificate -h - m1-session-cli set-certificate -p -c [] - m1-session-cli del-certificate -h - m1-session-cli del-certificate -p -c - m1-session-cli check-certificates-renewal -h - m1-session-cli check-certificates-renewal - m1-session-cli renew-certificates -h - m1-session-cli renew-certificates -p - m1-session-cli renew-certificates [] - m1-session-cli set-consumption-reporting -h - m1-session-cli set-consumption-reporting -p [-i ] [-s ] [-l] [-A] - m1-session-cli show-consumption-reporting -h - m1-session-cli show-consumption-reporting -p - m1-session-cli del-consumption-reporting -h - m1-session-cli del-consumption-reporting -p - m1-session-cli new-policy-template -h - m1-session-cli new-policy-template -p -e [-D ] [-S ] - [--qos-reference ] [--max-up ] [--max-down ] - [--max-auth-up ] [--max-auth-down ] - [--default-packet-loss-up ] [--default-packet-loss-down ] - [--chg-sponsor-id ] [--chg-sponsor-enabled|--chg-sponsor-disabled] - [--gpsi ]... - m1-session-cli update-policy-template -h - m1-session-cli update-policy-template -p -t [-D ] [-S ] - [--qos-reference ] [--max-up ] [--max-down ] - [--max-auth-up ] [--max-auth-down ] - [--default-packet-loss-up ] [--default-packet-loss-down ] - [--chg-sponsor-id ] - [--chg-sponsor-enabled|--chg-sponsor-disabled|--chg-sponsor-none] - [--gpsi [--gpsi ]...|--no-gpsi] - m1-session-cli del-policy-template -h - m1-session-cli del-policy-template -p -t - m1-session-cli show-policy-template -h - m1-session-cli show-policy-template -p -t - -Parameters: - -a ID --asp-id ID The application service provider id. - -A --access-reporting Include access reporting. - -c ID --certificate-id ID The certificate id to operate on. - -d FQDN --domain-name-alias FQDN The alternate domain name to use. - -D DNN --designated-network-name DNN The designated network name to set in a policy template. - -e ID --external-app-id ID The external application id. - -h --help Display the help message. - -i SEC --interval SEC The reporting interval in seconds. - -l --location-reporting Include location reporting. - -n NAME --name NAME The hosting name. - -p ID --provisioning-session-id ID The provisioning session id to use. - -s PCT --sample-percentage PCT The sampling percentage. - -S ID --s-nssai ID The 6 digit S-NSSAI for the policy template. - -t ID --policy-template-id ID The policy template id to use. - --ssl-only Provide HTTPS only. - --with-ssl Provide both HTTPS and HTTP. - --csr When reserving a cetrificate, pass back the CSR. - --qos-reference REF The QoS Reference name. - --max-up BITRATE The QoS maximum uplink bitrate. - --max-down BITRATE The QoS maximum downlink bitrate. - --max-auth-up BITRATE The QoS maximum authorised uplink bitrate. - --max-auth-down BITRATE The QoS maximum authorised downlink bitrate. - --default-packet-loss-up RATE The QoS default packet loss rate for uplink traffic. - --default-packet-loss-down RATE The QoS default packet loss rate for downlink traffic. - --chg-sponsor-id ID The charging specification sponsor id. - --chg-sponsor-enabled The charging sponsor is enabled flag. - --chg-sponsor-disabled The charging sponsor is disabled flag. - --chg-sponsor-none Remove the charging sponsor flag on update. - --gpsi GPSI A charging GPSI value (may be given multiple times). - --no-gpsi Remove all charging GPSI values on update. - -Arguments: - certificate-PEM-file The file path of a PEM holding a public certificate. - ContentHostingConfiguration-JSON The file path of a JSON file holding a ContentHostingConfiguration. - entry-point-suffix-URL Optional media entry URL path. - ingest-URL The base URL to fetch content from. - key The configuration field name. - value The configuration field value. -''' - -import aiofiles -import argparse -import asyncio -import configparser -import copy -import datetime -from io import StringIO -import logging -import os -import os.path -import sys -import traceback -from typing import Tuple, List, Optional - -#logging.basicConfig(level=logging.DEBUG) - -import json -import OpenSSL - -installed_packages_dir = '@python_packages_dir@' -if os.path.isdir(installed_packages_dir) and installed_packages_dir not in sys.path: - sys.path.append(installed_packages_dir) - -from rt_m1_client.session import M1Session -from rt_m1_client.exceptions import M1Error -from rt_m1_client.data_store import JSONFileDataStore -from rt_m1_client.types import ContentHostingConfiguration, ConsumptionReportingConfiguration, PolicyTemplate, BitRate, SponsoringStatus -from rt_m1_client.configuration import Configuration - -async def cmd_configure_show(args: argparse.Namespace, config: Configuration) -> int: - '''Perform ``configure show`` operation - - Will write to stdout the current configuration. - ''' - default_marker = {True: ' (default)', False: ''} - print('Configuration settings:') - print('\n'.join([f'{key} = {config.get(key, raw=True)}{default_marker[config.isDefault(key)]}' for key in config.getKeys()])) - return 0 - -async def cmd_configure_reset(args: argparse.Namespace, config: Configuration) -> int: - '''Perform ``configure reset`` operation - - Will reset the configuration option *key* back to its default value. - ''' - config.resetValue(args.key) - return 0 - -async def cmd_configure_get(args: argparse.Namespace, config: Configuration) -> int: - '''Perform ``configure get`` operation - - Write to stdout an interpolated configuration option in the form ``=""``. This could be evaluated in an external - shell. - ''' - print(f'{args.key}={repr(config.get(args.key))}') - return 0 - -async def cmd_configure_set(args: argparse.Namespace, config: Configuration) -> int: - '''Perform ``configure set`` operation - - Set a configuration value and save the new configuration. - ''' - config.set(args.key, args.value) - return 0 - -def __formatX509Name(x509name: OpenSSL.crypto.X509Name) -> str: - '''Format an X509Name as a comma separated DN string - - :meta private: - :param OpenSSL.crypto.X509Name x509name: The X509 name to convert to a string. - :return: a ``str`` version of the X509 Name as comma separated DN fields. - :rtype: str - ''' - ret = ",".join([f"{name.decode('utf-8')}={value.decode('utf-8')}" for name,value in x509name.get_components()]) - return ret - -async def __prettyPrintCertificate(cert: str, indent: int = 0) -> None: - '''Print certificate information from X509 PEM data - - :param str cert: X509 certificate encoded as PEM data - :param int indent: The indent to use in the certificate output - ''' - try: - x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert) - except OpenSSL.crypto.Error as err: - print(f'{" "*indent} Certificate not understood as PEM data: {err}') - return - serial = x509.get_serial_number() - subject = x509.get_subject() - issuer = x509.get_issuer() - start_str = x509.get_notBefore() - if isinstance(start_str, bytes): - start_str = start_str.decode('utf-8') - start = datetime.datetime.strptime(start_str, '%Y%m%d%H%M%SZ').replace(tzinfo=datetime.timezone.utc) - end_str = x509.get_notAfter() - if isinstance(end_str, bytes): - end_str = end_str.decode('utf-8') - end = datetime.datetime.strptime(end_str, '%Y%m%d%H%M%SZ').replace(tzinfo=datetime.timezone.utc) - subject_key = None - issuer_key = None - sans = [] - for ext_num in range(x509.get_extension_count()): - ext = x509.get_extension(ext_num) - ext_name = ext.get_short_name().decode('utf-8') - if ext_name == "subjectKeyIdentifier": - subject_key = str(ext) - elif ext_name == "authorityKeyIdentifier": - issuer_key = str(ext) - elif ext_name == "subjectAltName": - sans += [s.strip() for s in str(ext).split(',')] - cert_info_prefix=' '*indent - cert_desc=f'{cert_info_prefix}Serial = {serial}\n{cert_info_prefix}Not before = {start}\n{cert_info_prefix}Not after = {end}\n{cert_info_prefix}Subject = {__formatX509Name(subject)}\n' - if subject_key is not None: - cert_desc += f'{cert_info_prefix} key={subject_key}\n' - cert_desc += f'{cert_info_prefix}Issuer = {__formatX509Name(issuer)}' - if issuer_key is not None: - cert_desc += f'\n{cert_info_prefix} key={issuer_key}' - if len(sans) > 0: - cert_desc += f'\n{cert_info_prefix}Subject Alternative Names:' - cert_desc += ''.join([f'\n{cert_info_prefix} {san}' for san in sans]) - print(f'{cert_desc}') - -async def cmd_list_verbose(args: argparse.Namespace, config: Configuration) -> int: - '''Perform ``list -v`` operation - - Output to stdout a verbose list of the defined provisioning sessions and their resources. - ''' - session = await get_session(config) - for ps_id in await session.provisioningSessionIds(): - print(f'{ps_id}:') - certs = await session.certificateIds(ps_id) - print(' Certificates:') - for cert_id in certs: - print(f' {cert_id}:') - try: - cert = await session.certificateGet(ps_id, cert_id) - if cert is not None: - await __prettyPrintCertificate(cert, indent=6) - else: - print(' Certificate not yet uploaded') - except M1Error as err: - print(f' Certificate not available: {str(err)}') - chc = await session.contentHostingConfigurationGet(ps_id) - print(' ContentHostingConfiguration:') - if chc is not None: - print('\n'.join([' '+line for line in ContentHostingConfiguration.format(chc).split('\n')])) - else: - print(' Not defined') - crc = await session.consumptionReportingConfigurationGet(ps_id) - print(' ConsumptionReportingConfiguration:') - if crc is not None: - print(ConsumptionReportingConfiguration.format(crc, indent=4)) - else: - print(' Not defined') - pol_ids = await session.policyTemplateIds(ps_id) - if pol_ids is not None and len(pol_ids) > 0: - print(' PolicyTemplates:') - for polid in pol_ids: - print(f' {polid}:') - pol = await session.policyTemplateGet(ps_id, polid) - if pol is not None: - print(PolicyTemplate.format(pol, indent=6)) - return 0 - -async def cmd_list(args: argparse.Namespace, config: Configuration) -> int: - '''Perform ``list`` operation - - Output to stdout a list of the defined provisioning session ids, one per line. - ''' - if args.verbose: - return await cmd_list_verbose(args, config) - session = await get_session(config) - print('\n'.join(await session.provisioningSessionIds())) - return 0 - -async def cmd_new_provisioning_session(args: argparse.Namespace, config: Configuration) -> int: - '''Perform ``new-provisioning-session`` operation - - This will reserve a new, empty, provisioning session. - - Will output to stdout the result including the new provisioning session id. - ''' - session = await get_session(config) - app_id = args.app_id or config.get('external_app_id') - asp_id = args.asp_id or config.get('asp_id') - provisioning_session_id: Optional[ResourceId] = await session.createDownlinkPullProvisioningSession(app_id, asp_id=asp_id) - if provisioning_session_id is None: - print(f'Failed to create a new provisioing session') - return 1 - print(f'Provisioning session {provisioning_session_id} created') - return 0 - -async def cmd_set_stream(args: argparse.Namespace, config: Configuration) -> int: - '''Perform ``set-stream`` operation - - This will set the ContentHostingConfiguration for a provisioning session. - - Will output to stdout the result. - ''' - session = await get_session(config) - provisioning_session_id = args.provisioning_session - - async with aiofiles.open(args.file, 'r') as json_in: - chc = json.loads(await json_in.read()) - old_chc = await session.contentHostingConfigurationGet(provisioning_session_id) - if old_chc is None: - result = await session.contentHostingConfigurationCreate(provisioning_session_id, chc) - else: - # Remove any read-only fields - for dc in chc['distributionConfigurations']: - for strip_field in ['canonicalDomainName', 'baseURL']: - if strip_field in dc: - del dc[strip_field] - result = await session.contentHostingConfigurationUpdate(provisioning_session_id, chc) - if not result: - print(f'Failed to set hosting for provisioning session {provisioning_session_id}') - return 1 - print(f'Hosting set for provisioning session {provisioning_session_id}') - return 0 - -async def cmd_new_stream(args: argparse.Namespace, config: Configuration) -> int: - '''Perform ``new-stream`` operation - - This will generate and set the ContentHostingConfiguration for a provisioning session. If asked to provide an SSL distribution - point it will also generate the ServerCertificate within the provisioning session. - - Will output to stdout the result. - ''' - session = await get_session(config) - name = args.name - use_ssl = args.with_ssl or args.ssl_only - use_plain = not args.ssl_only - app_id = args.app_id or config.get('external_app_id') - asp_id = args.asp_id or config.get('asp_id') - domain_name_alias = args.domain_name_alias - provisioning_session_id = await session.createNewDownlinkPullStream(args.ingesturl, app_id, args.entrypoints, name=name, ssl=use_ssl, insecure=use_plain, asp_id=asp_id, domain_name_alias=domain_name_alias) - print(f'Hosting created as provisioning session {provisioning_session_id}') - return 0 - -async def cmd_delete_stream(args: argparse.Namespace, config: Configuration) -> int: - '''Perform ``delete-stream`` operation - - This will delete the provisioning session. - - This will remove the provisioning session and all its resources. - ''' - session = await get_session(config) - if args.provisioning_session is not None: - ps_id = args.provisioning_session - else: - ps_id = await session.provisioningSessionIdByIngestUrl(args.ingesturl, args.entrypoint) - if ps_id is None: - print('No such hosting session found') - return 1 - result = await session.provisioningSessionDestroy(ps_id) - if result is None: - print(f'Provisioning Session {ps_id} not found') - return 1 - if not result: - print(f'Failed to destroy Provisioning Session {ps_id}') - return 1 - print(f'Provisioning Session {ps_id} and all its resources were destroyed') - return 0 - -async def cmd_show_stream(args: argparse.Namespace, config: Configuration) -> int: - session = await get_session(config) - if args.provisioning_session is not None: - ps_id = args.provisioning_session - else: - ps_id = await session.provisioningSessionIdByIngestUrl(args.ingesturl, args.entrypoint) - if ps_id is None: - print('No such hosting session found') - return 1 - result = await session.contentHostingConfigurationGet(ps_id) - if result is None: - print(f'Provisioning Session {ps_id} does not have a ContentHostingConfiguration') - return 1 - if args.raw: - print(json.dumps(result, indent=2, sort_keys=True)) - else: - print(f'ContentHostingConfiguration for provisioning session {ps_id}:') - print('\n'.join([' '+line for line in ContentHostingConfiguration.format(result).split('\n')])) - return 0 - -async def cmd_protocols(args: argparse.Namespace, config: Configuration) -> int: - '''Perform ``protocols`` operation - - This will list the download and upload protocols for the provisioning session. - ''' - session = await get_session(config) - result = await session.provisioningSessionProtocols(args.provisioning_session) - if result is None: - print(f'Failed to fetch the content protocols for provisioning session {args.provisioning_session}') - return 1 - print(f'Protocols for {args.provisioning_session}:') - if 'downlinkIngestProtocols' in result: - print(' Downlink:') - print('\n'.join([f' {proto["termIdentifier"]}' for proto in result['downlinkIngestProtocols']])) - else: - print(' No downlink capability') - if 'uplinkEgestProtocols' in result: - print(' Uplink:') - print('\n'.join([f' {proto["termIdentifier"]}' for proto in result['uplinkEgestProtocols']])) - else: - print(' No uplink capability') - if 'geoFencingLocatorTypes' in result: - print(' Geo-fencing:') - print('\n'.join([f' {proto}' for proto in result['geoFencingLocatorTypes']])) - else: - print(' No geo-fencing capability') - return 0 - -async def cmd_new_certificate(args: argparse.Namespace, config: Configuration) -> int: - ''' Perform ``new-certificate`` operation - - This will create or reserve a new certificate in the provisioning session. - ''' - session = await get_session(config) - if args.csr: - result = await session.certificateNewSigningRequest(args.provisioning_session, extra_domain_names=args.domain_name_alias) - if result is None: - print('Failed to reserve certificate') - return 1 - cert_id, csr = result - print(f'certificate_id={cert_id}') - print(csr) - return 0 - cert_id = await session.createNewCertificate(args.provisioning_session, extra_domain_names=args.domain_name_alias) - if cert_id is None: - print('Failed to create certificate') - return 1 - print(f'certificate_id={cert_id}') - return 0 - -async def cmd_show_certificate(args: argparse.Namespace, config: Configuration) -> int: - ''' Perform ``show-certificate`` operation - - Display the certificate details for a given certificate. - ''' - session = await get_session(config) - result = await session.certificateGet(args.provisioning_session, args.certificate_id) - if result is None: - print(f'Unable to get certificate {args.certificate_id} for provisioning session {args.provisioning_session}') - return 1 - if args.raw: - print(result) - else: - print(f'Certificate details for {args.certificate_id}:') - await __prettyPrintCertificate(result, indent=2) - return 0 - -async def cmd_set_certificate(args: argparse.Namespace, config: Configuration) -> int: - ''' Perform ``set-certificate`` operation - - Set the public certificate for a ``new-certificate`` generated with the ``--csr`` flag. - ''' - session = await get_session(config) - if args.certificate_pem_file is None: - loop = asyncio.get_event_loop() - reader = asyncio.StreamReader() - protocol = asyncio.StreamReaderProtocol(reader) - await loop.connect_read_pipe(lambda: protocol, sys.stdin) - else: - reader = aiofiles.open(args.certificate_pem_file, 'r') - cert_pem = await reader.read() - await reader.close() - result = await session.certificateSet(args.provisioning_session, args.certificate_id, cert_pem) - if result is None: - print('Failed to set certificate') - return 1 - if not result: - print('Certificate already set') - return 1 - print('Certificate set') - return 0 - -async def cmd_check_all_renewal(args: argparse.Namespace, config: Configuration) -> int: - '''Perform ``check-all-renewal`` operation - - **TODO** - ''' - session = await get_session(config) - for ps_id in await session.provisioningSessionIds(): - chc = await session.getContentHostingConfiguration(ps_id) - # extract current cert ids - # get public cert for each cert id - # check for soon or past expiry - # request a new certificate - # change id in chc and remember old cert ids - # if any cert ids changed in chc upload replacement chc - # delete old certs - sys.stderr.write('check-all-renewal not yet implemented\n') - return 1 - -async def cmd_renew_certs(args: argparse.Namespace, config: Configuration) -> int: - '''Perform ``renew-certs`` operation - - **TODO** - ''' - session = await get_session(config) - ps_id = args.provisioning_session - chc = await session.getContentHostingConfiguration(ps_id) - # get list of unique cert ids in chc - # for each cert id in list - # request a new certificate - # change ids in chc for new cert id - # upload replacement chc - # delete old certs - sys.stderr.write('renew-certs not yet implemented\n') - return 1 - -async def cmd_set_consumption(args: argparse.Namespace, config: Configuration) -> int: - '''Activate or set consumption reporting parameters on a provisioning session - - ''' - session = await get_session(config) - ps_id = args.provisioning_session - crc: ConsumptionReportingConfiguration = {} - if args.interval is not None: - crc['reportingInterval'] = args.interval - if args.sample_percentage is not None: - crc['samplePercentage'] = args.sample_percentage - if args.location_reporting: - crc['locationReporting'] = True - if args.access_reporting: - crc['accessReporting'] = True - result: bool = await session.setOrUpdateConsumptionReporting(ps_id, crc) - if result: - print('Consumption reporting parameters set') - return 0 - print('Failed to set consumption reporting parameters') - return 1 - -async def cmd_show_consumption(args: argparse.Namespace, config: Configuration) -> int: - '''Display current consumption reporting parameters for a provisioning session - - ''' - session = await get_session(config) - ps_id = args.provisioning_session - crc: Optional[ConsumptionReportingConfiguration] = await session.consumptionReportingConfigurationGet(ps_id) - if crc is None: - print('No consumption reporting configured') - else: - print('Consumption Reporting:') - print(ConsumptionReportingConfiguration.format(crc, indent=2)) - return 0 - -async def cmd_del_consumption(args: argparse.Namespace, config: Configuration) -> int: - '''Remove the consumption reporting parameters for a provisioning session - - ''' - session = await get_session(config) - ps_id = args.provisioning_session - result: bool = await session.consumptionReportingConfigurationDelete(ps_id) - if result: - print('Consumption reporting removed') - return 0 - print('No consumption reporting to remove') - return 1 - -async def _make_policy_template_from_args(args: argparse.Namespace, extra_flags: bool = False, - base_policy: Optional[PolicyTemplate] = None) -> Optional[PolicyTemplate]: - if base_policy is None: - pt = dict() - else: - pt = copy.deepcopy(base_policy) - if not extra_flags: - if args.external_policy_id: - pt['externalReference'] = args.external_policy_id - else: - for v,a in [('dnn', 'dnn'), ('qos_reference', 'qos-reference'), ('max_auth_up', 'max-auth-up'), ('max_auth_down', 'max-auth-down'), ('default_packet_loss_up', 'default-packet-loss-up'), ('default_packet_loss_down', 'default-packet-loss-down'), ('gpsi', 'gpsi')]: - if getattr(args, 'no_'+v, False) and getattr(args, v, None) is not None: - print(f'Cannot specify both --no-{a} and --{a} arguments') - return None - if getattr(args, 'chg_sponsor_none', False) and (getattr(args, 'chg_sponsor_status', None) is not None or getattr(args, 'chg_sponsor_id', None) is not None): - print('Cannot specify both --chg-sponsor-none and other --chg-sponsor-... arguments') - return None - - # [--s-nssai ] - v = getattr(args, 's_nssai', None) - if v is not None: - (sst,sd) = (v.split(':') + [None])[:2] - if 'applicationSessionContext' not in pt: - pt['applicationSessionContext'] = {} - pt['applicationSessionContext']['sliceInfo'] = {'sst': int(sst)} - if sd is not None: - pt['applicationSessionContext']['sliceInfo']['sd'] = sd - - # [--no-s-nssai] - v = getattr(args, 'no_s_nssai', False) - if v: - if 'applicationSessionContext' in pt and 'sliceInfo' in pt['applicationSessionContext']: - del pt['applicationSessionContext']['sliceInfo'] - if len(pt['applicationSessionContext'].keys()) == 0: - del pt['applicationSessionContext'] - - # [--dnn ] - v = getattr(args, 'dnn', None) - if v is not None: - if 'applicationSessionContext' not in pt: - pt['applicationSessionContext'] = {} - pt['applicationSessionContext']['dnn'] = v - - # [--no-dnn] - v = getattr(args, 'no_dnn', False) - if v: - if 'applicationSessionContext' in pt and 'dnn' in pt['applicationSessionContext']: - del pt['applicationSessionContext']['dnn'] - if len(pt['applicationSessionContext'].keys()) == 0: - del pt['applicationSessionContext'] - - # [--qos-reference ] - v = getattr(args, 'qos_reference', None) - if v is not None: - if 'qoSSpecification' not in pt: - pt['qoSSpecification'] = {} - pt['qoSSpecification']['qosReference'] = v - - # [--no-qos-reference] - v = getattr(args, 'no_qos_reference', False) - if v: - if 'qoSSpecification' in pt and 'qosReference' in pt['qoSSpecification']: - del pt['qoSSpecification']['qosReference'] - if len(pt['qoSSpecification'].keys()) == 0: - del pt['qoSSpecification'] - - # [--max-auth-up ] - v = getattr(args, 'max_auth_up', None) - if v is not None: - if 'qoSSpecification' not in pt: - pt['qoSSpecification'] = {} - pt['qoSSpecification']['maxAuthBtrUl'] = BitRate(v) - - # [--no-max-auth-up] - v = getattr(args, 'no_max_auth_up', False) - if v: - if 'qoSSpecification' in pt and 'maxAuthBtrUl' in pt['qoSSpecification']: - del pt['qoSSpecification']['maxAuthBtrUl'] - if len(pt['qoSSpecification'].keys()) == 0: - del pt['qoSSpecification'] - - # [--max-auth-down ] - v = getattr(args, 'max_auth_down', None) - if v is not None: - if 'qoSSpecification' not in pt: - pt['qoSSpecification'] = {} - pt['qoSSpecification']['maxAuthBtrDl'] = BitRate(v) - - # [--no-max-auth-down] - v = getattr(args, 'no_max_auth_down', False) - if v: - if 'qoSSpecification' in pt and 'maxAuthBtrDl' in pt['qoSSpecification']: - del pt['qoSSpecification']['maxAuthBtrDl'] - if len(pt['qoSSpecification'].keys()) == 0: - del pt['qoSSpecification'] - - # [--default-packet-loss-up ] - v = getattr(args, 'default_packet_loss_up', None) - if v is not None: - if 'qoSSpecification' not in pt: - pt['qoSSpecification'] = {} - pt['qoSSpecification']['defPacketLossRateUl'] = int(v) - - # [--no-default-packet-loss-up] - v = getattr(args, 'no_default_packet_loss_up', False) - if v: - if 'qoSSpecification' in pt and 'defPacketLossRateUl' in pt['qoSSpecification']: - del pt['qoSSpecification']['defPacketLossRateUl'] - if len(pt['qoSSpecification'].keys()) == 0: - del pt['qoSSpecification'] - - # [--default-packet-loss-down ] - v = getattr(args, 'default_packet_loss_down', None) - if v is not None: - if 'qoSSpecification' not in pt: - pt['qoSSpecification'] = {} - pt['qoSSpecification']['defPacketLossRateDl'] = int(v) - - # [--no-default-packet-loss-down] - v = getattr(args, 'no_default_packet_loss_down', False) - if v: - if 'qoSSpecification' in pt and 'defPacketLossRateDl' in pt['qoSSpecification']: - del pt['qoSSpecification']['defPacketLossRateDl'] - if len(pt['qoSSpecification'].keys()) == 0: - del pt['qoSSpecification'] - - # [--chg-sponsor-id ] - v = getattr(args, 'chg_sponsor_id', None) - if v is not None: - if 'chargingSpecification' not in pt: - pt['chargingSpecification'] = {} - pt['chargingSpecification']['sponId'] = v - if 'sponStatus' not in pt['chargingSpecification']: - pt['chargingSpecification']['sponStatus'] = SponsoringStatus.SPONSOR_DISABLED - - # [--chg-sponsor-enabled] - # [--chg-sponsor-disabled] - v = getattr(args, 'chg_sponsor_status', None) - if v is not None: - if 'chargingSpecification' not in pt: - pt['chargingSpecification'] = {} - if v: - pt['chargingSpecification']['sponStatus'] = SponsoringStatus.SPONSOR_ENABLED - else: - pt['chargingSpecification']['sponStatus'] = SponsoringStatus.SPONSOR_DISABLED - - # [--chg-sponsor-none] - v = getattr(args, 'chg_sponsor_none', False) - if v: - if 'chargingSpecification' in pt: - if 'sponId' in pt['chargingSpecification']: - del pt['chargingSpecification']['sponId'] - if 'sponStatus' in pt['chargingSpecification']: - del pt['chargingSpecification']['sponStatus'] - if len(pt['chargingSpecification'].keys()) == 0: - del pt['chargingSpecification'] - - # [--gpsi ...] - v = getattr(args, 'gpsi', None) - if v is not None: - if not isinstance(v, list): - v = [v] - if 'chargingSpecification' not in pt: - pt['chargingSpecification'] = {} - pt['chargingSpecification']['gpsi'] = v - - # --no-gpsi - v = getattr(args, 'no_gpsi', False) - if v: - if 'chargingSpecification' in pt and 'gpsi' in pt['chargingSpecification']: - del pt['chargingSpecification']['gpsi'] - if len(pt['chargingSpecification'].keys()) == 0: - del pt['chargingSpecification'] - - return PolicyTemplate(pt) - -async def cmd_new_policy_template(args: argparse.Namespace, config: Configuration) -> int: - session = await get_session(config) - ps_id = args.provisioning_session - pol = await _make_policy_template_from_args(args, extra_flags=False) - result: Optional[ResourceId] = await session.policyTemplateCreate(ps_id, pol) - if result is not None: - print(f'Added PolicyTemplate {result} to provisioning session') - return 0 - print(f'Addition of PolicyTemplate to provisioning session failed!') - return 1 - -async def cmd_update_policy_template(args: argparse.Namespace, config: Configuration) -> int: - session = await get_session(config) - ps_id = args.provisioning_session - pol_id = args.policy_template_id - pol: Optional[PolicyTemplate] = await session.policyTemplateGet(ps_id, pol_id) - if pol is None: - print('Attempt to update a PolicyTemplate that does not exist') - return 1 - pol = await _make_policy_template_from_args(args, extra_flags=True, base_policy=pol) - result: Optional[PolicyTemplate] = await session.policyTemplateUpdate(ps_id, pol_id, pol) - if result is not None: - print(f'Updated PolicyTemplate for the provisioning session') - return 0 - print(f'Update of PolicyTemplate {pol_id} for the provisioning session {ps_id} failed!') - return 1 - -async def cmd_del_policy_template(args: argparse.Namespace, config: Configuration) -> int: - session = await get_session(config) - ps_id = args.provisioning_session - pol_id = args.policy_template_id - result: bool = await session.policyTemplateDelete(ps_id, pol_id) - if result: - print(f'Policy template {pol_id} removed from provisioning session {ps_id}') - return 0 - print(f'Failed to delete policy template {pol_id} removed from provisioning session {ps_id}') - return 1 - -async def cmd_show_policy_template(args: argparse.Namespace, config: Configuration) -> int: - session = await get_session(config) - ps_id = args.provisioning_session - pol_id = args.policy_template_id - result: Optional[PolicyTemplate] = await session.policyTemplateGet(ps_id, pol_id) - if result is not None: - print(PolicyTemplate.format(result, indent=0)) - return 0 - print(f'Failed to find policy template {pol_id} for provisioning session {ps_id}') - return 1 - -async def parse_args() -> Tuple[argparse.Namespace,Configuration]: - '''Parse command line options and load app configuration - - :return: Tuple containing the command line arguments after validation and the app configuration - :rtype: Tuple[argparse.Namespace,Configuration] - ''' - cfg = Configuration() - - parser = argparse.ArgumentParser(prog='m1-session', description='M1 Session Tool') - parser.add_argument('-D', '--debug', action='store_true', help='Enable debugging mode') - subparsers = parser.add_subparsers(required=True) - - # m1-session-cli configure ... - parser_configure = subparsers.add_parser('configure', help='Local configuration') - configure_subparsers = parser_configure.add_subparsers(required=True) - # m1-session-cli configure show - parser_configure_show = configure_subparsers.add_parser('show', help='Show local configuration') - parser_configure_show.set_defaults(command=cmd_configure_show) - # m1-session-cli configure get - parser_configure_get = configure_subparsers.add_parser('get', help='Get local configuration value') - parser_configure_get.set_defaults(command=cmd_configure_get) - parser_configure_get.add_argument('key', metavar='KEY', type=cfg.isKey) - # m1-session-cli configure set - parser_configure_set = configure_subparsers.add_parser('set', help='Set local configuration value') - parser_configure_set.set_defaults(command=cmd_configure_set) - parser_configure_set.add_argument('key', metavar='KEY', type=cfg.isKey) - parser_configure_set.add_argument('value', metavar='VALUE') - # m1-session-cli configure reset - parser_configure_reset = configure_subparsers.add_parser('reset', help='Reset configuration value to its default') - parser_configure_reset.set_defaults(command=cmd_configure_reset) - parser_configure_reset.add_argument('key', metavar='KEY', type=cfg.isKey) - - # m1-session-cli list [-v] - parser_list = subparsers.add_parser('list', help='List provisioning sessions') - parser_list.set_defaults(command=cmd_list) - parser_list.add_argument('-v', '--verbose', required=False, action='store_true') - - # m1-session-cli new-stream [-e ] [-a ] [-n ] [--with-ssl|--ssl-only] [-d ] \ - # [...] - parser_newstream = subparsers.add_parser('new-stream', help='Create a new ingest stream') - parser_newstream.set_defaults(command=cmd_new_stream) - parser_newstream.add_argument('-n', '--name', metavar='NAME', help='The name of the new stream', required=False) - parser_newstream.add_argument('-e', '--external-app-id', dest='app_id', metavar="APPLICATION-ID", help='The external application id to register the stream to', required=False) - parser_newstream.add_argument('-a','--asp-id', metavar="PROVIDER-ID", help="The Application Service Provider Id to use", required=False) - parser_newstream_ssl_options = parser_newstream.add_mutually_exclusive_group(required=False) - parser_newstream_ssl_options.add_argument('--with-ssl', action='store_true') - parser_newstream_ssl_options.add_argument('--ssl-only', action='store_true') - parser_newstream.add_argument('-d', '--domain-name-alias', dest='domain_name_alias', metavar='FQDN', help='Optional domain name alias for the distribution', required=False) - parser_newstream.add_argument('ingesturl', metavar='ingest-URL', help='The ingest URL prefix to use') - parser_newstream.add_argument('entrypoints', metavar='entry-point-path', nargs='*', - help='The media player entry point paths.') - - # m1-session-cli del-stream -p - # m1-session-cli del-stream [] - parser_delstream = subparsers.add_parser('del-stream', help='Delete an ingest stream') - parser_delstream.set_defaults(command=cmd_delete_stream) - parser_delstream_filter = parser_delstream.add_mutually_exclusive_group(required=True) - parser_delstream_filter.add_argument('-p', '--provisioning-session', help='Delete by provisioning session id') - parser_delstream_filter.add_argument('ingesturl', metavar='ingest-URL', nargs='?', help='The ingest URL prefix to identify the provisioning session.') - # The entry-point-path should go with ingest-URL, but argparser lacks the ability to do subgroups - parser_delstream.add_argument('entrypoint', metavar='entry-point-path', nargs='?', help='The media player entry point suffix to identify the provisioning session.') - - # m1-session-cli set-stream -p - parser_set_stream = subparsers.add_parser('set-stream', help='Set the hosting for a provisioning session from a JSON file') - parser_set_stream.set_defaults(command=cmd_set_stream) - parser_set_stream.add_argument('-p', '--provisioning-session', help='The provisioning session id to set the hosting for', - required=True) - parser_set_stream.add_argument('file', metavar='CHC-JSON-FILE', help='A filepath to a JSON encoded ContentHostingConfiguration') - - # m1-session-cli show-stream (-p | []) [-r] - parser_show_stream = subparsers.add_parser('show-stream', - help='Display the ContentHostingConfiguration for a provisioning session') - parser_show_stream.set_defaults(command=cmd_show_stream) - parser_show_stream_filter = parser_show_stream.add_mutually_exclusive_group(required=True) - parser_show_stream_filter.add_argument('-p', '--provisioning-session', help='The provisioning session id to show', - required=False) - parser_show_stream_filter.add_argument('ingesturl', metavar='ingest-URL', nargs='?', - help='The ingest URL prefix used to identify the provisioing session') - parser_show_stream.add_argument('entrypoint', metavar='entry-point-path', nargs='?', - help='A media player entry point suffix to identify the provisioning session.') - parser_show_stream.add_argument('-r', '--raw', required=False, action="store_true", - help='Use "raw" output mode to present the ContentHostingConfiguration JSON') - - # m1-session-cli new-provisioning-session [-e ] [-a ] - parser_new_provisioning_session = subparsers.add_parser('new-provisioning-session', help='Create a new provisioning session') - parser_new_provisioning_session.set_defaults(command=cmd_new_provisioning_session) - parser_new_provisioning_session.add_argument('-e', '--external-app-id', dest='app_id', metavar="APPLICATION-ID", - help='The external application id to register the stream to', required=False) - parser_new_provisioning_session.add_argument('-a','--asp-id', metavar="PROVIDER-ID", - help="The Application Service Provider Id to use", required=False) - - # m1-session-cli protocols -p - parser_protocols = subparsers.add_parser('protocols', - help='Get the available upload/download protocols for a provisioning session') - parser_protocols.set_defaults(command=cmd_protocols) - parser_protocols.add_argument('-p', '--provisioning-session', - help='Provisioning session id to list the upload and download protocols for') - - # m1-session-cli new-certificate -p [-d ...] [--csr] - parser_new_certificate = subparsers.add_parser('new-certificate', help='Create a new certificate') - parser_new_certificate.set_defaults(command=cmd_new_certificate) - parser_new_certificate.add_argument('-p', '--provisioning-session', - help='Provisioning session id to create the new certificate for') - parser_new_certificate.add_argument('-d', '--domain-name-alias', dest='domain_name_alias', nargs='*', metavar='FQDN', - help='FQDN to add as an extra domain name to the certificate') - parser_new_certificate.add_argument('--csr', action='store_true', - help='Return a CSR to be signed externally and published using set-certificate') - - # m1-session-cli show-certificate -p -c - parser_show_certificate = subparsers.add_parser('show-certificate', help='Retrieve a public certificate') - parser_show_certificate.set_defaults(command=cmd_show_certificate) - parser_show_certificate.add_argument('-p', '--provisioning-session', required=True, - help='Provisioning session id to show the certificate for') - parser_show_certificate.add_argument('-c', '--certificate-id', required=True, - help='The certificate id of the certificate to show') - parser_show_certificate.add_argument('-r', '--raw', required=False, action="store_true", - help='Use "raw" output mode to present the public certificate PEM data') - - # m1-session-cli set-certificate -p -c [] - parser_set_certificate = subparsers.add_parser('set-certificate', - help='Set the public certificate for a certificate created using --csr') - parser_set_certificate.set_defaults(command=cmd_set_certificate) - parser_set_certificate.add_argument('-p', '--provisioning-session', required=True, - help='Provisioning session id to set the certificate for') - parser_set_certificate.add_argument('-c', '--certificate-id', required=True, - help='The certificate id of the certificate to set') - parser_set_certificate.add_argument('certificate-PEM-file', nargs='?', - help='PEM file to load the public certificate from, if omitted will use stdin instead') - - # m1-session-cli check-certificate-renewal - #parser_checkrenewal = subparsers.add_parser('check-certificate-renewal', help='Renew all certificates if close to expiry') - #parser_checkrenewal.set_defaults(command=cmd_check_all_renewal) - - # m1-session-cli renew-certificate -p - # m1-session-cli renew-certificate [] - #parser_renewcert = subparsers.add_parser('renew-certificate', help='Force renewal of a specific certificate') - #parser_renewcert.set_defaults(command=cmd_renew_certs) - #parser_renewcert_filter = parser_renewcert.add_mutually_exclusive_group(required=True) - #parser_renewcert_filter.add_argument('-p', '--provisioning-session', help='Renew by provisioning session id') - #parser_renewcert_filter.add_argument('ingesturl', metavar='ingest-URL', nargs='?', help='The ingest URL prefix to use') - # The entry-point-path should go with ingest-URL, but argparser lacks the ability to do subgroups - #parser_renewcert.add_argument('entrypoint', metavar='entry-point-path', nargs='?', help='The media player entry point suffix.') - - # m1-session-cli set-consumption-reporting -p [-i ] [-s ] [-l] [-A] - parser_set_consumption = subparsers.add_parser('set-consumption-reporting', help='Activate/set consumption reporting') - parser_set_consumption.set_defaults(command=cmd_set_consumption) - parser_set_consumption.add_argument('-p', '--provisioning-session', required=True, - help='Provisioning session id to set the consumption reporting for') - parser_set_consumption.add_argument('-i', '--interval', type=int, help='The reporting interval to request in seconds') - parser_set_consumption.add_argument('-s', '--sample-percentage', dest='sample_percentage', type=float, - help='The sampling percentage to request') - parser_set_consumption.add_argument('-l', '--location-reporting', dest='location_reporting', action='store_true', - help='Include location reporting') - parser_set_consumption.add_argument('-A', '--access-reporting', dest='access_reporting', action='store_true', - help='Include access reporting') - - # m1-session-cli show-consumption-reporting -p - parser_show_consumption = subparsers.add_parser('show-consumption-reporting', help='Display the consumption reporting parameters') - parser_show_consumption.set_defaults(command=cmd_show_consumption) - parser_show_consumption.add_argument('-p', '--provisioning-session', required=True, - help='Provisioning session id to get the consumption reporting for') - - # m1-session-cli del-consumption-reporting -p - parser_del_consumption = subparsers.add_parser('del-consumption-reporting', help='Deactivate consumption reporting') - parser_del_consumption.set_defaults(command=cmd_del_consumption) - parser_del_consumption.add_argument('-p', '--provisioning-session', required=True, - help='Provisioning session id to remove the consumption reporting for') - - # m1-session-cli new-policy-template -p -e [-D ] [-S ] - # [--qos-reference ] [--max-auth-up ] [--max-auth-down ] - # [--default-packet-loss-up ] [--default-packet-loss-down ] - # [--chg-sponsor-id ] [--chg-sponsor-enabled|--chg-sponsor-disabled] - # [--gpsi ]... - parser_new_policy_template = subparsers.add_parser('new-policy-template', help='Add a new policy template') - parser_new_policy_template.set_defaults(command=cmd_new_policy_template) - parser_new_policy_template.add_argument('-p', '--provisioning-session', required=True, - help='Provisioning session id to create the policy template for') - parser_new_policy_template.add_argument('-e', '--external-policy-id', required=True, - help='The external identifier for this policy template') - parser_new_policy_template.add_argument('-D', '--dnn', help='The designated network name for the app session context') - parser_new_policy_template.add_argument('-S', '--s-nssai', metavar='SST[:SD]', - help='The Single NSSAI which will be used in the app session context') - parser_new_policy_template.add_argument('--qos-reference', help='The QoS reference for the QoS Specification') - parser_new_policy_template.add_argument('--max-auth-up', type=BitRate, - help='The maximum authorised uplink bitrate for the QoS Specification') - parser_new_policy_template.add_argument('--max-auth-down', type=BitRate, - help='The maximum authorised downlink bitrate for the QoS Specification') - parser_new_policy_template.add_argument('--default-packet-loss-up', type=int, - help='The number of packets that can be lost for an uplink in the QoS Specification') - parser_new_policy_template.add_argument('--default-packet-loss-down', type=int, - help='The number of packets that can be lost for an downlink in the QoS Specification') - parser_new_policy_template.add_argument('--chg-sponsor-id', metavar='sponsor-id', - help='The Sponsor id for the charging specification') - parser_new_policy_template.add_argument('--chg-sponsor-enabled', action='store_true', dest='chg_sponsor_status', default=None, - help='The Sponsor is enabled for charging') - parser_new_policy_template.add_argument('--chg-sponsor-disabled', action='store_false', dest='chg_sponsor_status', default=None, - help='The Sponsor is disabled for charging') - parser_new_policy_template.add_argument('--gpsi', action='append', help='The GPSI(s) to use for charging') - - # m1-session-cli update-policy-template -p -t [-D ] [-S ] - # [--qos-reference ] [--max-auth-up |--no-max-auth-up] - # [--max-auth-down |--no-max-auth-down] - # [--default-packet-loss-up |--no-default-packet-loss-up] - # [--default-packet-loss-down |--no-default-packet-loss-down] - # [--chg-sponsor-id [--chg-sponsor-enabled|--chg-sponsor-disabled] - # |--chg-sponsor-none] - # [--gpsi [--gpsi ]...|--no-gpsi] - parser_update_policy_template = subparsers.add_parser('update-policy-template', help='Modify a policy template') - parser_update_policy_template.set_defaults(command=cmd_update_policy_template) - parser_update_policy_template.add_argument('-p', '--provisioning-session', required=True, - help='Provisioning session id to update the policy template for') - parser_update_policy_template.add_argument('-t', '--policy-template-id', required=True, - help='The policy template id of the policy template to update') - parser_update_policy_template.add_argument('-D', '--dnn', help='The designated network name for the app session context') - parser_update_policy_template.add_argument('--no-dnn', action='store_true', - help='Remove the designated network name for the app session context') - parser_update_policy_template.add_argument('-S', '--s-nssai', metavar='SST[:SD]', - help='The Single NSSAI which will be used in the app session context') - parser_update_policy_template.add_argument('--no-s-nssai', action='store_true', - help='Remove the Single NSSAI used in the app session context') - parser_update_policy_template.add_argument('--qos-reference', help='The QoS reference for the QoS Specification') - parser_update_policy_template.add_argument('--no-qos-reference', action='store_true', - help='Remove the QoS reference from the QoS Specification') - parser_update_policy_template.add_argument('--max-auth-up', type=BitRate, - help='The maximum authorised uplink bitrate for the QoS Specification') - parser_update_policy_template.add_argument('--no-max-auth-up', action='store_true', - help='Remove maximum authorised uplink bitrate for the QoS Specification') - parser_update_policy_template.add_argument('--max-auth-down', type=BitRate, - help='The maximum authorised downlink bitrate for the QoS Specification') - parser_update_policy_template.add_argument('--no-max-auth-down', action='store_true', - help='Remove maximum authorised downlink bitrate for the QoS Specification') - parser_update_policy_template.add_argument('--default-packet-loss-up', type=int, - help='The number of packets that can be lost for an uplink in the QoS Specification') - parser_update_policy_template.add_argument('--no-default-packet-loss-up', action='store_true', - help='Remove number of packets that can be lost for an uplink in the QoS Specification') - parser_update_policy_template.add_argument('--default-packet-loss-down', type=int, - help='The number of packets that can be lost for an downlink in the QoS Specification') - parser_update_policy_template.add_argument('--no-default-packet-loss-down', action='store_true', - help='Remove number of packets that can be lost for an downlink in the QoS Specification') - parser_update_policy_template.add_argument('--chg-sponsor-id', metavar='sponsor-id', - help='The Sponsor id for the charging specification') - parser_update_policy_template.add_argument('--chg-sponsor-enabled', action='store_true', dest='chg_sponsor_status', default=None, - help='The Sponsor is enabled for charging') - parser_update_policy_template.add_argument('--chg-sponsor-disabled', action='store_false', dest='chg_sponsor_status', default=None, - help='The Sponsor is disabled for charging') - parser_update_policy_template.add_argument('--chg-sponsor-none', action='store_true', dest='chg_sponsor_none', - help='The Sponsor status and id should be removed for charging') - parser_update_policy_template.add_argument('--gpsi', action='append', - help='The set the GPSI(s) to use for charging (use --no-gpsi to remove gpsis)') - parser_update_policy_template.add_argument('--no-gpsi', action='store_true', help='Remove all GPSI values for charging') - - # m1-session-cli del-policy-template -p -t - parser_del_policy_template = subparsers.add_parser('del-policy-template', help='Delete a policy template') - parser_del_policy_template.set_defaults(command=cmd_del_policy_template) - parser_del_policy_template.add_argument('-p', '--provisioning-session', required=True, - help='Provisioning session id to remove the policy template for') - parser_del_policy_template.add_argument('-t', '--policy-template-id', required=True, - help='The policy template id of the policy template to delete') - - # m1-session-cli show-policy-template -p -t - parser_show_policy_template = subparsers.add_parser('show-policy-template', help='Display a policy template') - parser_show_policy_template.set_defaults(command=cmd_show_policy_template) - parser_show_policy_template.add_argument('-p', '--provisioning-session', required=True, - help='Provisioning session id to display the policy template for') - parser_show_policy_template.add_argument('-t', '--policy-template-id', required=True, - help='The policy template id of the policy template to display') - - args = parser.parse_args() - - return (args,cfg) - -_m1_session = None #: singleton variable for the M1Session object - -async def get_session(config: Configuration) -> M1Session: - '''Get the current M1Session object - - If the M1Session object does not exist, create it. - - :param Configuration config: The application configuration to use for connection information. - :return: the M1Session instance. - :rtype: M1Session - ''' - global _m1_session - if _m1_session is None: - data_store_dir = config.get('data_store') - if data_store_dir is not None: - data_store = await JSONFileDataStore(config.get('data_store')) - else: - data_store = None - _m1_session = await M1Session((config.get('m1_address', 'localhost'), config.get('m1_port',7777)), data_store, config.get('certificate_signing_class')) - return _m1_session - -async def main(): - ''' - Async application entry point - ''' - log_levels = { - 'debug': logging.DEBUG, - 'info': logging.INFO, - 'warn': logging.WARN, - 'error': logging.ERROR, - 'crit': logging.CRITICAL, - } - try: - (args, config) = await parse_args() - if args.debug: - log_lvl = logging.DEBUG - elif config.get('log_level') in log_levels: - log_lvl = log_levels[config.get('log_level')] - else: - print(f'Warning: Bad logging level "{config.get("log_level")}" in configuration.') - log_lvl = logging.INFO - logging.basicConfig(level=log_lvl) - log = logging.getLogger() - for lgr in log.manager.loggerDict.values(): - if isinstance(lgr, logging.Logger): - if not args.debug and lgr.name == 'httpx': - lgr.setLevel(logging.WARN) - else: - lgr.setLevel(log_lvl) - if hasattr(args, 'command'): - return await args.command(args, config) - else: - print(repr(parse_args())) - except M1Error as err: - print(f'Communication error: {err}') - return 2 - except Exception as err: - print(f'General failure: {err}') - if args.debug: - traceback.print_exc() - return 2 - return 0 - -def app(): - ''' - Sync application entry point - ''' - logging.basicConfig(level=logging.INFO) - return asyncio.run(main()) - -if __name__ == '__main__': - sys.exit(app()) diff --git a/tools/python3/m1_sync_config.py b/tools/python3/m1_sync_config.py deleted file mode 100755 index 1bea6ec..0000000 --- a/tools/python3/m1_sync_config.py +++ /dev/null @@ -1,651 +0,0 @@ -#!/usr/bin/python3 -#============================================================================== -# 5G-MAG Reference Tools: M1 Session CLI -#============================================================================== -# -# File: m1_sync_config.py -# License: 5G-MAG Public License (v1.0) -# Author: David Waring -# Copyright: (C) 2023 British Broadcasting Corporation -# -# For full license terms please see the LICENSE file distributed with this -# program. If this file is missing then the license can be retrieved from -# https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view -# -#============================================================================== -# -# AF configuration sync tool -# ========================== -# -# This is a command line tool which takes a configuration from -# /etc/rt-5gms/streams.json and applies it to the running AF. -# -# Communication with the AF should be preconfigured using m1-session configure. -# -''' -================================================== -5G-MAG Reference Tools: AF configuration sync tool -================================================== - -This command line app takes the configuration in /etc/rt-5gms/streams.json and -applies it to a running 5GMS AF using the M1 interface. It also stores M8 JSON -data in the HTTP document roots for any hostnames defined as domainNameAlias -entries in the streams.json. - -This shares some configuration with the m1-session tool and -`m1-session configure` should be used to configure the M1 communication address -and port. - -This tool is designed to be run immediately after the AF is started in order to configure the Provisioning Sessions in the AF. As such it will normally only be invoked by t - -The streams to configure are found in the /etc/rt-5gms/streams.json file. - -**af-sync.conf file** - -This file defines configuration values specifically for this AF configuration -sync tool, and can be found at `/etc/rt-5gms/af-sync.conf`. - -```ini -[af-sync] -m5_authority = af.example.com:1234 -docroot = /var/cache/rt-5gms/as/docroots -default_docroot = /usr/share/nginx/html -``` - -The *m5_authority* is a URL authority describing the location of the M5 -interface to be advertised via the M8 interface. - -The *docroot* is the file path of the document roots used by the 5GMS -Application Server. This is for publishing the M8 JSON file. The file will be -placed at `{docroot}/{domain_name}/m8.json`. - -The *default_docroot* is for the directory path to the root directory for the -fallback AS listening point. This will normally be `/usr/share/nginx/html`. - -**streams.json format** - -This file defines the streams to configure and is located at -`/etc/rt-5gms/streams.json`. - -```json -{ - "aspId": "MyASPId", - "appId": "BBCRD5GTestbed", - "streams": { - "stream-id-1": { - "name": "Stream name to appear in 5GMS Aware App", - "ingestURL": "http://media.example.com/some-media-asset/", - "distributionConfigurations": [ - { - "domainNameAlias": "5gms-as.example.com", - "entryPoint": { - "relativePath": "media.mpd", - "contentType": "application/dash+xml", - "profiles": ["urn:mpeg:dash:profile:isoff-live:2011"] - } - }, - { - "domainNameAlias": "5gms-as.example.com", - "entryPoint": { - "relativePath": "media.m3u8", - "contentType": "application/vnd.apple.mpegurl" - } - } - ], - "consumptionReporting": { - "reportingInterval": 30, - "samplePercentage": 66.666, - "locationReporting": true, - "accessReporting": true, - }, - "policies": { - "policy-external-ref-1": { - "applicationSessionContext": { - "sliceInfo": { - "sst": 1, - "sd": "000001" - }, - "dnn": "internet" - }, - "qoSSpecification": { - "qosReference": "qos-1", - "maxAuthBtrUl": "200 Kbps", - "maxAuthBtrDl": "20 Mbps", - "defPacketLossRateDl": 0, - "defPacketLossRateUl": 0 - }, - "chargingSpecification": { - "sponId": "sponsor-id-1", - "sponsorEnabled": true, - "gpsi": ["msimsi-1234567890"] - } - } - } - }, - "vod-root-1": { - "name": "VoD Service Name", - "ingestURL": "http://media.example.com/", - "distributionConfigurations": [ - {"domainNameAlias": "5gms-as.example.com"}, - {"domainNameAlias": "5gms-as.example.com", "certificateId": "placeholder1"} - ], - "consumptionReporting": { - "reportingInterval": 20, - "samplePercentage": 80, - }, - "policies": { - "policy-external-ref-1": {} - } - } - }, - "vodMedia": [ - { - "name": "VoD Stream 1 Name for UE", - "stream": "vod-root-1", - "entryPoints": [ - { - "relativePath": "stream1/media.mpd", - "contentType": "application/dash+xml", - "profiles": ["urn:mpeg:dash:profile:isoff-live:2011"] - }, - { - "relativePath": "stream1/media.m3u8", - "contentType": "application/vnd.apple.mpegurl" - } - ] - }, - { - "name": "VoD Stream 2 Name for UE", - "stream": "vod-root-1", - "entryPoints": [ - { - "relativePath": "stream2/media.mpd", - "contentType": "application/dash+xml", - "profiles": ["urn:mpeg:dash:profile:isoff-live:2011"] - }, - { - "relativePath": "stream2/media.m3u8", - "contentType": "application/vnd.apple.mpegurl" - } - ] - } - ] -} -``` - -The *aspId* is optional and is the ASP identifier for the provisioning sessions. - -The *appId* is the mandatory external application identifier for the -provisioning sessions. - -The *streams* map lists Provisioning Session configurations with a local -identfier as the map key. This identifier can be used in the *vodMedia* list to -identfiy the stream used for VoD media lists (media entry points described in -the M8 interface). If a stream contains *entryPoint* fields in the -*distributionConfigurations* then these will be advertised via M5 only and will -not appear in the M8 entry points. The *consumptionReporting* parameters, if -present, will configure consumption reporting for the Provisioning Session. See -3GPP TS 26.512 for a discription of what may appear in a -DistributionConfiguration or the ConsumptionReportingConfiguration. - -The *vodMedia* list is for describing media and their entry points that use a -common Provisioning Session. The Provisioning Session is identfied by the -*stream* field which is a reference to a key in the *streams* map. The entry in -the *streams* map should not have any *distributionConfigurations.entryPoint* -fields defined so that it acts as a top level ingest point for multiple media. -''' - -import aiofiles -import asyncio -import configparser -import importlib -import json -import logging -import os.path -import sys -from typing import List, Optional - -installed_packages_dir = '@python_packages_dir@' -if os.path.isdir(installed_packages_dir) and installed_packages_dir not in sys.path: - sys.path.append(installed_packages_dir) - -from rt_m1_client.session import M1Session -from rt_m1_client.exceptions import M1Error -from rt_m1_client.data_store import JSONFileDataStore -from rt_m1_client.types import ContentHostingConfiguration, DistributionConfiguration, IngestConfiguration, M1MediaEntryPoint, PathRewriteRule, ConsumptionReportingConfiguration, PolicyTemplate, M1QoSSpecification, ChargingSpecification, AppSessionContext, Snssai -from rt_m1_client.configuration import Configuration - -g_streams_config = os.path.join(os.path.sep, 'etc', 'rt-5gms', 'streams.json') -g_sync_config = os.path.join(os.path.sep, 'etc', 'rt-5gms', 'af-sync.conf') - -logging.basicConfig(level=logging.INFO) -g_log = logging.getLogger(__name__) - -def log_debug(*args, **kwargs): - global g_log - g_log.debug(*args, **kwargs) - -def log_info(*args, **kwargs): - global g_log - g_log.info(*args, **kwargs) - -def log_warn(*args, **kwargs): - global g_log - g_log.warn(*args, **kwargs) - -def log_error(*args, **kwargs): - global g_log - g_log.error(*args, **kwargs) - -async def path_rewrite_rule_equal(a: PathRewriteRule, b: PathRewriteRule) -> bool: - if a['requestPathPattern'] != b['requestPathPattern']: - return False - if a['mappedPath'] != b['mappedPath']: - return False - return True - -async def path_rewrite_rules_equal(a: List[PathRewriteRule], b: List[PathRewriteRule]) -> bool: - if len(a) != len(b): - return False - for prr_a in a: - for prr_b in b: - if await path_rewrite_rule_equal(prr_a, prr_b): - break - else: - return False - return True - -async def entry_points_equal(a: M1MediaEntryPoint, b: M1MediaEntryPoint) -> bool: - if a['relativePath'] != b['relativePath']: - return False - if a['contentType'] != b['contentType']: - return False - if 'profiles' not in a and 'profiles' not in b: - return True - if 'profiles' in a and 'profiles' in b and set(a['profiles']) == set(b['profiles']): - return True - return False - -async def distrib_config_equal(a: DistributionConfiguration, b: DistributionConfiguration) -> bool: - a_keys = set(a.keys()) - b_keys = set(b.keys()) - # Ignore fields generated by the AF - for gen_field in ['canonicalDomainName', 'baseURL']: - a_keys.discard(gen_field) - b_keys.discard(gen_field) - # Distribution configuration must have the same fields present - if a_keys != b_keys: - return False - - if 'entryPoint' in a and not await entry_points_equal(a['entryPoint'], b['entryPoint']): - return False - if 'contentPreparationTemplateId' in a and a['contentPreparationTemplateId'] != b['contentPreparationTemplateId']: - return False - if 'domainNameAlias' in a and a['domainNameAlias'] != b['domainNameAlias']: - return False - if 'pathRewriteRules' in a and not await path_rewrite_rules_equal(a['pathRewriteRules'], b['pathRewriteRules']): - return False - - # Ignore: cachingConfigurations, geoFencing, urlSignature, supplementaryDistributionNetworks - - return True - -async def distrib_configs_equal(a: List[DistributionConfiguration], b: List[DistributionConfiguration]) -> bool: - if len(a) != len(b): - return False - for dc_a in a: - for dc_b in b: - if await distrib_config_equal(dc_a, dc_b): - break - else: - return False - return True - -async def _flagsEqual(a: Optional[bool], b: Optional[bool]) -> bool: - if a is None and b is None: - return True - if a is None and b is not None and not b: - return True - if b is None and a is not None and not a: - return True - if a is not None and b is not None and a == b: - return True - return False - -async def consumption_reporting_equal(a: Optional[ConsumptionReportingConfiguration], b: Optional[ConsumptionReportingConfiguration]) -> bool: - if a is None and b is None: - return True - if a is None and b is not None: - return False - if b is None and a is not None: - return False - for i in ['locationReporting', 'accessReporting']: - if not await _flagsEqual(a.get(i, None), b.get(i, None)): - return False - for i in ['reportingInterval', 'samplePercentage']: - if i in a and i not in b: - return False - if i in b and i not in a: - return False - if i in a and a[i] != b[i]: - return False - return True - -async def snssai_match(sai1: Optional[Snssai], sai2: Optional[Snssai]) -> bool: - if sai1 is None and sai2 is None: - return True - if sai1 is None or sai2 is None: - return False - for i in ['sst', 'sd']: - if i not in sai1 and i in sai2: - return False - if i in sai1 and i not in sai2: - return False - if i in sai1 and sai1[i] != sai2[i]: - return False - return True - -async def m1_qos_specs_match(qos1: Optional[M1QoSSpecification], qos2: Optional[M1QoSSpecification]) -> bool: - if qos1 is None and qos2 is None: - return True - if qos1 is None or qos2 is None: - return False - for i in ['qosReference', 'maxAuthBtrUl', 'maxAuthBtrDl', 'defPacketLossRateDl', 'defPacketLossRateUl']: - if i not in qos1 and i in qos2: - return False - if i in qos1 and i not in qos2: - return False - if i in qos1 and qos1[i] != qos2[i]: - return False - return True - -async def policy_app_sessions_match(as1: Optional[AppSessionContext], as2: Optional[AppSessionContext]) -> bool: - if as1 is None and as2 is None: - return True - if as1 is None or as2 is None: - return False - if not await snssai_match(as1.get('sliceInfo', None), as2.get('sliceInfo', None)): - return False - if 'dnn' not in as1 and 'dnn' in as2: - return False - if 'dnn' in as1 and 'dnn' not in as2: - return False - if 'dnn' in as1 and as1['dnn'] != as2['dnn']: - return False - return True - -async def charging_specs_match(cs1: Optional[ChargingSpecification], cs2: Optional[ChargingSpecification]) -> bool: - if cs1 is None and cs2 is None: - return True - if cs1 is None or cs2 is None: - return False - for i in ['sponId', 'sponStatus']: - if i in cs1 and i not in cs2: - return False - if i not in cs1 and i in cs2: - return False - if i in cs1 and cs1[i] != cs2[i]: - return False - if 'gpsi' in cs1 and 'gpsi' not in cs2: - return False - if 'gpsi' not in cs1 and 'gpsi' in cs2: - return False - if 'gpsi' in cs1 and sorted(cs1['gpsi']) != sorted(cs2['gpsi']): - return False - return True - -async def policies_match(p1: Optional[PolicyTemplate], p2: Optional[PolicyTemplate]) -> bool: - if p1 is None and p2 is None: - return True - if p1 is None or p2 is None: - return False - if 'externalReference' in p1 and 'externalReference' not in p2: - return False - if 'externalReference' not in p1 and 'externalReference' in p2: - return False - if 'externalReference' in p1 and p1['externalReference'] != p2['externalReference']: - return False - if not await policy_app_sessions_match(p1.get('applicationSessionContext', None), p2.get('applicationSessionContext', None)): - return False - # ignore read-only fields policyTemplateId, state and stateReason - if not await m1_qos_specs_match(p1.get('qoSSpecification', None), p2.get('qoSSpecification', None)): - return False - if not await charging_specs_match(p1.get('chargingSpecification', None), p2.get('chargingSpecification', None)): - return False - return True - -async def sync_configuration(m1: M1Session, streams: dict) -> dict: - have = {} - to_check = streams['streams'] - del_ps_id = [] - stream_map = {} - for ps_id in await m1.provisioningSessionIds(): - chc = await m1.contentHostingConfigurationGet(ps_id) - if chc is None: - log_warn(f'Provisioning Session {ps_id} has no ContentHostingConfiguration, removing from the AF') - del_ps_id += [ps_id] - continue - for chk_id, chk_stream in to_check.items(): - if ( - chk_stream['name'] == chc['name'] and - chk_stream['ingestURL'] == chc['ingestConfiguration']['baseURL'] and - await distrib_configs_equal(chk_stream['distributionConfigurations'], chc['distributionConfigurations']) - ): - del to_check[chk_id] - have[chk_id] = chk_stream - stream_map[chk_id] = ps_id - break - else: - del_ps_id += [ps_id] - # have = already configured, to_check = need to configure, del_ps_id = configuration not found in the configured streams - for ps_id in del_ps_id: - await m1.provisioningSessionDestroy(ps_id) - for cfg_id, cfg in to_check.items(): - chc = { 'name': cfg['name'], - 'ingestConfiguration': { - 'baseURL': cfg['ingestURL'], - 'pull': True, - 'protocol': 'urn:3gpp:5gms:content-protocol:http-pull-ingest', - }, - 'distributionConfigurations': cfg['distributionConfigurations'], - } - crc = cfg.get('consumptionReporting', None) - policies = cfg.get('policies', None) - ps_id = await m1.createDownlinkPullProvisioningSession(streams.get('appId'), streams.get('aspId', None)) - if ps_id is None: - log_error("Failed to create Provisioning Session for %r", cfg) - else: - stream_map[cfg_id] = ps_id - certs = {} - for dc in chc['distributionConfigurations']: - if 'certificateId' in dc: - if dc['certificateId'] not in certs: - cert_id = await m1.createNewCertificate(ps_id, extra_domain_names=dc.get('domainNameAlias', None)) - if cert_id is None: - log_error("Failed to create certificate for Provisioning Session %s, skipping %r", ps_id, cfg) - chc = None - break - certs[dc['certificateId']] = cert_id - else: - cert_id = certs[dc['certificateId']] - dc['certificateId'] = cert_id - if chc is not None: - if not await m1.contentHostingConfigurationCreate(ps_id, chc): - log_error("Failed to create ContentHostingConfiguration for Provisioning Session %s, skipping %r", ps_id, cfg) - if crc is not None: - if not await m1.consumptionReportingConfigurationCreate(ps_id, crc): - log_error("Failed to activate ConsumptionReportingConfiguration for Provisioning Session %s") - if policies is not None: - if isinstance(policies,dict): - pol_list = policies.items() - elif isinstance(policies,list): - pol_list = [(p.get('externalReference', None), p) for p in policies] - else: - log_error(f'Configured policies for provisioning session "{cfg_id}" should be an object or array') - pol_list = None - if pol_list is not None: - for ext_id, pol in pol_list: - pt = dict() - if ext_id is not None: - pt.update({'externalReference': ext_id}) - pt.update(pol) - result = await m1.policyTemplateCreate(ps_id, pt) - if result is None: - log_error(f'Failed to create policy template {ext_id!r} in provisioning session {ps_id}') - # Check for other changes in the configured sessions - for cfg_id, cfg in have.items(): - # Check for ConsumptionReportingConfiguration changes in already configured sessions - ps_id = stream_map[cfg_id] - old_crc: Optional[ConsumptionReportingConfiguration] = await m1.consumptionReportingConfigurationGet(ps_id) - new_crc: Optional[ConsumptionReportingConfiguration] = cfg.get('consumptionReporting', None) - if not await consumption_reporting_equal(old_crc, new_crc): - if old_crc is None: - # No pre-existing CRC, add the new one - if not await m1.consumptionReportingConfigurationCreate(ps_id, new_crc): - log_error("Failed to activate ConsumptionReportingConfiguration for Provisioning Session %s", ps_id) - elif new_crc is None: - # There is a CRC, but shouldn't be one, remove it - if not await m1.consumptionReportingConfigurationDelete(ps_id): - log_error("Failed to remove ConsumptionReportingConfiguration for Provisioning Session %s", ps_id) - else: - # The CRC has changed, update it - if not await m1.consumptionReportingConfigurationUpdate(ps_id, new_crc): - log_error("Failed to update ConsumptionReportingConfiguration for Provisioning Session %s", ps_id) - # Check PolicyTemplates for changes in the already configured sessions - del_policy: List[ResourceId] = [] - have_policy: List[ResourceId] = [] - new_policy: List[PolicyTemplate] = [] - old_pol_ids: Optional[List[ResourceId]] = await m1.policyTemplateIds(ps_id) - policies = cfg.get('policies', None) - pol_list = None - if policies is not None: - if isinstance(policies,dict): - pol_list = policies.items() - elif isinstance(policies,list): - pol_list = [(p.get('externalReference', None), p) for p in policies] - else: - log_error(f'Configured policies for provisioning session "{cfg_id}" should be an object or array') - if old_pol_ids is None or len(old_pol_ids) == 0: - if policies is not None: - if pol_list is not None: - for pol_ext_id, pol in pol_list: - pt = dict() - if pol_ext_id is not None: - pt.update({'externalReference': pol_ext_id}) - pt.update(pol) - new_policy += [pt] - else: - new_pol_left = pol_list - for pol_id in old_pol_ids: - if new_pol_left is None or len(new_pol_left) == 0: - del_policy += [pol_id] - else: - old_pol: Optional[PolicyTemplate] = await m1.policyTemplateGet(ps_id, pol_id) - next_new_pol_list = [] - found = False - for pol_ext_id, pol in new_pol_left: - if not found and await policies_match(old_pol, pol): - have_policy += [pol_id] - found = True - else: - next_new_pol_list += [(pol_ext_id, pol)] - if not found: - del_policy += [pol_id] - new_pol_left = next_new_pol_list - for pol_ext_id, pol in new_pol_left: - pt = dict() - if pol_ext_id is not None: - pt.update({'externalReference': pol_ext_id}) - pt.update(pol) - new_policy += [pt] - # Now we have del_policy as a list of policy ids to delete, have_policy as a list of ids to keep and new_policy as a list - # of new policies to add. - for pol_id in del_policy: - await m1.policyTemplateDelete(ps_id, pol_id) - for pol in new_policy: - await m1.policyTemplateCreate(ps_id, pol) - return stream_map - -async def get_app_config() -> configparser.ConfigParser: - global g_sync_config - config = configparser.ConfigParser() - config.read_string(''' -[af-sync] -m5_authority = 127.0.0.23:7777 -docroot = /var/cache/rt-5gms/as/docroots -default_docroot = /usr/share/nginx/html -''', source='defaults') - async with aiofiles.open(g_sync_config, mode='r') as conffile: - config.read_string(await conffile.read(), source=g_sync_config) - return config - -async def get_streams_config() -> dict: - global g_streams_config - async with aiofiles.open(g_streams_config, mode='r') as infile: - streams = json.loads(await infile.read()) - return streams - -async def get_m1_session(cfg: Configuration) -> M1Session: - data_store = None - data_store_dir = cfg.get('data_store') - if data_store_dir is not None: - data_store = await JSONFileDataStore(data_store_dir) - session = await M1Session((cfg.get('m1_address', 'localhost'), cfg.get('m1_port',7777)), data_store, cfg.get('certificate_signing_class')) - return session - -async def dump_m8_files(m1: M1Session, stream_map: dict, vod_streams: List[dict], cfg: Configuration, config: configparser.ConfigParser): - # Assume M5 and M1 share an interface - m8_config = {'m5BaseUrl': f'http://{config.get("af-sync", "m5_authority")}/3gpp-m5/v2/', 'serviceList': []} - publish_dirs = {config.get("af-sync", "default_docroot")} - vod_stream_ids = set([v['stream'] for v in vod_streams]) - vod_ps_ids = set([v for k,v in stream_map.items() if k in vod_stream_ids]) - for ps_id in await m1.provisioningSessionIds(): - log_debug("Probing Provisioning Session %s", ps_id) - chc = await m1.contentHostingConfigurationGet(ps_id) - if chc is not None: - if ps_id not in vod_ps_ids: - m8_config['serviceList'] += [{'provisioningSessionId': ps_id, 'name': chc['name']}] - for dc in chc['distributionConfigurations']: - for hostfield in ['canonicalDomainName', 'domainNameAlias']: - if hostfield in dc: - publish_dirs.add(os.path.join(config.get("af-sync", "docroot"), dc[hostfield])) - else: - log_error(f"Provisioning Session {ps_id} was not initialised correctly: omitting") - for vod in vod_streams: - ps_id = stream_map[vod['stream']] - chc = await m1.contentHostingConfigurationGet(ps_id) - if chc is not None: - entryPoints = [] - for vep in vod['entryPoints']: - for dc in chc['distributionConfigurations']: - ep = {'locator': dc['baseURL'] + vep['relativePath'], 'contentType': vep['contentType']} - if 'profiles' in vep: - ep['profiles'] = vep['profiles'] - entryPoints += [ep] - m8_config['serviceList'] += [{'provisioningSessionId': ps_id, 'name': vod['name'], 'entryPoints': entryPoints}] - else: - log_error(f"Provisioning Session {ps_id} not initialised correctly: unable to include '{vod['name']}' in M8 data") - m8_json = json.dumps(m8_config) - log_debug("m8_json = %r", m8_json) - log_info("Publishing M8 info to: %s", ', '.join(publish_dirs)) - for pdir in publish_dirs: - pfile = os.path.join(pdir, 'm8.json') - async with aiofiles.open(pfile, mode='w') as outfile: - await outfile.write(m8_json) - -async def main(): - cfg = Configuration() - session = await get_m1_session(cfg) - streams = await get_streams_config() - config = await get_app_config() - - stream_map = await sync_configuration(session, streams) - - await dump_m8_files(session, stream_map, streams['vodMedia'], cfg, config) - - return 0 - -if __name__ == "__main__": - sys.exit(asyncio.run(main())) - -# vim:ts=8:sts=4:sw=4:expandtab: diff --git a/tools/python3/pylint.rc b/tools/python3/pylint.rc deleted file mode 100644 index a338feb..0000000 --- a/tools/python3/pylint.rc +++ /dev/null @@ -1,615 +0,0 @@ -[MAIN] - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Load and enable all available extensions. Use --list-extensions to see a list -# all available extensions. -#enable-all-extensions= - -# In error mode, messages with a category besides ERROR or FATAL are -# suppressed, and no reports are done by default. Error mode is compatible with -# disabling specific errors. -#errors-only= - -# Always return a 0 (non-error) status code, even if lint errors are found. -# This is primarily useful in continuous integration scripts. -#exit-zero= - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code. -extension-pkg-allow-list= - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code. (This is an alternative name to extension-pkg-allow-list -# for backward compatibility.) -extension-pkg-whitelist= - -# Return non-zero exit code if any of these messages/categories are detected, -# even if score is above --fail-under value. Syntax same as enable. Messages -# specified are enabled, while categories only check already-enabled messages. -fail-on= - -# Specify a score threshold to be exceeded before program exits with error. -fail-under=10 - -# Interpret the stdin as a python script, whose filename needs to be passed as -# the module_or_package argument. -#from-stdin= - -# Files or directories to be skipped. They should be base names, not paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the ignore-list. The -# regex matches against paths and can be in Posix or Windows format. -ignore-paths= - -# Files or directories matching the regex patterns are skipped. The regex -# matches against base names, not paths. The default value ignores Emacs file -# locks -ignore-patterns=^\.# - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis). It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the -# number of processors available to use, and will cap the count on Windows to -# avoid hangs. -jobs=1 - -# Control the amount of potential inferred values when inferring a single -# object. This can help the performance when dealing with large functions or -# complex, nested conditions. -limit-inference-results=100 - -# List of plugins (as comma separated values of python module names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# Minimum Python version to use for version dependent checks. Will default to -# the version used to run pylint. -py-version=3.11 - -# Discover python modules and packages in the file system subtree. -recursive=no - -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - -# In verbose mode, extra non-checker-related info will be displayed. -#verbose= - - -[REPORTS] - -# Python expression which should return a score less than or equal to 10. You -# have access to the variables 'fatal', 'error', 'warning', 'refactor', -# 'convention', and 'info' which contain the number of messages in each -# category, as well as 'statement' which is the total number of statements -# analyzed. This score is used by the global evaluation report (RP0004). -evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details. -msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio). You can also give a reporter class, e.g. -# mypackage.mymodule.MyReporterClass. -#output-format= - -# Tells whether to display a full report or only the messages. -reports=no - -# Activate the evaluation score. -score=yes - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, -# UNDEFINED. -confidence=HIGH, - CONTROL_FLOW, - INFERENCE, - INFERENCE_FAILURE, - UNDEFINED - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once). You can also use "--disable=all" to -# disable everything first and then re-enable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use "--disable=all --enable=classes -# --disable=W". -disable=raw-checker-failed, - bad-inline-option, - locally-disabled, - file-ignored, - suppressed-message, - useless-suppression, - deprecated-pragma, - use-symbolic-message-instead - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable=c-extension-no-member - - -[BASIC] - -# Naming style matching correct argument names. -argument-naming-style=snake_case - -# Regular expression matching correct argument names. Overrides argument- -# naming-style. If left empty, argument names will be checked with the set -# naming style. -#argument-rgx= - -# Naming style matching correct attribute names. -attr-naming-style=snake_case - -# Regular expression matching correct attribute names. Overrides attr-naming- -# style. If left empty, attribute names will be checked with the set naming -# style. -#attr-rgx= - -# Bad variable names which should always be refused, separated by a comma. -bad-names=foo, - bar, - baz, - toto, - tutu, - tata - -# Bad variable names regexes, separated by a comma. If names match any regex, -# they will always be refused -bad-names-rgxs= - -# Naming style matching correct class attribute names. -class-attribute-naming-style=any - -# Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style. If left empty, class attribute names will be checked -# with the set naming style. -#class-attribute-rgx= - -# Naming style matching correct class constant names. -class-const-naming-style=UPPER_CASE - -# Regular expression matching correct class constant names. Overrides class- -# const-naming-style. If left empty, class constant names will be checked with -# the set naming style. -#class-const-rgx= - -# Naming style matching correct class names. -class-naming-style=PascalCase - -# Regular expression matching correct class names. Overrides class-naming- -# style. If left empty, class names will be checked with the set naming style. -#class-rgx= - -# Naming style matching correct constant names. -const-naming-style=UPPER_CASE - -# Regular expression matching correct constant names. Overrides const-naming- -# style. If left empty, constant names will be checked with the set naming -# style. -#const-rgx= - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming style matching correct function names. -function-naming-style=snake_case - -# Regular expression matching correct function names. Overrides function- -# naming-style. If left empty, function names will be checked with the set -# naming style. -#function-rgx= - -# Good variable names which should always be accepted, separated by a comma. -good-names=i, - j, - k, - ex, - Run, - _ - -# Good variable names regexes, separated by a comma. If names match any regex, -# they will always be accepted -good-names-rgxs= - -# Include a hint for the correct naming format with invalid-name. -include-naming-hint=no - -# Naming style matching correct inline iteration names. -inlinevar-naming-style=any - -# Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style. If left empty, inline iteration names will be checked -# with the set naming style. -#inlinevar-rgx= - -# Naming style matching correct method names. -#method-naming-style=camelCase - -# Regular expression matching correct method names. Overrides method-naming- -# style. If left empty, method names will be checked with the set naming style. -method-rgx=^(?:__[a-zA-Z][a-zA-Z0-9_]*|_?[a-z][a-zA-Z0-9]*)$ - -# Naming style matching correct module names. -module-naming-style=snake_case - -# Regular expression matching correct module names. Overrides module-naming- -# style. If left empty, module names will be checked with the set naming style. -#module-rgx= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -# These decorators are taken in consideration only for invalid-name. -property-classes=abc.abstractproperty - -# Regular expression matching correct type variable names. If left empty, type -# variable names will be checked with the set naming style. -#typevar-rgx= - -# Naming style matching correct variable names. -variable-naming-style=snake_case - -# Regular expression matching correct variable names. Overrides variable- -# naming-style. If left empty, variable names will be checked with the set -# naming style. -#variable-rgx= - - -[CLASSES] - -# Warn about protected attribute access inside special methods -check-protected-access-in-special-methods=no - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp, - __post_init__ - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=cls - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - -# Complete name of functions that never returns. When checking for -# inconsistent-return-statements if a never returning function is called then -# it will be considered as an explicit return statement and no message will be -# printed. -never-returning-functions=sys.exit,argparse.parse_error - - -[DESIGN] - -# List of regular expressions of class ancestor names to ignore when counting -# public methods (see R0903) -exclude-too-few-public-methods= - -# List of qualified class names to ignore when counting class parents (see -# R0901) -ignored-parents= - -# Maximum number of arguments for function / method. -max-args=5 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Maximum number of boolean expressions in an if statement (see R0916). -max-bool-expr=5 - -# Maximum number of branch for function / method body. -max-branches=12 - -# Maximum number of locals for function / method body. -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body. -max-returns=6 - -# Maximum number of statements in function / method body. -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when caught. -overgeneral-exceptions=BaseException, - Exception - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module. -max-module-lines=1000 - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[IMPORTS] - -# List of modules that can be imported at any level, not just the top level -# one. -allow-any-import-level= - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Deprecated modules which should not be used, separated by a comma. -deprecated-modules= - -# Output a graph (.gv or any supported image format) of external dependencies -# to the given file (report RP0402 must not be disabled). -ext-import-graph= - -# Output a graph (.gv or any supported image format) of all (i.e. internal and -# external) dependencies to the given file (report RP0402 must not be -# disabled). -import-graph= - -# Output a graph (.gv or any supported image format) of internal dependencies -# to the given file (report RP0402 must not be disabled). -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - -# Couples of modules and preferred modules, separated by a comma. -preferred-modules= - - -[LOGGING] - -# The type of string formatting that logging methods do. `old` means using % -# formatting, `new` is for `{}` formatting. -logging-format-style=old - -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules=logging - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - -# Regular expression of note tags to take in consideration. -notes-rgx= - - -[SIMILARITIES] - -# Comments are removed from the similarity computation -ignore-comments=yes - -# Docstrings are removed from the similarity computation -ignore-docstrings=yes - -# Imports are removed from the similarity computation -ignore-imports=yes - -# Signatures are removed from the similarity computation -ignore-signatures=yes - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions=4 - -# Spelling dictionary name. Available dictionaries: en_AG (hunspell), en_AU -# (hunspell), en_BS (hunspell), en_BW (hunspell), en_BZ (hunspell), en_CA -# (hunspell), en_DK (hunspell), en_GB (hunspell), en_GH (hunspell), en_HK -# (hunspell), en_IE (hunspell), en_IN (hunspell), en_JM (hunspell), en_MW -# (hunspell), en_NA (hunspell), en_NG (hunspell), en_NZ (hunspell), en_PH -# (hunspell), en_SG (hunspell), en_TT (hunspell), en_US (hunspell), en_ZA -# (hunspell), en_ZM (hunspell), en_ZW (hunspell). -spelling-dict= - -# List of comma separated words that should be considered directives if they -# appear at the beginning of a comment and should not be checked. -spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains the private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to the private dictionary (see the -# --spelling-private-dict-file option) instead of raising a message. -spelling-store-unknown-words=no - - -[STRING] - -# This flag controls whether inconsistent-quotes generates a warning when the -# character used as a quote delimiter is used inconsistently within a module. -check-quote-consistency=no - -# This flag controls whether the implicit-str-concat should generate a warning -# on implicit string concatenation in sequences defined over several lines. -check-str-concat-over-line-jumps=no - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether to warn about missing members when the owner of the attribute -# is inferred to be None. -ignore-none=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of symbolic message names to ignore for Mixin members. -ignored-checks-for-mixins=no-member, - not-async-context-manager, - not-context-manager, - attribute-defined-outside-init - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - -# Regex pattern to define which classes are considered mixins. -mixin-class-rgx=.*[Mm]ixin - -# List of decorators that change the signature of a decorated function. -signature-mutators= - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid defining new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of names allowed to shadow builtins -allowed-redefined-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_, - _cb - -# A regular expression matching the name of dummy variables (i.e. expected to -# not be used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore. -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io