Merge pull request #10592 from SmithSamuelM/sam_raet_6

Sam raet 6  Basic messenging with encryption signing curvecp handshake
This commit is contained in:
Thomas S Hatch 2014-02-19 14:32:12 -07:00
commit f7c1fae41c
6 changed files with 424 additions and 82 deletions

View File

@ -67,6 +67,14 @@ class Device(object):
self.sid = 1 # rollover to 1
return self.sid
def validSid(self, sid):
'''
Compare new sid to old .sid and return True if new is greater than old
modulo N where N is 2^32 = 0x100000000
And greater means the difference is less than N/2
'''
return (((sid - self.sid) % 0x100000000) < (0x100000000 / 2))
def nextTid(self):
'''
Generates next session id number.
@ -126,5 +134,10 @@ class RemoteDevice(Device):
self.privee = nacling.Privateer() # short term key
self.publee = nacling.Publican() # correspondent short term key manager
def validRsid(self, rsid):
'''
Compare new rsid to old .rsid and return True if new is greater than old
modulo N where N is 2^32 = 0x100000000
And greater means the difference is less than N/2
'''
return (((rsid - self.rsid) % 0x100000000) < (0x100000000 / 2))

View File

@ -180,7 +180,8 @@ class TxBody(Body):
self.packed = ''
self.kind = self.packet.data['bk']
if self.kind == raeting.bodyKinds.json:
self.packed = json.dumps(self.data, separators=(',', ':'))
if self.data:
self.packed = json.dumps(self.data, separators=(',', ':'))
if self.kind == raeting.bodyKinds.raw:
self.packed = self.data # data is already formatted string
@ -244,8 +245,9 @@ class TxCoat(Coat):
if self.kind == raeting.coatKinds.nacl:
msg = self.packet.body.packed
cipher, nonce = self.packet.encrypt(msg)
self.packed = "".join([cipher, nonce])
if msg:
cipher, nonce = self.packet.encrypt(msg)
self.packed = "".join([cipher, nonce])
if self.kind == raeting.coatKinds.nada:
self.packed = self.packet.body.packed
@ -266,12 +268,14 @@ class RxCoat(Coat):
raise raeting.PacketError(emsg)
if self.kind == raeting.coatKinds.nacl:
tl = raeting.tailSizes.nacl # nonce length
cipher = self.packed[:-tl]
nonce = self.packed[-tl:]
msg = self.packet.decrypt(cipher, nonce)
self.packet.body.packed = msg
if self.packed:
tl = raeting.tailSizes.nacl # nonce length
cipher = self.packed[:-tl]
nonce = self.packed[-tl:]
msg = self.packet.decrypt(cipher, nonce)
self.packet.body.packed = msg
else:
self.packet.body.packed = self.packed
if self.kind == raeting.coatKinds.nada:
self.packet.body.packed = self.packed
@ -478,7 +482,7 @@ class RxPacket(Packet):
with short term keys
'''
remote = self.stack.devices[self.data['sd']]
return (self.stack.device.privee.decrypt(cipher, nonce, remote.publee, key))
return (remote.privee.decrypt(cipher, nonce, remote.publee.key))
def unpack(self, packed=None):
'''

View File

@ -150,13 +150,13 @@ TAIL_SIZES = odict([('nada', 0), ('nacl', 24), ('crc16', 2), ('crc64', 8),
TailSize = namedtuple('TailSize', TAIL_SIZES.keys())
tailSizes = TailSize(**TAIL_SIZES)
TRNS_KINDS = odict([('data', 0), ('join', 1), ('accept', 2), ('allow', 3),
TRNS_KINDS = odict([('message', 0), ('join', 1), ('accept', 2), ('allow', 3),
('unknown', 255)])
TRNS_KIND_NAMES = odict((v, k) for k, v in TRNS_KINDS.iteritems()) # inverse map
TrnsKind = namedtuple('TrnsKind', TRNS_KINDS.keys())
trnsKinds = TrnsKind(**TRNS_KINDS)
PCKT_KINDS = odict([('data', 0), ('ack', 1), ('nack', 2),
PCKT_KINDS = odict([('message', 0), ('ack', 1), ('nack', 2),
('request', 3), ('response', 4),
('hello', 4), ('cookie', 5), ('initiate', 6),
('unknown', 255)])

View File

@ -29,13 +29,20 @@ class StackUdp(object):
Count = 0
Hk = raeting.headKinds.json # stack default
Bk = raeting.bodyKinds.json # stack default
Fk = raeting.footKinds.nacl # stack default
Ck = raeting.coatKinds.nacl # stack default
def __init__(self,
name='',
version=raeting.VERSION,
device=None,
did=None,
ha=("", raeting.RAET_PORT)):
ha=("", raeting.RAET_PORT),
udpRxMsgs = None,
udpTxMsgs = None,
udpRxes = None,
udpTxes = None,
):
'''
Setup StackUdp instance
'''
@ -48,8 +55,10 @@ class StackUdp(object):
# local device for this stack
self.device = device or devicing.LocalDevice(stack=self, did=did, ha=ha)
self.transactions = odict() #transactions
self.udpRxes = deque()
self.udpTxes = deque()
self.udpRxMsgs = udpRxMsgs or deque() # messages received
self.udpTxMsgs = udpTxMsgs or deque() # messages to transmit (msg, ddid) ddid=0 is broadcast
self.udpRxes = udpRxes or deque() # udp packets received
self.udpTxes = udpTxes or deque() # udp packet to transmit
self.serverUdp = aiding.SocketUdpNb(ha=self.device.ha)
self.serverUdp.reopen() # open socket
self.device.ha = self.serverUdp.ha # update device host address after open
@ -104,7 +113,6 @@ class StackUdp(object):
else:
del self.transactions[index]
def serviceUdp(self):
'''
Service the UDP receive and transmit queues
@ -134,6 +142,16 @@ class StackUdp(object):
raise raeting.StackError(msg)
self.udpTxes.append((packed, self.devices[ddid].ha))
def serviceUdpTxMsg(self):
'''
Service .udpTxMsgs queue of outgoint udp messages for message transactions
'''
while self.udpTxMsgs:
body, ddid = self.udpTxMsgs.popleft() # duple (body dict, destination did)
self.message(body, ddid)
print "{0} sending\n{1}".format(self.name, body)
def fetchParseUdpRx(self):
'''
Fetch from UDP deque next packet tuple
@ -210,7 +228,12 @@ class StackUdp(object):
if (packet.data['tk'] == raeting.trnsKinds.allow and
packet.data['pk'] == raeting.pcktKinds.hello and
packet.data['si'] != 0):
self.replyEndow(packet)
self.replyAllow(packet)
if (packet.data['tk'] == raeting.trnsKinds.message and
packet.data['pk'] == raeting.pcktKinds.message and
packet.data['si'] != 0):
self.replyMessage(packet)
def join(self):
'''
@ -234,24 +257,45 @@ class StackUdp(object):
# need to perform the check for accepted status somewhere
joinent.accept()
def endow(self, rdid=None):
def allow(self, rdid=None):
'''
Initiate endow transaction
Initiate allow transaction
'''
data = odict(hk=self.Hk, bk=raeting.bodyKinds.raw)
endower = transacting.Allower(stack=self, rdid=rdid, txData=data)
endower.hello()
data = odict(hk=self.Hk, bk=raeting.bodyKinds.raw, fk=self.Fk)
allower = transacting.Allower(stack=self, rdid=rdid, txData=data)
allower.hello()
def replyEndow(self, packet):
def replyAllow(self, packet):
'''
Correspond to new endow transaction
Correspond to new allow transaction
'''
data = odict(hk=self.Hk, bk=raeting.bodyKinds.raw)
endowent = transacting.Allowent(stack=self,
data = odict(hk=self.Hk, bk=raeting.bodyKinds.raw, fk=self.Fk)
allowent = transacting.Allowent(stack=self,
rdid=packet.data['sd'],
sid=packet.data['si'],
tid=packet.data['ti'],
txData=data,
rxPacket=packet)
endowent.hello()
allowent.hello()
def message(self, body=None, ddid=None):
'''
Initiate message transaction
'''
data = odict(hk=self.Hk, bk=self.Bk, fk=self.Fk, ck=self.Ck)
messenger = transacting.Messenger(stack=self, txData=data, rdid=ddid)
messenger.message(body)
def replyMessage(self, packet):
'''
Correspond to new Message transaction
'''
data = odict(hk=self.Hk, bk=self.Bk, fk=self.Fk, ck=self.Ck)
messengent = transacting.Messengent(stack=self,
rdid=packet.data['sd'],
sid=packet.data['si'],
tid=packet.data['ti'],
txData=data,
rxPacket=packet)
messengent.message()

View File

@ -42,6 +42,8 @@ def test():
prikey=masterPriKeyHex,)
stack1 = stacking.StackUdp(device=device)
print "\n********* Join Transaction **********"
stack1.join()
timer = Timer(duration=0.5)
@ -73,7 +75,10 @@ def test():
for device in stack1.devices.values():
print "Remote Device {0} joined= {1}".format(device.did, device.joined)
stack1.endow()
print "\n********* Allow Transaction **********"
stack1.allow()
timer.restart()
while not timer.expired:
stack1.serviceUdp()
@ -119,6 +124,116 @@ def test():
print "Remote Device {0} allowed= {1}".format(device.did, device.allowed)
print "\n********* Message Transaction Minion to Master **********"
body = odict(what="This is a message to the master. How are you", extra="And some more.")
stack1.message(body=body, ddid=1)
timer.restart()
while not timer.expired:
stack1.serviceUdp()
stack0.serviceUdp()
while stack0.udpRxes:
stack0.processUdpRx()
timer.restart()
while not timer.expired:
stack0.serviceUdp()
stack1.serviceUdp()
while stack1.udpRxes:
stack1.processUdpRx()
print "{0} did={1}".format(stack0.name, stack0.device.did)
print "{0} devices=\n{1}".format(stack0.name, stack0.devices)
print "{0} transactions=\n{1}".format(stack0.name, stack0.transactions)
print "{0} Received Messages =\n{1}".format(stack0.name, stack0.udpRxMsgs)
print "{0} did={1}".format(stack1.name, stack1.device.did)
print "{0} devices=\n{1}".format(stack1.name, stack1.devices)
print "{0} transactions=\n{1}".format(stack1.name, stack1.transactions)
print "{0} Received Messages =\n{1}".format(stack1.name, stack1.udpRxMsgs)
print "\n********* Message Transaction Master to Minion **********"
body = odict(what="This is a message to the minion. Get to Work", extra="Fix the fence.")
stack0.message(body=body, ddid=2)
timer.restart()
while not timer.expired:
stack0.serviceUdp()
stack1.serviceUdp()
while stack1.udpRxes:
stack1.processUdpRx()
timer.restart()
while not timer.expired:
stack1.serviceUdp()
stack0.serviceUdp()
while stack0.udpRxes:
stack0.processUdpRx()
print "{0} did={1}".format(stack0.name, stack0.device.did)
print "{0} devices=\n{1}".format(stack0.name, stack0.devices)
print "{0} transactions=\n{1}".format(stack0.name, stack0.transactions)
print "{0} Received Messages =\n{1}".format(stack0.name, stack0.udpRxMsgs)
print "{0} did={1}".format(stack1.name, stack1.device.did)
print "{0} devices=\n{1}".format(stack1.name, stack1.devices)
print "{0} transactions=\n{1}".format(stack1.name, stack1.transactions)
print "{0} Received Messages =\n{1}".format(stack1.name, stack1.udpRxMsgs)
print "\n********* Message Transaction Minion to Master **********"
stack1.udpTxMsgs.append((odict(house="Mama mia1", queue="fix me"), None))
stack1.udpTxMsgs.append((odict(house="Mama mia2", queue="help me"), None))
stack1.udpTxMsgs.append((odict(house="Mama mia3", queue="stop me"), None))
stack1.udpTxMsgs.append((odict(house="Mama mia4", queue="run me"), None))
stack0.udpTxMsgs.append((odict(house="Papa pia1", queue="fix me"), None))
stack0.udpTxMsgs.append((odict(house="Papa pia2", queue="help me"), None))
stack0.udpTxMsgs.append((odict(house="Papa pia3", queue="stop me"), None))
stack0.udpTxMsgs.append((odict(house="Papa pia4", queue="run me"), None))
stack1.serviceUdpTxMsg()
stack0.serviceUdpTxMsg()
timer.restart()
while not timer.expired:
stack1.serviceUdp()
stack0.serviceUdp()
while stack0.udpRxes:
stack0.processUdpRx()
while stack1.udpRxes:
stack1.processUdpRx()
timer.restart()
while not timer.expired:
stack0.serviceUdp()
stack1.serviceUdp()
while stack1.udpRxes:
stack1.processUdpRx()
while stack0.udpRxes:
stack0.processUdpRx()
print "{0} did={1}".format(stack0.name, stack0.device.did)
print "{0} devices=\n{1}".format(stack0.name, stack0.devices)
print "{0} transactions=\n{1}".format(stack0.name, stack0.transactions)
print "{0} Received Messages".format(stack0.name)
for msg in stack0.udpRxMsgs:
print msg
print
print "{0} did={1}".format(stack1.name, stack1.device.did)
print "{0} devices=\n{1}".format(stack1.name, stack1.devices)
print "{0} transactions=\n{1}".format(stack1.name, stack1.transactions)
print "{0} Received Messages".format(stack1.name)
for msg in stack0.udpRxMsgs:
print msg
if __name__ == "__main__":
test()

