diff --git a/Hw4.Tests/Hw4.Tests.csproj b/Hw4.Tests/Hw4.Tests.csproj
new file mode 100644
index 0000000..af4c0e9
--- /dev/null
+++ b/Hw4.Tests/Hw4.Tests.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net7.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Hw4.Tests/ServerAndClientTests.cs b/Hw4.Tests/ServerAndClientTests.cs
new file mode 100644
index 0000000..b0706fe
--- /dev/null
+++ b/Hw4.Tests/ServerAndClientTests.cs
@@ -0,0 +1,80 @@
+namespace Hw4.Tests;
+
+using NUnit.Framework.Internal;
+
+
+public class Tests
+{
+ private Client client;
+ private Server server;
+
+ [OneTimeSetUp]
+ public void SetUp()
+ {
+ Directory.CreateDirectory("testDir");
+ File.WriteAllText("testDir/test.txt", "test message");
+
+ Directory.CreateDirectory("testDir/directory");
+ File.WriteAllText("testDir/directory/test.txt", "test message");
+ File.WriteAllText("testDir/test.txt", "test message");
+
+ Directory.CreateDirectory("downloadedFiles");
+
+ server = new Server(2303);
+ client = new Client(2303);
+ Task.Run(() => server.Start());
+ }
+
+ [OneTimeTearDown]
+ public void Delete()
+ {
+ server.Stop();
+ Directory.Delete("testDir", true);
+ Directory.Delete("downloadedFiles", true);
+ }
+
+ [Test]
+ public async Task ListTest()
+ {
+ var result = await client.List("testDir");
+ Assert.Multiple(() =>
+ {
+ Assert.That(result.size, Is.EqualTo(2));
+ Assert.That(result.Item2, Is.EqualTo(new List<(string, bool)> { ("testDir/directory", true), ("testDir/test.txt", false) }));
+ });
+ }
+
+ [Test]
+ public async Task GetTest()
+ {
+ using (FileStream SourceStream = File.Open("downloadedFiles/file1", FileMode.OpenOrCreate))
+ {
+ await client.Get("testDir/test.txt", SourceStream);
+ }
+
+ var expected = File.ReadAllBytes("testDir/test.txt");
+ var result = File.ReadAllBytes("downloadedFiles/file1");
+
+ Assert.That(result, Is.EqualTo(expected));
+ }
+
+ [Test]
+ public async Task GetForNotExistingFileTest()
+ {
+ var result = 0;
+ using (FileStream SourceStream = File.Open("downloadedFiles/file1", FileMode.OpenOrCreate))
+ {
+ result = await client.Get("notExistingFile.txt", SourceStream);
+ }
+
+ Assert.That(result, Is.EqualTo(-1));
+ }
+
+ [Test]
+ public async Task ListForNotExistingFileTest()
+ {
+ var result = await client.List("notExistingDirectory");
+
+ Assert.That(result.size, Is.EqualTo(-1));
+ }
+}
\ No newline at end of file
diff --git a/Hw4.Tests/Usings.cs b/Hw4.Tests/Usings.cs
new file mode 100644
index 0000000..9a28bd8
--- /dev/null
+++ b/Hw4.Tests/Usings.cs
@@ -0,0 +1 @@
+global using NUnit.Framework;
diff --git a/Hw4/Client.cs b/Hw4/Client.cs
new file mode 100644
index 0000000..39d6bc5
--- /dev/null
+++ b/Hw4/Client.cs
@@ -0,0 +1,143 @@
+using System.Net.Sockets;
+using System.Text;
+
+namespace Hw4;
+
+public class Client
+{
+ private string host;
+ private int port;
+
+ ///
+ /// Instantiates a new instance of Client class.
+ ///
+ public Client(int port, string host = "localhost")
+ {
+ this.port = port;
+ this.host = host;
+ }
+
+ ///
+ /// Requests a listing of files in a directory on the server and reads server's response
+ ///
+ /// The path on the server to list files from.
+ /// the parsed response from the server - how many entries in directory (size), list of entries with bool value - "is this entry a directory"
+ public async Task<(int size, List<(string, bool)>?)> List(string filePath)
+ {
+ using (var client = new TcpClient(host, port))
+ {
+ var stream = client.GetStream();
+ var writer = new StreamWriter(stream) { AutoFlush = true };
+ var reader = new StreamReader(stream);
+ await writer.WriteAsync("1 " + filePath + "\n");
+ try
+ {
+ return await ListResponse(reader);
+ }
+ catch
+ {
+ throw;
+ }
+ }
+ }
+
+ ///
+ /// Requests the file size and contents in bytes and writes the contents to the passed stream.
+ ///
+ /// The path to get files from.
+ /// The stream to which Get sends content from downloaded file.
+ /// amount of bytes in downloaded file
+ public async Task Get(string filePath, Stream outputStream)
+ {
+ using (var client = new TcpClient(host, port))
+ {
+ var stream = client.GetStream();
+ var writer = new StreamWriter(stream) { AutoFlush = true };
+ var reader = new StreamReader(stream);
+ await writer.WriteAsync("2 " + filePath + "\n");
+ try
+ {
+ return await GetResponse(reader, outputStream);
+ }
+ catch (InvalidDataException e)
+ {
+ throw new InvalidDataException(e.Message + ":Wrong server response");
+ }
+ catch (InvalidOperationException)
+ {
+ throw new InvalidOperationException("Asynchronous read operation returned null");
+ }
+ }
+ }
+
+ private static async Task<(int size, List<(string, bool)>?)> ListResponse(StreamReader reader)
+ {
+ var listResponse = await reader.ReadLineAsync() ?? throw new InvalidOperationException();
+ var parsedListResponse = listResponse.Split();
+ var isFirstItemNumber = int.TryParse(parsedListResponse[0], out int size);
+ if (!isFirstItemNumber)
+ {
+ throw new InvalidDataException("Amount of content in directory expected");
+ }
+
+ if (size == -1)
+ {
+ return (-1, null);
+ }
+
+ List<(string, bool)> directoryContent = new();
+
+ if (size * 2 != parsedListResponse.Length - 1)
+ {
+ throw new InvalidDataException("Size isn't equal to amount of content in directory");
+ }
+ for (int i = 1; i <= size; i++)
+ {
+ string currentPath = parsedListResponse[2 * i - 1];
+ bool currentPathIsDirectory;
+ if (parsedListResponse[i * 2] == "True")
+ {
+ currentPathIsDirectory = true;
+ }
+ else if (parsedListResponse[i * 2] == "False")
+ {
+ currentPathIsDirectory = false;
+ }
+ else
+ {
+ throw new InvalidDataException("True or false expected");
+ }
+ directoryContent.Add((currentPath, currentPathIsDirectory));
+ }
+
+ return (size, directoryContent);
+ }
+
+ private static async Task GetResponse(StreamReader reader, Stream output)
+ {
+ var response = await reader.ReadToEndAsync();
+
+ var responseArray = response.Split(" ", 2);
+ var stringSize = responseArray[0];
+
+ var isFirstItemNumber = int.TryParse(stringSize, out int size);
+
+ if (!isFirstItemNumber || size < -1)
+ {
+ throw new InvalidDataException("Amount of bytes in file expected");
+ }
+
+ if (size == -1)
+ {
+ return -1;
+ }
+
+ var content = responseArray[1];
+
+ await output.WriteAsync(Encoding.UTF8.GetBytes(content).AsMemory(0, size));
+ return size;
+ }
+}
+
+
+
diff --git a/Hw4/Hw4.csproj b/Hw4/Hw4.csproj
new file mode 100644
index 0000000..4658cbf
--- /dev/null
+++ b/Hw4/Hw4.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net7.0
+ enable
+ enable
+
+
+
diff --git a/Hw4/Hw4.sln b/Hw4/Hw4.sln
new file mode 100644
index 0000000..2bb4550
--- /dev/null
+++ b/Hw4/Hw4.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 25.0.1706.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hw4", "Hw4.csproj", "{9757B2DC-6B82-4675-B32F-615890E1AC73}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hw4.Tests", "..\Hw4.Tests\Hw4.Tests.csproj", "{03EAEE58-748F-446E-B3C3-0395DA404E39}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {9757B2DC-6B82-4675-B32F-615890E1AC73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9757B2DC-6B82-4675-B32F-615890E1AC73}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9757B2DC-6B82-4675-B32F-615890E1AC73}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9757B2DC-6B82-4675-B32F-615890E1AC73}.Release|Any CPU.Build.0 = Release|Any CPU
+ {03EAEE58-748F-446E-B3C3-0395DA404E39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {03EAEE58-748F-446E-B3C3-0395DA404E39}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {03EAEE58-748F-446E-B3C3-0395DA404E39}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {03EAEE58-748F-446E-B3C3-0395DA404E39}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {3D80B059-63A4-4CF8-B8EA-D0774CEDF1B8}
+ EndGlobalSection
+EndGlobal
diff --git a/Hw4/Server.cs b/Hw4/Server.cs
new file mode 100644
index 0000000..d86a772
--- /dev/null
+++ b/Hw4/Server.cs
@@ -0,0 +1,129 @@
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Hw4;
+public class Server
+{
+ private TcpListener listener;
+ private CancellationTokenSource cancellationTokenSource;
+
+ ///
+ /// Instantiates a new instance of Server class.
+ ///
+ public Server(int port)
+ {
+ listener = new TcpListener(IPAddress.Any, port);
+ cancellationTokenSource = new CancellationTokenSource();
+ }
+
+ ///
+ /// Starts the server and begins listening for client connections..
+ ///
+ public async Task Start()
+ {
+ listener.Start();
+ var serverTasks = new List();
+ while (!cancellationTokenSource.IsCancellationRequested)
+ {
+ var socket = await listener.AcceptSocketAsync();
+ serverTasks.Add(Task.Run(async () =>
+ {
+ var stream = new NetworkStream(socket);
+ var reader = new StreamReader(stream);
+ var writer = new StreamWriter(stream) { AutoFlush = true };
+
+ var data = await reader.ReadLineAsync();
+
+ if (data != null)
+ {
+ (int request, string filePath) = ClientRequestParse(data);
+ try
+ {
+ if (request == 1)
+ {
+ await List(filePath, writer);
+ }
+ else if (request == 2)
+ {
+ await Get(filePath, writer);
+ }
+ else
+ {
+ throw new ArgumentException("Expected number 1 or 2 in request");
+ }
+ }
+ catch (Exception e)
+ {
+ socket.Close();
+ throw new ArgumentException(e.Message + ": Wrong Client request");
+ }
+
+ }
+ else
+ {
+ socket.Close();
+ throw new InvalidOperationException("Asynchronous read operation returned null");
+ }
+ socket.Close();
+ }
+ ));
+ }
+ await Task.WhenAll(serverTasks);
+ }
+
+ private static (int, string) ClientRequestParse(string clientRequest)
+ {
+ var clientRequestParsed = clientRequest.Split();
+ var stringRequestType = clientRequestParsed[0];
+ bool isRequestTypeNumber = int.TryParse(stringRequestType, out int requestType);
+
+ if (!isRequestTypeNumber)
+ {
+ throw new ArgumentException("Number expected");
+ }
+
+ return (requestType, clientRequestParsed[1]);
+ }
+
+ private static async Task List(string path, StreamWriter writer)
+ {
+ if (!Directory.Exists(path))
+ {
+ await writer.WriteAsync("-1");
+ return;
+ }
+
+ var fileSystemEntries = Directory.GetFileSystemEntries(path);
+ var size = fileSystemEntries.Length;
+
+ await writer.WriteAsync(size.ToString());
+ foreach (var entry in fileSystemEntries)
+ {
+ await writer.WriteAsync($" {entry} {Directory.Exists(entry)}");
+ }
+ }
+
+ private static async Task Get(string path, StreamWriter writer)
+ {
+ if (!File.Exists(path))
+ {
+ await writer.WriteAsync("-1");
+ return;
+ }
+ var content = await File.ReadAllBytesAsync(path);
+ await writer.WriteAsync($"{content.Length} {Encoding.UTF8.GetString(content)}");
+ }
+
+ ///
+ /// Stops the server.
+ ///
+ public void Stop()
+ {
+ cancellationTokenSource.Cancel();
+ listener.Stop();
+ }
+}
+
+
+