├── Bloxy
├── app.config
├── InquiryInfo.cs
├── Properties.cs
├── Utilities.cs
├── HCIEventEventArgs.cs
├── Logger.cs
├── Properties
│ └── AssemblyInfo.cs
├── InquiryResult.cs
├── DeviceConfiguration.cs
├── Bloxy.csproj
├── Opcode.cs
├── BloxyServer.cs
├── Startup.cs
└── USBBluetoothAdapter.cs
├── .gitattributes
├── Bloxy.sln
├── README.md
└── .gitignore
/Bloxy/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Bloxy/InquiryInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace Bloxy
7 | {
8 | public class InquiryInfo
9 | {
10 | public InquiryResult Result { get; set; }
11 | public string RemoteName { get; set; }
12 |
13 | public InquiryInfo(InquiryResult result, string remoteName)
14 | {
15 | Result = result;
16 | RemoteName = remoteName;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Bloxy/Properties.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace Bloxy
7 | {
8 | public static class Properties
9 | {
10 | public static BloxyServer Server { get; set; }
11 | public static USBBluetoothAdapter Adapter { get; set; }
12 | public static DeviceConfiguration RealConfiguration { get; set; }
13 | public static DeviceConfiguration EmulatedConfiguration { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/Bloxy/Utilities.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace Bloxy
7 | {
8 | public static class Utilities
9 | {
10 | public static ulong GetLEULong(byte[] buffer, int offset, int length)
11 | {
12 | ulong ret = (ulong)(buffer[offset + 0] & 0xFF);
13 | int shifter = 8;
14 |
15 | for (int i = 1; i < length; i++)
16 | {
17 | ret |= (ulong)((ulong)(((ulong)(buffer[offset + i])) << shifter));
18 | shifter += 8;
19 | }
20 |
21 | return ret;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Bloxy/HCIEventEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace Bloxy
7 | {
8 | public class HCIEventEventArgs : EventArgs
9 | {
10 | private byte[] _buffer;
11 |
12 | public HCIEventEventArgs(byte[] buffer, int length)
13 | {
14 | //Only preserve the buffer up to a certain length
15 | _buffer = new byte[length];
16 | Array.Copy(buffer, 0, _buffer, 0, length);
17 | }
18 |
19 | public byte[] Buffer
20 | {
21 | get
22 | {
23 | return _buffer;
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Bloxy/Logger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | namespace Bloxy
8 | {
9 | public static class Logger
10 | {
11 | public const bool _DO_LOGGING = true;
12 | public const bool _LOG_DATA = true;
13 |
14 | public static void WriteLine()
15 | {
16 | Logger.WriteLine(String.Empty);
17 | }
18 |
19 | public static void WriteLine(string message)
20 | {
21 | if (_DO_LOGGING)
22 | Console.WriteLine(message);
23 | }
24 |
25 | public static void LogData(char cmd, byte[] data, int offset, int count)
26 | {
27 | if (_LOG_DATA)
28 | File.AppendAllText("log.txt", cmd + ": " +
29 | BitConverter.ToString(data, offset, count) + Environment.NewLine);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Bloxy.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 11.00
3 | # Visual C# Express 2010
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bloxy", "Bloxy\Bloxy.csproj", "{8C065AF6-B648-4EFD-B0B1-59A16128D881}"
5 | EndProject
6 | Global
7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
8 | Debug|x86 = Debug|x86
9 | Release|x86 = Release|x86
10 | EndGlobalSection
11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
12 | {8C065AF6-B648-4EFD-B0B1-59A16128D881}.Debug|x86.ActiveCfg = Debug|x86
13 | {8C065AF6-B648-4EFD-B0B1-59A16128D881}.Debug|x86.Build.0 = Debug|x86
14 | {8C065AF6-B648-4EFD-B0B1-59A16128D881}.Release|x86.ActiveCfg = Release|x86
15 | {8C065AF6-B648-4EFD-B0B1-59A16128D881}.Release|x86.Build.0 = Release|x86
16 | EndGlobalSection
17 | GlobalSection(SolutionProperties) = preSolution
18 | HideSolutionNode = FALSE
19 | EndGlobalSection
20 | EndGlobal
21 |
--------------------------------------------------------------------------------
/Bloxy/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Bloxy")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Microsoft")]
12 | [assembly: AssemblyProduct("Bloxy")]
13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2012")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("e2deab26-ddcf-4961-b938-de62b2c67ec1")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | bloxy
2 | =====
3 |
4 | Bluetooth Proxy
5 |
6 | This application allows two computers, each equipped with a USB Bluetooth adapter, to sit between the connection for two Bluetooth devices:
7 |
8 | Bluetooth Host <-> PC1 <-> PC2 <-> Bluetooth Peripheral
9 |
10 | The PCs can log communication between the Bluetooth devices, extend the range, or whatever else you can think of.
11 |
12 | Each PC requires knowledge of where the other one is on the network, as well as information about both the Bluetooth device it's "emulating" AND the one it's talking to.
13 |
14 | It stores information about the device it's "emulating" in a file called emulated.txt.
15 | It stores information about the device it's actually talking to in a file called real.txt.
16 |
17 | When running it the first time, it will perform an inquiry scan to find each device; you select the device from a list, and it will save the information to the text file.
18 |
19 | Required Parameters:
20 |
21 | /vid=[USB vendor ID of the USB Bluetooth adapter to use]
22 |
23 | /pid=[USB product ID of the USB Bluetooth adapter to use]
24 |
25 | /buddy=[IP address/hostname of the other PC]
26 |
27 | /inport=[TCP port number for incoming connections; should match other PC's outport]
28 |
29 | /outport=[TCP port number for outgoing connections; should match other PC's inport]
30 |
31 | Remember to install the LibUsbDotNet filter driver for the USB Bluetooth adapter as well.
32 | You should probably just install a generated libusb-win32 driver instead, so that Windows won't try to communicate with it at the same time.
33 |
34 | I'll be honest with you -- if you think you have a use for this, you should probably re-evaluate what you're doing.
35 | Good luck.
36 |
--------------------------------------------------------------------------------
/Bloxy/InquiryResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace Bloxy
7 | {
8 | public class InquiryResult
9 | {
10 | #region Declarations
11 |
12 | private ulong _bdAddr;
13 | private byte _pageScanRepititionMode;
14 | private byte _reserved1;
15 | private byte _reserved2;
16 | private uint _deviceClass;
17 | private ushort _clockOffset;
18 |
19 | #endregion
20 |
21 | #region Constructors/Teardown
22 |
23 | public InquiryResult(ulong bdAddr, byte pageScanRepetitionMode,
24 | byte reserved1, byte reserved2, uint deviceClass, ushort clockOffset)
25 | {
26 | _bdAddr = bdAddr;
27 | _pageScanRepititionMode = pageScanRepetitionMode;
28 | _reserved1 = reserved1;
29 | _reserved2 = reserved2;
30 | _deviceClass = deviceClass;
31 | _clockOffset = clockOffset;
32 | }
33 |
34 | #endregion
35 |
36 | #region Public Properties
37 |
38 | public ulong BDAddr
39 | {
40 | get
41 | {
42 | return _bdAddr;
43 | }
44 | }
45 |
46 | public byte PageScanRepetitionMode
47 | {
48 | get
49 | {
50 | return _pageScanRepititionMode;
51 | }
52 | }
53 |
54 | public byte Reserved1
55 | {
56 | get
57 | {
58 | return _reserved1;
59 | }
60 | }
61 |
62 | public byte Reserved2
63 | {
64 | get
65 | {
66 | return _reserved2;
67 | }
68 | }
69 |
70 | public uint DeviceClass
71 | {
72 | get
73 | {
74 | return _deviceClass;
75 | }
76 | }
77 |
78 | public ushort ClockOffset
79 | {
80 | get
81 | {
82 | return _clockOffset;
83 | }
84 | }
85 |
86 | #endregion
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## Eclipse
3 | #################
4 |
5 | *.pydevproject
6 | .project
7 | .metadata
8 | bin/
9 | tmp/
10 | *.tmp
11 | *.bak
12 | *.swp
13 | *~.nib
14 | local.properties
15 | .classpath
16 | .settings/
17 | .loadpath
18 |
19 | # External tool builders
20 | .externalToolBuilders/
21 |
22 | # Locally stored "Eclipse launch configurations"
23 | *.launch
24 |
25 | # CDT-specific
26 | .cproject
27 |
28 | # PDT-specific
29 | .buildpath
30 |
31 |
32 | #################
33 | ## Visual Studio
34 | #################
35 |
36 | ## Ignore Visual Studio temporary files, build results, and
37 | ## files generated by popular Visual Studio add-ons.
38 |
39 | # User-specific files
40 | *.suo
41 | *.user
42 | *.sln.docstates
43 |
44 | # Build results
45 | [Dd]ebug/
46 | [Rr]elease/
47 | *_i.c
48 | *_p.c
49 | *.ilk
50 | *.meta
51 | *.obj
52 | *.pch
53 | *.pdb
54 | *.pgc
55 | *.pgd
56 | *.rsp
57 | *.sbr
58 | *.tlb
59 | *.tli
60 | *.tlh
61 | *.tmp
62 | *.vspscc
63 | .builds
64 | *.dotCover
65 |
66 | ## TODO: If you have NuGet Package Restore enabled, uncomment this
67 | #packages/
68 |
69 | # Visual C++ cache files
70 | ipch/
71 | *.aps
72 | *.ncb
73 | *.opensdf
74 | *.sdf
75 |
76 | # Visual Studio profiler
77 | *.psess
78 | *.vsp
79 |
80 | # ReSharper is a .NET coding add-in
81 | _ReSharper*
82 |
83 | # Installshield output folder
84 | [Ee]xpress
85 |
86 | # DocProject is a documentation generator add-in
87 | DocProject/buildhelp/
88 | DocProject/Help/*.HxT
89 | DocProject/Help/*.HxC
90 | DocProject/Help/*.hhc
91 | DocProject/Help/*.hhk
92 | DocProject/Help/*.hhp
93 | DocProject/Help/Html2
94 | DocProject/Help/html
95 |
96 | # Click-Once directory
97 | publish
98 |
99 | # Others
100 | [Bb]in
101 | [Oo]bj
102 | sql
103 | TestResults
104 | *.Cache
105 | ClientBin
106 | stylecop.*
107 | ~$*
108 | *.dbmdl
109 | Generated_Code #added for RIA/Silverlight projects
110 |
111 | # Backup & report files from converting an old project file to a newer
112 | # Visual Studio version. Backup files are not needed, because we have git ;-)
113 | _UpgradeReport_Files/
114 | Backup*/
115 | UpgradeLog*.XML
116 |
117 |
118 |
119 | ############
120 | ## Windows
121 | ############
122 |
123 | # Windows image file caches
124 | Thumbs.db
125 |
126 | # Folder config file
127 | Desktop.ini
128 |
129 |
130 | #############
131 | ## Python
132 | #############
133 |
134 | *.py[co]
135 |
136 | # Packages
137 | *.egg
138 | *.egg-info
139 | dist
140 | build
141 | eggs
142 | parts
143 | bin
144 | var
145 | sdist
146 | develop-eggs
147 | .installed.cfg
148 |
149 | # Installer logs
150 | pip-log.txt
151 |
152 | # Unit test / coverage reports
153 | .coverage
154 | .tox
155 |
156 | #Translations
157 | *.mo
158 |
159 | #Mr Developer
160 | .mr.developer.cfg
161 |
162 | # Mac crap
163 | .DS_Store
164 |
--------------------------------------------------------------------------------
/Bloxy/DeviceConfiguration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | namespace Bloxy
8 | {
9 | public class DeviceConfiguration
10 | {
11 | #region Declarations
12 |
13 | public ulong BDAddr { get; set; }
14 | public string RemoteName { get; set; }
15 | public uint DeviceClass { get; set; }
16 | public byte PageScanRepetitionMode { get; set; }
17 | public ushort ClockOffset { get; set; } //I'm aware this shouldn't even be saved nor is it even doing it properly, but whatever.
18 |
19 | #endregion
20 |
21 | #region Constructors/Teardown
22 |
23 | public DeviceConfiguration()
24 | {
25 | //Do nothing
26 | }
27 |
28 | #endregion
29 |
30 | #region Public Methods
31 |
32 | public static DeviceConfiguration Load(string fileName)
33 | {
34 | var ret = new DeviceConfiguration();
35 |
36 | var reader = File.OpenText(fileName);
37 | while (!reader.EndOfStream)
38 | {
39 | var line = reader.ReadLine();
40 |
41 | if (!String.IsNullOrEmpty(line) && line.Contains("="))
42 | {
43 | var key = line.Substring(0, line.IndexOf("="));
44 | var value = line.Substring(line.IndexOf("=") + 1);
45 |
46 | switch (key)
47 | {
48 | case "BDAddr":
49 | {
50 | ret.BDAddr = Convert.ToUInt64(value, 16);
51 | break;
52 | }
53 | case "RemoteName":
54 | {
55 | ret.RemoteName = value;
56 | break;
57 | }
58 | case "DeviceClass":
59 | {
60 | ret.DeviceClass = Convert.ToUInt32(value, 16);
61 | break;
62 | }
63 | case "PageScanRepetitionMode":
64 | {
65 | ret.PageScanRepetitionMode = Convert.ToByte(value, 16);
66 | break;
67 | }
68 | case "ClockOffset":
69 | {
70 | ret.ClockOffset = Convert.ToUInt16(value, 16);
71 | break;
72 | }
73 | default:
74 | {
75 | //Do nothing, ignore it
76 | break;
77 | }
78 | }
79 | }
80 | }
81 |
82 | return ret;
83 | }
84 |
85 | public void Save(string fileName)
86 | {
87 | if (File.Exists(fileName)) File.Delete(fileName);
88 | var writer = File.CreateText(fileName);
89 |
90 | writer.WriteLine("BDAddr=" + this.BDAddr.ToString("X12"));
91 | writer.WriteLine("RemoteName=" + this.RemoteName);
92 | writer.WriteLine("DeviceClass=" + this.DeviceClass.ToString("X06"));
93 | writer.WriteLine("PageScanRepetitionMode=" + this.PageScanRepetitionMode.ToString("X02"));
94 | writer.WriteLine("ClockOffset=" + this.ClockOffset.ToString("X04"));
95 | writer.Close();
96 | }
97 |
98 | #endregion
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Bloxy/Bloxy.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | x86
6 | 8.0.30703
7 | 2.0
8 | {8C065AF6-B648-4EFD-B0B1-59A16128D881}
9 | Exe
10 | Properties
11 | Bloxy
12 | Bloxy
13 | v4.0
14 |
15 |
16 | 512
17 |
18 |
19 | x86
20 | true
21 | full
22 | false
23 | bin\Debug\
24 | DEBUG;TRACE
25 | prompt
26 | 4
27 |
28 |
29 | x86
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 |
37 |
38 |
39 | ..\..\..\..\..\..\Program Files\LibUsbDotNet\LibUsbDotNet.dll
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
74 |
--------------------------------------------------------------------------------
/Bloxy/Opcode.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace Bloxy
7 | {
8 | public enum OpcodeGroupField
9 | {
10 | LinkControl = 0x01,
11 | HCIPolicy = 0x02,
12 | HCBaseband = 0x03,
13 | Information = 0x04,
14 | Status = 0x05,
15 | Testing = 0x06
16 | };
17 |
18 | public enum OpcodeCommandField
19 | {
20 | Inquiry = 0x01,
21 | Reset = 0x03,
22 | CreateConnection = 0x05,
23 | AcceptConnectionRequest = 0x09,
24 | SendPINCodeRequestReply = 0x0D,
25 | SendPINCodeRequestNegativeReply = 0x0E,
26 | WriteLocalName = 0x13,
27 | RemoteNameRequest = 0x19,
28 | WriteScanEnable = 0x1A,
29 | WriteDeviceClass = 0x24
30 | };
31 |
32 | public class Opcode
33 | {
34 | #region Declarations
35 |
36 | private OpcodeGroupField _ogf;
37 | private OpcodeCommandField _ocf;
38 |
39 | #endregion
40 |
41 | #region Constructors/Teardown
42 |
43 | public Opcode(OpcodeGroupField ogf, OpcodeCommandField ocf)
44 | {
45 | _ogf = ogf;
46 | _ocf = ocf;
47 | }
48 |
49 | public Opcode(ushort ogf, ushort ocf)
50 | {
51 | _ogf = (OpcodeGroupField)ogf;
52 | _ocf = (OpcodeCommandField)ocf;
53 | }
54 |
55 | public Opcode(byte[] buffer, int offset)
56 | {
57 | _ogf = (OpcodeGroupField)(buffer[offset + 1] >> 2);
58 | _ocf = (OpcodeCommandField)(((buffer[offset + 1] & 0x03) << 8) | (buffer[offset + 0]));
59 | }
60 |
61 | public Opcode(ushort data)
62 | {
63 | _ogf = (OpcodeGroupField)(data >> 10);
64 | _ocf = (OpcodeCommandField)(data & 0x03FF);
65 | }
66 |
67 | #endregion
68 |
69 | #region Public Properties
70 |
71 | public OpcodeGroupField Ogf
72 | {
73 | get
74 | {
75 | return _ogf;
76 | }
77 | }
78 |
79 | public OpcodeCommandField Ocf
80 | {
81 | get
82 | {
83 | return _ocf;
84 | }
85 | }
86 |
87 | public ushort Data
88 | {
89 | get
90 | {
91 | //Overkill much?
92 | return (ushort)((ushort)(((ushort)(_ogf) << 10)) | ((ushort)_ocf));
93 | }
94 | }
95 |
96 | #endregion
97 |
98 | #region Overrides
99 |
100 | public override bool Equals(object obj)
101 | {
102 | Opcode o = obj as Opcode;
103 | bool ret = false;
104 |
105 | if (this == o)
106 | {
107 | ret = true;
108 | }
109 | else if (this != null && obj != null)
110 | {
111 | ret = (this.Ogf == o.Ogf && this.Ocf == o.Ocf);
112 | }
113 |
114 | return ret;
115 | }
116 |
117 | public static bool operator ==(Opcode a, Opcode b)
118 | {
119 | if (System.Object.ReferenceEquals(a, b))
120 | return true;
121 |
122 | if (((object)a == null) || ((object)b == null))
123 | return false;
124 |
125 | return a.Ogf == b.Ogf && a.Ocf == b.Ocf;
126 | }
127 |
128 | public static bool operator !=(Opcode a, Opcode b)
129 | {
130 | return !(a == b);
131 | }
132 |
133 | public override int GetHashCode()
134 | {
135 | return this.Data;
136 | }
137 |
138 | public override string ToString()
139 | {
140 | return this.Data.ToString("X4");
141 | }
142 |
143 | #endregion
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/Bloxy/BloxyServer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Net.Sockets;
6 | using System.Text;
7 | using System.Threading;
8 |
9 | namespace Bloxy
10 | {
11 | public class BloxyServer
12 | {
13 | #region Declarations
14 |
15 | private string _server;
16 | private int _inport;
17 | private int _outport;
18 | private ushort _currentConnectionHandle;
19 | private Thread _listenThread;
20 | private TcpListener _listener;
21 | private TcpClient _client;
22 |
23 | #endregion
24 |
25 | #region Constructors/Teardown
26 |
27 | public BloxyServer(string server, int inport, int outport)
28 | {
29 | _server = server;
30 | _inport = inport;
31 | _outport = outport;
32 | }
33 |
34 | #endregion
35 |
36 | #region Public Methods
37 |
38 | public void Start()
39 | {
40 | try { Stop(); }
41 | catch { /* Whatever... */ }
42 |
43 | //Set up events
44 | Properties.Adapter.ConnectionComplete += adapter_ConnectionComplete;
45 | Properties.Adapter.ConnectionRequestReceived += adapter_ConnectionRequestReceived;
46 | Properties.Adapter.IncomingDataReceived += adapter_IncomingDataReceived;
47 |
48 | _listener = new TcpListener(IPAddress.Any, _inport);
49 | _listener.Start();
50 |
51 | _listenThread = new Thread(new ThreadStart(_Listen));
52 | _listenThread.IsBackground = true;
53 | _listenThread.Start();
54 | }
55 |
56 | public void Stop()
57 | {
58 | //Tear down events
59 | Properties.Adapter.ConnectionComplete -= adapter_ConnectionComplete;
60 | Properties.Adapter.ConnectionRequestReceived -= adapter_ConnectionRequestReceived;
61 | Properties.Adapter.IncomingDataReceived -= adapter_IncomingDataReceived;
62 |
63 | if (_listenThread != null)
64 | {
65 | _listenThread.Abort();
66 | _listenThread = null;
67 | }
68 |
69 | if (_listener != null)
70 | {
71 | _listener.Stop();
72 | _listener = null;
73 | }
74 | }
75 |
76 | #endregion
77 |
78 | private void _Listen()
79 | {
80 | while (true)
81 | {
82 | var client = _listener.AcceptTcpClient();
83 |
84 | var th = new Thread(new ParameterizedThreadStart(_HandleMessages));
85 | th.IsBackground = true;
86 | th.Start(client);
87 |
88 | Thread.Sleep(100);
89 | }
90 | }
91 |
92 | private void _HandleMessages(object client)
93 | {
94 | try
95 | {
96 | var c = client as TcpClient;
97 | var stream = c.GetStream();
98 |
99 | while (true)
100 | {
101 | //Get message prefixed with 4-byte length field
102 | var length = new byte[4];
103 | int bytesRead = stream.Read(length, 0, 4);
104 | if (bytesRead <= 0)
105 | break; //Fall over and die if we get something weird
106 | ulong l = (ulong)((ulong)length[0] | (ulong)((ulong)(length[1] << 8) & 0xFF00) |
107 | (ulong)((ulong)(length[2] << 16) & 0xFF0000) | (ulong)((length[3] << 24) & 0xFF000000));
108 | var buffer = new byte[l];
109 | stream.Read(buffer, 0, (int)l);
110 |
111 | //Handle message
112 | switch (buffer[0])
113 | {
114 | case (byte)'I':
115 | {
116 | //Incoming ACL data from the other PC/device received
117 | Logger.WriteLine("Network: Received ACL data: " + BitConverter.ToString(buffer, 1, buffer.Length - 1));
118 |
119 | //Change the connection handle to our adapter's
120 | buffer[1] = (byte)(_currentConnectionHandle & 0xFF);
121 | byte temp = (byte)(buffer[2] & 0xF0);
122 | buffer[2] = (byte)((byte)((_currentConnectionHandle >> 8) & 0xFF) | (byte)temp);
123 |
124 | //Send it on
125 | Logger.WriteLine("Sending ACL data: " + BitConverter.ToString(buffer, 1, buffer.Length - 1));
126 | Properties.Adapter.SendACLData(buffer, 1, buffer.Length - 1);
127 |
128 | break;
129 | }
130 | case (byte)'C':
131 | {
132 | //The other PC/device completed connecting
133 | ushort connectionHandle = (ushort)(buffer[4] | (buffer[5] << 8));
134 | ulong bdAddr = Utilities.GetLEULong(buffer, 6, 6);
135 |
136 | //We only care if the device we're emulating has completed connecting
137 | if (bdAddr == Properties.EmulatedConfiguration.BDAddr)
138 | {
139 | Logger.WriteLine("Network: Received connection complete");
140 |
141 | //Accept this connection request
142 | Properties.Adapter.AcceptConnectionRequest(Properties.RealConfiguration.BDAddr, 0x01);
143 | }
144 |
145 | break;
146 | }
147 | case (byte)'R':
148 | {
149 | //The other PC/device received a connection request
150 | Logger.WriteLine("Network: Received connection request");
151 |
152 | //We must now establish the connection on our end
153 | Properties.Adapter.Connect(Properties.RealConfiguration.BDAddr,
154 | Properties.RealConfiguration.PageScanRepetitionMode, Properties.RealConfiguration.ClockOffset);
155 |
156 | break;
157 | }
158 | default:
159 | {
160 | Logger.WriteLine("Network: Unknown command received: " + ((char)buffer[0]).ToString());
161 | break;
162 | }
163 | }
164 |
165 | Thread.Sleep(100);
166 | }
167 | }
168 | catch
169 | {
170 | //Whatever...
171 | }
172 | }
173 |
174 | private void adapter_IncomingDataReceived(object sender, HCIEventEventArgs e)
175 | {
176 | //Let the other PC/device know we have incoming data
177 | _Send('I', e.Buffer);
178 | }
179 |
180 | private void adapter_ConnectionRequestReceived(object sender, HCIEventEventArgs e)
181 | {
182 | //Let the other PC/device know we received a connection request
183 | _Send('R', e.Buffer);
184 | }
185 |
186 | private void adapter_ConnectionComplete(object sender, HCIEventEventArgs e)
187 | {
188 | //The real device's connection is complete, save its handle
189 | _currentConnectionHandle = (ushort)(e.Buffer[3] | (e.Buffer[4] << 8));
190 |
191 | //Let the other PC/device know we're done
192 | _Send('C', e.Buffer);
193 | }
194 |
195 | private void _Send(char command, byte[] message)
196 | {
197 | //Build the whole buffer
198 | var msg = new byte[message.Length + 1];
199 | msg[0] = (byte)command;
200 | Array.Copy(message, 0, msg, 1, message.Length);
201 |
202 | //Connect if we need to
203 | if (_client == null)
204 | {
205 | _client = new TcpClient();
206 | _client.Connect(_server, _outport);
207 | }
208 |
209 | //Put the length in front of it (needs to be combined with above, I know)
210 | var m = new byte[msg.Length + 4];
211 | m[0] = (byte)(msg.Length & 0xFF);
212 | m[1] = (byte)((msg.Length >> 8) & 0xFF);
213 | m[2] = (byte)((msg.Length >> 16) & 0xFF);
214 | m[3] = (byte)((msg.Length >> 24) & 0xFF);
215 | Array.Copy(msg, 0, m, 4, msg.Length);
216 |
217 | //Send it off
218 | var stream = _client.GetStream();
219 | stream.Write(m, 0, m.Length);
220 | stream.Flush();
221 | }
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/Bloxy/Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net;
6 | using System.Net.Sockets;
7 | using System.Text;
8 | using System.Threading;
9 |
10 | namespace Bloxy
11 | {
12 | class Startup
13 | {
14 | private const string REAL_CONFIG_FILENAME = "real.txt";
15 | private const string EMULATED_CONFIG_FILENAME = "emulated.txt";
16 | private const int INQUIRY_TIMEOUT_SECS = 10;
17 |
18 | //This application allows two computers, each equipped with a USB Bluetooth adapter, to sit between
19 | // the connection for two Bluetooth devices:
20 | // Bluetooth Host <-> PC1 <-> PC2 <-> Bluetooth Peripheral
21 | //The PCs can log communication between the Bluetooth devices, extend the range, or
22 | // whatever else you can think of.
23 | //Each PC requires knowledge of where the other one is on the network, as well as information
24 | // about both the Bluetooth device it's "emulating" AND the one it's talking to.
25 | //It stores information about the device it's "emulating" in a file called emulated.txt.
26 | //It stores information about the device it's actually talking to in a file called real.txt.
27 | //When running it the first time, it will perform an inquiry scan to find each device;
28 | // you select the device from a list, and it will save the information to the text file.
29 | //Parameters:
30 | // /vid=[USB vendor ID of the USB Bluetooth adapter to use]
31 | // /pid=[USB product ID of the USB Bluetooth adapter to use]
32 | // /buddy=[IP address/hostname of the other PC]
33 | // /inport=[TCP port number for incoming connections; should match other PC's outport]
34 | // /outport=[TCP port number for outgoing connections; should match other PC's inport]
35 | //Remember to install the LibUsbDotNet filter driver for the USB Bluetooth adapter as well.
36 | // You should probably just install a generated libusb-win32 driver instead, so that Windows
37 | // won't try to communicate with it at the same time.
38 | static void Main(string[] args)
39 | {
40 | try
41 | {
42 | //Parse command line arguments
43 | ushort? vid = null;
44 | ushort? pid = null;
45 | string buddy = String.Empty;
46 | int? inport = null;
47 | int? outport = null;
48 | foreach (var arg in args)
49 | {
50 | if (arg.StartsWith("/") && arg.Contains("="))
51 | {
52 | var key = arg.Substring(1, arg.IndexOf("=") - 1);
53 | var value = arg.Substring(arg.IndexOf("=") + 1);
54 |
55 | switch (key.ToLower())
56 | {
57 | case "vid":
58 | {
59 | vid = Convert.ToUInt16(value, 16);
60 | break;
61 | }
62 | case "pid":
63 | {
64 | pid = Convert.ToUInt16(value, 16);
65 | break;
66 | }
67 | case "inport":
68 | {
69 | inport = Convert.ToInt32(value);
70 | break;
71 | }
72 | case "outport":
73 | {
74 | outport = Convert.ToInt32(value);
75 | break;
76 | }
77 | case "buddy":
78 | {
79 | buddy = value;
80 | break;
81 | }
82 | default:
83 | {
84 | Logger.WriteLine("Unknown command line argument: " + arg);
85 | break;
86 | }
87 | }
88 | }
89 | }
90 |
91 | //Validate parameters
92 | if (String.IsNullOrEmpty(buddy))
93 | throw new ArgumentException("No buddy name/IP specified");
94 | if (!inport.HasValue || !outport.HasValue)
95 | throw new ArgumentException("No incoming/outgoing port(s) specified");
96 | if (!vid.HasValue || !pid.HasValue)
97 | throw new ArgumentException("No vendor/product ID(s) specified");
98 |
99 | //Initialize the USB Bluetooth adapter device
100 | Properties.Adapter = new USBBluetoothAdapter(vid.Value, pid.Value);
101 | Properties.Adapter.Open();
102 |
103 | //Make sure we have device configurations; get them if not
104 | if (!File.Exists(REAL_CONFIG_FILENAME))
105 | if (!_CreateConfiguration(REAL_CONFIG_FILENAME)) return;
106 | if (!File.Exists(EMULATED_CONFIG_FILENAME))
107 | if (!_CreateConfiguration(EMULATED_CONFIG_FILENAME)) return;
108 |
109 | //Retrieve device configurations
110 | Properties.RealConfiguration = DeviceConfiguration.Load(REAL_CONFIG_FILENAME);
111 | Properties.EmulatedConfiguration = DeviceConfiguration.Load(EMULATED_CONFIG_FILENAME);
112 |
113 | //Set ourselves as the device from the configuration file
114 | Properties.Adapter.SetLocalName(Properties.EmulatedConfiguration.RemoteName);
115 | Properties.Adapter.SetDeviceClass(Properties.EmulatedConfiguration.DeviceClass);
116 |
117 | //Set ourselves discoverable
118 | Properties.Adapter.SetDiscoverableMode(true);
119 |
120 | //Set up the server for talking to the other PC
121 | Properties.Server = new BloxyServer(buddy, inport.Value, outport.Value);
122 | Properties.Server.Start();
123 |
124 | //Main loop...
125 | Logger.WriteLine("Waiting for event...");
126 | while (true)
127 | {
128 | if (Console.KeyAvailable)
129 | if (Console.ReadKey(true).Key == ConsoleKey.Escape)
130 | break;
131 |
132 | Thread.Sleep(100);
133 | }
134 |
135 | //Clean up
136 | Properties.Server.Stop();
137 | Logger.WriteLine("Done.");
138 | }
139 | catch (Exception ex)
140 | {
141 | Logger.WriteLine("ERROR: " + ex.ToString());
142 | }
143 | finally
144 | {
145 | try
146 | {
147 | //More cleanup...
148 | if (Properties.Adapter != null)
149 | Properties.Adapter.Close();
150 | }
151 | catch
152 | {
153 | //Whatever...
154 | }
155 | }
156 | }
157 |
158 | private static bool _CreateConfiguration(string fileName)
159 | {
160 | //Do an inquiry scan, find a device, and save its information
161 | Logger.WriteLine(String.Format("Configuration file '{0}' does not exist.", fileName));
162 | Logger.WriteLine("Performing inquiry scan...");
163 |
164 | var devices = Properties.Adapter.DoInquiryScan(INQUIRY_TIMEOUT_SECS);
165 | Logger.WriteLine(String.Format("Found {0} devices:", devices.Count));
166 | var list = new Dictionary();
167 | for (int i = 1; i < devices.Count + 1; i++)
168 | {
169 | var name = Properties.Adapter.GetRemoteName(devices[i - 1]);
170 | Logger.WriteLine(String.Format("\t{0}: {1} - {2}", i.ToString(), devices[i - 1].BDAddr.ToString("X12"), name));
171 |
172 | list.Add(i, new InquiryInfo(devices[i - 1], name));
173 | }
174 |
175 | while (true)
176 | {
177 | Logger.WriteLine("Enter the index of the device you want to use (or 'x' to quit): ");
178 | var selected = Console.ReadKey().KeyChar;
179 | Logger.WriteLine();
180 |
181 | //HACK: Yeah, I know this would freak out with more than 9 devices...
182 | if (selected == 'x')
183 | return false;
184 | else if (list.ContainsKey(selected - 0x30))
185 | {
186 | var item = list[selected - 0x30];
187 |
188 | //Build the configuration
189 | var file = new DeviceConfiguration();
190 | file.BDAddr = item.Result.BDAddr;
191 | file.RemoteName = item.RemoteName;
192 | file.DeviceClass = item.Result.DeviceClass;
193 | file.PageScanRepetitionMode = item.Result.PageScanRepetitionMode;
194 | file.ClockOffset = item.Result.ClockOffset;
195 |
196 | //Write it out
197 | Logger.WriteLine(String.Format("Saving configuration file '{0}'...", fileName));
198 | file.Save(fileName);
199 |
200 | break;
201 | }
202 | else
203 | {
204 | Logger.WriteLine("Invalid selection, try again.");
205 | }
206 | }
207 |
208 | return true;
209 | }
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/Bloxy/USBBluetoothAdapter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading;
6 | using LibUsbDotNet;
7 | using LibUsbDotNet.Main;
8 |
9 | namespace Bloxy
10 | {
11 | public enum AssignedNumbers
12 | {
13 | GeneralUnlimitedIAC = 0x9E8B33,
14 | LimitedDedicatedIAC = 0x9E8B00
15 | };
16 |
17 | public enum HCIEvent
18 | {
19 | InquiryComplete = 0x01,
20 | InquiryResult = 0x02,
21 | ConnectionComplete = 0x03,
22 | ConnectionRequest = 0x04,
23 | DisconnectionComplete = 0x05,
24 | RemoteNameRequestComplete = 0x07,
25 | QoSSetupComplete = 0x0D,
26 | Complete = 0x0E,
27 | Status = 0x0F,
28 | PINCodeRequestEvent = 0x16,
29 | MaxSlotsChangeEvent = 0x1B,
30 | PageScanRepetitionModeChangeEvent = 0x20
31 | };
32 |
33 | public class USBBluetoothAdapter
34 | {
35 | #region Declarations
36 |
37 | private ushort _vendorId;
38 | private ushort _productId;
39 | private UsbDevice _device;
40 | private Dictionary _commandData;
41 | private List _completedCommands;
42 | private UsbEndpointReader _reader;
43 | private UsbEndpointWriter _writer;
44 | private UsbEndpointReader _isoReader;
45 | private UsbEndpointWriter _isoWriter;
46 |
47 | #endregion
48 |
49 | #region Constructors/Teardown
50 |
51 | public USBBluetoothAdapter(ushort vendorId, ushort productId)
52 | {
53 | _vendorId = vendorId;
54 | _productId = productId;
55 |
56 | _commandData = new Dictionary();
57 | _completedCommands = new List();
58 | }
59 |
60 | #endregion
61 |
62 | #region Public Events
63 |
64 | public event EventHandler ConnectionComplete;
65 | public event EventHandler ConnectionRequestReceived;
66 | public event EventHandler IncomingDataReceived;
67 |
68 | #endregion
69 |
70 | #region Public Methods
71 |
72 | public void Open()
73 | {
74 | _device = UsbDevice.OpenUsbDevice(new UsbDeviceFinder(_vendorId, _productId));
75 |
76 | if (_device != null)
77 | {
78 | IUsbDevice whole = _device as IUsbDevice;
79 |
80 | if (!ReferenceEquals(whole, null))
81 | {
82 | whole.SetConfiguration(1);
83 | whole.ClaimInterface(1);
84 | }
85 |
86 | //Set up the endpoints
87 | var hci = _device.OpenEndpointReader(ReadEndpointID.Ep01);
88 | _reader = _device.OpenEndpointReader(ReadEndpointID.Ep02);
89 | _writer = _device.OpenEndpointWriter(WriteEndpointID.Ep02);
90 | _isoReader = _device.OpenEndpointReader(ReadEndpointID.Ep03);
91 | _isoWriter = _device.OpenEndpointWriter(WriteEndpointID.Ep03);
92 |
93 | //Set up our read callback(s)
94 | hci.DataReceived += hci_DataReceived;
95 | hci.DataReceivedEnabled = true;
96 | _reader.DataReceived += reader_DataReceived;
97 | _reader.DataReceivedEnabled = true;
98 | _isoReader.DataReceived += _isoReader_DataReceived;
99 | _isoReader.DataReceivedEnabled = true;
100 |
101 | //Reset the device
102 | Reset();
103 | }
104 | }
105 |
106 | public void Reset()
107 | {
108 | _SendHCICommand(new Opcode(OpcodeGroupField.HCBaseband, OpcodeCommandField.Reset));
109 |
110 | _commandData.Clear();
111 | _completedCommands.Clear();
112 | }
113 |
114 | public List DoInquiryScan(int timeoutSeconds)
115 | {
116 | const int iac = (int)AssignedNumbers.GeneralUnlimitedIAC;
117 | var data = new byte[5];
118 | int timeout = Convert.ToInt32(Math.Round(timeoutSeconds / 1.28));
119 | if (timeout <= 0 || timeout > 0x30) throw new ArgumentException("Invalid timeout");
120 | var ret = new List();
121 |
122 | data[0] = (byte)(iac & 0xFF);
123 | data[1] = (byte)((iac & 0xFF00) >> 8);
124 | data[2] = (byte)((iac & 0xFF0000) >> 16);
125 | data[3] = Convert.ToByte(timeout);
126 |
127 | var opcode = new Opcode(OpcodeGroupField.LinkControl, OpcodeCommandField.Inquiry);
128 | _SendHCICommand(opcode, data);
129 |
130 | if (_commandData.ContainsKey(opcode))
131 | {
132 | ret = _commandData[opcode] as List;
133 | _commandData.Remove(opcode);
134 | }
135 |
136 | return ret;
137 | }
138 |
139 | public string GetRemoteName(InquiryResult device)
140 | {
141 | var data = new byte[10];
142 | var ret = String.Empty;
143 |
144 | data[0] = (byte)(device.BDAddr & 0xFF);
145 | data[1] = (byte)((device.BDAddr & 0xFF00) >> 8);
146 | data[2] = (byte)((device.BDAddr & 0xFF0000) >> 16);
147 | data[3] = (byte)((device.BDAddr & 0xFF000000) >> 24);
148 | data[4] = (byte)((device.BDAddr & 0xFF00000000) >> 32);
149 | data[5] = (byte)((device.BDAddr & 0xFF0000000000) >> 40);
150 | data[6] = device.PageScanRepetitionMode;
151 | data[8] = (byte)(device.ClockOffset & 0xFF);
152 | data[9] = (byte)((byte)((device.ClockOffset & 0xFF00) >> 8) | (byte)0x80);
153 |
154 | var opcode = new Opcode(OpcodeGroupField.LinkControl, OpcodeCommandField.RemoteNameRequest);
155 | _SendHCICommand(opcode, data);
156 |
157 | if (_commandData.ContainsKey(opcode))
158 | {
159 | ret = _commandData[opcode] as string;
160 | _commandData.Remove(opcode);
161 | }
162 |
163 | return ret;
164 | }
165 |
166 | public void SetDiscoverableMode(bool discoverable)
167 | {
168 | var opcode = new Opcode(OpcodeGroupField.HCBaseband, OpcodeCommandField.WriteScanEnable);
169 | _SendHCICommand(opcode, new byte[] {discoverable ? (byte)0x03 : (byte)0x01});
170 | }
171 |
172 | public void SetLocalName(string name)
173 | {
174 | var data = new byte[name.Length + 1];
175 | Array.Copy(ASCIIEncoding.ASCII.GetBytes(name), data, name.Length);
176 |
177 | var opcode = new Opcode(OpcodeGroupField.HCBaseband, OpcodeCommandField.WriteLocalName);
178 | _SendHCICommand(opcode, data);
179 | }
180 |
181 | public void SetDeviceClass(uint deviceClass)
182 | {
183 | var data = new byte[3];
184 |
185 | data[0] = (byte)(deviceClass & 0xFF);
186 | data[1] = (byte)((deviceClass >> 8) & 0xFF);
187 | data[2] = (byte)((deviceClass >> 16) & 0xFF);
188 |
189 | var opcode = new Opcode(OpcodeGroupField.HCBaseband, OpcodeCommandField.WriteDeviceClass);
190 | _SendHCICommand(opcode, data);
191 | }
192 |
193 | public void SendACLData(byte[] buffer, int offset, int count)
194 | {
195 | int transferred;
196 | _writer.Write(buffer, offset, count, 10000, out transferred);
197 |
198 | lock (_writer)
199 | {
200 | Logger.LogData('I', buffer, offset, count);
201 | }
202 | }
203 |
204 | public void AcceptConnectionRequest(ulong bdAddr, byte role)
205 | {
206 | var data = new byte[7];
207 |
208 | data[0] = (byte)(bdAddr & 0xFF);
209 | data[1] = (byte)((bdAddr & 0xFF00) >> 8);
210 | data[2] = (byte)((bdAddr & 0xFF0000) >> 16);
211 | data[3] = (byte)((bdAddr & 0xFF000000) >> 24);
212 | data[4] = (byte)((bdAddr & 0xFF00000000) >> 32);
213 | data[5] = (byte)((bdAddr & 0xFF0000000000) >> 40);
214 | data[6] = role;
215 |
216 | var opcode = new Opcode(OpcodeGroupField.LinkControl, OpcodeCommandField.AcceptConnectionRequest);
217 | _SendHCICommand(opcode, data, true);
218 | }
219 |
220 | public void Connect(InquiryResult result)
221 | {
222 | Connect(result.BDAddr, result.PageScanRepetitionMode, result.ClockOffset);
223 | }
224 |
225 | public void Connect(ulong bdAddr, byte pageScanRepetitionMode, ushort clockOffset)
226 | {
227 | var data = new byte[13];
228 |
229 | data[0] = (byte)(bdAddr & 0xFF);
230 | data[1] = (byte)((bdAddr & 0xFF00) >> 8);
231 | data[2] = (byte)((bdAddr & 0xFF0000) >> 16);
232 | data[3] = (byte)((bdAddr & 0xFF000000) >> 24);
233 | data[4] = (byte)((bdAddr & 0xFF00000000) >> 32);
234 | data[5] = (byte)((bdAddr & 0xFF0000000000) >> 40);
235 | data[6] = (byte)0x18;
236 | data[7] = (byte)0x00;
237 | data[8] = pageScanRepetitionMode;
238 | data[9] = 0x00;
239 | data[10] = (byte)(clockOffset & 0xFF);
240 | data[11] = (byte)(((clockOffset >> 8) & 0xFF) | 0x80);
241 | data[12] = 0x00;
242 |
243 | var opcode = new Opcode(OpcodeGroupField.LinkControl, OpcodeCommandField.CreateConnection);
244 | _SendHCICommand(opcode, data, true);
245 | }
246 |
247 | //NOTE: This doesn't work if you send a PIN, not sure why.
248 | public void SendPINCodeReply(ulong bdAddr, string pin)
249 | {
250 | Opcode opcode;
251 | var data = new byte[6 + (String.IsNullOrEmpty(pin) ? 0 : pin.Length + 1)];
252 |
253 | data[0] = (byte)(bdAddr & 0xFF);
254 | data[1] = (byte)((bdAddr & 0xFF00) >> 8);
255 | data[2] = (byte)((bdAddr & 0xFF0000) >> 16);
256 | data[3] = (byte)((bdAddr & 0xFF000000) >> 24);
257 | data[4] = (byte)((bdAddr & 0xFF00000000) >> 32);
258 | data[5] = (byte)((bdAddr & 0xFF0000000000) >> 40);
259 | if (!String.IsNullOrEmpty(pin))
260 | {
261 | data[6] = (byte)pin.Length;
262 | for (int i = 0; i < pin.Length; i++)
263 | data[7 + i] = (byte)pin[i];
264 |
265 | opcode = new Opcode(OpcodeGroupField.LinkControl, OpcodeCommandField.SendPINCodeRequestReply);
266 | }
267 | else
268 | opcode = new Opcode(OpcodeGroupField.LinkControl, OpcodeCommandField.SendPINCodeRequestNegativeReply);
269 |
270 | _SendHCICommand(opcode, data, true);
271 | }
272 |
273 | public void Close()
274 | {
275 | if (_device != null)
276 | {
277 | if (_device.IsOpen)
278 | {
279 | IUsbDevice whole = _device as IUsbDevice;
280 |
281 | if (!ReferenceEquals(whole, null))
282 | {
283 | whole.ReleaseInterface(1);
284 | }
285 |
286 | _device.Close();
287 | UsbDevice.Exit();
288 | }
289 | }
290 | }
291 |
292 | #endregion
293 |
294 | #region Event Handlers
295 |
296 | private void hci_DataReceived(object sender, EndpointDataEventArgs e)
297 | {
298 | switch ((HCIEvent)e.Buffer[0])
299 | {
300 | case HCIEvent.InquiryComplete:
301 | {
302 | lock (_completedCommands)
303 | {
304 | _completedCommands.Add(new Opcode(OpcodeGroupField.LinkControl, OpcodeCommandField.Inquiry));
305 | }
306 |
307 | break;
308 | }
309 | case HCIEvent.InquiryResult:
310 | {
311 | var opcode = new Opcode(OpcodeGroupField.LinkControl, OpcodeCommandField.Inquiry);
312 | if (!_commandData.ContainsKey(opcode)) _commandData.Add(opcode, new List());
313 | var list = _commandData[opcode] as List;
314 |
315 | int responses = Convert.ToInt32(e.Buffer[2]);
316 | int offset = 3;
317 | for (int i = 0; i < responses; i++)
318 | {
319 | list.Add(new InquiryResult(Utilities.GetLEULong(e.Buffer, offset + 0, 6), e.Buffer[offset + 6],
320 | e.Buffer[offset + 7], e.Buffer[offset + 8], (uint)Utilities.GetLEULong(e.Buffer, offset + 9, 3),
321 | (ushort)Utilities.GetLEULong(e.Buffer, offset + 12, 2)));
322 | offset += 14;
323 | }
324 |
325 | break;
326 | }
327 | case HCIEvent.ConnectionComplete:
328 | {
329 | Logger.WriteLine("Connection Complete, Status: " + e.Buffer[2].ToString("X2"));
330 | ushort connectionHandle = (ushort)(e.Buffer[3] | (e.Buffer[4] << 8));
331 | ulong bdAddr = Utilities.GetLEULong(e.Buffer, 5, 6);
332 |
333 | //Raise event out
334 | if (ConnectionComplete != null)
335 | ConnectionComplete(this, new HCIEventEventArgs(e.Buffer, e.Count));
336 |
337 | break;
338 | }
339 | case HCIEvent.DisconnectionComplete:
340 | {
341 | Logger.WriteLine("Disconnection Complete, Status: " + e.Buffer[2].ToString("X2"));
342 |
343 | break;
344 | }
345 | case HCIEvent.ConnectionRequest:
346 | {
347 | var bdAddr = Utilities.GetLEULong(e.Buffer, 2, 6);
348 | var deviceClass = Utilities.GetLEULong(e.Buffer, 8, 3);
349 | byte linkType = e.Buffer[11];
350 | Logger.WriteLine(String.Format("Connection Request Received, BD_ADDR {0}, class {1}, link type {2}",
351 | bdAddr.ToString("X12"), deviceClass.ToString("X6"), linkType.ToString("X2")));
352 |
353 | //Accept this request (or do whatever with it)
354 | if (ConnectionRequestReceived != null)
355 | ConnectionRequestReceived(this, new HCIEventEventArgs(e.Buffer, e.Count));
356 |
357 | break;
358 | }
359 | case HCIEvent.RemoteNameRequestComplete:
360 | {
361 | var opcode = new Opcode(OpcodeGroupField.LinkControl, OpcodeCommandField.RemoteNameRequest);
362 | if (!_commandData.ContainsKey(opcode))
363 | _commandData.Add(opcode, ASCIIEncoding.ASCII.GetString(e.Buffer, 9, 248).Trim(new char[] {'\0'} ));
364 |
365 | lock (_completedCommands)
366 | {
367 | _completedCommands.Add(opcode);
368 | }
369 |
370 | break;
371 | }
372 | case HCIEvent.QoSSetupComplete:
373 | {
374 | Logger.WriteLine("QoS Setup Complete");
375 |
376 | break;
377 | }
378 | case HCIEvent.Complete:
379 | {
380 | var command = new Opcode(e.Buffer, 3);
381 |
382 | lock (_completedCommands)
383 | {
384 | _completedCommands.Add(command);
385 | }
386 |
387 | break;
388 | }
389 | case HCIEvent.Status:
390 | {
391 | var command = new Opcode(e.Buffer, 4);
392 |
393 | break;
394 | }
395 | case HCIEvent.PINCodeRequestEvent:
396 | {
397 | var bdAddr = Utilities.GetLEULong(e.Buffer, 2, 6);
398 |
399 | //Send the reply
400 | SendPINCodeReply(bdAddr, String.Empty);
401 |
402 | break;
403 | }
404 | default:
405 | {
406 | //Uh?
407 | Logger.WriteLine("Unknown HCI event: " + e.Buffer[0].ToString("X2"));
408 |
409 | break;
410 | }
411 | }
412 | }
413 |
414 | private void reader_DataReceived(object sender, EndpointDataEventArgs e)
415 | {
416 | Logger.WriteLine("Incoming: " + BitConverter.ToString(e.Buffer, 0, e.Count));
417 | lock (_writer)
418 | {
419 | Logger.LogData('O', e.Buffer, 0, e.Count);
420 | }
421 |
422 | if (IncomingDataReceived != null)
423 | IncomingDataReceived(this, new HCIEventEventArgs(e.Buffer, e.Count));
424 | }
425 |
426 | private void _isoReader_DataReceived(object sender, EndpointDataEventArgs e)
427 | {
428 | Logger.WriteLine("Incoming isochronous data: " + BitConverter.ToString(e.Buffer, 0, e.Count));
429 | }
430 |
431 | #endregion
432 |
433 | #region Local Methods
434 |
435 | private void _SendHCICommand(Opcode command)
436 | {
437 | _SendHCICommand(command, false);
438 | }
439 |
440 | private void _SendHCICommand(Opcode command, bool returnImmediately)
441 | {
442 | _SendHCICommand(command, null, returnImmediately);
443 | }
444 |
445 | private void _SendHCICommand(Opcode command, byte[] parameterData)
446 | {
447 | _SendHCICommand(command, parameterData, false);
448 | }
449 |
450 | private void _SendHCICommand(Opcode command, byte[] parameterData, bool returnImmediately)
451 | {
452 | var cmdData = new byte[3 + (parameterData != null ? parameterData.Length : 0)];
453 | var packet = new UsbSetupPacket(0x20, 0x00, 0x0000, 0x0000, (short)cmdData.Length);
454 |
455 | cmdData[0] = (byte)(command.Data & 0xFF);
456 | cmdData[1] = (byte)((command.Data >> 8) & 0xFF);
457 | if (parameterData != null && parameterData.Length > 0)
458 | {
459 | cmdData[2] = (byte)parameterData.Length;
460 | for (int i = 0; i < parameterData.Length; i++)
461 | cmdData[3 + i] = parameterData[i];
462 | }
463 |
464 | int transferred;
465 | _device.ControlTransfer(ref packet, cmdData, cmdData.Length, out transferred);
466 | if (transferred != cmdData.Length)
467 | throw new InvalidOperationException(String.Format("Failed to send command; sent {0} bytes instead of {1}",
468 | transferred, cmdData.Length));
469 |
470 | if (!returnImmediately)
471 | {
472 | //Wait for command to complete
473 | _WaitForCompletion(command);
474 | }
475 | }
476 |
477 | private void _WaitForCompletion(Opcode command)
478 | {
479 | while (true)
480 | {
481 | lock (_completedCommands)
482 | {
483 | if (_completedCommands.Contains(command))
484 | {
485 | _completedCommands.Remove(command);
486 | break;
487 | }
488 | }
489 |
490 | Thread.Sleep(10);
491 | }
492 | }
493 |
494 | #endregion
495 | }
496 | }
497 |
--------------------------------------------------------------------------------