# Final Exam - Practical Part

> Distributed Systems and Network Programming - Spring 2023

## Task 1 - UDP Socket Programming [8.5 Points]

Write a UDP server and a client using Python `socket` module to implement a minimalistic DNS service

- The client sends queries to the server over a UDP socket
- The server replies with a Resource Record (RR) from a predefined list or an error if the record is not found

### Client (`client.py`)

- Create a `class Query` with two string attributes (`type, key`)
- Once started, the client creates the following three `Query` instances

      Query(type="A", key="example.com")
      Query(type="PTR", key="1.2.3.4")
      Query(type="CNAME", key="moodle.com")

- Serialize the instances using `json` module and send them (one by one) over UDP to the listening server
- Receive and deserialize the server reply, then print the resulting dictionary to the standard output

### Server (`server.py`)

- Create a `class RR` with three string attributes (`type`, `key`, `value`)
- Once started, the server listens on all interfaces on port `50000`
- The server runs forever, unless a `KeyboardInterrupt` is received
- The server maintains a list of resource records. The list initially contains the following two records

      RR(type="A", key="example.com", value="1.2.3.4")
      RR(type="PTR", key="1.2.3.4", value="example.com")

- Once the server receives a `Query` from a client, it replies with the requested record or an error
- To create an error reply, add `'value': 'NXDOMAIN'` to the received query and send it back to the client

### Example Run

    $ python3 server.py
    Server: listening on 0.0.0.0:50000
    Client: {'type': 'A', 'key': 'example.com'}
    Server: Record found. Sending answer.
    Client: {'type': 'PTR', 'key': '1.2.3.4'}
    Server: Record found. Sending answer.
    Client: {'type': 'CNAME', 'key': 'moodle.com'}
    Server: Record not found. Sending error.
    ^CServer: Shutting down...

    $ python3 client.py
    Client: Sending query for example.com
    Server: {'type': 'A', 'key': 'example.com', 'value': '1.2.3.4'}
    Client: Sending query for 1.2.3.4
    Server: {'type': 'PTR', 'key': '1.2.3.4', 'value': 'example.com'}
    Client: Sending query for moodle.com
    Server: {'type': 'CNAME', 'key': 'moodle.com', 'value': 'NXDOMAIN'}

## Task 2 - Remote Procedure Call [8.5 Points]

Write a server and a client to implement a simple remote calculator

- The client calls stub functions to do elementary mathematical operations
- The server executes the corresponding implementations to calculate the required results and send them back to the client
- Communication happens over the local network, with Protocol Buffers as the data serialization format

### Server (`server.py`)

- Run a gRPC server that listens on all interfaces on port `50000`
- The server should run forever unless a `KeyboardInterrupt` is received
- Expose the following functions for RPC. The logic for each function is explained below:
  - `Add(a: int, b: int)` returns `a + b`
  - `Subtract(a: int, b: int)` returns `a - b`
  - `Multiply(a: int, b: int)` returns `a * b`
  - `Divide(a: int, b: int)` returns `a / b` or `math.nan` if `b` equals zero
- Handle remote calls from the client and return the required results

### Client (`client.py`)

- Once executed, the client calls the following remote functions one by one

      Add(10,2)
      Subtract(10,2)
      Multiply(10,2)
      Divide(10,2)
      Divide(10,0)

- The client should print the query and the received results, as shown in the example run below

### Schema file (`calculator.proto`)

- Defines the protocol of communication between the client and the server
  - The calculator `service` with remote functions that can be called through `rpc`
  - Request/response `message` types for the client/server communication
- Make sure you have `grpcio` and `grpcio-tools` installed. You may use a virtual environment if needed
- Compile the `.proto` file using the following command

      python3 -m grpc_tools.protoc --proto_path=. \
      --python_out=. --grpc_python_out=. calculator.proto

### Example Run

    $ python3 server.py
    gRPC server is listening on 0.0.0.0:50000
    Add(10,2)
    Subtract(10,2)
    Multiply(10,2)
    Divide(10,2)
    Divide(10,0)
    ^CShutting down...

    $ python3 client.py
    Add(10,2) = 12.0
    Subtract(10,2) = 8.0
    Multiply(10,2) = 20.0
    Divide(10,2) = 5.0
    Divide(10,0) = nan