"""Test interval stats conversion and interpolation (STM-6045)"""
import asyncio
from hashlib import sha256
import os
import sys
import time
import unittest
from uuid import uuid4

import google.protobuf.json_format

from preseem import UpdateIntervalManager
from preseem_protobuf.network_poller.network_poller_pb2 import NetworkElementUpdate

sys.path.append(os.path.dirname(__file__))  # let code under test load stubs

def _make_poller_hash(company_uuid, mac):
    hash_id = int(mac.replace(':', ''), 16).to_bytes(6, byteorder='big')
    return sha256(company_uuid + hash_id).digest()

class TestIntervalStats(unittest.TestCase):
    def setUp(self):
        self.loop = asyncio.new_event_loop() # needed to initialize asyncio
        asyncio.set_event_loop(self.loop)
        self.mgr = UpdateIntervalManager()

    def tearDown(self):
        self.loop.close()

    def _await(self, co):
        return self.loop.run_until_complete(co)

    def post(self, msg, asyn=False, filt=None):
        return self._await(self._post(msg, asyn, filt))

    def test_interval_stats_01(self):
        company_uuid = uuid4().bytes
        msg = NetworkElementUpdate()
        msg.time.FromSeconds(1612927766)
        msg.company_uuid = company_uuid
        msg.instance = 'test-interval-stats'
        msg.active = msg.pingable = True

        msg.data.management_ip = '192.0.2.100'
        msg.data.name = 'Test AP'
        msg.data.system_mac_address = '0a:00:01:0b:0c:0d'
        msg.data.poller_hash = _make_poller_hash(company_uuid, msg.data.system_mac_address)
        i1 = msg.data.interfaces.add()
        i1.id = '1'
        i1.name = 'WLAN'
        i1.in_octets = 10000
        i1.in_errors = 0

        # The original message is filtered out since the interval isn't complete
        new_msg = self.mgr.handle_message(msg)
        self.assertIsNone(new_msg)

        # Send the next message to get message 1
        msg2 = NetworkElementUpdate()
        msg2.CopyFrom(msg)
        msg2.time.FromSeconds(msg.time.seconds + 60)
        msg2.data.interfaces[0].in_octets = 10060
        msg2.data.interfaces[0].in_errors = 0
        new_msg = self.mgr.handle_message(msg2)
        self.assertIsNotNone(new_msg)
        self.assertFalse(new_msg.data.interfaces[0].HasField('in_octets'))
        self.assertEqual(new_msg.interval_start.seconds, 1612927740)
        self.assertEqual(new_msg.interval_end.seconds, 1612927800)

        # Send the next message to get message 2
        msg3 = NetworkElementUpdate()
        msg3.CopyFrom(msg2)
        msg3.time.FromSeconds(msg2.time.seconds + 60)
        msg3.data.interfaces[0].in_octets = 10120
        msg3.data.interfaces[0].in_errors = 0
        new_msg = self.mgr.handle_message(msg3)
        self.assertIsNotNone(new_msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 60)
        # check that counters that were 0 in both are reported as 0
        self.assertTrue(new_msg.data.interfaces[0].HasField('in_errors'))
        self.assertEqual(new_msg.data.interfaces[0].in_errors, 0)
        self.assertEqual(new_msg.interval_start.seconds, 1612927800)
        self.assertEqual(new_msg.interval_end.seconds, 1612927860)

        # The next message has an unknown/invalid value for the counter
        msg4 = NetworkElementUpdate()
        msg4.CopyFrom(msg2)
        msg4.time.FromSeconds(msg3.time.seconds + 60)
        msg4.data.interfaces[0].ClearField('in_octets')
        new_msg = self.mgr.handle_message(msg4)
        self.assertIsNotNone(new_msg)
        self.assertFalse(new_msg.data.interfaces[0].HasField('in_octets'))
        self.assertEqual(new_msg.interval_start.seconds, 1612927860)
        self.assertEqual(new_msg.interval_end.seconds, 1612927920)

        # The next message results in a counter reset.
        msg5 = NetworkElementUpdate()
        msg5.CopyFrom(msg4)
        msg5.time.FromSeconds(msg4.time.seconds + 60)
        msg5.data.interfaces[0].in_octets = 10180
        new_msg = self.mgr.handle_message(msg5)
        self.assertIsNotNone(new_msg)
        self.assertFalse(new_msg.data.interfaces[0].HasField('in_octets'))
        self.assertEqual(new_msg.interval_start.seconds, 1612927920)
        self.assertEqual(new_msg.interval_end.seconds, 1612927980)

        # The next message is correct and gets divided properly for a >60s delta
        msg6 = NetworkElementUpdate()
        msg6.CopyFrom(msg4)
        msg6.time.FromSeconds(msg5.time.seconds + 70)
        msg6.data.interfaces[0].in_octets = 10250
        new_msg = self.mgr.handle_message(msg6)
        self.assertIsNotNone(new_msg)
        self.assertFalse(new_msg.data.interfaces[0].HasField('in_octets'))
        self.assertEqual(new_msg.interval_start.seconds, 1612927980)
        self.assertEqual(new_msg.interval_end.seconds, 1612928040)

        # The next messages is a regular increment.
        msg7 = NetworkElementUpdate()
        msg7.CopyFrom(msg6)
        msg7.time.FromSeconds(msg6.time.seconds + 60)
        msg7.data.interfaces[0].in_octets = 10320
        new_msg = self.mgr.handle_message(msg7)
        self.assertIsNotNone(new_msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 64)
        self.assertEqual(new_msg.data.interfaces[0].in_errors, 0)
        self.assertEqual(new_msg.interval_start.seconds, 1612928040)
        self.assertEqual(new_msg.interval_end.seconds, 1612928100)

        # The next message doesn't increment the counter
        msg8 = NetworkElementUpdate()
        msg8.CopyFrom(msg7)
        msg8.time.FromSeconds(msg7.time.seconds + 60)
        msg8.data.interfaces[0].in_octets = 10320
        new_msg = self.mgr.handle_message(msg8)
        self.assertIsNotNone(new_msg)
        self.assertEqual(new_msg.interval_start.seconds, 1612928100)
        self.assertEqual(new_msg.interval_end.seconds, 1612928160)

        # The next message is a counter rollover
        msg9 = NetworkElementUpdate()
        msg9.CopyFrom(msg8)
        msg9.time.FromSeconds(msg8.time.seconds + 60)
        msg9.data.interfaces[0].in_octets = 10
        new_msg = self.mgr.handle_message(msg9)
        self.assertIsNotNone(new_msg)
        self.assertFalse(new_msg.data.interfaces[0].HasField('in_octets'))
        self.assertEqual(new_msg.interval_start.seconds, 1612928160)
        self.assertEqual(new_msg.interval_end.seconds, 1612928220)

        # The next message is valid
        msg10 = NetworkElementUpdate()
        msg10.CopyFrom(msg9)
        msg10.time.FromSeconds(msg9.time.seconds + 60)
        msg10.data.interfaces[0].in_octets = 130
        new_msg = self.mgr.handle_message(msg10)
        self.assertIsNotNone(new_msg)
        self.assertFalse(new_msg.data.interfaces[0].HasField('in_octets'))
        self.assertEqual(new_msg.interval_start.seconds, 1612928220)
        self.assertEqual(new_msg.interval_end.seconds, 1612928280)

        # The next message is valid
        msg11 = NetworkElementUpdate()
        msg11.CopyFrom(msg10)
        msg11.time.FromSeconds(msg10.time.seconds + 60)
        msg11.data.interfaces[0].in_octets = 250
        new_msg = self.mgr.handle_message(msg11)
        self.assertIsNotNone(new_msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 120)
        self.assertEqual(new_msg.data.interfaces[0].in_errors, 0)
        self.assertEqual(new_msg.interval_start.seconds, 1612928280)
        self.assertEqual(new_msg.interval_end.seconds, 1612928340)

        # The next message is multiple intervals
        msg12 = NetworkElementUpdate()
        msg12.CopyFrom(msg11)
        msg12.time.FromSeconds(msg11.time.seconds + 300)
        msg12.data.interfaces[0].in_octets += 600
        new_msg = self.mgr.handle_message(msg12)
        self.assertIsNotNone(new_msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 120)
        self.assertEqual(new_msg.data.interfaces[0].in_errors, 0)
        self.assertEqual(new_msg.interval_start.seconds, 1612928340)
        self.assertEqual(new_msg.interval_end.seconds, 1612928400)

        # The next message is valid
        msg13 = NetworkElementUpdate()
        msg13.CopyFrom(msg12)
        msg13.time.FromSeconds(msg12.time.seconds + 60)
        msg13.data.interfaces[0].in_octets += 120
        new_msg = self.mgr.handle_message(msg13)
        self.assertIsNotNone(new_msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 600)
        self.assertEqual(new_msg.data.interfaces[0].in_errors, 0)
        self.assertEqual(new_msg.interval_start.seconds, 1612928400)
        self.assertEqual(new_msg.interval_end.seconds, 1612928700)

        # The next message fits within the current interval
        msg14 = NetworkElementUpdate()
        msg14.CopyFrom(msg13)
        msg14.time.FromSeconds(msg13.time.seconds + 10)
        msg14.data.interfaces[0].in_octets += 20
        new_msg = self.mgr.handle_message(msg14)
        self.assertIsNone(new_msg)

        # The next message closes the interval, exactly.
        msg15 = NetworkElementUpdate()
        msg15.CopyFrom(msg14)
        msg15.time.FromSeconds(msg14.time.seconds + 14)
        msg15.data.interfaces[0].in_octets += 28
        new_msg = self.mgr.handle_message(msg15)
        self.assertIsNotNone(new_msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 120)
        self.assertEqual(new_msg.data.interfaces[0].in_errors, 0)
        self.assertEqual(new_msg.interval_start.seconds, 1612928700)
        self.assertEqual(new_msg.interval_end.seconds, 1612928760)

        # The next message is exactly one interval.
        msg16 = NetworkElementUpdate()
        msg16.CopyFrom(msg15)
        msg16.time.FromSeconds(msg15.time.seconds + 60)
        msg16.data.interfaces[0].in_octets += 83
        new_msg = self.mgr.handle_message(msg16)
        self.assertIsNotNone(new_msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 83)
        self.assertEqual(new_msg.data.interfaces[0].in_errors, 0)
        self.assertEqual(new_msg.interval_start.seconds, 1612928760)
        self.assertEqual(new_msg.interval_end.seconds, 1612928820)

        # The next message is a partial, nothing should be returned
        msg17 = NetworkElementUpdate()
        msg17.CopyFrom(msg16)
        msg17.time.FromSeconds(msg16.time.seconds + 59)
        msg17.data.interfaces[0].in_octets += 59
        new_msg = self.mgr.handle_message(msg17)
        self.assertIsNone(new_msg)

        # The next message completes that interval
        msg18 = NetworkElementUpdate()
        msg18.CopyFrom(msg17)
        msg18.time.FromSeconds(msg17.time.seconds + 60)
        msg18.data.interfaces[0].in_octets += 120
        new_msg = self.mgr.handle_message(msg18)
        self.assertIsNotNone(new_msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 61)
        self.assertEqual(new_msg.data.interfaces[0].in_errors, 0)
        self.assertEqual(new_msg.interval_start.seconds, 1612928820)
        self.assertEqual(new_msg.interval_end.seconds, 1612928880)

    def test_interval_stats_02(self):
        """Do a basic tests for all counter fields."""
        # Use two intervals of a real captured message to test this.
        with open('test/data/test_interval_stats_02_msg1.json') as f:
            json1 = f.read()
        with open('test/data/test_interval_stats_02_msg2.json') as f:
            json2 = f.read()
        with open('test/data/test_interval_stats_02_msg3.json') as f:
            json3 = f.read()
        with open('test/data/test_interval_stats_02_msg.json') as f:
            expected_result = f.read()
        msg1 = NetworkElementUpdate()
        google.protobuf.json_format.Parse(json1, msg1)
        msg2 = NetworkElementUpdate()
        google.protobuf.json_format.Parse(json2, msg2)
        msg3 = NetworkElementUpdate()
        google.protobuf.json_format.Parse(json3, msg3)
        expected_msg = NetworkElementUpdate()
        google.protobuf.json_format.Parse(expected_result, expected_msg)

        msg = self.mgr.handle_message(msg1)
        self.assertIsNone(msg)
        msg = self.mgr.handle_message(msg2)
        self.assertIsNotNone(msg)
        # The first one is all null's for the reset.
        msg = self.mgr.handle_message(msg3)
        self.assertIsNotNone(msg)
        #print(google.protobuf.json_format.MessageToJson(msg, preserving_proto_field_name=True))
        self.assertEqual(expected_msg, msg)

    def test_interval_stats_02_modules(self):
        """Do a basic tests for all counter fields for mesgs with modules."""
        # Use two intervals of a real captured message to test this.
        with open('test/data/m1_1_time20_32.json') as f:
            json1 = f.read()

        with open('test/data/m1_2_time20_36.json') as f:
            json2 = f.read()

        with open('test/data/m1_3_time20_39.json') as f:
            json3 = f.read()

        with open('test/data/m1_interval_time20_35.json') as f:
            expected_result = f.read()

        msg1 = NetworkElementUpdate()
        google.protobuf.json_format.Parse(json1, msg1)
        msg2 = NetworkElementUpdate()
        google.protobuf.json_format.Parse(json2, msg2)

        msg3 = NetworkElementUpdate()
        google.protobuf.json_format.Parse(json3, msg3)

        expected_msg = NetworkElementUpdate()
        google.protobuf.json_format.Parse(expected_result, expected_msg)

        msg = self.mgr.handle_message(msg1)
        self.assertIsNone(msg)
        msg = self.mgr.handle_message(msg2)
        self.assertIsNotNone(msg)
        # The first one is all null's for the reset.
        msg = self.mgr.handle_message(msg3)
        self.assertIsNotNone(msg)
        #print(google.protobuf.json_format.MessageToJson(msg, preserving_proto_field_name=True))
        self.assertEqual(expected_msg, msg)

    def test_interval_stats_03(self):
        """Test that an incomplete messages gets timed out.
           What do we do with a partial interval that's never completed?"""
        company_uuid = uuid4().bytes
        msg = NetworkElementUpdate()
        msg.time.FromSeconds(1612927766)
        msg.company_uuid = company_uuid
        msg.instance = 'test-interval-stats-3'
        msg.active = msg.pingable = True

        # Send three messages to get a message with a valid interval
        msg.data.management_ip = '192.0.2.100'
        msg.data.name = 'Test AP'
        msg.data.system_mac_address = '0a:00:01:0b:0c:0d'
        msg.data.poller_hash = _make_poller_hash(company_uuid, msg.data.system_mac_address)
        i1 = msg.data.interfaces.add()
        i1.id = '1'
        i1.name = 'WLAN'
        i1.out_octets = 0
        new_msg = self.mgr.handle_message(msg)
        self.assertIsNone(new_msg)

        msg2 = NetworkElementUpdate()
        msg2.CopyFrom(msg)
        msg2.time.FromSeconds(msg.time.seconds + 60)
        msg2.data.interfaces[0].out_octets += 60
        new_msg = self.mgr.handle_message(msg2)
        self.assertIsNotNone(new_msg)
        self.assertFalse(new_msg.data.interfaces[0].HasField('out_octets'))

        msg3 = NetworkElementUpdate()
        msg3.CopyFrom(msg2)
        msg3.time.FromSeconds(msg2.time.seconds + 60)
        msg3.data.interfaces[0].out_octets += 60
        new_msg = self.mgr.handle_message(msg3)
        self.assertIsNotNone(new_msg)
        self.assertEqual(new_msg.data.interfaces[0].out_octets, 60)

        # Make sure the message doesn't expire if it's outside the expire time
        self.mgr.timeout_messages(0.1)
        msg4 = NetworkElementUpdate()
        msg4.CopyFrom(msg3)
        msg4.time.FromSeconds(msg3.time.seconds + 60)
        msg4.data.interfaces[0].out_octets += 60
        new_msg = self.mgr.handle_message(msg4)
        self.assertIsNotNone(new_msg)
        self.assertEqual(new_msg.data.interfaces[0].out_octets, 60)

        # Expire the message
        time.sleep(0.2)
        self.mgr.timeout_messages(0.1)
        msg5 = NetworkElementUpdate()
        msg5.CopyFrom(msg4)
        msg5.time.FromSeconds(msg4.time.seconds + 60)
        msg5.data.interfaces[0].out_octets += 60
        new_msg = self.mgr.handle_message(msg5)
        self.assertIsNone(new_msg)

    def test_interval_stats_04(self):
        """Test collisions between two elements, and that an id_key resolves it."""
        company_uuid = uuid4().bytes
        msg = NetworkElementUpdate()
        msg.time.FromSeconds(1612927766)
        msg.company_uuid = company_uuid
        msg.instance = 'test-interval-stats-4'
        msg.active = msg.pingable = True
        msg.data.management_ip = '192.0.2.100'
        msg.data.name = 'Test AP'
        msg.data.system_mac_address = '0a:00:01:0b:0c:0d'
        msg.data.poller_hash = _make_poller_hash(company_uuid, msg.data.system_mac_address)
        i1 = msg.data.interfaces.add()
        i1.id = '1'
        i1.name = 'WLAN'
        i1.out_octets = 0
        new_msg = self.mgr.handle_message(msg, 'idA')
        self.assertIsNone(new_msg)

        msg2 = NetworkElementUpdate()
        msg2.CopyFrom(msg)
        msg2.time.FromSeconds(msg.time.seconds + 60)
        msg2.data.interfaces[0].out_octets += 60
        new_msg = self.mgr.handle_message(msg2, 'idA')
        self.assertIsNotNone(new_msg)
        self.assertFalse(new_msg.data.interfaces[0].HasField('out_octets'))

        # A message from another element.  Change interface name to test that
        # code path.
        msg3 = NetworkElementUpdate()
        msg3.CopyFrom(msg2)
        msg3.time.FromSeconds(msg2.time.seconds + 60)
        msg3.data.interfaces[0].out_octets = 1000
        msg3.data.interfaces[0].name = 'WLAN 1'
        new_msg = self.mgr.handle_message(msg3, 'idB')
        self.assertIsNone(new_msg)

        # Send another message for the first one to make sure its independent
        msg3 = NetworkElementUpdate()
        msg3.CopyFrom(msg2)
        msg3.time.FromSeconds(msg2.time.seconds + 60)
        msg3.data.interfaces[0].out_octets += 60
        new_msg = self.mgr.handle_message(msg3, 'idA')
        self.assertIsNotNone(new_msg)
        self.assertEqual(new_msg.data.interfaces[0].out_octets, 60)

    def test_interval_stats_05(self):
        """A message occures within the same minute as the previous message,
           with a new station/link."""
        company_uuid = uuid4().bytes
        msg = NetworkElementUpdate()
        msg.time.FromSeconds(1612927766)
        msg.company_uuid = company_uuid
        msg.instance = 'test-interval-stats-4'
        msg.active = msg.pingable = True
        msg.data.management_ip = '192.0.2.100'
        msg.data.name = 'Test AP'
        msg.data.system_mac_address = '0a:00:01:0b:0c:0d'
        msg.data.poller_hash = _make_poller_hash(company_uuid, msg.data.system_mac_address)
        i1 = msg.data.interfaces.add()
        i1.id = '1'
        i1.name = 'WLAN'
        i1.out_octets = 0
        l1 = i1.links.add()
        l1.mac_address = '0b:00:01:00:00:01'
        l1.poller_hash = _make_poller_hash(company_uuid, l1.mac_address)
        l1.in_octets = 1000
        new_msg = self.mgr.handle_message(msg, 'idA')
        self.assertIsNone(new_msg)

        msg2 = NetworkElementUpdate()
        msg2.CopyFrom(msg)
        msg2.time.FromSeconds(msg.time.seconds + 60)
        msg2.data.interfaces[0].out_octets += 60
        msg2.data.interfaces[0].links[0].in_octets += 60
        new_msg = self.mgr.handle_message(msg2, 'idA')
        self.assertIsNotNone(new_msg)
        self.assertFalse(new_msg.data.interfaces[0].HasField('out_octets'))
        self.assertFalse(new_msg.data.interfaces[0].links[0].HasField('out_octets'))

        msg3 = NetworkElementUpdate()
        msg3.CopyFrom(msg2)
        msg3.time.FromSeconds(msg2.time.seconds + 60)
        msg3.data.interfaces[0].out_octets += 60
        msg3.data.interfaces[0].links[0].in_octets += 60
        new_msg = self.mgr.handle_message(msg3, 'idA')
        self.assertEqual(new_msg.data.interfaces[0].out_octets, 60)
        self.assertEqual(new_msg.data.interfaces[0].links[0].in_octets, 60)

        # update within the same minute.  The way the code works now, it will
        # not add the new station until the next message.  This is an edge case.
        msg4 = NetworkElementUpdate()
        msg4.CopyFrom(msg3)
        msg4.time.FromSeconds(msg3.time.seconds + 30)
        msg4.data.interfaces[0].out_octets += 60
        msg4.data.interfaces[0].links[0].in_octets += 30
        l2 = msg4.data.interfaces[0].links.add()
        l2.mac_address = '0b:00:01:00:00:02'
        l2.poller_hash = _make_poller_hash(company_uuid, l2.mac_address)
        l2.in_octets = 1000
        new_msg = self.mgr.handle_message(msg4, 'idA')
        self.assertIsNone(new_msg)

        # back to 60s cadence
        msg5 = NetworkElementUpdate()
        msg5.CopyFrom(msg4)
        msg5.time.FromSeconds(msg4.time.seconds + 60)
        msg5.data.interfaces[0].out_octets += 120
        msg5.data.interfaces[0].links[0].in_octets += 60
        msg5.data.interfaces[0].links[1].in_octets += 60
        new_msg = self.mgr.handle_message(msg5, 'idA')
        self.assertEqual(len(new_msg.data.interfaces), 1)  # no new link yet
        self.assertEqual(new_msg.data.interfaces[0].out_octets, 94)
        self.assertEqual(new_msg.data.interfaces[0].links[0].in_octets, 60)

        msg6 = NetworkElementUpdate()
        msg6.CopyFrom(msg5)
        msg6.time.FromSeconds(msg5.time.seconds + 60)
        msg6.data.interfaces[0].out_octets += 120
        msg6.data.interfaces[0].links[0].in_octets += 60
        msg6.data.interfaces[0].links[1].in_octets += 60
        new_msg = self.mgr.handle_message(msg6, 'idA')
        self.assertEqual(new_msg.data.interfaces[0].out_octets, 120)
        self.assertEqual(new_msg.data.interfaces[0].links[0].in_octets, 60)
        self.assertEqual(new_msg.data.interfaces[0].links[1].in_octets, 60)

    def test_interval_stats_06(self):
        """A message has an interface not present in the previous message."""
        company_uuid = uuid4().bytes
        msg = NetworkElementUpdate()
        msg.time.FromSeconds(1612927766)
        msg.company_uuid = company_uuid
        msg.instance = 'test-interval-stats'
        msg.active = msg.pingable = True

        msg.data.management_ip = '192.0.2.100'
        msg.data.name = 'Test AP'
        msg.data.system_mac_address = '0a:00:01:0b:0c:0d'
        msg.data.poller_hash = _make_poller_hash(company_uuid, msg.data.system_mac_address)
        i1 = msg.data.interfaces.add()
        i1.id = '1'
        i1.name = 'WLAN'
        i1.poller_hash = _make_poller_hash(company_uuid, i1.id)
        i1.in_octets = 10000
        i1.in_errors = 0

        # The original message is filtered out since the interval isn't complete
        new_msg = self.mgr.handle_message(msg)
        self.assertIsNone(new_msg)

        # Send the next message to get message 1
        msg2 = NetworkElementUpdate()
        msg2.CopyFrom(msg)
        msg2.time.FromSeconds(msg.time.seconds + 60)
        msg2.data.interfaces[0].in_octets = 10060
        msg2.data.interfaces[0].in_errors = 0
        new_msg = self.mgr.handle_message(msg2)
        self.assertIsNotNone(new_msg)
        self.assertFalse(new_msg.data.interfaces[0].HasField('in_octets'))
        self.assertEqual(new_msg.interval_start.seconds, 1612927740)
        self.assertEqual(new_msg.interval_end.seconds, 1612927800)

        # Send the next message to get message 2
        msg3 = NetworkElementUpdate()
        msg3.CopyFrom(msg2)
        msg3.time.FromSeconds(msg2.time.seconds + 60)
        msg3.data.interfaces[0].in_octets = 10120
        msg3.data.interfaces[0].in_errors = 0
        new_msg = self.mgr.handle_message(msg3)
        self.assertIsNotNone(new_msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 60)
        # check that counters that were 0 in both are reported as 0
        self.assertTrue(new_msg.data.interfaces[0].HasField('in_errors'))
        self.assertEqual(new_msg.data.interfaces[0].in_errors, 0)
        self.assertEqual(new_msg.interval_start.seconds, 1612927800)
        self.assertEqual(new_msg.interval_end.seconds, 1612927860)

        # Message 3 has a new interface
        msg4 = NetworkElementUpdate()
        msg4.CopyFrom(msg3)
        msg4.time.FromSeconds(msg3.time.seconds + 60)
        msg4.data.interfaces[0].in_octets = 10180
        msg4.data.interfaces[0].in_errors = 0
        i2 = msg4.data.interfaces.add()
        i2.id = '2'
        i2.name = 'LAN1'
        i2.poller_hash = _make_poller_hash(company_uuid, i2.id)
        i2.in_octets = 10000
        i2.in_errors = 0
        new_msg = self.mgr.handle_message(msg4)
        self.assertIsNotNone(new_msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 60)

        # Message 4 should send the new port with null counters
        msg5 = NetworkElementUpdate()
        msg5.CopyFrom(msg4)
        msg5.time.FromSeconds(msg4.time.seconds + 60)
        msg5.data.interfaces[0].in_octets = 10240
        msg5.data.interfaces[0].in_errors = 0
        msg5.data.interfaces[1].in_octets = 10120
        msg5.data.interfaces[1].in_errors = 0
        new_msg = self.mgr.handle_message(msg5)
        self.assertIsNotNone(new_msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 60)

        # Message 5 should send the new port with interval counters
        msg6 = NetworkElementUpdate()
        msg6.CopyFrom(msg5)
        msg6.time.FromSeconds(msg5.time.seconds + 60)
        msg6.data.interfaces[0].in_octets = 10300
        msg6.data.interfaces[0].in_errors = 0
        msg6.data.interfaces[1].in_octets = 10240
        msg6.data.interfaces[1].in_errors = 0
        new_msg = self.mgr.handle_message(msg6)
        self.assertIsNotNone(new_msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 60)

    def test_interval_stats_07(self):
        """STM-6177 intervals handle a one-off zero-valued counter gracefully."""
        company_uuid = uuid4().bytes
        msg = NetworkElementUpdate()
        msg.company_uuid = company_uuid
        msg.instance = 'test-interval-stats'
        msg.active = msg.pingable = True
        msg.data.management_ip = "10.16.19.2"
        msg.data.name = 'br-merom_segrain.water.gville'
        msg.data.system_mac_address = 'bc:e6:7c:20:09:0b'
        msg.data.poller_hash = _make_poller_hash(company_uuid, msg.data.system_mac_address)
        i1 = msg.data.interfaces.add()
        i1.mac_address = 'bc:e6:7c:20:09:0b'
        tmsg = msg

        msg.time.FromSeconds(1638374073) # 15:54:33
        i1.in_octets = 417705082908
        new_msg = self.mgr.handle_message(msg)
        self.assertIsNone(new_msg)

        msg = NetworkElementUpdate()
        msg.CopyFrom(tmsg)
        msg.time.FromSeconds(1638374193) # 15:56:33
        i1 = msg.data.interfaces[0]
        i1.in_octets = 417729627827
        new_msg = self.mgr.handle_message(msg)
        self.assertIsNotNone(new_msg)
        self.assertFalse(new_msg.data.interfaces[0].HasField('in_octets'))

        msg = NetworkElementUpdate()
        msg.CopyFrom(tmsg)
        msg.time.FromSeconds(1638374373) # 15:59:33
        i1 = msg.data.interfaces[0]
        i1.in_octets = 417744391351
        new_msg = self.mgr.handle_message(msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 21236841)

        # Send a 0.  We expect a null counter from the interval we completed,
        # because we don't know how many bytes would _really_ have been in this
        # message.
        msg = NetworkElementUpdate()
        msg.CopyFrom(tmsg)
        msg.time.FromSeconds(1638374493) # 16:01:33
        i1 = msg.data.interfaces[0]
        i1.in_octets = 0
        new_msg = self.mgr.handle_message(msg)
        self.assertFalse(new_msg.data.interfaces[0].HasField('in_octets'))

        # Continue the counter.  We do NOT want a counter reset in this case,
        # that would end up putting the entire counter value into an interval
        # bucket which is wrong in this "transient error" case where we got
        # a zero back but the counter did not, in fact, reset.
        msg = NetworkElementUpdate()
        msg.CopyFrom(tmsg)
        msg.time.FromSeconds(1638374613) # 16:03:33
        i1 = msg.data.interfaces[0]
        i1.in_octets = 417790449325
        new_msg = self.mgr.handle_message(msg)

        msg = NetworkElementUpdate()
        msg.CopyFrom(tmsg)
        msg.time.FromSeconds(1638374733) # 16:05:33
        i1 = msg.data.interfaces[0]
        i1.in_octets = 417800474800
        new_msg = self.mgr.handle_message(msg)

        msg = NetworkElementUpdate()
        msg.CopyFrom(tmsg)
        msg.time.FromSeconds(1638374853) # 16:07:33
        i1 = msg.data.interfaces[0]
        i1.in_octets = 417801806432
        new_msg = self.mgr.handle_message(msg)

    def test_interval_stats_08(self):
        """PPA-1575 changes in counter source are handled correctly."""
        company_uuid = uuid4().bytes
        msg = NetworkElementUpdate()
        msg.company_uuid = company_uuid
        msg.instance = 'test-interval-stats'
        msg.active = msg.pingable = True
        msg.data.management_ip = "10.16.19.2"
        msg.data.name = 'br-merom_segrain.water.gville'
        msg.data.system_mac_address = 'bc:e6:7c:20:09:0b'
        msg.data.poller_hash = _make_poller_hash(company_uuid, msg.data.system_mac_address)
        msg.data.counter_source = 'source1'
        i1 = msg.data.interfaces.add()
        i1.mac_address = 'bc:e6:7c:20:09:0b'
        tmsg = msg

        msg.time.FromSeconds(1638374073) # 15:54:33
        i1.in_octets = 417705082908
        new_msg = self.mgr.handle_message(msg)
        self.assertIsNone(new_msg)

        msg = NetworkElementUpdate()
        msg.CopyFrom(tmsg)
        msg.time.FromSeconds(msg.time.seconds + 60)
        i1 = msg.data.interfaces[0]
        i1.in_octets += 60000
        new_msg = self.mgr.handle_message(msg)
        self.assertIsNotNone(new_msg)
        self.assertFalse(new_msg.data.interfaces[0].HasField('in_octets'))

        msg = NetworkElementUpdate()
        msg.CopyFrom(tmsg)
        msg.time.FromSeconds(msg.time.seconds + 120)
        i1 = msg.data.interfaces[0]
        i1.in_octets += 180000
        new_msg = self.mgr.handle_message(msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 87000)

        # Now that we have the state going, send a message with a different counter
        # source and verify that we get no counters for the overlapping intervals.
        msg = NetworkElementUpdate()
        msg.CopyFrom(tmsg)
        msg.data.counter_source = 'source2'
        msg.time.FromSeconds(msg.time.seconds + 180)
        i1 = msg.data.interfaces[0]
        i1.in_octets += 240000
        new_msg = self.mgr.handle_message(msg)
        self.assertFalse(new_msg.data.interfaces[0].HasField('in_octets'))

        msg = NetworkElementUpdate()
        msg.CopyFrom(tmsg)
        msg.data.counter_source = 'source2'
        msg.time.FromSeconds(msg.time.seconds + 240)
        i1 = msg.data.interfaces[0]
        i1.in_octets += 300000
        new_msg = self.mgr.handle_message(msg)
        self.assertFalse(new_msg.data.interfaces[0].HasField('in_octets'))

        msg = NetworkElementUpdate()
        msg.CopyFrom(tmsg)
        msg.data.counter_source = 'source2'
        msg.time.FromSeconds(msg.time.seconds + 300)
        i1 = msg.data.interfaces[0]
        i1.in_octets += 360000
        new_msg = self.mgr.handle_message(msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 60000)

    def test_interval_stats_09(self):
        """PPA-1575 changes in peer counter source are handled correctly."""
        company_uuid = uuid4().bytes
        msg = NetworkElementUpdate()
        msg.company_uuid = company_uuid
        msg.instance = 'test-interval-stats'
        msg.active = msg.pingable = True
        msg.data.management_ip = "10.16.19.2"
        msg.data.name = 'br-merom_segrain.water.gville'
        msg.data.system_mac_address = 'bc:e6:7c:20:09:0b'
        msg.data.poller_hash = _make_poller_hash(company_uuid, msg.data.system_mac_address)
        i1 = msg.data.interfaces.add()
        i1.name = 'ap_wlan'
        i1.mac_address = 'bc:e6:7c:20:09:0c'
        i1.poller_hash = _make_poller_hash(company_uuid, 'bc:e6:7c:20:09:0c')
        peer = msg.peers.add()
        peer.poller_hash = _make_poller_hash(company_uuid, '00:01:02:03:04:05')
        peer.counter_source = 'source1'
        i2 = peer.interfaces.add()
        i2.name = 'sta_wlan'
        i2.poller_hash = _make_poller_hash(company_uuid, '00:01:02:03:04:06')
        l1 = i1.links.add()
        l1.poller_hash = i2.poller_hash
        l2 = i2.links.add()
        l2.poller_hash = i1.poller_hash

        tmsg = msg

        msg.time.FromSeconds(1638374073) # 15:54:33
        i1.in_octets = 100000
        i2.in_octets = 200000
        l1.in_octets = l2.out_octets = i1.in_octets
        l2.in_octets = l1.out_octets = i2.in_octets
        new_msg = self.mgr.handle_message(msg)
        self.assertIsNone(new_msg)

        msg = NetworkElementUpdate()
        msg.CopyFrom(tmsg)
        msg.time.FromSeconds(msg.time.seconds + 60)
        i1 = msg.data.interfaces[0]
        i1.in_octets += 60000
        i2 = msg.peers[0].interfaces[0]
        i2.in_octets += 60000
        l1 = msg.data.interfaces[0].links[0]
        l2 = msg.peers[0].interfaces[0].links[0]
        l1.in_octets = l2.out_octets = i1.in_octets
        l2.in_octets = l1.out_octets = i2.in_octets
        new_msg = self.mgr.handle_message(msg)
        self.assertIsNotNone(new_msg)
        self.assertFalse(new_msg.data.interfaces[0].HasField('in_octets'))
        self.assertFalse(new_msg.data.interfaces[0].links[0].HasField('in_octets'))
        self.assertFalse(new_msg.data.interfaces[0].links[0].HasField('out_octets'))
        self.assertFalse(new_msg.peers[0].interfaces[0].HasField('in_octets'))
        self.assertFalse(new_msg.peers[0].interfaces[0].links[0].HasField('in_octets'))
        self.assertFalse(new_msg.peers[0].interfaces[0].links[0].HasField('out_octets'))

        msg = NetworkElementUpdate()
        msg.CopyFrom(tmsg)
        msg.time.FromSeconds(msg.time.seconds + 120)
        i1 = msg.data.interfaces[0]
        i1.in_octets += 120000
        i2 = msg.peers[0].interfaces[0]
        i2.in_octets += 120000
        l1 = msg.data.interfaces[0].links[0]
        l2 = msg.peers[0].interfaces[0].links[0]
        l1.in_octets = l2.out_octets = i1.in_octets
        l2.in_octets = l1.out_octets = i2.in_octets
        new_msg = self.mgr.handle_message(msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 60000)
        self.assertEqual(new_msg.data.interfaces[0].links[0].in_octets, 60000)
        self.assertEqual(new_msg.data.interfaces[0].links[0].out_octets, 60000)
        self.assertEqual(new_msg.peers[0].interfaces[0].in_octets, 60000)
        self.assertEqual(new_msg.peers[0].interfaces[0].links[0].in_octets, 60000)
        self.assertEqual(new_msg.peers[0].interfaces[0].links[0].out_octets, 60000)

        # Now that we have the state going, send a message with a different counter
        # source and verify that we get no counters for the overlapping intervals.
        msg = NetworkElementUpdate()
        msg.CopyFrom(tmsg)
        msg.peers[0].counter_source = 'source2'
        msg.time.FromSeconds(msg.time.seconds + 180)
        i1 = msg.data.interfaces[0]
        i1.in_octets += 180000
        i2 = msg.peers[0].interfaces[0]
        i2.in_octets += 180000
        l1 = msg.data.interfaces[0].links[0]
        l2 = msg.peers[0].interfaces[0].links[0]
        l1.in_octets = l2.out_octets = i1.in_octets
        l2.in_octets = l1.out_octets = i2.in_octets
        new_msg = self.mgr.handle_message(msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 60000)
        self.assertFalse(new_msg.data.interfaces[0].links[0].HasField('in_octets'))
        self.assertFalse(new_msg.data.interfaces[0].links[0].HasField('out_octets'))
        self.assertFalse(new_msg.peers[0].interfaces[0].HasField('in_octets'))
        self.assertFalse(new_msg.peers[0].interfaces[0].links[0].HasField('in_octets'))
        self.assertFalse(new_msg.peers[0].interfaces[0].links[0].HasField('out_octets'))

        msg = NetworkElementUpdate()
        msg.CopyFrom(tmsg)
        msg.peers[0].counter_source = 'source2'
        msg.time.FromSeconds(msg.time.seconds + 240)
        i1 = msg.data.interfaces[0]
        i1.in_octets += 240000
        i2 = msg.peers[0].interfaces[0]
        i2.in_octets += 240000
        l1 = msg.data.interfaces[0].links[0]
        l2 = msg.peers[0].interfaces[0].links[0]
        l1.in_octets = l2.out_octets = i1.in_octets
        l2.in_octets = l1.out_octets = i2.in_octets
        new_msg = self.mgr.handle_message(msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 60000)
        self.assertFalse(new_msg.data.interfaces[0].links[0].HasField('in_octets'))
        self.assertFalse(new_msg.data.interfaces[0].links[0].HasField('out_octets'))
        self.assertFalse(new_msg.peers[0].interfaces[0].HasField('in_octets'))
        self.assertFalse(new_msg.peers[0].interfaces[0].links[0].HasField('in_octets'))
        self.assertFalse(new_msg.peers[0].interfaces[0].links[0].HasField('out_octets'))

        msg = NetworkElementUpdate()
        msg.CopyFrom(tmsg)
        msg.peers[0].counter_source = 'source2'
        msg.time.FromSeconds(msg.time.seconds + 300)
        i1 = msg.data.interfaces[0]
        i1.in_octets += 300000
        i2 = msg.peers[0].interfaces[0]
        i2.in_octets += 300000
        l1 = msg.data.interfaces[0].links[0]
        l2 = msg.peers[0].interfaces[0].links[0]
        l1.in_octets = l2.out_octets = i1.in_octets
        l2.in_octets = l1.out_octets = i2.in_octets
        new_msg = self.mgr.handle_message(msg)
        self.assertEqual(new_msg.data.interfaces[0].in_octets, 60000)
        self.assertEqual(new_msg.data.interfaces[0].links[0].in_octets, 60000)
        self.assertEqual(new_msg.data.interfaces[0].links[0].out_octets, 60000)
        self.assertEqual(new_msg.peers[0].interfaces[0].in_octets, 60000)
        self.assertEqual(new_msg.peers[0].interfaces[0].links[0].in_octets, 60000)
        self.assertEqual(new_msg.peers[0].interfaces[0].links[0].out_octets, 60000)
