VOL-2596 : Measure optical distance between OLT and ONU

Change-Id: Ib13d85fe47a800348f82a5d76a90d97d98b20e1e
diff --git a/agent/common/core.h b/agent/common/core.h
index c68fdf3..d277904 100644
--- a/agent/common/core.h
+++ b/agent/common/core.h
@@ -192,6 +192,7 @@
        (type == BCMOLT_INTERFACE_TYPE_NNI) ? "NNI" : \
        (type == BCMOLT_INTERFACE_TYPE_HOST) ? "HOST" : "unknown"
 
+#define LOGICAL_DISTANCE(MLD,EQD,TD) (MLD-(EQD*TD)*102) /* Round-trip time of 102 meters is 1us */
 extern State state;
 
 //***************************************//
@@ -232,6 +233,8 @@
 Status DeleteGroup_(uint32_t group_id);
 Status OnuItuPonAlarmSet_(const openolt::OnuItuPonAlarm* request);
 uint32_t GetPortNum_(uint32_t flow_id);
+Status GetLogicalOnuDistanceZero_(uint32_t intf_id, openolt::OnuLogicalDistance* response);
+Status GetLogicalOnuDistance_(uint32_t intf_id, uint32_t onu_id, openolt::OnuLogicalDistance* response);
 int get_status_bcm_cli_quit(void);
 uint16_t get_dev_id(void);
 Status pushOltOperInd(uint32_t intf_id, const char *type, const char *state);
diff --git a/agent/common/server.cc b/agent/common/server.cc
index baeb590..5a30f37 100644
--- a/agent/common/server.cc
+++ b/agent/common/server.cc
@@ -322,6 +322,25 @@
             openolt::Empty* response) override {
         return OnuItuPonAlarmSet_(request);
     };
