{*******************************************************}
{                                                       }
{       nrCommLib Communication Library for Delphi      }
{       GSM PDU format encode/decode GSM 07.07 (VCL)    }
{       Copyright (c) 1997-2011 Roman Novgorodov        }
{       v9.27                                           }
{       last update: 24 Nov 2007                        }
{                                                       }
{       e-mail:noro@deepsoftware.com                    }
{       DeepSoftware                                    }
{       http://www.deepsoftware.com/                    }
{                                                       }
{*******************************************************}
{$I nrcommlib.inc}
unit nrgsmpdu;

interface

uses Classes, sysutils, windows;


Const
  SMS_MAX_LENGTH_7BIT = 160;
  SMS_MAX_LENGTH_8BIT = 140;
  SMS_MAX_LENGTH_16BIT = 70;

type

  TnrPduCoding = (pcAuto,pc7bit,pc8bit,pc16bit);
  TnrPduTTL = (ttlNone,ttlOffset,ttlExt,ttlAbs);

  TnrPduSms = class
  private
    fPhone: string;
    fServiceCenter: string;
    fText: WideString;
//    fTextWide: string;
    fText7bit : string;
    fDateTime: TDateTime;
    fGmt:integer;
    fFo : byte;
    fPID, fDCS : byte;
    fPartNumber: integer;
    fPartId: integer;
    fPartCount: integer;
    fError: boolean;
    fCoding: TnrPduCoding;
    fTag: integer;
    fConfirm: boolean;
    fIsHeader: boolean;
    fPartMaxLen: integer;
    fIsEmpty:boolean;
    fIncoming: boolean;
    fNotLast: boolean;
    fTTLValue: TDateTime;
    fTTLType: TnrPduTTL;
    fAutoCutText: boolean;
    fReport: boolean;
    fReportStatus: byte;
    fRefId: byte;
    fReportDateTime: TDateTime;
    fAlert: boolean;

    function GetAsPDU: string;
    procedure SetAsPDU( Value: string);
    procedure SetText(const Value: WideString);
//    procedure SetTextWide(const Value: string);
    function EncodeFirstOctet:byte;
    procedure DecodeFirstOctet(byt:byte);
    function EncodeDataHeader:string;
    function GetCoding: TnrPduCoding;
    function GetConfirm: boolean;
    function GetDateTime: TDateTime;
    function GetDCS: byte;
    function GetError: boolean;
    function GetGmt: integer;
    function GetIsHeader: boolean;
    function GetPartCount: integer;
    function GetPartId: integer;
    function GetPartMaxLen: integer;
    function GetPartNumber: integer;
    function GetPhone: string;
    function GetPID: byte;
    function GetServiceCenter: string;
    function GetTag: integer;
    function GetText: WideString;
//    function GetTextWide: string;
    procedure SetConfirm(const Value: boolean);
    procedure SetDateTime(const Value: TDateTime);
    procedure SetIsHeader(const Value: boolean);
    procedure SetPhone(const Value: string);
    procedure SetServiceCenter(const Value: string);
    procedure SetTag(const Value: integer);
    function EncodePDUDateTime(aDT:TDateTime; flAbsolute:boolean):string;
    function DecodePDUDateTime(S:string; var i:integer):TDateTime;
    function GetMaxPartLength(const aCoding:TnrPduCoding):integer;
    procedure SetPartId(const Value: integer);
    procedure SetPartNumber(const Value: integer);
    function GetReportText: string;

  public
    constructor Create(aIncoming:boolean);
    procedure Clear;
    procedure Assign(aSrc:TnrPduSms);
    property Phone:string read GetPhone write SetPhone;
    property ServiceCenter:string read GetServiceCenter write SetServiceCenter;
    property DateTime:TDateTime read GetDateTime write SetDateTime;
    property Text:WideString read GetText write SetText;
//    property AsTextWide:string read GetTextWide write SetTextWide;
    property AsPDU:string read GetAsPDU write SetAsPDU;
    property PID : byte read GetPID;
    property DCS : byte read GetDCS;
    property GMT : integer read GetGmt;
    property PartCount:integer read GetPartCount;
    property PartNumber:integer read GetPartNumber write SetPartNumber;
    property PartId:integer read GetPartId write SetPartId;
    property PartMaxLen:integer read GetPartMaxLen;
    property Error:boolean read GetError;
    property Coding:TnrPduCoding read GetCoding write fCoding;
    property Confirm:boolean read GetConfirm write SetConfirm;
    property Tag:integer read GetTag write SetTag;
    property IsHeader:boolean read GetIsHeader write SetIsHeader;
    property IsEmpty:boolean read fIsEmpty;
    property Incoming:boolean read fIncoming write fIncoming;
    property Report:boolean read fReport write fReport;
    property ReportStatus:byte read fReportStatus write fReportStatus;
    property ReportDateTime:TDateTime read fReportDateTime write fReportDateTime;
    property NotLast:boolean read fNotLast write fNotLast;
    property TTLType: TnrPduTTL read fTTLType write fTTLType;
    property TTLValue: TDateTime read fTTLValue write fTTLValue;
    property AutoCutText:boolean read fAutoCutText write fAutoCutText;
    property RefId:byte read fRefId write fRefId;
    property ReportText:string read GetReportText;
    property Alert:boolean read fAlert write fAlert;
  end;

  function Is7BitString(const s:string):boolean;

  function DetectStrCoding(const s:WideString):TnrPduCoding;
  function OctetsToUnicode(aStr:string; Index:integer):WideString;
  function UnicodeToOctets(aTxt:WideString):string;
  function DecodeGsmDateTime(sT :string):TDateTime;


implementation
uses nrsysutils {$IFDEF TRIAL}, shellapi{$ENDIF} {$IFDEF GEN6},DateUtils{$ENDIF};

Const

  TP_MTI_Reserved  = $03;                // incoming message or report
  TP_MTI       = $01;                // outgoing message or report
  TP_MTI_REP   = $02;                // status report
  TP_MMS       = $04;                // more messages to send (1 - no more msg)
  TP_SRR       = $20;                // status report (confirm)
  TP_UDHI      = $40;                // data header

  TP_VP_NONE   = $00;                // TP-VP (TTL) field not present
  TP_VP_OFFSET = $10;                // TP-VP field present. Relative format (one byte)
  TP_VP_EXT    = $08;                // TP-VP field present. Enhanced format (7 bytes)
  TP_VP_ABS    = $18;                // TP-VP field present. Absolute format (7 bytes)

  cs_CharTable_GSM7bit = #0#1#2#3#4#5#6#7#8#9+
                         #11#12#14#15#17#28#29#30#31#36#64+
                         #91#92#93#94#95#96#123#124#125#126#127;
  cs_CharTable_ISO     = '@'#163'$'#165#232#233#249#236#242#199+
                         #216#248#197#229#95#198#230#223#201#164#161+
                         #196#214#209#220#167#191#228#246#241#252#224;

  cs_EscCharTable_GSM7bit = #10#20#40#41#47#60#61#62#64#101;
  cs_EscCharTable_ISO     = #12#94#123#125#92#91#126#93#124#164;

{ ---- }

function ReturnByte(s:string; i:integer):byte;
begin
  Result := 0;
  if(((s[i] < #$30) or (s[i] > #$39))and
    ((s[i] < #$41) or (s[i] > #$46)))or
    (((s[i+1] < #$30) or (s[i+1] > #$39)) and
    ((s[i+1] < #$41) or (s[i+1] > #$46)))then exit;

  if s[i] >= #$41
    then Result := Byte(s[i]) - $41+$A
    else Result := Byte(s[i]) - $30;

  if s[i+1] >= #$41
    then Result := (Result shl 4) + Byte(s[i+1]) - $41+$A
    else Result := (Result shl 4) + Byte(s[i+1]) - $30;

end;

function SwappedBCDToByte(aBCD:byte):byte;
begin
  Result := (aBCD and $0F) * 10 + ((aBCD and $F0) shr 4);
end;

// =============================
// decoding text functions ...
// -----------------------------

function Convert7bitGsmToISO(aStr:string):string;
var i,j:integer;
    flEsc:boolean;
begin
  Result := '';
  flEsc := False;
  for i := 1 to Length(aStr) do begin

    if (not flEsc) and  (aStr[i] = #27)then begin
      flEsc := True;
      continue;
    end;

    if flEsc then begin
      j := Pos(aStr[i], cs_EscCharTable_GSM7bit);
      if j > 0
        then Result := Result + cs_EscCharTable_ISO[j];
    end else begin
      j := Pos(aStr[i], cs_CharTable_GSM7bit);
      if j > 0
        then Result := Result + cs_CharTable_ISO[j]
        else Result := Result + aStr[i];
    end;

    flEsc := False;

  end;
end;

function SeptetsToStr(aStr:string; Index,lenMax,shiftBits:integer):string;
var i,j, off , len, lenCurrent : integer;
    byt, bOld, bCh, bMsk:byte;
    w:word;
begin
  len := Length(aStr);
  i := Index;
  j := 1;
  Result := '';
  bOld := 0;
  lenCurrent := 0;
  while i <= len do begin
    off := (j + shiftBits)  and 7;
    byt := ReturnByte(aStr,i);
    if off <> 0 then begin
      bMsk := ($FF shr off);
      w:= byt and bMsk;
      w := w shl 8 + bOld;
      bCh := w shr (8 - (off-1));
      Result := Result + Char(bCh);
      Inc(lenCurrent);
      bOld := byt;
      if (off = 7) and (lenCurrent < lenMax) then begin
        bCh := bOld shr 1;
        Result := Result + Char(bCh);
        Inc(lenCurrent);
        bOld := 0;
      end;
      i := i + 2;
    end;
    if lenCurrent >= lenMax then break;
    Inc(j);
  end;
  if shiftBits <> 0 then Result := Copy(Result,2,length(Result)-1);
end;

function OctetsToUnicode(aStr:string; Index:integer):WideString;
var i,len : integer;
    wCh: word;
begin
  Result := '';
  len := Length(aStr);
  if len < 2 then exit;
  i := Index;
  wCh := ReturnByte(aStr,i);
  if (wCh = $80) then inc(i,2);

  while i <= len do begin
    wCh := ReturnByte(aStr,i);
    wCh := (wCh shl 8) + ReturnByte(aStr,i+2);
    {$IFDEF GEN3}
    Result := Result + 'a';
    Result[Length(Result)] := WideChar(wCh);
    {$ELSE}
    Result := Result + WideChar(wCh);
    {$ENDIF}
    inc(i,4);
  end;
end;

function OctetsToStr(aStr:string; Index:integer):string;
var i,len : integer;
begin
  Result := '';
  len := Length(aStr);
  i := Index;
  while i <= len do begin
    Result := Result + Char(ReturnByte(aStr,i));
    inc(i,2);
  end;
end;

function DecodeGsmDateTime(sT :string):TDateTime;
var Y,M,D:word;
    h,min:word;
begin
  Result := 0;
  sT := Trim(sT);
  try
    Y := StrToInt(Copy(sT,1,4));
    M := StrToInt(Copy(sT,6,2));
    D := StrToInt(Copy(sT,9,2));
    h := StrToInt(Copy(sT,12,2));
    min := StrToInt(Copy(sT,15,2));
    Result := EncodeDateTime(Y,M,D,h,min,0,0);
  except
  end;
end;

// =============================
// encoding text functions ...
// -----------------------------

function UnicodeToOctets(aTxt:WideString):string;
var i:integer;
begin
  Result := '';
  for i := 1 to Length(aTxt)
    do Result := Result + IntToHex(Word(aTxt[i]),4);
end;

function StrToOctets(aTxt:string):string;
var i:integer;
begin
  Result := '';
  for i := 1 to Length(aTxt)
    do Result := Result + IntToHex(Byte(aTxt[i]),2);
end;

function ConvertISOTo7bitGsm(aStr:string):string;
var i,j:integer;
begin
  Result := '';
  for i := 1 to Length(aStr) do begin
    j := Pos(aStr[i], cs_CharTable_ISO);
    if j > 0 then Result := Result + cs_CharTable_GSM7bit[j]
    else begin
      j := Pos(aStr[i], cs_EscCharTable_ISO);
      if j > 0
        then Result := Result + #27 + cs_EscCharTable_GSM7bit[j]
        else Result := Result + aStr[i];
    end
  end;
end;

function StrToSeptets(aTxt:string; shiftBits:integer):string;
var i, off, len:integer;
    bchCurrent,bchNext:byte;
begin
  Result := '';
  //aTxt := ' ' + aTxt;  //!!!!
  len := Length(aTxt);
  for i := 1 to len do begin
    off := (i + shiftBits) and 7;
    bchCurrent := Byte(aTxt[i]) and $7F;

    if off = 0 then continue;

    if i < len
      then bchNext := Byte(aTxt[i+1]) and $7F
      else bchNext := 0;

    bchCurrent := (bchCurrent shr (off - 1)) or (bchNext shl (8 - off ));

    Result := Result + IntToHex(bchCurrent,2);

  end;
end;

function GetQuotedString(aStr:string):string;
var len:integer;
begin
  Result := Trim(aStr);
  len := Length(Result);
  if len<1 then exit;
  if (Result[1] <> '"') and (Result[1] <> '''') then begin
    Result := '"'+Result;
    Inc(len);
  end;
  if Result[1] <> Result[len] then Result := Result + Result[1];
end;

function Is7BitString(const s:string):boolean;
var i:integer;
begin
  Result := True;
  for i := 1 to Length(s) do
    if ( Byte(s[i]) and $80 ) <> 0 then begin
      Result := False;
      break;
    end;
end;

function IsDigitsOnlyString(const s:string):boolean;
var i:integer;
begin
  Result := True;
  for i := 1 to Length(s) do
    if (( Byte(s[i]) > $39 ) or ( Byte(s[i]) < $30 )) then begin
      if (i = 1) and (s[i] = '+') then continue;
      Result := False;
      break;
    end;
end;

function DetectStrCoding(const s:WideString):TnrPduCoding;
var i:integer;
    w:word;
begin
  Result := pc7bit;
  for i := 1 to Length(s) do begin
    w := Word(s[i]);
    if (w and $FF00) <> 0 then begin
      Result := pc16bit;
      break;
    end else
      if (w and $80) <> 0 then Result := pc8bit
  end;
end;

// trim spaces and other wrong chars ....
function GetNormalPhoneNumber(aPhone:string):string;
var i:integer;
begin
  Result := '';
  for i := 1 to length(aPhone) do
    if ((aPhone[i] >= '0') and (aPhone[i] <= '9'))or
        (aPhone[i] = '#') or (aPhone[i] = '*')
    then Result := Result + aPhone[i];
end;

function PduToPhone(s:string; i,len:integer; aType:byte{ = $91}):string;
var j:integer;
    ch:char;
begin
  Result :='';
  if (aType and $D0) = $D0 then begin
//    len := ;
    len := len shr 1;
    j := (len div 7) * 8 + (len mod 7);
    Result := Convert7bitGsmToISO(SeptetsToStr(s,i,j,0));
  end else begin
    Ch := '0';
    for j := i to len+i do
      if ((j-i+1) and 1)=1
        then ch := s[j]
        else begin
          Result := Result + s[j];
          if Ch<>'F'
            then Result := Result +Ch
            else break;
        end;
  end;
end;

function PhoneToPdu(aPhone:string; flDataLen:boolean):string;
var i, len:integer;
    s,typ:string;
begin
  Result :='';
//  if aPhone = '' then exit;
  if IsDigitsOnlyString(aPhone) then begin

    len := Length(aPhone);

    if (len > 0) and (aPhone[1] = '+') then begin
      Delete(aPhone,1,1);
      Dec(len);
    end;

    for i := 1 to len do begin
      if (i and 1) = 1
        then Result := Result + 'F'+aPhone[i]
        else Result[i-1] := aPhone[i];
    end;
    typ := '91';
    if (len > 0) and flDataLen then begin
      if (len and 1) = 1 then len := (len shr 1) + 1
      else len := (len shr 1);
      len := len +1;
    end;

  end else begin
    Result := StrToSeptets( ConvertISOTo7bitGsm(aPhone),0);
    typ := 'D0';
    len := Length(Result);
  end;

  s := IntToHex(len,2);
  if len > 0
    then Result := s + typ + Result
    else Result := s;

end;

{ TnrPduSms }

procedure TnrPduSms.Assign(aSrc: TnrPduSms);
begin
  if Assigned(aSrc) then begin
    Phone := aSrc.Phone;
    ServiceCenter := aSrc.ServiceCenter;
    DateTime := aSrc.DateTime;
    Text := aSrc.Text;
//    AsTextWide := aSrc.AsTextWide;
  end;
end;

procedure TnrPduSms.Clear;
begin
  fPhone := '';
  fServiceCenter := '';
  fText := '';
  fDateTime := 0;
  fPartCount  := 1;
  fPartNumber := 1;
  fPartId := 0;
  fIsEmpty := True;
  fAutoCutText := False;
  fReportStatus := 0;
  fRefId := 0;
  fReport := False;
  fAlert := False;
end;

function TnrPduSms.EncodeDataHeader: string;
begin
  Result := '';
  if IsHeader {(fPartCount > 1)} then begin // multipart header ...
    Result := '050003' + IntToHex(fPartId and $FF,2) +
                         IntToHex(fPartCount,2) +
                         IntToHex(fPartNumber,2);
  end;
end;

Var
  arrTTLFlags:array[TnrPduTTL] of byte = (TP_VP_NONE, TP_VP_OFFSET, TP_VP_EXT, TP_VP_ABS);

function TnrPduSms.EncodeFirstOctet: byte;
begin
  if fIncoming then begin
     Result := 0;
     if not fNotLast then Result :=  Result or TP_MMS;
     if fReport then Result :=  Result or TP_MTI_REP;
  end else begin
    Result := TP_MTI or arrTTLFlags[fTTLType];
  end;
//  Result := $11;
  if fConfirm then Result := Result or TP_SRR;

  if not fIsHeader then fIsHeader := (fPartCount > 1);

  if fIsHeader then Result := Result or TP_UDHI;
end;

procedure TnrPduSms.DecodeFirstOctet(byt: byte);
var i: TnrPduTTL;
begin
  fIncoming := (byt and TP_MTI_Reserved) = TP_MTI_Reserved;

  if not fIncoming
    then fIncoming := (byt and TP_MTI) <> TP_MTI;

  fIsHeader := (byt and TP_UDHI) = TP_UDHI;
//  fIncoming := (byt and TP_MTI) <> TP_MTI;
  fReport := (byt and TP_MTI_REP) = TP_MTI_REP;
  fConfirm := (byt and TP_SRR) = TP_SRR;
  fNotLast := not ((byt and TP_MMS) = TP_MMS);

  for i := ttlAbs downto ttlNone do
    if (byt and arrTTLFlags[i]) = arrTTLFlags[i] then begin
      fTTLType := i;
      break;
    end;
end;

function TnrPduSms.GetCoding: TnrPduCoding;
begin
  Result := fCoding;
end;

function TnrPduSms.GetConfirm: boolean;
begin
  Result := fConfirm;
end;

function TnrPduSms.GetDateTime: TDateTime;
begin
  Result := fDateTime;
end;

function TnrPduSms.GetDCS: byte;
begin
  Result := fDCS;
end;

function TnrPduSms.GetError: boolean;
begin
  Result := fError;
end;

function TnrPduSms.GetGmt: integer;
begin
  Result := fGmt;
end;

function TnrPduSms.GetIsHeader: boolean;
begin
  Result := fIsHeader;
end;

function TnrPduSms.GetPartCount: integer;
begin
  Result := fPartCount;
end;

function TnrPduSms.GetPartId: integer;
begin
  Result := fPartId;
end;

function TnrPduSms.GetPartMaxLen: integer;
begin
  Result := fPartMaxLen;
end;

function TnrPduSms.GetPartNumber: integer;
begin
  Result := fPartNumber;
end;

function TnrPduSms.GetPhone: string;
begin
  Result := fPhone;
end;

function TnrPduSms.GetPID: byte;
begin
  Result := fPID;
end;

function TnrPduSms.GetServiceCenter: string;
begin
  Result := fServiceCenter;
end;

function TnrPduSms.GetTag: integer;
begin
  Result := fTag;
end;

function TnrPduSms.GetText: WideString;
begin
  Result := fText;
end;

function TnrPduSms.DecodePDUDateTime(S: string; var i: integer): TDateTime;
var j:integer;
    arrDate:array[0..6]of word;
    byt:byte;
begin
  Result := 0;
  if fIncoming or (fTTLType > ttlOffset) then begin

    for j := 0 to 6 do begin
      arrDate[j] := SwappedBCDToByte(ReturnByte(S,i));
      i := i+2;
    end;

    if arrDate[0] > 90 then arrDate[0] := arrDate[0] + 1900
                       else arrDate[0] := arrDate[0] + 2000;

    fGmt  := arrDate[6] shr 2;

    Try
      Result := EncodeDateTime(arrDate[0],arrDate[1],arrDate[2],arrDate[3],arrDate[4],arrDate[5],0);
    except
      Result := 0;//
      //OutputDebugString(PChar('Erro:'+s));
    end;

  end else begin
    if fTTLType = ttlOffset then begin

      byt := ReturnByte(S,i);
      if byt <= 143 then Result := (byt + 1) / 288
      else
      if byt <= 167 then Result := 1/2 + (byt - 143) / 48
      else
      if byt <= 196 then Result := (byt - 166)
      else
                        Result := (byt - 192) * 7;
      i := i+2;
    end;
  end;

end;

function TnrPduSms.EncodePDUDateTime(aDT:TDateTime; flAbsolute:boolean): string;
var //dt: TDateTime;
    i:integer;
    arrDate:array[0..6] of word;
    byt:byte;
    s:string;
begin
  Result := '';
//  if fIncoming
//    then dt := fDateTime
//    else dt := fTTLValue;

  //if fIncoming or (fTTLType > ttlOffset) then begin
  if flAbsolute then begin
    DecodeDateTime(aDT, arrDate[0],arrDate[1],arrDate[2],arrDate[3],arrDate[4],arrDate[5],arrDate[6]);
    arrDate[6] := fGMT shl 2;
    for i := 0 to 6 do begin
      s := Format('%.02u',[arrDate[i] mod 100]);//  IntToStr(arrDate[i] mod 100);
      Result := Result + s[2]+s[1];
    end;
  end else begin
//    if fTTLType = ttlOffset then begin
      if aDT < 1/2 then byt :=  Trunc(288 * aDT) - 1
      else
      if aDT < 2 then byt := Trunc((aDT - 1/2) * 48) + 143
      else
      if aDT < (5 * 7) then byt := Trunc(aDT  + 166)
      else
                           byt := Trunc((aDT / 7) + 197);
      Result := IntToHex(byt,2);
//    end;
  end;
end;

function TnrPduSms.GetAsPDU: string;
var len, iPart , off:integer;
    byt:byte;
    sData,s, sDataHeader:string;
    dt:TDateTime;
begin
  Result := '';

  Result := PhoneToPdu(fServiceCenter, True);

  Result := Result + IntToHex(EncodeFirstOctet,2);

  if (not fIncoming) or fReport then Result := Result + IntToHex(fRefId,2); //'00'

  Result := Result + PhoneToPdu(fPhone, False);

  if fReport then begin
    Result := Result + EncodePDUDateTime(fDateTime, True); //'00'
    Result := Result + EncodePDUDateTime(fReportDateTime, True); //'00'
    Result := Result + IntToHex(fReportStatus,2);
    fIsEmpty := False;
    exit;
  end;

  Result := Result + '00';

  sDataHeader := EncodeDataHeader;
  len := Length(sDataHeader) shr 1;

  if fCoding = pc7bit
    then s := fText7bit
    else s := fText;

  if fPartMaxLen <= 0 then fPartMaxLen := GetMaxPartLength(fCoding);

  if fAutoCutText
    then iPart := fPartNumber
    else iPart := 1;

  s := Copy(s, (iPart - 1)*fPartMaxLen + 1, fPartMaxLen);

  // add fill bits for 7bit encoding ...
  if (len > 0) and (fCoding = pc7bit) then s := ' ' + s;

  byt := length(s);

  if Alert
    then Result := Result + '1'
    else Result := Result + '0'; // first half of DCS octet (high 4 bits) !!!!

  case fCoding of
    pc7bit: begin
      Result := Result + '0'; // second half of DCS octet (low 4 bits) !!!!
      //if iPart = 1
        //then off := 1
        //else
        off := len mod 7;
      sData := StrToSeptets(s, off );
    end;
    pc8bit: begin
      Result := Result + '4';
      sData := StrToOctets(s);
    end;
    else begin
      Result := Result + '8';
      sData := UnicodeToOctets(s);
      byt := byt * 2;
    end;
  end;

  if fIncoming or (fTTLType <> ttlNone)  then begin
    if fIncoming
      then dt := fDateTime
      else dt := fTTLValue;
    Result := Result + EncodePDUDateTime(dt,fIncoming or (fTTLType > ttlOffset));
  end;

  byt := byt + len;
  Result := Result + IntToHex(byt,2) + sDataHeader + sData;
  fIsEmpty := False;
end;

procedure TnrPduSms.SetAsPDU(Value: string);
var i, len,len2,iTxt, lenHdr:integer;
    sBytes:string;
    byt:byte;
    aLen:integer;
    aDt:TDateTime;
begin
  fError := True;   
  Clear;
  Value := Uppercase(Trim(Value));

  if Pos('000000FF', Value) = 1 then begin
    Delete(Value, 1, 8);
  end;

  len := Length(Value);
  if len < 4 then exit;



  i := 1;

  byt := ReturnByte(Value,i);

  if (byt <> 0) and (byt < len) then begin
    len2 := (byt-1)*2;
    byt := ReturnByte(Value,3);
    i := 5;
    fServiceCenter := PduToPhone(Value,i,len2, byt);
    i := i+len2;
  end else i := 3;

  fFo := ReturnByte(Value,i); // First octet ...

  DecodeFirstOctet(fFo);
  i := i+2;

  if (not fIncoming) or fReport then begin
    fRefId := ReturnByte(Value,i);
    i := i+2;
  end;

  byt := ReturnByte(Value,i);

  if (byt <> 0) and (byt < len) then begin
    if (byt and 1)=1
      then len2 := byt +1
      else len2 := byt;
    i := i + 2;
    byt := ReturnByte(Value,i);
    i := i + 2;
    fPhone := PduToPhone(Value,i,len2, byt);
    i := i+len2;
  end;

  if not fReport then begin
    fPID := ReturnByte(Value,i);
    i := i+2;
    fDCS := ReturnByte(Value,i);
    i := i+2;
  end;

  fAlert := (fDCS and $10) <> 0;

  aDt := DecodePDUDateTime(Value,i);

  if fIncoming
    then fDateTime := aDt
    else fTTLValue := aDt;

  if fIncoming and fReport then begin
    fReportDateTime := DecodePDUDateTime(Value,i); // read charge
    fReportStatus := ReturnByte(Value,i);
    i := i +2;
    if i >= len then begin
      fError := False;
      fIsEmpty := False;
      exit;
    end
  end;

//  i := i+2;
  aLen := ReturnByte(Value,i);
  i := i+2;

  case fDCS and $C of
    8: fCoding := pc16bit;
    4: fCoding := pc8bit;
    else fCoding := pc7bit;
  end;

  iTxt := i;
  lenHdr := 0;
  if fIsHeader then begin
    byt := ReturnByte(Value,i);
    lenHdr := byt + 1;
    i := i + 2;
    iTxt := i + byt*2;

    byt := ReturnByte(Value,i);

    if byt = 0 then begin // multipart sms ...
      i := i + 4; // skip length ...
      fPartId := ReturnByte(Value,i);
      i := i + 2;
      fPartCount  := ReturnByte(Value,i);
      i := i + 2;
      fPartNumber := ReturnByte(Value,i);
    end;
  end;

  case fCoding of
    pc16bit : sBytes := OctetsToUnicode(Value,iTxt);
    pc8bit  : sBytes := OctetsToStr(Value,iTxt);
    else begin
      fText7bit := SeptetsToStr(Value,iTxt,aLen,lenHdr mod 7);
      sBytes := Convert7bitGsmToISO(fText7bit);
    end;
  end;

  fText := sBytes;
  fError := False;
  fIsEmpty := False;
end;

procedure TnrPduSms.SetConfirm(const Value: boolean);
begin
  fConfirm := Value;
end;

procedure TnrPduSms.SetDateTime(const Value: TDateTime);
begin
  fDateTime := Value;
end;

procedure TnrPduSms.SetIsHeader(const Value: boolean);
begin
  fIsHeader := Value;
end;

procedure TnrPduSms.SetPhone(const Value: string);
begin
  fPhone := Value;
end;

procedure TnrPduSms.SetServiceCenter(const Value: string);
begin
  fServiceCenter := Value;
end;

procedure TnrPduSms.SetTag(const Value: integer);
begin
  fTag := Value;
end;

procedure TnrPduSms.SetPartId(const Value: integer);
begin
  fPartId := Value;
end;

procedure TnrPduSms.SetPartNumber(const Value: integer);
begin
  fPartNumber := Value;
end;

function TnrPduSms.GetMaxPartLength(const aCoding: TnrPduCoding): integer;
begin
  if aCoding = pc7bit
    then Result :=  SMS_MAX_LENGTH_7BIT
    else
  if aCoding = pc8bit
    then Result :=  SMS_MAX_LENGTH_8BIT
    else
         Result :=  SMS_MAX_LENGTH_16BIT;
end;

procedure TnrPduSms.SetText(const Value: WideString);
var len, max_len, off:integer;
    s:string;
begin
  fText := Value;
  if fText <> '' then begin
    if fCoding = pcAuto then fCoding :=  DetectStrCoding(Value);
    s := Value;
    off := 3;
    max_len := GetMaxPartLength(fCoding);

    case fCoding of
      pc7bit: begin
        s := ConvertISOTo7bitGsm(Value);
        fText7bit := s;
        off := 7;
      end;
      pc8bit: off := 1;
    end;

    len := length(s); // real length of txt in GSM 7bit encoding ...

    if len > max_len
      then fPartMaxLen := max_len - off
      else fPartMaxLen := max_len;

    if AutoCutText then begin // auto detect count of parts ...
      fPartCount :=  len div fPartMaxLen;
      if (len mod fPartMaxLen) > 0 then Inc(fPartCount);
    end;

    fIsEmpty := False;
  end;
end;

constructor TnrPduSms.Create(aIncoming: boolean);
begin
  inherited Create;
  Clear;
  fIncoming := aIncoming;
  fCoding := pcAuto;
  if fIncoming then begin
    //
  end else begin
    TTLType :=  ttlOffset;
    TTLValue := 4;
  end;
end;

function TnrPduSms.GetReportText: string;
begin
  if Report
    then Result := 'REPORT: Sms is Delivered to ' + Phone + ' at ' + DateTimeToStr(DateTime)
    else Result := '';
end;

end.


