Add VES O1 pnfRegistration (stndDefined) capabilities.

Issue-ID: SIM-102
Change-Id: I94bb9c99d9c901ac2429269648f0c4b634bd2dd9
Signed-off-by: Alex Stancu <alexandru.stancu@highstreet-technologies.com>
diff --git a/docs/overview.rst b/docs/overview.rst
index 5ff4b6e..1db9f92 100644
--- a/docs/overview.rst
+++ b/docs/overview.rst
@@ -137,7 +137,7 @@
 
 - **network-functions** - represents the simulation data, which will be best described below
 - **sdn-controller** - this container groups the configuration related to the ODL based SDN controller that the simulated devices can connect to:
-  
+
     - **controller-protocol** - SDN controller protocol (http/https)
     - **controller-ip** - the IP address of the ODL based SDN controller where the simulated devices can be mounted. Both IPv4 and IPv6 are supported
     - **controller-port** - the port of the ODL based SDN controller
@@ -157,7 +157,7 @@
         + *cert-only* - certificate only authentication in this case the certificate to be used for the communication must be configured
         + *basic-auth* - classic username/password authentication in this case both the username and password need to be configured
         + *cert-basic-auth* - authentication that uses both username/password and a certificate all three values need to be configured in this case
-        + 
+        +
     - **ves-endpoint-username** - the username to be used when authenticating to the VES endpoint
     - **ves-endpoint-password** - the password to be used when authenticating to the VES endpoint
     - **ves-endpoint-certificate** - the certificate to be used when authenticating to the VES endpoint
@@ -180,7 +180,7 @@
 - **started-devices** - represents the number of simulated devices. The default value is 0, meaning that when the NTS is started, there are no simulated devices. When this value is increased to **n**, the NTS Manager starts docker containers in order to reach **n** simulated devices. If the value is decreased to **k**, the NTS Manager will remove docker containers in a LIFO manner, until the number of simulated devices reaches **k**
 - **mounted-devices** - represents the number of devices to be mounted to an ODL based SDN Controller. The same phylosophy as in the case of the previous leaf applies. If this number is increased, the number of ODL mountpoints increases. Else, the simulated devices are being unmounted from ODL. The number of mounted devices cannot exceed the number of started devices. The details about the ODL controller where to mount/unmount are given by the **sdn-controller** container
 - **mount-point-addressing-method** - addressing method of the mount point. Possible values are:
-  
+
     + *docker-mapping* - [default value] future started simulated devices will be mapped on the Docker container
     + *host-mapping* - future started simulated devices will me mapped on the host's IP address and port based on *base-port*
 - **docker-instance-name** - the prefix for future simulated devices (to this name a dash and an increasing number is added)
@@ -202,12 +202,12 @@
 There are 2 defined **notifications**:
 
 - **instance-changed** notification: is called by the manager whenever a change is done to any of the network functions. This contains data about the change:
-  
+
     - **change-status**: is a string which has the following structure: operation STATUS - info. operation can be *start*, *stop*, *mount*, *unmount*, *config* and *reconfig*; STATUS can be SUCCESS or FAILED; info can be present or not, depending on what further information is available about the change
     - **function-type**: the function-type for the instance
     - **name**: name of the instance that is changed
     - **networking**: when starting and configuring an instance, this container has all the necessary networking data, such as IP and ports
-  
+
 - **operation-status-changed** notification is called by the manager at the end of an operation:
 
     - **status** returns the status of the operation: SUCCESS/FAILED. This status can also be statically read from the operational datastore under *nts-manager:simulation/last-operation-status*
@@ -304,6 +304,7 @@
     - **ves-file-ready** - enables VES file ready, and stats a FTP and a SFTP server on the network function
     - **ves-heartbeat** - enabled VES heartbeat feature
     - **ves-pnf-registration*** - enables VES PNF registration
+    - **ves-o1-pnf-registration*** - enables O1 VES PNF registration (stndDefined)
     - **manual-notification-generation** - enables the manual notification generation feature
     - **netconf-call-home*** - enables NETCONF's Call Home feature
     - **web-cut-through** - enables web cut through, adding the info to the ietf-system module
@@ -311,11 +312,11 @@
 - **invoke-notification** - this RPC is used for forcing a simulated device to send a NETCONF notification, as defined by the user:
 
     - The **input** needed by the RPC:
-  
+
         - **notification-format** - can be either *json* or *xml*
         - **notification-object** - this is a string containing the notification object that we are trying to send from the simulated device, in JSON format. **Please note that the user has the responsibility to ensure that the JSON object is valid, according to the definition of the notification in the YANG module.** There is no possibility to see what was wrong when trying to send an incorrect notification. The RPC will only respond with an "ERROR" status in that case, without further information. E.g. of a JSON containing a notification object of type ***otdr-scan-result*** defined in the ***org-openroadm-device*** YANG module: ***{"org-openroadm-device:otdr-scan-result":{"status":"Successful","status-message":"Scan result was successful","result-file":"/home/result-file.txt"}}***. **Please note that the notification object contains also the name of the YANG model defning it, as a namespace, as seen in the example.**
     - The **output** returned by the RPC:
-  
+
         - **status** - if the notification was send successfully by the simulated device, the RPC will return a **SUCCESS** value. Else, the RPC will return a **ERROR** value.
 
 - **invoke-ves-pm-file-ready** - as name impiles, it invokes a file ready VES request, with a specified *file-location*
@@ -367,7 +368,7 @@
         ],
 
         "debug-max-string-size" : 50,       //max size of string. if not set, default is 255
-        
+
         "default-list-instances": 1,    //default number of instances a list or a leaflist should be populated with
         "custom-list-instances" : [     //custom number of list instances. instance is schema name, and should reflect a list or a leaflist
             {"/ietf-interfaces:interfaces/interface": 2}, //2 instances of this. if 0, list will be excluded from populating
@@ -385,7 +386,7 @@
             "path/to/data.json",
             "path/to/data.xml"
         ],
-        
+
         "pre-generated-running-data": [ //path with files containing NETCONF data, either JSON or XML
             "path/to/data.json",
             "path/to/data.xml"
@@ -475,7 +476,7 @@
 - --help - shows the help (also described here)
 - --version - describes ntsng version and build time
 - **main modes**:
-  
+
     - --container-init - is automatically used by Docker when building the images to install modules and enable features. Described in the next chapter. **Do not run manually**
     - --supervisor - runs in supervisor mode (configuration is done via config.json)
     - --manager - runs in manager mode
@@ -489,7 +490,7 @@
     - --verbose - set the verbose level. can range from 0 (errors-only) to 2 (verbose), default is 1 (info)
     - --workspace - set the current working workspace. the workspace **MUST** be writeable and should contain *config/config.json* file, otherwise a blank json file will be created
 - tools:
-  
+
     - --ls - list all modules in the datastore with their attributes
     - --schema - list the schema of an xpath given as parameter
 
@@ -620,7 +621,7 @@
 
 - **NTS_HOST_IP**: an IP address from the host, which should be used by systems outside the local machine to address the simulators;
 - **NTS_HOST_BASE_PORT**: the port from where the allocation for the simulated network functions should start, if not specified otherwise sepparately (see below); any port not defined will automatically be assigned to *BASE_PORT*; **NOTE** that in order for a port to be eligible, it must be greater than or equal to **1000**:
-  
+
     - **NTS_HOST_NETCONF_SSH_BASE_PORT**
     - **NTS_HOST_NETCONF_TLS_BASE_PORT**
     - **NTS_HOST_TRANSFER_FTP_BASE_PORT**
diff --git a/docs/release-notes.rst b/docs/release-notes.rst
index dd526af..7b25ca8 100644
--- a/docs/release-notes.rst
+++ b/docs/release-notes.rst
@@ -60,6 +60,11 @@
 Release Data
 ------------
 
+version 1.5.2
+
+- [feature-add] new VES O1 PNF Registration (stndDefined) message available. It can be enabled by using ves-o1-pnf-registration feature name.
+
+
 version 1.5.0
 
 - [feature-add] new topology-service image available, with latest YANG specifications from OAM project. It exposes a RESTCONF endpoint with specific topology information as configured.
diff --git a/ntsimulator/deploy/base/build_ntsim-ng.sh b/ntsimulator/deploy/base/build_ntsim-ng.sh
index e93ff5b..096dc3f 100755
--- a/ntsimulator/deploy/base/build_ntsim-ng.sh
+++ b/ntsimulator/deploy/base/build_ntsim-ng.sh
@@ -62,6 +62,7 @@
     "features/manual_notification/manual_notification.c"
     "features/netconf_call_home/netconf_call_home.c"
     "features/web_cut_through/web_cut_through.c"
+    "features/ves_o1_pnf_registration/ves_o1_pnf_registration.c"
     "main.c"
 )
 
diff --git a/ntsimulator/deploy/base/yang/nts-common.yang b/ntsimulator/deploy/base/yang/nts-common.yang
index 74a8a75..98c92ea 100644
--- a/ntsimulator/deploy/base/yang/nts-common.yang
+++ b/ntsimulator/deploy/base/yang/nts-common.yang
@@ -18,6 +18,12 @@
   description
     "This module contains common yang definitions YANG definitions for the Network Topology Simulator.";
 
+  revision 2022-09-29 {
+    description
+      "Added O1 pnfRegistration standard defined capabilities.";
+    reference
+      "O-RAN-SC SIM project";
+  }
   revision 2021-10-29 {
     description
       "Added separate SDN Controller IP address for NETCONF Call Home.";
@@ -173,6 +179,11 @@
         description
           "Controls the Web Cut Through feature.";
       }
+      bit ves-o1-pnf-registration {
+        position 6;
+        description
+          "Controls the ves-o1-pnf-registration (stndDefined) feature.";
+      }
     }
     description
       "Describes the features that can be enabled/disabled in the Network Function.";
