1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Diagnostics;
6 using System.IO.PortsTests;
7 using System.Text;
8 using System.Threading;
9 using Legacy.Support;
10 using Xunit;
11 
12 namespace System.IO.Ports.Tests
13 {
14     public class SerialStream_BeginWrite : PortsTest
15     {
16         // The string size used for large byte array testing
17         private const int LARGE_BUFFER_SIZE = 2048;
18 
19         // When we test Write and do not care about actually writing anything we must still
20         // create an byte array to pass into the method the following is the size of the
21         // byte array used in this situation
22         private const int DEFAULT_BUFFER_SIZE = 1;
23         private const int DEFAULT_BUFFER_OFFSET = 0;
24         private const int DEFAULT_BUFFER_COUNT = 1;
25 
26         // The maximum buffer size when an exception occurs
27         private const int MAX_BUFFER_SIZE_FOR_EXCEPTION = 255;
28 
29         // The maximum buffer size when an exception is not expected
30         private const int MAX_BUFFER_SIZE = 8;
31 
32         // The default number of times the write method is called when verifying write
33         private const int DEFAULT_NUM_WRITES = 3;
34 
35         // The default number of bytes to write
36         private const int DEFAULT_NUM_BYTES_TO_WRITE = 128;
37 
38         // Maximum time to wait for processing the read command to complete
39         private const int MAX_WAIT_WRITE_COMPLETE = 1000;
40 
41         #region Test Cases
42         [ConditionalFact(nameof(HasOneSerialPort))]
Buffer_Null()43         public void Buffer_Null()
44         {
45             VerifyWriteException(null, 0, 1, typeof(ArgumentNullException));
46         }
47 
48         [ConditionalFact(nameof(HasOneSerialPort))]
Offset_NEG1()49         public void Offset_NEG1()
50         {
51             VerifyWriteException(new byte[DEFAULT_BUFFER_SIZE], -1, DEFAULT_BUFFER_COUNT, typeof(ArgumentOutOfRangeException));
52         }
53 
54         [ConditionalFact(nameof(HasOneSerialPort))]
Offset_NEGRND()55         public void Offset_NEGRND()
56         {
57             Random rndGen = new Random(-55);
58 
59             VerifyWriteException(new byte[DEFAULT_BUFFER_SIZE], rndGen.Next(int.MinValue, 0), DEFAULT_BUFFER_COUNT, typeof(ArgumentOutOfRangeException));
60         }
61 
62         [ConditionalFact(nameof(HasOneSerialPort))]
Offset_MinInt()63         public void Offset_MinInt()
64         {
65             VerifyWriteException(new byte[DEFAULT_BUFFER_SIZE], int.MinValue, DEFAULT_BUFFER_COUNT, typeof(ArgumentOutOfRangeException));
66         }
67 
68         [ConditionalFact(nameof(HasOneSerialPort))]
Count_NEG1()69         public void Count_NEG1()
70         {
71             VerifyWriteException(new byte[DEFAULT_BUFFER_SIZE], DEFAULT_BUFFER_OFFSET, -1, typeof(ArgumentOutOfRangeException));
72         }
73 
74         [ConditionalFact(nameof(HasOneSerialPort))]
Count_NEGRND()75         public void Count_NEGRND()
76         {
77             Random rndGen = new Random(-55);
78 
79             VerifyWriteException(new byte[DEFAULT_BUFFER_SIZE], DEFAULT_BUFFER_OFFSET, rndGen.Next(int.MinValue, 0), typeof(ArgumentOutOfRangeException));
80         }
81 
82         [ConditionalFact(nameof(HasOneSerialPort))]
Count_MinInt()83         public void Count_MinInt()
84         {
85             VerifyWriteException(new byte[DEFAULT_BUFFER_SIZE], DEFAULT_BUFFER_OFFSET, int.MinValue, typeof(ArgumentOutOfRangeException));
86         }
87 
88         [ConditionalFact(nameof(HasOneSerialPort))]
OffsetCount_EQ_Length_Plus_1()89         public void OffsetCount_EQ_Length_Plus_1()
90         {
91             Random rndGen = new Random(-55);
92             int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE_FOR_EXCEPTION);
93             int offset = rndGen.Next(0, bufferLength);
94             int count = bufferLength + 1 - offset;
95             Type expectedException = typeof(ArgumentException);
96 
97             VerifyWriteException(new byte[bufferLength], offset, count, expectedException);
98         }
99 
100         [ConditionalFact(nameof(HasOneSerialPort))]
OffsetCount_GT_Length()101         public void OffsetCount_GT_Length()
102         {
103             Random rndGen = new Random(-55);
104             int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE_FOR_EXCEPTION);
105             int offset = rndGen.Next(0, bufferLength);
106             int count = rndGen.Next(bufferLength + 1 - offset, int.MaxValue);
107             Type expectedException = typeof(ArgumentException);
108 
109             VerifyWriteException(new byte[bufferLength], offset, count, expectedException);
110         }
111 
112         [ConditionalFact(nameof(HasOneSerialPort))]
Offset_GT_Length()113         public void Offset_GT_Length()
114         {
115             Random rndGen = new Random(-55);
116             int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE_FOR_EXCEPTION);
117             int offset = rndGen.Next(bufferLength, int.MaxValue);
118             int count = DEFAULT_BUFFER_COUNT;
119             Type expectedException = typeof(ArgumentException);
120 
121             VerifyWriteException(new byte[bufferLength], offset, count, expectedException);
122         }
123 
124         [ConditionalFact(nameof(HasOneSerialPort))]
Count_GT_Length()125         public void Count_GT_Length()
126         {
127             Random rndGen = new Random(-55);
128             int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE_FOR_EXCEPTION);
129             int offset = DEFAULT_BUFFER_OFFSET;
130             int count = rndGen.Next(bufferLength + 1, int.MaxValue);
131             Type expectedException = typeof(ArgumentException);
132 
133             VerifyWriteException(new byte[bufferLength], offset, count, expectedException);
134         }
135 
136         [ConditionalFact(nameof(HasNullModem))]
OffsetCount_EQ_Length()137         public void OffsetCount_EQ_Length()
138         {
139             Random rndGen = new Random(-55);
140             int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE);
141             int offset = rndGen.Next(0, bufferLength - 1);
142             int count = bufferLength - offset;
143 
144             VerifyWrite(new byte[bufferLength], offset, count);
145         }
146 
147         [ConditionalFact(nameof(HasNullModem))]
Offset_EQ_Length_Minus_1()148         public void Offset_EQ_Length_Minus_1()
149         {
150             Random rndGen = new Random(-55);
151             int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE);
152             int offset = bufferLength - 1;
153             int count = 1;
154 
155             VerifyWrite(new byte[bufferLength], offset, count);
156         }
157 
158         [ConditionalFact(nameof(HasNullModem))]
Count_EQ_Length()159         public void Count_EQ_Length()
160         {
161             Random rndGen = new Random(-55);
162             int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE);
163             int offset = 0;
164             int count = bufferLength;
165 
166             VerifyWrite(new byte[bufferLength], offset, count);
167         }
168 
169         [ConditionalFact(nameof(HasNullModem))]
ASCIIEncoding()170         public void ASCIIEncoding()
171         {
172             Random rndGen = new Random(-55);
173             int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE);
174             int offset = rndGen.Next(0, bufferLength - 1);
175             int count = rndGen.Next(1, bufferLength - offset);
176 
177             VerifyWrite(new byte[bufferLength], offset, count, new ASCIIEncoding());
178         }
179 
180         [ConditionalFact(nameof(HasNullModem))]
UTF8Encoding()181         public void UTF8Encoding()
182         {
183             Random rndGen = new Random(-55);
184             int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE);
185             int offset = rndGen.Next(0, bufferLength - 1);
186             int count = rndGen.Next(1, bufferLength - offset);
187 
188             VerifyWrite(new byte[bufferLength], offset, count, new UTF8Encoding());
189         }
190 
191         [ConditionalFact(nameof(HasNullModem))]
UTF32Encoding()192         public void UTF32Encoding()
193         {
194             Random rndGen = new Random(-55);
195             int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE);
196             int offset = rndGen.Next(0, bufferLength - 1);
197             int count = rndGen.Next(1, bufferLength - offset);
198 
199             VerifyWrite(new byte[bufferLength], offset, count, new UTF32Encoding());
200         }
201 
202         [ConditionalFact(nameof(HasNullModem))]
UnicodeEncoding()203         public void UnicodeEncoding()
204         {
205             Random rndGen = new Random(-55);
206             int bufferLength = rndGen.Next(1, MAX_BUFFER_SIZE);
207             int offset = rndGen.Next(0, bufferLength - 1);
208             int count = rndGen.Next(1, bufferLength - offset);
209 
210             VerifyWrite(new byte[bufferLength], offset, count, new UnicodeEncoding());
211         }
212 
213         [ConditionalFact(nameof(HasNullModem), nameof(HasHardwareFlowControl))]
LargeBuffer()214         public void LargeBuffer()
215         {
216             int bufferLength = LARGE_BUFFER_SIZE;
217             int offset = 0;
218             int count = bufferLength;
219 
220             VerifyWrite(new byte[bufferLength], offset, count, 1);
221         }
222 
223         [ConditionalFact(nameof(HasNullModem))]
Callback()224         public void Callback()
225         {
226             using (SerialPort com1 = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
227             using (SerialPort com2 = new SerialPort(TCSupport.LocalMachineSerialInfo.SecondAvailablePortName))
228             {
229                 CallbackHandler callbackHandler = new CallbackHandler();
230 
231                 Debug.WriteLine("Verifying BeginWrite with a callback specified");
232 
233                 com1.Open();
234                 com2.Open();
235 
236                 IAsyncResult writeAsyncResult = com1.BaseStream.BeginWrite(new byte[DEFAULT_NUM_BYTES_TO_WRITE], 0,
237                     DEFAULT_NUM_BYTES_TO_WRITE, callbackHandler.Callback, this);
238                 callbackHandler.BeginWriteAysncResult = writeAsyncResult;
239 
240                 Assert.Equal(this, writeAsyncResult.AsyncState);
241                 Assert.False(writeAsyncResult.CompletedSynchronously, "Should not have completed sync");
242                 Assert.False(writeAsyncResult.IsCompleted, "Should not have completed yet");
243 
244                 com2.RtsEnable = true;
245 
246                 // callbackHandler.WriteAysncResult guarantees that the callback has been called however it does not gauarentee that
247                 // the code calling the callback has finished it's processing
248                 IAsyncResult callbackWriteAsyncResult = callbackHandler.WriteAysncResult;
249 
250                 // No we have to wait for the callbackHandler to complete
251                 int elapsedTime = 0;
252                 while (!callbackWriteAsyncResult.IsCompleted && elapsedTime < MAX_WAIT_WRITE_COMPLETE)
253                 {
254                     Thread.Sleep(10);
255                     elapsedTime += 10;
256                 }
257 
258                 Assert.Equal(this, callbackWriteAsyncResult.AsyncState);
259                 Assert.False(callbackWriteAsyncResult.CompletedSynchronously, "Should not have completed sync (cback)");
260                 Assert.True(callbackWriteAsyncResult.IsCompleted, "Should have completed (cback)");
261                 Assert.Equal(this, writeAsyncResult.AsyncState);
262                 Assert.False(writeAsyncResult.CompletedSynchronously, "Should not have completed sync (write)");
263                 Assert.True(writeAsyncResult.IsCompleted, "Should have completed (write)");
264             }
265         }
266 
267         [ConditionalFact(nameof(HasNullModem), nameof(HasHardwareFlowControl))]
Callback_State()268         public void Callback_State()
269         {
270             using (SerialPort com1 = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
271             using (SerialPort com2 = new SerialPort(TCSupport.LocalMachineSerialInfo.SecondAvailablePortName))
272             {
273                 CallbackHandler callbackHandler = new CallbackHandler();
274 
275                 Debug.WriteLine("Verifying BeginWrite with a callback and state specified");
276                 com1.Handshake = Handshake.RequestToSend;
277                 com1.Open();
278                 com2.Open();
279 
280                 IAsyncResult writeAsyncResult = com1.BaseStream.BeginWrite(new byte[DEFAULT_NUM_BYTES_TO_WRITE], 0, DEFAULT_NUM_BYTES_TO_WRITE, callbackHandler.Callback, this);
281                 callbackHandler.BeginWriteAysncResult = writeAsyncResult;
282 
283                 Assert.Equal(this, writeAsyncResult.AsyncState);
284                 Assert.False(writeAsyncResult.CompletedSynchronously, "Should not have completed sync");
285                 Assert.False(writeAsyncResult.IsCompleted, "Should not have completed yet");
286 
287                 com2.RtsEnable = true;
288 
289                 // callbackHandler.WriteAysncResult guarantees that the callback has been called however it does not gauarentee that
290                 // the code calling the callback has finished it's processing
291                 IAsyncResult callbackWriteAsyncResult = callbackHandler.WriteAysncResult;
292 
293                 // No we have to wait for the callbackHandler to complete
294                 int elapsedTime = 0;
295                 while (!callbackWriteAsyncResult.IsCompleted && elapsedTime < MAX_WAIT_WRITE_COMPLETE)
296                 {
297                     Thread.Sleep(10);
298                     elapsedTime += 10;
299                 }
300 
301                 Assert.Equal(this, callbackWriteAsyncResult.AsyncState);
302                 Assert.False(writeAsyncResult.CompletedSynchronously, "Should not have completed sync (cback)");
303                 Assert.True(callbackWriteAsyncResult.IsCompleted, "Should have completed (cback)");
304                 Assert.Equal(this, writeAsyncResult.AsyncState);
305                 Assert.False(writeAsyncResult.CompletedSynchronously, "Should not have completed sync (write)");
306                 Assert.True(writeAsyncResult.IsCompleted, "Should have completed (write)");
307             }
308         }
309 
310         [ConditionalFact(nameof(HasOneSerialPort))]
InBreak()311         public void InBreak()
312         {
313             using (SerialPort com1 = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
314             {
315                 Debug.WriteLine("Verifying BeginWrite throws InvalidOperationException while in a Break");
316                 com1.Open();
317                 com1.BreakState = true;
318 
319                 Assert.Throws<InvalidOperationException>(() => com1.BaseStream.BeginWrite(new byte[8], 0, 8, null, null));
320             }
321         }
322         #endregion
323 
324         #region Verification for Test Cases
325 
VerifyWriteException(byte[] buffer, int offset, int count, Type expectedException)326         private void VerifyWriteException(byte[] buffer, int offset, int count, Type expectedException)
327         {
328             using (SerialPort com = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
329             {
330                 int bufferLength = null == buffer ? 0 : buffer.Length;
331 
332                 Debug.WriteLine("Verifying write method throws {0} buffer.Lenght={1}, offset={2}, count={3}",
333                     expectedException, bufferLength, offset, count);
334                 com.Open();
335 
336                 Assert.Throws(expectedException, () => com.BaseStream.BeginWrite(buffer, offset, count, null, null));
337             }
338         }
339 
VerifyWrite(byte[] buffer, int offset, int count)340         private void VerifyWrite(byte[] buffer, int offset, int count)
341         {
342             VerifyWrite(buffer, offset, count, new ASCIIEncoding());
343         }
344 
VerifyWrite(byte[] buffer, int offset, int count, int numWrites)345         private void VerifyWrite(byte[] buffer, int offset, int count, int numWrites)
346         {
347             VerifyWrite(buffer, offset, count, new ASCIIEncoding(), numWrites);
348         }
349 
VerifyWrite(byte[] buffer, int offset, int count, Encoding encoding)350         private void VerifyWrite(byte[] buffer, int offset, int count, Encoding encoding)
351         {
352             VerifyWrite(buffer, offset, count, encoding, DEFAULT_NUM_WRITES);
353         }
354 
VerifyWrite(byte[] buffer, int offset, int count, Encoding encoding, int numWrites)355         private void VerifyWrite(byte[] buffer, int offset, int count, Encoding encoding, int numWrites)
356         {
357             using (SerialPort com1 = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName))
358             using (SerialPort com2 = new SerialPort(TCSupport.LocalMachineSerialInfo.SecondAvailablePortName))
359             {
360                 Random rndGen = new Random(-55);
361 
362                 Debug.WriteLine("Verifying write method buffer.Lenght={0}, offset={1}, count={2}, endocing={3}",
363                     buffer.Length, offset, count, encoding.EncodingName);
364 
365                 com1.Encoding = encoding;
366                 com2.Encoding = encoding;
367 
368                 com1.Open();
369                 com2.Open();
370 
371                 for (int i = 0; i < buffer.Length; i++)
372                 {
373                     buffer[i] = (byte)rndGen.Next(0, 256);
374                 }
375 
376                 VerifyWriteByteArray(buffer, offset, count, com1, com2, numWrites);
377             }
378         }
379 
VerifyWriteByteArray(byte[] buffer, int offset, int count, SerialPort com1, SerialPort com2, int numWrites)380         private void VerifyWriteByteArray(byte[] buffer, int offset, int count, SerialPort com1, SerialPort com2, int numWrites)
381         {
382             int index = 0;
383             CallbackHandler callbackHandler = new CallbackHandler();
384 
385             var oldBuffer = (byte[])buffer.Clone();
386             var expectedBytes = new byte[count];
387             var actualBytes = new byte[expectedBytes.Length * numWrites];
388 
389             for (int i = 0; i < count; i++)
390             {
391                 expectedBytes[i] = buffer[i + offset];
392             }
393 
394             for (int i = 0; i < numWrites; i++)
395             {
396                 IAsyncResult writeAsyncResult = com1.BaseStream.BeginWrite(buffer, offset, count, callbackHandler.Callback, this);
397                 writeAsyncResult.AsyncWaitHandle.WaitOne();
398                 callbackHandler.BeginWriteAysncResult = writeAsyncResult;
399 
400                 com1.BaseStream.EndWrite(writeAsyncResult);
401 
402                 IAsyncResult callbackWriteAsyncResult = callbackHandler.WriteAysncResult;
403                 Assert.Equal(this, callbackWriteAsyncResult.AsyncState);
404                 Assert.False(callbackWriteAsyncResult.CompletedSynchronously, "Should not have completed sync (cback)");
405                 Assert.True(callbackWriteAsyncResult.IsCompleted, "Should have completed (cback)");
406                 Assert.Equal(this, writeAsyncResult.AsyncState);
407                 Assert.False(writeAsyncResult.CompletedSynchronously, "Should not have completed sync (write)");
408                 Assert.True(writeAsyncResult.IsCompleted, "Should have completed (write)");
409             }
410 
411             com2.ReadTimeout = 500;
412             Thread.Sleep((int)(((expectedBytes.Length * numWrites * 10.0) / com1.BaudRate) * 1000) + 250);
413 
414             // Make sure buffer was not altered during the write call
415             for (int i = 0; i < buffer.Length; i++)
416             {
417                 if (buffer[i] != oldBuffer[i])
418                 {
419                     Fail("ERROR!!!: The contents of the buffer were changed from {0} to {1} at {2}", oldBuffer[i], buffer[i], i);
420                 }
421             }
422 
423             while (true)
424             {
425                 int byteRead;
426                 try
427                 {
428                     byteRead = com2.ReadByte();
429                 }
430                 catch (TimeoutException)
431                 {
432                     break;
433                 }
434 
435                 if (actualBytes.Length <= index)
436                 {
437                     // If we have read in more bytes then we expect
438                     Fail("ERROR!!!: We have received more bytes then were sent");
439                     break;
440                 }
441 
442                 actualBytes[index] = (byte)byteRead;
443                 index++;
444                 if (actualBytes.Length - index != com2.BytesToRead)
445                 {
446                     Fail("ERROR!!!: Expected BytesToRead={0} actual={1}", actualBytes.Length - index, com2.BytesToRead);
447                 }
448             }
449 
450             // Compare the bytes that were read with the ones we expected to read
451             for (int j = 0; j < numWrites; j++)
452             {
453                 for (int i = 0; i < expectedBytes.Length; i++)
454                 {
455                     if (expectedBytes[i] != actualBytes[i + expectedBytes.Length * j])
456                     {
457                         Fail("ERROR!!!: Expected to read byte {0}  actual read {1} at {2}", (int)expectedBytes[i], (int)actualBytes[i + expectedBytes.Length * j], i);
458                     }
459                 }
460             }
461         }
462 
463         private class CallbackHandler
464         {
465             private IAsyncResult _writeAysncResult;
466             private IAsyncResult _beginWriteAysncResult;
467             private readonly SerialPort _com;
468 
CallbackHandler()469             public CallbackHandler() : this(null) { }
470 
CallbackHandler(SerialPort com)471             private CallbackHandler(SerialPort com)
472             {
473                 _com = com;
474             }
475 
Callback(IAsyncResult writeAysncResult)476             public void Callback(IAsyncResult writeAysncResult)
477             {
478                 Debug.WriteLine("About to enter callback lock (already entered {0})", Monitor.IsEntered(this));
479                 lock (this)
480                 {
481                     Debug.WriteLine("Inside callback lock");
482                     _writeAysncResult = writeAysncResult;
483 
484                     if (!writeAysncResult.IsCompleted)
485                     {
486                         throw new Exception("Err_23984afaea Expected IAsyncResult passed into callback to not be completed");
487                     }
488 
489                     while (null == _beginWriteAysncResult)
490                     {
491                         Assert.True(Monitor.Wait(this, 5000), "Monitor.Wait in Callback");
492                     }
493 
494                     if (null != _beginWriteAysncResult && !_beginWriteAysncResult.IsCompleted)
495                     {
496                         throw new Exception("Err_7907azpu Expected IAsyncResult returned from begin write to not be completed");
497                     }
498 
499                     if (null != _com)
500                     {
501                         _com.BaseStream.EndWrite(_beginWriteAysncResult);
502                         if (!_beginWriteAysncResult.IsCompleted)
503                         {
504                             throw new Exception("Err_6498afead Expected IAsyncResult returned from begin write to not be completed");
505                         }
506 
507                         if (!writeAysncResult.IsCompleted)
508                         {
509                             throw new Exception("Err_1398ehpo Expected IAsyncResult passed into callback to not be completed");
510                         }
511                     }
512 
513                     Monitor.Pulse(this);
514                 }
515             }
516 
517 
518             public IAsyncResult WriteAysncResult
519             {
520                 get
521                 {
522                     lock (this)
523                     {
524                         while (null == _writeAysncResult)
525                         {
526                             Monitor.Wait(this);
527                         }
528 
529                         return _writeAysncResult;
530                     }
531                 }
532             }
533 
534             public IAsyncResult BeginWriteAysncResult
535             {
536                 get
537                 {
538                     return _beginWriteAysncResult;
539                 }
540                 set
541                 {
542                     lock (this)
543                     {
544                         _beginWriteAysncResult = value;
545                         Monitor.Pulse(this);
546                     }
547                 }
548             }
549         }
550 
551         #endregion
552     }
553 }
554