1
+ using System ;
2
+ using System . IO ;
3
+ using System . Net . Sockets ;
4
+ using System . Text ;
5
+ using System . Threading ;
6
+ using System . Threading . Tasks ;
7
+ using MySql . Data . MySqlClient ;
8
+ using MySql . Data . Serialization ;
9
+
10
+ namespace MySqlConnector . Tests
11
+ {
12
+ internal sealed class FakeMySqlServerConnection
13
+ {
14
+ public FakeMySqlServerConnection ( FakeMySqlServer server , int connectionId )
15
+ {
16
+ m_server = server ?? throw new ArgumentNullException ( nameof ( server ) ) ;
17
+ m_connectionId = connectionId ;
18
+ }
19
+
20
+ public async Task RunAsync ( TcpClient client , CancellationToken token )
21
+ {
22
+ try
23
+ {
24
+ using ( token . Register ( client . Dispose ) )
25
+ using ( client )
26
+ using ( var stream = client . GetStream ( ) )
27
+ {
28
+ await SendAsync ( stream , 0 , WriteInitialHandshake ) ;
29
+ await ReadPayloadAsync ( stream , token ) ; // handshake response
30
+ await SendAsync ( stream , 2 , WriteOk ) ;
31
+
32
+ var keepRunning = true ;
33
+ while ( keepRunning )
34
+ {
35
+ var bytes = await ReadPayloadAsync ( stream , token ) ;
36
+ switch ( ( CommandKind ) bytes [ 0 ] )
37
+ {
38
+ case CommandKind . Quit :
39
+ await SendAsync ( stream , 1 , WriteOk ) ;
40
+ keepRunning = false ;
41
+ break ;
42
+
43
+ case CommandKind . Ping :
44
+ case CommandKind . Query :
45
+ case CommandKind . ResetConnection :
46
+ await SendAsync ( stream , 1 , WriteOk ) ;
47
+ break ;
48
+
49
+ default :
50
+ Console . WriteLine ( "** UNHANDLED ** {0}" , ( CommandKind ) bytes [ 0 ] ) ;
51
+ await SendAsync ( stream , 1 , WriteError ) ;
52
+ break ;
53
+ }
54
+ }
55
+ }
56
+ }
57
+ finally
58
+ {
59
+ m_server . ClientDisconnected ( ) ;
60
+ }
61
+ }
62
+
63
+ private static async Task SendAsync ( Stream stream , int sequenceNumber , Action < BinaryWriter > writePayload )
64
+ {
65
+ var packet = MakePayload ( sequenceNumber , writePayload ) ;
66
+ await stream . WriteAsync ( packet , 0 , packet . Length ) ;
67
+ }
68
+
69
+ private static byte [ ] MakePayload ( int sequenceNumber , Action < BinaryWriter > writePayload )
70
+ {
71
+ using ( var memoryStream = new MemoryStream ( ) )
72
+ {
73
+ using ( var writer = new BinaryWriter ( memoryStream , Encoding . UTF8 , leaveOpen : true ) )
74
+ {
75
+ writer . Write ( default ( int ) ) ;
76
+ writePayload ( writer ) ;
77
+ memoryStream . Position = 0 ;
78
+ writer . Write ( ( ( int ) ( memoryStream . Length - 4 ) ) | ( ( sequenceNumber % 256 ) << 24 ) ) ;
79
+ }
80
+ return memoryStream . ToArray ( ) ;
81
+ }
82
+ }
83
+
84
+ private static async Task < byte [ ] > ReadPayloadAsync ( Stream stream , CancellationToken token )
85
+ {
86
+ var header = await ReadBytesAsync ( stream , 4 , token ) ;
87
+ var length = header [ 0 ] | ( header [ 1 ] << 8 ) | ( header [ 2 ] << 16 ) ;
88
+ var sequenceNumber = header [ 3 ] ;
89
+ return await ReadBytesAsync ( stream , length , token ) ;
90
+ }
91
+
92
+ private static async Task < byte [ ] > ReadBytesAsync ( Stream stream , int count , CancellationToken token )
93
+ {
94
+ var bytes = new byte [ count ] ;
95
+ for ( var bytesRead = 0 ; bytesRead < count ; )
96
+ bytesRead += await stream . ReadAsync ( bytes , bytesRead , count - bytesRead , token ) ;
97
+ return bytes ;
98
+ }
99
+
100
+ private void WriteInitialHandshake ( BinaryWriter writer )
101
+ {
102
+ var random = new Random ( 1 ) ;
103
+ var authData = new byte [ 20 ] ;
104
+ random . NextBytes ( authData ) ;
105
+ var capabilities =
106
+ ProtocolCapabilities . LongPassword |
107
+ ProtocolCapabilities . FoundRows |
108
+ ProtocolCapabilities . LongFlag |
109
+ ProtocolCapabilities . IgnoreSpace |
110
+ ProtocolCapabilities . Protocol41 |
111
+ ProtocolCapabilities . Transactions |
112
+ ProtocolCapabilities . SecureConnection |
113
+ ProtocolCapabilities . MultiStatements |
114
+ ProtocolCapabilities . MultiResults |
115
+ ProtocolCapabilities . PluginAuth |
116
+ ProtocolCapabilities . ConnectionAttributes |
117
+ ProtocolCapabilities . PluginAuthLengthEncodedClientData ;
118
+
119
+ writer . Write ( ( byte ) 10 ) ; // protocol version
120
+ writer . WriteNullTerminated ( m_server . ServerVersion ) ; // server version
121
+ writer . Write ( m_connectionId ) ; // conection ID
122
+ writer . Write ( authData , 0 , 8 ) ; // auth plugin data part 1
123
+ writer . Write ( ( byte ) 0 ) ; // filler
124
+ writer . Write ( ( ushort ) capabilities ) ;
125
+ writer . Write ( ( byte ) CharacterSet . Utf8Binary ) ; // character set
126
+ writer . Write ( ( ushort ) 0 ) ; // status flags
127
+ writer . Write ( ( ushort ) ( ( uint ) capabilities >> 16 ) ) ;
128
+ writer . Write ( ( byte ) authData . Length ) ;
129
+ writer . Write ( new byte [ 10 ] ) ; // reserved
130
+ writer . Write ( authData , 8 , authData . Length - 8 ) ;
131
+ if ( authData . Length - 8 < 13 )
132
+ writer . Write ( new byte [ 13 - ( authData . Length - 8 ) ] ) ; // have to write at least 13 bytes
133
+ writer . WriteNullTerminated ( "mysql_native_password" ) ;
134
+ }
135
+
136
+ private static void WriteOk ( BinaryWriter writer )
137
+ {
138
+ writer . Write ( ( byte ) 0 ) ; // signature
139
+ writer . Write ( ( byte ) 0 ) ; // 0 rows affected
140
+ writer . Write ( ( byte ) 0 ) ; // last insert ID
141
+ writer . Write ( ( ushort ) 0 ) ; // server status
142
+ writer . Write ( ( ushort ) 0 ) ; // warning count
143
+ }
144
+
145
+ private static void WriteError ( BinaryWriter writer )
146
+ {
147
+ writer . Write ( ( byte ) 0xFF ) ; // signature
148
+ writer . Write ( ( ushort ) MySqlErrorCode . UnknownError ) ; // error code
149
+ writer . WriteRaw ( "#ERROR" ) ;
150
+ writer . WriteRaw ( "An unknown error occurred" ) ;
151
+ }
152
+
153
+ readonly FakeMySqlServer m_server ;
154
+ readonly int m_connectionId ;
155
+ }
156
+ }
0 commit comments