Ejemplo n.º 1
0
def test_broadcast_message(faker):
    for i in range(1000):
        # Create Messages with random `screen` value in the non-broadcast range
        # and ensure the `is_broadcast` field is correct in both the
        # instance and its parsed version
        screen = faker.pyint(max_value=0xfffe)
        msgobj = Message(screen=screen)
        msgobj.displays.append(Display(index=i))
        assert not msgobj.is_broadcast

        parsed, remaining = Message.parse(msgobj.build_message())
        assert not msgobj.is_broadcast

    # Create broadcast Messages using both methods and check the `is_broadcast`
    # field on the instances and their parsed versions
    msgobj1 = Message(screen=0xffff)
    msgobj1.displays.append(Display(index=1))
    assert msgobj1.is_broadcast
    parsed1, remaining = Message.parse(msgobj1.build_message())
    assert parsed1.is_broadcast

    msgobj2 = Message.broadcast(displays=[Display(index=1)])
    assert msgobj2.is_broadcast
    parsed2, remaining = Message.parse(msgobj2.build_message())
    assert parsed2.is_broadcast

    assert msgobj1 == msgobj2 == parsed1 == parsed2
Ejemplo n.º 2
0
def test_invalid_dmsg(uhs500_msg_bytes, faker):

    # Clip the dmsg header fields
    bad_bytes = bytearray(uhs500_msg_bytes[:8])

    # Insert the correct value for `PBC` field so it gets past initial checks
    bad_byte_count = struct.pack('<H', len(bad_bytes) - 2)
    bad_bytes[:2] = bad_byte_count
    bad_bytes = bytes(bad_bytes)
    with pytest.raises(DmsgParseError) as excinfo:
        r = Message.parse(bad_bytes)
    assert 'dmsg length' in str(excinfo.value).lower()

    # Clip the display text length field to the wrong size
    bad_bytes = bytearray(uhs500_msg_bytes[:10])

    # Insert the correct value for `PBC` field so it gets past initial checks
    bad_byte_count = struct.pack('<H', len(bad_bytes) - 2)
    bad_bytes[:2] = bad_byte_count
    bad_bytes = bytes(bad_bytes)
    with pytest.raises(DmsgParseError) as excinfo:
        r = Message.parse(bad_bytes)
    assert 'text length' in str(excinfo.value).lower()

    # Insert an incorrect value for the text length field
    bad_bytes = bytearray(uhs500_msg_bytes)
    txt_len_bytes = struct.pack('<H', len(uhs500_msg_bytes) + 10)
    bad_bytes[10:12] = txt_len_bytes
    bad_bytes = bytes(bad_bytes)
    with pytest.raises(DmsgParseError) as excinfo:
        r = Message.parse(bad_bytes)
    assert 'invalid text bytes' in str(excinfo.value).lower()
Ejemplo n.º 3
0
async def test_broadcast_display(uhs500_msg_bytes, uhs500_msg_parsed,
                                 udp_endpoint, udp_port, faker):
    transport, protocol, endpoint_port = udp_endpoint
    assert udp_port != endpoint_port

    loop = asyncio.get_event_loop()

    receiver = UmdReceiver(hostaddr='127.0.0.1', hostport=udp_port)

    evt_listener = EventListener()
    receiver.bind_async(loop, on_tally_added=evt_listener.callback)

    displays_by_index = {
        disp.index: disp
        for disp in uhs500_msg_parsed.displays
    }

    async with receiver:
        # Populate the receiver's tallies and wait for them to be added
        transport.sendto(uhs500_msg_bytes, ('127.0.0.1', udp_port))
        _ = await evt_listener.get()
        while not evt_listener.empty():
            _ = await evt_listener.get()

        assert len(receiver.tallies) == len(uhs500_msg_parsed.displays)

        receiver.unbind(evt_listener)
        receiver.bind_async(loop, on_tally_updated=evt_listener.callback)

        # Send a broadcast display message for each TallyColor with a random brightness
        # The control field is set so the Tally text field is unchanged
        for color in TallyColor:
            brightness = faker.pyint(max_value=3)
            bc_disp = Display.broadcast(
                control=b'foo',
                rh_tally=color,
                lh_tally=color,
                txt_tally=color,
                brightness=brightness,
            )
            msg = Message(displays=[bc_disp])
            transport.sendto(msg.build_message(), ('127.0.0.1', udp_port))

            _ = await evt_listener.get()
            while not evt_listener.empty():
                _ = await evt_listener.get()

            # Check each of the receiver's tallies against the bc_disp values
            # and make sure the text didn't change
            for tally in receiver.tallies.values():
                assert tally.index < 0xffff
                assert not tally.is_broadcast
                assert tally.rh_tally == color
                assert tally.txt_tally == color
                assert tally.lh_tally == color
                assert tally.control == bc_disp.control
                assert tally.brightness == brightness
                assert tally.text == displays_by_index[tally.index].text
