class Message: """ A class used for the exchange of arbitrary messages between components. A :class:`Message` can be used to simulate both asynchronous and synchronous function calls. Attributes: type(Enum): An enumeration object that defines the message type args(Dict[str, Any]): A dictionary containing the message's arguments eProcessed(Event): A SimPy event that is triggered when :meth:`setProcessed` is called. This is useful for simulating synchronous function calls and also allows for return values (an example is provided in :meth:`setProcessed`). """ def __init__(self, type: Enum, args: Dict[str, Any] = None): self.type = type self.args = args self.eProcessed = Event(SimMan.env) def setProcessed(self, returnValue: Any = None): """ Makes the :attr:`eProcessed` event succeed. Args: returnValue: If specified, will be used as the `value` of the :attr:`eProcessed` event. Examples: If `returnValue` is specified, SimPy processes can use Signals for simulating synchronous function calls with return values like this: :: signal = Signal(myType, {"key", value}) gate.output.send(signal) value = yield signal.eProcessed # value now contains the returnValue that setProcessed() was called with """ self.eProcessed.succeed(returnValue) def __repr__(self): return "Message(type: '{}', args: {})".format(self.type.name, self.args)
class Packet(object): # TODO blocksize should always be max_packet_size. # TODO Never read more data than necessary. # TODO Read and write should return packet events. def __init__(self, socket, max_packet_size=16384, blocksize=4096, encode=None, decode=None): self.env = socket.env self.socket = socket self.max_packet_size = max_packet_size self.blocksize = blocksize self.encode = encode self.decode = decode self._read_ev = None self._read_buf = b'' self._read_size = None self._write_ev = None self._write_buf = b'' def _wrap(self, event): if event.ok: event._value = Packet(event._value, self.max_packet_size, self.blocksize, self.encode, self.decode) def accept(self): event = self.socket.accept() event.callbacks.append(self._wrap) return event def bind(self, address): return self.socket.bind(address) def listen(self, backlog=5): return self.socket.listen(backlog) def connect(self, address): return self.socket.connect(address) @property def address(self): return self.socket.address @property def peer_address(self): return self.socket.address def read(self): if self._read_ev is not None: raise RuntimeError('Already reading') event = self._read_ev = Event(self.env) if self._read_buf: self._read_data(Event(self.env).succeed(b'')) else: self.socket.read(self.blocksize).callbacks.append(self._read_data) return event def _read_data(self, event): if not event.ok: event.defused = True self._read_ev.fail(event.value) self._read_ev = None return self._read_buf += event.value if self._read_size is None and len(self._read_buf) >= Header.size: self._read_size = Header.unpack_from(self._read_buf)[0] if self._read_size > self.max_packet_size: raise ValueError('Packet too large. Allowed %d bytes but ' 'got %d bytes' % (self.max_packet_size, self._read_size)) self._read_size += Header.size if (self._read_size is not None and len(self._read_buf) >= self._read_size): packet = self._read_buf[Header.size:self._read_size] if self.decode is None: self._read_ev.succeed(packet) else: # TODO Handle errors. self._read_ev.succeed(self.decode(packet)) self._read_buf = self._read_buf[self._read_size:] self._read_size = None self._read_ev = None return self.socket.read(self.blocksize).callbacks.append(self._read_data) def write(self, packet): if self._write_ev is not None: raise RuntimeError('Already writing') if self.encode is not None: packet = self.encode(packet) if len(packet) > self.max_packet_size: raise ValueError('Packet too large. Allowed %d bytes but ' 'got %d bytes' % (self.max_packet_size, len(packet))) self._write_ev = Event(self.env) self._write_buf = Header.pack(len(packet)) + packet self.socket.write(self._write_buf).callbacks.append(self._write_data) return self._write_ev def _write_data(self, event): if not event.ok: event.defused = True self._write_ev.fail(event.value) self._write_ev = None return self._write_buf = self._write_buf[event.value:] if not self._write_buf: self._write_ev.succeed() self._write_ev = None else: self.socket.write(self._write_buf).callbacks.append( self._write_data) def close(self): self.socket.close()
class SimpleMac(Module): """ A MAC layer implementation of the contention-free protocol described as follows: * Every SimpleMac has a unique 6-byte-long MAC address. * The MAC layer with address ``0`` is considered to belong to the RRM. * Time slots are grouped into frames. * Every second frame is reserved for the RRM and has a fixed length (number of time slots). * The RRM uses those frames to send a short *announcement* containing a destination MAC address and the frame length (number of time slots **n**) of the following frame. By doing so it allows the specified device to use the frequency band for the next frame. *Announcements* are packets with a :class:`~gymwipe.networking.messages.SimpleMacHeader` having the following attributes: :attr:`~gymwipe.networking.messages.SimpleMacHeader.sourceMAC`: The RRM MAC address :attr:`~gymwipe.networking.messages.SimpleMacHeader.destMAC`: The MAC address of the device that may transmit next :attr:`~gymwipe.networking.messages.SimpleMacHeader.flag`: ``1`` (flag for allowing a device to transmit) The packet's :attr:`~gymwipe.networking.messages.Packet.payload` is the number **n** mentioned above (wrapped inside a :class:`~gymwipe.networking.messages.Transmittable`) * Every other packet sent has a :class:`~gymwipe.networking.messages.SimpleMacHeader` with :attr:`~gymwipe.networking.messages.SimpleMacHeader.flag` ``0``. The `networkIn` gate accepts objects of the following types: * :class:`~gymwipe.networking.messages.Message` Types: * :attr:`~gymwipe.networking.messages.StackMessageTypes.RECEIVE` Listen for packets sent to this device. :class:`~gymwipe.networking.messages.Message` args: :duration: The time in seconds to listen for When a packet destinated to this device is received, the :class:`~gymwipe.networking.messages.Message.eProcessed` event of the :class:`~gymwipe.networking.messages.Message` will be triggered providing the packet as the value. If the time given by `duration` has passed and no packet was received, it will be triggered with ``None``. * :attr:`~gymwipe.networking.messages.Packet` Send a given packet (with a :attr:`~gymwipe.networking.messages.SimpleNetworkHeader`) to the MAC address defined in the header. The `phyIn` gate accepts objects of the following types: * :attr:`~gymwipe.networking.messages.Packet` A packet received by the physical layer """ @GateListener.setup def __init__(self, name: str, device: Device, frequencyBandSpec: FrequencyBandSpec, addr: bytes): """ Args: name: The layer's name device: The device that operates the SimpleMac layer addr: The 6-byte-long MAC address to be assigned to this MAC layer """ super(SimpleMac, self).__init__(name, owner=device) self._addPort("phy") self._addPort("network") self.addr = addr self._packetQueue = deque(maxlen=100) # allow 100 packets to be queued self._packetAddedEvent = Event(SimMan.env) self._mcs = BpskMcs(frequencyBandSpec) self._transmissionPower = 0.0 # dBm self._receiving = False self._receiveCmd = None self._receiveTimeout = None logger.debug("Initialization completed, MAC address: %s", self.addr, sender=self) rrmAddr = bytes(6) """bytes: The 6 bytes long RRM MAC address""" _macCounter = 0 @classmethod def newMacAddress(cls) -> bytes: """ A method for generating unique 6-byte-long MAC addresses (currently counting upwards starting at 1) """ cls._macCounter += 1 addr = bytearray(6) addr[5] = cls._macCounter return bytes(addr) @GateListener("phyIn", Packet) def phyInHandler(self, packet): header = packet.header if not isinstance(header, SimpleMacHeader): raise ValueError( "Can only deal with header of type SimpleMacHeader. Got %s.", type(header), sender=self) if header.destMAC == self.addr: # packet for us if header.sourceMAC == self.rrmAddr: # RRM sent the packet logger.debug("Received a packet from RRM: %s", packet, sender=self) if header.flag == 1: # we may transmit timeSlots = packet.payload.value timeTotal = timeSlots * TIME_SLOT_LENGTH stopTime = SimMan.now + timeTotal def timeLeft(): return stopTime - SimMan.now logger.info("Got permission to transmit for %d time slots", timeSlots, sender=self) timeoutEvent = SimMan.timeout(timeTotal) queuedPackets = True while not timeoutEvent.processed: if len(self._packetQueue) == 0: queuedPackets = False logger.debug( "Packet queue empty, nothing to transmit. Time left: %s s", timeLeft(), sender=self) yield self._packetAddedEvent | timeoutEvent if not timeoutEvent.processed: # new packet was added for sending logger.debug( "Packet queue was refilled. Time left: %s s", timeLeft(), sender=self) queuedPackets = True if queuedPackets: if not timeLeft() > self._packetQueue[ 0].transmissionTime(self._mcs.dataRate): logger.info( "Next packet is too large to be transmitted. Idling. Time left: %s s", timeLeft(), sender=self) yield timeoutEvent else: # enough time left to transmit the next packet # TODO This has to be done before adding the # packet to the queue! packet = self._packetQueue.popleft() message = Message( StackMessageTypes.SEND, { "packet": packet, "power": self._transmissionPower, "mcs": self._mcs }) self.gates["phyOut"].send( message) # make the PHY send the packet logger.debug( "Transmitting packet. Time left: %s", timeLeft(), sender=self) logger.debug("Packet: %s", packet, sender=self) yield message.eProcessed # wait until the transmission has completed else: # packet from any other device if self._receiving: logger.info("Received Packet.", sender=self) logger.debug("Packet: %s", packet.payload, sender=self) # return the packet's payload to the network layer self._receiveCmd.setProcessed(packet.payload) self._stopReceiving() else: logger.debug( "Received Packet from Phy, but not in receiving mode. Packet ignored.", sender=self) elif header.destMAC == self.rrmAddr: # packet from RRM to all devices pass @GateListener("networkIn", (Message, Packet)) def networkInHandler(self, cmd): if isinstance(cmd, Message): if cmd.type is StackMessageTypes.RECEIVE: logger.debug("%s: Entering receive mode.", self) # start receiving self._receiveCmd = cmd # set _receiving and a timeout event self._receiving = True self._receiveTimeout = SimMan.timeout(cmd.args["duration"]) self._receiveTimeout.callbacks.append( self._receiveTimeoutCallback) elif isinstance(cmd, Packet): payload = cmd packet = Packet( SimpleMacHeader(self.addr, payload.header.destMAC, flag=0), payload) self._packetQueue.append(packet) self._packetAddedEvent.succeed() self._packetAddedEvent = Event(SimMan.env) def _receiveTimeoutCallback(self, event: Event): if event is self._receiveTimeout: # the current receive message has timed out logger.debug("%s: Receive timed out.", self) self._receiveCmd.setProcessed() self._stopReceiving() def _stopReceiving(self): logger.debug("%s: Stopping to receive.", self) self._receiveCmd = None self._receiving = False self._receiveTimeout = None
class TCPSocket(BaseSocket): def __init__(self, env, max_buffer_size=4096, max_segment_size=None): self.env = env self.max_buffer_size = max_buffer_size if max_segment_size: self.max_segment_size = max_segment_size else: self.max_segment_size = max_buffer_size self.incoming = None self.outgoing = None self.backlog = None self._address = ('0.0.0.0', 0) self._peer = None self._peer_address = None self._latency = None self._incoming_error = errno.ENOTCONN self._incoming_avail = None self._incoming_reader = None self._outgoing_error = errno.EPIPE self._outgoing_avail = None self._outgoing_writer = None self._frame_transmission = None self._reader = None self._writer = None self.read = types.MethodType(Read, self) self.write = types.MethodType(Write, self) @classmethod def server(cls, env, address, backlog=5): socket = cls(env) socket.bind(address) socket.listen(backlog) return socket @classmethod def connection(cls, env, address): socket = cls(env) socket.connect(address) return socket def _link(self, address, peer, peer_address, latency): self._peer = peer self._address = address self._peer_address = peer_address self._latency = latency self._incoming_reader = self.env.process(self._read_incoming()) self._outgoing_writer = self.env.process(self._write_outgoing()) self._incoming_error = None self._outgoing_error = None self.incoming = b'' self.outgoing = b'' @property def address(self): if self._address is None: raise socket_error(errno.EBADF) return self._address @property def peer_address(self): if self._address is None: raise socket_error(errno.EBADF) if self._peer_address is None: raise socket_error(errno.ENOTCONN) return self._peer_address def bind(self, address): if self._address is None: raise socket_error(errno.EBADF) if self._incoming_error != errno.ENOTCONN: raise socket_error(platform.invalid_argument) self._address = self.env._get_address(address) def listen(self, backlog): if self._address is None: raise socket_error(errno.EBADF) if self._incoming_error != errno.ENOTCONN: raise socket_error(platform.invalid_argument) self.backlog = Store(self.env, capacity=backlog) self.env._register(self) def accept(self): if self.backlog is None: raise socket_error(platform.invalid_argument) return self.backlog.get() def connect(self, address): if self._address is None: raise socket_error(errno.EBADF) if self._peer is not None: # Already connected, do nothing. return if self.backlog is not None: raise socket_error(errno.EISCONN) self.env._establish_connection(self, address) def _try_read(self): if self._incoming_error is not None: self._reader.fail(socket_error(self._incoming_error)) self._reader = None return if not self.incoming: return data = self.incoming[:self._reader.amount] self.incoming = self.incoming[self._reader.amount:] self._reader.succeed(data) self._reader = None if self._incoming_avail: self._incoming_avail.succeed() self._incoming_avail = None def _try_write(self): if self._outgoing_error is not None: self._writer.fail(socket_error(self._outgoing_error)) self._writer = None return available = self.max_buffer_size - len(self.outgoing) if not available: return self.outgoing += self._writer.data[:available] self._writer.succeed(min(len(self._writer.data), available)) self._writer = None if self._outgoing_avail: # Notify the reader process about the new data. self._outgoing_avail.succeed() self._outgoing_avail = None def _read_incoming(self): """Pushes remote data frames into :attr:`incoming`.""" self._frame_transmission = Event(self.env) while True: if self._reader: self._try_read() if self._incoming_error is not None: break # Wait until there is room for incoming data. if len(self.incoming) >= self.max_buffer_size: self._incoming_avail = Event(self.env) yield self._incoming_avail continue frame = yield self._frame_transmission self._frame_transmission = Event(self.env) if frame is None: # A local close will end the frame transmission with None. continue if frame.data: read = min(self.max_buffer_size - len(self.incoming), len(frame.data)) self.incoming += frame.data[:read] # Simulate transmission of the frame acknowledgement. yield self.env.timeout(self._latency()) frame.succeed(read) else: # An empty frame has been received, this means the remote side # has closed the connection. self._incoming_error = errno.ECONNRESET self.env._unregister(self) frame.succeed() self._frame_transmission = None def _write_outgoing(self): """Pulls data from :attr:`outgoing` and pushes it to the peer.""" while True: if len(self.outgoing) == 0: if self._outgoing_error is None: self._outgoing_avail = Event(self.env) yield self._outgoing_avail data = self.outgoing[:self.max_segment_size] frame = Frame(self, data) yield self.env.timeout(self._latency()) if self._peer._frame_transmission: # Inform peer of the frame and wait for the acknowledgement. self._peer._frame_transmission.succeed(frame) sent = yield frame else: # Peer has closed the connection sent = 0 self._outgoing_error = errno.EPIPE if not frame.data: # The close frame has been sent. break self.outgoing = self.outgoing[sent:] if self._writer: self._try_write() def close(self): """Closes the socket, all further operations will raise ``EBADF``.""" if self._address is None: return self._incoming_error = errno.EBADF self._outgoing_error = errno.EBADF if self._incoming_avail is not None: self._incoming_avail.succeed() if self._outgoing_avail is not None: self._outgoing_avail.succeed() if self._frame_transmission: # Wake the reader if it is currently waiting for a frame. if not self._frame_transmission.triggered: self._frame_transmission.succeed() self._frame_transmission = None if self.backlog is not None: for accepter in self.backlog.get_queue: if not accepter.triggered: accepter.fail(socket_error(errno.EBADF)) self.env._unregister(self) self._address = None self._peer_address = None
class Message(object): # TODO Rename class and module (channel)? def __init__(self, env, socket, codec=None, message_limit=1024): self.env = env self.socket = socket if codec is None: codec = JSON() self.codec = codec self.message_limit = message_limit self._message_id = count() self._in_queue = [] self._out_queue = [] self._in_messages = {} """Maps incoming message objects to ids.""" self._out_messages = {} """Maps outgoing message ids to objects.""" self._send_ev = None self._recv_ev = None self.reader = Process(self.env, self._reader()) self.writer = Process(self.env, self._writer()) def _reader(self): try: buffer = b'' while True: data = yield self.socket.read() msg_type, msg_id, content = self.codec.decode(data) if msg_type == REQUEST: message = InMessage(self, msg_id, content) message.callbacks.append(self._reply) if len(self._in_messages) >= self.message_limit: # Close the connection if the maximum number of # incoming messages is reached. self.close() raise MessageOverflowError( 'Incoming message limit of %d has ' 'been exceeded' % self.message_limit) self._in_messages[message] = msg_id if self._recv_ev is not None: self._recv_ev.succeed(message) self._recv_ev = None else: self._in_queue.append(message) elif msg_type == SUCCESS: self._out_messages.pop(msg_id).succeed(content) elif msg_type == FAILURE: self._out_messages.pop(msg_id).fail( RemoteException(self, content)) else: raise RuntimeError('Invalid message type %d' % msg_type) except BaseException as e: self._handle_error(self.reader, e) def _writer(self): env = self.env try: while True: if not self._out_queue: self._send_ev = Event(self.env) yield self._send_ev yield self.socket.write(self._out_queue.pop(0)) except BaseException as e: self._handle_error(self.writer, e) def _handle_error(self, process, err): # FIXME Should I really ignore errors? if isinstance(err, socket.error) and err.errno in UNCRITICAL_ERRORS: uncritical = True else: uncritical = False process.defused = uncritical if self._send_ev is not None: # FIXME Is this safe? Is it impossible, that socket.write has been # triggered but not yet been processed? self._send_ev.defused = uncritical self._send_ev.fail(err) if self._out_messages is not None: for msg_id, event in self._out_messages.items(): event.defused = uncritical event.fail(err) if self._recv_ev is not None: self._recv_ev.defused = uncritical self._recv_ev.fail(err) self._in_messages = None self._out_messages = None self._in_queue = None self._out_queue = None self._recv_ev = None self._send_ev = None raise err def _reply(self, event): try: message_id = self._in_messages.pop(event) except AttributeError: if self._in_messages is not None: raise # Channel has been closed. Ignore the event. event.defused = True return if event.ok: failure = None try: self._out_queue.append( self.codec.encode((SUCCESS, message_id, event._value))) except BaseException as e: failure = e else: failure = event._value if failure is not None: # Failure is handled on the remote side. event.defused = True # FIXME Ugly hack for python < 3.3 if hasattr(failure, '__traceback__'): stacktrace = traceback.format_exception( failure.__class__, failure, failure.__traceback__) else: stacktrace = traceback.format_exception_only( failure.__class__, failure) self._out_queue.append( self.codec.encode((FAILURE, message_id, ''.join(stacktrace)))) if self._send_ev is not None: self._send_ev.succeed() self._send_ev = None def send(self, content): if self._out_queue is None: raise self.writer.value if len(self._out_messages) >= self.message_limit: raise MessageOverflowError('Outgoing message limit of %d has been ' 'exceeded' % self.message_limit) message_id = next(self._message_id) data = self.codec.encode((REQUEST, message_id, content)) message = OutMessage(self, message_id, content) self._out_queue.append(data) self._out_messages[message_id] = message # Wake the writer process. if self._send_ev is not None: self._send_ev.succeed() self._send_ev = None return message def recv(self): if self._in_queue is None: raise self.reader.value # Enqueue reads if there are no pending incoming messages. if not self._in_queue: if self._recv_ev is not None: raise RuntimeError('Concurrent receive attempt') self._recv_ev = Event(self.env) return self._recv_ev return Event(self.env).succeed(self._in_queue.pop(0)) def close(self): self.socket.close()