+
+    Status GetLogicalOnuDistanceZero(
+            ServerContext* context,
+            const openolt::Onu* request,
+            openolt::OnuLogicalDistance* response) override {
+        return GetLogicalOnuDistanceZero_(
+            request->intf_id(),
+            response);
+    };
+
+    Status GetLogicalOnuDistance(
+            ServerContext* context,
+            const openolt::Onu* request,
+            openolt::OnuLogicalDistance* response) override {
+        return GetLogicalOnuDistance_(
+            request->intf_id(),
+            request->onu_id(),
+            response);
+    };
 };
 
 void RunServer(int argc, char** argv) {
diff --git a/agent/device/asfvolt16/vendor.h b/agent/device/asfvolt16/vendor.h
index a15fec0..2c2c733 100644
--- a/agent/device/asfvolt16/vendor.h
+++ b/agent/device/asfvolt16/vendor.h
@@ -20,4 +20,6 @@
 #define ASFVOLT16
 #define MODEL_ID  "asfvolt16"
 #define MAX_SUPPORTED_PON 16
+#define ONU_BIT_TRANSMISSION_DELAY 0.1004823/1000 /* unit: ns to us */
+#define MINIMUM_ONU_RESPONSE_RANGING_TIME 1572135 /* hardcore: this is ranging time for the shortest distance, typically 35us */
 #endif
diff --git a/agent/device/asgvolt64/vendor.h b/agent/device/asgvolt64/vendor.h
index d7280e4..3ea790c 100644
--- a/agent/device/asgvolt64/vendor.h
+++ b/agent/device/asgvolt64/vendor.h
@@ -20,4 +20,6 @@
 #define ASGVOLT64
 #define MODEL_ID  "asgvolt64"
 #define MAX_SUPPORTED_PON 64
+#define ONU_BIT_TRANSMISSION_DELAY 0.8038585/1000 /* unit: ns to us */
+#define MINIMUM_ONU_RESPONSE_RANGING_TIME 198075 /* hardcore: this is ranging time for the shortest distance, typically 35us */
 #endif
diff --git a/agent/src/core_api_handler.cc b/agent/src/core_api_handler.cc
index 9a22497..9975c36 100644
--- a/agent/src/core_api_handler.cc
+++ b/agent/src/core_api_handler.cc
@@ -2868,4 +2868,73 @@
 
     OPENOLT_LOG(INFO, openolt_log_id, "Group %d has been deleted successfully.\n", group_id);
     return Status::OK;
-}
\ No newline at end of file
+}
+
+Status GetLogicalOnuDistanceZero_(uint32_t intf_id, openolt::OnuLogicalDistance* response) {
+    bcmos_errno err = BCM_ERR_OK;
+    uint32_t mld = 0;
+    double LD0;
+
+    err = getOnuMaxLogicalDistance(intf_id, &mld);
+    if (err != BCM_ERR_OK) {
+        return bcm_to_grpc_err(err, "Failed to retrieve ONU maximum logical distance");
+    }
+
+    LD0 = LOGICAL_DISTANCE(mld*1000, MINIMUM_ONU_RESPONSE_RANGING_TIME, ONU_BIT_TRANSMISSION_DELAY);
+    OPENOLT_LOG(INFO, openolt_log_id, "The ONU logical distance zero is %f, (PON %d)\n", LD0, intf_id);
+    response->set_intf_id(intf_id);
+    response->set_logical_onu_distance_zero(LD0);
+
+    return Status::OK;
+}
+
+Status GetLogicalOnuDistance_(uint32_t intf_id, uint32_t onu_id, openolt::OnuLogicalDistance* response) {
+    bcmos_errno err = BCM_ERR_OK;
+    bcmolt_itu_onu_params itu = {};
+    bcmolt_onu_cfg onu_cfg;
+    bcmolt_onu_key onu_key = {};
+    uint32_t mld = 0;
+    double LDi;
+
+    onu_key.pon_ni = intf_id;
+    onu_key.onu_id = onu_id;
+
+    err = getOnuMaxLogicalDistance(intf_id, &mld);
+    if (err != BCM_ERR_OK) {
+        return bcm_to_grpc_err(err, "Failed to retrieve ONU maximum logical distance");
+    }
+
+    /* Initialize the API struct. */
+    BCMOLT_CFG_INIT(&onu_cfg, onu, onu_key);
+    BCMOLT_FIELD_SET_PRESENT(&onu_cfg.data, onu_cfg_data, onu_state);
+    BCMOLT_FIELD_SET_PRESENT(&itu, itu_onu_params, ranging_time);
+    BCMOLT_FIELD_SET(&onu_cfg.data, onu_cfg_data, itu, itu);
+    #ifdef TEST_MODE
+    // It is impossible to mock the setting of onu_cfg.data.onu_state because
+    // the actual bcmolt_cfg_get passes the address of onu_cfg.hdr and we cannot
+    // set the onu_cfg.data.onu_state. So a new stub function is created and address
+    // of onu_cfg is passed. This is one-of case where we need to add test specific
+    // code in production code.
+    err = bcmolt_cfg_get__onu_state_stub(dev_id, &onu_cfg);
+    #else
+    /* Call API function. */
+    err = bcmolt_cfg_get(dev_id, &onu_cfg.hdr);
+    #endif
+    if (err != BCM_ERR_OK) {
+        OPENOLT_LOG(ERROR, openolt_log_id, "Failed to retrieve ONU ranging time for PON %d/ONU id %d, err = %s (%d)\n", intf_id, onu_id, bcmos_strerror(err), err);
+        return bcm_to_grpc_err(err, "Failed to retrieve ONU ranging time");
+    }
+
+    if (onu_cfg.data.onu_state != BCMOLT_ONU_STATE_ACTIVE) {
+        OPENOLT_LOG(ERROR, openolt_log_id, "ONU is not yet activated (PON %d, ONU id %d)\n", intf_id, onu_id);
+        return bcm_to_grpc_err(BCM_ERR_PARM, "ONU is not yet activated\n");
+    }
+
+    LDi = LOGICAL_DISTANCE(mld*1000, onu_cfg.data.itu.ranging_time, ONU_BIT_TRANSMISSION_DELAY);
+    OPENOLT_LOG(INFO, openolt_log_id, "The ONU logical distance is %f, (PON %d, ONU id %d)\n", LDi, intf_id, onu_id);
+    response->set_intf_id(intf_id);
+    response->set_onu_id(onu_id);
+    response->set_logical_onu_distance(LDi);
+
+    return Status::OK;
+}
diff --git a/agent/src/core_utils.cc b/agent/src/core_utils.cc
index 1412000..9a59487 100644
--- a/agent/src/core_utils.cc
+++ b/agent/src/core_utils.cc
@@ -1295,3 +1295,39 @@
     freeifaddrs(interfaces);
     return ipAddress;
 }
