11Imports System.IO
22Imports System.Net.Sockets
33
4+ ''' <summary>
5+ ''' Manages low-level interaction with an endpoint communicating in the NUT protocol (upsd).
6+ ''' Passes up most encountered exceptions, while resetting its state if necessary.
7+ ''' </summary>
48Public Class Nut_Socket
59
10+ Private Const TIMEOUT_MS = 5000
11+ Private ReadOnly NUT_CHARENCODING As Text.Encoding = Text.Encoding.ASCII
12+
613# Region "Properties"
714 Public ReadOnly Property ConnectionStatus As Boolean
815 Get
@@ -53,47 +60,48 @@ Public Class Nut_Socket
5360 Throw New InvalidOperationException( "Host and Port must be specified to connect." )
5461 End If
5562
63+ LogFile.LogTracing( String .Format( "Attempting TCP socket connection to {0}:{1}..." , Host, Port), LogLvl.LOG_NOTICE, Me )
64+
5665 Try
57- LogFile.LogTracing( String .Format( "Attempting TCP socket connection to {0}:{1}..." , Host, Port), LogLvl.LOG_NOTICE, Me )
66+ client = New TcpClient(Host, Port) With
67+ {
68+ .SendTimeout = TIMEOUT_MS,
69+ .ReceiveTimeout = TIMEOUT_MS
70+ }
5871
59- client = New TcpClient(Host, Port)
6072 NutStream = client.GetStream()
61- ReaderStream = New StreamReader(NutStream)
62- WriterStream = New StreamWriter(NutStream)
73+ ReaderStream = New StreamReader(NutStream, NUT_CHARENCODING )
74+ WriterStream = New StreamWriter(NutStream, NUT_CHARENCODING )
6375
6476 LogFile.LogTracing( "Connection established and streams ready." , LogLvl.LOG_NOTICE, Me )
65-
6677 LogFile.LogTracing( "Gathering basic info about the NUT server..." , LogLvl.LOG_DEBUG, Me )
6778
6879 Try
80+ 'Response: Network UPS Tools upsd 2.8.1 - https://www.networkupstools.org/
6981 Dim Nut_Query = Query_Data( "VER" )
70-
71- If Nut_Query.ResponseType = NUTResponse.OK Then
72- _NUTVersion = (Nut_Query.RawResponse.Split( " "c ))( 4 )
73- LogFile.LogTracing( "Server version: " & NUTVersion, LogLvl.LOG_NOTICE, Me )
74- End If
82+ _NUTVersion = Nut_Query.RawResponse
83+ LogFile.LogTracing( "Server version: " & NUTVersion, LogLvl.LOG_NOTICE, Me )
7584 Catch nutEx As NutException
7685 LogFile.LogTracing( "Error retrieving server version." , LogLvl.LOG_WARNING, Me )
7786 LogFile.LogException(nutEx, Me )
7887 End Try
7988
8089 Try
8190 Dim Nut_Query = Query_Data( "NETVER" )
82-
83- If Nut_Query.ResponseType = NUTResponse.OK Then
84- _NetVersion = Nut_Query.RawResponse
85- LogFile.LogTracing( "Protocol version: " & NetVersion, LogLvl.LOG_NOTICE, Me )
86- End If
91+ _NetVersion = Nut_Query.RawResponse
92+ LogFile.LogTracing( "Protocol version: " & NetVersion, LogLvl.LOG_NOTICE, Me )
8793 Catch nutEx As NutException
8894 LogFile.LogTracing( "Error retrieving protocol version." , LogLvl.LOG_WARNING, Me )
8995 LogFile.LogException(nutEx, Me )
9096 End Try
91-
92- LogFile.LogTracing( "Completed gathering basic info about NUT server ." , LogLvl.LOG_DEBUG, Me )
93- Catch Excep As Exception
97+ Catch ex As Exception
98+ LogFile.LogTracing( "Error connecting socket ." , LogLvl.LOG_DEBUG, Me )
99+ LogFile.LogException(ex, Me )
94100 Disconnect( True )
95- Throw ' Pass exception on up to UPS
101+ Throw
96102 End Try
103+
104+ LogFile.LogTracing( "Completed gathering basic info about NUT server." , LogLvl.LOG_DEBUG, Me )
97105 End Sub
98106
99107 Public Sub Login()
@@ -145,13 +153,34 @@ Public Class Nut_Socket
145153 End Sub
146154
147155 ''' <summary>
148- ''' Attempt to send a query to the NUT server, and do some basic parsing.
156+ ''' React to a hard error while using the underlying Socket, and make sure this object is left in a consistent state.
157+ ''' </summary>
158+ ''' <exception cref="Exception">Any exception <see cref="Query_Data(String)"/> can throw.</exception>
159+ ''' <param name="ex"></param>
160+ Private Sub OnSocketBroken(ex As Exception)
161+ LogFile.LogTracing( "Socket breaking." , LogLvl.LOG_DEBUG, Me )
162+ Disconnect( True )
163+ RaiseEvent Socket_Broken()
164+ If ex IsNot Nothing Then
165+ LogFile.LogException(ex, Me )
166+ Throw ex
167+ End If
168+ End Sub
169+
170+
171+ ''' <summary>
172+ ''' Synchronously send a query to the NUT server and collect the response. This method will throw all exceptions,
173+ ''' including NUT protocol (ERR) responses.
149174 ''' </summary>
150175 ''' <param name="Query_Msg">The query to be sent to the server, within specifications of the NUT protocol.</param>
151176 ''' <returns>The full <see cref="Transaction"/> of this function call.</returns>
152177 ''' <exception cref="InvalidOperationException">Thrown when calling this function while disconnected, or another
153178 ''' call is in progress.</exception>
154179 ''' <exception cref="NutException">Thrown when the NUT server returns an error or unexpected response.</exception>
180+ ''' <exception cref="ObjectDisposedException"></exception>
181+ ''' <exception cref="IOException">Attempted to read or write to a stream in an invalid state. </exception>
182+ ''' <exception cref="EndOfStreamException">An empty response was encountered, meaning the end of the stream.
183+ ''' This likely indicates that the server closed the connection.</exception>
155184 Function Query_Data(Query_Msg As String ) As Transaction
156185 If Not ConnectionStatus Then
157186 Throw New InvalidOperationException( "Attempted to send query " & Query_Msg & " while disconnected." )
@@ -165,47 +194,52 @@ Public Class Nut_Socket
165194 streamInUse = True
166195 WriterStream.WriteLine(Query_Msg)
167196 WriterStream.Flush()
168- Catch
169- Throw
197+ Catch ex As Exception
198+ LogFile.LogTracing( "Error writing to Stream." , LogLvl.LOG_ERROR, Me )
199+ OnSocketBroken(ex)
170200 Finally
171201 streamInUse = False
172202 End Try
173203
174- Dim responseEnum = NUTResponse.EMPTY
175- Dim response = ReaderStream.ReadLine()
204+ Dim response As String = Nothing
205+ Dim responseEnum As NUTResponse
206+ Dim splitResponse As String () = Nothing
207+
208+ Try
209+ response = ReaderStream.ReadLine()
210+ Catch ex As Exception
211+ LogFile.LogTracing( "Error reading from Stream." , LogLvl.LOG_ERROR, Me )
212+ OnSocketBroken(ex)
213+ End Try
176214
177215 If String .IsNullOrEmpty(response) Then
178216 ' End of stream reached, likely server terminated connection.
179- Disconnect( True )
180- RaiseEvent Socket_Broken()
217+ OnSocketBroken( New EndOfStreamException( "Server terminated connection." ))
181218 Else
182- Dim parseResponse = response.Trim().ToUpper(). Split( " "c ) ' TODO: Is Trim unnecessary?
219+ splitResponse = response.Split({ " "c }, 4 )
183220
184- Select Case parseResponse (0 )
221+ Select Case splitResponse (0 )
185222 Case "OK" , "VAR" , "DESC" , "UPS"
186223 responseEnum = NUTResponse.OK
187224 Case "BEGIN"
188225 responseEnum = NUTResponse.BEGINLIST
189226 Case "END"
190227 responseEnum = NUTResponse.ENDLIST
191- Case "NETWORK " , "1.0" , "1.1" , "1.2" , "1.3"
228+ Case "Network " , "1.0" , "1.1" , "1.2" , "1.3"
192229 'In case of "VER" or "NETVER" Query
193230 responseEnum = NUTResponse.OK
194231 Case "ERR"
195232 responseEnum = DirectCast ([Enum].Parse( GetType (NUTResponse),
196- parseResponse( 1 ).Replace( "-" , String .Empty)), NUTResponse)
233+ splitResponse( 1 ).Replace( "-" , String .Empty)), NUTResponse)
234+ LogFile.LogTracing( $"Parsed error response: { responseEnum }" , LogLvl.LOG_DEBUG, Me )
235+ Throw New NutException( New Transaction(Query_Msg, response, responseEnum, splitResponse))
197236 Case Else
198- responseEnum = NUTResponse.UNRECOGNIZED
237+ LogFile.LogTracing( $"Unrecognized response while parsing: { response }" , LogLvl.LOG_ERROR, Me )
238+ Throw New NutException( New Transaction(Query_Msg, response, NUTResponse.UNRECOGNIZED, splitResponse))
199239 End Select
200240 End If
201241
202- Dim transaction = New Transaction(Query_Msg, response, responseEnum)
203-
204- If responseEnum = NUTResponse.OK OrElse responseEnum = NUTResponse.BEGINLIST OrElse responseEnum = NUTResponse.ENDLIST Then
205- Return transaction
206- End If
207-
208- Throw New NutException(transaction)
242+ Return New Transaction(Query_Msg, response, responseEnum, splitResponse)
209243 End Function
210244
211245 Public Function Query_List_Datas(Query_Msg As String ) As List( Of UPS_List_Datas)
0 commit comments