Ejemplo n.º 4
0
def test_invalid_message(uhs500_msg_bytes, faker):
    bad_bytes = faker.binary(length=5)
    with pytest.raises(MessageParseError) as excinfo:
        r = Message.parse(bad_bytes)
    assert 'header' in str(excinfo.value).lower()

    bad_bytes = bytearray(uhs500_msg_bytes)
    bad_byte_count = struct.pack('<H', len(uhs500_msg_bytes) + 10)
    bad_bytes[:2] = bad_byte_count
    bad_bytes = bytes(bad_bytes)
    with pytest.raises(MessageParseError) as excinfo:
        r = Message.parse(bad_bytes)
    assert 'byte count' in str(excinfo.value).lower()
Ejemplo n.º 5
0
def test_dmsg_control(uhs500_msg_parsed, faker):
    tested_zero = False
    for _ in range(10):
        msgobj = Message(version=1, screen=5)
        for orig_disp in uhs500_msg_parsed.displays:
            if not tested_zero:
                data_len = 0
                tested_zero = True
            else:
                data_len = faker.pyint(min_value=0, max_value=1024)
            control_data = faker.binary(length=data_len)

            kw = orig_disp.to_dict()
            del kw['text']
            kw['control'] = control_data
            if not len(control_data):
                kw['type'] = MessageType.control
            disp = Display(**kw)

            assert disp.type == MessageType.control

            disp_bytes = disp.to_dmsg(msgobj.flags)
            parsed_disp, remaining = Display.from_dmsg(msgobj.flags,
                                                       disp_bytes)

            assert not len(remaining)
            assert parsed_disp.control == control_data
            assert parsed_disp == disp

            msgobj.displays.append(disp)

        parsed = None
        for msg_bytes in msgobj.build_messages():
            _parsed, remaining = Message.parse(msg_bytes)
            assert not len(remaining)
            if parsed is None:
                parsed = _parsed
            else:
                parsed.displays.extend(_parsed.displays)
        assert parsed == msgobj

        with pytest.raises(ValueError) as excinfo:
            disp = Display(index=1, control=b'foo', text='foo')
        excstr = str(excinfo.value).lower()
        assert 'control' in excstr and 'text' in excstr

        with pytest.raises(ValueError) as excinfo:
            disp = Display(index=1, text='foo', type=MessageType.control)
        excstr = str(excinfo.value).lower()
        assert 'control' in excstr and 'text' in excstr