+
+bcmos_errno getOnuMaxLogicalDistance(uint32_t intf_id, uint32_t *mld) {
+    bcmos_errno err = BCM_ERR_OK;
+    bcmolt_pon_distance pon_distance = {};
+    bcmolt_pon_interface_cfg pon_cfg; /* declare main API struct */
+    bcmolt_pon_interface_key key = {}; /* declare key */
+
+    key.pon_ni = intf_id;
+
+    if (!state.is_activated()) {
+        OPENOLT_LOG(ERROR, openolt_log_id, "ONU maximum logical distance is not available since OLT is not activated yet\n");
+        return BCM_ERR_STATE;
+    }
+
+    /* Initialize the API struct. */
+    BCMOLT_CFG_INIT(&pon_cfg, pon_interface, key);
+        BCMOLT_FIELD_SET_PRESENT(&pon_distance, pon_distance, max_log_distance);
+    BCMOLT_FIELD_SET(&pon_cfg.data, pon_interface_cfg_data, pon_distance, pon_distance);
+    #ifdef TEST_MODE
+    // It is impossible to mock the setting of pon_cfg.data.state because
+    // the actual bcmolt_cfg_get passes the address of pon_cfg.hdr and we cannot
+    // set the pon_cfg.data.state. So a new stub function is created and address
+    // of pon_cfg is passed. This is one-of case where we need to add test specific
+    // code in production code.
+    err = bcmolt_cfg_get__pon_intf_stub(dev_id, &pon_cfg);
+    #else
+    err = bcmolt_cfg_get(dev_id, &pon_cfg.hdr);
+    #endif
+        if (err != BCM_ERR_OK) {
+            OPENOLT_LOG(ERROR, openolt_log_id, "Failed to retrieve ONU maximum logical distance for PON %d, err = %s (%d)\n", intf_id, bcmos_strerror(err), err);
+            return err;
+        }
+        *mld = pon_distance.max_log_distance;
+
+    return BCM_ERR_OK;
+}
diff --git a/agent/src/core_utils.h b/agent/src/core_utils.h
index 5f64e47..5de6753 100644
--- a/agent/src/core_utils.h
+++ b/agent/src/core_utils.h
@@ -101,4 +101,5 @@
 Status check_bal_ready();
 Status check_connection();
 std::string get_ip_address(const char* nw_intf);
+bcmos_errno getOnuMaxLogicalDistance(uint32_t intf_id, uint32_t *mld);
 #endif // OPENOLT_CORE_UTILS_H_
diff --git a/agent/test/src/test_core.cc b/agent/test/src/test_core.cc
index db957e3..a6643d6 100644
--- a/agent/test/src/test_core.cc
+++ b/agent/test/src/test_core.cc
@@ -69,7 +69,6 @@
 MOCK_GLOBAL_FUNC2(bcmolt_cfg_get__nni_intf_stub, bcmos_errno(bcmolt_oltid, void*));
 MOCK_GLOBAL_FUNC2(bcmolt_cfg_get__flow_stub, bcmos_errno(bcmolt_oltid, void*));
 
-
 // Test Fixture for OltEnable
 
 // Test 1: OltEnableSuccess case
@@ -2821,9 +2820,6 @@
 
         virtual void SetUp() {
         }
-
-        virtual void TearDown() {
-        }
 };
 
 // Test 1 - DeleteGroup success case
@@ -2894,3 +2890,168 @@
     Status status = DeleteGroup_(group_id);
     ASSERT_TRUE( status.error_message() != Status::OK.error_message() );
 }
