/// <summary> /// Creates a message and all it's fragments. From a file. /// </summary> /// <param name="remoteEndPoint">Where to send the message</param> /// <param name="file">File to send</param> /// <param name="maxFragmentSize">Maximum size of one fragment in bytes</param> public static Message CreateFileMessage(IPEndPoint remoteEndPoint, string file, ushort maxFragmentSize) { // read file bytes var fileBytes = File.ReadAllBytes(file); // create a sorted list of fragments var fragmentList = new SortedList<ushort, byte[]>(); // add all fragments while (fileBytes.Length > 0) { // get the fragment bytes byte[] bytes; // check the remaining size if (fileBytes.Length > maxFragmentSize) { // take maximum allowed bytes and make a fragment bytes = fileBytes.Take(maxFragmentSize).ToArray(); // shift fileBytes = fileBytes.Skip(maxFragmentSize).ToArray(); } else { // take whole fragment bytes = fileBytes; fileBytes = new byte[0]; } // add the fragment to the list fragmentList.Add((ushort) fragmentList.Count, bytes); } // create a message var message = new Message((ushort) fragmentList.Count, fragmentList) { RemoteEndPoint = remoteEndPoint, FileName = Path.GetFileName(file) }; // return the message return message; }
/// <summary> /// Sends a fragment. /// </summary> private Task SendFragment(Message message, byte[] fragment, FragmentType type) { // create a task return Task.Run(() => { // check the listening state if (_listening) { // error Log.Singleton.LogError("Can't send a fragment while listening"); // delete message lock (message) { MessageCenter.Singleton.Messages.Remove(BitConverter.ToUInt16(message.ID, 0)); } // cancel return; } // append CRC checksum CRC.GenerateChecksum(ref fragment); // need to lock to prevent multiple tasks trying to send simultaneously lock (_clientMutex) { // check if we want to lose some fragments if (Options.LoseFragments) { // get a random value var random = new Random(); if ((ushort) random.Next() % 100 < 30) { // 30% of fragments will be lost // log that Log.Singleton.LogMessage($"Purposefully losing a fragment of type <{type}>"); // exit return; } } // check if we want to corrupt some fragments if (Options.SendCorrupt) { // get a random value var random = new Random(); if ((ushort) random.Next() % 100 < 40) { // 40% of fragments will be corrupted // log that Log.Singleton.LogMessage($"Purposefully corrupting a fragment of type <{type}>"); // change random bytes for (int i = 0, imax = Math.Abs(random.Next() + 2) % 16; i < imax; ++i) { fragment[random.Next() % fragment.Length] = (byte) random.Next(); } } } // create a client _client = new UdpClient(); // bind it try { _client.Client.Bind(new IPEndPoint(IPAddress.Any, Options.Port)); } catch (SocketException) { // nope, can't bind to that port Log.Singleton.LogError($"Failed to bind the sending socket to the port <{Options.Port}>"); // remove client _client = null; // remove message lock (message) { MessageCenter.Singleton.Messages.Remove(BitConverter.ToUInt16(message.ID, 0)); } // stop return; } // log lock (message) { Log.Singleton.LogMessage( $"Sending a <{type}> fragment for message <{message.ID[0].ToString("00")}{message.ID[1].ToString("00")}> to <{message.RemoteEndPoint}>"); } // send the fragment lock (message) { _client.Send(fragment, fragment.Length, message.RemoteEndPoint); } // close and remove client _client.Close(); _client = null; } }); }
/// <summary> /// Sends end fragment for a message. /// </summary> private void SendEndFragment(Message message, int retries = 0) { // create a task Task.Run(() => { // ask for an end fragment byte[] fragment; lock (message) { fragment = Fragmenter.MakeEndFragment(message); } // send the fragment SendFragment(message, fragment, FragmentType.End) // listen .ContinueWith(task => Listen()) // handle timeout .ContinueWith(task => { // check the result if (!task.Result.Result) { // timeout, check retries if (retries < Options.Retries) SendEndFragment(message, retries + 1); else { // message timed out lock (message) { MessageCenter.Singleton.Messages.Remove(BitConverter.ToUInt16(message.ID, 0)); // log Log.Singleton.LogError( $"Message <{message.ID[0].ToString("00")}{message.ID[1].ToString("00")}> timed out"); MessageCenter.Singleton.FireChange(MessageStatus.TimedOut); } } } }); }); }
/// <summary> /// Send a prepare fragment for a message. /// </summary> public void SendPrepareFragment(Message message, int retries = 0) { // create a task Task.Run(() => { // create a prepare fragment byte[] prepareFragment; lock (message) { prepareFragment = Fragmenter.MakePrepareFragment(message); } // update status lock (message) { message.Status = MessageStatus.Handshaking; } // log that lock (message) { Log.Singleton.LogMessage( $"Message <{message.ID[0].ToString("00")}{message.ID[1].ToString("00")}> is in state <{message.Status}>"); MessageCenter.Singleton.FireChange(message.Status); } // go ahead, send it SendFragment(message, prepareFragment, FragmentType.Prepare) // listen .ContinueWith(task => Listen()) // handle timeout .ContinueWith(task => { // check the result if (!task.Result.Result) { // timeout, check retries if (retries < Options.Retries) SendPrepareFragment(message, retries + 1); else { // message timed out lock (message) { MessageCenter.Singleton.Messages.Remove(BitConverter.ToUInt16(message.ID, 0)); // log Log.Singleton.LogError( $"Message <{message.ID[0].ToString("00")}{message.ID[1].ToString("00")}> timed out"); MessageCenter.Singleton.FireChange(MessageStatus.TimedOut); } } } }); }); }
/// <summary> /// Sends a prepared fragment for a message. /// </summary> public void SendPreparedFragment(Message message) { // create a task Task.Run(() => { // ask for a prepared fragment byte[] fragment; lock (message) { fragment = Fragmenter.MakePreparedFragment(message); } // send the fragment SendFragment(message, fragment, FragmentType.Prepared) // listen .ContinueWith(task => Listen(false)); }); }
/// <summary> /// Sends ending okay fragment. /// </summary> public void SendOkayFragment(Message message) { // create a task Task.Run(() => { // ask for an end fragment byte[] fragment; lock (message) { fragment = Fragmenter.MakeOkayFragment(message); } // send the fragment SendFragment(message, fragment, FragmentType.Okay) // set status .ContinueWith(task => { lock (message) { message.Status = MessageStatus.Finished; Log.Singleton.LogMessage( $"Message <{message.ID[0].ToString("00")}{message.ID[1].ToString("00")}> is in state <{message.Status}>"); MessageCenter.Singleton.FireChange(message.Status); } }); }); }
/// <summary> /// Sends a missing fragment for a message. /// </summary> public void SendMissingFragment(Message message, List<ushort> missingList) { // create a task Task.Run(() => { // ask for a missing fragment byte[] fragment; lock (message) { fragment = Fragmenter.MakeMissingFragment(message, missingList); } // send the fragment SendFragment(message, fragment, FragmentType.Missing) // listen .ContinueWith(task => Listen(false)); }); }
/// <summary> /// Sends only missing parts of a message. /// </summary> public void SendDataFragments(Message message, List<ushort> missingList) { // create a task Task.Run(() => { // set state lock (message) { message.Status = MessageStatus.Transmitting; } // log that lock (message) { Log.Singleton.LogMessage( $"Message <{message.ID[0].ToString("00")}{message.ID[1].ToString("00")}> is in state <{message.Status}>"); MessageCenter.Singleton.FireChange(message.Status); } // send missing data fragments var sendTasks = new List<Task>(); lock (message) { foreach (var missingNumber in missingList) { // request a data fragment var fragment = Fragmenter.MakeDataFragment(message, missingNumber); // send it var task = SendFragment(message, fragment, FragmentType.Data); sendTasks.Add(task); task.ContinueWith(t => { lock (message) { // update progress MessageCenter.Singleton.FireProgress(missingNumber, message.PartCount); // increment data counter message.DataCounter++; } }); } } // TODO: Handle keep-alive fragments Task.WhenAll(sendTasks).ContinueWith(task => { // set state lock (message) { message.Status = MessageStatus.Ending; } // log that lock (message) { Log.Singleton.LogMessage( $"Message <{message.ID[0].ToString("00")}{message.ID[1].ToString("00")}> is in state <{message.Status}>"); MessageCenter.Singleton.FireChange(message.Status); } // send end fragment SendEndFragment(message); }); }); }
/// <summary> /// Creates a message and all it's fragments. /// </summary> /// <param name="remoteEndPoint">Where to send the message</param> /// <param name="messageString">String to send</param> /// <param name="maxFragmentSize">Maximum size of one fragment in bytes</param> public static Message CreateMessage(IPEndPoint remoteEndPoint, string messageString, ushort maxFragmentSize) { // convert message string to bytes var messageStringBytes = Encoding.ASCII.GetBytes(messageString); // create a sorted list of fragments var fragmentList = new SortedList<ushort, byte[]>(); // add all fragments while (messageStringBytes.Length > 0) { // get the fragment bytes byte[] bytes; // check the remaining size if (messageStringBytes.Length > maxFragmentSize) { // take maximum allowed bytes and make a fragment bytes = messageStringBytes.Take(maxFragmentSize).ToArray(); // shift messageStringBytes = messageStringBytes.Skip(maxFragmentSize).ToArray(); } else { // take whole fragment bytes = messageStringBytes; messageStringBytes = new byte[0]; } // add the fragment to the list fragmentList.Add((ushort) fragmentList.Count, bytes); } // create a message var message = new Message((ushort) fragmentList.Count, fragmentList) { RemoteEndPoint = remoteEndPoint, Text = messageString }; // return the message return message; }
/// <summary> /// Makes a prepare fragment. /// </summary> public static byte[] MakePrepareFragment(Message message) { // the resulting array of bytes var data = new List<byte>(); // add the type data.Add((byte) FragmentType.Prepare); // add the id data.AddRange(message.ID); // add fragment count data.AddRange(BitConverter.GetBytes(message.PartCount)); // add name data.AddRange(Encoding.ASCII.GetBytes(Options.Name)); // return data return data.ToArray(); }
/// <summary> /// Makes an okay fragment. /// </summary> public static byte[] MakeOkayFragment(Message message) { // the resulting array of bytes var data = new List<byte>(); // add the type data.Add((byte) FragmentType.Okay); // add the id data.AddRange(message.ID); // return data return data.ToArray(); }
/// <summary> /// Makes a missing fragment. /// </summary> public static byte[] MakeMissingFragment(Message message, List<ushort> missingList) { // the resulting array of bytes var data = new List<byte>(); // add the type data.Add((byte) FragmentType.Missing); // add the id data.AddRange(message.ID); // add missing fragments foreach (var missingFragment in missingList) { data.AddRange(BitConverter.GetBytes(missingFragment)); } // return data return data.ToArray(); }
/// <summary> /// Makes a data fragment for a fragment of a message. /// </summary> public static byte[] MakeDataFragment(Message message, ushort number) { // the resulting array of bytes var data = new List<byte>(); // add the type data.Add((byte) FragmentType.Data); // add the id data.AddRange(message.ID); // add the fragment number data.AddRange(BitConverter.GetBytes(number)); // add data data.AddRange(message.PartList[number]); // return data return data.ToArray(); }