This shows you the differences between two versions of the page.
|
ac:laboratoare:08 [2023/12/07 14:45] marios.choudary |
ac:laboratoare:08 [2024/11/14 13:05] (current) dimitrie.valu |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ===== Lab 08 - Whatsapp End-to-end Encryption ===== | + | ===== Lab 08 - Whatsapp End-to-end Encryption (part 2) ===== |
| - | In this lab you will implement a simplified version of The Signal Protocol, which is the basis for WhatsApp's end-to-end encryption. | + | In this lab you will continue the implementation of the Signal Protocol, which is the basis for WhatsApp's end-to-end encryption. |
| - | The first versions of Whatsapp protocol were described [[https://cryptome.org/2016/04/whatsapp-crypto.pdf|here]]. A more recent document is available [[https://www.whatsapp.com/security/WhatsApp-Security-Whitepaper.pdf|here]]. | + | The protocol is described [[https://cryptome.org/2016/04/whatsapp-crypto.pdf|here]]. |
| - | WhatsApp's security is based on the Signal protocol, which was first used by TextSecure. The Signal protocol is | + | For more details you can also check [[https://s3.amazonaws.com/files.douglas.stebila.ca/files/research/papers/EuroSP-CCDGS17-full.pdf|this]] paper. |
| - | described in detail in [[https://s3.amazonaws.com/files.douglas.stebila.ca/files/research/papers/EuroSP-CCDGS17-full.pdf|this]] paper. | + | |
| For the Elliptic Curves, you can use [[https://github.com/Muterra/donna25519|this]] library. | For the Elliptic Curves, you can use [[https://github.com/Muterra/donna25519|this]] library. | ||
| - | For installation, follow these steps: | + | If you solved the previous lab, use your previous setup (replace the files with the ones from the ''%%.zip%%'' below to prevent any issues). If you are starting out with these labs, follow the steps below (NOTE: **you can use your ''%%fep%%'' instance via Python3 environments**): |
| - | * [If in the lab] Log-in as admin (need admin user/password from lab host) | + | * Install the necessary tools (not necessary on ''%%fep%%''): |
| - | * [If in the lab] Select Administration->Software Sources and then select the romanian repos for high bandwidth | + | <code> |
| - | * Install the necessary tools: sudo apt-get install build-essential setuptools python-dev | + | sudo apt install build-essential python3-dev |
| - | * Install pip: sudo apt-get install pip | + | sudo apt install python3-pip |
| - | * Install donna via pip: sudo pip install donna25519 | + | |
| - | * Log-out from admin account | + | |
| - | * Log-in with student as usual | + | |
| - | + | ||
| - | === Task 1 === | + | |
| - | Create a common master_secret for two clients which communicate through a server. (TODO 1.1 & TODO 1.2) | + | |
| - | Print it on both clients and make sure they both have the same secret. | + | |
| - | + | ||
| - | == How to run == | + | |
| - | Open three different terminals. | + | |
| - | + | ||
| - | First terminal: | + | |
| - | <code>python main_server.py</code> | + | |
| - | + | ||
| - | Second terminal: | + | |
| - | <code>python main_client.py</code> | + | |
| - | + | ||
| - | Third terminal: | + | |
| - | <code>python main_client.py | + | |
| - | MSG <id_other_client> Hello!</code> | + | |
| - | + | ||
| - | Second terminal: | + | |
| - | <code>RECV</code> | + | |
| - | + | ||
| - | <code python 'main_client.py'> | + | |
| - | from wa_client import Client | + | |
| - | from wa_server import Server | + | |
| - | + | ||
| - | SERVER_IP = '127.0.0.1' | + | |
| - | SERVER_PORT = 7778 | + | |
| - | + | ||
| - | if __name__ == '__main__': | + | |
| - | c = Client(SERVER_IP, SERVER_PORT) | + | |
| - | while True: | + | |
| - | cmd = raw_input('MSG <user_id> <message>\n') | + | |
| - | cmd = cmd.split(" ") | + | |
| - | if cmd[0] == "MSG": | + | |
| - | user_id = int(cmd[1]) | + | |
| - | msg = " ".join(cmd[2:]) | + | |
| - | c.send_message(user_id, msg) | + | |
| - | elif cmd[0] == "RECV": | + | |
| - | msg = c.recv_message() | + | |
| - | print msg | + | |
| </code> | </code> | ||
| - | + | * Use ''%%wget%%'' to download the required zip (find it below) | |
| - | <code python 'main_server.py'> | + | * Create a Python3 environment, make sure PyPI is up to date and install the required packages: |
| - | from wa_client import Client | + | <code> |
| - | from wa_server import Server | + | python3 -m venv create env |
| - | + | source ./env/bin/activate | |
| - | SERVER_PORT = 7778 | + | pip install --upgrade pip |
| - | + | pip install cryptography donna25519 | |
| - | if __name__ == '__main__': | + | |
| - | s = Server(SERVER_PORT) | + | |
| - | s.start() | + | |
| </code> | </code> | ||
| - | <code python 'wa_client.py'> | + | **If local installation does not work, use your ''%%fep%%'' instance.** |
| - | from donna25519 import * | + | |
| - | from Crypto.Cipher import AES | + | |
| - | from Crypto import Random | + | |
| - | import struct | + | |
| - | import socket | + | |
| - | import hashlib | + | |
| - | import hmac | + | |
| - | from math import ceil | + | |
| - | O_NUM = 10 # number of initial One-Time Pre Keys | ||
| - | hash_len = 32 | + | === Task - Vertical & Horizontal ratcheting === |
| - | def hmac_sha256(key, data): | + | |
| - | return hmac.new(key, data, hashlib.sha256).digest() | + | |
| - | def hkdf(length, ikm, salt = b""): | + | See the previous lab for how to create a common ''%%master_secret%%'' for two clients which communicate through a server. |
| - | """Parameters: | + | |
| - | length: output length in bytes | + | |
| - | ikm: input key material | + | |
| - | """ | + | |
| - | prk = hmac_sha256(salt, ikm) | + | |
| - | t = b"" | + | |
| - | okm = b"" | + | |
| - | for i in range(int(ceil(1.0 * length / hash_len))): | + | |
| - | t = hmac_sha256(prk, t + bytes([1+i])) | + | |
| - | okm += t | + | |
| - | return okm[:length] | + | |
| - | class ClientSession: | + | Then, send messages with different keys each time, by recalculating the Chain Key according to the Signal Protocol. |
| - | def __init__(self, root_key, chain_key_s, chain_key_r, eph_own, eph_peer): | + | |
| - | self.root_key = root_key | + | |
| - | self.chain_key_s = chain_key_s | + | |
| - | self.chain_key_r = chain_key_r | + | |
| - | self.eph_own = eph_own | + | |
| - | self.eph_peer = eph_peer | + | |
| - | def update_keys(self, eph_peer): | + | Recalculate the Root Key for each round trip with the new DH public keys sent in messages. |
| - | """Performs vertical ratcheting | + | |
| - | Updates root & chain keys to match peer's (for decrypting its messages) | + | |
| - | Then updates root & chain keys again with fresh ephemereal key (for encrypting | + | |
| - | my messages) | + | |
| - | """ | + | |
| - | self.eph_peer = eph_peer | + | |
| - | # Update RootKey & receiving ChainKey | + | |
| - | # TODO 2 Obtain new root_key and chain_key_r from hkdf over | + | |
| - | # DH(eph_own, eph_peer) and root_key (can be used as salt parameter in hkdf) | + | |
| - | self.root_key = "" | + | |
| - | self.chain_key_r = "" | + | |
| - | # Update RootKey & sending ChainKey | + | For this task you need to embed a new ephemeral public key in each message, in order to create a new RootKey. |
| - | self.eph_own = None # TODO 2 Generate new ephemereal key pair | + | |
| - | # TODO 2 Obtain new root_key and chain_key_s from hkdf over | + | |
| - | # DH(eph_own, eph_peer) and root_key (can be used as salt parameter in hkdf) | + | |
| - | self.root_key = "" | + | |
| - | self.chain_key_s = "" | + | |
| - | class Client: | + | You can find a good description of the ratcheting protocol [[https://signal.org/docs/specifications/doubleratchet/|here]]. |
| - | def __init__(self, server_ip, server_port): | + | |
| - | self.I = PrivateKey() # Identity Key Pair | + | |
| - | self.S = PrivateKey() # Signed Pre Key | + | |
| - | self.O_queue = [PrivateKey() for i in xrange(O_NUM)] # One-Time Pre Keys | + | |
| - | self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | + | For the sake of simplicity, we will consider that all messages are in-order and none of them is lost. |
| - | self.s.connect((server_ip, server_port)) | + | |
| - | self.user_id = self.register() | + | |
| - | # self.existing_sessions[user_id] = session with that user (ClientSession) | + | You may start this lab from {{:ac:laboratoare:lab08.zip|this}} code. |
| - | self.existing_sessions = {} # initially no existing session | + | |
| - | def register(self): | + | <note> |
| - | """Lets the server know about its presence""" | + | To generate the root keys and chain keys (e.g. in the method update_keys) you need to basically apply |
| - | # TODO 1.1 send public keys of I, S and the list of O to the server | + | the HKDF method provided (hkdf) with a 64 byte output (512 bits), |
| - | # e.g. self.s.send(self.I.get_public().public) | + | which is then split into the root key (first 32 bytes) and chain key (last 32 bytes) |
| - | # You may also print a log message, e.g. print "Sent I = " + self.I.get_public().public.encode('hex') | + | |
| - | # You can send the list of O by first sending its length and then the keys one by one | + | You should do the same also in the method record_new_client_session, for the case when the client is the initiator. |
| - | # use struct.pack('!i', my_int) to send 4 byte signed integers | + | When the client is not the initiator both chain keys (chain_key_s and chain_key_r) will be initialized after receiving a message, |
| + | so you can keep them as 'None'. As initiator you should only initialize root_key and chain_key_s as explained above, | ||
| + | while chain_key_r can be left as 'None' for now. | ||
| + | </note> | ||
| - | # receive your user id from the server (might need later) | + | == How to run == |
| - | # use struct.unpack('!i', received_int)[0] to get an integer | + | Open three different terminals. |
| - | # e.g. struct.unpack('!i', self.s.recv(4))[0] | + | |
| - | user_id = 0 # TODO 1.1 change with received user id | + | First terminal (start the server): |
| - | print "Got User ID = %d" % user_id | + | <code>python main_server.py</code> |
| - | return user_id | + | |
| - | + | ||
| - | def get_send_message_key(self, peer_user_id): | + | |
| - | """Gets sending MessageKey using ChainKey from peer_user_id's ClientSession""" | + | |
| - | session = self.existing_sessions[peer_user_id] | + | |
| - | + | ||
| - | # TODO 2 Generate message_key from chain_key_s (session.chain_key_s) | + | |
| - | # message_key = HMAC_SHA256(ChainKey, 0x01) | + | |
| - | message_key = None | + | |
| - | + | ||
| - | # TODO 2 Update chain_key_s | + | |
| - | # ChainKey = HMAC_SHA256(ChainKey, 0x02) | + | |
| - | session.chain_key_s = None | + | |
| - | + | ||
| - | return message_key | + | |
| - | + | ||
| - | def get_recv_message_key(self, peer_user_id): | + | |
| - | """Gets MessageKey using ChainKey from peer_user_id's ClientSession""" | + | |
| - | session = self.existing_sessions[peer_user_id] | + | |
| - | + | ||
| - | # TODO 2 Generate message_key from chain_key_r (session.chain_key_r) | + | |
| - | # message_key = HMAC_SHA256(ChainKey, 0x01) | + | |
| - | message_key = None | + | |
| - | + | ||
| - | # TODO 2 Update chain_key_r | + | |
| - | # ChainKey = HMAC_SHA256(ChainKey, 0x02) | + | |
| - | session.chain_key = None | + | |
| - | + | ||
| - | return message_key | + | |
| - | + | ||
| - | def pad_message(self, message): | + | |
| - | pad_number = (-len(message)) % 16 | + | |
| - | if pad_number == 0: | + | |
| - | pad_number += 16 | + | |
| - | + | ||
| - | message += ''.join([chr(pad_number) for i in xrange(pad_number)]) | + | |
| - | return message | + | |
| - | + | ||
| - | def unpad_message(self, message): | + | |
| - | pad_number = ord(message[-1]) | + | |
| - | message = message[:-pad_number] | + | |
| - | return message | + | |
| - | + | ||
| - | def send_message(self, to_user_id, message): | + | |
| - | """Sends a message to user with id <to_user_id> | + | |
| - | If there is no existing ClientSession between them, they create one | + | |
| - | """ | + | |
| - | if to_user_id not in self.existing_sessions: | + | |
| - | self.setup_session(to_user_id) | + | |
| - | + | ||
| - | self.s.send("SEND") | + | |
| - | self.s.send(struct.pack('!i', to_user_id)) | + | |
| - | + | ||
| - | # Get MessageKey for next message | + | |
| - | message_key = self.get_send_message_key(to_user_id) | + | |
| - | + | ||
| - | # Encrypt message using MessageKey | + | |
| - | iv = Random.new().read(AES.block_size) | + | |
| - | cipher = AES.new(message_key, AES.MODE_CBC, iv) | + | |
| - | message = self.pad_message(message) | + | |
| - | enc_message = iv + cipher.encrypt(message) | + | |
| - | + | ||
| - | # TODO 2 Send own ephemereal public key (get it from the ClientSession with to_user_id) | + | |
| - | session = self.existing_sessions[to_user_id] | + | |
| - | raw_eph_own = session.eph_own.get_public().public | + | |
| - | + | ||
| - | # TODO 2 Send encrypted message length to server | + | |
| - | + | ||
| - | # TODO 2 Send encrypted message to server | + | |
| - | + | ||
| - | def recv_message(self): | + | |
| - | """Receives a message | + | |
| - | It can be either from a new session exchange (tag=NEWS), or an actual message(tag=MESG)""" | + | |
| - | tag = self.s.recv(4) | + | |
| - | if tag == "NEWS": # NEW Session | + | |
| - | ini_user_id = struct.unpack('!i', self.s.recv(4))[0] | + | |
| - | self.setup_session_rec(ini_user_id) | + | |
| - | tag = self.s.recv(4) | + | |
| - | + | ||
| - | if tag == "MESG": # message incoming | + | |
| - | # Get sender user id | + | |
| - | sender_user_id = struct.unpack('!i', self.s.recv(4))[0] | + | |
| - | + | ||
| - | # Get ephemereal key | + | |
| - | raw_eph_peer = self.s.recv(32) | + | |
| - | + | ||
| - | + | ||
| - | session = self.existing_sessions[sender_user_id] | + | |
| - | # TOD2 3 If received eph_peer is unknown (different than the one stored in the | + | |
| - | # ClientSession), update the keys | + | |
| - | # The first time current eph_peer will be None, so make sure you update in that | + | |
| - | # case also | + | |
| - | if False: # change with actual condition | + | |
| - | eph_peer = PublicKey(raw_eph_peer) | + | |
| - | session.update_keys(eph_peer) | + | |
| - | + | ||
| - | # Get message length | + | |
| - | mlen = struct.unpack('!i', self.s.recv(4))[0] | + | |
| - | + | ||
| - | # Get message | + | |
| - | enc_message = self.s.recv(mlen) | + | |
| - | + | ||
| - | # Decrypt message using MessageKey | + | |
| - | message_key = self.get_recv_message_key(sender_user_id) | + | |
| - | iv = enc_message[:16] | + | |
| - | enc_message = enc_message[16:] | + | |
| - | cipher = AES.new(message_key, AES.MODE_CBC, iv) | + | |
| - | msg = cipher.decrypt(enc_message) | + | |
| - | + | ||
| - | msg = self.unpad_message(msg) | + | |
| - | + | ||
| - | return msg | + | |
| - | + | ||
| - | def record_new_client_session(self, master_secret, client_id, eph_peer = None): | + | |
| - | """Forges RootKey and ChainKey from master_secret and stores them in a ClientSession | + | |
| - | which is then put in the existing_sessions dictionary""" | + | |
| - | + | ||
| - | root_key = master_secret # master_secret is considered as the base root key | + | |
| - | if eph_peer: # initiator considers Srec as initial peer ephemereal key | + | |
| - | eph_own = PrivateKey() # Generate new send ephemereal key | + | |
| - | + | ||
| - | # TODO 2 Get root_key and chain_key_s with hkdf over DH(eph_own, eph_peer) and | + | |
| - | # master secret | + | |
| - | root_key = "" | + | |
| - | chain_key_s = "" | + | |
| - | chain_key_r = None # Will initialize with first received message | + | |
| - | else: # the non-initiator (first receiver) will enter this branch | + | |
| - | eph_own = self.S # initiator considers receiver's S as first "ephemereal" key | + | |
| - | chain_key_s = None | + | |
| - | chain_key_r = None | + | |
| - | + | ||
| - | new_client_session = ClientSession(root_key, chain_key_s, chain_key_r, eph_own, eph_peer) | + | |
| - | self.existing_sessions[client_id] = new_client_session | + | |
| - | + | ||
| - | def setup_session(self, to_user_id): | + | |
| - | """Initiates a new session with to_user_id""" | + | |
| - | self.s.send("GENS") # GENerate Session command | + | |
| - | self.s.send(struct.pack('!i', to_user_id)) | + | |
| - | resp = self.s.recv(4) | + | |
| - | if resp == "FAIL": | + | |
| - | print "User does not exist" | + | |
| - | return False | + | |
| - | + | ||
| - | # TODO 1.1 Get Irec (done), Srec, Orec from server | + | |
| - | Irec = PublicKey(self.s.recv(32)) | + | |
| - | # Srec = ... | + | |
| - | # Prec = ... | + | |
| - | + | ||
| - | # TODO 1.1 Generate Eini (ephemeral private key of initiator) | + | |
| - | # Eini = ... (see above for function to retrieve ephemeral private key) | + | |
| - | + | ||
| - | # TODO 1.1 Compute master_secret from DH(Iini, Srec), DH(Eini, Irec), DH(Eini, Srec), DH(Eini, Orec) | + | |
| - | # See method do_exchange in donna library: https://github.com/Muterra/donna25519 | + | |
| - | # Use '+' to concatenate output of various DH exchanges | + | |
| - | master_secret = "" | + | |
| - | print master_secret.encode('hex') | + | |
| - | + | ||
| - | # TODO 1.2 Send Eini to the other client (through server) | + | |
| - | + | ||
| - | # Generate new ClientSession using master_secret | + | |
| - | self.record_new_client_session(master_secret, to_user_id, Srec) | + | |
| - | + | ||
| - | def setup_session_rec(self, from_user_id): | + | |
| - | """Receives a new session with from_user_id""" | + | |
| - | + | ||
| - | # TODO 1.2 Receive initiator's public keys Eini, Iini and Orec_pub | + | |
| - | Eini = PublicKey(self.s.recv(32)) | + | |
| - | # Iini = ... | + | |
| - | # Orec_pub = ... | + | |
| - | + | ||
| - | # TODO 1.2 Get private Orec from my O_queue based on received public Orec and then remove from O_queue | + | |
| - | + | ||
| - | # TODO 1.2 Compute master_secret from DH(Iini, Srec), DH(Eini, Irec), DH(Eini, Srec), DH(Eini, Orec) | + | |
| - | # See above, in method setup_session | + | |
| - | master_secret = "" | + | |
| - | print master_secret.encode('hex') | + | |
| - | # Generate new ClientSession using master_secret | + | Second terminal (start the first client and enter ''%%RECV%%'' mode: |
| - | self.record_new_client_session(master_secret, from_user_id) | + | <code> |
| + | python main_client.py | ||
| + | RECV | ||
| </code> | </code> | ||
| - | <code python 'wa_server.py'> | + | Third terminal (start the second client and send a message): |
| - | from donna25519 import * | + | <code> |
| - | import socket | + | python main_client.py |
| - | import threading | + | MSG <id_other_client> Hello! |
| - | import struct | + | |
| - | + | ||
| - | TCP_IP = '127.0.0.1' | + | |
| - | MAX_CLIENTS = 10 | + | |
| - | + | ||
| - | class ClientRecord: | + | |
| - | """Structure for keeping record of a client's public keys""" | + | |
| - | def __init__(self, I, S, O_queue, clientsocket): | + | |
| - | self.I = I | + | |
| - | self.S = S | + | |
| - | self.O_queue = O_queue | + | |
| - | self.s = clientsocket | + | |
| - | + | ||
| - | class Server: | + | |
| - | def __init__(self, port): | + | |
| - | self.port = port | + | |
| - | self.next_id = 1 | + | |
| - | + | ||
| - | self.registered_clients = {} | + | |
| - | + | ||
| - | def start(self): | + | |
| - | """Start listening to client connections""" | + | |
| - | print "Starting server on port %d" % self.port | + | |
| - | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | + | |
| - | s.bind((TCP_IP, self.port)) | + | |
| - | s.listen(MAX_CLIENTS) | + | |
| - | while True: | + | |
| - | c, addr = s.accept() # connection with new client | + | |
| - | print "New client from address " + str(addr) | + | |
| - | threading.Thread(target = self.on_new_client, args = (c, addr)).start() | + | |
| - | + | ||
| - | s.close() | + | |
| - | + | ||
| - | def on_new_client(self, clientsocket, addr): | + | |
| - | """Runs when a new client connects to the server""" | + | |
| - | I = PublicKey(clientsocket.recv(32)) | + | |
| - | print "Received I = " + I.public.encode('hex') | + | |
| - | S = PublicKey(clientsocket.recv(32)) | + | |
| - | print "Received S = " + S.public.encode('hex') | + | |
| - | o_num = struct.unpack('!i', clientsocket.recv(4))[0] | + | |
| - | O_queue = [] | + | |
| - | for i in xrange(o_num): | + | |
| - | O_queue.append(PublicKey(clientsocket.recv(32))) | + | |
| - | print "Received O = " + O_queue[i].public.encode('hex') | + | |
| - | + | ||
| - | user_id = self.register_client(I, S, O_queue, clientsocket) | + | |
| - | clientsocket.send(struct.pack('!i', user_id)) | + | |
| - | + | ||
| - | while True: | + | |
| - | cmd = clientsocket.recv(4) | + | |
| - | if cmd == "GENS": # GENerate Session | + | |
| - | raw_id = clientsocket.recv(4) | + | |
| - | rec_user_id = struct.unpack('!i', raw_id)[0] | + | |
| - | if rec_user_id not in self.registered_clients: | + | |
| - | clientsocket.send("FAIL") | + | |
| - | continue | + | |
| - | clientsocket.send("GOOD") | + | |
| - | # Send Irec, Srec, Orec to initiator | + | |
| - | Irec = self.registered_clients[rec_user_id].I | + | |
| - | Srec = self.registered_clients[rec_user_id].S | + | |
| - | Orec = self.registered_clients[rec_user_id].O_queue.pop() | + | |
| - | clientsocket.send(Irec.public) | + | |
| - | clientsocket.send(Srec.public) | + | |
| - | clientsocket.send(Orec.public) | + | |
| - | + | ||
| - | # get Eini from initiator | + | |
| - | Eini = PublicKey(clientsocket.recv(32)) | + | |
| - | + | ||
| - | rec_clientsocket = self.registered_clients[rec_user_id].s | + | |
| - | rec_clientsocket.send("NEWS") # NEW Session | + | |
| - | rec_clientsocket.send(struct.pack('!i', user_id)) | + | |
| - | # Send Eini, Iini, Orec to recipient | + | |
| - | rec_clientsocket.send(Eini.public) | + | |
| - | rec_clientsocket.send(I.public) | + | |
| - | rec_clientsocket.send(Orec.public) | + | |
| - | if cmd == "SEND": # SEND Message | + | |
| - | # Get recipient used id | + | |
| - | raw_id = clientsocket.recv(4) | + | |
| - | rec_user_id = struct.unpack('!i', raw_id)[0] | + | |
| - | + | ||
| - | rec_clientsocket = self.registered_clients[rec_user_id].s | + | |
| - | rec_clientsocket.send("MESG") # send message tag | + | |
| - | + | ||
| - | # Send sender user id | + | |
| - | rec_clientsocket.send(struct.pack('!i', user_id)) | + | |
| - | + | ||
| - | # Forward sender ephemereal key | + | |
| - | eph_s = clientsocket.recv(32) | + | |
| - | rec_clientsocket.send(eph_s) | + | |
| - | + | ||
| - | # Forward message length | + | |
| - | raw_mlen = clientsocket.recv(4) | + | |
| - | rec_clientsocket.send(raw_mlen) | + | |
| - | + | ||
| - | # Forward message | + | |
| - | mlen = struct.unpack('!i', raw_mlen)[0] | + | |
| - | raw_msg = clientsocket.recv(mlen) | + | |
| - | rec_clientsocket.send(raw_msg) | + | |
| - | + | ||
| - | def register_client(self, I, S, O_queue, clientsocket): | + | |
| - | """Registers new client's I, S, and O_queue and returns the new client's id""" | + | |
| - | new_client = ClientRecord(I, S, O_queue, clientsocket) | + | |
| - | + | ||
| - | user_id = self.next_id | + | |
| - | self.next_id += 1 | + | |
| - | + | ||
| - | self.registered_clients[user_id] = new_client | + | |
| - | + | ||
| - | return user_id | + | |
| </code> | </code> | ||
| - | |||
| - | |||