Ejemplo n.º 6
0
def test_broadcast_display(uhs500_msg_parsed, faker):

    disp_attrs = ('rh_tally', 'txt_tally', 'lh_tally', 'text', 'brightness')
    msgobj = Message()

    for uhs_disp in uhs500_msg_parsed.displays:
        assert not uhs_disp.is_broadcast

        # General kwargs excluding the `index`
        kw = {attr: getattr(uhs_disp, attr) for attr in disp_attrs}

        # Create random Displays within non-broadcast range and check the
        # `is_broadcast` field of the instance and its parsed version
        for _ in range(1000):
            ix = faker.pyint(max_value=0xfffe)
            disp = Display(index=ix, **kw)
            assert not disp.is_broadcast

            parsed, remaining = Display.from_dmsg(msgobj.flags,
                                                  disp.to_dmsg(msgobj.flags))
            assert not parsed.is_broadcast
            assert parsed == disp

        # Create broadcast Displays using both methods and check the
        # `is_broadcast` field on the instances and their parsed versions
        bc_disp1 = Display.broadcast(**kw)
        bc_disp2 = Display(index=0xffff, **kw)
        assert bc_disp1.is_broadcast
        assert bc_disp2.is_broadcast

        parsed1, remaining = Display.from_dmsg(msgobj.flags,
                                               bc_disp1.to_dmsg(msgobj.flags))
        assert parsed1.is_broadcast

        parsed2, remaining = Display.from_dmsg(msgobj.flags,
                                               bc_disp2.to_dmsg(msgobj.flags))
        assert parsed2.is_broadcast

        assert bc_disp1 == bc_disp2 == parsed1 == parsed2

        # Add the broadcast Display to the Message at the top
        msgobj.displays.append(bc_disp1)

    # Check the `is_broadcast` field in the displays after Message building / parsing
    parsed, remaining = Message.parse(msgobj.build_message())
    for parsed_disp, bc_disp in zip(parsed.displays, msgobj.displays):
        assert parsed_disp.is_broadcast
        assert parsed_disp == bc_disp
Ejemplo n.º 7
0
async def test_parse_errors(faker, udp_endpoint, udp_port):
    transport, protocol, endpoint_port = udp_endpoint
    assert udp_port != endpoint_port

    loop = asyncio.get_event_loop()

    receiver = UmdReceiver(hostaddr='127.0.0.1', hostport=udp_port)

    evt_listener = EventListener()
    receiver.bind_async(loop, on_tally_added=evt_listener.callback)

    msgobj = Message()
    disp = Display(index=0, text='foo')
    msgobj.displays.append(disp)

    async with receiver:
        transport.sendto(msgobj.build_message(), ('127.0.0.1', udp_port))

        _ = await evt_listener.get()

        receiver.unbind(evt_listener)
        receiver.bind_async(loop, on_tally_updated=evt_listener.callback)

        screen = receiver.screens[msgobj.screen]
        rx_disp = screen[disp.index]
        assert rx_disp is receiver.tallies[rx_disp.id]
        assert rx_disp == disp

        for i in range(100):
            num_bytes = faker.pyint(min_value=1, max_value=1024)
            bad_bytes = faker.binary(length=num_bytes)

            with pytest.raises(ParseError):
                receiver.parse_incoming(bad_bytes,
                                        ('127.0.0.1', endpoint_port))

            transport.sendto(bad_bytes, ('127.0.0.1', udp_port))

            disp.text = f'foo_{i}'
            transport.sendto(msgobj.build_message(), ('127.0.0.1', udp_port))

            _ = await evt_listener.get()

            assert rx_disp == disp
Ejemplo n.º 8
0
def message_with_lots_of_displays():
    msgobj = Message()
    # Message header byte length: 6
    # Dmsg header byte length: 4
    # Text length: 2(length bytes) + 9(chars) = 11
    # Each Dmsg total: 4 + 11 = 15
    # Dmsg's: 4096 * 15 = 61440
    # 4096 Dmsg's with Message header: 4096 * 15 + 6 = 61446 bytes
    for i in range(4096):
        msgobj.displays.append(Display(index=i, text=f'Foo {i:05d}'))
    return msgobj
