{******************************************************************************} { } { Library: Fundamentals 5.00 } { File name: flcTCPBuffer.pas } { File version: 5.08 } { Description: TCP buffer. } { } { Copyright: Copyright (c) 2007-2020, David J Butler } { All rights reserved. } { This file is licensed under the BSD License. } { See http://www.opensource.org/licenses/bsd-license.php } { Redistribution and use in source and binary forms, with } { or without modification, are permitted provided that } { the following conditions are met: } { Redistributions of source code must retain the above } { copyright notice, this list of conditions and the } { following disclaimer. } { THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND } { CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED } { WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED } { WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A } { PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL } { THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, } { INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR } { CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, } { PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF } { USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) } { HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER } { IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING } { NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE } { USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE } { POSSIBILITY OF SUCH DAMAGE. } { } { Github: https://github.com/fundamentalslib } { E-mail: fundamentals.library at gmail.com } { } { Revision history: } { } { 2008/12/23 0.01 Initial development. } { 2010/12/02 0.02 Revision. } { 2011/04/22 0.03 Simple test cases. } { 2011/06/16 0.04 Minor change in PeekPtr routine. } { 2011/09/03 4.05 Revised for Fundamentals 4. } { 2016/01/09 5.06 Revised for Fundamentals 5. } { 2019/04/10 5.07 Change default buffer size. } { 2019/12/29 5.08 Minimum buffer size. } { } {******************************************************************************} {$INCLUDE ../flcInclude.inc} {$INCLUDE flcTCP.inc} unit flcTCPBuffer; interface uses { System } SysUtils, { Utils } flcStdTypes; { } { TCP Buffer } { } type ETCPBuffer = class(Exception); TTCPBuffer = record Ptr : Pointer; Size : Integer; Min : Int32; Max : Int32; Head : Int32; Used : Int32; end; const ETHERNET_MTU = 1500; ETHERNET_MTU_JUMBO = 9000; TCP_BUFFER_DEFAULTMAXSIZE = ETHERNET_MTU_JUMBO * 8; // 72,000 bytes TCP_BUFFER_DEFAULTMINSIZE = ETHERNET_MTU * 6; // 9,000 bytes procedure TCPBufferInitialise( var TCPBuf: TTCPBuffer; const TCPBufMaxSize: Int32 = TCP_BUFFER_DEFAULTMAXSIZE; const TCPBufMinSize: Int32 = TCP_BUFFER_DEFAULTMINSIZE); procedure TCPBufferFinalise(var TCPBuf: TTCPBuffer); function TCPBufferGetMaxSize(const TCPBuf: TTCPBuffer): Int32; {$IFDEF UseInline}inline;{$ENDIF} procedure TCPBufferSetMaxSize( var TCPBuf: TTCPBuffer; const MaxSize: Int32); function TCPBufferGetMinSize(const TCPBuf: TTCPBuffer): Int32; {$IFDEF UseInline}inline;{$ENDIF} procedure TCPBufferSetMinSize( var TCPBuf: TTCPBuffer; const MinSize: Int32); procedure TCPBufferPack(var TCPBuf: TTCPBuffer); procedure TCPBufferResize( var TCPBuf: TTCPBuffer; const TCPBufSize: Int32); procedure TCPBufferExpand( var TCPBuf: TTCPBuffer; const Size: Int32); procedure TCPBufferShrink(var TCPBuf: TTCPBuffer); procedure TCPBufferMinimize(var TCPBuf: TTCPBuffer); procedure TCPBufferClear(var TCPBuf: TTCPBuffer); function TCPBufferAddPtr( var TCPBuf: TTCPBuffer; const Size: Int32): Pointer; procedure TCPBufferAdded( var TCPBuf: TTCPBuffer; const Size: Int32); procedure TCPBufferAddBuf( var TCPBuf: TTCPBuffer; const Buf; const Size: Int32); function TCPBufferPeekPtr( const TCPBuf: TTCPBuffer; var BufPtr: Pointer): Int32; {$IFDEF UseInline}inline;{$ENDIF} function TCPBufferPeek( var TCPBuf: TTCPBuffer; var Buf; const Size: Int32): Int32; function TCPBufferPeekByte( var TCPBuf: TTCPBuffer; out B: Byte): Boolean; function TCPBufferRemove( var TCPBuf: TTCPBuffer; var Buf; const Size: Int32): Int32; function TCPBufferRemoveBuf( var TCPBuf: TTCPBuffer; var Buf; const Size: Int32): Boolean; function TCPBufferDiscard( var TCPBuf: TTCPBuffer; const Size: Int32): Int32; function TCPBufferUsed(const TCPBuf: TTCPBuffer): Int32; {$IFDEF UseInline}inline;{$ENDIF} function TCPBufferEmpty(const TCPBuf: TTCPBuffer): Boolean; {$IFDEF UseInline}inline;{$ENDIF} function TCPBufferAvailable(const TCPBuf: TTCPBuffer): Int32; {$IFDEF UseInline}inline;{$ENDIF} function TCPBufferPtr(const TCPBuf: TTCPBuffer): Pointer; {$IFDEF UseInline}inline;{$ENDIF} function TCPBufferLocateByteChar(const TCPBuf: TTCPBuffer; const Delimiter: ByteCharSet; const MaxSize: Integer): Int32; implementation { } { Resource strings } { } const SBufferOverflow = 'Buffer overflow'; { } { TCP Buffer } { } // Initialise a TCP buffer procedure TCPBufferInitialise( var TCPBuf: TTCPBuffer; const TCPBufMaxSize: Int32; const TCPBufMinSize: Int32); var L, M : Int32; begin TCPBuf.Ptr := nil; TCPBuf.Size := 0; TCPBuf.Head := 0; TCPBuf.Used := 0; L := TCPBufMinSize; if L < 0 then L := TCP_BUFFER_DEFAULTMINSIZE; M := TCPBufMaxSize; if M < 0 then M := TCP_BUFFER_DEFAULTMAXSIZE; if L > M then L := M; TCPBuf.Min := L; TCPBuf.Max := M; if L > 0 then GetMem(TCPBuf.Ptr, L); TCPBuf.Size := L; end; // Finalise a TCP buffer procedure TCPBufferFinalise(var TCPBuf: TTCPBuffer); var P : Pointer; begin P := TCPBuf.Ptr; if Assigned(P) then begin TCPBuf.Ptr := nil; FreeMem(P); end; TCPBuf.Size := 0; end; // Gets maximum buffer size function TCPBufferGetMaxSize(const TCPBuf: TTCPBuffer): Int32; begin Result := TCPBuf.Max; end; // Sets maximum buffer size // Note: This limit is not enforced. It is used by TCPBufferAvailable. procedure TCPBufferSetMaxSize( var TCPBuf: TTCPBuffer; const MaxSize: Int32); var L : Int32; begin L := MaxSize; if L < 0 then L := TCP_BUFFER_DEFAULTMAXSIZE; TCPBuf.Max := L; end; // Gets minimum buffer size function TCPBufferGetMinSize(const TCPBuf: TTCPBuffer): Int32; begin Result := TCPBuf.Min; end; procedure TCPBufferSetMinSize( var TCPBuf: TTCPBuffer; const MinSize: Int32); var L : Int32; begin L := MinSize; if L < 0 then L := TCP_BUFFER_DEFAULTMINSIZE; TCPBuf.Min := L; end; // Pack a TCP buffer // Moves data to front of buffer // Post: TCPBuf.Head = 0 procedure TCPBufferPack(var TCPBuf: TTCPBuffer); var P, Q : PByte; U, H : Int32; begin H := TCPBuf.Head; if H <= 0 then exit; U := TCPBuf.Used; if U <= 0 then begin TCPBuf.Head := 0; exit; end; Assert(Assigned(TCPBuf.Ptr)); P := TCPBuf.Ptr; Q := P; Inc(P, H); Move(P^, Q^, U); TCPBuf.Head := 0; end; // Resize a TCP buffer // New buffer size must be large enough to hold existing data // Post: TCPBuf.Size = TCPBufSize procedure TCPBufferResize( var TCPBuf: TTCPBuffer; const TCPBufSize: Int32); var U, L : Int32; begin L := TCPBufSize; U := TCPBuf.Used; // treat negative TCPBufSize parameter as zero if L < 0 then L := 0; // check if shrinking buffer to less than used size if U > L then raise ETCPBuffer.Create(SBufferOverflow); // check if packing required to fit buffer if U + TCPBuf.Head > L then TCPBufferPack(TCPBuf); Assert(U + TCPBuf.Head <= L); // resize ReallocMem(TCPBuf.Ptr, L); TCPBuf.Size := L; end; // Expand a TCP buffer // Expands the size of the TCP buffer to at least Size procedure TCPBufferExpand( var TCPBuf: TTCPBuffer; const Size: Int32); var S : Int32; N : Int64; I : Int64; begin S := TCPBuf.Size; N := Size; // check if expansion not required if N <= S then exit; // scale up new size proportional to current size // increase by at least quarter of current size // this reduces the number of resizes in growing buffers I := S + (S div 4); if N < I then N := I; // ensure new size is multiple of MTU size I := N mod ETHERNET_MTU; if I > 0 then Inc(N, ETHERNET_MTU - I); // resize buffer Assert(N >= Size); TCPBufferResize(TCPBuf, N); end; // Shrink the size of a TCP buffer to release all unused memory // Post: TCPBuf.Used = TCPBuf.Size and TCPBuf.Head = 0 procedure TCPBufferShrink(var TCPBuf: TTCPBuffer); var S, U : Int32; begin S := TCPBuf.Size; if S <= 0 then exit; U := TCPBuf.Used; if U = 0 then begin TCPBufferResize(TCPBuf, 0); TCPBuf.Head := 0; exit; end; if U = S then exit; TCPBufferPack(TCPBuf); // move data to front of buffer TCPBufferResize(TCPBuf, U); // set size equal to used bytes Assert(TCPBuf.Used = TCPBuf.Size); end; // Applies Min parameter to allocated memory procedure TCPBufferMinimize(var TCPBuf: TTCPBuffer); {$IFDEF UseInline}inline;{$ENDIF} var Mi : Int32; begin Mi := TCPBuf.Min; if Mi >= 0 then if TCPBuf.Used <= Mi then if TCPBuf.Size > Mi then TCPBufferResize(TCPBuf, Mi); end; // Clear the data from a TCP buffer procedure TCPBufferClear(var TCPBuf: TTCPBuffer); {$IFDEF UseInline}inline;{$ENDIF} begin TCPBuf.Used := 0; TCPBuf.Head := 0; TCPBufferMinimize(TCPBuf); end; // Returns a pointer to position in buffer to add new data of Size // Handles resizing and packing of buffer to fit new data function TCPBufferAddPtr( var TCPBuf: TTCPBuffer; const Size: Int32): Pointer; {$IFDEF UseInline}inline;{$ENDIF} var P : PByte; U : Int32; L : Int64; H : Int32; begin // return nil if nothing to add if Size <= 0 then begin Result := nil; exit; end; U := TCPBuf.Used; L := U + Size; // resize if necessary if L > TCPBuf.Size then TCPBufferExpand(TCPBuf, L); // pack if necessary if TCPBuf.Head + L > TCPBuf.Size then TCPBufferPack(TCPBuf); // buffer should now be large enough for new data H := TCPBuf.Head; Assert(TCPBuf.Size > 0); Assert(H + L <= TCPBuf.Size); // get buffer pointer Assert(Assigned(TCPBuf.Ptr)); P := TCPBuf.Ptr; Inc(P, H); Inc(P, U); Result := P; end; // Increases data used in buffer by Size. // TCPBufferAdded should only be called in conjuction with TCPBufferAddPtr. procedure TCPBufferAdded( var TCPBuf: TTCPBuffer; const Size: Int32); begin if Size <= 0 then exit; Assert(TCPBuf.Head + TCPBuf.Used + Size <= TCPBuf.Size); Inc(TCPBuf.Used, Size); end; // Adds new data from a buffer to a TCP buffer procedure TCPBufferAddBuf( var TCPBuf: TTCPBuffer; const Buf; const Size: Int32); {$IFDEF UseInline}inline;{$ENDIF} var P : PByte; begin if Size <= 0 then exit; // get TCP buffer pointer P := TCPBufferAddPtr(TCPBuf, Size); // move user buffer to TCP buffer Assert(Assigned(P)); Move(Buf, P^, Size); Inc(TCPBuf.Used, Size); Assert(TCPBuf.Head + TCPBuf.Used <= TCPBuf.Size); end; // Peek TCP buffer // Returns the number of bytes available to peek function TCPBufferPeekPtr( const TCPBuf: TTCPBuffer; var BufPtr: Pointer): Int32; {$IFDEF UseInline}inline;{$ENDIF} var P : PByte; L : Int32; begin // handle empty TCP buffer L := TCPBuf.Used; if L <= 0 then begin BufPtr := nil; Result := 0; exit; end; // get buffer pointer Assert(TCPBuf.Head + L <= TCPBuf.Size); Assert(Assigned(TCPBuf.Ptr)); P := TCPBuf.Ptr; Inc(P, TCPBuf.Head); BufPtr := P; // return size Result := L; end; // Peek data from a TCP buffer // Returns the number of bytes actually available and copied into the buffer function TCPBufferPeek( var TCPBuf: TTCPBuffer; var Buf; const Size: Int32): Int32; {$IFDEF UseInline}inline;{$ENDIF} var P : Pointer; L : Int32; begin // handle peeking zero bytes if Size <= 0 then begin Result := 0; exit; end; L := TCPBufferPeekPtr(TCPBuf, P); // peek from TCP buffer if L > Size then L := Size; Move(P^, Buf, L); Result := L; end; // Peek byte from a TCP buffer // Returns True if a byte is available function TCPBufferPeekByte( var TCPBuf: TTCPBuffer; out B: Byte): Boolean; var P : Pointer; L : Int32; begin L := TCPBufferPeekPtr(TCPBuf, P); // peek from TCP buffer if L = 0 then Result := False else begin B := PByte(P)^; Result := True; end; end; // Remove data from a TCP buffer // Returns the number of bytes actually available and copied into the user buffer function TCPBufferRemove( var TCPBuf: TTCPBuffer; var Buf; const Size: Int32): Int32; {$IFDEF UseInline}inline;{$ENDIF} var L, H, U : Int32; begin // peek data from buffer L := TCPBufferPeek(TCPBuf, Buf, Size); if L = 0 then begin Result := 0; exit; end; // remove from TCP buffer H := TCPBuf.Head; U := TCPBuf.Used; Dec(U, L); if U = 0 then H := 0 else Inc(H, L); TCPBuf.Head := H; TCPBuf.Used := U; TCPBufferMinimize(TCPBuf); Result := L; end; // Remove data from a TCP buffer // Returns True if Size bytes were available and copied into the user buffer // Returns False if Size bytes were not available function TCPBufferRemoveBuf( var TCPBuf: TTCPBuffer; var Buf; const Size: Int32): Boolean; {$IFDEF UseInline}inline;{$ENDIF} var H, U : Int32; P : PByte; begin // handle invalid size if Size <= 0 then begin Result := False; exit; end; // check if enough data available U := TCPBuf.Used; if U < Size then begin Result := False; exit; end; // get buffer H := TCPBuf.Head; Assert(H + Size <= TCPBuf.Size); P := TCPBuf.Ptr; Assert(Assigned(P)); Inc(P, H); Move(P^, Buf, Size); // remove from TCP buffer Dec(U, Size); if U = 0 then H := 0 else Inc(H, Size); TCPBuf.Head := H; TCPBuf.Used := U; TCPBufferMinimize(TCPBuf); Result := True; end; // Discard a number of bytes from the TCP buffer // Returns the number of bytes actually discarded from buffer function TCPBufferDiscard( var TCPBuf: TTCPBuffer; const Size: Int32): Int32; {$IFDEF UseInline}inline;{$ENDIF} var L, U : Int32; begin // handle discarding zero bytes from buffer L := Size; if L <= 0 then begin Result := 0; exit; end; // handle discarding the complete buffer U := TCPBuf.Used; if L >= U then begin TCPBuf.Used := 0; TCPBuf.Head := 0; TCPBufferMinimize(TCPBuf); Result := U; exit; end; // discard partial buffer Inc(TCPBuf.Head, L); Dec(U, L); TCPBuf.Used := U; TCPBufferMinimize(TCPBuf); Result := L; end; // Returns number of bytes used in TCP buffer function TCPBufferUsed(const TCPBuf: TTCPBuffer): Int32; {$IFDEF UseInline}inline;{$ENDIF} begin Result := TCPBuf.Used; end; function TCPBufferEmpty(const TCPBuf: TTCPBuffer): Boolean; {$IFDEF UseInline}inline;{$ENDIF} begin Result := TCPBuf.Used = 0; end; // Returns number of bytes available in TCP buffer // Note: this function can return a negative number if the TCP buffer uses more bytes than set in Max function TCPBufferAvailable(const TCPBuf: TTCPBuffer): Int32; {$IFDEF UseInline}inline;{$ENDIF} begin Result := TCPBuf.Max - TCPBuf.Used; end; // Returns pointer to TCP buffer head function TCPBufferPtr(const TCPBuf: TTCPBuffer): Pointer; {$IFDEF UseInline}inline;{$ENDIF} var P : PByte; begin Assert(Assigned(TCPBuf.Ptr)); P := TCPBuf.Ptr; Inc(P, TCPBuf.Head); Result := P; end; // LocateByteChar // Returns position of Delimiter in buffer // Returns >= 0 if found in buffer // Returns -1 if not found in buffer // MaxSize specifies maximum bytes before delimiter, of -1 for no limit function TCPBufferLocateByteChar(const TCPBuf: TTCPBuffer; const Delimiter: ByteCharSet; const MaxSize: Integer): Int32; var BufSize : Int32; LocLen : Int32; BufPtr : PByteChar; I : Int32; begin if MaxSize = 0 then begin Result := -1; exit; end; BufSize := TCPBuf.Used; if BufSize <= 0 then begin Result := -1; exit; end; if MaxSize < 0 then LocLen := BufSize else if BufSize < MaxSize then LocLen := BufSize else LocLen := MaxSize; BufPtr := TCPBufferPtr(TCPBuf); for I := 0 to LocLen - 1 do if BufPtr^ in Delimiter then begin Result := I; exit; end else Inc(BufPtr); Result := -1; end; end.