11"""
22Polite Ping-Pong — Bob (server).
33
4- Bob's behaviour is defined as a finite state machine. The event loop
5- reads one message at a time, looks up the (current_state, message) pair
6- in the dispatch table, and calls the corresponding handler. The handler
7- performs an action (typically sending a reply) and returns the next state.
4+ Bob sends "READY" immediately on connection and after each "PONG", signalling
5+ to Alice that he is ready for the next round. He handles "PING" (reply PONG
6+ then READY) and "BYE" (close).
87
98Bob's state diagram
109-------------------
1110
12- recv "ping" send "pong"
13- WAITING_PING ─────────────────────────────────────► WAITING_THANKS
14- │
15- recv "thank you"
16- send "you're welcome"
17- │
18- ▼
19- DONE
11+ ┌─ (connect) ──────────────────────────────────────────┐
12+ │ send "READY" │
13+ ▼ │
14+ WAITING_FOR_PING_OR_BYE (start)
15+ │
16+ recv "PING" → send "PONG", send "READY" → (stay)
17+ recv "BYE" → DONE
2018
21- As a transition table (this maps directly to BOB_DISPATCH below) :
19+ Transition table:
2220
23- Current state │ Message received │ Action │ Next state
24- ─────────────────┼──────────────────┼────────────────────────┼──────────────────
25- WAITING_PING │ "ping" │ send "pong" │ WAITING_THANKS
26- WAITING_THANKS │ "thank you" │ send "you're welcome" │ DONE
27-
28- Any (state, message) pair NOT in the table is rejected with a warning;
29- the state does not change and the loop continues.
21+ Current state │ Message │ Action │ Next state
22+ ────────────────────────┼─────────┼──────────────────────────┼───────────────────────
23+ WAITING_FOR_PING_OR_BYE │ "PING" │ send "PONG", send "READY"│ WAITING_FOR_PING_OR_BYE
24+ WAITING_FOR_PING_OR_BYE │ "BYE" │ — │ DONE
3025"""
3126from asyncio import StreamReader , StreamWriter
3227from pathlib import Path
3833
3934
4035# ── States ───────────────────────────────────────────────────────────────────
41- # Each constant names a state in Bob's state machine.
42- # The string value is used in log output so keep it human-readable.
4336
44- STATE_WAITING_PING = "WAITING_PING" # noqa: E221
45- STATE_WAITING_THANKS = "WAITING_THANKS"
46- STATE_DONE = "DONE" # noqa: E221
37+ STATE_WAITING_FOR_PING_OR_BYE = "WAITING_FOR_PING_OR_BYE"
38+ STATE_DONE = "DONE" # noqa: E221
4739
4840
4941# ── Handlers ─────────────────────────────────────────────────────────────────
50- # One handler per valid transition. A handler receives the writer (to send
51- # a reply) and returns the next state. It does NOT need to validate the
52- # current state — that is guaranteed by the dispatch table.
5342
5443async def handle_ping (writer : StreamWriter ) -> str :
55- """
56- Transition: WAITING_PING ──[recv "ping"]──► WAITING_THANKS
57-
58- Bob receives a ping and replies with a pong.
59- """
60- reply = "pong"
61- print (f"Bob: sending '{ reply } '" , flush = True )
62- writer .write (reply .encode ("utf-8" ))
63- await writer .drain ()
64- return STATE_WAITING_THANKS
65-
66-
67- async def handle_thank_you (writer : StreamWriter ) -> str :
68- """
69- Transition: WAITING_THANKS ──[recv "thank you"]──► DONE
70-
71- Bob receives a thank you and replies politely before finishing.
72- """
73- reply = "you're welcome"
74- print (f"Bob: sending '{ reply } '" , flush = True )
75- writer .write (reply .encode ("utf-8" ))
76- await writer .drain ()
44+ writer .write (b"PONG\n " )
45+ print ("Bob: sent PONG" , flush = True )
46+ writer .write (b"READY\n " )
47+ print ("Bob: sent READY" , flush = True )
48+ return STATE_WAITING_FOR_PING_OR_BYE
49+
50+
51+ async def handle_bye (writer : StreamWriter ) -> str :
52+ print ("Bob: received BYE, closing." , flush = True )
7753 return STATE_DONE
7854
7955
8056# ── Dispatch table ────────────────────────────────────────────────────────────
81- # Maps (current_state, message) → handler.
82- # This table IS the state machine: every valid transition is listed here,
83- # and anything not listed is automatically an invalid transition.
8457
8558BOB_DISPATCH = {
86- (STATE_WAITING_PING , "ping " ): handle_ping , # noqa: E241
87- (STATE_WAITING_THANKS , "thank you " ): handle_thank_you ,
59+ (STATE_WAITING_FOR_PING_OR_BYE , "PING " ): handle_ping ,
60+ (STATE_WAITING_FOR_PING_OR_BYE , "BYE " ): handle_bye , # noqa: E241
8861}
8962
9063
9164# ── Event loop ────────────────────────────────────────────────────────────────
9265
93- async def run_bob (reader : StreamReader , writer : StreamWriter ):
94- """
95- Bob's event loop.
96-
97- Repeatedly:
98- 1. Read the next message from Alice.
99- 2. Look up (current_state, message) in BOB_DISPATCH.
100- 3. If found, call the handler and move to the returned next state.
101- 4. If not found, log a warning and stay in the current state.
102- Loop exits when the state reaches STATE_DONE or the connection drops.
103- """
66+ async def run_bob (reader : StreamReader , writer : StreamWriter ) -> None :
10467 print ("Bob: Alice connected." , flush = True )
105- state = STATE_WAITING_PING
68+
69+ writer .write (b"READY\n " )
70+ print ("Bob: sent READY" , flush = True )
71+ state = STATE_WAITING_FOR_PING_OR_BYE
10672
10773 while state != STATE_DONE :
108- # 1. Wait for the next event (message from Alice)
109- data = await reader .read (255 )
74+ data = await reader .readline ()
11075 if not data :
11176 print (f"Bob [{ state } ]: connection dropped unexpectedly." , flush = True )
11277 break
113- msg = data .decode ("utf-8" )
78+ msg = data .decode (). strip ( )
11479 print (f"Bob [{ state } ]: received '{ msg } '" , flush = True )
11580
116- # 2. Look up the transition
11781 handler = BOB_DISPATCH .get ((state , msg ))
11882
119- # 3a. Invalid transition — warn and stay in current state
12083 if handler is None :
12184 print (
122- f"Bob [{ state } ]: no transition for message '{ msg } ' — ignoring." ,
85+ f"Bob [{ state } ]: no transition for '{ msg } ' — ignoring." ,
12386 flush = True ,
12487 )
12588 continue
12689
127- # 3b. Valid transition — execute handler, advance state
12890 state = await handler (writer )
12991
13092 print (f"Bob: event loop finished (final state: { state } )." , flush = True )
@@ -141,5 +103,5 @@ async def run_bob(reader: StreamReader, writer: StreamWriter):
141103 server = SimulaQronClassicalServer (sockets_config , "Bob" )
142104 server .register_client_handler (run_bob )
143105
144- print ("Bob: starting server..." )
106+ print ("Bob: starting server..." , flush = True )
145107 server .start_serving ()
0 commit comments