def _recv_Hello(self, data): # check compatibility peer_protocol_version = idec(data[0]) logger.debug('received Hello protocol_version:{0:#04x}'.format( peer_protocol_version)) if peer_protocol_version != packeter.PROTOCOL_VERSION: return self.send_Disconnect( reason='Incompatible network protocols.' ' Expected:{0:#04x} received:{1:#04x}'.format( packeter.PROTOCOL_VERSION, peer_protocol_version)) if idec(data[1]) != packeter.NETWORK_ID: return self.send_Disconnect(reason='Wrong genesis block') # add to known peers list in handshake signal self.hello_received = True if len(data) == 6: self.node_id = data[5] self.port = idec(data[3]) # replace connection port with listen port # reply with hello if not send if not self.hello_sent: self.send_Hello() signals.peer_handshake_success.send(sender=Peer, peer=self)
def load_packet(cls, packet): ''' Though TCP provides a connection-oriented medium, Ethereum nodes communicate in terms of packets. These packets are formed as a 4-byte synchronisation token (0x22400891), a 4-byte "payload size", to be interpreted as a big-endian integer and finally an N-byte RLP-serialised data structure, where N is the aforementioned "payload size". To be clear, the payload size specifies the number of bytes in the packet ''following'' the first 8. :return: (success, result), where result should be None when fail, and (header, payload_len, cmd, data) when success ''' header = idec(packet[:4]) if header != cls.SYNCHRONIZATION_TOKEN: return False, 'check header failed, skipping message,'\ 'sync token was hex: {0:x}'.format(header) try: payload_len = idec(packet[4:8]) payload = lrlp_decode(packet[8:8 + payload_len]) except Exception as e: return False, str(e) if (not len(payload)) or (idec(payload[0]) not in cls.cmd_map): return False, 'check cmd failed' cmd = Packeter.cmd_map.get(idec(payload[0])) return True, (header, payload_len, cmd, payload[1:])
def _recv_Status(self, data): # [0x10: P, protocolVersion: P, networkID: P, totalDifficulty: P, latestHash: B_32, genesisHash: B_32] # check compatibility try: ethereum_protocol_version, network_id = idec(data[0]), idec(data[1]) total_difficulty, head_hash, genesis_hash = idec(data[2]), data[3], data[4] except IndexError: return self.send_Disconnect(reason="Incompatible network protocols") logger.debug( "%r, received Status ETHPROTOCOL:%r TD:%d HEAD:%r GENESIS:%r", self, ethereum_protocol_version, total_difficulty, head_hash.encode("hex"), genesis_hash.encode("hex"), ) if ethereum_protocol_version != packeter.ETHEREUM_PROTOCOL_VERSION: return self.send_Disconnect(reason="Incompatible network protocols") if network_id != packeter.NETWORK_ID: return self.send_Disconnect(reason="Wrong genesis block") if genesis_hash != blocks.genesis().hash: return self.send_Disconnect(reason="Wrong genesis block") self.status_received = True self.status_head_hash = head_hash self.status_total_difficulty = total_difficulty signals.peer_status_received.send(sender=Peer, peer=self)
def _recv_Hello(self, data): # check compatibility client_id, peer_protocol_version = data[2], idec(data[0]) logger.debug('received Hello %s V:%r', client_id, peer_protocol_version) if peer_protocol_version != packeter.PROTOCOL_VERSION: return self.send_Disconnect( reason='Incompatible network protocols') if idec(data[1]) != packeter.NETWORK_ID: return self.send_Disconnect(reason='Wrong genesis block') # add to known peers list in handshake signal self.hello_received = True if len(data) == 6: self.node_id = data[5] self.port = idec( data[3]) # replace connection port with listen port # reply with hello if not send if not self.hello_sent: self.send_Hello() signals.peer_handshake_success.send(sender=Peer, peer=self)
def _recv_Hello(self, data): # check compatibility peer_protocol_version, network_id, client_id = idec(data[0]), idec(data[1]), data[2] capabilities, listen_port, node_id = idec(data[3]), idec(data[4]), data[5] logger.debug('received Hello %s V:%r N:%r C:%r P:%r I:%s', client_id, peer_protocol_version, network_id, capabilities, listen_port, node_id.encode('hex')) if peer_protocol_version != packeter.PROTOCOL_VERSION: return self.send_Disconnect( reason='Incompatible network protocols') if network_id != packeter.NETWORK_ID: return self.send_Disconnect(reason='Wrong genesis block') # add to known peers list in handshake signal self.hello_received = True self.client_id = client_id self.node_id = node_id self.port = listen_port # replace connection port with listen port # reply with hello if not send if not self.hello_sent: self.send_Hello() signals.peer_handshake_success.send(sender=Peer, peer=self)
def _recv_Status(self, data): # [0x10: P, protocolVersion: P, networkID: P, totalDifficulty: P, latestHash: B_32, genesisHash: B_32] # check compatibility try: ethereum_protocol_version, network_id = idec(data[0]), idec(data[1]) total_difficulty, head_hash, genesis_hash = idec(data[2]), data[3], data[4] except IndexError: return self.send_Disconnect(reason='Incompatible network protocols') log_eth.debug('received Status', remote_id=self, ethereum_protocol_version=ethereum_protocol_version, total_difficulty=total_difficulty, head=head_hash.encode('hex'), genesis=genesis_hash.encode('hex')) if ethereum_protocol_version != packeter.ETHEREUM_PROTOCOL_VERSION: return self.send_Disconnect(reason='Incompatible network protocols') if network_id != packeter.NETWORK_ID: return self.send_Disconnect(reason='Wrong genesis block') self.status_received = True self.status_head_hash = head_hash self.status_total_difficulty = total_difficulty signals.peer_status_received.send(sender=Peer, genesis_hash=genesis_hash, peer=self)
def _recv_Status(self, data): # [0x10: P, protocolVersion: P, networkID: P, totalDifficulty: P, latestHash: B_32, genesisHash: B_32] # check compatibility try: ethereum_protocol_version, network_id = idec(data[0]), idec( data[1]) total_difficulty, head_hash, genesis_hash = idec( data[2]), data[3], data[4] except IndexError: return self.send_Disconnect( reason='Incompatible network protocols') logger.debug( '%r, received Status ETHPROTOCOL:%r TD:%d HEAD:%r GENESIS:%r', self, ethereum_protocol_version, total_difficulty, head_hash.encode('hex'), genesis_hash.encode('hex')) if ethereum_protocol_version != packeter.ETHEREUM_PROTOCOL_VERSION: return self.send_Disconnect( reason='Incompatible network protocols') if network_id != packeter.NETWORK_ID: return self.send_Disconnect(reason='Wrong genesis block') if genesis_hash != blocks.genesis().hash: return self.send_Disconnect(reason='Wrong genesis block') self.status_received = True self.status_head_hash = head_hash self.status_total_difficulty = total_difficulty signals.peer_status_received.send(sender=Peer, peer=self)
def dump_packet(packet): try: header = idec(packet[:4]) payload_len = idec(packet[4:8]) data = lrlp_decode(packet[8:8 + payload_len]) cmd = WireProtocol.cmd_map.get( idec(data[0]), 'unknown %s' % idec(data[0])) return [header, payload_len, cmd] + data[1:] except Exception as e: return ['DUMP failed', packet, e]
def _rcv_Hello(self, peer, data): """ [0x00, PROTOCOL_VERSION, NETWORK_ID, CLIENT_ID, CAPABILITIES, LISTEN_PORT, NODE_ID] First packet sent over the connection, and sent once by both sides. No other messages may be sent until a Hello is received. PROTOCOL_VERSION is one of: 0x00 for PoC-1; 0x01 for PoC-2; 0x07 for PoC-3. 0x08 sent by Ethereum(++)/v0.3.11/brew/Darwin/unknown NETWORK_ID should be 0. CLIENT_ID Specifies the client software identity, as a human-readable string (e.g. "Ethereum(++)/1.0.0"). CAPABILITIES specifies the capabilities of the client as a set of flags; presently three bits are used: 0x01 for peers discovery, 0x02 for transaction relaying, 0x04 for block-chain querying. LISTEN_PORT specifies the port that the client is listening on (on the interface that the present connection traverses). If 0 it indicates the client is not listening. NODE_ID is optional and specifies a 512-bit hash, (potentially to be used as public key) that identifies this node. """ logger.debug(data[:-1] + [data[-1][20]]) # check compatibility if idec(data[0]) != self.PROTOCOL_VERSION: return self.send_Disconnect( peer, reason='Incompatible network protocols') if idec(data[1]) != self.NETWORK_ID: return self.send_Disconnect(peer, reason='Wrong genesis block') """ spec has CAPABILITIES after PORT, CPP client the other way round. emulating the latter, see https://github.com/ethereum/cpp-ethereum /blob/master/libethereum/PeerNetwork.cpp#L144 """ # TODO add to known peers list peer.hello_received = True if len(data) == 6: peer.node_id = data[5] # reply with hello if not send if not peer.hello_sent: peer.send_packet(peer, self.packeter.dump_Hello()) peer.hello_sent = True
def rcv_Hello(self, peer, data): """ [0x00, PROTOCOL_VERSION, NETWORK_ID, CLIENT_ID, CAPABILITIES, LISTEN_PORT, NODE_ID] First packet sent over the connection, and sent once by both sides. No other messages may be sent until a Hello is received. PROTOCOL_VERSION is one of: 0x00 for PoC-1; 0x01 for PoC-2; 0x07 for PoC-3. 0x08 sent by Ethereum(++)/v0.3.11/brew/Darwin/unknown NETWORK_ID should be 0. CLIENT_ID Specifies the client software identity, as a human-readable string (e.g. "Ethereum(++)/1.0.0"). CAPABILITIES specifies the capabilities of the client as a set of flags; presently three bits are used: 0x01 for peers discovery, 0x02 for transaction relaying, 0x04 for block-chain querying. LISTEN_PORT specifies the port that the client is listening on (on the interface that the present connection traverses). If 0 it indicates the client is not listening. NODE_ID is optional and specifies a 512-bit hash, (potentially to be used as public key) that identifies this node. [574621841, 116, 'Hello', '\x08', '', 'Ethereum(++)/v0.3.11/brew/Darwin/unknown', '\x07', 'v_', "\xc5\xfe\xc6\xea\xe4TKvz\x9e\xdc\xa7\x01\xf6b?\x7fB\xe7\xfc(#t\xe9}\xafh\xf3Ot'\xe5u\x07\xab\xa3\xe5\x95\x14 |P\xb0C\xa2\xe4jU\xc8z|\x86\xa6ZV!Q6\x82\xebQ$4+"] [574621841, 27, 'Hello', '\x08', '\x00', 'Ethereum(py)/0.0.1', 'vb', '\x07'] """ # check compatibility if idec(data[0]) != self.PROTOCOL_VERSION: return self.send_Disconnect( peer, reason='Incompatible network protocols') if idec(data[1]) != self.NETWORK_ID: return self.send_Disconnect(peer, reason='Wrong genesis block') """ spec has CAPABILITIES after PORT, CPP client the other way round. emulating the latter https://github.com/ethereum/cpp-ethereum/blob/master/libethereum/PeerNetwork.cpp#L144 """ # TODO add to known peers list peer.hello_received = True if len(data) == 6: peer.node_id = data[5] # reply with hello if not send if not peer.hello_sent: self.send_Hello(peer)
def _recv_Peers(self, data): for ip, port, pid in data: assert isinstance(ip, list) ip = '.'.join(str(ord(b or '\x00')) for b in ip) port = idec(port) logger.debug('received peer address: {0}:{1}'.format(ip, port)) signals.new_peer_received.send((ip, port, pid))
def _recv_Peers(self, data): addresses = [] for ip, port, pid in data: assert len(ip) == 4 ip = '.'.join(str(ord(b)) for b in ip) port = idec(port) log_p2p.trace('received peer address', remote_id=self, ip=ip, port=port) addresses.append([ip, port, pid]) signals.peer_addresses_received.send(sender=Peer, addresses=addresses)
def _recv_Disconnect(self, data): if len(data): reason = packeter.disconnect_reasons_map_by_id[idec(data[0])] forget = reason in self.reasons_to_forget else: forget = None reason = None log_p2p.debug('received disconnect', remote_id=self, reason=None) signals.peer_disconnect_requested.send(sender=Peer, remote_id=self, forget=forget)
def _recv_Disconnect(self, data): if len(data): reason = packeter.disconnect_reasons_map_by_id[idec(data[0])] logger.info('{0} sent disconnect, {1} '.format(repr(self), reason)) forget = reason in self.reasons_to_forget else: forget = None signals.peer_disconnect_requested.send( sender=Peer, peer=self, forget=forget)
def _recv_Disconnect(self, data): if len(data): reason = packeter.disconnect_reasons_map_by_id[idec(data[0])] logger.info('%r received disconnect: %r', self, reason) forget = reason in self.reasons_to_forget else: forget = None logger.info('%r received disconnect: w/o reason', self) signals.peer_disconnect_requested.send(sender=Peer, peer=self, forget=forget)
def _recv_Peers(self, data): addresses = [] for ip, port, pid in data: assert len(ip) == 4 ip = '.'.join(str(ord(b)) for b in ip) port = idec(port) logger.debug('received peer address: {0}:{1}'.format(ip, port)) addresses.append([ip, port, pid]) signals.peer_addresses_received.send(sender=Peer, addresses=addresses)
def rcv_packet(self, peer, packet): """ Though TCP provides a connection-oriented medium, Ethereum nodes communicate in terms of packets. These packets are formed as a 4-byte synchronisation token (0x22400891), a 4-byte "payload size", to be interpreted as a big-endian integer and finally an N-byte RLP-serialised data structure, where N is the aforementioned "payload size". To be clear, the payload size specifies the number of bytes in the packet ''following'' the first 8. """ # check header if not idec(packet[:4]) == self.SYNCHRONIZATION_TOKEN: logger.warn('check header failed, skipping message, sync token was {0}' .format(idec(packet[:4]))) return # unpack message payload_len = idec(packet[4:8]) # assert 8 + payload_len <= len(packet) # this get's sometimes raised!? data = lrlp_decode(packet[8:8 + payload_len]) # check cmd if (not len(data)) or (idec(data[0]) not in self.cmd_map): logger.warn('check cmd failed') return self.send_Disconnect(peer, reason='Bad protocol') # good peer peer.last_valid_packet_received = time.time() cmd_id = idec(data.pop(0)) func_name = "rcv_%s" % self.cmd_map[cmd_id] if not hasattr(self, func_name): logger.warn('unknown cmd \'{0}\''.format(func_name)) return """ return self.send_Disconnect( peer, reason='Incompatible network protocols') raise NotImplementedError('%s not implmented') """ # check Hello was sent # call the correspondig method return getattr(self, func_name)(peer, data)
def _recv_NewBlock(self, data): """ NewBlock [+0x07, [blockHeader, transactionList, uncleList], totalDifficulty] Specify a single block that the peer should know about. The composite item in the list (following the message ID) is a block in the format described in the main Ethereum specification. totalDifficulty is the total difficulty of the block (aka score). """ total_difficulty = idec(data[1]) transient_block = blocks.TransientBlock(rlp.encode(data[0])) log_eth.debug('NewBlock', block=transient_block) signals.new_block_received.send(sender=Peer, peer=self, block=transient_block)
def _recv_Hello(self, data): # check compatibility if idec(data[0]) != packeter.PROTOCOL_VERSION: return self.send_Disconnect( reason='Incompatible network protocols') if idec(data[1]) != packeter.NETWORK_ID: return self.send_Disconnect(reason='Wrong genesis block') """ spec has CAPABILITIES after PORT, CPP client the other way round. emulating the latter, see https://github.com/ethereum/cpp-ethereum /blob/master/libethereum/PeerNetwork.cpp#L144 """ # TODO add to known peers list self.hello_received = True if len(data) == 6: self.node_id = data[5] # reply with hello if not send if not self.hello_sent: self.send_Hello()
def _recv_GetChain(self, data): """ [0x14, Parent1, Parent2, ..., ParentN, Count] Request the peer to send Count (to be interpreted as an integer) blocks in the current canonical block chain that are children of Parent1 (to be interpreted as a SHA3 block hash). If Parent1 is not present in the block chain, it should instead act as if the request were for Parent2 &c. through to ParentN. If the designated parent is the present block chain head, an empty reply should be sent. If none of the parents are in the current canonical block chain, then NotInChain should be sent along with ParentN (i.e. the last Parent in the parents list). If no parents are passed, then reply need not be made. """ signals.local_chain_requested.send( sender=Peer, peer=self, block_hashes=data[:-1], count=idec(data[-1]))
def _recv_Hello(self, data): # check compatibility peer_protocol_version = idec(data[0]) logger.debug('received Hello protocol_version:{0:#04x}'.format( peer_protocol_version)) if peer_protocol_version != packeter.PROTOCOL_VERSION: return self.send_Disconnect( reason='Incompatible network protocols' 'expected:{0:#04x} received:{1:#04x}'.format( packeter.PROTOCOL_VERSION, peer_protocol_version)) if idec(data[1]) != packeter.NETWORK_ID: return self.send_Disconnect(reason='Wrong genesis block') # TODO add to known peers list self.hello_received = True if len(data) == 6: self.node_id = data[5] # reply with hello if not send if not self.hello_sent: self.send_Hello()
def _rcv_Peers(self, peer, data): """ [0x11, [IP1, Port1, Id1], [IP2, Port2, Id2], ... ] Specifies a number of known peers. IP is a 4-byte array 'ABCD' that should be interpreted as the IP address A.B.C.D. Port is a 2-byte array that should be interpreted as a 16-bit big-endian integer. Id is the 512-bit hash that acts as the unique identifier of the node. IPs look like this: ['6', '\xcc', '\n', ')'] """ for ip, port, pid in data: assert isinstance(ip, list) ip = '.'.join(str(ord(b or '\x00')) for b in ip) port = idec(port) logger.debug('received peer address: {0}:{1}'.format(ip, port)) self.peer_manager.add_peer_address(ip, port, pid)
def _recv_Peers(self, peer, data): """ [0x11, [IP1, Port1, Id1], [IP2, Port2, Id2], ... ] Specifies a number of known peers. IP is a 4-byte array 'ABCD' that should be interpreted as the IP address A.B.C.D. Port is a 2-byte array that should be interpreted as a 16-bit big-endian integer. Id is the 512-bit hash that acts as the unique identifier of the node. IPs look like this: ['6', '\xcc', '\n', ')'] """ for ip, port, pid in data: assert isinstance(ip, list) ip = '.'.join(str(ord(b or '\x00')) for b in ip) port = idec(port) logger.debug('received peer address: {0}:{1}'.format(ip, port)) self.peer_manager.add_peer_address(ip, port, pid)
def _recv_GetChain(self, data): """ [0x14, Parent1, Parent2, ..., ParentN, Count] Request the peer to send Count (to be interpreted as an integer) blocks in the current canonical block chain that are children of Parent1 (to be interpreted as a SHA3 block hash). If Parent1 is not present in the block chain, it should instead act as if the request were for Parent2 &c. through to ParentN. If the designated parent is the present block chain head, an empty reply should be sent. If none of the parents are in the current canonical block chain, then NotInChain should be sent along with ParentN (i.e. the last Parent in the parents list). If no parents are passed, then reply need not be made. """ signals.local_chain_requested.send(sender=Peer, peer=self, block_hashes=data[:-1], count=idec(data[-1]))
def _recv_GetChain(self, data): """ [0x14, Parent1, Parent2, ..., ParentN, Count] Request the peer to send Count (to be interpreted as an integer) blocks in the current canonical block chain that are children of Parent1 (to be interpreted as a SHA3 block hash). If Parent1 is not present in the block chain, it should instead act as if the request were for Parent2 &c. through to ParentN. If the designated parent is the present block chain head, an empty reply should be sent. If none of the parents are in the current canonical block chain, then NotInChain should be sent along with ParentN (i.e. the last Parent in the parents list). If no parents are passed, then reply need not be made. """ block_hashes = data[:-1] count = idec(data[-1]) if count > MAX_GET_CHAIN_REQUEST_BLOCKS: logger.warn('GetChain: Peer asking for too many blocks %d', count) if len(block_hashes) > MAX_GET_CHAIN_ACCEPT_HASHES: logger.warn('GetChain: Peer sending too many block hashes %d', len(block_hashes)) signals.local_chain_requested.send( sender=Peer, peer=self, block_hashes=block_hashes, count=count)
def receive_disconnect(self, data): reason = serialization.Serializer.disconnect_reasons_map_by_id[idec( data[0])] log('p2p.receive_disconnect', peer=self.peer, reason=reason) self.peer.stop()
def _recv_GetBlockHashes(self, data): block_hash, count = data[0], idec(data[1]) signals.get_block_hashes_received.send(sender=Peer, block_hash=block_hash, count=count, peer=self)
def _rcv_Disconnect(self, peer, data): if len(data): reason = self.packeter.disconnect_reasons_map_by_id[idec(data[0])] logger.info('{0} sent disconnect, {1} '.format(repr(peer), reason)) self.peer_manager.remove_peer(peer)
def packet_size(cls, packet): return idec(packet[4:8]) + 8
def packet_cmd(cls, packet): try: v = idec(rlp.descend(packet[8:200], 0)) except rlp.DecodingError: v = -1 return v
def _recv_Disconnect(self, peer, data): if len(data): reason = self.packeter.disconnect_reasons_map_by_id[idec(data[0])] logger.info('{0} sent disconnect, {1} '.format(repr(peer), reason)) self.peer_manager.remove_peer(peer)
def _recv_Disconnect(self, data): if len(data): reason = packeter.disconnect_reasons_map_by_id[idec(data[0])] logger.info('{0} sent disconnect, {1} '.format(repr(self), reason)) signals.disconnect_requested.send(sender=self)
def receive_authentication(self, other, ciphertext): """ Verification (function, upon receive of PresetAuthentication): 3. remote generates ecdhe-random and nonce and creates auth 4. remote receives auth and decrypts (ECIES performs authentication before decryption) - If address is known, lookup token and public key to be authenticated - derive signature-message = sha3(token || addr^addrRemote) - success -> AcknowledgeAuthentication 5. remote sends auth 6. remote derives shared-secret, aes-secret, mac-secret, ingress-mac, egress-mac """ # eciesEncrypt(remote-pubk, sign(privkey, token^nonce) || 0x80 || ecdhe-random || nonce ) data = self.node.decrypt(ciphertext) assert len(data) == 64 + 1 + 64 + 32 signature = data[:65] assert data[65] == '0x80' remote_ecdhe_pubkey = data[65:65 + 64] token_or_nonce = idec(data[-32:]) # verify signature if not self.node.verify(signature, token_or_nonce): return self.disconnect() # recover remote pubkey remote_pubkey = ecdsa_recover(token_or_nonce, signature) # lookup pubkey and related token token_database = dict() # FIXME token = token_database.get(remote_pubkey, None) if token and token != token_or_nonce: # something fishy # FIXME reset node reputation pass remote_nonce = token_or_nonce # send authentication if not yet if not self._authentication_sent: remote_node = RemoteNode(remote_pubkey) # FIXME LOOKUP self.send_authentication(remote_node) # - success -> AcknowledgeAuthentication self.acknowledge_authentication(other, remote_pubkey, remote_ecdhe_pubkey) # ecdhe_shared_secret = ecdh.agree(ecdhe-random, ecdhe-random-public) # Compute public key with the local private key and return a 512bits shared key ecdhe_shared_secret = self.ephemeral_ecc.get_ecdh_key(remote_pubkey) ecdhe_pubkey = ephemeral_ecc.get_pubkey() # shared-secret = sha3(ecdhe-shared-secret || sha3(nonce || remote-nonce)) shared_secret = sha3(ecdhe_shared_secret + sha3(ienc(self.nonce) + ienc(remote_nonce))) self.aes_secret = sha3_256(ecdhe_shared_secret + shared_secret) self.mac_secret = sha3_256(ecdhe_shared_secret + self.aes_secret) # egress-mac = sha3(mac-secret^nonce || auth) self.egress_mac = sha3_256( sxor(self.mac_secret, self.nonce) + ciphertext) # ingress-mac = sha3(mac-secret^remote-nonce || auth) self.ingress_mac = sha3_256( sxor(self.mac_secret, remote_nonce) + ciphertext) self.token = sha3(shared_secret) iv = pyelliptic.Cipher.gen_IV('aes-256-ctr') self.aes_enc = pyelliptic.Cipher(self.aes_secret, iv, 1, ciphername='aes-256-ctr') self.aes_dec = pyelliptic.Cipher(self.aes_secret, iv, 0, ciphername='aes-256-ctr') self.is_ready = True
def _recv_GetBlockHashes(self, data): block_hash, count = data[0], idec(data[1]) signals.get_block_hashes_received.send( sender=Peer, block_hash=block_hash, count=count, peer=self)
def _rcv_Disconnect(self, peer, data): if len(data): reason = self.packeter.disconnect_reasons_map_by_id[idec(data[0])]