@@ -307,6 +318,11 @@
         description
           "For enabling the PNF Registration messages. If set to 'true', each simulated device, when booting up, will send a PNF Registration message to the VES Collector.";
       }
+      leaf o1-pnf-registration {
+        type boolean;
+        description
+          "For enabling the O1 PNF Registration (stndDefined) messages. If set to 'true', each simulated device, when booting up, will send a O1 PNF Registration (stndDefined) message to the VES Collector.";
+      }
       leaf heartbeat-period {
         type uint16;
         description
diff --git a/ntsimulator/deploy/blank/container-tag.yaml b/ntsimulator/deploy/blank/container-tag.yaml
index b6527e1..f4af2ff 100644
--- a/ntsimulator/deploy/blank/container-tag.yaml
+++ b/ntsimulator/deploy/blank/container-tag.yaml
@@ -1,2 +1,2 @@
 ---
-tag: 1.5.0
+tag: 1.5.2
diff --git a/ntsimulator/deploy/nts-manager/container-tag.yaml b/ntsimulator/deploy/nts-manager/container-tag.yaml
index b6527e1..f4af2ff 100644
--- a/ntsimulator/deploy/nts-manager/container-tag.yaml
+++ b/ntsimulator/deploy/nts-manager/container-tag.yaml
@@ -1,2 +1,2 @@
 ---
-tag: 1.5.0
+tag: 1.5.2
diff --git a/ntsimulator/deploy/o-ran-du/container-tag.yaml b/ntsimulator/deploy/o-ran-du/container-tag.yaml
index b6527e1..f4af2ff 100644
--- a/ntsimulator/deploy/o-ran-du/container-tag.yaml
+++ b/ntsimulator/deploy/o-ran-du/container-tag.yaml
@@ -1,2 +1,2 @@
 ---
-tag: 1.5.0
+tag: 1.5.2
diff --git a/ntsimulator/deploy/o-ran-ru-fh/container-tag.yaml b/ntsimulator/deploy/o-ran-ru-fh/container-tag.yaml
index b6527e1..f4af2ff 100644
--- a/ntsimulator/deploy/o-ran-ru-fh/container-tag.yaml
+++ b/ntsimulator/deploy/o-ran-ru-fh/container-tag.yaml
@@ -1,2 +1,2 @@
 ---
-tag: 1.5.0
+tag: 1.5.2
diff --git a/ntsimulator/deploy/o-ran/container-tag.yaml b/ntsimulator/deploy/o-ran/container-tag.yaml
index b6527e1..f4af2ff 100644
--- a/ntsimulator/deploy/o-ran/container-tag.yaml
+++ b/ntsimulator/deploy/o-ran/container-tag.yaml
@@ -1,2 +1,2 @@
 ---
-tag: 1.5.0
+tag: 1.5.2
diff --git a/ntsimulator/deploy/smo-nts-ng-topology-server/container-tag.yaml b/ntsimulator/deploy/smo-nts-ng-topology-server/container-tag.yaml
index b6527e1..f4af2ff 100644
--- a/ntsimulator/deploy/smo-nts-ng-topology-server/container-tag.yaml
+++ b/ntsimulator/deploy/smo-nts-ng-topology-server/container-tag.yaml
@@ -1,2 +1,2 @@
 ---
-tag: 1.5.0
+tag: 1.5.2
diff --git a/ntsimulator/deploy/x-ran/container-tag.yaml b/ntsimulator/deploy/x-ran/container-tag.yaml
index b6527e1..f4af2ff 100644
--- a/ntsimulator/deploy/x-ran/container-tag.yaml
+++ b/ntsimulator/deploy/x-ran/container-tag.yaml
@@ -1,2 +1,2 @@
 ---
-tag: 1.5.0
+tag: 1.5.2
diff --git a/ntsimulator/ntsim-ng/core/app/network_function.c b/ntsimulator/ntsim-ng/core/app/network_function.c
index 63f041d..e34d85c 100644
--- a/ntsimulator/ntsim-ng/core/app/network_function.c
+++ b/ntsimulator/ntsim-ng/core/app/network_function.c
@@ -44,6 +44,7 @@
 #include "features/manual_notification/manual_notification.h"
 #include "features/netconf_call_home/netconf_call_home.h"
 #include "features/web_cut_through/web_cut_through.h"
+#include "features/ves_o1_pnf_registration/ves_o1_pnf_registration.h"
 
 #include "app_common.h"
 #include "nf_oran_du.h"
@@ -296,6 +297,16 @@
                 }
             }
 
