/* 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.Net.NetworkInformation; using PacketDotNet.Utils; using MiscUtil.Conversion; using System.IO; namespace PacketDotNet { namespace Ieee80211 { /// /// Base class of all 802.11 frame types /// public abstract class MacFrame : Packet { // 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 private int GetOffsetForAddress(int addressIndex) { int offset = header.Offset; offset += MacFields.Address1Position + MacFields.AddressLength * addressIndex; // the 4th address is AFTER the sequence control field so we need to skip past that // field if (addressIndex == 4) offset += MacFields.SequenceControlLength; return offset; } /// /// Frame control bytes are the first two bytes of the frame /// protected UInt16 FrameControlBytes { get { if(header.Length >= (MacFields.FrameControlPosition + MacFields.FrameControlLength)) { return EndianBitConverter.Big.ToUInt16 (header.Bytes, header.Offset); } else { return 0; } } set { EndianBitConverter.Big.CopyBytes(value, header.Bytes, header.Offset); } } /// /// Frame control field /// public FrameControlField FrameControl { get; set; } /// /// Duration bytes are the third and fourth bytes of the frame /// protected UInt16 DurationBytes { get { if(header.Length >= (MacFields.DurationIDPosition + MacFields.DurationIDLength)) { return EndianBitConverter.Little.ToUInt16(header.Bytes, header.Offset + MacFields.DurationIDPosition); } else { return 0; } } set { EndianBitConverter.Little.CopyBytes(value, header.Bytes, header.Offset + MacFields.DurationIDPosition); } } /// /// Gets or sets the duration value. The value represents the number of microseconds /// the the wireless medium is expected to remain busy. /// /// /// The duration field value /// public DurationField Duration {get; set;} /// /// Writes the address into the specified address position. /// /// The number of valid address positions in a MAC frame is determined by /// the type of frame. There are between 1 and 4 address fields in MAC frames /// Zero based address to look up /// protected void SetAddress(int addressIndex, PhysicalAddress address) { var offset = GetOffsetForAddress(addressIndex); SetAddressByOffset(offset, address); } /// /// Writes the provided address into the backing /// starting at the provided offset. /// /// /// The position where the address should start to be copied /// /// /// Address. /// protected void SetAddressByOffset(int offset, PhysicalAddress address) { byte[] hwAddress = null; //We will replace no address with a MAC of all zer if(address == PhysicalAddress.None) { hwAddress = new byte[]{0, 0, 0, 0, 0, 0}; } else { hwAddress = address.GetAddressBytes(); } // using the offset, set the address if (hwAddress.Length != MacFields.AddressLength) { throw new System.InvalidOperationException("address length " + hwAddress.Length + " not equal to the expected length of " + MacFields.AddressLength); } Array.Copy(hwAddress, 0, header.Bytes, offset, hwAddress.Length); } /// /// Gets the address. There can be up to four addresses in a MacFrame depending on its type. /// /// /// The address. /// /// /// Address index. /// protected PhysicalAddress GetAddress(int addressIndex) { var offset = GetOffsetForAddress(addressIndex); return GetAddressByOffset(offset); } /// /// Gets an address by offset. /// /// /// The address as the specified index. /// /// /// The offset into the packet buffer at which to start parsing the address. /// protected PhysicalAddress GetAddressByOffset(int offset) { if((header.Offset + header.Length) >= (offset + MacFields.AddressLength)) { byte[] hwAddress = new byte[MacFields.AddressLength]; Array.Copy(header.Bytes, offset, hwAddress, 0, hwAddress.Length); return new PhysicalAddress(hwAddress); } else { return PhysicalAddress.None; } } /// /// Frame check sequence, the last thing in the 802.11 mac packet /// public UInt32 FrameCheckSequence { get; set; } /// /// Recalculates and updates the frame check sequence. /// /// After calling this method the FCS will be valud regardless of what the packet contains. public void UpdateFrameCheckSequence () { var bytes = Bytes; var length = (AppendFcs) ? bytes.Length - 4 : bytes.Length; FrameCheckSequence = (uint) Crc32.Compute(Bytes, 0, length); } /// /// Length of the frame header. /// /// This does not include the FCS, it represents only the header bytes that would /// would preceed any payload. /// public abstract int FrameSize { get; } /// /// Returns the number of bytes of payload data currently available in /// the buffer. /// /// This method is used to work out how much space there is for the payload in the /// underlying ByteArraySegment. To find out the length of /// actual payload assigned to the packet use PayloadData.Length. /// /// The number of bytes of space available after the header for payload data. /// protected int GetAvailablePayloadLength() { int payloadLength = header.BytesLength - (header.Offset + FrameSize); return (payloadLength > 0) ? payloadLength : 0; } /// /// Parses the into a MacFrame. /// /// /// The parsed MacFrame or null if it could not be parsed. /// /// /// The bytes of the packet. bas.Offset should point to the first byte in the mac frame. /// /// If the provided bytes dont contain the FCS then call instead. The presence of the /// FCS is usually determined by configuration of the device used to capture the packets. public static MacFrame ParsePacketWithFcs (ByteArraySegment bas) { if (bas.Length < (MacFields.FrameControlLength + MacFields.FrameCheckSequenceLength)) { //There isn't enough data for there to be an FCS and a packet return null; } //remove the FCS from the buffer that we will pass to the packet parsers ByteArraySegment basWithoutFcs = new ByteArraySegment (bas.Bytes, bas.Offset, bas.Length - MacFields.FrameCheckSequenceLength, bas.BytesLength - MacFields.FrameCheckSequenceLength); UInt32 fcs = EndianBitConverter.Big.ToUInt32 (bas.Bytes, (bas.Offset + bas.Length) - MacFields.FrameCheckSequenceLength); MacFrame frame = ParsePacket (basWithoutFcs); if (frame != null) { frame.AppendFcs = true; frame.FrameCheckSequence = fcs; } return frame; } /// /// Parses the into a MacFrame. /// /// /// The parsed MacFrame or null if it could not be parsed. /// /// /// The bytes of the packet. bas.Offset should point to the first byte in the mac frame. /// /// If the provided bytes contain the FCS then call instead. The presence of the /// FCS is usually determined by configuration of the device used to capture the packets. public static MacFrame ParsePacket (ByteArraySegment bas) { if (bas.Length < MacFields.FrameControlLength) { //there isn't enough data to even try and work out what type of packet it is return null; } //this is a bit ugly as we will end up parsing the framecontrol field twice, once here and once //inside the packet constructor. Could create the framecontrol and pass it to the packet but I think that is equally ugly FrameControlField frameControl = new FrameControlField ( EndianBitConverter.Big.ToUInt16 (bas.Bytes, bas.Offset)); MacFrame macFrame = null; switch (frameControl.SubType) { case FrameControlField.FrameSubTypes.ManagementAssociationRequest: { macFrame = new AssociationRequestFrame (bas); break; } case FrameControlField.FrameSubTypes.ManagementAssociationResponse: { macFrame = new AssociationResponseFrame (bas); break; } case FrameControlField.FrameSubTypes.ManagementReassociationRequest: { macFrame = new ReassociationRequestFrame (bas); break; } case FrameControlField.FrameSubTypes.ManagementReassociationResponse: { macFrame = new AssociationResponseFrame (bas); break; } case FrameControlField.FrameSubTypes.ManagementProbeRequest: { macFrame = new ProbeRequestFrame (bas); break; } case FrameControlField.FrameSubTypes.ManagementProbeResponse: { macFrame = new ProbeResponseFrame (bas); break; } case FrameControlField.FrameSubTypes.ManagementReserved0: break; //TODO case FrameControlField.FrameSubTypes.ManagementReserved1: break; //TODO case FrameControlField.FrameSubTypes.ManagementBeacon: { macFrame = new BeaconFrame (bas); break; } case FrameControlField.FrameSubTypes.ManagementATIM: break; //TODO case FrameControlField.FrameSubTypes.ManagementDisassociation: { macFrame = new DisassociationFrame (bas); break; } case FrameControlField.FrameSubTypes.ManagementAuthentication: { macFrame = new AuthenticationFrame (bas); break; } case FrameControlField.FrameSubTypes.ManagementDeauthentication: { macFrame = new DeauthenticationFrame (bas); break; } case FrameControlField.FrameSubTypes.ManagementAction: { macFrame = new ActionFrame (bas); break; } case FrameControlField.FrameSubTypes.ManagementReserved3: break; //TODO case FrameControlField.FrameSubTypes.ControlBlockAcknowledgmentRequest: { macFrame = new BlockAcknowledgmentRequestFrame (bas); break; } case FrameControlField.FrameSubTypes.ControlBlockAcknowledgment: { macFrame = new BlockAcknowledgmentFrame (bas); break; } case FrameControlField.FrameSubTypes.ControlPSPoll: break; //TODO case FrameControlField.FrameSubTypes.ControlRTS: { macFrame = new RtsFrame (bas); break; } case FrameControlField.FrameSubTypes.ControlCTS: { macFrame = new CtsFrame (bas); break; } case FrameControlField.FrameSubTypes.ControlACK: { macFrame = new AckFrame (bas); break; } case FrameControlField.FrameSubTypes.ControlCFEnd: { macFrame = new ContentionFreeEndFrame (bas); break; } case FrameControlField.FrameSubTypes.ControlCFEndCFACK: break; //TODO case FrameControlField.FrameSubTypes.Data: case FrameControlField.FrameSubTypes.DataCFACK: case FrameControlField.FrameSubTypes.DataCFPoll: case FrameControlField.FrameSubTypes.DataCFAckCFPoll: { macFrame = new DataDataFrame (bas); break; } case FrameControlField.FrameSubTypes.DataNullFunctionNoData: case FrameControlField.FrameSubTypes.DataCFAckNoData: case FrameControlField.FrameSubTypes.DataCFPollNoData: case FrameControlField.FrameSubTypes.DataCFAckCFPollNoData: { macFrame = new NullDataFrame (bas); break; } case FrameControlField.FrameSubTypes.QosData: case FrameControlField.FrameSubTypes.QosDataAndCFAck: case FrameControlField.FrameSubTypes.QosDataAndCFPoll: case FrameControlField.FrameSubTypes.QosDataAndCFAckAndCFPoll: { macFrame = new QosDataFrame (bas); break; } case FrameControlField.FrameSubTypes.QosNullData: case FrameControlField.FrameSubTypes.QosCFAck: case FrameControlField.FrameSubTypes.QosCFPoll: case FrameControlField.FrameSubTypes.QosCFAckAndCFPoll: { macFrame = new QosNullDataFrame (bas); break; } default: //this is an unsupported (and unknown) packet type break; } return macFrame; } /// /// Calculates the FCS value for the provided bytes and compates it to the FCS value passed to the method. /// /// /// true if the FCS for the provided bytes matches the FCS passed in, false if not. /// /// /// The byte array for which the FCS will be calculated. /// /// /// The offset into data of the first byte to be covered by the FCS. /// /// /// The number of bytes to calculate the FCS for. /// /// /// The FCS to compare to the one calculated for the provided data. /// /// This method can be used to check the validity of a packet before attempting to parse it with either /// or . Attempting to parse a corrupted buffer /// using these methods could cause unexpected exceptions. public static bool PerformFcsCheck (Byte[] data, int offset, int length, UInt32 fcs) { // Cast to uint for proper comparison to FrameCheckSequence var check = (uint)Crc32.Compute(data, offset, length); return check == fcs; } /// /// FCSs the valid. /// /// /// The valid. /// public bool FCSValid { get { var packetBytes = Bytes; var packetLength = (AppendFcs) ? packetBytes.Length - MacFields.FrameCheckSequenceLength : packetBytes.Length; return PerformFcsCheck(packetBytes, 0, packetLength, FrameCheckSequence); } } /// /// Gets or sets a value indicating whether this should include an FCS at the end /// of the array returned by Bytes. /// /// /// true if append FCS should be appended; otherwise, false. /// public bool AppendFcs { get; set; } /// /// The option to return a ByteArraySegment means that this method /// is higher performance as the data can start at an offset other than /// the first byte. /// public override ByteArraySegment BytesHighPerformance { get { log.Debug(""); // ensure calculated values are properly updated //RecursivelyUpdateCalculatedValues(); // if we share memory with all of our sub packets we can take a // higher performance path to retrieve the bytes var totalPacketLength = TotalPacketLength; if(SharesMemoryWithSubPackets && ((!AppendFcs) || (header.Bytes.Length >= (header.Offset + totalPacketLength + MacFields.FrameCheckSequenceLength)))) { var packetLength = totalPacketLength; if(AppendFcs) { packetLength += MacFields.FrameCheckSequenceLength; //We need to update the FCS field because this couldn't be done during //RecursivelyUpdateCalculatedValues because we didn't know where it would be EndianBitConverter.Big.CopyBytes(FrameCheckSequence, header.Bytes, header.Offset + totalPacketLength); } // The high performance path that is often taken because it is called on // packets that have not had their header, or any of their sub packets, resized var newByteArraySegment = new ByteArraySegment(header.Bytes, header.Offset, packetLength); log.DebugFormat("SharesMemoryWithSubPackets, returning byte array {0}", newByteArraySegment.ToString()); return newByteArraySegment; } else // need to rebuild things from scratch { log.Debug("rebuilding the byte array"); var ms = new MemoryStream(); // TODO: not sure if this is a performance gain or if // the compiler is smart enough to not call the get accessor for Header // twice, once when retrieving the header and again when retrieving the Length var theHeader = Header; ms.Write(theHeader, 0, theHeader.Length); payloadPacketOrData.AppendToMemoryStream(ms); if(AppendFcs) { var fcsBuffer = EndianBitConverter.Big.GetBytes(FrameCheckSequence); ms.Write(fcsBuffer, 0, fcsBuffer.Length); } var newBytes = ms.ToArray(); return new ByteArraySegment(newBytes, 0, newBytes.Length); } } } /// /// ToString() override /// /// /// A /// public override String ToString() { return string.Format ("802.11 MacFrame: [{0}], {1} FCS {2}", FrameControl.ToString(), GetAddressString(), FrameCheckSequence); } /// /// Returns a string with a description of the addresses used in the packet. /// This is used as a compoent of the string returned by ToString(). /// /// /// The address string. /// protected abstract String GetAddressString(); } } }