mirror of
https://github.com/inthehand/32feet.git
synced 2026-10-20 23:56:14 +08:00
836 lines
40 KiB
C#
836 lines
40 KiB
C#
// 32feet.NET - Personal Area Networking for .NET
|
|
//
|
|
// InTheHand.Net.Bluetooth.ServiceElement
|
|
//
|
|
// Copyright (c) 2003-2020 In The Hand Ltd, All rights reserved.
|
|
// This source code is licensed under the MIT License
|
|
|
|
using InTheHand.Net.Bluetooth.AttributeIds;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
|
|
namespace InTheHand.Net.Bluetooth.Sdp
|
|
{
|
|
|
|
/// <summary>
|
|
/// Holds an SDP data element.
|
|
/// </summary>
|
|
/// -
|
|
/// <remarks>
|
|
/// <para>A Service Element hold the data in a SDP Service Record. It can
|
|
/// hold various types of data, being like the ‘variant’ type in some
|
|
/// environments. Each <see cref="T:InTheHand.Net.Bluetooth.ServiceAttribute"/> in
|
|
/// a <see cref="T:InTheHand.Net.Bluetooth.ServiceRecord"/> holds its content in a
|
|
/// Service Element.
|
|
/// </para>
|
|
/// <para>The types currently defined in the Service Discovery specification
|
|
/// include unsigned and signed integers
|
|
/// of various sizes (8-bit, 16-bit etc), UUIDs in the full 128-bit form or
|
|
/// in the 16 and 32-bit forms, TextString, Url etc. An element can itself
|
|
/// also contain a list of element, either as a ‘sequence’ or an
|
|
/// ‘alternative’, and thus an attribute can contain a tree of values,
|
|
/// e.g. as used by the
|
|
/// <see cref="F:InTheHand.Net.Bluetooth.AttributeIds.UniversalAttributeId.ProtocolDescriptorList"/>
|
|
/// attribute.
|
|
/// </para>
|
|
/// <para>The type that an element is holding can be accessed with the
|
|
/// <see cref="P:InTheHand.Net.Bluetooth.ServiceElement.ElementTypeDescriptor"/> and
|
|
/// <see cref="P:InTheHand.Net.Bluetooth.ServiceElement.ElementType"/> properties which
|
|
/// are of type <see cref="T:InTheHand.Net.Bluetooth.ElementTypeDescriptor"/> and
|
|
/// <see cref="T:InTheHand.Net.Bluetooth.ElementType"/> respectively, the former being
|
|
/// the ‘major’ type e.g.
|
|
/// <see cref="F:InTheHand.Net.Bluetooth.ElementTypeDescriptor.UnsignedInteger"/>, and
|
|
/// the latter the ‘minor’ type e.g.
|
|
/// <see cref="F:InTheHand.Net.Bluetooth.ElementType.UInt16"/>.
|
|
/// </para>
|
|
/// <para>The element's value can be accessed in various ways, either directly
|
|
/// in its internal form through its <see cref="P:InTheHand.Net.Bluetooth.ServiceElement.Value"/>
|
|
/// property. It has return type <see cref="T:System.Object"/> so the value
|
|
/// will have to be cast before use, see the <c>UInt16</c> example below. There
|
|
/// are also a number of type-specific methods, e.g.
|
|
/// <see cref="M:InTheHand.Net.Bluetooth.ServiceElement.GetValueAsElementArray"/>,
|
|
/// <see cref="M:InTheHand.Net.Bluetooth.ServiceElement.GetValueAsUuid"/>,
|
|
/// <see cref="M:InTheHand.Net.Bluetooth.ServiceElement.GetValueAsString(System.Text.Encoding)"/>
|
|
/// etc. Each will throw an <see cref="T:System.InvalidOperationException"/>
|
|
/// if the element is not of a suitable type. The complete set is:</para>
|
|
/// <list type="table">
|
|
/// <listheader><term><see cref="T:InTheHand.Net.Bluetooth.ElementType"/></term>
|
|
/// <description>Access method, or .NET Type for direct access</description>
|
|
/// </listheader>
|
|
/// <item><term><c>Nil</c></term>
|
|
/// <description><see langword="null"/></description></item>
|
|
///
|
|
/// <item><term><c>Uint8</c></term><description><see cref="T:System.Byte"/></description></item>
|
|
/// <item><term><c>Uint16</c></term><description><see cref="T:System.UInt16"/></description></item>
|
|
/// <item><term><c>Uint32</c></term><description><see cref="T:System.UInt32"/></description></item>
|
|
/// <item><term><c>Uint64</c></term><description>Currently unsupported.</description></item>
|
|
/// <item><term><c>Uint128</c></term><description>Currently unsupported.</description></item>
|
|
///
|
|
/// <item><term><c>Int8</c></term><description><see cref="T:System.SByte"/></description></item>
|
|
/// <item><term><c>Int16</c></term><description><see cref="T:System.Int16"/></description></item>
|
|
/// <item><term><c>Int32</c></term><description><see cref="T:System.Int32"/></description></item>
|
|
/// <item><term><c>Int64</c></term><description>Currently unsupported.</description></item>
|
|
/// <item><term><c>Int128</c></term><description>Currently unsupported.</description></item>
|
|
///
|
|
/// <item><term><c>Uuid16</c></term><description>Via <see cref="M:InTheHand.Net.Bluetooth.ServiceElement.GetValueAsUuid"/>, or as <see cref="T:System.UInt16"/></description></item>
|
|
/// <item><term><c>Uuid32</c></term><description>Via <see cref="M:InTheHand.Net.Bluetooth.ServiceElement.GetValueAsUuid"/>, or as <see cref="T:System.UInt16"/></description></item>
|
|
/// <item><term><c>Uuid128</c></term><description>Via <see cref="M:InTheHand.Net.Bluetooth.ServiceElement.GetValueAsUuid"/></description></item>
|
|
///
|
|
/// <item><term><c>TextString</c></term><description>With
|
|
/// <see cref="M:InTheHand.Net.Bluetooth.ServiceElement.GetValueAsString(System.Text.Encoding)"/>
|
|
/// or <see cref="M:InTheHand.Net.Bluetooth.ServiceElement.GetValueAsStringUtf8"/> etc.
|
|
/// The underlying value can be an array of bytes, or as a <see cref="T:System.String"/>
|
|
/// the <see cref="T:InTheHand.Net.Bluetooth.ServiceRecordParser"/> will set an
|
|
/// array of bytes, whereas a manually created record will likely contain a
|
|
/// <see cref="T:System.String"/>.
|
|
/// </description></item>
|
|
///
|
|
/// <item><term><c>Boolean</c></term><description><see cref="T:System.Boolean"/></description></item>
|
|
///
|
|
/// <item><term><c>ElementSequence</c></term><description>With
|
|
/// <see cref="M:InTheHand.Net.Bluetooth.ServiceElement.GetValueAsElementArray"/> or
|
|
/// <see cref="M:InTheHand.Net.Bluetooth.ServiceElement.GetValueAsElementList"/>
|
|
/// </description></item>
|
|
/// <item><term><c>ElementSequence</c></term><description>-"-</description></item>
|
|
///
|
|
/// <item><term><c>Url</c></term><description>Via <see cref="M:InTheHand.Net.Bluetooth.ServiceElement.GetValueAsUri"/>,
|
|
/// can be stored interally as <see cref="T:System.Uri"/> or as an array of bytes
|
|
/// </description></item>
|
|
/// </list>
|
|
///
|
|
/// <para>Note that there are no access
|
|
/// methods for the numeric type for instance so the
|
|
/// <see cref="P:InTheHand.Net.Bluetooth.ServiceElement.Value"/> property will have
|
|
/// to be used e.g.
|
|
/// <code lang="C#">
|
|
/// // ElementType is UInt16
|
|
/// ushort x = (ushort)element.Value;
|
|
/// </code>
|
|
/// or
|
|
/// <code lang="C#">
|
|
/// // ElementType is UInt16
|
|
/// Dim x As UShort = CUShort(element.Value);
|
|
/// </code>
|
|
/// </para>
|
|
/// <para>Additional type-specific methods can be added as required, in fact the
|
|
/// full set of 19+ could be added, it just requires implementation and test…
|
|
/// </para>
|
|
/// </remarks>
|
|
public sealed class ServiceElement
|
|
{
|
|
//--------------------------------------------------------------
|
|
ElementType m_type;
|
|
ElementTypeDescriptor m_etd;
|
|
object m_rawValue;
|
|
//
|
|
private const bool _strictStringDecoding = true;
|
|
|
|
|
|
//--------------------------------------------------------------
|
|
|
|
#pragma warning disable 618
|
|
|
|
/// <overloads>
|
|
/// Initializes a new instance of the <see cref="T:InTheHand.Net.Bluetooth.ServiceElement"/> class.
|
|
/// </overloads>
|
|
/// -
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="T:InTheHand.Net.Bluetooth.ServiceElement"/> class.
|
|
/// </summary>
|
|
/// -
|
|
/// <remarks>
|
|
/// <para>The type of the object passed in the <paramref name="value"/> parameter
|
|
/// <strong>must</strong> suit the type of the element. For instance if the element type is
|
|
/// <see cref="F:InTheHand.Net.Bluetooth.ElementType.UInt8"/> then the object
|
|
/// passed in must be a <see cref="T:System.Byte"/>, if the element type is
|
|
/// <see cref="F:InTheHand.Net.Bluetooth.ElementType.TextString"/> then the object
|
|
/// must either be a <see cref="T:System.String"/> or the string encoded as
|
|
/// an array of <see cref="T:System.Byte"/>,
|
|
/// and if the element type is <see cref="F:InTheHand.Net.Bluetooth.ElementType.Uuid16"/>
|
|
/// then the object passed in must be a <see cref="T:System.UInt16"/>,
|
|
/// etc.
|
|
/// For the full list of types see the class level documentation
|
|
/// (<see cref="T:InTheHand.Net.Bluetooth.ServiceElement"/>).
|
|
/// </para>
|
|
/// <para>For numerical element types the
|
|
/// <see cref="M:InTheHand.Net.Bluetooth.ServiceElement.CreateNumericalServiceElement(InTheHand.Net.Bluetooth.ElementType,System.Object)"/>
|
|
/// factory method will accept any integer type and attempt to convert it to the
|
|
/// required type before creating the <see cref="T:InTheHand.Net.Bluetooth.ServiceElement"/>,
|
|
/// for example for element type <see cref="F:InTheHand.Net.Bluetooth.ElementType.UInt8"/>
|
|
/// it will accept an <see cref="T:System.Int32"/> parameter and convert
|
|
/// it to a <see cref="T:System.Byte"/> internally.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// -
|
|
/// <param name="type">The type of the element as an ElementType.
|
|
/// </param>
|
|
/// <param name="value">The value for the new element,
|
|
/// <strong>must</strong> suit the type of the element.
|
|
/// See the remarks for more information.
|
|
/// </param>
|
|
/// -
|
|
/// <example>
|
|
/// <code lang="C#">
|
|
/// ServiceElement e
|
|
/// e = new ServiceElement(ElementType.TextString, "Hello world");
|
|
/// e = new ServiceElement(ElementType.TextString, new byte[] { (byte)'h', (byte)'i', });
|
|
/// e = new ServiceElement(ElementType.Uuid16, (UInt16)0x1101);
|
|
///
|
|
///
|
|
/// int i = 10;
|
|
/// int j = -1;
|
|
///
|
|
/// // Error, Int32 not suitable for element type UInt8.
|
|
/// ServiceElement e0 = new ServiceElement(ElementType.UInt8, i);
|
|
///
|
|
/// // Success, Byte value 10 stored.
|
|
/// ServiceElement e1 = ServiceElement.CreateNumericalServiceElement(ElementType.UInt8, i);
|
|
///
|
|
/// // Error, -1 not in range of type Byte.
|
|
/// ServiceElement e2 = ServiceElement.CreateNumericalServiceElement(ElementType.UInt8, j);
|
|
/// </code>
|
|
/// </example>
|
|
public ServiceElement(ElementType type, object value)
|
|
: this(GetEtdForType(type), type, value)
|
|
{ }
|
|
|
|
#pragma warning restore 618
|
|
|
|
|
|
private static ElementTypeDescriptor GetEtdForType(ElementType type)
|
|
{
|
|
return ServiceRecordParser.GetEtdForType(type);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="T:InTheHand.Net.Bluetooth.ServiceElement"/> class.
|
|
/// </summary>
|
|
/// -
|
|
/// <param name="type">The type of the element as an ElementType.
|
|
/// Should be either <c>ElementSequence</c>/<c>ElementAlternative</c> types.
|
|
/// </param>
|
|
/// <param name="childElements">A list of elements.
|
|
/// </param>
|
|
public ServiceElement(ElementType type, IList<ServiceElement> childElements)
|
|
: this(type, (object)childElements)
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="T:InTheHand.Net.Bluetooth.ServiceElement"/> class.
|
|
/// </summary>
|
|
/// -
|
|
/// <param name="type">The type of the element as an ElementType.
|
|
/// Should be either <c>ElementSequence</c>/<c>ElementAlternative</c> types.
|
|
/// </param>
|
|
/// <param name="childElements">A list of elements.
|
|
/// </param>
|
|
public ServiceElement(ElementType type, params ServiceElement[] childElements)
|
|
: this(checkTypeSuitsElementParamsArray(type, childElements), (object)childElements)
|
|
{ }
|
|
|
|
private static ElementType checkTypeSuitsElementParamsArray(ElementType typePassThru, ServiceElement[] childElements)
|
|
{
|
|
// Throw a more specific error about the params array when the value is null.
|
|
// As error would be thrown by SetValue, but we can report a more helpful message.
|
|
if (childElements == null) {
|
|
// Caller passed a literal null, so could be aiming for other ElementType
|
|
// so can't complain that we need to be a Seq/Alt type.
|
|
} else {
|
|
//
|
|
if (typePassThru != ElementType.ElementSequence && typePassThru != ElementType.ElementAlternative) {
|
|
throw new ArgumentException(ErrorMsgSeqAltTypeNeedElementArray);
|
|
}
|
|
}
|
|
return typePassThru;
|
|
}
|
|
|
|
//--------------------------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Obsolete, use <see cref="M:InTheHand.Net.Bluetooth.ServiceElement.#ctor(InTheHand.Net.Bluetooth.ElementType,System.Object)"/> instead.
|
|
/// Initializes a new instance of the <see cref="T:InTheHand.Net.Bluetooth.ServiceElement"/> class.
|
|
/// </summary>
|
|
internal ServiceElement(ElementTypeDescriptor etd, ElementType type, object value)
|
|
{
|
|
ServiceRecordParser.VerifyTypeMatchesEtd(etd, type);
|
|
m_type = type;
|
|
m_etd = etd;
|
|
SetValue(value);
|
|
}
|
|
|
|
internal void SetValue(object value)
|
|
{
|
|
ElementTypeDescriptor etd = m_etd;
|
|
ElementType type = m_type;
|
|
//
|
|
if (value == null) {
|
|
if (etd == ElementTypeDescriptor.ElementSequence || etd == ElementTypeDescriptor.ElementAlternative) {
|
|
throw new ArgumentNullException("value", "Type DataElementSequence and DataElementAlternative need an list of AttributeValue.");
|
|
} else if (etd == ElementTypeDescriptor.Nil) {
|
|
} else if (etd == ElementTypeDescriptor.Unknown) {
|
|
} else {
|
|
throw new ArgumentNullException("value", "Null not valid for type: '" + type + "'.");
|
|
}
|
|
} else {
|
|
// Check iff type=seq/alt then value is IList<ServiceElement>
|
|
|
|
IList<ServiceElement> asElementList = value as IList<ServiceElement>;
|
|
|
|
if (etd == ElementTypeDescriptor.ElementSequence || etd == ElementTypeDescriptor.ElementAlternative) {
|
|
if (asElementList == null) {
|
|
throw new ArgumentException("Type ElementSequence and ElementAlternative need an list of ServiceElement.");
|
|
}
|
|
} else {
|
|
if (asElementList != null) {
|
|
throw new ArgumentException("Type ElementSequence and ElementAlternative must be used for an list of ServiceElement.");
|
|
}
|
|
}
|
|
//--------
|
|
bool validTypeForType;
|
|
if (type == ElementType.Nil) {
|
|
validTypeForType = value == null;
|
|
} else if (etd == ElementTypeDescriptor.UnsignedInteger || etd == ElementTypeDescriptor.TwosComplementInteger) {
|
|
switch (type) {
|
|
case ElementType.UInt8:
|
|
validTypeForType = value is byte;
|
|
break;
|
|
case ElementType.Int8:
|
|
validTypeForType = value is sbyte;
|
|
break;
|
|
case ElementType.UInt16:
|
|
validTypeForType = value is ushort;
|
|
break;
|
|
case ElementType.Int16:
|
|
validTypeForType = value is short;
|
|
break;
|
|
case ElementType.UInt32:
|
|
validTypeForType = value is uint;
|
|
break;
|
|
case ElementType.Int32:
|
|
validTypeForType = value is int;
|
|
break;
|
|
case ElementType.UInt64:
|
|
validTypeForType = value is ulong;
|
|
break;
|
|
case ElementType.Int64:
|
|
validTypeForType = value is long;
|
|
break;
|
|
case ElementType.UInt128:
|
|
case ElementType.Int128:
|
|
const int NumBytesIn128bits = 16;
|
|
byte[] arr = value as byte[];
|
|
if (arr != null && arr.Length == NumBytesIn128bits) {
|
|
validTypeForType = true;
|
|
} else { // HACK UNTESTED
|
|
validTypeForType = false;
|
|
throw new ArgumentException(
|
|
"Element type '" + type + "' needs a length 16 byte array.");
|
|
}
|
|
break;
|
|
default:
|
|
System.Diagnostics.Debug.Fail("Unexpected numerical type");
|
|
validTypeForType = false;
|
|
break;
|
|
}//switch
|
|
} else if (type == ElementType.Uuid16) {
|
|
validTypeForType = value is ushort;
|
|
validTypeForType = value is ushort;
|
|
} else if (type == ElementType.Uuid32) {
|
|
validTypeForType
|
|
= value is UInt16
|
|
|| value is Int16
|
|
|| value is UInt32
|
|
|| value is Int32
|
|
;
|
|
} else if (type == ElementType.Uuid128) {
|
|
validTypeForType = value is Guid;
|
|
} else if (type == ElementType.TextString) {
|
|
validTypeForType = value is byte[] || value is String;
|
|
} else if (type == ElementType.Boolean) {
|
|
validTypeForType = value is bool;
|
|
} else if (type == ElementType.ElementSequence || type == ElementType.ElementAlternative) {
|
|
validTypeForType = asElementList != null;
|
|
} else {
|
|
// if (type == ElementType.Url)
|
|
System.Diagnostics.Debug.Assert(type == ElementType.Url);
|
|
validTypeForType = value is byte[] || value is Uri || value is string;
|
|
}
|
|
if (!validTypeForType) {
|
|
throw new ArgumentException("CLR type '" + value.GetType().Name + "' not valid type for element type '" + type + "'.");
|
|
}
|
|
}
|
|
m_rawValue = value;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Create an instance of <see cref="T:InTheHand.Net.Bluetooth.ServiceElement"/>
|
|
/// but internally converting the numeric value to the required type.
|
|
/// </summary>
|
|
/// -
|
|
/// <remarks>
|
|
/// <para>As noted in the constructor documentation
|
|
/// (<see cref="M:InTheHand.Net.Bluetooth.ServiceElement.#ctor(InTheHand.Net.Bluetooth.ElementType,System.Object)"/>)
|
|
/// the type of the value supplied <strong>must</strong> exactly match the element's natural type,
|
|
/// the contructor will return an error if that is not the case. This method
|
|
/// will instead attempt to convert the value to the required type. It uses
|
|
/// the <see cref="T:System.IConvertible"/> interface to do the conversion, for
|
|
/// instance if the element type is <c>Uint16</c> then it will cast the input value
|
|
/// to <see cref="T:System.IConvertible"/> and call
|
|
/// <see cref="M:System.IConvertible.ToUInt16(System.IFormatProvider)"/> on it.
|
|
/// If the value is not convertible to the element type then an
|
|
/// <see cref="T:System.ArgumentOutOfRangeException"/> will be thrown see below.
|
|
/// </para>
|
|
/// <para>For instance, passing in an C# <c>int</c> / Visual Basic <c>Integer</c>
|
|
/// to the constructor will fail for element types <see cref="F:InTheHand.Net.Bluetooth.ElementType.UInt8"/>
|
|
/// etc, however by using this method it will succeed if the value is in the
|
|
/// correct range.
|
|
/// For example
|
|
/// <code lang="C#">
|
|
/// int i = 10;
|
|
/// int j = -1;
|
|
///
|
|
/// // Error, Int32 not suitable for element type UInt8.
|
|
/// ServiceElement e0 = new ServiceElement(ElementType.UInt8, i);
|
|
///
|
|
/// // Success, Byte value 10 stored.
|
|
/// ServiceElement e1 = ServiceElement.CreateNumericalServiceElement(ElementType.UInt8, i);
|
|
///
|
|
/// // Error, -1 not in range of type Byte.
|
|
/// ServiceElement e2 = ServiceElement.CreateNumericalServiceElement(ElementType.UInt8, j);
|
|
/// </code>
|
|
/// The last example failing with:
|
|
/// <code lang="none">
|
|
/// System.ArgumentOutOfRangeException: Value '-1' of type 'System.Int32' not valid for element type UInt16.
|
|
/// ---> System.OverflowException: Value was either too large or too small for a UInt16.
|
|
/// at System.Convert.ToUInt16(Int32 value)
|
|
/// at System.Int32.System.IConvertible.ToUInt16(IFormatProvider provider)
|
|
/// at InTheHand.Net.Bluetooth.ServiceElement.ConvertNumericalValue(ElementType elementType, Object value)
|
|
/// --- End of inner exception stack trace ---
|
|
/// at InTheHand.Net.Bluetooth.ServiceElement.ConvertNumericalValue(ElementType elementType, Object value)
|
|
/// at InTheHand.Net.Bluetooth.ServiceElement.CreateNumericalServiceElement(ElementType elementType, Object value)
|
|
/// at MiscFeatureTestCs.Main(String[] args)
|
|
/// </code>
|
|
/// </para>
|
|
/// </remarks>
|
|
/// -
|
|
/// <param name="elementType">The type of the element as an ElementType.
|
|
/// Should be one of the <c>UnsignedInteger</c>/<c>TwosComplementInteger</c> types.
|
|
/// </param>
|
|
/// <param name="value">The value for the new element,
|
|
/// should be a numerical type.
|
|
/// </param>
|
|
/// -
|
|
/// <returns>The new element.
|
|
/// </returns>
|
|
/// -
|
|
/// <exception cref="T:System.ArgumentException">
|
|
/// The <paramref name="elementType"/> is not a numerical type.
|
|
/// </exception>
|
|
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
|
/// The value wasn’t convertible to the required type, e.g. if -1 is
|
|
/// passed for element type UInt8, as shown above.
|
|
/// </exception>
|
|
public static ServiceElement CreateNumericalServiceElement(ElementType elementType, object value)
|
|
{
|
|
ElementTypeDescriptor etd = GetEtdForType(elementType);
|
|
if (!(etd == ElementTypeDescriptor.UnsignedInteger
|
|
|| etd == ElementTypeDescriptor.TwosComplementInteger)) {
|
|
throw new ArgumentException(string.Format(System.Globalization.CultureInfo.InvariantCulture,
|
|
ErrorMsgFmtCreateNumericalGivenNonNumber,
|
|
etd/*, (int)etd, elementType, (int)elementType*/));
|
|
}
|
|
object valueConverted = ConvertNumericalValue(elementType, value);
|
|
return new ServiceElement(elementType, valueConverted);
|
|
}
|
|
|
|
private static object ConvertNumericalValue(ElementType elementType, object value)
|
|
{
|
|
object naturalTypedValue = null;
|
|
Exception innerEx;
|
|
//
|
|
if (value is byte || value is Int16 || value is Int32 || value is Int64
|
|
|| value is sbyte || value is UInt16 || value is UInt32 || value is UInt64
|
|
|| value is Enum) {
|
|
try {
|
|
IConvertible cble = (IConvertible)value;
|
|
IFormatProvider fp = System.Globalization.CultureInfo.InvariantCulture;
|
|
switch (elementType) {
|
|
case ElementType.UInt8:
|
|
naturalTypedValue = cble.ToByte(fp);
|
|
break;
|
|
case ElementType.Int8:
|
|
naturalTypedValue = cble.ToSByte(fp);
|
|
break;
|
|
//--
|
|
case ElementType.UInt16:
|
|
naturalTypedValue = cble.ToUInt16(fp);
|
|
break;
|
|
case ElementType.Int16:
|
|
naturalTypedValue = cble.ToInt16(fp);
|
|
break;
|
|
//--
|
|
case ElementType.UInt64:
|
|
naturalTypedValue = cble.ToUInt64(fp);
|
|
break;
|
|
case ElementType.Int64:
|
|
naturalTypedValue = cble.ToInt64(fp);
|
|
break;
|
|
//--
|
|
case ElementType.UInt32:
|
|
naturalTypedValue = cble.ToUInt32(fp);
|
|
break;
|
|
default:
|
|
//case ElementType.Int32:
|
|
System.Diagnostics.Debug.Assert(elementType == ElementType.Int32, "Unexpected numeric type");
|
|
naturalTypedValue = cble.ToInt32(fp);
|
|
break;
|
|
}
|
|
return naturalTypedValue;
|
|
} catch (OverflowException ex) {
|
|
innerEx = ex;
|
|
//} catch (InvalidCastException ex) {
|
|
// innerEx = ex;
|
|
}
|
|
} else {
|
|
innerEx = null;
|
|
}
|
|
throw ServiceRecordParser.new_ArgumentOutOfRangeException(
|
|
string.Format(System.Globalization.CultureInfo.InvariantCulture,
|
|
"Value '{1}' of type '{2}' not valid for element type {0}.",
|
|
elementType, value, value.GetType()), innerEx);
|
|
}
|
|
|
|
//--------------------------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Gets the type of the element as an <see cref="T:InTheHand.Net.Bluetooth.ElementType"/>.
|
|
/// </summary>
|
|
public ElementType ElementType
|
|
{
|
|
[System.Diagnostics.DebuggerStepThrough]
|
|
get { return m_type; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the SDP Element Type Descriptor of the element
|
|
/// as an <see cref="T:InTheHand.Net.Bluetooth.ElementTypeDescriptor"/>.
|
|
/// </summary>
|
|
public ElementTypeDescriptor ElementTypeDescriptor
|
|
{
|
|
[System.Diagnostics.DebuggerStepThrough]
|
|
get { return m_etd; }
|
|
}
|
|
|
|
//------------------------
|
|
|
|
/// <summary>
|
|
/// Gets the value of the element as the .NET type it is stored as.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// In most cases the type-specific property should be used instead, e.g
|
|
/// <see cref="M:InTheHand.Net.Bluetooth.ServiceElement.GetValueAsElementList"/>,
|
|
/// <see cref="M:InTheHand.Net.Bluetooth.ServiceElement.GetValueAsUri"/>,
|
|
/// <see cref="M:InTheHand.Net.Bluetooth.ServiceElement.GetValueAsUuid"/>, etc.
|
|
/// </remarks>
|
|
public object Value
|
|
{
|
|
[System.Diagnostics.DebuggerStepThrough]
|
|
get { return m_rawValue; }
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the value as a list of <see cref="T:InTheHand.Net.Bluetooth.ServiceElement"/>.
|
|
/// </summary>
|
|
/// -
|
|
/// <returns>The list of elements as an list.
|
|
/// </returns>
|
|
/// -
|
|
/// <exception cref="T:System.InvalidOperationException">
|
|
/// The service element is not of type
|
|
/// <c>ElementType</c>.<see cref="F:InTheHand.Net.Bluetooth.ElementType.ElementSequence"/>
|
|
/// or <see cref="F:InTheHand.Net.Bluetooth.ElementType.ElementAlternative"/>.
|
|
/// </exception>
|
|
#if CODE_ANALYSIS
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
|
|
#endif
|
|
public IList<ServiceElement> GetValueAsElementList()
|
|
{
|
|
if (m_etd != ElementTypeDescriptor.ElementSequence && m_etd != ElementTypeDescriptor.ElementAlternative) {
|
|
throw new InvalidOperationException(ErrorMsgNotSeqAltType);
|
|
}
|
|
// #ctor disallows null value for seq/alt.
|
|
System.Diagnostics.Debug.Assert(m_rawValue != null);
|
|
return (IList<ServiceElement>)m_rawValue;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the value as a array of <see cref="T:InTheHand.Net.Bluetooth.ServiceElement"/>.
|
|
/// </summary>
|
|
/// -
|
|
/// <returns>The list of elements as an array.
|
|
/// </returns>
|
|
/// -
|
|
/// <exception cref="T:System.InvalidOperationException">
|
|
/// The service element is not of type
|
|
/// <c>ElementType</c>.<see cref="F:InTheHand.Net.Bluetooth.ElementType.ElementSequence"/>
|
|
/// or <see cref="F:InTheHand.Net.Bluetooth.ElementType.ElementAlternative"/>.
|
|
/// </exception>
|
|
#if CODE_ANALYSIS
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
|
|
#endif
|
|
public ServiceElement[] GetValueAsElementArray()
|
|
{
|
|
IList<ServiceElement> list = GetValueAsElementList();
|
|
ServiceElement[] arr = new ServiceElement[list.Count];
|
|
GetValueAsElementList().CopyTo(arr, 0);
|
|
return arr;
|
|
}
|
|
|
|
//------------------------
|
|
|
|
/// <summary>
|
|
/// Gets the value as a <see cref="T:System.Uri"/>.
|
|
/// </summary>
|
|
/// -
|
|
/// <returns>The Url value as a <see cref="T:System.Uri"/>.
|
|
/// </returns>
|
|
/// -
|
|
/// <remarks>
|
|
/// <para>It turns out that we can't trust vendors to add only valid
|
|
/// URLs to their records, for instance the iPhone has an attribute
|
|
/// with value "www.apple.com" which isn't a URL as it has no scheme
|
|
/// part (http://) etc.
|
|
/// </para>
|
|
/// <para>Thus a Url value in an element can be stored in a number of
|
|
/// formats. If created by the parser then it will be stored as a
|
|
/// <see cref="T:System.String"/> or as an array of
|
|
/// <see cref="T:System.Byte"/> if property
|
|
/// <see cref="P:InTheHand.Net.Bluetooth.ServiceRecordParser.LazyUrlCreation">ServiceRecordParser.LazyUrlCreation</see>
|
|
/// is set. If created locally it can be those types or also
|
|
/// <see cref="T:System.Uri"/> .
|
|
/// </para>
|
|
/// <para>This method will try to convert from those formats to <see cref="T:System.Uri"/>.
|
|
/// If the URL is invalid e.g. has bad characters or is missing the scheme
|
|
/// part etc then an error will occur. One can instead access the
|
|
/// element's <see cref="P:InTheHand.Net.Bluetooth.ServiceElement.Value"/>
|
|
/// property and expect one of the three types. When created by the
|
|
/// parser it will be of type <see cref="T:System.String"/> unless
|
|
/// <see cref="P:InTheHand.Net.Bluetooth.ServiceRecordParser.LazyUrlCreation"/>
|
|
/// is set.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// -
|
|
/// <exception cref="T:System.InvalidOperationException">
|
|
/// The service element is not of type
|
|
/// <c>ElementType</c>.<see cref="F:InTheHand.Net.Bluetooth.ElementType.Url"/>.
|
|
/// </exception>
|
|
#if CODE_ANALYSIS
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
|
|
#endif
|
|
public Uri GetValueAsUri()
|
|
{
|
|
if (m_type != ElementType.Url) {
|
|
throw new InvalidOperationException(ErrorMsgNotUrlType);
|
|
}
|
|
System.Diagnostics.Debug.Assert(m_rawValue != null);
|
|
Uri asUri = m_rawValue as Uri;
|
|
if (asUri == null) {
|
|
var arr = m_rawValue as byte[];
|
|
string str;
|
|
if (arr != null) {
|
|
str = ServiceRecordParser.CreateUriStringFromBytes(arr);
|
|
} else {
|
|
str = (string)m_rawValue;
|
|
}
|
|
asUri = new Uri(str);
|
|
}
|
|
return asUri;
|
|
}
|
|
|
|
//------------------------
|
|
|
|
/// <summary>
|
|
/// Gets the value as a <see cref="T:System.Guid"/>.
|
|
/// </summary>
|
|
/// -
|
|
/// <returns>The UUID value as a <see cref="T:System.Guid"/>.
|
|
/// </returns>
|
|
/// -
|
|
/// <exception cref="T:System.InvalidOperationException">
|
|
/// The service element is not of type
|
|
/// <c>ElementType</c>.<see cref="F:InTheHand.Net.Bluetooth.ElementType.Uuid128"/>.
|
|
/// </exception>
|
|
#if CODE_ANALYSIS
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
|
|
#endif
|
|
public Guid GetValueAsUuid()
|
|
{
|
|
if (m_etd != ElementTypeDescriptor.Uuid) {
|
|
throw new InvalidOperationException(ErrorMsgNotUuidType);
|
|
}
|
|
//
|
|
Guid result;
|
|
if (m_type == ElementType.Uuid16) {
|
|
result = BluetoothService.CreateBluetoothUuid((UInt16)Value);
|
|
return result;
|
|
} else if (m_type == ElementType.Uuid32) {
|
|
result = BluetoothService.CreateBluetoothUuid((UInt32)Value);
|
|
return result;
|
|
} else {
|
|
return (Guid)Value;
|
|
}
|
|
}
|
|
|
|
//TODO ((Could have GetValueAsUuid16/32. Check subtype, but convert to short form if is Bluetooth Based.))
|
|
|
|
//------------------------
|
|
|
|
/// <summary>
|
|
/// Get the value of the <see cref="F:InTheHand.Net.Bluetooth.ElementTypeDescriptor.TextString"/>,
|
|
/// where it is encoded using the given encoding form.
|
|
/// </summary>
|
|
/// -
|
|
/// <param name="encoding">The <see cref="T:System.Text.Encoding"/>
|
|
/// object to be used to decode the string value
|
|
/// if it has been read as a raw byte array.
|
|
/// </param>
|
|
/// -
|
|
/// <returns>
|
|
/// A <see cref="T:System.String"/> holding the value of the
|
|
/// <see cref="F:InTheHand.Net.Bluetooth.ElementTypeDescriptor.TextString"/>
|
|
/// from the service element.
|
|
/// </returns>
|
|
/// -
|
|
/// <exception cref="T:System.InvalidOperationException">
|
|
/// The service element is not of type
|
|
/// <see cref="F:InTheHand.Net.Bluetooth.ElementTypeDescriptor.TextString"/>.
|
|
/// </exception>
|
|
public string GetValueAsString(Encoding encoding)
|
|
{
|
|
if (encoding == null) {
|
|
throw new ArgumentNullException("encoding");
|
|
}
|
|
if (m_type != ElementType.TextString) {
|
|
throw new InvalidOperationException(ErrorMsgNotTextStringType);
|
|
}
|
|
//
|
|
string stringAsStored = m_rawValue as string;
|
|
if (stringAsStored != null) {
|
|
return stringAsStored;
|
|
}
|
|
//
|
|
byte[] rawBytes = (byte[])m_rawValue;
|
|
string str = encoding.GetString(rawBytes, 0, rawBytes.Length);
|
|
if (str.Length > 0 && str[str.Length - 1] == 0) { // dodgy null-termination
|
|
str = str.Substring(0, str.Length - 1);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the value of the <see cref="F:InTheHand.Net.Bluetooth.ElementTypeDescriptor.TextString"/>,
|
|
/// when it is encoded as specified by the given IETF Charset identifer.
|
|
/// </summary>
|
|
/// -
|
|
/// <remarks>
|
|
/// Note that a strict decoding of the string is carried out
|
|
/// (except on the NETCF where it is not supported).
|
|
/// Thus if the value is not in the specified encoding, or has been
|
|
/// encoded incorrectly, then an error will occur.
|
|
/// </remarks>
|
|
/// -
|
|
/// <returns>
|
|
/// A <see cref="T:System.String"/> holding the value of the
|
|
/// <see cref="F:InTheHand.Net.Bluetooth.ElementTypeDescriptor.TextString"/>
|
|
/// from the service element.
|
|
/// </returns>
|
|
/// -
|
|
/// <exception cref="T:System.InvalidOperationException">
|
|
/// The service element is not of type
|
|
/// <see cref="F:InTheHand.Net.Bluetooth.ElementTypeDescriptor.TextString"/>.
|
|
/// </exception>
|
|
/// <exception cref="T:System.Text.DecoderFallbackException">
|
|
/// If the value in the service element is not a valid string in the given encoding.
|
|
/// </exception>
|
|
public string GetValueAsString(LanguageBaseItem languageBase)
|
|
{
|
|
if (languageBase == null) {
|
|
throw new ArgumentNullException("languageBase");
|
|
}
|
|
Encoding enc = languageBase.GetEncoding();
|
|
|
|
if (_strictStringDecoding) {
|
|
enc = (Encoding)enc.Clone(); //not in NETCFv1
|
|
#if !UAP
|
|
enc.DecoderFallback = new DecoderExceptionFallback(); // not in NETCF.
|
|
// Not intended for encoding, but set it anyway.
|
|
enc.EncoderFallback = new EncoderExceptionFallback(); // not in NETCF.
|
|
#endif
|
|
}
|
|
|
|
return GetValueAsString(enc);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the value of the <see cref="F:InTheHand.Net.Bluetooth.ElementTypeDescriptor.TextString"/>,
|
|
/// when it is encoded as UTF-8.
|
|
/// </summary>
|
|
/// -
|
|
/// <remarks>
|
|
/// Note: a strict decoding is used.
|
|
/// Thus if the value is not in UTF-8 encoding or has been
|
|
/// encoded incorrectly an error will occur.
|
|
/// </remarks>
|
|
/// -
|
|
/// <returns>
|
|
/// A <see cref="T:System.String"/> holding the value of the
|
|
/// <see cref="F:InTheHand.Net.Bluetooth.ElementTypeDescriptor.TextString"/>
|
|
/// from the service element.
|
|
/// </returns>
|
|
/// -
|
|
/// <exception cref="T:System.Text.DecoderFallbackException">
|
|
/// If the value in the service element is not a valid string in the given encoding.
|
|
/// On NETCF, an <see cref="T:System.ArgumentException"/> is thrown; not that
|
|
/// <see cref="T:System.ArgumentException"/> is the base class of the
|
|
/// <see cref="T:System.Text.DecoderFallbackException"/> exception.
|
|
/// </exception>
|
|
/// <exception cref="T:System.InvalidOperationException">
|
|
/// The service element is not of type
|
|
/// <see cref="F:InTheHand.Net.Bluetooth.ElementTypeDescriptor.TextString"/>.
|
|
/// </exception>
|
|
#if CODE_ANALYSIS
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "but throws")]
|
|
#endif
|
|
public string GetValueAsStringUtf8()
|
|
{
|
|
Encoding enc = new UTF8Encoding(false, true); //throwOnInvalidBytes=true
|
|
return GetValueAsString(enc);
|
|
}
|
|
|
|
//--------------------------------------------------------------
|
|
/// <exclude/>
|
|
public const string ErrorMsgNotUuidType = "Element is not of type UUID.";
|
|
/// <exclude/>
|
|
public const string ErrorMsgNotTextStringType = "Not TextString type.";
|
|
/// <exclude/>
|
|
public const string ErrorMsgNotUrlType = "Not Url type.";
|
|
/// <exclude/>
|
|
public const string ErrorMsgNotSeqAltType = "Not Element Sequence or Alternative type.";
|
|
/// <exclude/>
|
|
public const string ErrorMsgSeqAltTypeNeedElementArray = "ElementType Sequence or Alternative needs an array of ServiceElement.";
|
|
/// <exclude/>
|
|
public const string ErrorMsgFmtCreateNumericalGivenNonNumber
|
|
= "Not a numerical type ({0})."; // ET is our own!!
|
|
/// <exclude/>
|
|
public const string ErrorMsgListContainsNotElement
|
|
= "The list contains a element which is not a ServiceElement.";
|
|
|
|
}//class
|
|
}
|