View File

@ -165,6 +165,7 @@ class Joiner(Initiator):
self.rdid = self.stack.devices.values()[0].did # zeroth is channel master
self.sid = 0
self.tid = self.stack.devices[self.rdid].nextTid()
self.prep()
self.add(self.index)
def receive(self, packet):
@ -174,20 +175,15 @@ class Joiner(Initiator):
super(Joiner, self).receive(packet)
if packet.data['tk'] == raeting.trnsKinds.join:
if packet.data['pk'] == raeting.pcktKinds.ack:
if packet.data['pk'] == raeting.pcktKinds.ack: #pended
self.pend()
elif packet.data['pk'] == raeting.pcktKinds.response:
self.accept()
def join(self):
def prep(self):
'''
Send join request
Prepare .txData
'''
if self.rdid not in self.stack.devices:
emsg = "Invalid remote destination device id '{0}'".format(self.rdid)
raise raeting.TransactionError(emsg)
self.txData.update( sh=self.stack.device.host,
sp=self.stack.device.port,
dh=self.stack.devices[self.rdid].host,
@ -202,6 +198,14 @@ class Joiner(Initiator):
ck=raeting.coatKinds.nada,
fk=raeting.footKinds.nada)
def join(self):
'''
Send join request
'''
if self.rdid not in self.stack.devices:
emsg = "Invalid remote destination device id '{0}'".format(self.rdid)
raise raeting.TransactionError(emsg)
body = odict([('verhex', self.stack.device.signer.verhex),
('pubhex', self.stack.device.priver.pubhex)])
packet = packeting.TxPacket(stack=self.stack,
@ -227,7 +231,7 @@ class Joiner(Initiator):
def accept(self):
'''
Perform acceptance in response to accept packt
Perform acceptance in response to joint response packt
'''
data = self.rxPacket.data
body = self.rxPacket.body.data
@ -260,8 +264,7 @@ class Joiner(Initiator):
remote.pubber = nacling.Publican(key=pubhex)
if remote.did != rdid: #move remote device to new index
self.stack.moveRemoteDevice(remote.did, rdid)
#self.stack.device.accepted = True
remote.joined = True
remote.joined = True #accepted
remote.nextSid()
self.remove(index)
@ -275,9 +278,27 @@ class Joinent(Correspondent):
'''
kwa['kind'] = raeting.trnsKinds.join
super(Joinent, self).__init__(**kwa)
self.prep()
# Since corresponding bootstrap transaction use packet.index not self.index
self.add(self.rxPacket.index)
def prep(self):
'''
Prepare .txData
'''
#since bootstrap transaction use the reversed sdid and ddid from packet
self.txData.update( sh=self.stack.device.host,
sp=self.stack.device.port,
sd=self.rxPacket.data['dd'],
dd=self.rxPacket.data['sd'],
tk=self.kind,
cf=self.rmt,
bf=self.bcst,
si=self.sid,
ti=self.tid,
ck=raeting.coatKinds.nada,
fk=raeting.footKinds.nada,)
def join(self):
'''
Process join packet
@ -320,20 +341,9 @@ class Joinent(Correspondent):
emsg = "Invalid remote destination device id '{0}'".format(self.rdid)
raise raeting.TransactionError(emsg)
#since bootstrap transaction use the reversed sdid and ddid from packet
self.txData.update( sh=self.stack.device.host,
sp=self.stack.device.port,
dh=self.stack.devices[self.rdid].host,
dp=self.stack.devices[self.rdid].port,
sd=self.rxPacket.data['dd'],
dd=self.rxPacket.data['sd'],
tk=self.kind,
cf=self.rmt,
bf=self.bcst,
si=self.sid,
ti=self.tid,
ck=raeting.coatKinds.nada,
fk=raeting.footKinds.nada,)
#since bootstrap transaction use updated self.rid
self.txData.update( dh=self.stack.devices[self.rdid].host,
dp=self.stack.devices[self.rdid].port,)
body = odict()
packet = packeting.TxPacket(stack=self.stack,
kind=raeting.pcktKinds.ack,
@ -357,20 +367,7 @@ class Joinent(Correspondent):
raise raeting.TransactionError(emsg)
remote = self.stack.devices[self.rdid]
#since bootstrap transaction use the reversed sdid and ddid from packet
self.txData.update( sh=self.stack.device.host,
sp=self.stack.device.port,
dh=remote.host,
dp=remote.port,
sd=self.rxPacket.data['dd'],
dd=self.rxPacket.data['sd'],
tk=self.kind,
cf=self.rmt,
bf=self.bcst,
si=self.sid,
ti=self.tid,
ck=raeting.coatKinds.nada,
fk=raeting.footKinds.nada,)
body = odict([ ('ldid', self.rdid),
('rdid', self.stack.device.did),
('verhex', self.stack.device.signer.verhex),
@ -385,13 +382,14 @@ class Joinent(Correspondent):
print ex
self.remove(self.rxPacket.index)
return
remote.joined = True
remote.joined = True # accepted
remote.nextSid()
self.transmit(packet)
self.remove(self.rxPacket.index)
class Allower(Initiator):
'''
RAET protocol Endower Initiator class Dual of Allowent
RAET protocol Allower Initiator class Dual of Allowent
CurveCP handshake
'''
def __init__(self, **kwa):
@ -405,9 +403,9 @@ class Allower(Initiator):
self.rdid = self.stack.devices.values()[0].did # zeroth is channel master
remote = self.stack.devices[self.rdid]
if not remote.joined:
emsg = "Must be accepted first"
emsg = "Must be joined first"
raise raeting.TransactionError(emsg)
remote.refresh() # refresh short term keys and .endowed
remote.refresh() # refresh short term keys and .allowed
self.sid = remote.sid
self.tid = remote.nextTid()
self.prep() # prepare .txData
@ -440,9 +438,7 @@ class Allower(Initiator):
cf=self.rmt,
bf=self.bcst,
si=self.sid,
ti=self.tid,
ck=raeting.coatKinds.nada,
fk=raeting.footKinds.nacl)
ti=self.tid, )
def hello(self):
'''
@ -554,7 +550,7 @@ class Allower(Initiator):
class Allowent(Correspondent):
'''
RAET protocol Endowent Correspondent class Dual of Allower
RAET protocol Allowent Correspondent class Dual of Allower
CurveCP handshake
'''
def __init__(self, **kwa):
@ -568,10 +564,16 @@ class Allowent(Correspondent):
super(Allowent, self).__init__(**kwa)
remote = self.stack.devices[self.rdid]
if not remote.joined:
emsg = "Must be accepted first"
emsg = "Must be joined first"
raise raeting.TransactionError(emsg)
#Current .sid was set by stack from rxPacket.data sid so it is the new rsid
if not remote.validRsid(self.sid):
emsg = "Stale sid '{0}' in packet".format(self.sid)
raise raeting.TransactionError(emsg)
remote.rsid = self.sid #update last received rsid for device
remote.rtid = self.tid #update last received rtid for device
self.oreo = None #keep locally generated oreo around for redos
remote.refresh() # refresh short term keys and .endowed
remote.refresh() # refresh short term keys and .allowed
self.prep() # prepare .txData
self.add(self.index)
@ -602,9 +604,7 @@ class Allowent(Correspondent):
cf=self.rmt,
bf=self.bcst,
si=self.sid,
ti=self.tid,
ck=raeting.coatKinds.nada,
fk=raeting.footKinds.nacl)
ti=self.tid, )
def hello(self):
'''
@ -745,3 +745,169 @@ class Allowent(Correspondent):
self.stack.devices[self.rdid].allowed = True
#self.remove()
# keep around for 2 minutes to save cookie (self.oreo)
class Messenger(Initiator):
'''
RAET protocol Messenger Initiator class Dual of Messengent
Generic messages
'''
def __init__(self, **kwa):
'''
Setup instance
'''
kwa['kind'] = raeting.trnsKinds.message
super(Messenger, self).__init__(**kwa)
if self.rdid is None:
self.rdid = self.stack.devices.values()[0].did # zeroth is channel master
remote = self.stack.devices[self.rdid]
if not remote.allowed:
emsg = "Must be allowed first"
raise raeting.TransactionError(emsg)
self.sid = remote.sid
self.tid = remote.nextTid()
self.prep() # prepare .txData
self.add(self.index)
def receive(self, packet):
"""
Process received packet belonging to this transaction
"""
super(Messenger, self).receive(packet)
if packet.data['tk'] == raeting.trnsKinds.message:
if packet.data['pk'] == raeting.pcktKinds.ack:
self.done()
def prep(self):
'''
Prepare .txData
'''
remote = self.stack.devices[self.rdid]
self.txData.update( sh=self.stack.device.host,
sp=self.stack.device.port,
dh=remote.host,
dp=remote.port,
sd=self.stack.device.did,
dd=self.rdid,
tk=self.kind,
cf=self.rmt,
bf=self.bcst,
si=self.sid,
ti=self.tid,)
def message(self, body=None):
'''
Send message
'''
if self.rdid not in self.stack.devices:
emsg = "Invalid remote destination device id '{0}'".format(self.rdid)
raise raeting.TransactionError(emsg)
if body is None:
body = odict()
packet = packeting.TxPacket(stack=self.stack,
kind=raeting.pcktKinds.message,
embody=body,
data=self.txData)
try:
packet.pack()
except raeting.PacketError as ex:
print ex
self.remove()
return
self.transmit(packet)
def done(self):
'''
Complete transaction and remove
'''
self.remove()
class Messengent(Correspondent):
'''
RAET protocol Messengent Correspondent class Dual of Messenger
Generic Messages
'''
def __init__(self, **kwa):
'''
Setup instance
'''
kwa['kind'] = raeting.trnsKinds.message
if 'rdid' not in kwa:
emsg = "Missing required keyword argumens: '{0}'".format('rdid')
raise TypeError(emsg)
super(Messengent, self).__init__(**kwa)
remote = self.stack.devices[self.rdid]
if not remote.allowed:
emsg = "Must be allowed first"
raise raeting.TransactionError(emsg)
#Current .sid was set by stack from rxPacket.data sid so it is the new rsid
if not remote.validRsid(self.sid):
emsg = "Stale sid '{0}' in packet".format(self.sid)
raise raeting.TransactionError(emsg)
remote.rsid = self.sid #update last received rsid for device
remote.rtid = self.tid #update last received rtid for device
self.prep() # prepare .txData
self.add(self.index)
def receive(self, packet):
"""
Process received packet belonging to this transaction
"""
super(Messengent, self).receive(packet)
# resent message
if packet.data['tk'] == raeting.trnsKinds.message:
if packet.data['pk'] == raeting.pcktKinds.message:
self.message()
def prep(self):
'''
Prepare .txData
'''
remote = self.stack.devices[self.rdid]
self.txData.update( sh=self.stack.device.host,
sp=self.stack.device.port,
dh=remote.host,
dp=remote.port,
sd=self.stack.device.did,
dd=self.rdid,
tk=self.kind,
cf=self.rmt,
bf=self.bcst,
si=self.sid,
ti=self.tid,)
def message(self):
'''
Process message packet
'''
data = self.rxPacket.data
body = self.rxPacket.body.data
self.stack.udpRxMsgs.append(body)
self.ackMessage()
def ackMessage(self):
'''
Send ack to message
'''
if self.rdid not in self.stack.devices:
msg = "Invalid remote destination device id '{0}'".format(self.rdid)
raise raeting.TransactionError(msg)
body = odict()
packet = packeting.TxPacket(stack=self.stack,
kind=raeting.pcktKinds.ack,
embody=body,
data=self.txData)
try:
packet.pack()
except raeting.PacketError as ex:
print ex
self.remove()
return
self.transmit(packet)
self.remove()