/// 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;
}
}
}