Ejemplo n.º 9
0
def test_messages():
    msgobj = Message(version=1, screen=5)
    rh_tallies = [
        getattr(TallyColor, attr) for attr in ['RED', 'OFF', 'GREEN', 'AMBER']
    ]
    lh_tallies = [
        getattr(TallyColor, attr) for attr in ['GREEN', 'RED', 'OFF', 'RED']
    ]
    txt_tallies = [
        getattr(TallyColor, attr)
        for attr in ['OFF', 'GREEN', 'AMBER', 'GREEN']
    ]
    txts = ['foo', 'bar', 'baz', 'blah']
    indices = [4, 3, 7, 1]
    for i in range(4):
        disp = Display(
            index=indices[i],
            rh_tally=rh_tallies[i],
            lh_tally=lh_tallies[i],
            txt_tally=txt_tallies[i],
            text=txts[i],
            brightness=i,
        )
        msgobj.displays.append(disp)

    parsed, remaining = Message.parse(msgobj.build_message())
    assert not len(remaining)

    for i in range(len(rh_tallies)):
        disp1, disp2 = msgobj.displays[i], parsed.displays[i]
        assert disp1.rh_tally == disp2.rh_tally == rh_tallies[i]
        assert disp1.lh_tally == disp2.lh_tally == lh_tallies[i]
        assert disp1.txt_tally == disp2.txt_tally == txt_tallies[i]
        assert disp1.text == disp2.text == txts[i]
        assert disp1.index == disp2.index == indices[i]
        assert disp1 == disp2

    for attr in ['version', 'flags', 'screen', 'scontrol']:
        assert getattr(msgobj, attr) == getattr(parsed, attr)

    assert msgobj == parsed
Ejemplo n.º 10
0
def uhs500_msg_parsed() -> Message:
    """Expected :class:`~tslumd.messages.Message` object
    matching data from :func:`uhs500_msg_bytes`
    """
    data = json.loads(MESSAGE_JSON.read_text())
    data['scontrol'] = b''
    displays = []
    for disp in data['displays']:
        for key in ['rh_tally', 'txt_tally', 'lh_tally']:
            disp[key] = getattr(TallyColor, disp[key])
        displays.append(Display(**disp))
    data['displays'] = displays
    return Message(**data)
Ejemplo n.º 11
0
def test_scontrol(faker):
    for _ in range(100):
        data_len = faker.pyint(min_value=1, max_value=1024)
        control_data = faker.binary(length=data_len)

        msgobj = Message(scontrol=control_data)
        assert msgobj.type == MessageType.control
        assert Flags.SCONTROL in msgobj.flags

        msg_bytes = msgobj.build_message()
        parsed, remaining = Message.parse(msg_bytes)
        assert not len(remaining)

        assert parsed.type == MessageType.control
        assert parsed.scontrol == control_data
        assert parsed == msgobj

        disp = Display(index=1)

        with pytest.raises(ValueError) as excinfo:
            disp_msg = Message(displays=[disp], scontrol=control_data)
        assert 'SCONTROL' in str(excinfo.value)
Ejemplo n.º 12
0
def test_invalid_dmsg_control(uhs500_msg_bytes, faker):
    msg = Message()
    disp = Display(index=1, control=b'foo\x00')
    msg.displays.append(disp)
    msg_bytes = msg.build_message()

    # Clip the length field to the wrong size
    bad_bytes = bytearray(msg_bytes)
    bad_bytes = bad_bytes[:-5]
    bad_byte_count = struct.pack('<H', len(bad_bytes) - 2)
    bad_bytes[:2] = bad_byte_count
    bad_bytes = bytes(bad_bytes)
    with pytest.raises(DmsgControlParseError):
        r = Message.parse(bad_bytes)

    # Clip the control bytes to the wrong length
    bad_bytes = bytearray(msg_bytes)
    bad_bytes = bad_bytes[:-2]
    bad_byte_count = struct.pack('<H', len(bad_bytes) - 2)
    bad_bytes[:2] = bad_byte_count
    bad_bytes = bytes(bad_bytes)
    with pytest.raises(DmsgControlParseError):
        r = Message.parse(bad_bytes)
Ejemplo n.º 13
0
async def test_scontrol(faker, udp_endpoint, udp_port):
    transport, protocol, endpoint_port = udp_endpoint
    assert udp_port != endpoint_port

    loop = asyncio.get_event_loop()

    receiver = UmdReceiver(hostaddr='127.0.0.1', hostport=udp_port)

    evt_listener = EventListener()
    receiver.bind_async(loop, on_scontrol=evt_listener.callback)

    async with receiver:
        for i in range(100):
            data_len = faker.pyint(min_value=1, max_value=1024)
            control_data = faker.binary(length=data_len)

            msgobj = Message(screen=i, scontrol=control_data)
            transport.sendto(msgobj.build_message(), ('127.0.0.1', udp_port))

            args, kwargs = await evt_listener.get()

            screen, rx_data = args
            assert screen.index == i
            assert rx_data == control_data
