/* This file is part of PacketDotNet PacketDotNet is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. PacketDotNet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PacketDotNet. If not, see . */ /* * Copyright 2010 Chris Morgan */ using System; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using System.IO; using System.Text; using System.Linq; using PacketDotNet.Utils; using MiscUtil.Conversion; namespace PacketDotNet { namespace Ieee80211 { /// /// .Net analog of a ieee80211_radiotap_header from airpcap.h /// public class RadioPacket : InternetLinkLayerPacket { // NOTE: No need to warn about lack of use, the compiler won't // put any calls to 'log' here but we need 'log' to exist to compile #pragma warning disable 0169, 0649 private static readonly ILogInactive log; #pragma warning restore 0169, 0649 /// /// Version 0. Only increases for drastic changes, introduction of compatible /// new fields does not count. /// public byte Version { get; set; } private byte VersionBytes { get { return header.Bytes[header.Offset + RadioFields.VersionPosition]; } set { header.Bytes[header.Offset + RadioFields.VersionPosition] = value; } } /// /// Length of the whole header in bytes, including it_version, it_pad, it_len /// and data fields /// public UInt16 Length {get; set;} private UInt16 LengthBytes { get { return EndianBitConverter.Little.ToUInt16(header.Bytes, header.Offset + RadioFields.LengthPosition); } set { EndianBitConverter.Little.CopyBytes(value, header.Bytes, header.Offset + RadioFields.LengthPosition); } } /// /// Returns an array of UInt32 bitmap entries. Each bit in the bitmap indicates /// which fields are present. Set bit 31 (0x8000000) /// to extend the bitmap by another 32 bits. Additional extensions are made /// by setting bit 31. /// private UInt32[] Present {get; set;} private UInt32[] ReadPresentFields() { // make an array of the bitmask fields // the highest bit indicates whether other bitmask fields follow // the current field var bitmaskFields = new List(); UInt32 bitmask = EndianBitConverter.Little.ToUInt32(header.Bytes, header.Offset + RadioFields.PresentPosition); bitmaskFields.Add(bitmask); int bitmaskOffsetInBytes = 4; while ((bitmask & (1 << 31)) == 1) { // retrieve the next field bitmask = EndianBitConverter.Little.ToUInt32(header.Bytes, header.Offset + RadioFields.PresentPosition + bitmaskOffsetInBytes); bitmaskFields.Add(bitmask); bitmaskOffsetInBytes += 4; } return bitmaskFields.ToArray(); } /// /// Initializes a new instance of the class. /// public RadioPacket() { Present = new UInt32[1]; RadioTapFields = new SortedDictionary(); Length = (ushort)RadioFields.DefaultHeaderLength; } internal RadioPacket (ByteArraySegment bas) { log.Debug (""); // slice off the header portion header = new ByteArraySegment (bas); header.Length = RadioFields.DefaultHeaderLength; Version = VersionBytes; Length = LengthBytes; // update the header size based on the headers packet length header.Length = Length; Present = ReadPresentFields(); RadioTapFields = ReadRadioTapFields(); //Before we attempt to parse the payload we need to work out if //the FCS was valid and if it will be present at the end of the frame FlagsRadioTapField flagsField = this[RadioTapType.Flags] as FlagsRadioTapField; payloadPacketOrData = ParseEncapsulatedBytes(header.EncapsulatedBytes(), flagsField); } /// public override string ToString(StringOutputType outputFormat) { var buffer = new StringBuilder(); string color = ""; string colorEscape = ""; if (outputFormat == StringOutputType.Colored || outputFormat == StringOutputType.VerboseColored) { color = Color; colorEscape = AnsiEscapeSequences.Reset; } if (outputFormat == StringOutputType.Normal || outputFormat == StringOutputType.Colored) { // build the output string buffer.AppendFormat("{0}[Ieee80211RadioPacket: Version={2}, Length={3}, Present[0]=0x{4:x}]{1}", color, colorEscape, Version, Length, Present[0]); } if (outputFormat == StringOutputType.Verbose || outputFormat == StringOutputType.VerboseColored) { // collect the properties and their value Dictionary properties = new Dictionary(); properties.Add("version", Version.ToString()); properties.Add("length", Length.ToString()); properties.Add("present", " (0x" + Present[0].ToString("x") + ")"); var radioTapFields = this.RadioTapFields; foreach (var r in radioTapFields) { properties.Add(r.Value.FieldType.ToString(), r.Value.ToString()); } // calculate the padding needed to right-justify the property names int padLength = Utils.RandomUtils.LongestStringLength(new List(properties.Keys)); // build the output string buffer.AppendLine("Ieee80211RadioPacket"); foreach (var property in properties) { buffer.AppendLine("TAP: " + property.Key.PadLeft(padLength) + " = " + property.Value); } buffer.AppendLine("TAP:"); } // append the base output buffer.Append(base.ToString(outputFormat)); return buffer.ToString(); } /// /// Add the specified field to the packet. /// /// /// Field to be added /// public void Add(RadioTapField field) { RadioTapFields[field.FieldType] = field; Length += field.Length; var presenceBit = (int)field.FieldType; var presenceField = (presenceBit / 32); if(Present.Length <= presenceField) { var newPresentFields = new UInt32[presenceField]; Array.Copy(Present, newPresentFields, Present.Length); //set bit 31 to true for every present field except the last one for(int i = 0; i < newPresentFields.Length - 1; i++) { newPresentFields[i] |= 0x80000000; } Present = newPresentFields; } Present[presenceField] |= (UInt32)(1 << presenceBit); } /// /// Removes a field of the specified type if one is present in the packet. /// /// /// Field type. /// public void Remove(RadioTapType fieldType) { RadioTapField field; if(RadioTapFields.TryGetValue(fieldType, out field)) { RadioTapFields.Remove(fieldType); Length -= field.Length; var presenceBit = (int)field.FieldType; var presenceField = (presenceBit / 32); Present[presenceField] &= (UInt32)~(1 << presenceBit); } } /// /// Checks for the presence of a field of the specified type in the packet. /// /// /// The field type to check for. /// /// true if the packet contains a field of the specified type; otherwise, false. public bool Contains(RadioTapType fieldType) { return RadioTapFields.ContainsKey(fieldType); } /// /// Gets the with the specified type, or null if the /// field is not in the packet. /// /// /// Radio Tap field type /// public RadioTapField this[RadioTapType type] { get { RadioTapField field; RadioTapFields.TryGetValue(type, out field); return field; } } /// /// Array of radio tap fields /// private SortedDictionary RadioTapFields { get; set; } private byte[] UnhandledFieldBytes {get; set;} private SortedDictionary ReadRadioTapFields() { var bitmasks = Present; var retval = new SortedDictionary(); int bitIndex = 0; // create a binary reader that points to the memory immediately after the bitmasks var offset = header.Offset + RadioFields.PresentPosition + (bitmasks.Length) * Marshal.SizeOf (typeof(UInt32)); var br = new BinaryReader (new MemoryStream (header.Bytes, offset, (int)(Length - offset))); // now go through each of the bitmask fields looking at the least significant // bit first to retrieve each field foreach (var bmask in bitmasks) { int[] bmaskArray = new int[1]; bmaskArray [0] = (int)bmask; var ba = new BitArray (bmaskArray); bool unhandledFieldFound = false; // look at all of the bits, note we don't want to consider the // highest bit since that indicates another bitfield that follows for (int x = 0; x < 31; x++) { if (ba [x] == true) { var field = RadioTapField.Parse (bitIndex, br); if (field != null) { retval[field.FieldType] = field; } else { //We have found a field that we dont handle. As we dont know how big //it is we can't handle any fields after it. We will just copy //the rest of the data around as a lump unhandledFieldFound = true; break; } } bitIndex++; } if(unhandledFieldFound) { break; } } //this will read the rest of the bytes. We pass in max value because we dont know how //much there is but this will ensure we get up to the end of the buffer UnhandledFieldBytes = br.ReadBytes(UInt16.MaxValue); return retval; } /// /// Called to ensure that field values are updated before /// the packet bytes are retrieved /// public override void UpdateCalculatedValues() { if ((header == null) || (header.Length < Length)) { //the backing buffer isnt big enough to accommodate the info elements so we need to resize it header = new ByteArraySegment (new Byte[Length]); } VersionBytes = Version; LengthBytes = Length; var index = RadioFields.PresentPosition; foreach(var presentField in Present) { EndianBitConverter.Little.CopyBytes(presentField, header.Bytes, header.Offset + index); index += RadioFields.PresentLength; } foreach(var field in RadioTapFields) { //then copy the field data to the appropriate index field.Value.CopyTo(header.Bytes, header.Offset + index); index += field.Value.Length; } if((UnhandledFieldBytes != null) && (UnhandledFieldBytes.Length > 0)) { Array.Copy(UnhandledFieldBytes, 0, header.Bytes, header.Offset + index, UnhandledFieldBytes.Length); } } internal static PacketOrByteArraySegment ParseEncapsulatedBytes (ByteArraySegment payload, FlagsRadioTapField flagsField) { var payloadPacketOrData = new PacketOrByteArraySegment (); MacFrame frame = null; if (flagsField != null) { bool fcsPresent = ((flagsField.Flags & RadioTapFlags.FcsIncludedInFrame) == RadioTapFlags.FcsIncludedInFrame); if (fcsPresent) { frame = MacFrame.ParsePacketWithFcs (payload); } else { frame = MacFrame.ParsePacket (payload); } } else { frame = MacFrame.ParsePacket (payload); } if (frame == null) { payloadPacketOrData.TheByteArraySegment = payload; } else { payloadPacketOrData.ThePacket = frame; } return payloadPacketOrData; } } } }