ipkcpd is a C# command-line application that starts a server listening on a specified IPv4 address and port. It supports two modes of operation: TCP and UDP:
ipkcpd -h <host> -p <port> -m <mode: udp|tcp>
Where host is IPv4 address, port is port and mode must be specified either as udp or as tcp.
Project can be built using make which executes dotnet to build the desired project.
Makefile contains options:
make- default make, builds and publishes ipkcpd project,make build- builds ipkcpd project,make test- builds and runs ipkcpd.Tests.
The project contains two subprojects: ipkcpd and ipkcpd.Tests.
ipkcpd is a server that uses IPK Calculator Protocol and serves as a calculator.
The server can be run either as UDP server or as TCP server.
Class Program contains Main method which contains argument parsing using System.CommandLine NuGet package from Microsoft (only in prerelease) and based on specified mode runs either TcpServer or UdpServer class.
The main body also contains SIGINT handler, which, based on specified mode, tries to dispose of the currently running server and all its resources (which in case of TCP includes sending last message "BYE" to all connected clients) before shutting down.
UdpServer class contains implementation of UDP server. It uses UdpClient from System.Net.Sockets namespace. First, it validates that first and second byte are as specified in IPK Calculator Protocol and then proceeds with calling class ExpressionParser to solve the given expression. In case of an exception it sends back a message with information of the error that was found in the specified message or expression.
TcpServer class contains partial implementation of TCP server. It uses TcpListener from System.Net.Sockets namespace. It awaits incomming clients and after a client joins it is send to an asynchronously running task which calls class TcpClientHandler. This cycle continues till the server receives SIGINT. This enables the TCP server to have joined multiple clients at the same time. If SIGINT is received, all of those handlers are disposed (the dispose method of each handler contains implementation of sending the last "BYE" to the specified client before closing the connection).
TcpClientHandler class contains implementation for servicing of the specific client. First it awaits "HELLO" from the client and after that proceeds with parsing sent queries. In case of an invalid message the handler sends "BYE" and closes the connection with the specific client.
ExpressionParser class contains implementation for parsing and solving given expression based on message format specified in IPK Calculator Protocol. First it validates the expression and then continues with solving it. In case of any error, exception InvalidExpressionException is thrown with corresponding discription.
Testing was done using XUnit.net Framework.
Tests are split into tests for ExpressionParser (valid and invalid inputs), TcpServer and UdpServer.
The tests:
| Test Name | Result | Time Elapsed |
|---|---|---|
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput.InvalidInput_ThrowInvalidExpressionException(expr: " (+ 1 2)") | Passed | 3 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ValidInputs_ReturnExpectedResult(input: "(* 1 2 3 4 5)", expected: 120) | Passed | 5 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput.InvalidInput_ThrowInvalidExpressionException(expr: "+ 1 2)") | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput.InvalidInput_ThrowInvalidExpressionException(expr: " ") | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ValidInputs_ReturnExpectedResult(input: "(+ (- 2 1) (* 1 1))", expected: 2) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ValidInputs_ReturnExpectedResult(input: "(- (* 2 3 4) (/ 16 2) (+ 5 4))", expected: 7) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput.InvalidInput_ThrowInvalidExpressionException(expr: "(/ 5 (- 3 3))") | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ValidInputs_ReturnExpectedResult(input: "(* 3 4)", expected: 12) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ValidInputs_ReturnExpectedResult(input: "(/ 1000 5 2 2 5)", expected: 10) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput.InvalidInput_ThrowInvalidExpressionException(expr: "(+ 1 2) ") | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ValidInputs_ReturnExpectedResult(input: "(/ (+ (/ (+ (/ 121 (* (/ 2048 64 2) 5 3 2 18)) (* ", expected: 3) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ValidInputs_ReturnExpectedResult(input: "(+ (/ 10 2) (* (- 3 4) (+ 1 2)))", expected: 2) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput.InvalidInput_ThrowInvalidExpressionException(expr: "(+ 1 )") | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ValidInputs_ReturnExpectedResult(input: "(- 12 4 2 1)", expected: 5) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput.InvalidInput_ThrowInvalidExpressionException(expr: "(1 2)") | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ValidInputs_ReturnExpectedResult(input: "(- 100 50 20 10)", expected: 20) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ReturnExpectedResult(input: "(+ 2 2)", expected: 4) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput._ThrowInvalidExpressionException(expr: "(+ 1)") | Passed | < 1 ms |
| MessageWithTwoLines_CorrectResults | Passed | 8 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ReturnExpectedResult(input: "(* (+ 3 4 5) (- 8 6) (/ 20 4))", expected: 120) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ReturnExpectedResult(input: "(+ 5 (* 3 2))", expected: 11) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput._ThrowInvalidExpressionException(expr: "(* (* 4 5 (* 4 5) ) ") | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ReturnExpectedResult(input: "(* 2 3 4)", expected: 24) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput._ThrowInvalidExpressionException(expr: "(/ 0 0)") | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ReturnExpectedResult(input: "(- 7 3 1)", expected: 3) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ReturnExpectedResult(input: "(/ 100 2 2 5)", expected: 5) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput._ThrowInvalidExpressionException(expr: "(* - 4 5)") | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ReturnExpectedResult(input: "(/ (- 10 6) (+ 1 2 3) (* 3 4))", expected: 0) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ReturnExpectedResult(input: "(* 2 3 4 5)", expected: 120) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput._ThrowInvalidExpressionException(expr: "(/ 5 0)") | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ReturnExpectedResult(input: "(- 10 (/ 20 2))", expected: 0) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ReturnExpectedResult(input: "(+ 1 (* 2 3) (/ 4 2))", expected: 9) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput._ThrowInvalidExpressionException(expr: "(+ 1 2") | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ReturnExpectedResult(input: "(/ 200 (+ 100 50) (* 2 2))", expected: 0) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ReturnExpectedResult(input: "(/ 10 2)", expected: 5) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput._ThrowInvalidExpressionException(expr: "(/ 0 (- 3 3))") | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ReturnExpectedResult(input: "(+ (* 2 3) (/ 8 2) (- 10 6))", expected: 14) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput._ThrowInvalidExpressionException(expr: "( * 4 5 ) ") | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ReturnExpectedResult(input: "(* (- 5 2) (+ 1 2 3) (/ 18 3))", expected: 108) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ValidInputs_ReturnExpectedResult(input: "(* 2 (+ 3 4) (- 5 1))", expected: 56) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput.InvalidInput_ThrowInvalidExpressionException(expr: "(* -4 5)") | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ValidInputs_ReturnExpectedResult(input: "(+ 1 (/ 5 5))", expected: 2) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput.InvalidInput_ThrowInvalidExpressionException(expr: "(*(/ 4 4))HELLO") | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ValidInputs_ReturnExpectedResult(input: "(/ 16 2 2)", expected: 4) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput.InvalidInput_ThrowInvalidExpressionException(expr: "(* --4 5)") | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ValidInputs_ReturnExpectedResult(input: "(+ 2 (* 3 4) (- 10 6))", expected: 18) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ValidInputs_ReturnExpectedResult(input: "(* (- 4 2) (+ 3 4) (/ 10 2))", expected: 70) | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsInvalidInput.InvalidInput_ThrowInvalidExpressionException(expr: "") | Passed | < 1 ms |
| ipkcpd.Tests.ExpressionSolverTests.ExpressionSolverTestsValidInput.ValidInputs_ReturnExpectedResult(input: "(- 8 5)", expected: 3) | Passed | < 1 ms |
| ipkcpd.Tests.Server.TcpServer.TcpServerTests.MultipleClients_NotThrowOrDisconnect | Passed | 928 ms |
| ipkcpd.Tests.Server.UdpServer.UdpServerTests.SendInvalidMessage_Receive1OnSecondIndex | Passed | 949 ms |
| ipkcpd.Tests.Server.UdpServer.UdpServerTests.SendCorrectMessage_Receive0OnSecondIndex | Passed | 104 ms |
| ipkcpd.Tests.Server.TcpServer.TcpServerTests.SendInvalidMessage_GetBye | Passed | 999 ms |
| ipkcpd.Tests.Server.TcpServer.TcpServerTests.SendFirstHello_GetHello | Passed | 1 s |
| ipkcpd.Tests.Server.TcpServer.TcpServerTests.DisconnectClientWithoutBye_NotThrow | Passed | 1 ms |
| ipkcpd.Tests.Server.TcpServer.TcpServerTests.TwoCorrectExpressionsInARow_CorrectResults | Passed | 1 ms |
| Test Run Successful. | ||
| Total tests: 56 | ||
| Passed: 56 | ||
| Total time: 4.6924 Seconds |
[System.Net.Sockets] https://learn.microsoft.com/cs-cz/dotnet/api/system.net.sockets.socket?view=net-7.0 [XUnit] https://xunit.net/ Used testing framework [IPK Calculator Protocol] https://git.fit.vutbr.cz/NESFIT/IPK-Projekty/src/branch/master/Project%201/Protocol.md