Changelog:

  • 6 Sep 2025: supply updated config.py; more fully remove references to MAXIMUM_SEQUENCE from the writeup since it’s just set to a huge number
  • 8 Sep 2025: correct ReliableSender/ReliableReceiver to MySender/MyReceiver; note explicitly that to_network exists in MyReceiver like in MySender
  • 9 Sep 2025: update config.py in reliable skeleton to default to one-zero and not list no-ack mode that new error-checking code does not support
  • 15 Sep 2025: note about using config settings and not using from config import …
  • 16 Sep 2025: correct sliding to sliding-window to match connection.py
  • 16 Sep 2025: update config.py in skeleton code not to refer to wrong sliding setting
  • 18 Sep 2025: update main.py to be more permissive about timeout enforcement in sliding-window setting
  • 17 Dec 2025 (for future semesters): don’t say to set moving average to initial timeout

If you downloaded the skeleton code before 6 Septemer 2025 around 5pm, you may need an updated version of config.py. Also after the morning of 18 September 2025, there is an updated main.py was updated to be more permissive about timeout enforcement for the sliding window case, which would likely be relevant for solutions attempting to selective acknowledgments or similar.

1 Your Task

  1. Download the supplied simulator and skeleton code at here (last updated 18 September 2025).

    In the skeleton code, you should only modify ends.py, which initially has code that implements transmission without any sort of reliablity.

    In ends.py, MySender receives messages from its simulated application layer then transmits them using the simulated network layer:

    • We will call the from_application method with each message to be sent. This method should return True if an attempt was made to transmit the message. If an attempt cannot immediately be made to transmit the message, then it should return False. (If you return False, do not attempt to transmit the message in the future; our code will call from_application again later.)
    • Given a Packet object p, you can call self.to_network(p) to send it to the receiver. (You will probably do this from the from_application method. I would recommend making a new Packet object for each call (see below).)
    • Whenever you are ready to receive more data from the application (after having returned False from from_application), you must call self.ready_for_more_from_application(). It is okay if you call this method more times than necessary.

    Then MyReceiver receives those messages from its simulated network layer (in the from_network method), and forwards them to its simulated application layer:

    • We will call the from_network method with each packet received. (This method’s return value does not matter.)
    • Given a Message object m, you can call self.to_application(m) to send that message to the application.
    • Like with MyReceiver, you can call self.to_network to send a Packet to the network, like in the MySender class.

    In config.py, we set global variables that will control your implementation, a variable FOO in this file is references as config.FOO below.

    Packet objects which have the following fields:

    • data: up to 20 bytes, or the special value None to indicate no data is present
    • is_end: a boolean field
    • seq_num: an integer sequence number
    • ack_num: an integer ack number or the special value None to specify no ack number

    Message objects which have the following fields:

    • data: up to 20 bytes (never None)
    • is_end: a boolean field, which will be True if and only if this is the last message to be sent

    We supply you with code that constructs Packet objects from Message objects and vice-versa. However, this code does not implement any form of reliablity; you will need to modify or replace it.

    Note that your submissions will only include ends.py; we will test your submission with unmodified versions of the other files.

1.1 Part 1 (first submission)

1.1.1 one/zero acknowledgments

  1. Modify ends.py, so that when config.MODE is one-zero:

    • it does not send a new message until the previous is acknowledged (like in section 2.5.1 of Computer Networks: A Systems Approach), and retransmits a packet (from the sender)

    • if a message is not acknowledged, it is resent (as many times as needed) config.INITIAL_TIMEOUT time units after a packet is sent.

    • the acknowledgment number of an acknowledgment matches the sequence number of the message being acknowledged

    • sequence numbers either:

      • alternate between 0 and 1, or

      • start at some value and increase indefinitely

  2. If MySender’s from_application cannot immediately transmit a message, then it should return False and not queue the message. (Our simulator/testing code will try resending it later.)

  3. Your implementation must send acknowledgment with an ack_num equal to the number for the most recently received message.

  4. Test your implementation:

    • With no losses (loss rate 0), two packets per packet of data + 1 or 2 extra packets should be sent in total. For example:

       python3 main.py --generate-input 1000 --delay 1 --initial-timeout 10

      should result in everything being received and not much more than 1000 frames sent.

    • Running the simulator with high loss rates (such as by adding --drop 0.4) should still result in everything being transmitted consistently, though using more frames.

    • With no or almost no losses (such as --drop 0.01), one segment should be transmitted per two delays, so running the simulator with --delay=1 should result in a transmission rate of about 20 bytes/(1*2 time units) = 10 bytes per time unit.

