You may remember Kara, who recently found some "interesting" serialization code. Now, this code happens to be responsible for sending commands to pieces of machine equipment.
Low-level machine interfaces remain one of the domains where serial protocols rule. Serial communications use simple hardware and have minimal overhead, and something like RS232 has been in real-world use since the 60s. Sure, it's slow, sure it's not great with coping with noise, sure you have to jump through some hoops if you want a connection longer than 15m, but its failures are well understood.
Nothing is so well understood that some developer can't make a mess of it.
Public Function SendCommand(ByVal cmd As String) As String
Dim status As Integer
' Write cmd to the serial port using a protocol that is too painful to reproduce here.
' status receives an appropriate value along the way as the protocol checks for various error
' conditions including timeout
If status <> 0 Then Throw MakeComPortException(status)
End Function
Private Function MakeComPortException(ByVal status As Integer) As ComPortException
Dim code As Integer
Dim message As String = Nothing
GetErrorCode(status, code, message)
Return New ComPortException(code, message)
End Function
Private Sub GetErrorCode(ByVal ErrorNum As Integer, ByRef code As Integer, ByRef message As String)
code = ErrorNum
Select Case ErrorNum
Case 129 : message = "Hardware error occured during Send Data" ' Talk Error'
Case 130 : message = "System asked to talk but did not recieve Previous Talk Command" ' Nothing to say
Case 131 :
SendCommand("ERRMS?")
Dim EMsg As String = GetResponse()
Dim EmsgStart As Integer = EMsg.IndexOf(" (")
Try
If EMsg.Contains("ERR=") Then code = CInt(EMsg.Substring(4, EmsgStart - 4))
Catch ex As Exception
End Try
message = EMsg.Substring(EmsgStart)
Case 132 : message = "H/W Error while system trying to accept data" 'Listen Error
Case 133 : message = "More than 80 characters received before term char"
Case 134 : message = "Archive media is full"
Case 135 : message = "Listen state interrupted by ESC key" ' Interrupted from keyboard
Case 136 : message = "Listen state interrupted by controller sending '*'" ' Interrupted by Controller
Case 137 : message = "Error Occured in UART"
Case StatusCodes.PortDeviceNotFoundErrorCode : message = "No device Found"
Case StatusCodes.PortTimeoutErrorCode : message = "COM port Timeout Error"
' This next occurs if cable is unplugged at controller
Case StatusCodes.PortDisconnectedErrorCode : message = "Serial cable disconnected"
Case Else : message = "Error #: " & ErrorNum
End Select
End Sub
So, the SendCommand
method takes a string and passes it down the serial port. The protocol details were elided here, but we know that we receive a status number. MakeComPortException
takes that number and helpfully looks up the message which goes with it, using GetErrorCode
.
GetErrorCode
is one gigantic switch statement. And let's pay close attention to the message lookup process for error 131
. You'll note that we call SendCommand
to ask the remote device to tell us what the error message was. But in some cases, it's going to reply to that request with an error code. Error 131
, to be exact.
So if we trace this: we call SendCommand
which gets a 131
error, which forces it to throw
the results of calling MakeComPortException
, which calls GetErrorCode
, which calls SendCommand
, which throws a new MakeComPortException
, which…
There's an interesting side effect of this approach. Despite looking like a series of recursive calls, throw
unwinds the stack, so this code will never actually trigger a stack overflow. It's actually more of an exception-assisted infinite loop.
For a bonus, note the PortTimeoutErrorCode
entry. On the hardware side, they use a custom serial cable which wires a loopback on the RS232 "Ready to Send" and "Clear to Send" pins, which the software uses to detect that the cable is unplugged. It also has the side effect of ensuring that no off-the-shelf RS232 cables will work with the software. This is either a stupid mistake, or a fiendishly clever way to sell heavily marked-up replacement cables.