def createchan(self, pkt, x, y): # Older clients first report version here self.version=pkt.p2 name=str(pkt.body).strip('\0') pv = self.server.GetPV(name) if pv is None: # PV does not exist log.debug("Can't create channel for non-existant PV %s",name) fail = CAmessage(cmd=26, p1=pkt.p1) self.send(fail.pack()) return chan=Channel(self.next_sid, pkt.p1, self.server, self, pv) self.channels[chan.sid]=chan dtype, maxcount = pv.info(chan) ok = CAmessage(cmd=18, dtype=dtype, count=maxcount, p1=pkt.p1, p2=chan.sid) rights = CAmessage(cmd=22, p1=pkt.p1, p2=chan.rights) self.send(ok.pack()+rights.pack()) self.next_sid=self.next_sid+1 while self.next_sid in self.channels: self.next_sid=self.next_sid+1
def test_two(self): """Unpack two packets from the same buffer """ msg1='\x00\x01\x00\x08\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07' \ 'hello w\x00' msg2 = '\x00\x02\x00\x00\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07' msg3 = 'testing' buf = msg1 + msg2 + msg3 exp1 = CAmessage(cmd=1, size=8, dtype=2, count=3, p1=0x40005, p2=0x60007, body='hello w\x00') exp2 = CAmessage(cmd=2, size=0, dtype=2, count=3, p1=0x40005, p2=0x60007) pkt1, rem = CAmessage.unpack(buf) self.assertEqual(pkt1, exp1) self.assertEqual(rem, buffer(msg2 + msg3)) pkt2, rem = CAmessage.unpack(rem) self.assertEqual(pkt2, exp2) self.assertEqual(rem, buffer(msg3))
def connectionMade(self): self.peer = self.transport.getPeer() log.debug('Open %s', self) user = padString(self.client.user) host = padString(self.client.host) msg = CAmessage(cmd=0, dtype=self.prio, count=defs.CA_VERSION).pack() msg += CAmessage(cmd=20, size=len(user), body=user).pack() msg += CAmessage(cmd=21, size=len(host), body=host).pack() self.transport.write(msg) self.readyWait = reactor.callLater(0.1, self.circuitReady)
def test_tcplookup(self): client=StubClient() client.tst=self name1=padString('test1') name2=padString('anotherpv') user=padString('hello') host=padString('world') sfact=self.sfact=CAExpectFactory() sfact.tst=self self.serv=reactor.listenTCP(0, sfact, interface='127.0.0.1') target=('127.0.0.1', self.serv.getHost().port) sfact.program= \ [('send',CAmessage(dtype=0, count=12)), ('recv',CAmessage(dtype=0, count=CA_VERSION)), ('recv',CAmessage(cmd=20, size=len(user), body=user)), ('recv',CAmessage(cmd=21, size=len(host), body=host)), ('recv',CAmessage(cmd=6, size=len(name1), dtype=5, count=CA_VERSION, p1=0, p2=0, body=name1)), ('send',CAmessage(cmd=6, size=8, dtype=target[1], p1=0xffffffff, p2=0, body=searchbody.pack(12))), ('recv',CAmessage(cmd=6, size=len(name2), dtype=5, count=CA_VERSION, p1=1, p2=1, body=name2)), ('send',CAmessage(cmd=6, size=8, dtype=target[1], p1=0xffffffff, p2=1, body=searchbody.pack(12))), ] self.cfact=CACircuitFactory(client) conf=Config(Config.empty) conf.nameservs=[target] resolv=self.resolv=Resolver(conf=conf, tcpfactory=self.cfact) d=resolv.lookup('test1') @d.addCallback def result(srv): self.assertEqual(srv, target) self.assertEqual(len(self.sfact.program),2) return resolv.lookup('anotherpv') @d.addCallback def result(srv): self.assertEqual(srv, target) self.assertEqual(len(self.sfact.program),0) return d
def _chanOk(self, chan): self.__D = None if chan is None: # channel has shutdown self.close() return assert self._chan is chan ver = chan._circ.version self.ioid = chan._circ.pendingActions.add(self) dbf = self.dbf if dbf is None: dbf, _ = dbr_to_dbf(chan.dbr) dbr = dbf_to_dbr(dbf, self._meta) # use dynamic array length whenever possible cnt = self.count if ver < 13 else 0 if cnt is None or cnt > chan.maxcount: cnt = chan.maxcount msg = CAmessage(cmd=15, dtype=dbr, count=cnt, p1=chan.sid, p2=self.ioid).pack() chan._circ.send(msg) d = self.__D = self._chan.whenDis d.addCallback(self._circuitLost) return chan
def done(_): # send channel create fail self.chan.dispatch(CAmessage(cmd=26), circ) self.assertTrue(self.chan._d is None) self.assertEqual(self.chan.state, self.chan.S_init) self.chan.close() circ.close()
def clearchan(self, pkt, x, y): chan=self.channels.get(pkt.p1) if not chan: log.warning('Attempt to clean non-existent channel') return chan.close() ok = CAmessage(cmd=12, p1=pkt.p1, p2=pkt.p2) self.send(ok.pack())
def test_udplookup(self): name=padString('test1') serv=CAExpectDatagramProtocol(self, [], halt=False) up=self.up=reactor.listenUDP(0, serv, interface='127.0.0.1') addr=up.getHost() addr=addr.host, addr.port conf=Config(Config.empty) conf.addrs=[addr] resolv=self.resolv=Resolver(conf=conf) serv.dest='127.0.0.1', resolv._udp.getHost().port # name search # respond after second request serv.program= \ [('recv',CAmessage(dtype=0, count=CA_VERSION)), ('recv',CAmessage(cmd=6, size=len(name), dtype=5, count=CA_VERSION, p1=0, p2=0, body=name)), ('recv',CAmessage(dtype=0, count=CA_VERSION)), ('recv',CAmessage(cmd=6, size=len(name), dtype=5, count=CA_VERSION, p1=0, p2=0, body=name)), ('send',CAmessage(cmd=6, size=8, dtype=addr[1], p1=0xffffffff, p2=0, body=searchbody.pack(11))), ] d=resolv.lookup('test1') @d.addCallback def result(srv): self.assertEqual(srv, addr) self.assertEqual(len(serv.program),0) return d
def post(self, mask): """Send monitor update if mask matches Data is read from the PV with the type meta-data requested by the client """ if (self.mask & mask) == 0: return try: count = self.count if count == 0 and self.channel.circuit.version >= 13: # when a client requests a dcount it must get the exact # count. Additional space is zeros. # with version 13 a request for zero data get the # current native size count = self.channel.pv.count count = min(count, self.channel.pv.maxcount) data, count = self.channel.pv.get(self.channel, self.dbr, count) if self.count != 0 and count < self.count: # Zero pad data dbf, _ = dbr_to_dbf(self.dbr) pad = dbf_element_size(dbf) * (self.count - count) data = padString(data + '\0' * pad) count = self.count pkt = CAmessage(cmd=1, size=len(data), dtype=self.dbr, count=count, p1=ECA_NORMAL, p2=self.ioid, body=data) log.debug('post to %s', self.channel.circuit.peer) except CAError, e: log.exception('Post failed') pkt = CAmessage(cmd=1, size=0, dtype=self.dbr, count=0, p1=e.code, p2=self.ioid)
def monitordel(self, pkt, peer, circuit): if pkt.p2 not in self.monitors: raise CAError('Attempt to cancel non-existant monitor', ECA_BADCHID) mon = self.monitors.pop(pkt.p2) log.debug('Del %s', mon) pkt = CAmessage(cmd=1, dtype=mon.dbr, p1=self.circuit.cid, p2=mon.ioid) self.circuit.send(pkt.pack())
def test_tcpabort(self): """Abort a TCP persistent circuit """ client=StubClient() client.tst=self name1=padString('test1') user=padString('hello') host=padString('world') sfact=self.sfact=CAExpectFactory() sfact.tst=self self.serv=reactor.listenTCP(0, sfact, interface='127.0.0.1') target=('127.0.0.1', self.serv.getHost().port) sfact.program= \ [('send',CAmessage(dtype=0, count=12)), ('recv',CAmessage(dtype=0, count=CA_VERSION)), ('recv',CAmessage(cmd=20, size=len(user), body=user)), ('recv',CAmessage(cmd=21, size=len(host), body=host)), ]+[('recv',CAmessage(cmd=6, size=len(name1), dtype=5, count=CA_VERSION, p1=0, p2=0, body=name1))]*6 self.cfact=CACircuitFactory(client) conf=Config(Config.empty) conf.nameservs=[target] resolv=Resolver(conf=conf, tcpfactory=self.cfact) d=resolv.lookup('test1') @d.addCallback def result(srv): self.assertTrue(srv is None) d2=deferLater(reactor, 0.5, resolv.close) return gatherResults([d,d2])
def regreq(self, peer): node = RepeaterNode(self, peer) p = Port(0, node, 'localhost') p.startListening() p.connect(peer[0], peer[1]) self.clients.add(node) log.debug('Repeater add %s', peer) rep = CAmessage(cmd=17, p2=INADDR_LOOPBACK) node.transport.write(rep.pack())
def _chanOk(self, chan): self.__D = None if chan is None: self.close() # channel has shutdown return assert self._chan is chan self.ioid = chan._circ.pendingActions.add(self) dbf = self.dbf if dbf is None: dbf, _ = dbr_to_dbf(chan.dbr) dbr = dbf_to_dbr(dbf, self._meta) meta = self.meta if meta is None: meta = caMeta(dbf) data = self._data cnt = len(data) if cnt > chan.maxcount: cnt = chan.maxcount data = data[:chan.maxcount] data, cnt = tostring(data, meta, dbr, cnt) log.debug('Set %s to %s', chan, data) cmd = 19 if self._wait else 4 msg = CAmessage(cmd=cmd, size=len(data), dtype=dbr, count=cnt, p1=chan.sid, p2=self.ioid, body=data).pack() chan._circ.send(msg) if not self._wait: log.debug('Send put request (no wait) %s', self._chan.name) # do completion here self.ioid = None self.done = True self._comp.callback(ECA_NORMAL) else: log.debug('Send put request (wait) %s', self._chan.name) d = self.__D = self._chan.whenDis d.addCallback(self._circuitLost) return chan
def setUp(self): client = StubClient() user = padString('hello') host = padString('world') self.program = \ [('send',CAmessage(dtype=0, count=13)), ('recv',CAmessage(dtype=0, count=CA_VERSION)), ('recv',CAmessage(cmd=20, size=len(user), body=user)), ('recv',CAmessage(cmd=21, size=len(host), body=host)), ] sfact = CAExpectFactory() sfact.tst = self sfact.program = self.program + self.program self.serv = reactor.listenTCP(0, sfact, interface='127.0.0.1') self.target = ('127.0.0.1', self.serv.getHost().port) self.cfact = CACircuitFactory(client)
def __init__(self, name, id, manager): self.name, self.id, self.manager = name, id, manager self.d = Deferred() self.wait = 0.04 self.T = None self.Skip = set() nbody = padString(name) self.udp = CAmessage(cmd=0, count=CA_VERSION).pack() self.tcp = CAmessage(cmd=6, size=len(nbody), dtype=5, count=CA_VERSION, p1=id, p2=id, body=nbody).pack() self.udp = self.udp + self.tcp self.lookup()
def test_normal(self): data = [ ('\x00' * 16, CAmessage()), ('\x00\x01\x00\x00\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07', CAmessage(cmd=1, size=0, dtype=2, count=3, p1=0x40005, p2=0x60007)), ('\x00\x01\x00\x08\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07' + 'hello w\x00', CAmessage(cmd=1, size=8, dtype=2, count=3, p1=0x40005, p2=0x60007, body='hello w\x00')), ] for raw, cook in data: raw2 = cook.pack() self.assertEqual(raw, raw2) cook2, rem = CAmessage.unpack(raw) self.assertEqual(cook, cook2) self.assertEqual(len(rem), 0)
def dropchan(self, channel): """Remove a channel from this circuit """ assert channel in self.channels self.channels.pop(channel.cid) msg = CAmessage(cmd=12, p1=channel.sid, p2=channel.cid).pack() self.transport.write(msg) if len(self.channels) != 0: return self.loseConnection()
def test_handshakeV12(self): """Handshake with a v11 server. Server sends version on connection to facilitate name server on TCP """ user = padString('hello') host = padString('world') self.sfact.program= \ [('send',CAmessage(dtype=0, count=12)), ('recv',CAmessage(dtype=0, count=CA_VERSION)), ('recv',CAmessage(cmd=20, size=len(user), body=user)), ('recv',CAmessage(cmd=21, size=len(host), body=host)), ] # since client gets notification before program # completes have server do shutdown self.sfact.halt = True d = self.cfact.requestCircuit(self.target) @d.addCallback def postCondition(circ): self.assertTrue(circ is not None) # we get notification when the first packet is processed # the next three may have been received self.assertTrue(len(self.sfact.program) <= 3) self.assertEqual(circ.version, 12) return circ.transport.connector.whenDis @d.addCallback def done(circ): self.assertEqual(self.sfact.program, []) return d
def addchan(self, channel): """Add a channel to this circuit """ assert channel not in self.channels channel.cid = self.channels.add(channel) name = padString(channel.name) msg = CAmessage(cmd=18, size=len(name), p1=channel.cid, p2=defs.CA_VERSION, body=name).pack() self.transport.write(msg)
def test_handshakeV11(self): """Handshake with a v11 server. Server sends version after authentication """ user = padString('hello') host = padString('world') self.sfact.program= \ [('recv',CAmessage(dtype=0, count=CA_VERSION)), ('recv',CAmessage(cmd=20, size=len(user), body=user)), ('recv',CAmessage(cmd=21, size=len(host), body=host)), ] d = self.cfact.requestCircuit(self.target) @d.addCallback def postCondition(circ): self.assertTrue(circ is not None) self.assertEqual(self.sfact.program, []) self.assertEqual(circ.version, 11) return d
def connectionMade(self): self.peer=self.transport.getPeer() self.tcpport=self.transport.getHost().port # before 3.14.12 servers didn't send version until client authenticated # from 3.14.12 clients attempting to do TCP name resolution don't authenticate # but expect a version message immediately pkt=CAmessage(cmd=0, dtype=self.prio, count=defs.CA_VERSION) self.send(pkt.pack()) log.debug('connection from %s',self.peer) log.debug('Create %s',self) self.server.circuits.add(self) self.__D=DeferredManager() self.__C.callback(self)
def close(self, connected=False): """Called when the client closes the channel """ log.debug('Destroy %s', self) self.pv.disconnect(self) if self.__D is not None: self.__D.addErrback(lambda e: e.trap(CancelledError)) self.__D.cancel() self.__D = None if connected: pkt = CAmessage(cmd=27, p1=self.cid) self.circuit.send(pkt.pack()) self.circuit.dropchan(self)
def sendBeacon(self): for dest, srv, sock in self.becdests: # Note that broadcast beacons include to full address # and do not depend on the repeater to determine them. # TODO: Is this correct? What about unicast. b = CAmessage(cmd=13, dtype=srv.port, p1=self.beaconID, p2=addr2int(srv.host)).pack() try: sock.write(b, (dest, self.cport)) except socket.error, e: #TODO: Why is this raising EINVAL for some bcast? #print repr(b), (intr, self.cport) pass
def nameres(self, pkt, endpoint, peer): name=str(pkt.body).strip('\0') log.info('%s is looking for %s',str(peer),name) ret = self.Lookup(name) if isinstance(ret, tuple) and pkt.count<11: # redirect not supported by older clients return elif ret and not isinstance(ret, tuple): ret = (0xffffffff, endpoint.tcpport) if ret: ack=CAmessage(cmd=6, size=8, dtype=ret[1], p1=ret[0], p2=pkt.p2, body=packSearchBody(CA_VERSION)) endpoint.sendto(ack.pack(), peer)
def _chanOk(self, chan): self.__D=None if chan is None: self.close() # channel shutdown return assert self._chan is chan ver=chan._circ.version self.subid=chan._circ.subscriptions.add(self) dbf=self.dbf if dbf is None: self.dbf,_=dbr_to_dbf(chan.dbr) dbr=dbf_to_dbr(self.dbf, self._meta) if self.dbf_conv is None: self.meta=caMeta(self.dbf) else: self.meta=caMeta(dbf_conv) # use dynamic array length whenever possible cnt=self.count if ver<13 else 0 if cnt is None or cnt>chan.maxcount: cnt=chan.maxcount b=monitormask.pack(self.mask) msg=CAmessage(cmd=1, size=len(b), dtype=dbr, count=cnt, p1=chan.sid, p2=self.subid, body=b).pack() chan._circ.send(msg) log.debug('Start monitor %s (%d)',chan.name,self.mask) d=self.__D=self._chan.whenDis d.addCallback(self._circuitLost) return chan
def close(self): """Cancel request """ if self.subid is None: return if self._chan.sid is not None: log.debug('Cancel %s (%d)',self._chan.name,self.mask) msg=CAmessage(cmd=2, p1=self._chan.sid, p2=self.subid).pack() self._chan._circ.send(msg) if self.__D is not None and hasattr(self.__D, 'cancel'): self.__D.addErrback(lambda e:e.trap(CancelledError)) self.__D.cancel() self.__D=None # any updates in the queue will be lost if self._chan._circ is not None: self._chan._circ.subscriptions.remove(self) self.subid=None self._updates(None, 0, ECA_DISCONN)
def test_opencircuit(self): name = padString('testpv') user = padString('testuser') host = padString('testhost') namelookup = CAExpectDatagramProtocol(self, [], halt=False) udp = self.udp = reactor.listenUDP(0, namelookup, interface='127.0.0.1') sfact = self.sfact = CAExpectFactory() sfact.tst = self tcp = self.tcp = reactor.listenTCP(0, sfact, interface='127.0.0.1') udptarget = '127.0.0.1', udp.getHost().port tcptarget = '127.0.0.1', tcp.getHost().port # name search # respond after second request namelookup.program= \ [('recv',CAmessage(dtype=0, count=CA_VERSION)), ('recv',CAmessage(cmd=6, size=len(name), dtype=5, count=CA_VERSION, p1=0, p2=0, body=name)), ('recv',CAmessage(dtype=0, count=CA_VERSION)), ('recv',CAmessage(cmd=6, size=len(name), dtype=5, count=CA_VERSION, p1=0, p2=0, body=name)), ('send',CAmessage(cmd=6, size=8, dtype=tcptarget[1], p1=0xffffffff, p2=0, body=searchbody.pack(11))), ] #namelookup.debug=True sfact.program= \ [('send',CAmessage(dtype=0, count=12)), ('recv',CAmessage(dtype=0, count=CA_VERSION)), ('recv',CAmessage(cmd=20, size=len(user), body=user)), ('recv',CAmessage(cmd=21, size=len(host), body=host)), ('recv',CAmessage(cmd=100)), ] conf = Config(Config.empty) conf.addrs = [udptarget] cli = self.cli = CAClient(conf, user='******', host='testhost') namelookup.dest = '127.0.0.1', cli.resolv._udp.getHost().port d = cli.lookup('testpv') @d.addCallback @inlineCallbacks def findAndConnect(srv): self.assertEqual(srv, tcptarget) circ = yield cli.openCircuit(srv) self.assertTrue(circ is not None) self.assertEqual(circ.version, 12) return d
def test_reconn(self): """connect, disconnect, and reconnect """ self.chan = CAClientChannel('somepv', self.cli) #self.check=ref(self.chan) #TODO: find the reference leak! cb = Counter() def conCB(chan, status): if status: cb.c += 5 else: cb.c -= 1 self.chan.status.add(conCB) circ = MockCircuit() circ.transport.connector.doCon(circ) self.cli._L.callback(('localhost', 42)) self.cli._O.callback(circ) # wait until channel calls circ.addchan(chan) chan = yield circ.reqAttach self.assertEqual(chan, self.chan) self.assertEqual(chan.state, chan.S_attach) self.assertTrue(chan._d is not None) # fall out of circ.addchan(chan) # before sending packets yield deferLater(reactor, 0, lambda: None) # send rights self.chan.dispatch(CAmessage(cmd=22, p2=3), circ) # send channel create ok self.chan.dispatch(CAmessage(cmd=18, dtype=6, count=10, p2=52), circ) self.assertTrue(chan._d is None) # verify decode self.assertEqual(chan.rights, 3) self.assertEqual(chan.sid, 52) self.assertEqual((chan.dbr, chan.maxcount), (6, 10)) self.assertEqual(self.chan.state, self.chan.S_connect) # wait for channel to signal connected chan = yield self.chan.whenCon # check that status callback was fired self.assertEqual(cb.c, 5) # prepare to drop circuit circ2 = MockCircuit() circ2.transport.connector.doCon(circ2) self.cli._L = succeed(('localhost', 43)) self.cli._O = succeed(circ2) circ.transport.connector.doLost(circ) circ.close() # wait for channel to signal disconnected chan = yield self.chan.whenDis self.assertEqual(chan.state, chan.S_init) # verify decode self.assertEqual(chan.rights, 0) self.assertEqual(chan.sid, None) self.assertEqual((chan.dbr, chan.maxcount), (None, 0)) # check the status cb was notified of dcnt self.assertEqual(cb.c, 4) # wait until channel reattaches chan = yield circ2.reqAttach self.assertEqual(self.chan, chan) self.assertEqual(chan._circ, circ2) self.assertEqual(chan.state, chan.S_attach) self.assertTrue(chan._d is not None) # fall out of circ.addchan(chan) yield deferLater(reactor, 0, lambda: None) # send rights self.chan.dispatch(CAmessage(cmd=22, p2=2), circ2) # send channel create ok self.chan.dispatch(CAmessage(cmd=18, dtype=5, count=11, p2=53), circ2) # verify decode self.assertEqual(chan.rights, 2) self.assertEqual(chan.sid, 53) self.assertEqual((chan.dbr, chan.maxcount), (5, 11)) self.assertEqual(self.chan.state, self.chan.S_connect) # wait for channel to signal connected chan = yield self.chan.whenCon # check that status callback was fired self.assertEqual(cb.c, 9) self.chan.close() circ2.close() returnValue(None)