+
+////////////////////////////////////////////////////////////////////////////
+// For testing OnuLogicalDistanceZero functionality
+////////////////////////////////////////////////////////////////////////////
+class TestOnuLogicalDistanceZero : public Test {
+    protected:
+        NiceMock<BalMocker> balMock;
+        bcmolt_pon_ni pon_ni = 0;
+        openolt::OnuLogicalDistance *onu_logical_distance_zero;
+
+        virtual void SetUp() {
+            onu_logical_distance_zero = new openolt::OnuLogicalDistance;
+            onu_logical_distance_zero->set_intf_id(pon_ni);
+        }
+        virtual void TearDown() {
+        }
+};
+
+//
+// Test 1 - GetLogicalOnuDistanceZero-Get 0KM logical ONU distance success case
+//
+TEST_F(TestOnuLogicalDistanceZero, OnuLogicalDistanceZeroSuccess) {
+    bcmolt_pon_distance pon_distance = {};
+    bcmolt_pon_interface_cfg pon_cfg;
+    bcmolt_pon_interface_key key = {};
+
+    key.pon_ni = pon_ni;
+    BCMOLT_CFG_INIT(&pon_cfg, pon_interface, key);
+    state.activate();
+    bcmos_errno olt_cfg_get_pon_stub_res = BCM_ERR_OK;
+
+    EXPECT_GLOBAL_CALL(bcmolt_cfg_get__pon_intf_stub, bcmolt_cfg_get__pon_intf_stub(_, _))
+                     .WillOnce(DoAll(SetArg1ToBcmOltPonCfg(pon_cfg), Return(olt_cfg_get_pon_stub_res)));
+
+    Status status = GetLogicalOnuDistanceZero_(pon_ni, onu_logical_distance_zero);
+    ASSERT_TRUE( status.error_message() == Status::OK.error_message() );
+}
+
+// Test 2 - GetLogicalOnuDistanceZero-Get 0KM logical ONU distance failure case
+// The PON state is not ready for failure case
+//
+TEST_F(TestOnuLogicalDistanceZero, OnuLogicalDistanceZeroPonStateFailure) {
+    bcmolt_pon_distance pon_distance = {};
+    bcmolt_pon_interface_cfg pon_cfg;
+    bcmolt_pon_interface_key key = {};
+
+    key.pon_ni = pon_ni;
+    BCMOLT_CFG_INIT(&pon_cfg, pon_interface, key);
+    state.deactivate();
+    bcmos_errno olt_cfg_get_pon_stub_res = BCM_ERR_INTERNAL;
+
+    Status status = GetLogicalOnuDistanceZero_(pon_ni, onu_logical_distance_zero);
+    ASSERT_TRUE( status.error_message() != Status::OK.error_message() );
+}
+
+class TestOnuLogicalDistance : public Test {
+    protected:
+        NiceMock<BalMocker> balMock;
+        bcmolt_pon_ni pon_ni = 0;
+        bcmolt_onu_id onu_id = 1;
+        openolt::OnuLogicalDistance *onu_logical_distance;
+
+        virtual void SetUp() {
+            onu_logical_distance = new openolt::OnuLogicalDistance;
+            onu_logical_distance->set_intf_id(pon_ni);
+            onu_logical_distance->set_onu_id(onu_id);
+        }
+        virtual void TearDown() {
+        }
+};
+
+//
+// Test 1 - GetLogicalOnuDistance-Get logical ONU distance success case
+//
+TEST_F(TestOnuLogicalDistance, OnuLogicalDistanceSuccess) {
+    bcmolt_pon_distance pon_distance = {};
+    bcmolt_pon_interface_cfg pon_cfg;
+    bcmolt_pon_interface_key key = {};
+    bcmos_errno olt_cfg_get_pon_stub_res = BCM_ERR_OK;
+
+    key.pon_ni = pon_ni;
+    state.activate();
+    BCMOLT_CFG_INIT(&pon_cfg, pon_interface, key);
+
+    EXPECT_GLOBAL_CALL(bcmolt_cfg_get__pon_intf_stub, bcmolt_cfg_get__pon_intf_stub(_, _))
+                     .WillOnce(DoAll(SetArg1ToBcmOltPonCfg(pon_cfg), Return(olt_cfg_get_pon_stub_res)));
+
+    bcmolt_onu_cfg onu_cfg;
+    bcmolt_onu_key onu_key = {};
+    bcmos_errno onu_cfg_get_stub_res = BCM_ERR_OK;
+
+    onu_key.pon_ni = pon_ni;
+    onu_key.onu_id = onu_id;
+    BCMOLT_CFG_INIT(&onu_cfg, onu, onu_key);
+    onu_cfg.data.onu_state = BCMOLT_ONU_STATE_ACTIVE;
+
+    EXPECT_GLOBAL_CALL(bcmolt_cfg_get__onu_state_stub, bcmolt_cfg_get__onu_state_stub(_, _))
+                     .WillRepeatedly(DoAll(SetArg1ToBcmOltOnuCfg(onu_cfg), Return(onu_cfg_get_stub_res)));
+
+    Status status = GetLogicalOnuDistance_(pon_ni, onu_id, onu_logical_distance);
+    ASSERT_TRUE( status.error_message() == Status::OK.error_message() );
+}
+
+// Test 2 - GetLogicalOnuDistance-Get logical ONU distance failure case
+// The failure case is for retrieving ONU ranging time
+//
+TEST_F(TestOnuLogicalDistance, OnuLogicalDistanceRetrieveOnuRangingTimeFailure) {
+    bcmolt_pon_distance pon_distance = {};
+    bcmolt_pon_interface_cfg pon_cfg;
+    bcmolt_pon_interface_key key = {};
+    bcmos_errno olt_cfg_get_pon_stub_res = BCM_ERR_OK;
+
+    key.pon_ni = pon_ni;
+    state.activate();
+    BCMOLT_CFG_INIT(&pon_cfg, pon_interface, key);
+
+    EXPECT_GLOBAL_CALL(bcmolt_cfg_get__pon_intf_stub, bcmolt_cfg_get__pon_intf_stub(_, _))
+                     .WillOnce(DoAll(SetArg1ToBcmOltPonCfg(pon_cfg), Return(olt_cfg_get_pon_stub_res)));
+
+    bcmolt_onu_cfg onu_cfg;
+    bcmolt_onu_key onu_key = {};
+    bcmos_errno onu_cfg_get_stub_res = BCM_ERR_INTERNAL;
+
+    onu_key.pon_ni = pon_ni;
+    onu_key.onu_id = onu_id;
+    BCMOLT_CFG_INIT(&onu_cfg, onu, onu_key);
+
+    EXPECT_GLOBAL_CALL(bcmolt_cfg_get__onu_state_stub, bcmolt_cfg_get__onu_state_stub(_, _))
+                     .WillRepeatedly(DoAll(SetArg1ToBcmOltOnuCfg(onu_cfg), Return(onu_cfg_get_stub_res)));
+
+    Status status = GetLogicalOnuDistance_(pon_ni, onu_id, onu_logical_distance);
+    ASSERT_TRUE( status.error_message() != Status::OK.error_message() );
+}
+
+// Test 3 - GetLogicalOnuDistance-Get logical ONU distance failure case
+// The failure case is for ONU is not yet activated
+//
+TEST_F(TestOnuLogicalDistance, OnuLogicalDistanceOnuNotActivatedFailure) {
+    bcmolt_pon_distance pon_distance = {};
+    bcmolt_pon_interface_cfg pon_cfg;
+    bcmolt_pon_interface_key key = {};
+    bcmos_errno olt_cfg_get_pon_stub_res = BCM_ERR_OK;
+
+    key.pon_ni = pon_ni;
+    state.activate();
+    BCMOLT_CFG_INIT(&pon_cfg, pon_interface, key);
+
+    EXPECT_GLOBAL_CALL(bcmolt_cfg_get__pon_intf_stub, bcmolt_cfg_get__pon_intf_stub(_, _))
+                     .WillOnce(DoAll(SetArg1ToBcmOltPonCfg(pon_cfg), Return(olt_cfg_get_pon_stub_res)));
+
+    bcmolt_onu_cfg onu_cfg;
+    bcmolt_onu_key onu_key = {};
+    bcmos_errno onu_cfg_get_stub_res = BCM_ERR_OK;
+
+    onu_key.pon_ni = pon_ni;
+    onu_key.onu_id = onu_id;
+    BCMOLT_CFG_INIT(&onu_cfg, onu, onu_key);
+
+    EXPECT_GLOBAL_CALL(bcmolt_cfg_get__onu_state_stub, bcmolt_cfg_get__onu_state_stub(_, _))
+                     .WillRepeatedly(DoAll(SetArg1ToBcmOltOnuCfg(onu_cfg), Return(onu_cfg_get_stub_res)));
+
+    onu_cfg.data.onu_state = BCMOLT_ONU_STATE_INACTIVE;
+    Status status = GetLogicalOnuDistance_(pon_ni, onu_id, onu_logical_distance);
+    ASSERT_TRUE( status.error_message() != Status::OK.error_message() );
+}