예제 #1
0
파일: client.py 프로젝트: ZLLentz/caproto
 async def send(self, port, *commands):
     """
     Process a command and tranport it over the UDP socket.
     """
     bytes_to_send = self.broadcaster.send(*commands)
     for host in ca.get_address_list():
         if ':' in host:
             host, _, specified_port = host.partition(':')
             is_register = isinstance(commands[0],
                                      ca.RepeaterRegisterRequest)
             if not self.registered and is_register:
                 logger.debug(
                     'Specific IOC host/port designated in address'
                     'list: %s:%s.  Repeater registration '
                     'requirement ignored', host, specified_port)
                 async with self.broadcaster_command_condition:
                     # TODO how does this work with multiple addresses
                     # listed?
                     response = (('127.0.0.1', ca.EPICS_CA1_PORT), [
                         ca.RepeaterConfirmResponse(
                             repeater_address='127.0.0.1')
                     ])
                     await self.command_bundle_queue.put(response[1])
                     await self.broadcaster_command_condition.notify_all()
                 continue
             await self.udp_sock.sendto(bytes_to_send,
                                        (host, int(specified_port)))
         else:
             await self.udp_sock.sendto(bytes_to_send, (host, port))
예제 #2
0
def test_broadcaster_checks():
    b = ca.Broadcaster(ca.CLIENT)
    with pytest.raises(ca.LocalProtocolError):
        b.send(
            ca.SearchRequest(name='LIRR',
                             cid=0,
                             version=ca.DEFAULT_PROTOCOL_VERSION))

    b.send(ca.RepeaterRegisterRequest('1.2.3.4'))
    res = ca.RepeaterConfirmResponse('5.6.7.8')
    commands = b.recv(bytes(res), ('5.6.7.8', 6666))
    assert commands[0] == res
    b.process_commands(commands)

    req = ca.SearchRequest(name='LIRR',
                           cid=0,
                           version=ca.DEFAULT_PROTOCOL_VERSION)
    with pytest.raises(ca.LocalProtocolError):
        b.send(req)
    b.send(ca.VersionRequest(priority=0, version=ca.DEFAULT_PROTOCOL_VERSION),
           req)

    res = ca.SearchResponse(port=6666,
                            ip='1.2.3.4',
                            cid=0,
                            version=ca.DEFAULT_PROTOCOL_VERSION)
    addr = ('1.2.3.4', 6666)
    commands = b.recv(bytes(res), addr)
    # see changes to _broadcaster.py.  Apparently rsrv does not always conform
    # to the protocol and include a version response before search responsesq
    # with pytest.raises(ca.RemoteProtocolError):
    #     b.process_commands(commands)
    # commands = b.recv(bytes(ca.VersionResponse(version=ca.DEFAULT_PROTOCOL_VERSION)) + bytes(res), addr)
    b.process_commands(commands)  # this gets both
예제 #3
0
def test_broadcaster_checks():
    b = ca.Broadcaster(ca.CLIENT)
    with pytest.raises(ca.LocalProtocolError):
        b.send(ca.SearchRequest(name='LIRR', cid=0, version=13))

    b.send(ca.RepeaterRegisterRequest('1.2.3.4'))
    res = ca.RepeaterConfirmResponse('5.6.7.8')
    commands = b.recv(bytes(res), ('5.6.7.8', 6666))
    assert commands[0] == res
    b.process_commands(commands)

    req = ca.SearchRequest(name='LIRR', cid=0, version=13)
    with pytest.raises(ca.LocalProtocolError):
        b.send(req)
    b.send(ca.VersionRequest(priority=0, version=13), req)

    res = ca.SearchResponse(port=6666, ip='1.2.3.4', cid=0, version=13)
    addr = ('1.2.3.4', 6666)
    commands = b.recv(bytes(res), addr)
    with pytest.raises(ca.RemoteProtocolError):
        b.process_commands(commands)
    commands = b.recv(bytes(ca.VersionResponse(version=13)) + bytes(res), addr)
    b.process_commands(commands)  # this gets both