1.1.2 dynamic timeouts

  1. Instead of just using config.INITIAL_TIMEOUT, compute the timeout dynamically based on the observed round-trip time.

    My recommendation to do this would be:

    • sample the round-trip time using the timestamp field in packets:

      Use the now() function to get the simulation timestamp and set this in the sender. Be sure to record a new time each time a packet is sent, even if it is resent from a timeout.

      In the receiver, copy the timestamp from you receive into the acknowledgment packet.

      In the sender, when you receive an ack subtract the timestamp you receive from now() to get a sample of the round-tript time

    • take an exponentially weighted moving average1 of the sampled round-trip times:

      • initialize the average based on your initial timeout or the first recorded RTT

      • choose an alpha to determine how much the average is weighted toward recent RTT measurements. I recommend something like 0.2; higher values mean recent changes in RTT affect the average more.

      • each time you record a new RTT, update the average:

        new avg = RTT * alpha + old avg * (1 - alpha)

    • set the timeout to around twice that moving average. (A more sophisticated approach would estimate the variance of the RTT and use that information to set the timeout.)

  2. Test your implementation:

    • With no losses, setting the initial timeout low compared to the actual timeout should result in relatively few extra messages sent, for example:

       python3 main.py --generate-input 100 --initial-timeout 1 --delay 10

      should result in not much more than 120 frames being sent in each direction, not something like 1000 frames.

    • With a relatively low loss rate, setting the initial timeout very high should result in transmission taking not much longer than setting it more normally. For example,

       python3 main.py --generate-input 10000 --drop 0.01 -- delay 1 --initial-timeout 100

      should take somewhat more than 20000 time units, not anything larger than 30000 time units.

1.1.3 submission

  1. Submit your ends.py on the submission site.

1.2 Part 2 (second submission)

1.2.1 Sliding windows

  1. Modify ends.py, so that when config.MODE is sliding-window, it uses a sliding window (like in section 2.5.2 of Computer Networks: A Systems Approach) with:

    • a window size equal to config.INITIAL_WINDOW (which will be at least 1)

    In addition:

    • messages are assigned consecutive sequence numbers; so if data packets for the first message are assigned sequence number 0, then the next should be sequence number 1, then 2, and so on (regardless of the size of the messages)

    • your implementation must call self.to_application on messages in the order they were sent (even if they are not received in order)

    • when sending acknowledgments, your implementation must use cumulative acknolwedgments with ack_num set to the highest sequence number such that that data packet and all previous data packets were also successfully received.

      (For example, if sequence numbers 0, 1, 2, 3, 4, 5, and 6 were sent, but only 0, 1, 2, 3, 5, and 6 were received, you should send an ack_num of 3; after sequence number 4 is received, you should send an ack_num of 6.)

    Optionally: you may chose to:

    • have your implementation resend packets immediately after some number of duplicate ACKs (or similar signal indicating missing packets).

    • use the sack field of packets to provide selective acknowledgment information about additional frames that have been received after ack_num to avoid some retransmissions

    • choose to wraparound sequence numbers at some point (as long as there are more than 2 times the window size plus 1 sequence numbers)

  2. Test your implementation:

    • With no losses (loss rate 0) and a timeout substantially longer than twice the transmission delay, no more than two packets per packet of data + 1 or 2 extra packets should be sent in total.

    • With no or almost no losses, about config.INITIAL_WINDOW / 2 segments should be transmitted per round-trip. With a --delay=1 and INITIAL_WINDOW of 10, this should resultin a transmission rate of around 20 bytes * 5 segments/(1 * 2 time unit) = 50 bytes per time unit

1.2.2 submission

  1. Submit your ends.py on the submission site.

2 API

2.1 config settings

  1. After doing import config, you can access settings in config.py with syntax like config.MODE.

  2. I do not recommend trying to use from config import FOO instead of config.FOO, since you may not see changes our main.py makes to these config settings.

2.2 sending/making packets

  1. From both MySender/MyReceiver, you can send packets with the to_network method (that we will inject in those classes).

2.3 timers

  1. You will need to setup a timer to handle retransmissions, the util library provides a create_timer and cancel_timer method to do this. For example, code like:

    self.resend_timer = create_timer(
        10,
        lambda: self._do_resend()
    )

    will cause self._do_resend() to run 10 time units in the future.

    If, before self._do_resend() runs you decide you do not want to run it, then you can use

    cancel_timer(self.resend_timer)

    Most likely your from_network implementation that receives acknowledgments will do something like this.

2.4 trace()

  1. There is a trace() function you can call like

    trace("some-name", "some message here")

    if some-name in the set in config.py’s TRACE variable, then this will cause some message here to be output. If some-name is not in config.py’s TRACE variable, nothing will be output.

    You can also pass an option like

    --trace=some-name,some-other-name,...

    to override the value of TRACE from the comman dline.

3 Hints

3.1 timers doing the wrong thing

  1. If you do something like:

    def bar():
        if True:
            x = 1
            some_timer = create_timer(10, lambda: foo(x))
        ...
        if True:
            ...
            x = 2

    then the timer will call foo with 2, not 1. One way to avoid this is to call function with its own local variables to create timers.

3.2 Part 1

  1. If your timeouts are becoming very large, make sure you are setting a new timestamp each time you resend a packet. Otherwise, you may end up measuring the timeout to set your timeout and causing it to get very large.

  2. If your implementation for part 1 hangs, one possible reason can be failing to account for timeouts occuring multiple times. If you resend after a timeout, and then fail to receive an ACK again, you need to make sure you resend again after a second timeout.


  1. This is the type of average typically used in TCP; other types of moving averages would also be fine.↩︎