unit dzlib;

//
//  RFC-1950 ZLIB Compressed Data Format Specification version 3.3
//    https://www.ietf.org/rfc/rfc1950.txt
//

{$MODE FPC}
{$MODESWITCH ADVANCEDRECORDS}
{$MODESWITCH OUT}
{$MODESWITCH RESULT}

interface

const
  // possible values for CM (CompressionMethod)
  ZLIB_CM_DEFLATE = 8;

type
TZlibHeader = record
  CMF: Byte; // Compression Method and flags
  FLG: Byte; // FLaGs
  DICTID: UInt32; // if FDICT is True
  ErrorCode: LongInt;
  ErrorMsg: PAnsiChar;
  function CompressionMethod: Cardinal; inline;
  function CompressionInfo: Cardinal; inline;
  function FCHECK: Cardinal; inline;
  function FDICT: Boolean; inline;
  function FLEVEL: Cardinal; inline;
end;

function ZlibHeader(Data: Pointer;
                    DataEnd: Pointer;
                    out Header: TZlibHeader;
                    out NextData: Pointer): Boolean;
      // Tries to read header of zlib data. On success, fills CMF, FLG and maybe DICTID

implementation

function Error(var Header: TZlibHeader;
               Code: LongInt;
               Msg: PAnsiChar): Boolean;
begin
  Header.ErrorCode := Code;
  Header.ErrorMsg := Msg;
  Exit(Code = 0);
end;

function TZlibHeader.CompressionMethod: Cardinal; inline;
begin
  Exit(CMF and %1111);
end;

function TZlibHeader.CompressionInfo: Cardinal; inline;
begin
  Exit(CMF shr 4);
end;

function TZlibHeader.FCHECK: Cardinal; inline;
begin
  Exit(FLG and $f);
end;

function TZlibHeader.FDICT: Boolean; inline;
begin
  Exit(FLG and (1 shl 5) <> 0);
end;

function TZlibHeader.FLEVEL: Cardinal; inline;
begin
  Exit((FLG shr 6) and 3);
end;

function ZlibHeader(Data: Pointer;
                    DataEnd: Pointer;
                    out Header: TZlibHeader;
                    out NextData: Pointer): Boolean;
begin
  if DataEnd - Data < 2 then
    Exit(Error(Header, 1, 'no header'));

  if SwapEndian(UInt16(Data^)) mod 31 <> 0 then
    Exit(Error(Header, 1, 'bad FCHECK'));

  Header.CMF := Byte(Data^);
  Header.FLG := Byte((Data + 1)^);

  if Header.FDICT then begin
    if DataEnd - Data < 6 then
      Exit(Error(Header, 1, 'no DICTID'));
    Header.DICTID := SwapEndian(UInt32((Data + 2)^));
    NextData := Data + 6;
  end else begin
    NextData := Data + 2;
  end;

  if Header.CompressionMethod = ZLIB_CM_DEFLATE then begin
    if Header.CompressionInfo > 7 then
      Exit(Error(Header, 1, 'CINFO>7'));
  end else
    Exit(Error(Header, 1, 'unknown CM'));

  Exit(True);
end;

end.