예제 #4
0
def test_nonet():
    # Register with the repeater.
    assert not cli_b._registered
    bytes_to_send = cli_b.send(ca.RepeaterRegisterRequest('0.0.0.0'))
    assert not cli_b._registered

    # Receive response
    data = bytes(ca.RepeaterConfirmResponse('127.0.0.1'))
    commands = cli_b.recv(data, cli_addr)
    cli_b.process_commands(commands)
    assert cli_b._registered

    # Search for pv1.
    # CA requires us to send a VersionRequest and a SearchRequest bundled into
    # one datagram.
    bytes_to_send = cli_b.send(ca.VersionRequest(0, ca.DEFAULT_PROTOCOL_VERSION),
                               ca.SearchRequest(pv1, 0,
                                                ca.DEFAULT_PROTOCOL_VERSION))

    commands = srv_b.recv(bytes_to_send, cli_addr)
    srv_b.process_commands(commands)
    ver_req, search_req = commands
    bytes_to_send = srv_b.send(
        ca.VersionResponse(ca.DEFAULT_PROTOCOL_VERSION),
        ca.SearchResponse(5064, None, search_req.cid, ca.DEFAULT_PROTOCOL_VERSION))

    # Receive a VersionResponse and SearchResponse.
    commands = iter(cli_b.recv(bytes_to_send, cli_addr))
    command = next(commands)
    assert type(command) is ca.VersionResponse
    command = next(commands)
    assert type(command) is ca.SearchResponse
    address = ca.extract_address(command)

    circuit = ca.VirtualCircuit(our_role=ca.CLIENT,
                                address=address,
                                priority=0)
    circuit.log.setLevel('DEBUG')
    chan1 = ca.ClientChannel(pv1, circuit)
    assert chan1.states[ca.CLIENT] is ca.SEND_CREATE_CHAN_REQUEST
    assert chan1.states[ca.SERVER] is ca.IDLE

    srv_circuit = ca.VirtualCircuit(our_role=ca.SERVER,
                                    address=address, priority=None)

    cli_send(chan1.circuit, ca.VersionRequest(priority=0,
                                              version=ca.DEFAULT_PROTOCOL_VERSION))

    srv_recv(srv_circuit)

    srv_send(srv_circuit, ca.VersionResponse(version=ca.DEFAULT_PROTOCOL_VERSION))
    cli_recv(chan1.circuit)
    cli_send(chan1.circuit, ca.HostNameRequest('localhost'))
    cli_send(chan1.circuit, ca.ClientNameRequest('username'))
    cli_send(chan1.circuit, ca.CreateChanRequest(name=pv1, cid=chan1.cid,
                                                 version=ca.DEFAULT_PROTOCOL_VERSION))
    assert chan1.states[ca.CLIENT] is ca.AWAIT_CREATE_CHAN_RESPONSE
    assert chan1.states[ca.SERVER] is ca.SEND_CREATE_CHAN_RESPONSE

    srv_recv(srv_circuit)
    assert chan1.states[ca.CLIENT] is ca.AWAIT_CREATE_CHAN_RESPONSE
    assert chan1.states[ca.SERVER] is ca.SEND_CREATE_CHAN_RESPONSE
    srv_chan1, = srv_circuit.channels.values()
    assert srv_chan1.states[ca.CLIENT] is ca.AWAIT_CREATE_CHAN_RESPONSE
    assert srv_chan1.states[ca.SERVER] is ca.SEND_CREATE_CHAN_RESPONSE

    srv_send(srv_circuit, ca.CreateChanResponse(cid=chan1.cid, sid=1,
                                                data_type=5, data_count=1))
    assert srv_chan1.states[ca.CLIENT] is ca.CONNECTED
    assert srv_chan1.states[ca.SERVER] is ca.CONNECTED

    # At this point the CLIENT is not aware that we are CONNECTED because it
    # has not yet received the CreateChanResponse. It should not be allowed to
    # read or write.
    assert chan1.states[ca.CLIENT] is ca.AWAIT_CREATE_CHAN_RESPONSE
    assert chan1.states[ca.SERVER] is ca.SEND_CREATE_CHAN_RESPONSE

    # Try sending a premature read request.
    read_req = ca.ReadNotifyRequest(sid=srv_chan1.sid,
                                    data_type=srv_chan1.native_data_type,
                                    data_count=srv_chan1.native_data_count,
                                    ioid=0)
    with pytest.raises(ca.LocalProtocolError):
        cli_send(chan1.circuit, read_req)

    # The above failed because the sid is not recognized. Remove that failure
    # by editing the sid cache, and check that it *still* fails, this time
    # because of the state machine prohibiting this command before the channel
    # is in a CONNECTED state.
    chan1.circuit.channels_sid[1] = chan1
    with pytest.raises(ca.LocalProtocolError):
        cli_send(chan1.circuit, read_req)

    cli_recv(chan1.circuit)
    assert chan1.states[ca.CLIENT] is ca.CONNECTED
    assert chan1.states[ca.SERVER] is ca.CONNECTED

    # Test subscriptions.
    assert chan1.native_data_type and chan1.native_data_count
    add_req = ca.EventAddRequest(data_type=chan1.native_data_type,
                                 data_count=chan1.native_data_count,
                                 sid=chan1.sid,
                                 subscriptionid=0,
                                 low=0, high=0, to=0, mask=1)
    cli_send(chan1.circuit, add_req)
    srv_recv(srv_circuit)
    add_res = ca.EventAddResponse(data=(3,),
                                  data_type=chan1.native_data_type,
                                  data_count=chan1.native_data_count,
                                  subscriptionid=0,
                                  status=1)

    srv_send(srv_circuit, add_res)
    cli_recv(chan1.circuit)

    cancel_req = ca.EventCancelRequest(data_type=add_req.data_type,
                                       sid=add_req.sid,
                                       subscriptionid=add_req.subscriptionid)

    cli_send(chan1.circuit, cancel_req)
    srv_recv(srv_circuit)

    # Test reading.
    cli_send(chan1.circuit, ca.ReadNotifyRequest(data_type=5, data_count=1,
                                                 sid=chan1.sid,
                                                 ioid=12))
    srv_recv(srv_circuit)
    srv_send(srv_circuit, ca.ReadNotifyResponse(data=(3,),
                                                data_type=5, data_count=1,
                                                ioid=12, status=1))
    cli_recv(chan1.circuit)

    # Test writing.
    request = ca.WriteNotifyRequest(data_type=2, data_count=1,
                                    sid=chan1.sid,
                                    ioid=13, data=(4,))

    cli_send(chan1.circuit, request)
    srv_recv(srv_circuit)
    srv_send(srv_circuit, ca.WriteNotifyResponse(data_type=5, data_count=1,
                                                 ioid=13, status=1))
    cli_recv(chan1.circuit)

    # Test "clearing" (closing) the channel.
    cli_send(chan1.circuit, ca.ClearChannelRequest(sid=chan1.sid, cid=chan1.cid))
    assert chan1.states[ca.CLIENT] is ca.MUST_CLOSE
    assert chan1.states[ca.SERVER] is ca.MUST_CLOSE

    srv_recv(srv_circuit)
    assert srv_chan1.states[ca.CLIENT] is ca.MUST_CLOSE
    assert srv_chan1.states[ca.SERVER] is ca.MUST_CLOSE

    srv_send(srv_circuit, ca.ClearChannelResponse(sid=chan1.sid, cid=chan1.cid))
    assert srv_chan1.states[ca.CLIENT] is ca.CLOSED
    assert srv_chan1.states[ca.SERVER] is ca.CLOSED