Ejemplo n.º 14
0
async def test_queued_updates_are_separate_messages(udp_endpoint, udp_port):
    transport, protocol, endpoint_port = udp_endpoint
    assert udp_port != endpoint_port

    loop = asyncio.get_event_loop()

    sender = UmdSender(clients=[('127.0.0.1', endpoint_port)])

    screens = {}

    async with sender:
        # Create 10 screens with 10 tallies each
        # and trigger an update by `set_tally_text`.
        #
        # Don't await within the loop so the sender.update_queue gets filled up
        for screen_index in range(10):
            screen = Screen(screen_index)
            screens[screen_index] = screen
            for tally_index in range(10):
                t_id = (screen_index, tally_index)
                txt = f'Tally-{t_id}'
                sender.set_tally_text(t_id, txt)

        # Now give the `sender.tx_loop` a chance to process the queue
        await asyncio.sleep(.1)

        # Check each message to make sure they only have a single screen's data.
        assert not protocol.queue.empty()
        while not protocol.queue.empty():
            data, addr = await protocol.queue.get()
            protocol.queue.task_done()

            parsed, _ = Message.parse(data)
            print(f'screen {parsed.screen} disp_len={len(parsed.displays)}')
            # print(parsed)
            screen = screens[parsed.screen]
            screen.update_from_message(parsed)

        # Ensure nothing got packed incorrectly by the unique tally.id in the text field
        for screen in screens.values():
            for tally in screen:
                assert tally.text == f'Tally-{tally.id}'
                assert len(screen.tallies) == 10
Ejemplo n.º 15
0
    def parse_incoming(self, data: bytes, addr: Tuple[str, int]):
        """Parse data received by the server
        """
        while True:
            message, remaining = Message.parse(data)
            if message.screen not in self.screens:
                screen = Screen(message.screen)
                self.screens[screen.index] = screen
                self._bind_screen(screen)
                self.emit('on_screen_added', screen)
                logger.debug(f'new screen: {screen.index}')
            else:
                screen = self.screens[message.screen]

            if message.is_broadcast:
                for screen in self.screens.values():
                    screen.update_from_message(message)
            else:
                screen.update_from_message(message)
            if not len(remaining):
                break
Ejemplo n.º 16
0
def test_packet_length(faker, message_with_lots_of_displays):
    msgobj = message_with_lots_of_displays

    # Make sure the 2048 byte limit is respected
    with pytest.raises(MessageLengthError):
        _ = msgobj.build_message()

    # Ensure that the limit can be bypassed
    msg_bytes = msgobj.build_message(ignore_packet_length=True)
    parsed, remaining = Message.parse(msg_bytes)
    assert parsed == msgobj

    # Iterate over individual message packets and make sure we get all displays
    all_parsed_displays = []
    for msg_bytes in msgobj.build_messages():
        assert len(msg_bytes) <= 2048
        parsed, remaining = Message.parse(msg_bytes)
        assert not len(remaining)
        all_parsed_displays.extend(parsed.displays)

    assert len(all_parsed_displays) == len(msgobj.displays)
    for disp, parsed_disp in zip(msgobj.displays, all_parsed_displays):
        assert disp.index == parsed_disp.index
        assert disp.text == parsed_disp.text

    # Create an SCONTROL that exceeds the limit
    msgobj = Message(scontrol=faker.binary(length=2048))
    with pytest.raises(MessageLengthError):
        it = msgobj.build_messages()
        _ = next(it)

    # Create a Dmsg control that exceeds the limit
    msgobj = Message(
        displays=[Display(index=0, control=faker.binary(length=2048))])
    with pytest.raises(MessageLengthError):
        it = msgobj.build_messages()
        _ = next(it)
Ejemplo n.º 17
0
 def _build_message(self, **kwargs) -> Message:
     return Message(**kwargs)
Ejemplo n.º 18
0
 async def send_message(self, msg: Message):
     for data in msg.build_messages():
         for client in self.clients:
             self.transport.sendto(data, client)