+            if(strstr(nf_function_control_string, "ves-o1-pnf-registration") != 0) {
+                if(nf_function_control_string[0] == '1') {
+                    // check if O1 PNF registration is enabled and send PNF registration message if so
+                    rc = ves_o1_pnf_registration_feature_start(session_running);
+                    if(rc != 0) {
+                        log_error("ves_o1_pnf_registration_feature_start() failed\n");
+                    }
+                }
+            }
+
             if(strstr(nf_function_control_string, "ves-heartbeat") != 0) {
                 if(nf_function_control_string[0] == '1') {
                     // start feature for handling the heartbeat VES message
@@ -698,7 +709,11 @@
     if(web_cut_through_feature_get_status()) {
         strcat(started_features, "web-cut-through ");
     }
-    
+
+    if(ves_o1_pnf_registration_feature_get_status()) {
+        strcat(started_features, "ves-o1-pnf-registration ");
+    }
+
     if(strlen(started_features)) {
         started_features[strlen(started_features) - 1] = 0;
     }
diff --git a/ntsimulator/ntsim-ng/core/xpath.h b/ntsimulator/ntsim-ng/core/xpath.h
index a3cf2b8..984ed9b 100644
--- a/ntsimulator/ntsim-ng/core/xpath.h
+++ b/ntsimulator/ntsim-ng/core/xpath.h
@@ -39,6 +39,7 @@
 #define NTS_NF_VES_FAULTS_ENABLED_SCHEMA_XPATH                  "/nts-network-function:simulation/network-function/ves/faults-enabled"
 #define NTS_NF_VES_HEARTBEAT_SCHEMA_XPATH                       "/nts-network-function:simulation/network-function/ves/heartbeat-period"
 #define NTS_NF_VES_PNF_REGISTRATION_SCHEMA_XPATH                "/nts-network-function:simulation/network-function/ves/pnf-registration"
+#define NTS_NF_VES_O1_PNF_REGISTRATION_SCHEMA_XPATH             "/nts-network-function:simulation/network-function/ves/o1-pnf-registration"
 #define NTS_NF_NETWORK_EMULATION_SCHEMA_XPATH                   "/nts-network-function:simulation/network-emulation"
 #define NTS_NF_HARDWARE_EMULATION_SCHEMA_XPATH                  "/nts-network-function:simulation/hardware-emulation"
 #define NTS_NF_HE_NETCONF_DELAY_SCHEMA_XPATH                    "/nts-network-function:simulation/hardware-emulation/netconf-delay"
diff --git a/ntsimulator/ntsim-ng/features/ves_o1_pnf_registration/ves_o1_pnf_registration.c b/ntsimulator/ntsim-ng/features/ves_o1_pnf_registration/ves_o1_pnf_registration.c
new file mode 100644
index 0000000..077a3c4
--- /dev/null
+++ b/ntsimulator/ntsim-ng/features/ves_o1_pnf_registration/ves_o1_pnf_registration.c
@@ -0,0 +1,529 @@
+/*************************************************************************
+*
+* Copyright 2022 highstreet technologies GmbH and others
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+***************************************************************************/
+
+#define _GNU_SOURCE
+
+#include "ves_o1_pnf_registration.h"
+#include "utils/log_utils.h"
+#include "utils/sys_utils.h"
+#include "utils/rand_utils.h"
+#include "utils/http_client.h"
+#include "utils/nts_utils.h"
+#include <stdio.h>
+#include <assert.h>
+#include <pthread.h>
+
+#include "core/session.h"
+#include "core/framework.h"
+#include "core/xpath.h"
+
+static int ves_o1_pnf_sequence_number = 0;
+static pthread_t ves_o1_pnf_registration_thread;
+static void* ves_o1_pnf_registration_thread_routine(void *arg);
+static int ves_o1_pnf_registration_send(sr_session_ctx_t *current_session, const char *nf_ip_v4_address, const char *nf_ip_v6_address, int nf_port, nts_mount_point_addressing_method_t mp, bool is_tls);
+static cJSON* ves_create_o1_pnf_registration_fields(const char *nf_ip_v4_address, const char *nf_ip_v6_address, int nf_port, bool is_tls);
+
+static int ves_o1_pnf_registration_status = 0;
+
+int ves_o1_pnf_registration_feature_get_status(void) {
+    return ves_o1_pnf_registration_status;
+}
+
+int ves_o1_pnf_registration_feature_start(sr_session_ctx_t *current_session) {
+    assert(current_session);
+
+    ves_o1_pnf_sequence_number = 0;
+
+    sr_val_t *value = 0;
+    int rc = NTS_ERR_OK;
+    bool o1_pnf_registration_enabled = false;
+    if(strlen(framework_environment.nts.nf_standalone_start_features)) {
+        o1_pnf_registration_enabled = true;
+    }
+    else {
+        rc = sr_get_item(current_session, NTS_NF_VES_O1_PNF_REGISTRATION_SCHEMA_XPATH, 0, &value);
+        if(rc == SR_ERR_OK) {
+            o1_pnf_registration_enabled = value->data.bool_val;
+            sr_free_val(value);
+        }
+        else if(rc != SR_ERR_NOT_FOUND) {
+            log_error("sr_get_item failed\n");
+            return NTS_ERR_FAILED;
+        }
+    }
+
+    if(o1_pnf_registration_enabled == false) {
+        log_add_verbose(2, "O1 PNF registration (stndDefined) is disabled\n");
+        return NTS_ERR_OK;
+    }
+
+    if(pthread_create(&ves_o1_pnf_registration_thread, 0, ves_o1_pnf_registration_thread_routine, current_session)) {
+        log_error("could not create thread for o1 pnf registration\n");
+        return NTS_ERR_FAILED;
+    }
+
+    return NTS_ERR_OK;
+}
+
+static void* ves_o1_pnf_registration_thread_routine(void *arg) {
+    sr_session_ctx_t *current_session = arg;
+
+    int ssh_base_port = 0;
+    int tls_base_port = 0;
+    char nf_ip_v4_address[128];
+    char nf_ip_v6_address[128];
+
+    nf_ip_v4_address[0] = 0;
+    nf_ip_v6_address[0] = 0;
+
+    nts_mount_point_addressing_method_t mp = nts_mount_point_addressing_method_get(current_session);
+    if(mp == UNKNOWN_MAPPING) {
+        log_error("mount-point-addressing-method failed\n");
+        return (void*)NTS_ERR_FAILED;
+    }
+    else if(mp == DOCKER_MAPPING) {
+        if (framework_environment.settings.ip_v4 != 0) {
+            strcpy(nf_ip_v4_address, framework_environment.settings.ip_v4);
+        }
+        if (framework_environment.settings.ip_v6 && framework_environment.settings.ip_v6_enabled) {
+            strcpy(nf_ip_v6_address, framework_environment.settings.ip_v6);
+        }
+
+        ssh_base_port = STANDARD_NETCONF_PORT;
+        tls_base_port = ssh_base_port + framework_environment.settings.ssh_connections;
+    }
+    else {
+        if(framework_environment.settings.ip_v6_enabled) {
+            strcpy(nf_ip_v6_address, framework_environment.host.ip);
+        }
+        else {
+            strcpy(nf_ip_v4_address, framework_environment.host.ip);
+        }
+
+        ssh_base_port = framework_environment.host.ssh_base_port;
+        tls_base_port = framework_environment.host.tls_base_port;
+    }
+
+    uint32_t total_regs = 0;
+    struct regs_s {
+        bool sent;
+        uint16_t port;
+        bool is_tls;
+    } *regs;
+
+    regs = (struct regs_s *)malloc(sizeof(struct regs_s) * (1 + framework_environment.settings.ssh_connections + framework_environment.settings.tls_connections));
+    if(regs == 0) {
+        log_error("malloc failed\n");
+        return (void*)NTS_ERR_FAILED;
+    }
+
+
+    if((framework_environment.settings.ssh_connections + framework_environment.settings.tls_connections) > 1) {
+        for(int port = ssh_base_port; port < ssh_base_port + framework_environment.settings.ssh_connections; port++) {
+            regs[total_regs].sent = false;
+            regs[total_regs].port = port;
+            regs[total_regs].is_tls = false;
+            total_regs++;
+        }
+
+        for(int port = tls_base_port; port < tls_base_port + framework_environment.settings.tls_connections; port++) {
+            regs[total_regs].sent = false;
+            regs[total_regs].port = port;
+            regs[total_regs].is_tls = true;
+            total_regs++;
+        }
+    }
+    else {
+        bool tls;
+        if(framework_environment.settings.tls_connections == 0) {
+            tls = false;
+        }
+        else {
+            tls = true;
+        }
+
+        regs[total_regs].sent = false;
+        regs[total_regs].port = 0;
+        regs[total_regs].is_tls = tls;
+        total_regs++;
+    }
+
+    uint32_t remaining = total_regs;
+    while(remaining) {
+        for(int i = 0; i < total_regs; i++) {
+            if(regs[i].sent == false) {
+                uint16_t port = regs[i].port;
+                bool is_tls = regs[i].is_tls;
+                int rc = ves_o1_pnf_registration_send(current_session, nf_ip_v4_address, nf_ip_v6_address, port, mp, is_tls);
+                if(rc == NTS_ERR_OK) {
+                    remaining--;
+                    regs[i].sent = true;
+                }
+                else {
+                    log_error("O1 pnfRegistration (stndDefined) failed for ipv4=%s ipv6=%s port=%d is_tls=%d\n", nf_ip_v4_address, nf_ip_v6_address, port, is_tls);
+                }
+            }
+        }
+        if(remaining) {
+            log_error("O1 pnfRegistration (stndDefined) could not register all ports; retrying in 5 seconds...\n");
+            sleep(5);
+        }
+    }
+    free(regs);
+    log_add_verbose(2, "O1 PNF registration (stndDefined) finished\n");
+    ves_o1_pnf_registration_status = 1;
+
+    return NTS_ERR_OK;
+}
+
+static int ves_o1_pnf_registration_send(sr_session_ctx_t *current_session, const char *nf_ip_v4_address, const char *nf_ip_v6_address, int nf_port, nts_mount_point_addressing_method_t mp, bool is_tls) {
+    assert(current_session);
+
+    cJSON *post_data_json = cJSON_CreateObject();
+    if(post_data_json == 0) {
+        log_error("could not create cJSON object\n");
+        return NTS_ERR_FAILED;
+    }
+
+    cJSON *event = cJSON_CreateObject();
+    if(event == 0) {
+        log_error("could not create cJSON object\n");
+        cJSON_Delete(post_data_json);
+        return NTS_ERR_FAILED;
+    }
+
+    if(cJSON_AddItemToObject(post_data_json, "event", event) == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(post_data_json);
+        return NTS_ERR_FAILED;
+    }
+
+    char *hostname_string = framework_environment.settings.hostname;
+    cJSON *common_event_header = ves_create_common_event_header_721("stndDefined", "o1-notify-pnf-registration", hostname_string, nf_port, "Low", ves_o1_pnf_sequence_number++, "o1-notify-pnf-registration");
+    if(common_event_header == 0) {
+        log_error("could not create cJSON object\n");
+        cJSON_Delete(post_data_json);
+        return NTS_ERR_FAILED;
+    }
+
+    if(nf_port == 0) {
+        if(mp == DOCKER_MAPPING) {
+            nf_port = STANDARD_NETCONF_PORT;
+        }
+        else {
+            if(is_tls) {
+                nf_port = framework_environment.host.tls_base_port;
+            }
+            else {
+                nf_port = framework_environment.host.ssh_base_port;
+            }
+        }
+    }
+
+    if(cJSON_AddItemToObject(event, "commonEventHeader", common_event_header) == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(post_data_json);
+        return NTS_ERR_FAILED;
+    }
+
+	cJSON *o1_pnf_registration_fields = ves_create_o1_pnf_registration_fields(nf_ip_v4_address, nf_ip_v6_address, nf_port, is_tls);
+    if(o1_pnf_registration_fields == 0) {
+        log_error("could not create cJSON object\n");
+        cJSON_Delete(post_data_json);
+        return NTS_ERR_FAILED;
+    }
+
+    if(cJSON_AddItemToObject(event, "stndDefinedFields", o1_pnf_registration_fields) == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(post_data_json);
+        return NTS_ERR_FAILED;
+    }
+
+    char *post_data = cJSON_PrintUnformatted(post_data_json);
+    cJSON_Delete(post_data_json);
+    if(post_data == 0) {
+        log_error("cJSON_PrintUnformatted failed\n");
+        return NTS_ERR_FAILED;
+    }
+
+
+    ves_details_t *ves_details = ves_endpoint_details_get(current_session, 0);
+    if(!ves_details) {
+        log_error("ves_endpoint_details_get failed\n");
+        free(post_data);
+        return NTS_ERR_FAILED;
+    }
+
+    int rc = http_request(ves_details->url, ves_details->username, ves_details->password, "POST", post_data, 0, 0);
+    ves_details_free(ves_details);
+    free(post_data);
+
+    if(rc != NTS_ERR_OK) {
+        log_error("http_request failed\n");
+        return NTS_ERR_FAILED;
+    }
+
+    return NTS_ERR_OK;
+}
+
+static cJSON* ves_create_o1_pnf_registration_fields(const char *nf_ip_v4_address, const char *nf_ip_v6_address, int nf_port, bool is_tls) {
+
+    cJSON *stnd_defined_fields = cJSON_CreateObject();
+    if(stnd_defined_fields == 0) {
+        log_error("could not create JSON object\n");
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(stnd_defined_fields, "stndDefinedFieldsVersion", "1.0") == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(stnd_defined_fields, "schemaReference", "https://gerrit.o-ran-sc.org/r/gitweb?p=scp/oam/modeling.git;a=blob;f=data-model/yang/working/o-ran-sc/template/yes-o1-notify-pnf-registration.yang#/components/schemas/o1-notify-pnf-registration") == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    cJSON *data = cJSON_CreateObject();
+    if(data == 0) {
+        log_error("could not create JSON object\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+    cJSON_AddItemToObject(stnd_defined_fields, "data", data);
+
+    if(cJSON_AddStringToObject(data, "object-class", "manged-function") == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(data, "object-instance", "YES-API-PROVIDER") == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddNumberToObject(data, "notification-identifier", (double)(0)) == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(data, "notification-type", "o1-notify-pnf-registration") == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    char *current_date_and_time = get_current_date_and_time();
+    if(current_date_and_time == 0) {
+        log_error("get_current_date_and_time failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(data, "event-time", current_date_and_time) == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        free(current_date_and_time);
+        return 0;
+    }
+    free(current_date_and_time);
+
+    if(cJSON_AddStringToObject(data, "system-distinguished-name", "DN:managed-element=YES-API-PROVIDER") == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(data, "o1-specification-version", "v07.00") == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    char serial_number[512];
+    sprintf(serial_number, "%s-%s-%d-Simulated Device Melacon", framework_environment.settings.hostname, nf_ip_v4_address, nf_port);
+
+    if(cJSON_AddStringToObject(data, "serial-number", serial_number) == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddNumberToObject(data, "vendor-pen", (double)(57272)) == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if (nf_ip_v4_address != 0 && strlen(nf_ip_v4_address) > 0) {
+        if(cJSON_AddStringToObject(data, "oam-host", nf_ip_v4_address) == 0) {
+            log_error("cJSON_AddItemToObject failed\n");
+            cJSON_Delete(stnd_defined_fields);
+            return 0;
+        }
+    }
+
+    if (nf_ip_v6_address != 0 && strlen(nf_ip_v6_address) > 0) {
+        if(cJSON_AddStringToObject(data, "oam-host", nf_ip_v6_address) == 0) {
+            log_error("cJSON_AddItemToObject failed\n");
+            cJSON_Delete(stnd_defined_fields);
+            return 0;
+        }
+    }
+
+    if(cJSON_AddNumberToObject(data, "oam-port", (double)(nf_port)) == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    char *mac_addr = rand_mac_address();
+    if(mac_addr == 0) {
+        log_error("rand_mac_address failed\n")
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(data, "mac-address", mac_addr) == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        free(mac_addr);
+        return 0;
+    }
+    free(mac_addr);
+
+    if(cJSON_AddStringToObject(data, "unit-family", "O-RAN-SC SIM") == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(data, "unit-type", "O1-Interface") == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(data, "model-number", "1859a6ea-2520-11ed-861d-0242ac120002") == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(data, "software-version", "G-Release") == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(data, "restart-reason", "External trigger") == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(data, "manufacture-date", "2022-08-26Z") == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(data, "last-service-date", "2022-08-26Z") == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(data, "username", "netconf") == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(is_tls) {
+        //TLS specific configuration
+        if(cJSON_AddStringToObject(data, "transport-protocol", "tls") == 0) {
+            log_error("cJSON_AddItemToObject failed\n");
+            cJSON_Delete(stnd_defined_fields);
+            return 0;
+        }
+
+        if(cJSON_AddStringToObject(data, "key-reference", KS_KEY_NAME) == 0) {
+            log_error("cJSON_AddItemToObject failed\n");
+            cJSON_Delete(stnd_defined_fields);
+            return 0;
+        }
+    }
+    else {
+        //SSH specific configuration
+        if(cJSON_AddStringToObject(data, "transport-protocol", "ssh") == 0) {
+            log_error("cJSON_AddItemToObject failed\n");
+            cJSON_Delete(stnd_defined_fields);
+            return 0;
+        }
+
+        // hardcoded password here
+        if(cJSON_AddStringToObject(data, "password", "netconf!") == 0) {
+            log_error("cJSON_AddItemToObject failed\n");
+            cJSON_Delete(stnd_defined_fields);
+            return 0;
+        }
+    }
+
+    if(cJSON_AddFalseToObject(data, "reconnect-on-changed-schema") == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddNumberToObject(data, "connection-timeout", (double)(20000)) == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddNumberToObject(data, "max-connection-attempts", (double)(100)) == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddNumberToObject(data, "between-attempts-timeout", (double)(2000)) == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddNumberToObject(data, "sleep-factor", (double)(1.5)) == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    if(cJSON_AddNumberToObject(data, "keepalive-delay", (double)(120)) == 0) {
+        log_error("cJSON_AddItemToObject failed\n");
+        cJSON_Delete(stnd_defined_fields);
+        return 0;
+    }
+
+    return stnd_defined_fields;
+}
diff --git a/ntsimulator/ntsim-ng/features/ves_o1_pnf_registration/ves_o1_pnf_registration.h b/ntsimulator/ntsim-ng/features/ves_o1_pnf_registration/ves_o1_pnf_registration.h
new file mode 100644
index 0000000..e6815cf
--- /dev/null
+++ b/ntsimulator/ntsim-ng/features/ves_o1_pnf_registration/ves_o1_pnf_registration.h
@@ -0,0 +1,23 @@
+/*************************************************************************
+*
+* Copyright 2022 highstreet technologies GmbH and others
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+***************************************************************************/
+
+#pragma once
+
+#include <sysrepo.h>
+
+int ves_o1_pnf_registration_feature_get_status(void);
+int ves_o1_pnf_registration_feature_start(sr_session_ctx_t *current_session);
diff --git a/ntsimulator/ntsim-ng/utils/nts_utils.c b/ntsimulator/ntsim-ng/utils/nts_utils.c
index d6c8a29..19cc9eb 100644
--- a/ntsimulator/ntsim-ng/utils/nts_utils.c
+++ b/ntsimulator/ntsim-ng/utils/nts_utils.c
@@ -161,6 +161,168 @@
     return common_event_header;
 }
 
+cJSON* ves_create_common_event_header_721(const char *domain, const char *event_type, const char *hostname, int port, const char *priority, int seq_id, const char *stnd_defined_namespace) {
+    assert(domain);
+    assert(event_type);
+    assert(hostname);
+    assert(priority);
+
+    char *eventId = 0;
+    long useconds = get_microseconds_since_epoch();
+
+    asprintf(&eventId, "%s-%d", event_type, seq_id);
+    if(eventId == 0) {
+        log_error("asprintf failed\n");
+        return 0;
+    }
+
+    cJSON *common_event_header = cJSON_CreateObject();
+    if(common_event_header == 0) {
+        log_error("could not create JSON object\n");
+        free(eventId);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(common_event_header, "domain", domain) == 0) {
+        log_error("cJSON AddStringToObject error\n");
+        free(eventId);
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(common_event_header, "eventId", eventId) == 0) {
+        log_error("cJSON AddStringToObject error\n");
+        free(eventId);
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    free(eventId);
+
+    char *eventName = 0;
+    asprintf(&eventName, "ves-stndDefined-%s", event_type);
+    if(eventId == 0) {
+        log_error("asprintf failed\n");
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(common_event_header, "eventName", eventName) == 0) {
+        log_error("cJSON AddStringToObject error\n");
+        cJSON_Delete(common_event_header);
+        free(eventName);
+        return 0;
+    }
+    free(eventName);
+
+    if(cJSON_AddStringToObject(common_event_header, "eventType", event_type) == 0) {
+        log_error("cJSON AddStringToObject error\n");
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    if(cJSON_AddNumberToObject(common_event_header, "sequence", (double)(seq_id)) == 0) {
+        log_error("cJSON AddNumberToObject error\n");
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(common_event_header, "priority", priority) == 0) {
+        log_error("cJSON AddStringToObject error\n");
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(common_event_header, "reportingEntityId", "") == 0) {
+        log_error("cJSON AddStringToObject error\n");
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    char source_name[512];
+    if(port) {
+        sprintf(source_name, "%s-%d", hostname, port);
+    }
+    else {
+        sprintf(source_name, "%s", hostname);
+    }
+
+    if(cJSON_AddStringToObject(common_event_header, "reportingEntityName", source_name) == 0) {
+        log_error("cJSON AddStringToObject error\n");
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(common_event_header, "sourceId", "") == 0) {
+        log_error("cJSON AddStringToObject error\n");
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(common_event_header, "sourceName", source_name) == 0) {
+        log_error("cJSON AddStringToObject error\n");
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    if(cJSON_AddNumberToObject(common_event_header, "startEpochMicrosec", (double)(useconds)) == 0) {
+        log_error("cJSON AddNumberToObject error\n");
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    if(cJSON_AddNumberToObject(common_event_header, "lastEpochMicrosec", (double)(useconds)) == 0) {
+        log_error("cJSON AddNumberToObject error\n");
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(common_event_header, "nfNamingCode", "sdn controller") == 0) {
+        log_error("cJSON AddStringToObject error\n");
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(common_event_header, "nfVendorName", "sdn") == 0) {
+        log_error("cJSON AddStringToObject error\n");
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(common_event_header, "timeZoneOffset", "+00:00") == 0) {
+        log_error("cJSON AddStringToObject error\n");
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(common_event_header, "version", "4.1") == 0) {
+        log_error("cJSON AddStringToObject error\n");
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(common_event_header, "vesEventListenerVersion", "7.2.1") == 0) {
+        log_error("cJSON AddStringToObject error\n");
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(common_event_header, "stndDefinedNamespace", stnd_defined_namespace) == 0) {
+        log_error("cJSON AddStringToObject error\n");
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    if(cJSON_AddStringToObject(common_event_header, "nfcNamingCode", "NFC") == 0) {
+        log_error("cJSON AddStringToObject error\n");
+        cJSON_Delete(common_event_header);
+        return 0;
+    }
+
+    return common_event_header;
+}
+
+
+
 nts_mount_point_addressing_method_t nts_mount_point_addressing_method_get(sr_session_ctx_t *current_session) {
     assert_session();
 
@@ -306,9 +468,9 @@
             // normal addressing with IP and Port
             asprintf(&ret->url, "%s://%s:%d/eventListener/v7", ret->protocol, ret->ip, ret->port);
         }
-        
+
     }
-    
+
     if((ret->protocol == 0) || (ret->ip == 0) || (ret->auth_method == 0) || (ret->username == 0) || (ret->password == 0) || (ret->url == 0)) {
         free(ret->protocol);
         free(ret->ip);
diff --git a/ntsimulator/ntsim-ng/utils/nts_utils.h b/ntsimulator/ntsim-ng/utils/nts_utils.h
index 34d990e..af9a264 100644
--- a/ntsimulator/ntsim-ng/utils/nts_utils.h
+++ b/ntsimulator/ntsim-ng/utils/nts_utils.h
@@ -38,7 +38,7 @@
     char *url;
 } ves_details_t;
 
-typedef struct {   
+typedef struct {
     char *ip;
     uint16_t port;
     char *nc_callhome_ip;
@@ -52,6 +52,7 @@
 } controller_details_t;
 
 cJSON* ves_create_common_event_header(const char *domain, const char *event_type, const char *hostname, int port, const char *priority, int seq_id);
+cJSON* ves_create_common_event_header_721(const char *domain, const char *event_type, const char *hostname, int port, const char *priority, int seq_id, const char *stnd_defined_namespace);
 
 nts_mount_point_addressing_method_t nts_mount_point_addressing_method_get(sr_session_ctx_t *current_session);