/InTheHand.Net.Personal/InTheHand.Net.Personal/Net.Bluetooth/BluetoothWin32Authentication.cs

# · C# · 729 lines · 369 code · 25 blank · 335 comment · 96 complexity · eb7f6d76715bcd02335285933c3b5668 MD5 · raw file

  1. using System;
  2. using System.Diagnostics;
  3. using System.Runtime.InteropServices;
  4. using InTheHand.Net.Bluetooth.Msft;
  5. using InTheHand.Win32;
  6. using System.Diagnostics.CodeAnalysis;
  7. namespace InTheHand.Net.Bluetooth
  8. {
  9. /// <summary>
  10. /// Provides Bluetooth authentication services on desktop Windows.
  11. /// </summary>
  12. /// -
  13. /// <remarks>
  14. /// <note>This class is supported on desktop Windows and with the Microsoft
  15. /// stack only.
  16. /// </note>
  17. /// <para>This class can be used in one of two ways. Firstly
  18. /// an instance can be created specifying one device that is being connected
  19. /// to and the PIN string to use for it. (That form is used internally by
  20. /// <see cref="T:InTheHand.Net.Sockets.BluetoothClient"/> to support
  21. /// its <see cref="M:InTheHand.Net.Sockets.BluetoothClient.SetPin(System.String)"/> method).
  22. /// </para>
  23. /// <para>Secondly it can also be used a mode where a user supplied
  24. /// callback will be called when any device requires authentication,
  25. /// the callback includes a parameter of type
  26. /// <see cref="T:InTheHand.Net.Bluetooth.BluetoothWin32AuthenticationEventArgs"/>.
  27. /// Various authentication methods are available in Bluetooth version
  28. /// 2.1 and later. Which one is being used is indicated by the
  29. /// <see cref="P:InTheHand.Net.Bluetooth.BluetoothWin32AuthenticationEventArgs.AuthenticationMethod"/>
  30. /// property.
  31. /// If it is <see cref="F:InTheHand.Net.Bluetooth.BluetoothAuthenticationMethod.Legacy"/>
  32. /// then the callback method should set the
  33. /// <see cref="P:InTheHand.Net.Bluetooth.BluetoothWin32AuthenticationEventArgs.Pin"/>
  34. /// property.
  35. /// </para>
  36. /// <para>
  37. /// For the other authentication methods
  38. /// e.g. <see cref="F:InTheHand.Net.Bluetooth.BluetoothAuthenticationMethod.NumericComparison"/>
  39. /// or <see cref="F:InTheHand.Net.Bluetooth.BluetoothAuthenticationMethod.OutOfBand"/>
  40. /// the callback method should use one or more of the other properties and
  41. /// methods e.g.
  42. /// <see cref="P:InTheHand.Net.Bluetooth.BluetoothWin32AuthenticationEventArgs.NumberOrPasskey"/>,
  43. /// <see cref="P:InTheHand.Net.Bluetooth.BluetoothWin32AuthenticationEventArgs.Confirm"/>,
  44. /// <see cref="P:InTheHand.Net.Bluetooth.BluetoothWin32AuthenticationEventArgs.ResponseNumberOrPasskey"/>,
  45. /// <see cref="M:InTheHand.Net.Bluetooth.BluetoothWin32AuthenticationEventArgs.ConfirmOob(System.Byte[],System.Byte[])"/>
  46. /// etc.
  47. /// </para>
  48. /// <para>
  49. /// See the example below for a 'Legacy' method handler.
  50. /// The callback mode can be configured to do a callback after the
  51. /// &#x2018;send PIN&#x2019; action, this allows one to see if it was successful
  52. /// etc. An example sequence where the PIN was <strong>incorrect</strong> is as follows.
  53. /// </para>
  54. /// <code lang="none">
  55. ///Authenticate one device -- with wrong passcode here the first two times.
  56. ///Passcode respectively: 'BAD-x', 'BAD-y', '9876'
  57. ///Making PC discoverable
  58. ///Hit Return to complete
  59. ///Authenticating 0017E464CF1E wm_alan1
  60. /// Attempt# 0, Last error code 0
  61. /// Sending "BAD-x"
  62. ///Authenticating 0017E464CF1E wm_alan1
  63. /// Attempt# 1, Last error code 1244
  64. /// Sending "BAD-y"
  65. ///Authenticating 0017E464CF1E wm_alan1
  66. /// Attempt# 2, Last error code 1167
  67. /// Sending "9876"
  68. ///Authenticating 0017E464CF1E wm_alan1
  69. /// Attempt# 3, Last error code 1167
  70. ///etc
  71. ///</code>
  72. /// <para>
  73. /// That is we see the error code of <c>1244=NativeErrorNotAuthenticated</c>
  74. /// once, and then the peer device disappears (<c>1167=NativeErrorDeviceNotConnected</c>).
  75. /// I suppose that's a security feature -- its stops an attacker
  76. /// from trying again and again with different passcodes.
  77. ///
  78. /// Anyway the result of that is that is it <strong>not</strong> worth repeating
  79. /// the callback after the device disappears. The code now enforces this. With
  80. /// <see cref="P:InTheHand.Net.Bluetooth.BluetoothWin32AuthenticationEventArgs.CallbackWithResult"/>
  81. /// set to <c>true</c>, if the result of the previous attempt was &#x2018;success&#x2019;
  82. /// or &#x2018;device not connected&#x2019; then any new PIN set in the callback
  83. /// won&#x2019;t be used and thus the callback won&#x2019;t be called again
  84. /// for that authentication attempt.
  85. /// </para>
  86. /// <para>A successful authentication process can thus be detected by checking if
  87. /// <code>e.PreviousNativeErrorCode == NativeErrorSuccess &amp;&amp; e.AttemptNumber != 0</code>
  88. /// </para>
  89. /// <para>
  90. /// </para>
  91. /// <para>The instance will continue receiving authentication requests
  92. /// until it is disposed or garbage collected, so keep a reference to it
  93. /// whilst it should be active and call
  94. /// <see cref="M:InTheHand.Net.Bluetooth.BluetoothWin32Authentication.Dispose"/>
  95. /// when you&#x2019;re finished.
  96. /// </para>
  97. /// </remarks>
  98. /// -
  99. /// <example>
  100. /// If one wants to respond to PIN requests for one device with a known PIN then
  101. /// use the simple form which is initialized with an address and PIN.
  102. /// <code lang="C#">
  103. /// BluetoothWin32Authentication authenticator
  104. /// = new BluetoothWin32Authentication(remoteEP.Address, m_pin);
  105. /// // when the peer is expected to require pairing, perhaps do some work.
  106. /// authenticator.Dispose();
  107. /// </code>
  108. ///
  109. /// If one wants to see the PIN request, perhaps to be able to check the type
  110. /// of the peer by its address then use the form here which requests callbacks.
  111. /// (Note that this code assumes that 'Legacy' PIN-based pairing is being
  112. /// used; setting the Pin property will presumably have no effect if the
  113. /// authentication method being used is one of the v2.1 SSP forms).
  114. /// <code lang="VB.NET">
  115. /// Using pairer As New BluetoothWin32Authentication(AddressOf Win32AuthCallbackHandler)
  116. /// Console.WriteLine("Hit Return to stop authenticating")
  117. /// Console.ReadLine()
  118. /// End Using
  119. /// ...
  120. ///
  121. /// Sub Win32AuthCallbackHandler(ByVal sender As Object, ByVal e As InTheHand.Net.Bluetooth.BluetoothWin32AuthenticationEventArgs)
  122. /// ' Note we assume here that 'Legacy' pairing is being used,
  123. /// ' and thus we only set the Pin property!
  124. /// Dim address As String = e.Device.DeviceAddress.ToString()
  125. /// Console.WriteLine("Received an authentication request from address " + address)
  126. ///
  127. /// ' compare the first 8 hex numbers, this is just a special case because in the
  128. /// ' used scenario the model of the devices can be identified by the first 8 hex
  129. /// ' numbers, the last 4 numbers being the device specific part.
  130. /// If address.Substring(0, 8).Equals("0099880D") OrElse _
  131. /// address.Substring(0, 8).Equals("0099880E") Then
  132. /// ' send authentication response
  133. /// e.Pin = "5276"
  134. /// ElseIf (address.Substring(0, 8).Equals("00997788")) Then
  135. /// ' send authentication response
  136. /// e.Pin = "ásdfghjkl"
  137. /// End If
  138. /// End Sub
  139. /// </code>
  140. /// </example>
  141. //[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, UnmanagedCode = true)]
  142. public partial class BluetoothWin32Authentication : IDisposable
  143. {
  144. /// <summary>
  145. /// Windows&#x2019; ERROR_SUCCESS
  146. /// </summary>
  147. /// <remarks><see cref="P:InTheHand.Net.Bluetooth.BluetoothWin32AuthenticationEventArgs.PreviousNativeErrorCode"/>
  148. /// </remarks>
  149. public const int NativeErrorSuccess = 0;
  150. /// <summary>
  151. /// Windows&#x2019; ERROR_NOT_AUTHENTICATED
  152. /// </summary>
  153. /// <remarks><see cref="P:InTheHand.Net.Bluetooth.BluetoothWin32AuthenticationEventArgs.PreviousNativeErrorCode"/>
  154. /// </remarks>
  155. public const int NativeErrorNotAuthenticated = (int)Win32Error.ERROR_NOT_AUTHENTICATED;
  156. /// <summary>
  157. /// Windows&#x2019; ERROR_DEVICE_NOT_CONNECTED
  158. /// </summary>
  159. /// <remarks><see cref="P:InTheHand.Net.Bluetooth.BluetoothWin32AuthenticationEventArgs.PreviousNativeErrorCode"/>
  160. /// </remarks>
  161. public const int NativeErrorDeviceNotConnected = (int)Win32Error.ERROR_DEVICE_NOT_CONNECTED;
  162. // This class is XP only, but XmlDocs generation is from CF2 only, so we need
  163. // to compile on that platform too. So, leave the skeleton with no innards.
  164. #if WinXP
  165. //
  166. bool m_hasKB942567 = true; // "Windows Vista Feature Pack for Wireless" etc.
  167. readonly IntPtr m_radioHandle = IntPtr.Zero;
  168. BluetoothAuthenticationRegistrationHandle m_regHandle;
  169. NativeMethods.BluetoothAuthenticationCallback m_callback; // Stop gc of callback thunk.
  170. NativeMethods.BluetoothAuthenticationCallbackEx m_callbackEx; // Stop gc of callback thunk.
  171. //
  172. readonly BluetoothAddress m_remoteAddress;
  173. readonly String m_pin;
  174. readonly EventHandler<BluetoothWin32AuthenticationEventArgs> m_userCallback;
  175. #endif
  176. //--------------------------------------------------------------
  177. /// <overloads>
  178. /// Initializes a new instance of the <see cref="T:InTheHand.Net.Bluetooth.BluetoothWin32Authentication"/> class.
  179. /// </overloads>
  180. /// -
  181. /// <summary>
  182. /// Initializes a new instance of the <see cref="T:InTheHand.Net.Bluetooth.BluetoothWin32Authentication"/> class,
  183. /// to respond to a specific address with a specific PIN string.
  184. /// </summary>
  185. /// -
  186. /// <remarks>
  187. /// <para>The instance will continue receiving authentication requests
  188. /// until it is disposed or garbage collected, so keep a reference to it
  189. /// whilst it should be active, and call
  190. /// <see cref="M:InTheHand.Net.Bluetooth.BluetoothWin32Authentication.Dispose"/>
  191. /// when you&#x2019;re finished.
  192. /// </para>
  193. /// </remarks>
  194. /// -
  195. /// <param name="remoteAddress">The address of the device to authenticate,
  196. /// as a <see cref="T:InTheHand.Net.BluetoothAddress"/>.
  197. /// </param>
  198. /// <param name="pin">The PIN string to use for authentication, as a
  199. /// <see cref="T:System.String"/>.
  200. /// </param>
  201. public BluetoothWin32Authentication(BluetoothAddress remoteAddress, String pin)
  202. {
  203. #if ! WinXP
  204. throw new PlatformNotSupportedException("BluetoothWin32Authentication is Win32 only.");
  205. #else
  206. if (remoteAddress == null) {
  207. throw new ArgumentNullException("remoteAddress");
  208. }
  209. if (remoteAddress.ToInt64() == 0) {
  210. throw new ArgumentNullException("remoteAddress", "A non-blank address must be specified.");
  211. }
  212. if (pin == null) {
  213. throw new ArgumentNullException("pin");
  214. }
  215. m_remoteAddress = remoteAddress;
  216. m_pin = pin;
  217. Register(remoteAddress);
  218. #endif
  219. }
  220. /// <summary>
  221. /// Initializes a new instance of the <see cref="T:InTheHand.Net.Bluetooth.BluetoothWin32Authentication"/> class,
  222. /// to call a specified handler when any device requires authentication.
  223. /// </summary>
  224. /// -
  225. /// <remarks>
  226. /// <para>See the example below.
  227. /// </para>
  228. /// <para>The callback mode can be configured to do a callback after the
  229. /// &#x2018;send PIN&#x2019;action, this allows one to see if it was successful
  230. /// etc. An example sequence where the PIN was <strong>incorrect</strong> is as follows.
  231. /// </para>
  232. /// <code lang="none">
  233. ///Authenticate one device -- with wrong passcode here the first two times.
  234. ///Passcode respectively: 'BAD-x', 'BAD-y', '9876'
  235. ///Making PC discoverable
  236. ///Hit Return to complete
  237. ///Authenticating 0017E464CF1E wm_alan1
  238. /// Attempt# 0, Last error code 0
  239. /// Sending "BAD-x"
  240. ///Authenticating 0017E464CF1E wm_alan1
  241. /// Attempt# 1, Last error code 1244
  242. /// Sending "BAD-y"
  243. ///Authenticating 0017E464CF1E wm_alan1
  244. /// Attempt# 2, Last error code 1167
  245. /// Sending "9876"
  246. ///Authenticating 0017E464CF1E wm_alan1
  247. /// Attempt# 3, Last error code 1167
  248. ///etc
  249. ///</code>
  250. /// <para>
  251. /// That is we see the error code of <c>1244=NativeErrorNotAuthenticated</c>
  252. /// once, and then the peer device disappears (<c>1167=NativeErrorDeviceNotConnected</c>).
  253. /// I suppose that's a security feature -- its stops an attacker
  254. /// from trying again and again with different passcodes.
  255. ///
  256. /// Anyway the result of that is that is it <strong>not</strong> worth repeating
  257. /// the callback after the device disappears. The code now enforces this. With
  258. /// <see cref="P:InTheHand.Net.Bluetooth.BluetoothWin32AuthenticationEventArgs.CallbackWithResult"/>
  259. /// set to <c>true</c>, if the result of the previous attempt was &#x2018;success&#x2019;
  260. /// or &#x2018;device not connected&#x2019; then any new PIN set in the callback
  261. /// won&#x2019;t be used and thus the callback won&#x2019;t be called again
  262. /// for that authentication attempt.
  263. /// </para>
  264. /// <para>A successful authentication process can thus be detected by setting
  265. /// <c>CallbackWithResult=true</c> and checking in the callback if
  266. /// <code> e.PreviousNativeErrorCode == NativeErrorSuccess &amp;&amp; e.AttemptNumber != 0</code>
  267. /// </para>
  268. /// <para>
  269. /// </para>
  270. /// <para>The instance will continue receiving authentication requests
  271. /// until it is disposed or garbage collected, so keep a reference to it
  272. /// whilst it should be active, and call
  273. /// <see cref="M:InTheHand.Net.Bluetooth.BluetoothWin32Authentication.Dispose"/>
  274. /// when you&#x2019;re finished.
  275. /// </para>
  276. /// </remarks>
  277. /// -
  278. /// <param name="handler">A reference to a handler function that can respond
  279. /// to authentication requests.
  280. /// </param>
  281. /// -
  282. /// <example>
  283. /// <code lang="VB.NET">
  284. /// Using pairer As New BluetoothWin32Authentication(AddressOf Win32AuthCallbackHandler)
  285. /// Console.WriteLine("Hit Return to stop authenticating")
  286. /// Console.ReadLine()
  287. /// End Using
  288. /// ...
  289. ///
  290. /// Sub Win32AuthCallbackHandler(ByVal sender As Object, ByVal e As InTheHand.Net.Bluetooth.BluetoothWin32AuthenticationEventArgs)
  291. /// Dim address As String = e.Device.DeviceAddress.ToString()
  292. /// Console.WriteLine("Received an authentication request from address " + address)
  293. ///
  294. /// ' compare the first 8 hex numbers, this is just a special case because in the
  295. /// ' used scenario the model of the devices can be identified by the first 8 hex
  296. /// ' numbers, the last 4 numbers being the device specific part.
  297. /// If address.Substring(0, 8).Equals("0099880D") OrElse _
  298. /// address.Substring(0, 8).Equals("0099880E") Then
  299. /// ' send authentication response
  300. /// e.Pin = "5276"
  301. /// ElseIf (address.Substring(0, 8).Equals("00997788")) Then
  302. /// ' send authentication response
  303. /// e.Pin = "ásdfghjkl"
  304. /// End If
  305. /// End Sub
  306. /// </code>
  307. /// </example>
  308. public BluetoothWin32Authentication(EventHandler<BluetoothWin32AuthenticationEventArgs> handler)
  309. {
  310. #if ! WinXP
  311. throw new PlatformNotSupportedException("BluetoothWin32Authentication is Win32 only.");
  312. #else
  313. m_userCallback = handler;
  314. Register(new BluetoothAddress(0)); // All devices
  315. #endif
  316. }
  317. #if WinXP
  318. private void Register(BluetoothAddress remoteAddress)
  319. {
  320. System.Diagnostics.Debug.Assert(m_pin == null ^ m_userCallback == null);
  321. //
  322. m_callback = new NativeMethods.BluetoothAuthenticationCallback(NativeCallback);
  323. m_callbackEx = new NativeMethods.BluetoothAuthenticationCallbackEx(NativeCallback);
  324. BLUETOOTH_DEVICE_INFO bdi = new BLUETOOTH_DEVICE_INFO(remoteAddress);
  325. UInt32 ret;
  326. if (m_hasKB942567) {
  327. try {
  328. ret = NativeMethods.BluetoothRegisterForAuthenticationEx(
  329. ref bdi, out m_regHandle, m_callbackEx, IntPtr.Zero);
  330. } catch (EntryPointNotFoundException) {
  331. m_hasKB942567 = false;
  332. ret = NativeMethods.BluetoothRegisterForAuthentication(
  333. ref bdi, out m_regHandle, m_callback, IntPtr.Zero);
  334. }
  335. } else {
  336. ret = NativeMethods.BluetoothRegisterForAuthentication(
  337. ref bdi, out m_regHandle, m_callback, IntPtr.Zero);
  338. }
  339. int gle = Marshal.GetLastWin32Error();
  340. System.Diagnostics.Debug.Assert(ret == NativeErrorSuccess,
  341. "BluetoothRegisterForAuthentication failed, GLE="
  342. + gle.ToString() + "=0x" + gle.ToString("X"));
  343. if (ret != NativeErrorSuccess) {
  344. throw new System.ComponentModel.Win32Exception(gle);
  345. }
  346. m_regHandle.SetObjectToKeepAlive(m_callback, m_callbackEx);
  347. }
  348. //--------------------------------------------------------------
  349. private bool NativeCallback(IntPtr param, ref BLUETOOTH_AUTHENTICATION_CALLBACK_PARAMS pAuthCallbackParams)
  350. {
  351. Debug.WriteLine("BtRegForAuthEx Callback:");
  352. Debug.WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture,
  353. "authParams: addr: 0x{0:X12},\n meth: '{1}'\n capa: '{2}'\n requ: '{3}'\n n/pk: 0x{4:X}={4}",
  354. pAuthCallbackParams.deviceInfo.Address,
  355. pAuthCallbackParams.authenticationMethod,
  356. pAuthCallbackParams.ioCapability,
  357. pAuthCallbackParams.authenticationRequirements,
  358. pAuthCallbackParams.Numeric_Value_Passkey));
  359. //if (pAuthCallbackParams.authenticationMethod
  360. // == BluetoothAuthenticationMethod.Legacy) {
  361. BLUETOOTH_DEVICE_INFO bdi = pAuthCallbackParams.deviceInfo;
  362. BLUETOOTH_AUTHENTICATION_CALLBACK_PARAMS? refParams = pAuthCallbackParams;
  363. NativeCallback(pAuthCallbackParams.authenticationMethod, param, ref bdi, true, ref refParams);
  364. //}
  365. return false;
  366. }
  367. private bool NativeCallback(IntPtr param, ref BLUETOOTH_DEVICE_INFO bdi)
  368. {
  369. Debug.WriteLine("BtRegForAuth[NO-Ex] Callback.");
  370. BLUETOOTH_AUTHENTICATION_CALLBACK_PARAMS? nullRefParams = null;
  371. return NativeCallback(BluetoothAuthenticationMethod.Legacy,
  372. param, ref bdi, false, ref nullRefParams);
  373. }
  374. [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "param")]
  375. private bool NativeCallback(BluetoothAuthenticationMethod method,
  376. IntPtr param, ref BLUETOOTH_DEVICE_INFO bdi, bool versionEx,
  377. ref BLUETOOTH_AUTHENTICATION_CALLBACK_PARAMS? pAuthCallbackParams)
  378. {
  379. System.Diagnostics.Debug.Assert(m_pin == null ^ m_userCallback == null);
  380. //
  381. System.Diagnostics.Debug.WriteLine(String.Format(
  382. System.Globalization.CultureInfo.InvariantCulture,
  383. "AuthenticateResponder callback (for {0}): 0x{1:X} 0x{2:X}",
  384. m_remoteAddress, param, bdi.Address));
  385. //
  386. String pin;
  387. Int32 ret;
  388. if (m_pin != null) {
  389. // Pre-specified case.
  390. System.Diagnostics.Debug.Assert(bdi.Address == m_remoteAddress.ToInt64(),
  391. "Should only get callback for the single device.");
  392. //TODO if (bdi.Address != m_remoteAddress.ToInt64()) {
  393. // return false;
  394. //}
  395. pin = m_pin;
  396. if (versionEx) {
  397. // TODO Want to send a positive response here to NumericComparison??
  398. ret = BluetoothSendAuthenticationResponseExPin(ref bdi, pin);
  399. } else {
  400. ret = NativeMethods.BluetoothSendAuthenticationResponse(
  401. m_radioHandle, ref bdi, pin);
  402. }
  403. } else if (method == BluetoothAuthenticationMethod.Legacy) {
  404. // Callback case.
  405. System.Diagnostics.Debug.Assert(m_userCallback != null);
  406. BluetoothWin32AuthenticationEventArgs e
  407. = new BluetoothWin32AuthenticationEventArgs(bdi);
  408. while (true) {
  409. // Callback the user code
  410. OnAuthentication(e);
  411. // Don't proceed if no (null) passcode given, or
  412. // if the last attempt was successful, or
  413. // the decvice has disppeared.
  414. if (e.Pin == null) {
  415. ret = NativeErrorSuccess;
  416. break;
  417. }
  418. if (e.PreviousNativeErrorCode == NativeErrorSuccess && e.AttemptNumber != 0) {
  419. Debug.Assert(e.CallbackWithResult, "NOT CbWR but here (A#)!!");
  420. ret = NativeErrorSuccess;
  421. break;
  422. }
  423. if (e.PreviousNativeErrorCode == NativeErrorDeviceNotConnected) {
  424. Debug.Assert(e.CallbackWithResult, "NOT CbWR but here (DNC)!!");
  425. // When I try this (against Win2k+Belkin and iPaq hx2190,
  426. // both apparently with Broadcom) I see:
  427. //[[
  428. //Authenticate one device -- with wrong passcode here the first two times.
  429. //Passcode respectively: 'BAD-x', 'BAD-y', '9876'
  430. //Making PC discoverable
  431. //Hit Return to complete
  432. //Authenticating 0017E464CF1E wm_alan1
  433. // Attempt# 0, Last error code 0
  434. //Using '0.23672947484847'
  435. //Authenticating 0017E464CF1E wm_alan1
  436. // Attempt# 1, Last error code 1244
  437. //Using '0.54782851764365'
  438. //Authenticating 0017E464CF1E wm_alan1
  439. // Attempt# 2, Last error code 1167
  440. //Using '9876'
  441. //Authenticating 0017E464CF1E wm_alan1
  442. // Attempt# 3, Last error code 1167
  443. //etc
  444. //]]
  445. // That is we see the error code of 1244=ErrorNotAuthenticated
  446. // once, and then the peer device disappears (1167=ErrorDeviceNotConnected).
  447. // I suppose that's a security feature -- its stops an attacker
  448. // from trying again and again with different passcodes.
  449. //
  450. // Anyway the result of that is that is it NOT worth repeating
  451. // the callback after the device disappears.
  452. ret = NativeErrorSuccess;
  453. break;
  454. }
  455. pin = e.Pin;
  456. System.Diagnostics.Debug.WriteLine(String.Format(System.Globalization.CultureInfo.InvariantCulture,
  457. "BW32Auth SendAuthRsp pin {0}", pin));
  458. if (versionEx) {
  459. ret = BluetoothSendAuthenticationResponseExPin(ref bdi, pin);
  460. } else {
  461. ret = NativeMethods.BluetoothSendAuthenticationResponse(
  462. m_radioHandle, ref bdi, pin);
  463. }
  464. if (ret != NativeErrorSuccess) {
  465. System.Diagnostics.Trace.WriteLine(String.Format(
  466. System.Globalization.CultureInfo.InvariantCulture,
  467. " BluetoothSendAuthenticationResponse failed: {0}=0x{0:X}", ret));
  468. }
  469. // Have to callback the user code after the attempt?
  470. BluetoothWin32AuthenticationEventArgs lastEa = e;
  471. if (!lastEa.CallbackWithResult) {
  472. break;
  473. }
  474. e = new BluetoothWin32AuthenticationEventArgs(ret, lastEa);
  475. }
  476. } else if (method == BluetoothAuthenticationMethod.NumericComparison
  477. || method == BluetoothAuthenticationMethod.Passkey
  478. || method == BluetoothAuthenticationMethod.PasskeyNotification
  479. || method == BluetoothAuthenticationMethod.OutOfBand
  480. ) {
  481. // Callback case.
  482. System.Diagnostics.Debug.Assert(m_userCallback != null);
  483. BluetoothWin32AuthenticationEventArgs e
  484. = new BluetoothWin32AuthenticationEventArgs(bdi, ref pAuthCallbackParams);
  485. while (true) {
  486. // Callback the user code
  487. OnAuthentication(e);
  488. // Check if after e.CallbackWithResult...
  489. if (e.PreviousNativeErrorCode == NativeErrorSuccess && e.AttemptNumber != 0) {
  490. Debug.Assert(e.CallbackWithResult, "NOT CbWR but here (A#)!!");
  491. ret = NativeErrorSuccess;
  492. break;
  493. }
  494. if (e.PreviousNativeErrorCode == NativeErrorDeviceNotConnected) {
  495. Debug.Assert(e.CallbackWithResult, "NOT CbWR but here (DNC)!!");
  496. ret = NativeErrorSuccess;
  497. break;
  498. }
  499. bool? confirm = e.Confirm;
  500. System.Diagnostics.Debug.WriteLine(String.Format(System.Globalization.CultureInfo.InvariantCulture,
  501. "BW32Auth SendAuthRspEx-NumComparison {0}", confirm));
  502. if (confirm == null) {
  503. ret = NativeErrorSuccess;
  504. break;
  505. }
  506. if (method != BluetoothAuthenticationMethod.OutOfBand) {
  507. ret = BluetoothSendAuthenticationResponseExNumCompPasskey(ref bdi, confirm, e);
  508. } else {
  509. ret = BluetoothSendAuthenticationResponseExOob(ref bdi, confirm, e);
  510. }
  511. if (ret != NativeErrorSuccess) {
  512. System.Diagnostics.Trace.WriteLine(String.Format(
  513. System.Globalization.CultureInfo.InvariantCulture,
  514. " BluetoothSendAuthenticationResponseEx failed: {0}=0x{0:X}", ret));
  515. }
  516. // Have to callback the user code after the attempt?
  517. BluetoothWin32AuthenticationEventArgs lastEa = e;
  518. if (!lastEa.CallbackWithResult) {
  519. break;
  520. }
  521. e = new BluetoothWin32AuthenticationEventArgs(ret, lastEa);
  522. }
  523. } else {
  524. Debug.Fail("Unsupported auth method: " + method);
  525. ret = NativeErrorSuccess;
  526. }
  527. //
  528. if (ret != NativeErrorSuccess) {
  529. System.Diagnostics.Trace.WriteLine(String.Format(
  530. System.Globalization.CultureInfo.InvariantCulture,
  531. "BluetoothSendAuthenticationResponse failed: {0}=0x{0:X}", ret));
  532. }
  533. return true; // "The return value from this function is ignored by the system."
  534. }
  535. private int BluetoothSendAuthenticationResponseExPin(ref BLUETOOTH_DEVICE_INFO bdi, String pin)
  536. {
  537. Int32 ret;
  538. BLUETOOTH_AUTHENTICATE_RESPONSE__PIN_INFO rsp = new BLUETOOTH_AUTHENTICATE_RESPONSE__PIN_INFO();
  539. rsp.authMethod = BluetoothAuthenticationMethod.Legacy;
  540. rsp.bthAddressRemote = bdi.Address;
  541. rsp.pinInfo.pin = new byte[BLUETOOTH_PIN_INFO.BTH_MAX_PIN_SIZE];
  542. byte[] buf = System.Text.Encoding.UTF8.GetBytes(pin);
  543. int len = Math.Min(BLUETOOTH_PIN_INFO.BTH_MAX_PIN_SIZE, buf.Length);
  544. Array.Copy(buf, rsp.pinInfo.pin, len);
  545. rsp.pinInfo.pinLength = len;
  546. ret = NativeMethods.BluetoothSendAuthenticationResponseEx(
  547. m_radioHandle, ref rsp);
  548. return ret;
  549. }
  550. private int BluetoothSendAuthenticationResponseExNumCompPasskey(ref BLUETOOTH_DEVICE_INFO bdi,
  551. bool? confirm, BluetoothWin32AuthenticationEventArgs e)
  552. {
  553. if (!confirm.HasValue) {
  554. return NativeErrorSuccess;
  555. }
  556. Int32 ret;
  557. var rsp = new BLUETOOTH_AUTHENTICATE_RESPONSE__NUMERIC_COMPARISON_PASSKEY_INFO();
  558. rsp.negativeResponse = 1; // Default to NEGATIVE, really set below.
  559. rsp.authMethod = e.AuthenticationMethod;
  560. if (!(e.AuthenticationMethod == BluetoothAuthenticationMethod.NumericComparison
  561. || e.AuthenticationMethod == BluetoothAuthenticationMethod.Passkey
  562. || e.AuthenticationMethod == BluetoothAuthenticationMethod.PasskeyNotification)) {
  563. Debug.Fail("Bad call!!! method is: " + e.AuthenticationMethod);
  564. return NativeErrorNotAuthenticated;
  565. }
  566. rsp.bthAddressRemote = bdi.Address;
  567. switch (confirm) {
  568. case true:
  569. rsp.negativeResponse = 0;
  570. // Set the response number/passcode value
  571. if (e.ResponseNumberOrPasskey.HasValue) {
  572. rsp.numericComp_passkey = checked((uint)e.ResponseNumberOrPasskey.Value);
  573. }
  574. break;
  575. case false:
  576. case null:
  577. Debug.Assert(confirm != null, "Should have exited above when non-response.");
  578. rsp.negativeResponse = 1;
  579. break;
  580. }
  581. ret = NativeMethods.BluetoothSendAuthenticationResponseEx(
  582. m_radioHandle, ref rsp);
  583. return ret;
  584. }
  585. private int BluetoothSendAuthenticationResponseExOob(ref BLUETOOTH_DEVICE_INFO bdi,
  586. bool? confirm, BluetoothWin32AuthenticationEventArgs e)
  587. {
  588. if (!confirm.HasValue) {
  589. return NativeErrorSuccess;
  590. }
  591. Int32 ret;
  592. var rsp = new BLUETOOTH_AUTHENTICATE_RESPONSE__OOB_DATA_INFO();
  593. rsp.negativeResponse = 1; // Default to NEGATIVE, really set below.
  594. rsp.authMethod = e.AuthenticationMethod;
  595. if (!(e.AuthenticationMethod == BluetoothAuthenticationMethod.OutOfBand)) {
  596. Debug.Fail("Bad call!!! method is: " + e.AuthenticationMethod);
  597. return NativeErrorNotAuthenticated;
  598. }
  599. rsp.bthAddressRemote = bdi.Address;
  600. switch (confirm) {
  601. case true:
  602. rsp.negativeResponse = 0;
  603. // Set the oob values
  604. // (Testing shows that P/Invoke disallowed only incorrect
  605. // lengthinline arrays, null arrays are ok).
  606. if (e.OobC != null) rsp.oobInfo.C = e.OobC;
  607. if (e.OobR != null) rsp.oobInfo.R = e.OobR;
  608. break;
  609. case false:
  610. case null:
  611. Debug.Assert(confirm != null, "Should have exited above when non-response.");
  612. rsp.negativeResponse = 1;
  613. break;
  614. }
  615. ret = NativeMethods.BluetoothSendAuthenticationResponseEx(
  616. m_radioHandle, ref rsp);
  617. return ret;
  618. }
  619. /// <summary>
  620. /// Calls the authentication callback handler.
  621. /// </summary>
  622. /// -
  623. /// <param name="e">An instance of <see cref="T:InTheHand.Net.Bluetooth.BluetoothWin32AuthenticationEventArgs"/>
  624. /// containing the details of the authentication callback.
  625. /// </param>
  626. protected virtual void OnAuthentication(BluetoothWin32AuthenticationEventArgs e)
  627. {
  628. EventHandler<BluetoothWin32AuthenticationEventArgs> callback = m_userCallback;
  629. if (callback != null) {
  630. m_userCallback(this, e);
  631. }
  632. }
  633. #endif
  634. //--------------------------------------------------------------
  635. #region IDisposable Members
  636. /// <summary>
  637. /// Release the unmanaged resources used by the <see cref="T:InTheHand.Net.Bluetooth.BluetoothWin32Authentication"/>.
  638. /// </summary>
  639. public void Dispose()
  640. {
  641. Dispose(true);
  642. GC.SuppressFinalize(this);
  643. }
  644. /// <summary>
  645. /// Release the unmanaged resources used by the <see cref="T:InTheHand.Net.Bluetooth.BluetoothWin32Authentication"/>,
  646. /// and optionally disposes of the managed resources.
  647. /// </summary>
  648. protected virtual void Dispose(bool disposing)
  649. {
  650. #if WinXP
  651. if (disposing) {
  652. if (m_regHandle != null) {
  653. m_regHandle.Dispose();
  654. }
  655. }
  656. #endif
  657. }
  658. #endregion
  659. }//class
  660. #if WinXP
  661. // Or should we derive from Microsoft.Win32.SafeHandles.SafeHandleMinusOneIsInvalid?
  662. sealed internal class BluetoothAuthenticationRegistrationHandle
  663. : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid
  664. {
  665. object m_objectToKeepAlive, m_objectToKeepAlive2;
  666. #if DEBUG
  667. WeakReference m_weakRef, m_weakRef2;
  668. #endif
  669. public BluetoothAuthenticationRegistrationHandle()
  670. : base(true)
  671. { }
  672. protected override bool ReleaseHandle()
  673. {
  674. #if false // was: DEBUG -- fails in Finalizer case IIRC
  675. System.Diagnostics.Debug.Assert((m_weakRef == null) == (m_objectToKeepAlive == null),
  676. "Only one of the two objects set.");
  677. if (m_weakRef != null) {
  678. bool isAlive = m_weakRef.IsAlive;
  679. try {
  680. System.Diagnostics.Debug.Assert(isAlive
  681. || AppDomain.CurrentDomain.IsFinalizingForUnload(),
  682. "m_weakRef.IsAlive");
  683. } catch (ObjectDisposedException) { } // SafeHandle->CriticalFinalizer
  684. }
  685. #endif
  686. bool success = NativeMethods.BluetoothUnregisterAuthentication(handle);
  687. int gle = Marshal.GetLastWin32Error();
  688. System.Diagnostics.Debug.Assert(success,
  689. "BluetoothUnregisterAuthentication returned false, GLE="
  690. + gle.ToString() + "=0x" + gle.ToString("X"));
  691. return success;
  692. }
  693. internal void SetObjectToKeepAlive(object objectToKeepAlive, object objectToKeepAlive2)
  694. {
  695. m_objectToKeepAlive = objectToKeepAlive;
  696. m_objectToKeepAlive2 = objectToKeepAlive2;
  697. #if DEBUG
  698. m_weakRef = new WeakReference(m_objectToKeepAlive);
  699. m_weakRef2 = new WeakReference(m_objectToKeepAlive);
  700. #endif
  701. }
  702. }//class
  703. #endif
  704. }