├── 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 | --------------------------------------------------------------------------------