Ejemplo n.º 19
0
async def test_broadcast_screen_updates(udp_endpoint, udp_port):
    transport, protocol, endpoint_port = udp_endpoint
    assert udp_port != endpoint_port

    loop = asyncio.get_event_loop()

    sender = UmdSender(clients=[('127.0.0.1', endpoint_port)])

    screens = {}
    bc_screen = Screen.broadcast()

    async with sender:

        # Create 10 screens with 10 tallies each and set their initial values to
        # `text='Tally-{tally.id}', rh_tally=TallyColor.RED`
        for screen_index in range(10):
            screen = Screen(screen_index)
            screens[screen_index] = screen
            for tally_index in range(10):
                t_id = (screen_index, tally_index)
                txt = f'Tally-{t_id}'
                sender.set_tally_text(t_id, txt)
                tx_tally = sender.tallies[t_id]

                # Wait for data from the sender and parse it manually into the screen
                data, addr = await protocol.queue.get()
                protocol.queue.task_done()
                parsed, _ = Message.parse(data)
                assert parsed.screen == screen_index
                screen.update_from_message(parsed)

                assert tally_index in screen
                tally = screen[tally_index]
                assert tally.text == txt

        # For each screen, send a broadcast tally setting `rh_tally` to `RED`
        for screen_index, screen in screens.items():
            await sender.send_broadcast_tally(screen_index,
                                              rh_tally=TallyColor.RED)

            # Wait for data and parse it again into the screen
            data, addr = await protocol.queue.get()
            protocol.queue.task_done()

            parsed, _ = Message.parse(data)
            assert parsed.screen == screen_index
            screen.update_from_message(parsed)

            for tally in screen:
                assert tally.rh_tally == TallyColor.RED

        # For each tally, send a screen-broadcast (not tally-broadcast) setting
        # `text='Broadcast-{tally.index}', rh_tally=TallyColor.GREEN`
        for tally_index in range(10):
            t_id = (0xffff, tally_index)
            txt = f'Broadcast-{tally_index}'
            tally = sender.broadcast_screen.add_tally(
                tally_index, text=txt)  #, rh_tally=TallyColor.GREEN)
            tally.rh_tally = TallyColor.GREEN

            # Wait for data and parse it into a separate broadcast screen
            data, addr = await protocol.queue.get()
            protocol.queue.task_done()
            parsed, _ = Message.parse(data)
            assert parsed.screen == 0xffff
            bc_screen.update_from_message(parsed)

            assert tally_index in bc_screen
            bc_tally = bc_screen[tally_index]
            assert bc_tally.id == t_id
            assert bc_tally.text == txt
            assert bc_tally.rh_tally == TallyColor.GREEN

            # Parse the same screen-broadcast message into each of the 10 normal screens
            # This **should** change the tally values as well
            # (unless I'm mis-interpreting the specification)
            for screen in screens.values():
                screen.update_from_message(parsed)

                sc_tally = screen[tally_index]

                assert sc_tally.text == txt
                assert sc_tally.rh_tally == TallyColor.GREEN

        # Wait for the periodic refresh from the sender which **should** send
        # the original tally states and not the broadcast ones (?)
        #
        # That's unclear, but we definitely don't want the broadcast screen
        # sending constant updates, so let's check that doesn't happen.
        # (even though that isn't specifically stated either)
        assert protocol.queue.empty()
        await asyncio.sleep(sender.tx_interval * 2)

        assert not protocol.queue.empty()
        while not protocol.queue.empty():
            data, addr = await protocol.queue.get()
            protocol.queue.task_done()
            parsed, _ = Message.parse(data)

            assert parsed.screen in screens
            assert parsed.screen != 0xffff

            screen = screens[parsed.screen]
            screen.update_from_message(parsed)

            for tally in screen:
                assert tally.text == f'Tally-{tally.id}'
                assert tally.rh_tally == TallyColor.RED
Ejemplo n.º 20
0
def test_uhs_message(uhs500_msg_bytes, uhs500_msg_parsed):
    parsed, remaining = Message.parse(uhs500_msg_bytes)
    assert not len(remaining)
    assert parsed == uhs500_msg_parsed