// 32feet.NET - Personal Area Networking for .NET // // InTheHand.Net.Bluetooth.ServiceRecordParser // // Copyright (c) 2007 Andy Hume // Copyright (c) 2003-2020 In The Hand Ltd, All rights reserved. // This source code is licensed under the MIT License using System; using System.Collections.Generic; using System.Diagnostics; using System.Net; namespace InTheHand.Net.Bluetooth.Sdp { /// /// Parses an array of bytes into the contained SDP /// . /// /// - /// /// See the /// /// methods for more information. /// /// - /// public class ServiceRecordParser { //-------------------------------------------------------------- delegate T SequenceItemParser(byte[] buffer, int offset, int length, out int readLength); //-------------------------------------------------------------- private bool m_allowAnySizeForUnknownTypeDescriptorElements = true; //-------------------------------------------------------------- // Properties //-------------------------------------------------------------- /// /// Gets or set whether the parser will attempt to skip any unknown element /// type rather than producing an error. /// /// - /// /// /// An element type is added instead with /// ElementType. /// and ElementTypeDescriptor.. /// /// public bool SkipUnhandledElementTypes { get; set; } /// /// Gets or sets whether any URL elements will be converted to /// instances at parse time, or left as raw byte arrays. /// /// - /// /// This is useful when the URL element is badly formatted and thus the /// parser will reject the record, setting this property to true will /// allow the parse to complete without attempting to decode the URL value. /// /// When true the value is stored as a array of bytes, when /// false it is stored as a ; /// however in earlier versions it was stored as , /// and since there was often invalid content on devices (e.g. iPhone) /// this often failed. /// /// public bool LazyUrlCreation { get; set; } //-------------------------------------------------------------- // Methods //-------------------------------------------------------------- /// /// Parses an array of bytes into its contained /// . /// /// - /// /// See /// for more information. /// /// - /// A byte array containing the encoded Service Record. /// /// The new parsed from the byte array. /// /// - /// /// public ServiceRecord Parse(byte[] buffer) { if (buffer == null) { throw new ArgumentNullException("buffer"); } return Parse(buffer, 0, buffer.Length); } /// /// Parses an array of bytes into its contained /// . /// /// - /// /// If the record contains any element type not supported by the parser /// it will throw . The /// only element types defined by SDP in v2.0 that are not currently implemented /// are 64- and 128-bit integers. Of course any types defined in a later /// version will also throw this. This behaviour can be changed with the /// /// property. /// /// /// - /// A byte array containing a Service Record. /// /// The position in the data buffer at which to /// begin parsing the Service Record. /// /// The length of the Service Record in the byte array. /// /// The Service Record parse from the byte array. /// /// - /// /// The record contains invalid content. /// /// /// The record contains an element type not supported by the parser. /// /// - /// /// public ServiceRecord Parse(byte[] buffer, int offset, int length) { if (buffer == null) { throw new ArgumentNullException("buffer"); } if (length <= 0) { throw new_ArgumentOutOfRangeException("buffer", ErrorMsgServiceRecordBytesZeroLength); } if (offset < 0) { throw new ArgumentOutOfRangeException("offset"); } if ((buffer.Length - offset) < length) { throw new ArgumentException("offset is past the end of the data."); } // ElementTypeDescriptor etd; System.Diagnostics.Debug.Assert(length >= 1); //is checked above! etd = GetElementTypeDescriptor(buffer[offset]); if (etd != ElementTypeDescriptor.ElementSequence) { throw CreateInvalidException(ErrorMsgTopElementNotSequence, offset); } // int elementLength = GetElementLength(buffer, offset, length, out int contentOffset, out int contentLength); if (elementLength > length) { throw CreateInvalidExceptionOverruns(offset, elementLength, length); } List children = new List(); SequenceItemParser itemParser = ParseAttributeElementPair; ParseSeqOrAlt(buffer, offset, elementLength, children, itemParser); //if (elementLength < length) { // Console.WriteLine("Data left in buffer: buf-length: {0}, elementLength: {1}", length, elementLength); //} // ServiceRecord result = new ServiceRecord(children); if (offset == 0) { result.SetSourceBytes(buffer); //TODO Set the source bytes on the record in the case where they're offset in the buffer. } return result; } private ServiceAttribute ParseAttributeElementPair(byte[] buffer, int offset, int length, out int readLength) { // Verify that the first element of the pair is a UInt16 to hold the attr id. GetElementLength(buffer, offset, length, out int contentOffset, out int contentLength); ElementTypeDescriptor etd = GetElementTypeDescriptor(buffer[offset]); bool goodIdType = (etd == ElementTypeDescriptor.UnsignedInteger /*|| etd == ElementTypeDescriptor.TwosComplementInteger*/); goodIdType = goodIdType && (contentLength == 2); if (!goodIdType) { throw CreateInvalidException(ErrorMsgAttributePairFirstMustUint16, offset); } // Now we can read the pair -- the id and the following element. readLength = 0; ushort attrId = ReadElementUInt16(buffer, offset, length, out int elementLength); readLength += elementLength; offset += elementLength; length -= elementLength; ServiceElement value = ParseInternal(buffer, offset, length, out elementLength); readLength += elementLength; ServiceAttribute attr = new ServiceAttribute(attrId, value); return attr; }//fn //-------------------------------------------------------------- private ServiceElement ParseInternal(byte[] buffer, int offset, int length, out int readLength) { SplitHeaderByte(buffer[offset], out ElementTypeDescriptor etd, out SizeIndex sizeIndex); VerifyAllowedSizeIndex(etd, sizeIndex, m_allowAnySizeForUnknownTypeDescriptorElements); readLength = GetElementLength(buffer, offset, length, out int contentOffset, out int contentLength); if (readLength > length) { throw CreateInvalidExceptionOverruns(offset, readLength, length); } object rawValue = null; // The code at the bottom of the method uses the value of 'type' to know // whether the element was handled and parsed. So if we don't handle a // particular type we'll drop through, and throw an error or create a // 'unknown' element depending on the settings. ElementType type = ElementType.Unknown; // -- Parse -- if (etd == ElementTypeDescriptor.Nil) { //---------------- type = ElementType.Nil; rawValue = null; } else if (etd == ElementTypeDescriptor.UnsignedInteger || etd == ElementTypeDescriptor.TwosComplementInteger) { //---------------- if (contentLength == 1) { Byte valueU8 = ReadElementUInt8(buffer, offset, length, out readLength); if (etd == ElementTypeDescriptor.UnsignedInteger) { type = ElementType.UInt8; rawValue = valueU8; } else { type = ElementType.Int8; rawValue = unchecked((SByte)valueU8); } } else if (contentLength == 2) { ushort valueU16 = ReadElementUInt16(buffer, offset, length, out readLength); if (etd == ElementTypeDescriptor.UnsignedInteger) { type = ElementType.UInt16; rawValue = valueU16; } else { type = ElementType.Int16; rawValue = unchecked((short)valueU16); } } else if (contentLength == 4) { uint valueU32 = ReadElementUInt32(buffer, offset, length, out readLength); if (etd == ElementTypeDescriptor.UnsignedInteger) { type = ElementType.UInt32; rawValue = valueU32; } else { type = ElementType.Int32; rawValue = unchecked((int)valueU32); } } else if (contentLength == 8) { ulong valueU64 = ReadElementUInt64(buffer, offset, length, out readLength); if (etd == ElementTypeDescriptor.UnsignedInteger) { type = ElementType.UInt64; rawValue = valueU64; } else { type = ElementType.Int64; rawValue = unchecked((long)valueU64); } } else if (contentLength == 16) { byte[] valueArr128 = ReadArrayContent(buffer, offset, length, out readLength); Debug.Assert(readLength == 17, "but readLength is : " + readLength); Debug.Assert(valueArr128.Length == 16, "but valueArr128.Length is : " + valueArr128.Length); if (etd == ElementTypeDescriptor.UnsignedInteger) { type = ElementType.UInt128; rawValue = valueArr128; } else { type = ElementType.Int128; rawValue = valueArr128; } } } else if (etd == ElementTypeDescriptor.Uuid) { //---------------- if (contentLength == 2) { ushort valueU16 = ReadFieldUInt16(buffer, offset + contentOffset, length - contentOffset); type = ElementType.Uuid16; rawValue = valueU16; } else if (contentLength == 4) { uint valueU32 = ReadFieldUInt32(buffer, offset + contentOffset, length - contentOffset); type = ElementType.Uuid32; rawValue = valueU32; } else if (contentLength == 16) { offset += contentOffset; Guid guidValue = new Guid( // NETCF doesn't have #ctor(uint, ushort, ...) overload. unchecked((int)ReadFieldUInt32(buffer, offset, contentLength)),//-- unchecked((short)ReadFieldUInt16(buffer, offset + 4, contentLength - 4)),//-- unchecked((short)ReadFieldUInt16(buffer, offset + 6, contentLength - 6)),//-- ReadFieldUInt8(buffer, offset + 8, contentLength - 8), ReadFieldUInt8(buffer, offset + 9, contentLength - 9),//-- ReadFieldUInt8(buffer, offset + 10, contentLength - 10), ReadFieldUInt8(buffer, offset + 11, contentLength - 11), ReadFieldUInt8(buffer, offset + 12, contentLength - 12), ReadFieldUInt8(buffer, offset + 13, contentLength - 13), ReadFieldUInt8(buffer, offset + 14, contentLength - 14), ReadFieldUInt8(buffer, offset + 15, contentLength - 15)); type = ElementType.Uuid128; rawValue = guidValue; } } else if (etd == ElementTypeDescriptor.Boolean) { //---------------- Debug.Assert(contentLength == 1); byte valueU8 = ReadFieldUInt8(buffer, offset + contentOffset, length - contentOffset); bool valueBoolean = valueU8 != 0; rawValue = valueBoolean; type = ElementType.Boolean; } else if (etd == ElementTypeDescriptor.ElementSequence || etd == ElementTypeDescriptor.ElementAlternative) { List children = new List(); SequenceItemParser itemParser = ParseInternal; ParseSeqOrAlt(buffer, offset, readLength, children, itemParser); type = (etd == ElementTypeDescriptor.ElementSequence) ? ElementType.ElementSequence : ElementType.ElementAlternative; rawValue = children; } else if (etd == ElementTypeDescriptor.Url) { //---------------- byte[] valueArray = ReadArrayContent(buffer, offset, length, out int myReadLength); Debug.Assert(myReadLength == readLength); if (LazyUrlCreation) { rawValue = valueArray; } else { string valueUri = CreateUriStringFromBytes(valueArray); rawValue = valueUri; } type = ElementType.Url; } else if (etd == ElementTypeDescriptor.TextString) { //---------------- byte[] valueArray = ReadArrayContent(buffer, offset, length, out int myReadLength); Debug.Assert(myReadLength == readLength); type = ElementType.TextString; rawValue = valueArray; } // -------- // Use parse result (or lack of!). if (type == ElementType.Unknown) { // If we haven't handled the type, we'll end-up here. string msg = "Element type: " + etd + ", SizeIndex: " + sizeIndex + ", at offset: " + offset + "."; if (SkipUnhandledElementTypes) { type = ElementType.Unknown; etd = ElementTypeDescriptor.Unknown; rawValue = null; Debug.WriteLine( "Unhandled SDP Parse " + msg); } else { throw new_NotImplementedException(msg); } } #pragma warning disable 618 ServiceElement value = new ServiceElement(etd, type, rawValue); #pragma warning restore 618 return value; } private static void ParseSeqOrAlt(byte[] buffer, int offset, int length, IList children, SequenceItemParser itemParser) { Debug.Assert(!(itemParser == null), "NULL itemParser"); Debug.Assert(!(children == null), "NULL children"); Debug.Assert(buffer.Length >= offset + length); // ElementTypeDescriptor etd = GetElementTypeDescriptor(buffer[offset]); Debug.Assert(!(etd != ElementTypeDescriptor.ElementSequence && etd != ElementTypeDescriptor.ElementAlternative), "Not Element Sequence or Alternative."); // int elementLength; elementLength = GetElementLength(buffer, offset, length, out int contentOffset, out int curContentLength); Debug.Assert(!(elementLength != length), "Given length is not equal to the Seq/Alt length field."); // int curOffset = offset + contentOffset; while (curOffset < offset + length && curContentLength > 0) { T child = itemParser(buffer, curOffset, curContentLength, out int readLength); children.Add(child); curOffset += readLength; curContentLength -= readLength; }//while // These can be wrong if the record is wrong but check them so we can // see this during development. // Now, we throw if the record is invalid, so these aren't executed in // that case, and thus they *are* full invariants. Debug.Assert(curOffset == offset + length); Debug.Assert(curContentLength == 0); } //-------------------------------------------------------------- /// /// Split a sequence of records into the component records. /// /// - /// /// The Bluetooth SDP operation ServiceSearchAttribute returns its /// result as a “data element sequence where each element in turn is /// a data element sequence representing an attribute list.” This /// method split that sequence into the individual attribute lists. /// /// On CE/Windows Mobile the result of a record lookup is in this form /// so /// etc use this method to split the result into is constituent records. /// /// /// - /// A byte array holding the /// “data element sequence where each element in turn is /// a data element sequence representing an attribute list.” /// /// - /// An array of byte arrays where each holds a SDP record /// (a “data element sequence representing an attribute list.”). /// If the input was zero length or empty then a zero length array is returned. /// /// - /// /// is . /// public static byte[][] SplitSearchAttributeResult(byte[] multiRecord) { if (multiRecord == null) { throw new ArgumentNullException("multiRecord"); } if (multiRecord.Length == 0) { // Should we error here? We handle the zero element case below // (where the input array is 0x35 0x00 etc). So the caller likely // made an error in passing a totally empty array... return new byte[0][]; } // byte[] buffer = multiRecord; int offset = 0; int length = multiRecord.Length; // ElementTypeDescriptor etd; Debug.Assert(length >= 1); //is checked above! etd = GetElementTypeDescriptor(buffer[offset]); if (etd != ElementTypeDescriptor.ElementSequence) { throw CreateInvalidException(ErrorMsgTopElementNotSequence, offset); } // int elementLength = GetElementLength(buffer, offset, length, out int contentOffset, out int contentLength); if (elementLength > length) { throw CreateInvalidExceptionOverruns(offset, elementLength, length); } // List list = new List(); // int curOffset = offset + contentOffset; int curLength = contentLength; while (curLength > 0) { ElementTypeDescriptor etdInner; Debug.Assert(length >= 1); //is checked above! etdInner = GetElementTypeDescriptor(buffer[curOffset]); if (etdInner != ElementTypeDescriptor.ElementSequence) { throw CreateInvalidException(ErrorMsgMultiSeqChildElementNotSequence, curOffset); } // int elementLengthInner = GetElementLength(buffer, curOffset, curLength, out var contentOffsetInner, out var contentLengthInner); if (elementLengthInner > curLength) { throw CreateInvalidExceptionOverruns(curOffset, elementLengthInner, curLength); } // int recordLength = contentOffsetInner + contentLengthInner; byte[] curRecord = new byte[recordLength]; Array.Copy(buffer, curOffset, curRecord, 0, recordLength); list.Add(curRecord); // curOffset += recordLength; curLength -= recordLength; }//while Debug.Assert(curLength == 0, "curLength == 0"); return list.ToArray(); } //-------------------------------------------------------------- private static byte ReadElementUInt8(byte[] buffer, int offset, int length, out int readLength) { int contentOffset; int contentLength; readLength = GetElementLength(buffer, offset, length, out contentOffset, out contentLength); ElementTypeDescriptor etd = GetElementTypeDescriptor(buffer[offset]); Debug.Assert(etd == ElementTypeDescriptor.UnsignedInteger || etd == ElementTypeDescriptor.TwosComplementInteger); Debug.Assert(contentLength == 1); byte value = ReadFieldUInt8(buffer, offset + contentOffset, length - contentOffset); return value; } private static ushort ReadElementUInt16(byte[] buffer, int offset, int length, out int readLength) { readLength = GetElementLength(buffer, offset, length, out var contentOffset, out var contentLength); ElementTypeDescriptor etd = GetElementTypeDescriptor(buffer[offset]); Debug.Assert(etd == ElementTypeDescriptor.UnsignedInteger || etd == ElementTypeDescriptor.TwosComplementInteger); Debug.Assert(contentLength == 2); ushort value = ReadFieldUInt16(buffer, offset + contentOffset, length - contentOffset); return value; } private static uint ReadElementUInt32(byte[] buffer, int offset, int length, out int readLength) { readLength = GetElementLength(buffer, offset, length, out var contentOffset, out var contentLength); ElementTypeDescriptor etd = GetElementTypeDescriptor(buffer[offset]); Debug.Assert(etd == ElementTypeDescriptor.UnsignedInteger || etd == ElementTypeDescriptor.TwosComplementInteger); Debug.Assert(contentLength == 4); uint value = ReadFieldUInt32(buffer, offset + contentOffset, length - contentOffset); return value; } private static ulong ReadElementUInt64(byte[] buffer, int offset, int length, out int readLength) { readLength = GetElementLength(buffer, offset, length, out var contentOffset, out var contentLength); ElementTypeDescriptor etd = GetElementTypeDescriptor(buffer[offset]); Debug.Assert(etd == ElementTypeDescriptor.UnsignedInteger || etd == ElementTypeDescriptor.TwosComplementInteger); Debug.Assert(contentLength == 8, "NOT contentLength == 8, is: " + contentLength); ulong value = ReadFieldUInt64(buffer, offset + contentOffset, length - contentOffset); return value; } private static byte[] ReadArrayContent(byte[] buffer, int offset, int length, out int readLength) { readLength = GetElementLength(buffer, offset, length, out int contentOffset, out int contentLength); // byte[] result = new byte[contentLength]; Array.Copy(buffer, offset + contentOffset, result, 0, contentLength); return result; } //-------- /// /// For use when the content of the element is in an array /// i.e. the stack parses the element structure and returns the values in byte arrays. /// /// - /// Whether the stack uses network order /// for UnsignedInteger and TwosComplementInteger elements (as used in the SDP format) /// or instead that the numerical values are in host order /// in the byte array. /// /// Whether the stack uses network order /// for Uuid elements (as used in the SDP format) /// or instead that the numerical values are in host order /// in the byte array. /// /// The byte array containing the SDP value. /// /// (?Always zero). /// /// The length of the byte array. /// (Always equals ). /// /// /// /// The Element Type. /// /// (Not used). /// /// The size of the value. /// /// (?Always zero). /// /// - /// /// internal ServiceElement ParseContent(bool networkOrderInteger, bool networkOrderUuid, byte[] buffer, int offset, int length, ref int _readLength, ElementTypeDescriptor etd, SizeIndex dbgSizeIndex, int contentLength, int contentOffset) { //TODO ParseContent ENDIAN!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Debug.Assert(offset == 0, "NOT offset == 0, is: " + contentOffset); Debug.Assert(length == buffer.Length, "NOT length == buffer.Length..."); Debug.Assert(contentOffset == 0, "NOT contentOffset == 0, is: " + contentOffset); Debug.Assert(contentLength == length, "DIFFERENT contentLength: " + contentLength + ", length: " + length); //-- object rawValue = null; // The code at the bottom of the method uses the value of 'type' to know // whether the element was handled and parsed. So if we don't handle a // particular type we'll drop through, and throw an error or create a // 'unknown' element depending on the settings. ElementType type = ElementType.Unknown; // -- Parse -- if (etd == ElementTypeDescriptor.Nil) { //---------------- type = ElementType.Nil; rawValue = null; } else if (etd == ElementTypeDescriptor.UnsignedInteger || etd == ElementTypeDescriptor.TwosComplementInteger) { //---------------- if (contentLength == 1) { byte valueU8 = ReadFieldUInt8_Content(networkOrderInteger, buffer, offset, length); if (etd == ElementTypeDescriptor.UnsignedInteger) { type = ElementType.UInt8; rawValue = valueU8; } else { type = ElementType.Int8; rawValue = unchecked((SByte)valueU8); } } else if (contentLength == 2) { ushort valueU16 = ReadFieldUInt16_Content(networkOrderInteger, buffer, offset, length); if (etd == ElementTypeDescriptor.UnsignedInteger) { type = ElementType.UInt16; rawValue = valueU16; } else { type = ElementType.Int16; rawValue = unchecked((Int16)valueU16); } } else if (contentLength == 4) { uint valueU32 = ReadFieldUInt32_Content(networkOrderInteger, buffer, offset, length); if (etd == ElementTypeDescriptor.UnsignedInteger) { type = ElementType.UInt32; rawValue = valueU32; } else { type = ElementType.Int32; rawValue = unchecked((Int32)valueU32); } } else if (contentLength == 8) { ulong valueU64 = ReadFieldUInt64_Content(networkOrderInteger, buffer, offset, length); if (etd == ElementTypeDescriptor.UnsignedInteger) { type = ElementType.UInt64; rawValue = valueU64; } else { type = ElementType.Int64; rawValue = unchecked((Int64)valueU64); } } else if (contentLength == 16) { //int my_readLength; byte[] valueArr128 = ReadArray_Content(buffer, offset, length); if (!networkOrderInteger) { Array.Reverse(valueArr128); } //Debug.Assert(my_readLength == 16, "but readLength is : " + my_readLength); //Debug.Assert(valueArr128.Length == 16, "but valueArr128.Length is : " + valueArr128.Length); if (etd == ElementTypeDescriptor.UnsignedInteger) { type = ElementType.UInt128; rawValue = valueArr128; } else { type = ElementType.Int128; rawValue = valueArr128; } } } else if (etd == ElementTypeDescriptor.Uuid) { //---------------- if (contentLength == 2) { ushort valueU16 = ReadFieldUInt16_Content(networkOrderUuid, buffer, offset + contentOffset, length - contentOffset); type = ElementType.Uuid16; rawValue = valueU16; } else if (contentLength == 4) { uint valueU32 = ReadFieldUInt32_Content(networkOrderUuid, buffer, offset + contentOffset, length - contentOffset); type = ElementType.Uuid32; rawValue = valueU32; } else if (contentLength == 16) { offset += contentOffset; Guid guidValue = new Guid( // NETCF doesn't have #ctor(uint, ushort, ...) overload. unchecked((int)ReadFieldUInt32(buffer, offset, contentLength)),//-- unchecked((short)ReadFieldUInt16(buffer, offset + 4, contentLength - 4)),//-- unchecked((short)ReadFieldUInt16(buffer, offset + 6, contentLength - 6)),//-- ReadFieldUInt8(buffer, offset + 8, contentLength - 8), ReadFieldUInt8(buffer, offset + 9, contentLength - 9),//-- ReadFieldUInt8(buffer, offset + 10, contentLength - 10), ReadFieldUInt8(buffer, offset + 11, contentLength - 11), ReadFieldUInt8(buffer, offset + 12, contentLength - 12), ReadFieldUInt8(buffer, offset + 13, contentLength - 13), ReadFieldUInt8(buffer, offset + 14, contentLength - 14), ReadFieldUInt8(buffer, offset + 15, contentLength - 15)); type = ElementType.Uuid128; rawValue = guidValue; } } else if (etd == ElementTypeDescriptor.Boolean) { //---------------- Debug.Assert(contentLength == 1); byte valueU8 = ReadFieldUInt8(buffer, offset + contentOffset, length - contentOffset); bool valueBoolean = valueU8 != 0; rawValue = valueBoolean; type = ElementType.Boolean; } else if (etd == ElementTypeDescriptor.ElementSequence || etd == ElementTypeDescriptor.ElementAlternative) { throw new InvalidOperationException("INTERNAL ERROR: Not supported here!!"); } else if (etd == ElementTypeDescriptor.Url) { //---------------- byte[] valueArray = ReadArray_Content(buffer, offset, length); if (LazyUrlCreation) { rawValue = valueArray; } else { string valueUri = CreateUriStringFromBytes(valueArray); rawValue = valueUri; } type = ElementType.Url; } else if (etd == ElementTypeDescriptor.TextString) { //---------------- byte[] valueArray = ReadArray_Content(buffer, offset, length); type = ElementType.TextString; rawValue = valueArray; } // -------- // Use parse result (or lack of!). if (type == ElementType.Unknown) { // If we haven't handled the type, we'll end-up here. string msg = "Element type: " + etd + ", SizeIndex: " + dbgSizeIndex + ", at contentLength: " + contentLength + ", at offset: " + offset + "."; if (SkipUnhandledElementTypes) { type = ElementType.Unknown; etd = ElementTypeDescriptor.Unknown; rawValue = null; Debug.WriteLine( "Unhandled SDP Parse " + msg); } else { throw new_NotImplementedException(msg); } } #pragma warning disable 618 ServiceElement value = new ServiceElement(etd, type, rawValue); #pragma warning restore 618 return value; } private static byte ReadFieldUInt8_Content(bool networkOrder, byte[] buffer, int offset, int length) { return ReadFieldUInt8(buffer, offset, length); } private static ushort ReadFieldUInt16_Content(bool networkOrder, byte[] buffer, int offset, int length) { return ReadFieldUInt16(networkOrder, buffer, offset, length); } private static uint ReadFieldUInt32_Content(bool networkOrder, byte[] buffer, int offset, int length) { return ReadFieldUInt32(networkOrder, buffer, offset, length); } private static ulong ReadFieldUInt64_Content(bool networkOrder, byte[] buffer, int offset, int length) { return ReadFieldUInt64(networkOrder, buffer, offset, length); } //TODO Make these swap to Host Order....??? private static byte[] ReadArray_Content(byte[] buffer, int offset, int length) { int contentOffset = 0; int contentLength = length; byte[] result = new byte[contentLength]; Array.Copy(buffer, offset + contentOffset, result, 0, contentLength); return result; } //-------------------------------------------------------------- // The result length values always also covers the header bytes themselves. // This is different from how the protocol itself writes length values. /// #if CODE_ANALYSIS [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "3#")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "4#")] #endif protected static int GetElementLength(byte[] buffer, int index, int length, out int contentOffset, out int contentLength) { // As we can be called by sub-types we need to do 'public' arg checking... if (buffer == null) { throw new ArgumentNullException("buffer"); } if (length <= 0) { throw new ArgumentOutOfRangeException("length"); } if (index < 0 || index == int.MaxValue) { // As suggested by FxCop throw new ArgumentOutOfRangeException("index"); } if (length > buffer.Length - index) { throw new ArgumentException("length and index overruns buffer."); } // ElementTypeDescriptor etd; SizeIndex si; SplitHeaderByte(buffer[index], out etd, out si); if (etd == ElementTypeDescriptor.Nil) { if (si == SizeIndex.LengthOneByteOrNil) { contentOffset = 1; //not valid for Nil, but return a length anyway contentLength = 0; return 1; //one byte length element } throw CreateInvalidException(ErrorMsgSizeIndexNotSuitTypeD, index); } else { switch (si) { case SizeIndex.LengthOneByteOrNil: contentOffset = 1; return FixupLength(1, contentOffset, out contentLength, index); case SizeIndex.LengthTwoBytes: contentOffset = 1; return FixupLength(2, contentOffset, out contentLength, index); case SizeIndex.LengthFourBytes: contentOffset = 1; return FixupLength(4, contentOffset, out contentLength, index); case SizeIndex.LengthEightBytes: contentOffset = 1; return FixupLength(8, contentOffset, out contentLength, index); case SizeIndex.LengthSixteenBytes: contentOffset = 1; return FixupLength(16, contentOffset, out contentLength, index); case SizeIndex.AdditionalUInt8: CheckParseLength(index, length, 1 + 1); contentOffset = 2; Byte length8 = ReadFieldUInt8(buffer, index + 1, length - 1); return FixupLength(length8, contentOffset, out contentLength, index); case SizeIndex.AdditionalUInt16: CheckParseLength(index, length, 1 + 2); contentOffset = 3; UInt16 length16 = ReadFieldUInt16(buffer, index + 1, length - 1); return FixupLength(length16, contentOffset, out contentLength, index); default: System.Diagnostics.Debug.Assert(si == SizeIndex.AdditionalUInt32); CheckParseLength(index, length, 1 + 4); contentOffset = 5; UInt32 length32 = ReadFieldUInt32(buffer, index + 1, length - 1); return FixupLength(length32, contentOffset, out contentLength, index); } } } private static int FixupLength(UInt32 contentLength, int contentOffsetAlsoHeaderBytesLength, out int outContentLength, int index) { Int64 fullLength = contentLength + contentOffsetAlsoHeaderBytesLength; if (fullLength > Int32.MaxValue) { throw new System.Net.ProtocolViolationException( String.Format(System.Globalization.CultureInfo.InvariantCulture, ErrorMsgFormatNotSupportFull32bitSized, index)); } int resultLength = checked((int)fullLength); outContentLength = resultLength - contentOffsetAlsoHeaderBytesLength; System.Diagnostics.Debug.Assert(contentLength == outContentLength); return resultLength; } //-------------------------------------------------------------- private static void CheckParseLength(int index, int length, int requiredLength) { if (requiredLength > length) { throw CreateInvalidException(ErrorMsgFormatTruncated, index); } } private static Byte ReadFieldUInt8(byte[] bytes, int index, int length) { CheckParseLength(index, length, 1); Byte value8 = bytes[index]; return value8; } private static UInt16 ReadFieldUInt16(byte[] bytes, int index, int length) { return ReadFieldUInt16(true, bytes, index, length); } private static UInt16 ReadFieldUInt16(bool networkOrder, byte[] bytes, int index, int length) { CheckParseLength(index, length, 2); short value16TypeToSuitHostToNetworkOrder = BitConverter.ToInt16(bytes, index); short value16Signed; if (networkOrder) value16Signed = System.Net.IPAddress.NetworkToHostOrder(value16TypeToSuitHostToNetworkOrder); else value16Signed = value16TypeToSuitHostToNetworkOrder; UInt16 value16 = unchecked((UInt16)value16Signed); return value16; } private static UInt32 ReadFieldUInt32(byte[] bytes, int index, int length) { return ReadFieldUInt32(true, bytes, index, length); } private static UInt32 ReadFieldUInt32(bool networkOrder, byte[] bytes, int index, int length) { CheckParseLength(index, length, 4); int value32TypeToSuitHostToNetworkOrder = BitConverter.ToInt32(bytes, index); int value32Signed; if (networkOrder) value32Signed = System.Net.IPAddress.NetworkToHostOrder(value32TypeToSuitHostToNetworkOrder); else value32Signed = value32TypeToSuitHostToNetworkOrder; UInt32 value32 = unchecked((UInt32)value32Signed); return value32; } private static UInt64 ReadFieldUInt64(byte[] bytes, int index, int length) { return ReadFieldUInt64(true, bytes, index, length); } private static UInt64 ReadFieldUInt64(bool networkOrder, byte[] bytes, int index, int length) { CheckParseLength(index, length, 8); Int64 value64TypeToSuitHostToNetworkOrder = BitConverter.ToInt64(bytes, index); Int64 value64Signed; if (networkOrder) value64Signed = System.Net.IPAddress.NetworkToHostOrder(value64TypeToSuitHostToNetworkOrder); else value64Signed = value64TypeToSuitHostToNetworkOrder; UInt64 value64 = unchecked((UInt64)value64Signed); return value64; } //-------------------------------------------------------------- internal static string CreateUriStringFromBytes(byte[] valueArray) { System.Text.Encoding enc = System.Text.Encoding.ASCII; string str = enc.GetString(valueArray, 0, valueArray.Length); return str; } //-------------------------------------------------------------- private static Exception CreateInvalidException(string formatMessage, int index) { return new System.Net.ProtocolViolationException( string.Format(System.Globalization.CultureInfo.InvariantCulture, formatMessage, index)); } private static Exception CreateInvalidExceptionOverruns(int index, int elementLength, int length) { Debug.Assert(elementLength > length); Debug.WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture, ErrorMsgElementOverrunsBuffer_WithLengths, index, elementLength, length)); #if SDP_OVERRUN_HAS_LENGTHS // The lengths passed in are not always as we would expect, the checks // and calculations together detect all overruns but the calculations // at certain points are not as expected... :-,( string format = ErrorMsgElementOverrunsBuffer_WithLengths; #else string format = ErrorMsgElementOverrunsBuffer; #endif return new System.Net.ProtocolViolationException( String.Format(System.Globalization.CultureInfo.InvariantCulture, format, index, elementLength, length)); } //-------------------------------------------------------------- /// /// Split a header byte into its and /// parts. /// /// /// The returned is not checked to be a /// known value. /// /// - /// The byte from the header. /// /// The /// value from the header byte. /// /// The /// value from a header byte. /// /// - /// /// #if CODE_ANALYSIS [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "0#")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#")] #endif public static void SplitHeaderByte(byte headerByte, out ElementTypeDescriptor etd, out SizeIndex sizeIndex) { etd = GetElementTypeDescriptor(headerByte); sizeIndex = GetSizeIndex(headerByte); } /// /// Extract the value from a header byte. /// /// /// The returned is not checked to be a /// known value. /// /// - /// The byte from the header. /// /// - /// The value as a . /// - /// /// [System.Diagnostics.DebuggerStepThroughAttribute] #if CODE_ANALYSIS [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "0#")] #endif public static ElementTypeDescriptor GetElementTypeDescriptor(byte headerByte) { UInt32 etdRaw = (UInt32)headerByte >> ElementTypeDescriptorOffset; ElementTypeDescriptor etd = (ElementTypeDescriptor)etdRaw; return etd; } /// /// Extract the field from a header byte. /// /// - /// The byte from the header. /// /// - /// The value as a . /// - /// /// [System.Diagnostics.DebuggerStepThroughAttribute] #if CODE_ANALYSIS [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "0#")] #endif public static SizeIndex GetSizeIndex(byte headerByte) { int siRaw = headerByte & SizeIndexMask; SizeIndex sizeIndex = (SizeIndex)siRaw; return sizeIndex; } /// /// Bit offset of the ElementTypeDescriptor field in a header byte. /// /// /// The header byte has two parts: five bits of ElementTypeDescriptor and /// three bits of Size Index. /// /// internal const int ElementTypeDescriptorOffset = 3; /// /// Mask for the SizeIndex field in a header byte. /// /// /// The header byte has two parts: five bits of ElementTypeDescriptor and /// three bits of Size Index, upper and lower respectively. /// /// internal const int SizeIndexMask = 0x07; //-------------------------------------------------------------- internal static void VerifyTypeMatchesEtd(ElementTypeDescriptor etd, ElementType type) { if (!TypeMatchesEtd(etd, type)) { throw new ProtocolViolationException(String.Format(System.Globalization.CultureInfo.InvariantCulture, ErrorMsgFormatTypeNotTypeDSubtype, etd, type)); } } // is used by tests... /// public static bool TypeMatchesEtd(ElementTypeDescriptor etd, ElementType type) { bool matches = false; if (etd == ElementTypeDescriptor.Unknown) { matches = (type == ElementType.Unknown); } else if (etd == ElementTypeDescriptor.Nil) { matches = (type == ElementType.Nil); } else if (etd == ElementTypeDescriptor.UnsignedInteger) { matches = (type == ElementType.UInt8) || (type == ElementType.UInt16) || (type == ElementType.UInt32) || (type == ElementType.UInt64) || (type == ElementType.UInt128); } else if (etd == ElementTypeDescriptor.TwosComplementInteger) { matches = (type == ElementType.Int8) || (type == ElementType.Int16) || (type == ElementType.Int32) || (type == ElementType.Int64) || (type == ElementType.Int128); } else if (etd == ElementTypeDescriptor.Uuid) { matches = (type == ElementType.Uuid16) || (type == ElementType.Uuid32) || (type == ElementType.Uuid128); } else if (etd == ElementTypeDescriptor.TextString) { matches = (type == ElementType.TextString); } else if (etd == ElementTypeDescriptor.Boolean) { matches = (type == ElementType.Boolean); } else if (etd == ElementTypeDescriptor.ElementSequence) { matches = (type == ElementType.ElementSequence); } else if (etd == ElementTypeDescriptor.ElementAlternative) { matches = (type == ElementType.ElementAlternative); } else if (etd == ElementTypeDescriptor.Url) { matches = (type == ElementType.Url); } return matches; } /// public static ElementTypeDescriptor GetEtdForType(ElementType type) { ElementTypeDescriptor etd; //if (type == ElementType.Unknown) { // etd = ElementTypeDescriptor.Unknown;}else if (type == ElementType.Nil) { etd = ElementTypeDescriptor.Nil; } else if ((type == ElementType.UInt8) || (type == ElementType.UInt16) || (type == ElementType.UInt64) || (type == ElementType.UInt32)) { etd = ElementTypeDescriptor.UnsignedInteger; } else if ((type == ElementType.Int8) || (type == ElementType.Int16) || (type == ElementType.Int64) || (type == ElementType.Int32)) { etd = ElementTypeDescriptor.TwosComplementInteger; } else if ((type == ElementType.Uuid16) || (type == ElementType.Uuid32) || (type == ElementType.Uuid128)) { etd = ElementTypeDescriptor.Uuid; } else if ((type == ElementType.TextString)) { etd = ElementTypeDescriptor.TextString; } else if ((type == ElementType.Boolean)) { etd = ElementTypeDescriptor.Boolean; } else if ((type == ElementType.ElementSequence)) { etd = ElementTypeDescriptor.ElementSequence; } else if ((type == ElementType.ElementAlternative)) { etd = ElementTypeDescriptor.ElementAlternative; } else if ((type == ElementType.Url)) { etd = ElementTypeDescriptor.Url; } else { throw new_ArgumentOutOfRangeException("type", String.Format(System.Globalization.CultureInfo.InvariantCulture, ErrorMsgFormatUnknownType, type)); } #if DEBUG VerifyTypeMatchesEtd(etd, type); #endif return etd; } //-------------------------------------------------------------- internal static void VerifyAllowedSizeIndex(ElementTypeDescriptor etd, SizeIndex sizeIndex, bool allowAnySizeIndexForUnknownTypeDescriptorElements) { if (!IsAllowedSizeIndex(etd, sizeIndex, allowAnySizeIndexForUnknownTypeDescriptorElements)) { throw new ProtocolViolationException(ErrorMsgSizeIndexNotSuitTypeD); } } private static bool IsAllowedSizeIndex(ElementTypeDescriptor etd, SizeIndex sizeIndex, bool allowAnySizeIndexForUnknownTypeDescriptorElements) { bool isValidSi = false; if (etd == ElementTypeDescriptor.Nil || etd == ElementTypeDescriptor.Boolean) { isValidSi = (sizeIndex == SizeIndex.LengthOneByteOrNil); } else if (etd == ElementTypeDescriptor.UnsignedInteger || etd == ElementTypeDescriptor.TwosComplementInteger) { isValidSi = (sizeIndex == SizeIndex.LengthOneByteOrNil) || (sizeIndex == SizeIndex.LengthTwoBytes) || (sizeIndex == SizeIndex.LengthFourBytes) || (sizeIndex == SizeIndex.LengthEightBytes) || (sizeIndex == SizeIndex.LengthSixteenBytes); } else if (etd == ElementTypeDescriptor.Uuid) { isValidSi = (sizeIndex == SizeIndex.LengthTwoBytes) || (sizeIndex == SizeIndex.LengthFourBytes) || (sizeIndex == SizeIndex.LengthSixteenBytes); } else if (etd == ElementTypeDescriptor.TextString || etd == ElementTypeDescriptor.ElementSequence || etd == ElementTypeDescriptor.ElementAlternative || etd == ElementTypeDescriptor.Url) { isValidSi = (sizeIndex == SizeIndex.AdditionalUInt8) || (sizeIndex == SizeIndex.AdditionalUInt16) || (sizeIndex == SizeIndex.AdditionalUInt32); } else { isValidSi = allowAnySizeIndexForUnknownTypeDescriptorElements; } return isValidSi; }//fn //-------------------------------------------------------------- internal static Exception new_NotImplementedException(string message) { #if (PocketPC && V1) return new NotSupportedException(message); #else return new NotImplementedException(message); #endif } internal static Exception new_ArgumentOutOfRangeException(string paramName, string message) { #if (PocketPC && V1) return new ArgumentOutOfRangeException(message); #else return new ArgumentOutOfRangeException(paramName, message); #endif } internal static Exception new_ArgumentOutOfRangeException(string message, Exception innerException) { #if NETCF return new ArgumentOutOfRangeException(message); #else return new ArgumentOutOfRangeException(message, innerException); #endif } //-------------------------------------------------------------- /// public const string ErrorMsgFormatUnknownType = "Unknown ElementType '{0}'."; /// public const string ErrorMsgFormatTypeNotTypeDSubtype = "ElementType '{1}' is not of given TypeDescriptor '{0}'."; /// public const String ErrorMsgSizeIndexNotSuitTypeD = "SizeIndex is not value for TypeDescriptor."; /// public const string ErrorMsgServiceRecordBytesZeroLength = "ServiceRecord byte array must be at least one byte long."; // /// public const string ErrorMsgFormatInvalidHeaderBytes = "Invalid header bytes at index {0}."; /// public const string ErrorMsgFormatTruncated = "Header truncated from index {0}."; /// public const string ErrorMsgFormatNotSupportFull32bitSized = "No support for full sized 32bit length values (index {0})."; /// public const string ErrorMsgTypeNotAsExpected = "Element Type not as expected."; // /// public const string ErrorMsgTopElementNotSequence = "The top element must be a Element Sequence type."; /// public const string ErrorMsgMultiSeqChildElementNotSequence = "In a multi-record sequence each element must be an Element Sequence."; // /// public const string ErrorMsgSequenceOverruns = "Element Sequence overruns the data, from index {0}."; /// public const string ErrorMsgElementOverrunsBuffer_WithLengths = "Element overruns buffer section, from index {0}" + ", item length is {1} but remaining length is only {2}" + "."; /// public const string ErrorMsgElementOverrunsBuffer = "Element overruns buffer section, from index {0}."; /// public const string ErrorMsgElementOverrunsBufferPrefix = "Element overruns buffer section, from index "; /// public const string ErrorMsgAttributePairFirstMustUint16 = "The Attribute Id at index {0} is not of type Uint16."; }//class }