using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using QRCoder;
///
/// Disclaimer: Most of the mDNS related codebase is generated by ChatGPT.
///
public class AndroidWirelessDebuggingAutoConnect
{
private const string ADB_PATH = "adb.exe";
///
/// If true and an Android device broadcasts a connection service, app will attempt to connect to that device without waiting for
/// the pair service (which is broadcasted after the QR code is read). This is useful for quickly connecting to the last paired device
/// without requiring the QR code to be read.
///
private static bool canConnectWithoutPair = true;
private static bool hasPaired;
private class ServiceData
{
public readonly string Name;
public readonly ushort Port;
public ServiceData( string name, ushort port )
{
Name = name;
Port = port;
}
}
public static void Main( string[] args )
{
string name = GetRandomText( 5 );
string password = GetRandomText( 6 );
string pairServiceName = $"{name}_adb-tls-pairing";
string connectServiceName = $"_adb-tls-connect._tcp.local";
string qrCode = $"WIFI:T:ADB;S:{name};P:{password};;";
Console.WriteLine( AsciiQRCodeHelper.GetQRCode( qrCode, QRCodeGenerator.ECCLevel.M, invert: false ) );
using UdpClient client = new UdpClient();
IPEndPoint localEp = new IPEndPoint( IPAddress.Any, 5353 );
client.Client.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true );
client.ExclusiveAddressUse = false;
client.Client.Bind( localEp );
IPAddress multicastAddress = IPAddress.Parse( "224.0.0.251" );
client.JoinMulticastGroup( multicastAddress );
Console.WriteLine( "Scan the QR code with Wireless debugging..." );
while( true )
{
try
{
if( hasPaired || canConnectWithoutPair )
SendMdnsQuery( client, connectServiceName );
byte[] data = client.Receive( ref localEp );
List discoveredServices = ParseMdnsPacket( data );
if( !hasPaired && discoveredServices.Find( ( e ) => e.Name.Contains( pairServiceName ) ) is ServiceData pairService )
{
Console.WriteLine( $"Pairing with {localEp.Address}:{pairService.Port}..." );
if( RunADBCommand( $"pair {localEp.Address}:{pairService.Port} {password}" ) )
{
hasPaired = true;
client.Client.ReceiveTimeout = 5000;
Console.WriteLine( "Fetching connection information..." );
}
}
else if( ( hasPaired || canConnectWithoutPair ) && discoveredServices.Find( ( e ) => e.Name.Contains( connectServiceName ) ) is ServiceData connectService )
{
Console.WriteLine( $"Connecting to {localEp.Address}:{connectService.Port}..." );
if( RunADBCommand( $"connect {localEp.Address}:{connectService.Port}" ) )
{
Console.WriteLine( "Connected to device! Press any key to exit..." );
Console.ReadKey();
return;
}
else if( canConnectWithoutPair )
{
Console.WriteLine( "Auto-connect failed. Scan the QR code to connect manually..." );
canConnectWithoutPair = false;
}
}
Thread.Sleep( 500 );
}
catch( TimeoutException )
{
}
}
}
private static bool RunADBCommand( string command )
{
try
{
using( Process adb = new Process() )
{
adb.StartInfo = new ProcessStartInfo()
{
FileName = ADB_PATH,
Arguments = command,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
};
adb.Start();
string output = adb.StandardOutput.ReadToEnd();
Console.WriteLine( output );
adb.WaitForExit();
return adb.ExitCode == 0 && output.IndexOf( "failed", StringComparison.OrdinalIgnoreCase ) < 0;
}
}
catch( Exception e )
{
Console.WriteLine( $"Error while running adb command: {e}" );
return false;
}
}
private static string GetRandomText( int length )
{
const string randomCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
StringBuilder sb = new StringBuilder( length );
for( int i = 0; i < length; i++ )
sb.Append( randomCharacters[Random.Shared.Next( randomCharacters.Length )] );
return sb.ToString();
}
private static void SendMdnsQuery( UdpClient udpClient, string serviceName )
{
byte[] query = ConstructMdnsQuery( serviceName );
IPEndPoint endPoint = new IPEndPoint( IPAddress.Parse( "224.0.0.251" ), 5353 );
udpClient.Send( query, query.Length, endPoint );
}
private static byte[] ConstructMdnsQuery( string serviceName )
{
// Header section
byte[] header = new byte[12];
header[5] = 1; // QDCOUNT (1 question)
// Question section
byte[] nameBytes = EncodeDomainName( serviceName );
byte[] question = new byte[nameBytes.Length + 4];
Buffer.BlockCopy( nameBytes, 0, question, 0, nameBytes.Length );
question[nameBytes.Length] = 0; // QTYPE (A record)
question[nameBytes.Length + 1] = 255; // PTR
question[nameBytes.Length + 2] = 0; // QCLASS (IN)
question[nameBytes.Length + 3] = 255;
// Combine header and question sections
byte[] query = new byte[header.Length + question.Length];
Buffer.BlockCopy( header, 0, query, 0, header.Length );
Buffer.BlockCopy( question, 0, query, header.Length, question.Length );
return query;
}
private static byte[] EncodeDomainName( string domainName )
{
var parts = domainName.Split( '.' );
var result = new byte[domainName.Length + 2]; // Include the length bytes and the final zero byte
int index = 0;
foreach( var part in parts )
{
result[index++] = (byte) part.Length;
byte[] partBytes = Encoding.UTF8.GetBytes( part );
Buffer.BlockCopy( partBytes, 0, result, index, partBytes.Length );
index += partBytes.Length;
}
result[index] = 0; // Null terminator
return result;
}
private static List ParseMdnsPacket( byte[] data )
{
List result = new List( 1 );
try
{
int index = 0;
// Parse DNS header
ushort transactionId = ReadUInt16( data, ref index );
ushort flags = ReadUInt16( data, ref index );
ushort questionCount = ReadUInt16( data, ref index );
ushort answerCount = ReadUInt16( data, ref index );
ushort authorityCount = ReadUInt16( data, ref index );
ushort additionalCount = ReadUInt16( data, ref index );
//Console.WriteLine( $"Transaction ID: {transactionId} Flags: {flags} Questions: {questionCount} Answers: {answerCount} Authority RRs: {authorityCount} Additional RRs: {additionalCount}" );
// Parse DNS questions
for( int i = 0; i < questionCount; i++ )
{
string questionName = ReadDomainName( data, ref index );
ushort questionType = ReadUInt16( data, ref index );
ushort questionClass = ReadUInt16( data, ref index );
//Console.WriteLine( $"Question {i + 1} Name: {questionName} Type: {questionType} Class: {questionClass}" );
}
// Parse answers
for( int i = 0; i < answerCount; i++ )
{
string answerName = ReadDomainName( data, ref index );
ushort answerType = ReadUInt16( data, ref index );
ushort answerClass = ReadUInt16( data, ref index );
uint ttl = ReadUInt32( data, ref index );
ushort dataLength = ReadUInt16( data, ref index );
if( answerType == 33 ) // SRV record
{
ushort priority = ReadUInt16( data, ref index );
ushort weight = ReadUInt16( data, ref index );
ushort port = ReadUInt16( data, ref index );
string target = ReadDomainName( data, ref index );
//Console.WriteLine( $"Answer SRV: {answerName}, Port: {port}, Target: {target}, TTL: {ttl}" );
result.Add( new ServiceData( answerName, port ) );
}
else
{
// Skip the rest of the data
index += dataLength;
}
}
for( int i = 0; i < authorityCount; i++ )
{
string name = ReadDomainName( data, ref index );
ushort type = ReadUInt16( data, ref index );
ushort classValue = ReadUInt16( data, ref index );
uint ttl = ReadUInt32( data, ref index );
ushort dataLength = ReadUInt16( data, ref index );
if( type == 2 ) // NS record
{
string nsDomain = ReadDomainName( data, ref index );
//Console.WriteLine( $"Authority NS: {name}, NS: {nsDomain}, TTL: {ttl}" );
}
else if( type == 6 ) // SOA record
{
string mName = ReadDomainName( data, ref index );
string rName = ReadDomainName( data, ref index );
uint serial = ReadUInt32( data, ref index );
uint refresh = ReadUInt32( data, ref index );
uint retry = ReadUInt32( data, ref index );
uint expire = ReadUInt32( data, ref index );
uint minimum = ReadUInt32( data, ref index );
//Console.WriteLine( $"Authority SOA: {name}, MNAME: {mName}, RNAME: {rName}, Serial: {serial}, Refresh: {refresh}, Retry: {retry}, Expire: {expire}, Minimum: {minimum}" );
}
else if( type == 33 ) // SRV record
{
ushort priority = ReadUInt16( data, ref index );
ushort weight = ReadUInt16( data, ref index );
ushort port = ReadUInt16( data, ref index );
string target = ReadDomainName( data, ref index );
//Console.WriteLine( $"Authority SRV: {name}, Port: {port}, Target: {target}, TTL: {ttl}" );
result.Add( new ServiceData( name, port ) );
}
else
{
// Skip the rest of the data
index += dataLength;
}
}
for( int i = 0; i < additionalCount; i++ )
{
string name = ReadDomainName( data, ref index );
ushort type = ReadUInt16( data, ref index );
ushort classValue = ReadUInt16( data, ref index );
uint ttl = ReadUInt32( data, ref index );
ushort dataLength = ReadUInt16( data, ref index );
if( type == 33 ) // SRV record
{
ushort priority = ReadUInt16( data, ref index );
ushort weight = ReadUInt16( data, ref index );
ushort port = ReadUInt16( data, ref index );
string target = ReadDomainName( data, ref index );
//Console.WriteLine( $"Additional SRV: {name}, Port: {port}, Target: {target}, TTL: {ttl}" );
result.Add( new ServiceData( name, port ) );
}
else
{
// Skip the rest of the data
index += dataLength;
}
}
}
catch( Exception ex )
{
Console.WriteLine( $"Error parsing mDNS packet: {ex}" );
}
return result;
}
private static string ReadDomainName( byte[] data, ref int index )
{
StringBuilder name = new StringBuilder();
while( data[index] != 0 )
{
byte length = data[index++];
if( ( length & 0xC0 ) == 0xC0 ) // Compression
{
ushort pointer = (ushort) ( ( ( length & 0x3F ) << 8 ) | data[index++] );
int savedIndex = index;
index = pointer;
name.Append( ReadDomainName( data, ref index ) );
index = savedIndex;
return name.ToString();
}
if( name.Length > 0 )
name.Append( '.' );
name.Append( Encoding.UTF8.GetString( data, index, length ) );
index += length;
}
index++; // Skip the null byte at the end of the domain name
return name.ToString();
}
private static ushort ReadUInt16( byte[] data, ref int index )
{
ushort value = (ushort) ( ( data[index] << 8 ) | data[index + 1] );
index += 2;
return value;
}
private static uint ReadUInt32( byte[] data, ref int index )
{
uint value = (uint) ( ( data[index] << 24 ) | ( data[index + 1] << 16 ) | ( data[index + 2] << 8 ) | data[index + 3] );
index += 4;
return value;
}
}