예제 #5
0
def _run_repeater(server_sock, bind_addr):
    'Run the repeater using server_socket and bind_address'
    bind_host, bind_port = bind_addr

    servers = {}
    clients = {}
    broadcaster = caproto.Broadcaster(our_role=caproto.SERVER)

    logger.info("Repeater is listening on %s:%d", bind_host, bind_port)
    while True:
        msg, addr = server_sock.recvfrom(MAX_UDP_RECV)
        host, port = addr

        if port in clients and clients[port] != host:
            # broadcast only from one interface
            continue
        elif (port in servers and servers[port]['host'] != host):
            continue

        try:
            commands = broadcaster.recv(msg, addr)
            broadcaster.process_commands(commands)
        except Exception:
            logger.exception('Failed to process incoming datagram')
            continue

        if not commands:
            # NOTE: additional valid way of registration is an empty
            # message, according to broadcaster source
            if port not in clients:
                clients[port] = host
                logger.debug('New client %s (zero-length registration)', addr)
            continue

        to_forward = []
        for command in commands:
            if isinstance(command, caproto.Beacon):
                # Update our records of the last time each server checked in
                # (i.e. issued a heartbeat).
                servers[command.server_port] = dict(up_at=time.time(),
                                                    host=host)
                # The sender of this command may leave the IP field empty (0),
                # leaving it up to the repeater to fill in the address so that
                # the ultimate recipient knows the correct origin. By leaving
                # that up to the repeater, the sender avoids providing the
                # wrong return address (i.e. picking the wrong interface). It
                # is safer to let the repeater determine the return address
                # by inspection.
                if command.header.parameter2 == 0:
                    updated_ip = caproto.ipv4_to_int32(host)
                    command.header.parameter2 = updated_ip
                to_forward.append(command)
            elif isinstance(command, caproto.RepeaterRegisterRequest):
                if port not in clients:
                    clients[port] = host
                    logger.debug('New client %s', addr)

                confirmation_bytes = broadcaster.send(
                    caproto.RepeaterConfirmResponse(host))

                try:
                    server_sock.sendto(confirmation_bytes, (host, port))
                except OSError as exc:
                    raise caproto.CaprotoNetworkError(
                        f"Failed to send to {host}:{port}") from exc

                remove_clients = list(check_clients(clients, skip=port))
                _update_all(clients, servers, remove_clients=remove_clients)
                # Do not broadcast registration requests to other clients.
                # Omit it from `to_forward`.
            else:
                to_forward.append(command)
        bytes_to_broadcast = b''.join(bytes(cmd) for cmd in to_forward)
        to_remove = []
        for other_port, other_host in clients.items():
            if other_port != port:
                try:
                    server_sock.sendto(bytes_to_broadcast,
                                       (other_host, other_port))
                except Exception:
                    to_remove.append((other_host, other_port))

        if to_remove:
            _update_all(clients, servers, remove_clients=to_remove)
