/// Version 1.0.0 /// Last modified 23.5.2014 /// /// Copyright (C) 2014 Veli-Mikko Puupponen /// /// Halyri-system is a prototype emergency call system. Its purpose is to /// demonstrate the use of the advanced capabilities available in the current /// generation smartphones in facilitating the emergency service dispatcher's /// capability to determine the nature of the emergency and to dispatch help. /// /// For more information, see the README file of this package. /// /// The MIT License (MIT) /// /// Permission is hereby granted, free of charge, to any person obtaining a copy /// of this software and associated documentation files (the "Software"), to /// deal in the Software without restriction, including without limitation the /// rights to use, copy, modify, merge, publish, distribute, sublicense, /// and/or sell copies of the Software, and to permit persons to whom the /// Software is furnished to do so, subject to the following conditions: /// /// The above copyright notice and this permission notice shall be included in /// all copies or substantial portions of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING /// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS /// IN THE SOFTWARE. /// using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SimpleUdpMediaClient.Packets { /// Veli-Mikko Puupponen /// /// The class representts a user-level media packet. The packet /// is used when receiving and sending media using the UdpMediaClientSocket. /// It encapsulates the complete media data and media type information. It can /// provide a collection of NetworkPackets conforming to the network MTU /// for network transfer. It can later be reconstructed from such packets at /// the receiving end. /// public class MediaPacket { private MediaInformation mediaInformation; private DateTime initializationTime; private byte[] completePayloadData; private Guid userGuid; private Int32 originatingDataLength; private Int32 totalPacketCount; private bool constructedFromSubpackets = false; private MediaHeaderPacket headerPacket = null; private MediaContinuationPacket[] continuationPackets; private bool hasAllContinuationPackets = true; private bool payloadLengthMismatched = false; private Int64 sequence; /// /// The function to get the original length of the data prior to encoding into the /// specified transfer format. /// public Int32 OriginatingDataLength { get { return originatingDataLength; } } /// /// The function to get and set the sequence number of this packet. /// public Int64 Sequence { get { return sequence; } set { sequence = value; } } /// /// The function returns True, if not reconstructed from a network packet /// sequence or hass all packets from the relevant /// sequence. False otherwise. /// public bool HasAllContinuationPackets { get { return hasAllContinuationPackets; } } /// /// The function returns True, if the packet is reconstructed from a network packet /// sequence and one or more subpackets had an unexpected /// payload data length. Otherwise false. /// public bool PayloadLengthMismatched { get { return payloadLengthMismatched; } } /// /// The function to get the string Guid identifying the sender. /// public string SenderGuid { get { return (userGuid == null) ? "" : userGuid.ToString(); } } /// /// The function to get the time of the instantialization of the instance. /// public DateTime InitializationTime { get { return initializationTime; } } /// /// The function to get the complete payload data for this instance. /// public byte[] CompletePayloadData { get { return completePayloadData; } } /// /// The function to get the MediaInformation describing the media in this instance. /// Can be null on a packet constructed from a subpacket transfer /// sequence. /// public MediaInformation PacketMediaInformation { get { return mediaInformation; } } /// /// The function nstantializes a new MediaPacket with the specified media information, payload data, /// sender Guid and sequence number. /// /// It throws an ArgumentException if any of the arguments is null or the payload data length /// is zero. /// /// The MediaInformation instance describing the data contained /// in this instance. /// The media data. /// The sequence number for all subpackets generated from this /// packet during network transmission. /// The string representation of the sender's Guid. public MediaPacket(MediaInformation mediaInfo, byte[] mediaPayload, Int64 sequenceNumber, string guid) : this(mediaInfo, mediaPayload, guid) { sequence = sequenceNumber; } /// /// The function nstantializes a new MediaPacket with the specified media information, payload data, /// sender Guid, original data length and sequence number. /// /// It throws an ArgumentException if any of the arguments is null or the payload data length /// is zero. /// /// The MediaInformation instance describing the data contained /// in this instance. /// The media data. /// The string representation of the sender's Guid. /// The riginal length of the data prior to encoding into the /// specified transfer format. /// The sequence number for all subpackets generated from this /// packet during network transmission. public MediaPacket(MediaInformation mediaInfo, byte[] mediaPayload, string guid, Int32 originatingLength, Int64 sequenceNumber) : this(mediaInfo, mediaPayload, guid, originatingLength) { sequence = sequenceNumber; } /// /// The function nstantializes a new MediaPacket with the specified media information, payload data, /// sender Guid and original data length. /// /// It throws an ArgumentException if any of the arguments is null or the payload data length /// is zero. /// /// The MediaInformation instance describing the data contained /// in this instance. /// The media data. /// The string representation of the sender's Guid. /// The riginal length of the data prior to encoding into the /// specified transfer format. public MediaPacket(MediaInformation mediaInfo, byte[] mediaPayload, string guid, Int32 originatingLength) : this(mediaInfo, mediaPayload, guid) { originatingDataLength = originatingLength; } /// /// The function nstantializes a new MediaPacket with the specified media information, payload data and /// sender Guid. /// /// It throws an ArgumentException if any of the arguments is null, the payload data length /// is zero or the Guid format is incorrect. /// /// The MediaInformation instance describing the data contained /// in this instance. /// The media data. /// The string representation of the sender's Guid. public MediaPacket(MediaInformation mediaInfo, byte[] mediaPayload, string guid) { if (mediaInfo == null || mediaPayload == null || guid == null || guid.Length == 0) throw new ArgumentException("Null or empty parameters"); initializationTime = System.DateTime.Now; mediaInformation = mediaInfo; completePayloadData = mediaPayload; if (!Guid.TryParse(guid, out userGuid)) throw new ArgumentException("Guid format error"); } /// /// The function nstantializes a new MediaPacket using the MediaHeaderPacket /// from a network transfer packet sequence. If MediaPacket /// from which the MediaHeaderPacket was derived and had no more /// than one network MTU of payload data, this MediaPacket /// instance is now complete (HasAllContinuationPackets = true). /// /// Otherwise the MediaContinuationPackets from the transfer /// sequence need to be added to this instace using the method /// AddContinuationPacket. /// /// It throws an ArgumentException if the MediaHeaderPacket is not valid. /// /// The MediaHeaderPacket instance. public MediaPacket(MediaHeaderPacket newHeaderPacket) { if (newHeaderPacket == null || newHeaderPacket.SourceGuid == null || newHeaderPacket.SourceGuid.Length == 0 || newHeaderPacket.PayloadData == null || newHeaderPacket.PayloadData.Length == 0) throw new ArgumentException("Header is null of faulted"); constructedFromSubpackets = true; headerPacket = newHeaderPacket; totalPacketCount = newHeaderPacket.TotalPacketCount; userGuid = new Guid(newHeaderPacket.SourceGuid); sequence = newHeaderPacket.Sequence; originatingDataLength = newHeaderPacket.OriginatingDataLength; if (newHeaderPacket.TotalPacketCount > 1) { hasAllContinuationPackets = false; continuationPackets = new MediaContinuationPacket[totalPacketCount - 1]; completePayloadData = new byte[newHeaderPacket.TotalPayloadDataLength]; } else { hasAllContinuationPackets = true; completePayloadData = headerPacket.PayloadData; } mediaInformation = new MediaInformation((MediaFormat)newHeaderPacket.MediaFormat, (TransferCompression)newHeaderPacket.MediaTransferCompression); } /// /// The function nstantializes a new MediaPacket using any MediaContinuationPacket /// from a network transfer packet sequence. All MediaPackets /// require at least the MediaHeaderPacket from the relevant /// packet sequence generated from the original MediaPacket. /// Therefore this MediaPacket will not be complete until /// at least the MediaHeaderPacket has been added using the /// method AddHeaderPacket. /// /// Otherwise the MediaContinuationPackets from the transfer /// sequence need to be added to this instace using the method /// AddContinuationPacket. /// /// It throws an ArgumentException if the MediaHeaderPacket is not valid. /// /// The MediaContinuationPacket that is a part of /// the related transfer packet sequence. public MediaPacket(MediaContinuationPacket oneContinuationPacket) { if (oneContinuationPacket == null || oneContinuationPacket.PayloadData == null || oneContinuationPacket.PayloadData.Length == 0) throw new ArgumentException("Packet cannot be null"); constructedFromSubpackets = true; hasAllContinuationPackets = false; totalPacketCount = oneContinuationPacket.TotalPacketCount; userGuid = new Guid(oneContinuationPacket.SourceGuid); sequence = oneContinuationPacket.Sequence; continuationPackets = new MediaContinuationPacket[totalPacketCount - 1]; if ((oneContinuationPacket.PacketNumber - 1) < continuationPackets.Length) continuationPackets[oneContinuationPacket.PacketNumber - 1] = oneContinuationPacket; else throw new ArgumentException("Packet is faulted"); } /// /// The function adds a header packet to this MediaPacket. If there /// already is a header packet in this instance, it returns /// the HasAllContinuationPackets indicating whether some continuation /// packets are missing or not. /// /// If the MediaHeaderPacket is valid for this instance and /// no MediaContinuationPackets are missing, it returns true indicating /// that this instance is now complete. /// /// It throws an ArgumentException if the MediaHeaderPacket is not valid or /// has a mismatching sequence number or guid. /// /// The header packet for supplying first part /// of the payload data and overall information to this instance. /// True, if this instance now has all subpackets from the /// relevant transfer packet sequence, otherwise false. public bool AddHeaderPacket(MediaHeaderPacket newHeaderPacket) { if (headerPacket != null) return hasAllContinuationPackets; if (newHeaderPacket == null || newHeaderPacket.PayloadData == null) throw new ArgumentException("Header cannot be null"); if (newHeaderPacket.PayloadData.Length == 0) throw new ArgumentException("Header must have payload"); if (newHeaderPacket.Sequence != sequence) throw new ArgumentException("Header sequence number mismatch"); if (newHeaderPacket.SourceGuid != SenderGuid) throw new ArgumentException("Header GUID mismatch"); headerPacket = newHeaderPacket; mediaInformation = new MediaInformation((MediaFormat)newHeaderPacket.MediaFormat, (TransferCompression)newHeaderPacket.MediaTransferCompression); originatingDataLength = newHeaderPacket.OriginatingDataLength; completePayloadData = new byte[headerPacket.TotalPayloadDataLength]; CheckHasAllSubpackets(); if (hasAllContinuationPackets) ConcatenatePayloadFromSubpackets(); return hasAllContinuationPackets; } /// /// The function adds a MediaContinuationPacket to this MediaPacket. It returns a boolean /// indicaing if all MediaContinuationPackets necessary to complete this /// MediaPacket have now been added. /// /// It throws an ArgumentException if the MediaContinuationPacket is not valid or /// has a mismatching sequence number or guid. /// /// The MediaContinuationPacket that is a part of /// the related transfer packet sequence. /// True, if this MediaPacket now has all continuation packets, otherwise false. public bool AddContinuationPacket(MediaContinuationPacket continuationPacket) { if (hasAllContinuationPackets) return hasAllContinuationPackets; if (!constructedFromSubpackets) throw new NotSupportedException("Instance not constructed from subpackets"); if (continuationPacket == null || continuationPacket.Sequence != Sequence || (continuationPacket.PacketNumber - 1) >= continuationPackets.Length) throw new ArgumentException("Segment null, sequence number misnatched or packet number incorrect"); continuationPackets[continuationPacket.PacketNumber - 1] = continuationPacket; if (headerPacket == null) return false; CheckHasAllSubpackets(); if (hasAllContinuationPackets) ConcatenatePayloadFromSubpackets(); return hasAllContinuationPackets; } /// /// The function iterates over the continuation packet array. If there are /// any packets missing, hasAllContinuationPackets = false. /// If all packets are available, hasAllContinuationPackets /// will be set to true. /// private void CheckHasAllSubpackets() { bool continuationPacketListFull = true; foreach (MediaContinuationPacket packet in continuationPackets) { if (packet == null) { continuationPacketListFull = false; break; } } hasAllContinuationPackets = continuationPacketListFull; } /// /// The function reconstructs the payload data of the original MediaPacket /// by concatenating the payload from the MediaHeaderPacket and /// all subsequent MediaContinuationPackets. Sets the data to /// the completePayloadData array. /// private void ConcatenatePayloadFromSubpackets() { int actualPayloadLength = 0; int payloadCopyTargetIndex = 0; actualPayloadLength += headerPacket.PayloadData.Length; Array.Copy(headerPacket.PayloadData, 0, completePayloadData, payloadCopyTargetIndex, headerPacket.PayloadData.Length); payloadCopyTargetIndex += headerPacket.PayloadData.Length; for (int i = 0; i < continuationPackets.Length; i++) { if (continuationPackets[i].PayloadData != null && (completePayloadData.Length - payloadCopyTargetIndex) >= continuationPackets[i].PayloadData.Length) { Array.Copy(continuationPackets[i].PayloadData, 0, completePayloadData, payloadCopyTargetIndex, continuationPackets[i].PayloadData.Length); payloadCopyTargetIndex += continuationPackets[i].PayloadData.Length; actualPayloadLength += continuationPackets[i].PayloadData.Length; } else actualPayloadLength += continuationPackets[i].PayloadData.Length; } if (actualPayloadLength != completePayloadData.Length) payloadLengthMismatched = true; } /// /// The function converts this MediaPacket into a collection of NetworkPackets for /// network transfer. All resulting networkPackets will have a byte /// representation with length no more that the specified MTU. /// /// The MTU has to be 100 bytes or more. /// It throws an ArgumentException if MTU < 100. /// /// /// The maximal network transmission unit size used to /// limit the resulting NetworkPacket payload sizes. /// A collection of mediapackets from the given mediapacket. public INetworkPacket[] GetAllTransmissionPackets(int mtu) { if (mtu < 100) throw new ArgumentException("MTU too low."); INetworkPacket[] transferPackets = null; if (!constructedFromSubpackets) { int dataConsumedByHeaderPacket = mtu - MediaHeaderPacket.HeaderLengthInOctets; int dataConsumedByContinuationPacket = mtu - MediaContinuationPacket.HeaderLengthInOctets; int dataRemainingFromHeaderPacket = completePayloadData.Length - dataConsumedByHeaderPacket; int numberOfContinuationPackets = (int)Math.Ceiling(dataRemainingFromHeaderPacket / (dataConsumedByContinuationPacket * 1.0)); byte[] payloadInHeaderPacket; if (dataRemainingFromHeaderPacket <= 0) { totalPacketCount = 1; payloadInHeaderPacket = completePayloadData; } else { totalPacketCount = 1 + numberOfContinuationPackets; payloadInHeaderPacket = new byte[dataConsumedByHeaderPacket]; Array.Copy(completePayloadData, 0, payloadInHeaderPacket, 0, payloadInHeaderPacket.Length); } transferPackets = new INetworkPacket[totalPacketCount]; continuationPackets = new MediaContinuationPacket[totalPacketCount - 1]; headerPacket = new MediaHeaderPacket(userGuid.ToByteArray(), (short)mediaInformation.Format, (short)mediaInformation.Compression, totalPacketCount, completePayloadData.Length, originatingDataLength, payloadInHeaderPacket, sequence); transferPackets[0] = headerPacket; for (int i = 1; i < totalPacketCount; i++) { int baseIndex = dataConsumedByHeaderPacket + ((i - 1) * dataConsumedByContinuationPacket); int copyLength = completePayloadData.Length - baseIndex; byte[] continuationPayload; if (copyLength < dataConsumedByContinuationPacket) continuationPayload = new byte[copyLength]; else continuationPayload = new byte[dataConsumedByContinuationPacket]; Array.Copy(completePayloadData, baseIndex, continuationPayload, 0, continuationPayload.Length); MediaContinuationPacket currentContinuationPacket = new MediaContinuationPacket(userGuid.ToByteArray(), i, totalPacketCount, continuationPayload, sequence); transferPackets[i] = currentContinuationPacket; continuationPackets[i - 1] = currentContinuationPacket; } } if (constructedFromSubpackets && hasAllContinuationPackets) { transferPackets = new INetworkPacket[totalPacketCount]; transferPackets[0] = headerPacket; Array.Copy(continuationPackets, 0, transferPackets, 1, continuationPackets.Length); } return transferPackets; } } }