source: PinConnection/UCommTelnet.pas

Last change on this file was 440, checked in by chronos, 12 years ago
  • Fixed: Thread safe access to serial port pin interface using lock.
File size: 14.6 KB
Line 
1unit UCommTelnet;
2
3{$mode delphi}{$H+}
4
5interface
6
7uses
8 Classes, SysUtils, UCommPin, SpecializedList, DateUtils, Dialogs,
9 UBinarySerializer;
10
11type
12 TCommTelnet = class;
13
14 TTelnetCode = (tcEOR = 239, tcSE = 240, tcNOP = 241, tcDATA_MARK = 242,
15 tcBREAK = 243, tcIP = 244, tcAO = 245, tcAYT = 246, tcEC = 247,
16 tcEL = 248, tcGA = 249, tcSB = 250, tcWILL = 251, tcWONT = 252,
17 tcDO = 253, tcDONT = 254, tcIAC = 255);
18
19 TTelnetCommand = (tmTransmitBinary = 0, tmEcho = 1, tmReconnection = 2,
20 tmSupressGoAhead = 3, tmApproxMessageSizeNegotiation = 4,
21 tmStatus = 5, tmTimingMark = 6,
22 tmRemoteControlledTransAndEcho = 7, tmOutputLineWidth = 8, tmOutputPageSize = 9,
23 tmOutputCarriageReturnDisposition = 10, tmOutputHorizontalTabStops = 11,
24 tmOutputHorizontalTabDisposition = 12, tmOutputFormfeedDisposition = 13,
25 tmOutputVerticalTabstops = 14, tmOutputVerticalTabDisposition = 15,
26 tmOutputLinefeedDisposition = 16, tmExtendedASCII = 17, tmLogout = 18,
27 tmByteMacro = 19, tmDataEntryTerminal = 20,
28 tmSUPDUP = 21, tmSUPDUPOutput = 22, tmSendLocation = 23, tmTerminalType = 24,
29 tmEndOfRecord = 25, tmTACACSUserIdentification = 26, tmOutputMarking = 27,
30 tmTerminalLocationNumber = 28, tmTelnet3270Regime = 29, tmX_3PAD = 30,
31 tmNegotiateAboutWindowSize = 31, tmTerminalSpeed = 32,
32 tmRemoteFlowControl = 33, tmLineMode = 34,
33 tmXDisplayLocation = 35, tmEnvironmentOption = 36,
34 tmAuthentication = 37, tmEncryptionOption = 38, tmNewEnvironmentOption = 39,
35 tmTN3270E = 40, tmXauth = 41,
36 tmCharset = 42, tmRSP = 43, tmComPortControlOption = 44,
37 tmTelnetSuppressLocalEcho = 45, tmTelnetStartTLS = 46,
38 rmKERMIT = 47, tmSendURL = 48, tmForwardX = 49);
39
40 TTelnetState = (tsNormal, tsIAC, tsSB, tsSB_IAC, tsOption);
41
42 TTelnetOptionEvent = procedure (Sender: TCommTelnet; Data: TStream) of object;
43
44 TTelnetType = (ttClient, ttServer);
45
46 { TTelnetOption }
47
48 TTelnetOption = class
49 private
50 FActive: Boolean;
51 FOnRequest: TTelnetOptionEvent;
52 protected
53 procedure SetActive(AValue: Boolean); virtual;
54 public
55 Telnet: TCommTelnet;
56 Code: TTelnetCommand;
57 ServerChecked: Boolean;
58 SupportedByServer: Boolean;
59 procedure Assign(Source: TTelnetOption); virtual;
60 function CheckOption: Boolean;
61 procedure SendCommand(Request, Response: TListByte);
62 property OnRequest: TTelnetOptionEvent read FOnRequest write FOnRequest;
63 property Active: Boolean read FActive write SetActive;
64 end;
65
66 TTelnetPortType = (ptClient, ptServer);
67
68 { TCommTelnet }
69
70 TCommTelnet = class(TCommNode)
71 private
72 FResponses: TListObject; // TListObject<TListByte>
73 FActive: Boolean;
74 FState: TTelnetState;
75 FCommandData: TBinarySerializer;
76 procedure TelnetDataReceive(Sender: TCommPin; Stream: TListByte);
77 procedure RawDataReceive(Sender: TCommPin; Stream: TListByte);
78 procedure ReadResponse(Response: TListByte);
79 function ResponseCount: Integer;
80 protected
81 procedure SetActive(const AValue: Boolean); override;
82 procedure AssignTo(Dest: TPersistent); override;
83 public
84 Options: TListObject;
85 TelnetPin: TCommPin;
86 RawPin: TCommPin;
87 Timeout: TDateTime;
88 PortType: TTelnetPortType;
89 ErrorCount: Integer;
90 OptionsNegotationEnable: Boolean;
91 procedure Register(Option: TTelnetOption);
92 procedure Unregister(Option: TTelnetOption);
93 function CheckOption(OptionCode: TTelnetCommand): Boolean;
94 function SearchOption(OptionCode: TTelnetCommand): TTelnetOption;
95 procedure SendSubCommand(OptionCode: TTelnetCommand; Request, Response: TListByte);
96 procedure SendCommand(Code: TTelnetCode; Request, Response: TListByte);
97 procedure Purge;
98 constructor Create(AOwner: TComponent); override;
99 destructor Destroy; override;
100 property Active: Boolean read FActive write SetActive;
101 end;
102
103
104implementation
105
106resourcestring
107 SUnknownState = 'Unknown state';
108 SWrongResponseOption = 'Wrong response option';
109 SWrongResponseCode = 'Wrong response code';
110 SWrongResponse = 'Wrong response';
111 SOptionNotFound = 'Option not found';
112 STimeout = 'Telnet command timout';
113
114{ TTelnetOption }
115
116procedure TTelnetOption.SetActive(AValue: Boolean);
117begin
118 if FActive = AValue then Exit;
119 FActive := AValue;
120end;
121
122procedure TTelnetOption.Assign(Source: TTelnetOption);
123begin
124 SupportedByServer := Source.SupportedByServer;
125 ServerChecked := Source.ServerChecked;
126 Code := Source.Code;
127 FOnRequest := Source.FOnRequest;
128 Active := Source.FActive;
129end;
130
131function TTelnetOption.CheckOption: Boolean;
132var
133 RequestData: TBinarySerializer;
134 ResponseData: TBinarySerializer;
135begin
136 if not ServerChecked then
137 try
138 RequestData := TBinarySerializer.Create;
139 RequestData.List := TListByte.Create;
140 RequestData.OwnsList := True;
141 ResponseData := TBinarySerializer.Create;
142 ResponseData.List := TListByte.Create;
143 ResponseData.OwnsList := True;
144
145 RequestData.WriteByte(Byte(Code));
146 Telnet.SendCommand(tcDo, RequestData.List, ResponseData.List);
147 if ResponseData.List[0] = Byte(tcWILL) then SupportedByServer := True
148 else if ResponseData.List[0] = Byte(tcWONT) then SupportedByServer := False
149 else raise Exception.Create(SWrongResponse);
150 ServerChecked := True;
151 finally
152 RequestData.Free;
153 RequestData.Free;
154 end;
155 Result := SupportedByServer;
156end;
157
158procedure TTelnetOption.SendCommand(Request, Response: TListByte);
159var
160 RequestData: TBinarySerializer;
161 ResponseData: TBinarySerializer;
162begin
163 if Telnet.OptionsNegotationEnable then CheckOption;
164 try
165 RequestData := TBinarySerializer.Create;
166 RequestData.List := TListByte.Create;
167 RequestData.OwnsList := True;
168 ResponseData := TBinarySerializer.Create;
169 ResponseData.List := TListByte.Create;
170 ResponseData.OwnsList := True;
171
172 RequestData.WriteByte(Byte(Code));
173 RequestData.WriteList(Request, 0, Request.Count);
174 if Assigned(Response) then begin
175 Telnet.SendCommand(tcSB, RequestData.List, ResponseData.List);
176 if ResponseData.List[0] <> Byte(Code) then
177 raise Exception.Create(SWrongResponseOption);
178 ResponseData.List.Delete(0);
179 Response.Assign(ResponseData.List);
180 end else Telnet.SendCommand(tcSB, RequestData.List, nil);
181 finally
182 RequestData.Free;
183 ResponseData.Free;
184 end;
185end;
186
187{ TCommTelnet }
188
189procedure TCommTelnet.TelnetDataReceive(Sender: TCommPin; Stream: TListByte);
190var
191 Data: Byte;
192 RawData: TBinarySerializer;
193 I: Integer;
194begin
195 try
196 RawData := TBinarySerializer.Create;
197 RawData.List := TListByte.Create;
198 RawData.OwnsList := True;
199 for I := 0 to Stream.Count - 1 do begin
200 Data := Stream[I];
201 if Data = Byte(tcIAC) then begin
202 RawData.WriteByte(Byte(tcIAC));
203 RawData.WriteByte(Byte(tcIAC));
204 end else RawData.WriteByte(Data);
205 end;
206 RawPin.Send(RawData.List);
207 finally
208 RawData.Free;
209 end;
210end;
211
212procedure TCommTelnet.SetActive(const AValue: Boolean);
213var
214 I: Integer;
215begin
216 if FActive = AValue then Exit;
217 FActive := AValue;
218 for I := 0 to Options.Count - 1 do
219 with TTelnetOption(Options[I]) do begin
220 if (not ServerChecked) and OptionsNegotationEnable then CheckOption;
221 Active := AValue;
222 end;
223end;
224
225procedure TCommTelnet.RawDataReceive(Sender: TCommPin; Stream: TListByte);
226var
227 Data: Byte;
228 RawData: TBinarySerializer;
229 I: Integer;
230begin
231 try
232 RawData := TBinarySerializer.Create;
233 RawData.List := TListByte.Create;
234 RawData.OwnsList := True;
235
236 for I := 0 to Stream.Count - 1 do begin
237 Data := Stream[I];
238 if FState = tsNormal then begin
239 if Data = Byte(tcIAC) then begin
240 FCommandData.Clear;
241 FState := tsIAC;
242 end else RawData.WriteByte(Data);
243 end else
244 if FState = tsIAC then begin
245 if Data = Byte(tcIAC) then begin
246 RawData.WriteByte(Data);
247 FState := tsNormal;
248 end else
249 if Data = Byte(tcSB) then begin
250 // Subnegotation
251 FCommandData.WriteByte(Data);
252 FState := tsSB;
253 end else
254 if (Data = Byte(tcWILL)) or (Data = Byte(tcDONT)) or (Data = Byte(tcWONT)) or (Data = Byte(tcDO))
255 then begin
256 // Three byte negotation commands
257 FCommandData.WriteByte(Data);
258 FState := tsOption;
259 end else
260 if (Data = Byte(tcAYT)) or (Data = Byte(tcNOP)) or (Data = Byte(tcGA)) or
261 (Data = Byte(tcEL)) or (Data = Byte(tcEC)) or (Data = Byte(tcAO)) or
262 (Data = Byte(tcIP)) or (Data = Byte(tcBREAK)) or (Data = Byte(tcDATA_MARK)) or
263 (Data = Byte(tcEOR)) then begin
264 // Two byte commands
265 FCommandData.WriteByte(Data);
266 FResponses.AddNew(TListByte.Create);
267 TListByte(FResponses.Last).Assign(FCommandData.List); FState := tsNormal;
268 end else
269 FState := tsNormal;
270 end else
271 if FState = tsSB then begin
272 // Data inside subnegotation
273 if Data = Byte(tcIAC) then FState := tsSB_IAC
274 else FCommandData.WriteByte(Data);
275 end else
276 if FState = tsSB_IAC then begin
277 // End of subnegotation data
278 if Data = Byte(tcSE) then begin
279 FResponses.AddNew(TListByte.Create);
280 TListByte(FResponses.Last).Assign(FCommandData.List);
281 FState := tsNormal;
282 end else begin
283 Inc(ErrorCount);
284 FState := tsNormal;
285 end;
286 end else
287 if FState = tsOption then begin
288 // Third byte of negotation
289 FCommandData.WriteByte(Data);
290 FResponses.AddNew(TListByte.Create);
291 TListByte(FResponses.Last).Assign(FCommandData.List);
292 FState := tsNormal;
293 end else raise Exception.Create(SUnknownState);
294 end;
295 TelnetPin.Send(RawData.List);
296 finally
297 RawData.Free;
298 end;
299end;
300
301procedure TCommTelnet.ReadResponse(Response: TListByte);
302var
303 TimeStart: TDateTime;
304 ElapsedTime: TDateTime;
305begin
306 TimeStart := Now;
307 repeat
308 ElapsedTime := Now - TimeStart;
309 until (ElapsedTime > Timeout) or (ResponseCount > 0);
310 if ElapsedTime > Timeout then
311 raise Exception.Create(STimeout);
312 Response.Assign(TListByte(FResponses.First));
313 FResponses.Delete(0);
314end;
315
316function TCommTelnet.ResponseCount: Integer;
317begin
318 Result := FResponses.Count;
319end;
320
321procedure TCommTelnet.AssignTo(Dest: TPersistent);
322var
323 I: Integer;
324begin
325 if Dest is TCommTelnet then begin
326 TCommTelnet(Dest).Timeout := Timeout;
327 TCommTelnet(Dest).PortType := PortType;
328 TCommTelnet(Dest).ErrorCount := ErrorCount;
329 TCommTelnet(Dest).OptionsNegotationEnable := OptionsNegotationEnable;
330 for I := 0 to Options.Count - 1 do begin
331 TTelnetOption(TCommTelnet(Dest).Options[I]).Assign(TTelnetOption(Options[I]));
332 TTelnetOption(TCommTelnet(Dest).Options[I]).Telnet := TCommTelnet(Dest);
333 end;
334 end else inherited;
335end;
336
337procedure TCommTelnet.Register(Option: TTelnetOption);
338begin
339 Option.Telnet := Self;
340 Options.Add(Option);
341end;
342
343procedure TCommTelnet.Unregister(Option: TTelnetOption);
344begin
345 Options.Remove(Option);
346end;
347
348function TCommTelnet.CheckOption(OptionCode: TTelnetCommand): Boolean;
349var
350 Option: TTelnetOption;
351begin
352 Option := SearchOption(OptionCode);
353 if Assigned(Option) then Result := Option.CheckOption
354 else raise Exception.Create(SOptionNotFound);
355end;
356
357function TCommTelnet.SearchOption(OptionCode: TTelnetCommand): TTelnetOption;
358var
359 I: Integer;
360begin
361 I := 0;
362 while (I < Options.Count) and (TTelnetOption(Options[I]).Code <> OptionCode) do
363 Inc(I);
364 if I < Options.Count then Result := TTelnetOption(Options[I])
365 else Result := nil;
366end;
367
368procedure TCommTelnet.SendSubCommand(OptionCode: TTelnetCommand; Request,
369 Response: TListByte);
370var
371 Option: TTelnetOption;
372begin
373 Option := SearchOption(OptionCode);
374 if Assigned(Option) then Option.SendCommand(Request, Response)
375 else raise Exception.Create(SOptionNotFound);
376end;
377
378function ByteToStr(Value: Byte): string;
379begin
380 Result := IntToStr(Value);
381end;
382
383procedure TCommTelnet.SendCommand(Code: TTelnetCode; Request,
384 Response: TListByte);
385var
386 Data: TBinarySerializer;
387 LastIAC: Boolean;
388 I: Integer;
389begin
390 try
391 Data := TBinarySerializer.Create;
392 Data.List := TListByte.Create;
393 Data.OwnsList := True;
394 Data.WriteByte(Byte(tcIAC));
395 Data.WriteByte(Byte(Code));
396 for I := 0 to Request.Count - 1 do begin
397 if Request[I] = Byte(tcIAC) then Data.WriteByte(Byte(tcIAC));
398 Data.WriteByte(Request[I]);
399 end;
400 if Code = tcSB then begin
401 Data.WriteByte(Byte(tcIAC));
402 Data.WriteByte(Byte(tcSE));
403 end;
404 RawPin.Send(Data.List);
405 if Assigned(Response) then begin
406 ReadResponse(Response);
407 if Response[0] <> Byte(Code) then
408 raise Exception.Create(SWrongResponseCode);
409 Response.Delete(0);
410 if Code = tcSB then begin
411 if (Response[Response.Count - 2] <> Byte(tcIAC)) or
412 (Response[Response.Count - 1] <> Byte(tcSE)) then begin
413 ShowMessage(Response.Implode(' ', ByteToStr));
414 ReadResponse(Response);
415 ShowMessage(Response.Implode(' ', ByteToStr));
416 raise Exception.Create(SWrongResponse);
417 end;
418 Response.DeleteItems(Response.Count - 2, 2);
419 end;
420 // Remove IAC escape character from data
421 I := 0;
422 LastIAC := False;
423 while (I < Response.Count) do begin
424 if Response[I] = Byte(tcIAC) then begin
425 if not LastIAC then LastIAC := True
426 else begin
427 LastIAC := False;
428 Response.Delete(I);
429 Dec(I);
430 end;
431 end;
432 Inc(I);
433 end;
434 end;
435 finally
436 Data.Free;
437 end;
438end;
439
440procedure TCommTelnet.Purge;
441begin
442 FState := tsNormal;
443 FResponses.Clear;
444end;
445
446constructor TCommTelnet.Create(AOwner: TComponent);
447begin
448 inherited;
449 FResponses := TListObject.Create;
450 FCommandData := TBinarySerializer.Create;
451 FCommandData.List := TListByte.Create;
452 FCommandData.OwnsList := True;
453 TelnetPin := TCommPin.Create;
454 TelnetPin.OnReceive := TelnetDataReceive;
455 TelnetPin.Node := Self;
456 RawPin := TCommPin.Create;
457 RawPin.OnReceive := RawDataReceive;
458 RawPin.Node := Self;
459 Options := TListObject.Create;
460 Options.OwnsObjects := False;
461 Timeout := 2 * OneSecond;
462end;
463
464destructor TCommTelnet.Destroy;
465begin
466 FreeAndNil(FCommandData);
467 FreeAndNil(Options);
468 FreeAndNil(TelnetPin);
469 FreeAndNil(RawPin);
470 FreeAndNil(FResponses);
471 inherited Destroy;
472end;
473
474end.
475
Note: See TracBrowser for help on using the repository browser.