예제 #6
0
def main(*, skip_monitor_section=False):
    # A broadcast socket
    udp_sock = ca.bcast_socket()

    # Register with the repeater.
    bytes_to_send = b.send(ca.RepeaterRegisterRequest('0.0.0.0'))

    # TODO: for test environment with specific hosts listed in
    # EPICS_CA_ADDR_LIST
    if False:
        fake_reg = (('127.0.0.1', ca.EPICS_CA1_PORT),
                    [ca.RepeaterConfirmResponse(repeater_address='127.0.0.1')])
        b.command_queue.put(fake_reg)
    else:
        udp_sock.sendto(bytes_to_send, ('', CA_REPEATER_PORT))

        # Receive response
        data, address = udp_sock.recvfrom(1024)
        commands = b.recv(data, address)

    b.process_commands(commands)

    # Search for pv1.
    # CA requires us to send a VersionRequest and a SearchRequest bundled into
    # one datagram.
    bytes_to_send = b.send(ca.VersionRequest(0, 13),
                           ca.SearchRequest(pv1, 0, 13))
    for host in ca.get_address_list():
        if ':' in host:
            host, _, specified_port = host.partition(':')
            udp_sock.sendto(bytes_to_send, (host, int(specified_port)))
        else:
            udp_sock.sendto(bytes_to_send, (host, CA_SERVER_PORT))
    print('searching for %s' % pv1)
    # Receive a VersionResponse and SearchResponse.
    bytes_received, address = udp_sock.recvfrom(1024)
    commands = b.recv(bytes_received, address)
    b.process_commands(commands)
    c1, c2 = commands
    assert type(c1) is ca.VersionResponse
    assert type(c2) is ca.SearchResponse
    address = ca.extract_address(c2)

    circuit = ca.VirtualCircuit(our_role=ca.CLIENT,
                                address=address,
                                priority=0)
    circuit.log.setLevel('DEBUG')
    chan1 = ca.ClientChannel(pv1, circuit)
    sockets[chan1.circuit] = socket.create_connection(chan1.circuit.address)

    # Initialize our new TCP-based CA connection with a VersionRequest.
    send(chan1.circuit, ca.VersionRequest(priority=0, version=13))
    recv(chan1.circuit)
    # Send info about us.
    send(chan1.circuit, ca.HostNameRequest('localhost'))
    send(chan1.circuit, ca.ClientNameRequest('username'))
    send(chan1.circuit,
         ca.CreateChanRequest(name=pv1, cid=chan1.cid, version=13))
    commands = recv(chan1.circuit)

    # Test subscriptions.
    assert chan1.native_data_type and chan1.native_data_count
    add_req = ca.EventAddRequest(data_type=chan1.native_data_type,
                                 data_count=chan1.native_data_count,
                                 sid=chan1.sid,
                                 subscriptionid=0,
                                 low=0,
                                 high=0,
                                 to=0,
                                 mask=1)
    send(chan1.circuit, add_req)

    commands = recv(chan1.circuit)

    if not skip_monitor_section:
        try:
            print('Monitoring until Ctrl-C is hit. Meanwhile, use caput to '
                  'change the value and watch for commands to arrive here.')
            while True:
                commands = recv(chan1.circuit)
                if commands:
                    print(commands)
        except KeyboardInterrupt:
            pass

    cancel_req = ca.EventCancelRequest(data_type=add_req.data_type,
                                       sid=add_req.sid,
                                       subscriptionid=add_req.subscriptionid)

    send(chan1.circuit, cancel_req)
    commands, = recv(chan1.circuit)

    # Test reading.
    send(
        chan1.circuit,
        ca.ReadNotifyRequest(data_type=2, data_count=1, sid=chan1.sid,
                             ioid=12))
    commands, = recv(chan1.circuit)

    # Test writing.
    request = ca.WriteNotifyRequest(data_type=2,
                                    data_count=1,
                                    sid=chan1.sid,
                                    ioid=13,
                                    data=(4, ))

    send(chan1.circuit, request)
    recv(chan1.circuit)
    time.sleep(2)
    send(
        chan1.circuit,
        ca.ReadNotifyRequest(data_type=2, data_count=1, sid=chan1.sid,
                             ioid=14))
    recv(chan1.circuit)

    # Test "clearing" (closing) the channel.
    send(chan1.circuit, ca.ClearChannelRequest(chan1.sid, chan1.cid))
    recv(chan1.circuit)

    sockets.pop(chan1.circuit).close()
    udp_sock.close()
예제 #7
0
 def connection_made(self, transport):
     self.transport = transport
     confirmation = caproto.RepeaterConfirmResponse(self.addr[0])
     confirmation_bytes = self.proxy.broadcaster.send(confirmation)
     self.transport.sendto(confirmation_bytes)