[VOL-3396] Encryption of the GPON GEMs
- Enables downstream encryption on both GPON and XGSPON unicast GEM PortIDs through FlowAdd operation

Change-Id: I4171194db050b96a0940842afc10e8c8c9e285d1
diff --git a/BUILDING.md b/BUILDING.md
index 5a6f013..e0a5dfd 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -158,7 +158,7 @@
 make OPENOLTDEVICE=asfvolt16 OPENOLT_PROTO_VER=master
 ```
 
-By default, the `OPENOLT_PROTO_VER` defaults to git tag *v4.0.11* of the
+By default, the `OPENOLT_PROTO_VER` defaults to git tag *v4.0.14* of the
 [voltha-protos](https://gerrit.opencord.org/gitweb?p=voltha-protos.git;a=summary)
 repo.
 
diff --git a/Makefile b/Makefile
index ee5bda2..a689a37 100644
--- a/Makefile
+++ b/Makefile
@@ -27,7 +27,7 @@
 
 ## Variables
 OPENOLTDEVICE     ?= asfvolt16
-OPENOLT_PROTO_VER ?= v4.0.11
+OPENOLT_PROTO_VER ?= v4.0.14
 
 DOCKER                     ?= docker
 DOCKER_REGISTRY            ?=
diff --git a/agent/Makefile.in b/agent/Makefile.in
index 9a99b6b..5ccea59 100644
--- a/agent/Makefile.in
+++ b/agent/Makefile.in
@@ -42,7 +42,7 @@
 # This specifies the GIT tag in https://github.com/opencord/voltha-protos
 # repo that we need to refer to, to pick the right version of
 # openolt.proto and tech_profile.proto
-OPENOLT_PROTO_VER ?= v4.0.11
+OPENOLT_PROTO_VER ?= v4.0.14
 
 # Variables used for Inband build
 INBAND = "n"
diff --git a/agent/common/core.h b/agent/common/core.h
index ab4c788..b3a5408 100644
--- a/agent/common/core.h
+++ b/agent/common/core.h
@@ -225,7 +225,7 @@
                 int32_t alloc_id, int32_t network_intf_id,
                 int32_t gemport_id, const ::openolt::Classifier& classifier,
                 const ::openolt::Action& action, int32_t priority_value,
-                uint64_t cookie, int32_t group_id, uint32_t tech_profile_id);
+                uint64_t cookie, int32_t group_id, uint32_t tech_profile_id, bool enable_encryption = false);
 Status FlowRemoveWrapper_(const openolt::Flow* request);
 Status FlowRemove_(uint32_t flow_id, const std::string flow_type);
 Status Disable_();
diff --git a/agent/src/core_api_handler.cc b/agent/src/core_api_handler.cc
index 2c5ff83..3d8bd93 100644
--- a/agent/src/core_api_handler.cc
+++ b/agent/src/core_api_handler.cc
@@ -1149,6 +1149,7 @@
         }
     }
 
+// TODO: MOVE THIS TO A NEW METHOD
     if (omcc_encryption_mode == true) {
         // set the encryption mode for omci port id
         bcmolt_itupon_gem_cfg gem_cfg;
@@ -1162,7 +1163,7 @@
         BCMOLT_FIELD_SET(&gem_cfg.data, itupon_gem_cfg_data, encryption_mode, encryption_mode);
         err = bcmolt_cfg_set(dev_id, &gem_cfg.hdr);
         if(err != BCM_ERR_OK) {
-                OPENOLT_LOG(ERROR, openolt_log_id, "failed to confiure omci gem_port encryption mode = %d\n", onu_id);
+                OPENOLT_LOG(ERROR, openolt_log_id, "failed to configure omci gem_port encryption mode = %d\n", onu_id);
                 return bcm_to_grpc_err(err, "Access_Control set ITU PON OMCI Gem port failed");
         }
     }
@@ -1459,6 +1460,17 @@
     return Status::OK;
 }
 
+bool get_aes_flag_for_gem_port(const google::protobuf::Map<unsigned int, bool> &gemport_to_aes, uint32_t gemport_id) {
+    bool aes_flag = false;
+    for (google::protobuf::Map<unsigned int, bool>::const_iterator it=gemport_to_aes.begin(); it!=gemport_to_aes.end(); it++) {
+        if (it->first == gemport_id) {
+            aes_flag = it->second;
+            break;
+        }
+    }
+    return aes_flag;
+}
+
 Status FlowAddWrapper_(const ::openolt::Flow* request) {
 
     int32_t access_intf_id = request->access_intf_id();
@@ -1479,7 +1491,9 @@
     uint32_t tech_profile_id = request->tech_profile_id();
     bool replicate_flow = request->replicate_flow();
     const google::protobuf::Map<unsigned int, unsigned int> &pbit_to_gemport = request->pbit_to_gemport();
+    const google::protobuf::Map<unsigned int, bool> &gemport_to_aes = request->gemport_to_aes();
     uint16_t flow_id;
+    bool enable_encryption;
 
     // The intf_id variable defaults to access(PON) interface ID.
     // For trap-from-nni flow where access interface ID is not valid , change it to NNI interface ID
@@ -1515,11 +1529,12 @@
                 flow_id = dev_fl_symm_params[0].flow_id;
                 gemport_id = dev_fl_symm_params[0].gemport_id; // overwrite the gemport with symmetric flow gemport
                                                                // Should be same as what is coming in this request.
+                enable_encryption = get_aes_flag_for_gem_port(gemport_to_aes, gemport_id);
                 ::openolt::Classifier cl = ::openolt::Classifier(classifier);
                 cl.set_o_pbits(dev_fl_symm_params[0].pbit);
                 Status st = FlowAdd_(access_intf_id, onu_id, uni_id, port_no, flow_id,
                                     flow_type, alloc_id, network_intf_id, gemport_id, cl,
-                                    action, priority, cookie, group_id, tech_profile_id);
+                                    action, priority, cookie, group_id, tech_profile_id, enable_encryption);
                 if (st.error_code() == grpc::StatusCode::OK) {
                     device_flow dev_fl;
                     dev_fl.is_flow_replicated = false;
@@ -1536,10 +1551,11 @@
                 ::openolt::Classifier cl = ::openolt::Classifier(classifier);
                 flow_id = dev_fl_symm_params[i].flow_id;
                 gemport_id = dev_fl_symm_params[i].gemport_id;
+                enable_encryption = get_aes_flag_for_gem_port(gemport_to_aes, gemport_id);
                 cl.set_o_pbits(dev_fl_symm_params[i].pbit);
                 Status st = FlowAdd_(access_intf_id, onu_id, uni_id, port_no, flow_id,
                                     flow_type, alloc_id, network_intf_id, gemport_id, cl,
-                                    action, priority, cookie, group_id, tech_profile_id);
+                                    action, priority, cookie, group_id, tech_profile_id, enable_encryption);
                 if (st.error_code() != grpc::StatusCode::OK && st.error_code() != grpc::StatusCode::ALREADY_EXISTS) {
                     OPENOLT_LOG(ERROR, openolt_log_id, "failed to install device flow=%u for voltha flow=%lu. Undoing any device flows installed.", flow_id, voltha_flow_id);
                     // On failure remove any successfully replicated flows installed so far for the voltha_flow_id
@@ -1568,9 +1584,10 @@
                 OPENOLT_LOG(ERROR, openolt_log_id, "could not allocated flow id for voltha-flow-id=%lu\n", voltha_flow_id);
                 return ::Status(grpc::StatusCode::RESOURCE_EXHAUSTED, "flow-ids-exhausted");
             }
+            enable_encryption = get_aes_flag_for_gem_port(gemport_to_aes, gemport_id);
             Status st = FlowAdd_(access_intf_id, onu_id, uni_id, port_no, flow_id,
                                 flow_type, alloc_id, network_intf_id, gemport_id, classifier,
-                                action, priority, cookie, group_id, tech_profile_id);
+                                action, priority, cookie, group_id, tech_profile_id, enable_encryption);
             if (st.error_code() == grpc::StatusCode::OK) {
                 device_flow dev_fl;
                 dev_fl.is_flow_replicated = false;
@@ -1607,10 +1624,11 @@
                     ::openolt::Classifier cl = ::openolt::Classifier(classifier);
                     flow_id = dev_fl.params[cnt].flow_id;
                     gemport_id = dev_fl.params[cnt].gemport_id;
+                    enable_encryption = get_aes_flag_for_gem_port(gemport_to_aes, gemport_id);
                     cl.set_o_pbits(dev_fl.params[cnt].pbit);
                     Status st = FlowAdd_(access_intf_id, onu_id, uni_id, port_no, flow_id,
                                         flow_type, alloc_id, network_intf_id, gemport_id, cl,
-                                        action, priority, cookie, group_id, tech_profile_id);
+                                        action, priority, cookie, group_id, tech_profile_id, enable_encryption);
                     if (st.error_code() != grpc::StatusCode::OK) {
                         OPENOLT_LOG(ERROR, openolt_log_id, "failed to install device flow=%u for voltha flow=%lu. Undoing any device flows installed.", flow_id, voltha_flow_id);
                         // Remove any successfully replicated flows installed so far for the voltha_flow_id
@@ -1644,7 +1662,7 @@
                 int32_t alloc_id, int32_t network_intf_id,
                 int32_t gemport_id, const ::openolt::Classifier& classifier,
                 const ::openolt::Action& action, int32_t priority_value, uint64_t cookie,
-                int32_t group_id, uint32_t tech_profile_id) {
+                int32_t group_id, uint32_t tech_profile_id, bool aes_enabled) {
     bcmolt_flow_cfg cfg;
     bcmolt_flow_key key = { }; /**< Object key. */
     int32_t o_vid = -1;
@@ -1698,13 +1716,13 @@
             return bcm_to_grpc_err(BCM_ERR_PARM, "flow network setting invalid");
         }
 
-        if (onu_id >= 0) {
+        if (onu_id >= ONU_ID_START) {
             BCMOLT_MSG_FIELD_SET(&cfg, onu_id, onu_id);
         }
-        if (gemport_id >= 0) {
+        if (gemport_id >= GEM_PORT_ID_START) {
             BCMOLT_MSG_FIELD_SET(&cfg, svc_port_id, gemport_id);
         }
-        if (gemport_id >= 0 && port_no != 0) {
+        if (gemport_id >= GEM_PORT_ID_START && port_no != 0) {
             bcmos_fastlock_lock(&data_lock);
             if (key.flow_type == BCMOLT_FLOW_TYPE_DOWNSTREAM) {
                 port_to_flows[port_no].insert(key.flow_id);
@@ -1856,7 +1874,7 @@
 
     BCMOLT_MSG_FIELD_SET(&cfg, action, a_val);
 
-    if ((access_intf_id >= 0) && (onu_id >= 0)) {
+    if ((access_intf_id >= 0) && (onu_id >= ONU_ID_START)) {
         qos_type = get_qos_type(access_intf_id, onu_id, uni_id);
         if (key.flow_type == BCMOLT_FLOW_TYPE_DOWNSTREAM) {
             tm_val.sched_id = get_tm_sched_id(access_intf_id, onu_id, uni_id, downstream, tech_profile_id);
@@ -1996,6 +2014,22 @@
         bcmos_fastlock_unlock(&data_lock, 0);
 
     }
+
+    /*
+       Enable AES encryption on GEM ports if they are used in downstream unicast flows.
+       Rationale: We can't do upstream encryption in GPON. This change addresses the common denominator (and also minimum viable)
+       use case for both technologies which is downstream unicast GEM port encryption. Since the downstream traffic is inherently
+       broadcast to all the ONUs behind a PON port, encrypting the individual subscriber traffic in this direction is important
+       and considered good enough in terms of security (See Section 12.1 of G.984.3). For upstream unicast and downstream multicast
+       GEM encryption, we need to make additional changes specific to XGSPON. This will be done as a future work.
+    */
+    if (aes_enabled && (access_intf_id >= 0) && (gemport_id >= GEM_PORT_ID_START) && (key.flow_type == BCMOLT_FLOW_TYPE_DOWNSTREAM)) {
+        OPENOLT_LOG(INFO, openolt_log_id, "Setting encryption on pon = %d gem_port = %d through flow_id = %d\n", access_intf_id, gemport_id, flow_id);
+        enable_encryption_for_gem_port(access_intf_id, gemport_id);
+    } else {
+        OPENOLT_LOG(WARNING, openolt_log_id, "Flow config for flow_id = %d is not suitable for setting downstream encryption on pon = %d gem_port = %d. No action taken.\n", flow_id, access_intf_id, gemport_id);
+    }
+
     return Status::OK;
 }
 
diff --git a/agent/src/core_utils.cc b/agent/src/core_utils.cc
index 968975e..59c7cdc 100644
--- a/agent/src/core_utils.cc
+++ b/agent/src/core_utils.cc
@@ -918,7 +918,7 @@
 
     err = bcmolt_cfg_set(dev_id, &cfg.hdr);
     if(err != BCM_ERR_OK) {
-        OPENOLT_LOG(ERROR, openolt_log_id, "failed to install gem_port = %d err_text=%s\n", gemport_id, cfg.hdr.hdr.err_text);
+        OPENOLT_LOG(ERROR, openolt_log_id, "failed to install gem_port = %d err = %s (%d)\n", gemport_id, cfg.hdr.hdr.err_text, err);
         return bcm_to_grpc_err(err, "Access_Control set ITU PON Gem port failed");
     }
 
@@ -939,7 +939,7 @@
     err = bcmolt_cfg_clear(dev_id, &gem_cfg.hdr);
     if (err != BCM_ERR_OK)
     {
-        OPENOLT_LOG(ERROR, openolt_log_id, "failed to remove gem_port = %d err=%s\n", gemport_id, gem_cfg.hdr.hdr.err_text);
+        OPENOLT_LOG(ERROR, openolt_log_id, "failed to remove gem_port = %d err = %s (%d)\n", gemport_id, gem_cfg.hdr.hdr.err_text, err);
         return bcm_to_grpc_err(err, "Access_Control clear ITU PON Gem port failed");
     }
 
@@ -948,6 +948,32 @@
     return Status::OK;
 }
 
+Status enable_encryption_for_gem_port(int32_t intf_id, int32_t gemport_id) {
+    bcmos_errno err;
+    bcmolt_itupon_gem_cfg cfg;
+    bcmolt_itupon_gem_key key = {
+        .pon_ni = (bcmolt_interface)intf_id,
+        .gem_port_id = (bcmolt_gem_port_id)gemport_id
+    };
+
+    BCMOLT_CFG_INIT(&cfg, itupon_gem, key);
+
+    bcmolt_control_state encryption_mode;
+    encryption_mode = BCMOLT_CONTROL_STATE_ENABLE;
+    BCMOLT_FIELD_SET(&cfg.data, itupon_gem_cfg_data, encryption_mode, encryption_mode);
+
+    err = bcmolt_cfg_set(dev_id, &cfg.hdr);
+    if(err != BCM_ERR_OK) {
+        OPENOLT_LOG(ERROR, openolt_log_id, "failed to set encryption on pon = %d gem_port = %d, err = %s (%d)\n",
+            intf_id, gemport_id, cfg.hdr.hdr.err_text, err);
+        return bcm_to_grpc_err(err, "Failed to set encryption on GEM port");;
+    }
+
+    OPENOLT_LOG(INFO, openolt_log_id, "encryption set successfully on pon = %d gem_port = %d\n", intf_id, gemport_id);
+
+    return Status::OK;
+}
+
 Status update_acl_interface(int32_t intf_id, bcmolt_interface_type intf_type, uint32_t access_control_id,
                 bcmolt_members_update_command acl_cmd) {
     bcmos_errno err;
diff --git a/agent/src/core_utils.h b/agent/src/core_utils.h
index 8404912..0f7cfe7 100644
--- a/agent/src/core_utils.h
+++ b/agent/src/core_utils.h
@@ -93,6 +93,7 @@
 bcmos_errno get_nni_interface_status(bcmolt_interface id, bcmolt_interface_state *state);
 Status install_gem_port(int32_t intf_id, int32_t onu_id, int32_t gemport_id);
 Status remove_gem_port(int32_t intf_id, int32_t gemport_id);
+Status enable_encryption_for_gem_port(int32_t intf_id, int32_t gemport_id);
 Status update_acl_interface(int32_t intf_id, bcmolt_interface_type intf_type, uint32_t access_control_id,
                 bcmolt_members_update_command acl_cmd);
 Status install_acl(const acl_classifier_key acl_key);
diff --git a/agent/test/Makefile b/agent/test/Makefile
index 66986e3..df93b7c 100644
--- a/agent/test/Makefile
+++ b/agent/test/Makefile
@@ -21,7 +21,7 @@
 TOP_DIR=`pwd`
 OPENOLTDEVICE ?= asfvolt16
 
-OPENOLT_PROTO_VER ?= v4.0.11
+OPENOLT_PROTO_VER ?= v4.0.14
 
 ########################################################################
 ##
diff --git a/agent/test/src/test_core.cc b/agent/test/src/test_core.cc
index 7efb8c9..2d2bede 100644
--- a/agent/test/src/test_core.cc
+++ b/agent/test/src/test_core.cc
@@ -1237,6 +1237,7 @@
         uint64_t cookie = 0;
         int32_t group_id = -1;
         uint32_t tech_profile_id = 64;
+        bool enable_encryption = true;
 
         NiceMock<BalMocker> balMock;
         openolt::Flow* flow;
@@ -1536,7 +1537,81 @@
     ASSERT_TRUE( status.error_message() == Status::OK.error_message() );
 }
 
+// Test 11 - FlowAdd - success case (Downstream-Encrypted GEM)
+TEST_F(TestFlowAdd, FlowAddDownstreamEncryptedGemSuccess) {
+    onu_id = 2;
+    flow_id = 7;
+    flow_type = "downstream";
+    alloc_id = 1025;
+    enable_encryption = true;
 
+    bcmos_errno flow_cfg_get_stub_res = BCM_ERR_OK;
+    bcmos_errno olt_cfg_set_res = BCM_ERR_OK;
+    EXPECT_GLOBAL_CALL(bcmolt_cfg_get__flow_stub, bcmolt_cfg_get__flow_stub(_, _))
+                     .WillRepeatedly(DoAll(SetArg1ToBcmOltFlowCfg(flow_cfg), Return(flow_cfg_get_stub_res)));
+    ON_CALL(balMock, bcmolt_cfg_set(_, _)).WillByDefault(Return(olt_cfg_set_res));
+
+    Status status = FlowAdd_(access_intf_id, onu_id, uni_id, port_no, flow_id, flow_type, alloc_id, network_intf_id,
+        gemport_id, *classifier, *action, priority_value, cookie, group_id, tech_profile_id, enable_encryption);
+    ASSERT_TRUE( status.error_message() == Status::OK.error_message() );
+}
+
+// Test 12 - FlowAdd - success case (Downstream-Unencrypted GEM - prints warning that encryption will not applied)
+TEST_F(TestFlowAdd, FlowAddDownstreamUnencryptedGemWarning) {
+    onu_id = 2;
+    flow_id = 8;
+    flow_type = "downstream";
+    alloc_id = 1025;
+    enable_encryption = false;
+
+    bcmos_errno flow_cfg_get_stub_res = BCM_ERR_OK;
+    bcmos_errno olt_cfg_set_res = BCM_ERR_OK;
+    EXPECT_GLOBAL_CALL(bcmolt_cfg_get__flow_stub, bcmolt_cfg_get__flow_stub(_, _))
+                     .WillRepeatedly(DoAll(SetArg1ToBcmOltFlowCfg(flow_cfg), Return(flow_cfg_get_stub_res)));
+    ON_CALL(balMock, bcmolt_cfg_set(_, _)).WillByDefault(Return(olt_cfg_set_res));
+
+    Status status = FlowAdd_(access_intf_id, onu_id, uni_id, port_no, flow_id, flow_type, alloc_id, network_intf_id,
+        gemport_id, *classifier, *action, priority_value, cookie, group_id, tech_profile_id, enable_encryption);
+    ASSERT_TRUE( status.error_message() == Status::OK.error_message() );
+}
+
+// Test 13 - FlowAdd - success case (Upstream-Encrypted GEM - prints warning that encryption will not applied)
+TEST_F(TestFlowAdd, FlowAddUpstreamEncryptedGemWarning) {
+    onu_id = 2;
+    flow_id = 9;
+    flow_type = "upstream";
+    alloc_id = 1025;
+    enable_encryption = true;
+
+    bcmos_errno flow_cfg_get_stub_res = BCM_ERR_OK;
+    bcmos_errno olt_cfg_set_res = BCM_ERR_OK;
+    EXPECT_GLOBAL_CALL(bcmolt_cfg_get__flow_stub, bcmolt_cfg_get__flow_stub(_, _))
+                     .WillRepeatedly(DoAll(SetArg1ToBcmOltFlowCfg(flow_cfg), Return(flow_cfg_get_stub_res)));
+    ON_CALL(balMock, bcmolt_cfg_set(_, _)).WillByDefault(Return(olt_cfg_set_res));
+
+    Status status = FlowAdd_(access_intf_id, onu_id, uni_id, port_no, flow_id, flow_type, alloc_id, network_intf_id,
+        gemport_id, *classifier, *action, priority_value, cookie, group_id, tech_profile_id, enable_encryption);
+    ASSERT_TRUE( status.error_message() == Status::OK.error_message() );
+}
+
+// Test 14 - FlowAdd - success case (Multicast-Encrypted GEM - prints warning that encryption will not applied)
+TEST_F(TestFlowAdd, FlowAddMulticastEncryptedGemWarning) {
+    onu_id = 2;
+    flow_id = 10;
+    flow_type = "multicast";
+    alloc_id = 1025;
+    enable_encryption = true;
+
+    bcmos_errno flow_cfg_get_stub_res = BCM_ERR_OK;
+    bcmos_errno olt_cfg_set_res = BCM_ERR_OK;
+    EXPECT_GLOBAL_CALL(bcmolt_cfg_get__flow_stub, bcmolt_cfg_get__flow_stub(_, _))
+                     .WillRepeatedly(DoAll(SetArg1ToBcmOltFlowCfg(flow_cfg), Return(flow_cfg_get_stub_res)));
+    ON_CALL(balMock, bcmolt_cfg_set(_, _)).WillByDefault(Return(olt_cfg_set_res));
+
+    Status status = FlowAdd_(access_intf_id, onu_id, uni_id, port_no, flow_id, flow_type, alloc_id, network_intf_id,
+        gemport_id, *classifier, *action, priority_value, cookie, group_id, tech_profile_id, enable_encryption);
+    ASSERT_TRUE( status.error_message() == Status::OK.error_message() );
+}
 ////////////////////////////////////////////////////////////////////////////
 // For testing OnuPacketOut functionality
 ////////////////////////////////////////////////////////////////////////////
@@ -1716,6 +1791,10 @@
     FlowRemove_(4, "downstream");
     FlowRemove_(5, "upstream");
     FlowRemove_(6, "downstream");
+    FlowRemove_(7, "downstream");
+    FlowRemove_(8, "downstream");
+    FlowRemove_(9, "upstream");
+    FlowRemove_(10, "multicast");
 
     bcmos_errno flow_cfg_get_stub_res = BCM_ERR_OK;
     EXPECT_GLOBAL_CALL(bcmolt_cfg_get__flow_stub, bcmolt_cfg_get__flow_stub(_, _))
diff --git a/protos/Makefile b/protos/Makefile
index 2494a08..8b28160 100644
--- a/protos/Makefile
+++ b/protos/Makefile
@@ -19,7 +19,7 @@
 # This specifies the GIT tag in https://github.com/opencord/voltha-protos
 # repo that we need to refer to, to pick the right version of
 # openolt.proto, ext_config.proto and tech_profile.proto
-OPENOLT_PROTO_VER ?= v4.0.11
+OPENOLT_PROTO_VER ?= v4.0.14
 
 CXX ?= g++
 CPPFLAGS += `pkg-config --cflags protobuf grpc` -I googleapis/gens -I./