def send(self, command, data = '', stream_id = 0): """ Sends a message over the circuit. :param stem.client.RelayCommand command: command to be issued :param bytes data: message payload :param int stream_id: specific stream this concerns :returns: **list** of :class:`~stem.client.cell.RelayCell` responses """ with self.relay._orport_lock: orig_digest = self.forward_digest.copy() orig_key = copy.copy(self.forward_key) # Digests and such are computed using the RELAY cell payload. This # doesn't include the initial circuit id and cell type fields. # Circuit ids vary in length depending on the protocol version. header_size = self.relay.link_protocol.circ_id_size.size + 1 try: cell = stem.client.cell.RelayCell(self.id, command, data, 0, stream_id) payload_without_digest = cell.pack(self.relay.link_protocol)[header_size:] self.forward_digest.update(payload_without_digest) cell = stem.client.cell.RelayCell(self.id, command, data, self.forward_digest, stream_id) header, payload = split(cell.pack(self.relay.link_protocol), header_size) encrypted_payload = header + self.forward_key.update(payload) reply_cells = [] self.relay._orport.send(encrypted_payload) reply = self.relay._orport.recv() # Check that we got the correct number of bytes for a series of RELAY cells relay_cell_size = header_size + stem.client.cell.FIXED_PAYLOAD_LEN relay_cell_cmd = stem.client.cell.RelayCell.VALUE if len(reply) % relay_cell_size != 0: raise stem.ProtocolError('Circuit response should be a series of RELAY cells, but received an unexpected size for a response: %i' % len(reply)) while reply: circ_id, reply = self.relay.link_protocol.circ_id_size.pop(reply) command, reply = Size.CHAR.pop(reply) payload, reply = split(reply, stem.client.cell.FIXED_PAYLOAD_LEN) if command != relay_cell_cmd: raise stem.ProtocolError('RELAY cell responses should be %i but was %i' % (relay_cell_cmd, command)) elif circ_id != self.id: raise stem.ProtocolError('Response should be for circuit id %i, not %i' % (self.id, circ_id)) decrypted = self.backward_key.update(payload) reply_cells.append(stem.client.cell.RelayCell._unpack(decrypted, self.id, self.relay.link_protocol)) return reply_cells except: self.forward_digest = orig_digest self.forward_key = orig_key raise
async def _msg(self, cell: 'stem.client.cell.Cell') -> AsyncIterator['stem.client.cell.Cell']: """ Sends a cell on the ORPort and provides the response we receive in reply. Unfortunately unlike control sockets, ORPorts don't have generalized rules for predictable message IO. With control sockets... * Each message we send receives a single reply. * We may also receive asynchronous events marked with a 650 status. ORPorts by contrast receive variable length cells with differing rules on their arrival. As such making a best effort attempt at a send-and-receive method in which we do the following... * Discard any existing unread data from the socket. * Send our request. * Await up to a second for a reply. It's quite possible this is a stupid approach. If so, patches welcome. :param cell: cell to be sent :returns: **generator** with the cells received in reply """ # TODO: why is this an iterator? await self._orport.recv(timeout = 0) # discard unread data await self._orport.send(cell.pack(self.link_protocol)) response = await self._orport.recv(timeout = 1) yield stem.client.cell.Cell.pop(response, self.link_protocol)[0]