source: trunk/RouterOSAPI.pas

Last change on this file was 1, checked in by george, 15 years ago
  • Initial import.
File size: 16.7 KB
Line 
1{*******************************************************************************
2
3Author: Pavel Skuratovich (aka Chupaka), Minsk, Belarus
4Description: Implementation of MikroTik RouterOS API Client
5Version: 1.0
6E-Mail: chupaka@gmail.com
7Support: http://forum.mikrotik.com/viewtopic.php?t=31555
8Legal issues: Copyright © by Pavel Skuratovich
9
10 This source code is provided 'as-is', without any express or
11 implied warranty. In no event will the author be held liable
12 for any damages arising from the use of this software.
13
14 Permission is granted to anyone to use this software for any
15 purpose, including commercial applications, and to alter it
16 and redistribute it freely, subject to the following
17 restrictions:
18
19 1. The origin of this software must not be misrepresented,
20 you must not claim that you wrote the original software.
21 If you use this software in a product, an acknowledgment
22 in the product documentation would be appreciated but is
23 not required.
24
25 2. Altered source versions must be plainly marked as such, and
26 must not be misrepresented as being the original software.
27
28 3. This notice may not be removed or altered from any source
29 distribution.
30
31********************************************************************************
32
33Version history:
341.0 May 1, 2009
35 First public release
36
370.1 April 18, 2009
38 Unit was rewritten to implement database-like interface
39
400.0 May 10, 2008
41 The beginning
42
43*******************************************************************************}
44
45unit RouterOSAPI;
46
47interface
48
49uses
50 SysUtils, Classes, StrUtils, blcksock, synautil, synsock, synacode;
51
52type
53 TRosApiWord = record
54 Name,
55 Value: String;
56 end;
57
58 TRosApiSentence = array of TROSAPIWord;
59
60 TRosApiClient = class;
61
62 TRosApiResult = class
63 private
64 Client: TROSAPIClient;
65 Tag: String;
66 Sentences: array of TRosApiSentence;
67 FTrap: Boolean;
68 FTrapMessage: String;
69 FDone: Boolean;
70
71 constructor Create;
72
73 function GetValueByName(const Name: String): String;
74 function GetValues: TRosApiSentence;
75 function GetEof: Boolean;
76 function GetRowsCount: Integer;
77 public
78 property ValueByName[const Name: String]: String read GetValueByName; default;
79 property Values: TRosApiSentence read GetValues;
80 function GetOne(const Wait: Boolean): Boolean;
81 function GetAll: Boolean;
82
83 property RowsCount: Integer read GetRowsCount;
84
85 property Eof: Boolean read GetEof;
86 property Trap: Boolean read FTrap;
87 property Done: Boolean read FDone;
88 procedure Next;
89
90 procedure Cancel;
91 end;
92
93 TRosApiClient = class
94 private
95 FNextTag: Cardinal;
96 FSock: TTCPBlockSocket;
97 FTimeout: Integer;
98
99 FLastError: String;
100
101 Sentences: array of TRosApiSentence;
102
103 function SockRecvByte(out b: Byte; const Wait: Boolean = True): Boolean;
104 function SockRecvBufferStr(Length: Cardinal): String;
105
106 procedure SendWord(s: String);
107
108 function RecvWord(const Wait: Boolean; out w: String): Boolean;
109 function RecvSentence(const Wait: Boolean; out se: TROSAPISentence): Boolean;
110 function GetSentenceWithTag(const Tag: String; const Wait: Boolean; out Sentence: TROSAPISentence): Boolean;
111 procedure ClearSentenceTag(var Sentence: TRosApiSentence);
112 public
113 function Connect(const Hostname, Username, Password: String; const Port: String = '8728'): Boolean;
114 function Query(const Request: array of String;
115 const GetAllAfterQuery: Boolean): TROSAPIResult;
116 function Execute(const Request: array of String): Boolean;
117
118 property Timeout: Integer read FTimeout write FTimeout;
119 property LastError: String read FLastError;
120
121 constructor Create;
122 destructor Destroy; override;
123
124 procedure Disconnect;
125
126 function GetWordValueByName(Sentence: TROSAPISentence; Name: String;
127 RaiseErrorIfNotFound: Boolean = False): String;
128 end;
129
130implementation
131
132{******************************************************************************}
133
134function HexToStr(hex: String): String;
135const
136 Convert: array['0'..'f'] of SmallInt =
137 ( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1,
138 -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,
139 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
140 -1,10,11,12,13,14,15);
141var
142 i: Integer;
143begin
144 Result := '';
145
146 if Length(hex) mod 2 <> 0 then
147 raise Exception.Create('Invalid hex value') at @HexToStr;
148
149 SetLength(Result, Length(hex) div 2);
150
151 for i := 1 to Length(hex) div 2 do
152 begin
153 if not (hex[i * 2 - 1] in ['0'..'f']) or not (hex[i * 2] in ['0'..'f']) then Break;
154 Result[i] := Char((Convert[hex[i * 2 - 1]] shl 4) + Convert[hex[i * 2]]);
155 end;
156end;
157
158{******************************************************************************}
159
160constructor TRosApiResult.Create;
161begin
162 inherited Create;
163 FTrap := False;
164 FTrapMessage := '';
165 FDone := False;
166 SetLength(Sentences, 0);
167end;
168
169{******************************************************************************}
170
171constructor TRosApiClient.Create;
172begin
173 inherited Create;
174 FNextTag := 1;
175 FTimeout := 30000;
176 FLastError := '';
177 FSock := TTCPBlockSocket.Create;
178end;
179
180{******************************************************************************}
181
182destructor TRosApiClient.Destroy;
183begin
184 FSock.Free;
185 inherited Destroy;
186end;
187
188{******************************************************************************}
189
190function TRosApiClient.Connect(const Hostname, Username, Password: String; const Port: String = '8728'): Boolean;
191var
192 Res, Res2: TRosApiResult;
193begin
194 FLastError := '';
195 FSock.CloseSocket;
196 FSock.LineBuffer := '';
197 FSock.Connect(Hostname, Port);
198 Result := FSock.LastError = 0;
199 FLastError := FSock.LastErrorDesc;
200 if not Result then Exit;
201
202 Result := False;
203
204 Res := Query(['/login'], True);
205 if Res.Values[0].Name = '!done' then
206 begin
207 Res2 := Query(['/login', '=name=' + Username, '=response=00' +
208 StrToHex(MD5(#0 + Password + HexToStr(Res['=ret'])))], True);
209 if Res2.Trap then
210 FSock.CloseSocket
211 else
212 Result := True;
213 Res2.Free;
214 end
215 else
216 raise Exception.Create('Invalid response: ''' + Res.Values[0].Name + ''', expected ''!done''');
217 Res.Free;
218end;
219
220{******************************************************************************}
221
222procedure TRosApiClient.Disconnect;
223begin
224 FSock.CloseSocket;
225 FSock.LineBuffer := '';
226end;
227
228{******************************************************************************}
229
230function TRosApiClient.SockRecvByte(out b: Byte; const Wait: Boolean = True): Boolean;
231begin
232 Result := True;
233
234 if Wait then
235 b := FSock.RecvByte(FTimeout)
236 else
237 b := FSock.RecvByte(0);
238
239 if (FSock.LastError = WSAETIMEDOUT) and (not Wait) then
240 Result := False;
241 if (FSock.LastError = WSAETIMEDOUT) and Wait then
242 raise Exception.Create('Socket recv timeout in SockRecvByte');
243end;
244
245{******************************************************************************}
246
247function TRosApiClient.SockRecvBufferStr(Length: Cardinal): String;
248begin
249 Result := FSock.RecvBufferStr(Length, FTimeout);
250
251 if FSock.LastError = WSAETIMEDOUT then
252 begin
253 Result := '';
254 raise Exception.Create('Socket recv timeout in SockRecvBufferStr');
255 end;
256end;
257
258{******************************************************************************}
259
260procedure TRosApiClient.SendWord(s: String);
261var
262 l: Cardinal;
263begin
264 l := Length(s);
265 if l < $80 then
266 FSock.SendByte(l) else
267 if l < $4000 then begin
268 l := l or $8000;
269 FSock.SendByte((l shr 8) and $ff);
270 FSock.SendByte(l and $ff); end else
271 if l < $200000 then begin
272 l := l or $c00000;
273 FSock.SendByte((l shr 16) and $ff);
274 FSock.SendByte((l shr 8) and $ff);
275 FSock.SendByte(l and $ff); end else
276 if l < $10000000 then begin
277 l := l or $e0000000;
278 FSock.SendByte((l shr 24) and $ff);
279 FSock.SendByte((l shr 16) and $ff);
280 FSock.SendByte((l shr 8) and $ff);
281 FSock.SendByte(l and $ff); end
282 else begin
283 FSock.SendByte($f0);
284 FSock.SendByte((l shr 24) and $ff);
285 FSock.SendByte((l shr 16) and $ff);
286 FSock.SendByte((l shr 8) and $ff);
287 FSock.SendByte(l and $ff);
288 end;
289
290 FSock.SendString(s);
291end;
292
293{******************************************************************************}
294
295function TRosApiClient.Query(const Request: array of String;
296 const GetAllAfterQuery: Boolean): TROSAPIResult;
297var
298 i: Integer;
299begin
300 FLastError := '';
301
302 //Result := nil;
303 // if not FSock.Connected then Exit;
304
305 Result := TRosApiResult.Create;
306 Result.Client := Self;
307 Result.Tag := IntToHex(FNextTag, 4);
308 Inc(FNextTag);
309
310 for i := 0 to High(Request) do
311 SendWord(Request[i]);
312 SendWord('.tag=' + Result.Tag);
313 SendWord('');
314
315 if GetAllAfterQuery then
316 if not Result.GetAll then
317 raise Exception.Create('Cannot GetAll: ' + LastError);
318end;
319
320{******************************************************************************}
321
322function TRosApiClient.RecvWord(const Wait: Boolean; out w: String): Boolean;
323var
324 l: Cardinal;
325 b: Byte;
326begin
327 Result := False;
328 if not SockRecvByte(b, Wait) then Exit;
329 Result := True;
330
331 l := b;
332
333 if l >= $f8 then
334 raise Exception.Create('Reserved control byte received, cannot proceed') else
335 if (l and $80) = 0 then
336 else
337 if (l and $c0) = $80 then begin
338 l := (l and not $c0) shl 8;
339 SockRecvByte(b);
340 l := l + b; end else
341 if (l and $e0) = $c0 then begin
342 l := (l and not $e0) shl 8;
343 SockRecvByte(b);
344 l := (l + b) shl 8;
345 SockRecvByte(b);
346 l := l + b; end else
347 if (l and $f0) = $e0 then begin
348 l := (l and not $f0) shl 8;
349 SockRecvByte(b);
350 l := (l + b) shl 8;
351 SockRecvByte(b);
352 l := (l + b) shl 8;
353 SockRecvByte(b);
354 l := l + b; end else
355 if (l and $f8) = $f0 then begin
356 SockRecvByte(b);
357 l := b shl 8;
358 SockRecvByte(b);
359 l := (l + b) shl 8;
360 SockRecvByte(b);
361 l := (l + b) shl 8;
362 SockRecvByte(b);
363 l := l + b;
364 end;
365
366 w := SockRecvBufferStr(l);
367end;
368
369{******************************************************************************}
370
371function TRosApiClient.RecvSentence(const Wait: Boolean; out se: TROSAPISentence): Boolean;
372var
373 p: Integer;
374 w: String;
375begin
376 repeat
377 if RecvWord(Wait, w) then
378 begin
379 SetLength(se, 1);
380 se[0].Name := w;
381 end
382 else
383 begin
384 Result := False;
385 Exit;
386 end;
387 until w <> '';
388
389 repeat
390 if RecvWord(True, w) then
391 begin
392 if w = '' then
393 begin
394 Result := True;
395 Exit;
396 end
397 else
398 begin
399 SetLength(se, High(se) + 2);
400 p := PosEx('=', w, 2);
401 if p = 0 then
402 se[High(se)].Name := w
403 else
404 begin
405 se[High(se)].Name := Copy(w, 1, p - 1);
406 se[High(se)].Value := Copy(w, p + 1, Length(w) - p);
407 end;
408 end;
409 end
410 else
411 begin
412 Result := False;
413 Exit;
414 end;
415 until False;
416end;
417
418{******************************************************************************}
419
420function TRosApiClient.GetSentenceWithTag(const Tag: String; const Wait: Boolean; out Sentence: TROSAPISentence): Boolean;
421var
422 i, j: Integer;
423 se: TRosApiSentence;
424begin
425 Result := False;
426
427 for i := 0 to High(Sentences) do
428 begin
429 if GetWordValueByName(Sentences[i], '.tag') = Tag then
430 begin
431 Sentence := Sentences[i];
432 ClearSentenceTag(Sentence);
433 for j := i to High(Sentences) - 1 do
434 Sentences[j] := Sentences[j + 1];
435 SetLength(Sentences, High(Sentences));
436 Result := True;
437 Exit;
438 end;
439 end;
440
441 repeat
442 if RecvSentence(Wait, se) then
443 begin
444 if GetWordValueByName(se, '.tag', True) = Tag then
445 begin
446 Sentence := se;
447 ClearSentenceTag(Sentence);
448 Result := True;
449 Exit;
450 end;
451
452 SetLength(Sentences, High(Sentences) + 2);
453 Sentences[High(Sentences)] := se;
454 end
455 else
456 Exit;
457 until False;
458end;
459
460{******************************************************************************}
461
462procedure TRosApiClient.ClearSentenceTag(var Sentence: TRosApiSentence);
463var
464 i, j: Integer;
465begin
466 for i := High(Sentence) downto 0 do
467 if Sentence[i].Name = '.tag' then
468 begin
469 for j := i to High(Sentence) - 1 do
470 Sentence[j] := Sentence[j + 1];
471 SetLength(Sentence, High(Sentence));
472 end;
473end;
474
475{******************************************************************************}
476
477function TRosApiClient.GetWordValueByName(Sentence: TROSAPISentence; Name: String;
478 RaiseErrorIfNotFound: Boolean = False): String;
479var
480 i: Integer;
481begin
482 Result := '';
483 for i := 1 to High(Sentence) do
484 if (Sentence[i].Name = '=' + Name) or (Sentence[i].Name = Name) then
485 begin
486 Result := Sentence[i].Value;
487 Exit;
488 end;
489
490 if RaiseErrorIfNotFound then
491 raise Exception.Create('API Word ''' + Name + ''' not found in sentence');
492end;
493
494{******************************************************************************}
495
496function TRosApiResult.GetValueByName(const Name: String): String;
497begin
498 if High(Sentences) = -1 then
499 raise Exception.Create('No values - use Get* first?')
500 else
501 Result := Client.GetWordValueByName(Sentences[0], Name);
502end;
503
504{******************************************************************************}
505
506function TRosApiResult.GetValues: TRosApiSentence;
507begin
508 if High(Sentences) = -1 then
509 raise Exception.Create('No values - use Get* first?')
510 else
511 Result := Sentences[0];
512end;
513
514{******************************************************************************}
515
516function TRosApiResult.GetOne(const Wait: Boolean): Boolean;
517begin
518 Client.FLastError := '';
519 FTrap := False;
520
521 SetLength(Sentences, 1);
522
523 Result := Client.GetSentenceWithTag(Tag, Wait, Sentences[0]);
524 if not Result then Exit;
525
526 if Sentences[0][0].Name = '!trap' then
527 begin
528 FTrap := True;
529 Client.FLastError := Self['=message'];
530 end;
531
532 FDone := Sentences[0][0].Name = '!done';
533end;
534
535{******************************************************************************}
536
537function TRosApiResult.GetAll: Boolean;
538var
539 se: TRosApiSentence;
540begin
541 Client.FLastError := '';
542 FTrap := False;
543
544 repeat
545 Result := Client.GetSentenceWithTag(Tag, True, se);
546 if Result then
547 begin
548 if se[0].Name = '!trap' then
549 begin
550 FTrap := True;
551 if Client.FLastError <> '' then
552 Client.FLastError := Client.FLastError + '; ';
553 Client.FLastError := Client.FLastError + Client.GetWordValueByName(se, '=message');
554 end else
555 if se[0].Name = '!done' then
556 begin
557 FDone := True;
558 if High(se) > 0 then
559 begin
560 SetLength(Sentences, High(Sentences) + 2);
561 Sentences[High(Sentences)] := se;
562 end;
563
564 Exit;
565 end
566 else
567 begin
568 SetLength(Sentences, High(Sentences) + 2);
569 Sentences[High(Sentences)] := se;
570 end;
571 end;
572 until False;
573end;
574
575{******************************************************************************}
576
577function TRosApiResult.GetEof: Boolean;
578begin
579 Result := High(Sentences) = -1;
580end;
581
582{******************************************************************************}
583
584function TRosApiResult.GetRowsCount: Integer;
585begin
586 Result := Length(Sentences);
587end;
588
589{******************************************************************************}
590
591procedure TRosApiResult.Next;
592var
593 i: Integer;
594begin
595 Client.FLastError := '';
596
597 for i := 0 to High(Sentences) - 1 do
598 Sentences[i] := Sentences[i + 1];
599 SetLength(Sentences, High(Sentences));
600end;
601
602{******************************************************************************}
603
604procedure TRosApiResult.Cancel;
605begin
606 if not Client.Execute(['/cancel', '=tag=' + Tag]) then
607 raise Exception.Create('Cannot cancel: ' + Client.LastError);
608end;
609
610{******************************************************************************}
611
612function TRosApiClient.Execute(const Request: array of String): Boolean;
613var
614 Res: TRosApiResult;
615begin
616 Res := Query(Request, True);
617 Result := not Res.Trap;
618 Res.Free;
619end;
620
621{******************************************************************************}
622
623end.
Note: See TracBrowser for help on using the repository browser.