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 // Notes on test capabilities:
6 //
7 // A single port just means there's at least one port, which can be opened by the test application
8 // A loopback port is a port where the DATA is looped back, but the HANDSHAKE is not.   Several tests
9 // rely on enabling RTS/CTS handshake to block transmissions from the port, but if RTS/CTS is looped-back
10 // the port will never block and the tests will either hang or fail (we could perhaps detect this when probing for loopback ports)
11 // A null-modem connection is available when you have at least two openable ports, where some pair of ports is connected with
12 // a null modem connection - i.e. TX/RX and CTS/RTS are crossed between the ports.
13 
14 using System;
15 using System.Diagnostics;
16 using System.IO;
17 using System.IO.Ports;
18 using System.Threading;
19 using System.Threading.Tasks;
20 using Xunit.Sdk;
21 
22 namespace Legacy.Support
23 {
24     public class SerialPortConnection
25     {
VerifyConnection(string portName1, string portName2)26         public static bool VerifyConnection(string portName1, string portName2)
27         {
28             using (SerialPort com1 = new SerialPort(portName1, 115200))
29             using (SerialPort com2 = new SerialPort(portName2, 115200))
30             {
31                 bool connectionVerified;
32                 try
33                 {
34                     com1.Open();
35                     com2.Open();
36                     connectionVerified = VerifyReadWrite(com1, com2);
37                 }
38                 catch (Exception)
39                 {
40                     // One of the com ports does not exist on the machine that this is being run on
41                     // thus their can not be a connection between com1 and com2
42                     connectionVerified = false;
43                 }
44                 return connectionVerified;
45             }
46         }
47 
VerifyLoopback(string portName)48         public static bool VerifyLoopback(string portName)
49         {
50             using (SerialPort com = new SerialPort(portName, 115200))
51             {
52                 bool loopbackVerified;
53                 try
54                 {
55                     com.Open();
56                     loopbackVerified = VerifyReadWrite(com, com);
57                 }
58                 catch (Exception)
59                 {
60                     // The com ports does not exist on the machine that this is being run on
61                     // thus their can not be a loopback between the ports
62                     loopbackVerified = false;
63                 }
64                 return loopbackVerified;
65             }
66         }
67 
VerifyReadWrite(SerialPort com1, SerialPort com2)68         private static bool VerifyReadWrite(SerialPort com1, SerialPort com2)
69         {
70             try
71             {
72                 com1.ReadTimeout = 1000;
73                 com2.ReadTimeout = 1000;
74                 com1.WriteTimeout = 1000;
75                 com2.WriteTimeout = 1000;
76 
77                 com1.WriteLine("Ping");
78 
79                 if ("Ping" != com2.ReadLine())
80                 {
81                     return false;
82                 }
83 
84                 com2.WriteLine("Response");
85 
86                 if ("Response" != com1.ReadLine())
87                 {
88                     return false;
89                 }
90 
91                 return true;
92             }
93             catch (TimeoutException)
94             {
95                 return false;
96             }
97         }
98 
99         /// <summary>
100         /// Not all types of serial ports block transmission completely when they're trying to transmit
101         /// with flow-control set against them.  Some ports transmit a certain amount of data onward to
102         /// the hardware before stopping, so if you want them to block, you need to send more than
103         /// this minimum of data
104         ///
105         /// This routine performs a series of probes of the behaviour of a port, to establish how much
106         /// data is needed - the length of the probe packet is doubled on each probe up to a maximum of 8192 bytes
107         ///
108         /// A traditional 16550 UART port will block on the first byte.  Some FTDI USB-Serial devices need more than 4096 bytes before
109         /// they block
110         /// </summary>
111         /// <returns>The smallest probe </returns>
MeasureFlowControlCapabilities(string portName)112         public static FlowControlCapabilities MeasureFlowControlCapabilities(string portName)
113         {
114             for (int probeBase = 1; probeBase <= 65536; probeBase *= 2)
115             {
116                 // We always probe one over the powers of two to make sure we just exceed common buffer sizes
117                 int probeLength;
118                 probeLength = probeBase + 1;
119                 int bufferSize = MeasureTransmitBufferSize(portName, probeLength);
120                 if (bufferSize < probeLength)
121                 {
122                     Debug.WriteLine("{0}: Found blocking packet of length {1}, hardware buffer {2}", portName, probeLength, bufferSize);
123                     return new FlowControlCapabilities(probeLength, bufferSize, true);
124                 }
125             }
126 
127             Debug.WriteLine("Failed to achieve write blocking on serial port - no hardware flow-control available");
128             return new FlowControlCapabilities(0, -1, false);
129         }
130 
131         /// <summary>
132         /// Measure the amount of data which can be written to a blocked serial port before it
133         /// starts to queue-up
134         /// </summary>
MeasureTransmitBufferSize(string portName, int probeLength)135         private static int MeasureTransmitBufferSize(string portName, int probeLength)
136         {
137             int measuredHardwareCapacity = 0;
138             using (var com = new SerialPort(portName, 115200))
139             {
140                 com.Handshake = Handshake.RequestToSend;
141                 com.WriteTimeout = 2000;
142                 com.Open();
143                 com.DiscardOutBuffer();
144 
145                 var testBlock = new byte[probeLength];
146 
147                 Task t = Task.Run(() =>
148                 {
149                     int lastHardwareCapacity = -1;
150                     for (int i = 0; i < 10; i++)
151                     {
152                         int queuedLength = com.BytesToWrite;
153                         int hardwareCapacity = testBlock.Length - queuedLength;
154 
155                         if (hardwareCapacity == lastHardwareCapacity)
156                         {
157                             // We've had two readings the same
158                             measuredHardwareCapacity = hardwareCapacity;
159                             break;
160                         }
161                         // We're still pushing stuff out - wait for two readings the same
162                         lastHardwareCapacity = hardwareCapacity;
163                         Thread.Sleep(10);
164                     }
165                     com.Handshake = Handshake.None;
166                     com.DiscardOutBuffer();
167                 });
168 
169                 try
170                 {
171                     com.Write(testBlock, 0, testBlock.Length);
172                 }
173                 catch (IOException)
174                 {
175                     // We may see hardware exceptions when the task calls Discard
176                 }
177 
178                 t.Wait(2000);
179             }
180             return measuredHardwareCapacity;
181         }
182     }
183 }
184