2020 Programming Portfolio Combined Exercise: Graph Library and Dijkstra's Implementation
2020 Programming Portfolio Combined Exercise: Graph Library and Dijkstra's Implementation
In this exercise, you will develop a C program which combines aspects of all three modules, COMP1005,
COMP 1006 and COMP1007. This program will be a TCP/IP server that implements the version of Dijkstra’s
algorithm presented in COMP1006 to calculate the next hop across a network of networks.
The coursework is broken down into two parts, in the first part you will implement a set of C functions that
implement graphs using a linked list and implement the Dijkstra’s algorithm as presented in COMP1006. In
the second part of the exercise, you will develop a TCP/IP server that uses this library to be able to respond
to queries about what the next-hop should be to access one network from another.
Skeleton C files for both parts are supplied via git in the usual fashion, please ensure you use these for your
implementation. It is expected that you are developing and testing this coursework on the school’s Linux
systems.
Assessment Notes
This coursework will be marked automatically using the pipeline. The pipeline will mark your work and give
you a separate mark for each of the three modules. The table below shows the total number of marks available
for each module, what percentage of the portfolio component for that module comes from this exercise, and
finally what percentage of the final module mark comes from this exercise.
COMP1005 40 40%
COMP1006 25 37.5%
COMP1007 25 37.5%
The assessment criteria used to derive the mark for each module are listed at the end of each section.
Your provisional score for each task can be viewed on GitLab after every push to
projects.cs.nott.ac.uk. Your final score will be based on the code in the last commit pushed to
projects.cs.nott.ac.uk before the coursework deadline. Commits pushed after the coursework
deadline will be disregarded. After the coursework deadline, your code will undergo further review and,
based on this review, your provisional score may go up or down. This further review will include checks for
code plagiarism and for trivial implementations e.g. implementations just containing an empty main function
or clearly not written following the task guidelines.
Task 1
Your task is to implement a graph1 library.
A test program task1_test.c and graph library header file graph.h are provided for you. You must
not remove or edit these two files in any way.
Details
Graphs are fundamental data structures in Computer Science. They are commonly implemented using a
linked list using an adjacency list2 representation. You will use the implementation of a linked list provided
to implement a directed graph library using adjacency lists.
You do not need to implement a linked list library, as a full implementation is provided in the
linked_list.h and linked_list.c files. You must not edit these two files in any way. You should
use this implementation when implementing your graph library.
The header file which contain the interface for an implementation of a graph in C is given in the graph.h
file. This file contains the structures you will use to represent a graph, vertices, and edges. You will not need
to implement any further structures.
A skeleton implementation file graph.c which implements each of the functions declared the interface file
is also provided. Your task is to complete the implementation of the functions in graph.c. You should only
edit the graph.c file, and must not edit the graph.h file. You are free to implement the internals of each
function in any way you want, but you must not change the function definitions in the provided graph.c
file i.e. you must not change the function names, return types or parameters. All warnings and error
messages in your implementation should be printed to stderr. Further implementation guidelines are
given in the comments in graph.c. The print_graph function has been implemented and you must not
change this function.
A test program, task1_test.c, is provided which will include your implementation and use your
implementation to manipulate a graph. You must not edit the file task1_test.c or utility.h. To
compile your implementation and the test program, type:
$ make clean task1_test
To compile your implementation and the test program and then run the test program, type:
$ make clean task1_test_run
A typical output (your warning messages may vary) from a correctly working implementation is:
$ make task1_test_run
./task1_test 1:3:1 1:5:2 2:3:10 2:4:2 3:4:2
1
See https://en.wikipedia.org/wiki/Graph_(abstract_data_type)
2
See https://en.wikipedia.org/wiki/Adjacency_list
Performing Test 1...
warning: unable to remove vertex
warning: unable to find vertex
warning: unable to remove edge
warning: unable to find vertex
warning: unable to remove edge
warning: unable to add vertex
warning: unable to find vertex
warning: unable to add vertex
warning: unable to find vertex
warning: unable to add vertex
warning: unable to add edge
warning: unable to find vertex
warning: unable to add vertex
warning: unable to find vertex
warning: unable to add vertex
warning: unable to find vertex
warning: unable to print graph
warning: unable to free edge
warning: unable to free vertex
warning: unable to free graph
Completed Test 1.
Performing Test 2...
initialising graph...
adding edges...
removing graph...
Completed Test 2.
Performing Test 3...
initialising graph...
adding edges...
removing edges...
removing graph...
Completed Test 3.
Performing Test 4...
initialising graph...
adding edges...
removing vertices...
warning: unable to remove vertex
warning: unable to remove vertex
removing graph...
Completed Test 4.
Performing Test 5...
initialising graph...
adding edges...
printing graph...
1: 3[1.00] 5[2.00]
3: 1[1.00] 2[10.00] 4[2.00]
5: 1[2.00]
2: 3[10.00] 4[2.00]
4: 2[2.00] 3[2.00]
removing graph...
Completed Test 5.
Performing Test 6...
initialising graph...
adding edges...
removing edges...
adding edges...
printing graph...
1: 3[1.00] 5[2.00]
3: 1[1.00] 2[10.00] 4[2.00]
5: 1[2.00]
2: 3[10.00] 4[2.00]
4: 2[2.00] 3[2.00]
removing graph...
Completed Test 6.
Your implementation should handle dynamically allocated memory correctly, i.e. free all dynamically
allocated memory. To check your implementation using valgrind you can type:
$ make task1_test_memcheck
Task 2
Your task is to implement Dijkstra’s algorithm to find the shortest path between nodes in a graph.
A test program task2_test.c is provided for you. You must not remove or edit this file in any way.
Details
Dijkstra’s algorithm3 is an algorithm which is used to find the shortest path between nodes in a graph. In
this task, you will implement a function dijkstras which implements Dijkstra’s algorithm using the graph
library you developed in Task 1.
The interface for the dijkstras function is given in the graph.h file, and a skeleton implementation
and further notes are provided in the graph.c file. Your task is to complete the implementation of the
dijkstras function in graph.c. You should only edit the graph.c file, and must not edit the graph.h
file. The print_table function, which prints the table of shortest paths returned by your dijkstras
function, has been implemented for you and you must not change this function.
A test program, task2_test.c, is provided which will include your implementation of the dijkstras
function and use your implementation to find the shortest paths in an example graph. You must not edit the
file task2_test.c or utility.h. To compile your implementation and the test program, type:
$ make clean task2_test
To compile your implementation and the test program and then run the test program, type:
$ make clean task2_test_run
3
See https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm and also the Systems and Architecture video at
:
https://moodle.nottingham.ac.uk/course/view.php?id=102590#section-7
Your implementation should handle dynamically allocated memory correctly i.e. free all dynamically
allocated memory. As discussed in lectures, a good tool for assessing if a program has handled dynamic
memory allocation correctly is valgrind. To check your implementation using valgrind you can type:
$ make task2_test_memcheck
If your program has correctly handled dynamic memory allocation, the last line of output should read:
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Assessment Criteria
This part of the coursework is worth 40% of your final COMP1005 course grade. The points awarded for each
task are as follows:
Task 1 2 3 15 20
Task 2 2 3 15 20
For each task, you are awarded two points if your program compiles correctly: one point if it compiles without
errors, and a further one point if it compiles without errors and warnings. If your program follows the
implementation instructions given in the task details below, you are awarded three points.
For each of Tasks 1 and 2, if your program is implemented so that no memory leaks occur when it is executed,
you are awarded five points. If your program produces the correct output, you are awarded a further ten
points.
Final scores will be published on the COMP1005 moodle pages around a week after the coursework deadline.
Network Server
For the second part of this coursework, you are to write a TCP/IP server that can build up a graph of a network
of networks and then query that graph to find out which link between two networks should be used as the
next hop to send a packet of data from one network to another within the network of networks. The next
hop can be found by using the graph library and Dijkstra’s algorithm implemented in part one of this exercise,
as discussed in Systems and Architecture.4 The mark scheme for this exercise (see below) has been designed
so that it is still possible to obtain some marks for this section even if you have not completed the previous
section.
A skeleton file, server.c has been provided for you and you should implement your solution to this part
of the coursework within that file. This file can be built using the supplied Makefile by typing:
$ make server
4
See https://moodle.nottingham.ac.uk/course/view.php?id=102590#section-7
This will automatically compile your server and link it against your implementations of the graph library
above, provided in graph.c.
The graph is created and queried by clients connecting to the server and issuing commands to the server
following the protocol outlined below. The graph created should persist between different connections to
the server, but does not need to persist if the server is stopped and then restarted. The server should listen
for and accept incoming connections on the TCP/IP port specified as the first (and only) argument on the
command line, so if your program was called:
$ ./server 4242
where server is the name of your program, then your program should listen on port 4242.
To simplify things, we will represent the networks by an integer number between 1 and 255, and specify the
connections between two networks by specifying the source and destination network ID numbers along with
the weight (cost) of using that connection. You can assume that there is at most one link between two
networks. You should represent the networks as Vertices in your graph, and the links between networks as
edges.
Protocol
Your server should implement the following protocol, in most cases this can be done by writing C code to
parse the supplied command and arguments and then calling the appropriate function in the graph library
you implemented. Programs that do not implement all the functionality specified will still be able to obtain
marks (see the assessment criteria below) for the functionality implemented.
The client and server communicate in ASCII (i.e readable text) with data being sent as a series of lines of text,
terminated by both a carriage return (CR) and a line feed (LF).5 You can assume that no line of text will be
greater than 512 bytes long. All commands sent by the client to the server should be a single line long, while
the responses from the server will be at least one line long.
The first line of any response sent out by the server should indicate whether the action has been a success
or failure. In the case of success, the response should start with ‘+OK’ — in the case of an error, the response
should start with ‘-ERR’. Additional information may follow the ‘+OK’, ‘-ERR’ but this is optional except
where otherwise stated.
When a client connects to the server, the server will respond by sending a greeting. This is a simple line of
text that identifies the server. The greeting should begin with ‘+OK’ since the connection has been made.
The connection between the client and the server should be closed on receipt of the QUIT command from
the client. A single line response should be sent from the server to the client to indicate reception of the
command before the connection is terminated by calling close().
Your server should implement the following eight commands. As has been previously stated, most of these
commands can be implemented by making the appropriate calls to the graph library created in part one. If
your server receives any other command, it should respond with an error indicating that the command is not
implemented.
Protocol Commands
5
CRLF can be generated in C using \r\n
NET-ADD <integer>
Receipt of this command from the client should cause the server to add a new network to the graph. The ID
for the network is specified by the integer parameter. By default, the network is not connected to any other
network.
The server should respond appropriately to indicate success after adding the network to the graph. If a
network already exists with that ID, then the server should respond with an error and the graph should not
be altered.
NET-DELETE <integer>
Receipt of this command from the client should cause the server to delete an existing network from the
graph. The ID for the network is specified by the integer parameter. In addition, all edges (connections to
other networks) associated with this network should also be deleted.
The server should respond appropriately to indicate success after deleting the network from the graph. If no
network already exists with that ID, then the server should respond with an error and the graph should not
be altered.
NET-LIST
Receipt of this command should cause the server to list the IDs of all the networks that have been added to
it (using NET-ADD). The server will always respond with a success response containing the ‘+OK’ followed
by the number of networks to be listed, e.g.:
+OK 6
would indicate that details of six networks will follow. Following this line, the network IDs for the known
networks should be sent to the client, one per line. This command should never return an error response.
ROUTE-ADD <src network> <dest network> <weight>
Receipt of this command informs the server of a connection between two networks specified by their
network IDs as integers. Although the two networks are specified as source and destination the link should
be interpreted as being bidirectional. The weight is specified as an integer number indicating the cost of using
that link.
The specified link should be added to the graph, and an appropriate success response sent, unless one of the
network does not exist in which case an error should be sent. If the link already exists, then the weight should
be updated to the newly specified value.
ROUTE-DELETE <src network> <dest network>
Receipt of this command from the client should cause the server to delete the specified link between two
networks from the graph. The IDs for the networks are specified by the integer parameter.
The server should respond appropriately to indicate success after deleting the network from the graph. If the
link does not exist, then the server should respond with an error and the graph should not be altered.
ROUTE-SHOW <network>
Receipt of this command should cause the server to list the IDs of all the networks for which as link has been
added (by ROUTE-ADD) from the specified network. The server will respond with a success response
containing the ‘+OK’ followed by the number of networks to be listed, e.g.:
+OK 6
would indicate that details of six links will follow. Following this line, the network IDs for the other end of
the link should be sent to the client, one per line.
This command should never return an error response if the network does not exist.
ROUTE-HOP <src network> <dest network>
Receipt of this command should cause the server to query the graph and return the next hop where a packet
should be sent to forward it from the source network to the destination network. The networks are specified
using their integer IDs. Your server should run Dijkstra’s algorithm, as presented in Systems and Architecture,
on its graph and return the next-hop for the specified network, or -1 if there’s no possible route between the
two networks.
The program should respond indicating a success, following the ‘+OK’ with the network ID of the next hop
(i.e. the network to which this packet should be sent). An error should only be returned if either of the
networks does not exist, or if the source and destination networks are identical.
ROUTE-TABLE <network>
Receipt of this command should cause the server to list the complete routing table for the specified network.
The networks is specified using its integer IDs. Your server should run Dijkstra’s algorithm, as presented in
Systems and Architecture, on its graph and return the next-hop for every other network in the graph.
The server will respond with a success response containing the ‘+OK’ followed by the number of entries in
the routing table, e.g.:
+OK 6
This should then be followed by a line for each entry of the routing table (excluding the network specified),
in the following format:
<current> -> <destination>, next-hop <hop>, weight <weight>
where <current> is the integer ID for the network being queried, and <destination> is the network
ID of the destination network. <hop> is the network ID where packets should be sent to reach the
<destination> network (this may, or may not, be the same network), while <weight> should be the
cost of traversing that route (as calculated by Dijkstra’s algorithm).
Example Transaction
Outlined below is a sample transaction between the client and server. The C: and S: are not part of the
transaction and have been added for clarity to show which program sent the data.
S: +OK 2020 Programming Portfolio Route Server
C: NET-ADD 1
S: +OK Added 1
C: NET-ADD 2
S: +OK Added 2
C: NET-ADD 3
S: +OK Added 3
C: NET-ADD 4
S: +OK Added 4
C: NET-ADD 5
S: +OK Added 5
C: ROUTE-ADD 1 5 2
S: +OK Route Added
C: ROUTE-ADD 1 3 1
S: +OK Route Added
C: ROUTE-ADD 3 2 10
S: +OK Route Added
C: ROUTE-ADD 3 4 2
S: +OK Route Added
C: ROUTE-ADD 4 2 2
S: +OK Route Added
C: THIS-COMMAND-DOES-NOT-EXIST
S: -ERR Not Implemented
C: ROUTE-TABLE 3
S: +OK 5
S: 3 -> 1, next-hop 1, weight 1
S: 3 -> 2, next-hop 4, weight 4
S: 3 -> 3, next-hop NULL, weight -1
S: 3 -> 4, next-hop 4, weight 2
S: 3 -> 5, next-hop 1, weight 3
C: ROUTE-SHOW 5 3
S: +OK 1
S: 5 1 2
C: QUIT
S: +OK
The gitlab pipeline available for this coursework which will mark your program and give you feedback on your
implementation:
For COMP1006
Specifically, 25 marks will be awarded as follows:
• Program correctly calculates shortest next hop for a network of two networks [3 marks]
• Program correctly calculates shortest next hop for each node of a fully connected network of networks
(comprising more than two networks) [3 marks]
• Program correctly calculates shortest next hop for an arbitrary network of networks [3 marks]
• Program correctly shows that certain networks are unreachable if there is no route between two networks
[3 marks]
• Program correctly calculates the shortest next-hop when the connections between networks are updated
[9 marks]
• Program correctly opens a TCP socket on the port specified on the commands line that a client can connect
to [3 Marks]
• Server responds to the QUIT command by closing its side of the TCP/IP connection [3 marks]
• Server implements the protocol above according to the specification, broken down as follows:
• Server will correctly add, delete and list routes between nodes [3 marks]