西门子CPU读写MSSQL
侧边栏壁纸
  • 累计撰写 20 篇文章
  • 累计收到 3 条评论

西门子CPU读写MSSQL

滕招
2021-10-14 / 0 评论 / 7,084 阅读 / 正在检测是否收录...

开放性源代码:




TYPE "LSql_typeLoginInformation"
VERSION : 0.1
   STRUCT
      hostName : String;
      userName : String;
      password : String;
      appName : String;
      serverName : String;
      libraryName : String;
      local : String;
      databaseName : String;
      sspi : String;
      attachDbfile : String;
      changePassword : String;
   END_STRUCT;

END_TYPE

TYPE "LSql_typeDiagnostics"
VERSION : 0.1
   STRUCT
      status { ExternalVisible := 'False'; ExternalWritable := 'False'} : Word;
      subfunctionStatus { ExternalVisible := 'False'; ExternalWritable := 'False'} : Word;
      stateNumber { ExternalVisible := 'False'; ExternalWritable := 'False'} : DInt;
   END_STRUCT;

END_TYPE

TYPE "LSql_typeReceivedData"
VERSION : 0.1
   STRUCT
      len : UDInt;
      bytes : Array[0..1023] of Byte;
   END_STRUCT;

END_TYPE

TYPE "LSql_typeSqlBatchData"
VERSION : 0.1
   STRUCT
      header : Struct
         "type" : Byte := 16#01;
         status : Byte := 16#01;
         length : Word := 16#001E;
         channel : Word := 16#0000;
         packet : Byte := 16#01;
         window : Byte := 16#00;
      END_STRUCT;
      sqlBatch : Struct
         totalLength : DWord := 16#1600_0000;
         header : Struct
            headerLength : DWord := 16#1200_0000;
            headerType : Word := 16#0200;
            mars : Struct
               transactionDescriptor : Array[0..7] of Byte;
               outstandingRequestCount : DWord := 16#0100_0000;
            END_STRUCT;
         END_STRUCT;
         sqlText : Array[0..1023] of Byte;
      END_STRUCT;
   END_STRUCT;

END_TYPE

TYPE "LSql_typeLoginData"
VERSION : 0.1
   STRUCT
      header : Struct
         "type" : Byte := 16#10;
         status : Byte := 16#01;
         length : Word := 16#0090;
         spid : Word := 16#0000;
         packet : Byte := 16#01;
         window : Byte := 16#00;
      END_STRUCT;
      login : Struct
         length : DWord := 16#8800_0000;
         tdsVersion : DWord := 16#0200_0972;
         packetSize : DWord := 16#0010_0000;
         clientProgVer : DWord := 16#0000_0007;
         clientId : DWord := 16#0001_0000;
         conncetionId : DWord := 16#0000_0000;
         optionFlags1 : Byte := 16#E0;
         optionFlags2 : Byte := 16#03;
         typeFlags : Byte := 16#00;
         optionFlags3 : Byte := 16#00;
         clientTimeZone : DWord := 16#E001_0000;
         clientLCID : DWord := 16#0904_0000;
         offsetLength : Struct
            hostName : Struct
               offset : Word := 16#5E00;
               length : Word := 16#0800;
            END_STRUCT;
            userName : Struct
               offset : Word := 16#6E00;
               length : Word := 16#0200;
            END_STRUCT;
            password : Struct
               offset : Word := 16#7200;
               length : Word := 16#0000;
            END_STRUCT;
            appName : Struct
               offset : Word := 16#7200;
               length : Word := 16#0700;
            END_STRUCT;
            serverName : Struct
               offset : Word := 16#8000;
               length : Word := 16#0000;
            END_STRUCT;
            unused : Struct
               offset : Word := 16#8000;
               length : Word := 16#0000;
            END_STRUCT;
            libraryName : Struct
               offset : Word := 16#8000;
               length : Word := 16#0400;
            END_STRUCT;
            local : Struct
               offset : Word := 16#8800;
               length : Word := 16#0000;
            END_STRUCT;
            databaseName : Struct
               offset : Word := 16#8800;
               length : Word := 16#0000;
            END_STRUCT;
            clientId : Array[0..5] of Byte := 16#000D, 16#007D, 16#0095, 16#0036, 16#0063, 16#0028;
            sspi : Struct
               offset : Word := 16#8800;
               length : Word := 16#0000;
            END_STRUCT;
            atchDbFile : Struct
               offset : Word := 16#8800;
               length : Word := 16#0000;
            END_STRUCT;
            changePassword : Struct
               offset : Word := 16#8800;
               length : Word := 16#0000;
            END_STRUCT;
            cbSspiLong : DWord := 16#0000_0000;
         END_STRUCT;
         data : Array[0..1023] of Byte;
      END_STRUCT;
   END_STRUCT;

END_TYPE

TYPE "LSql_typePreloginData"
VERSION : 0.1
   STRUCT
      header : Struct
         "type" : Byte := 16#12;
         status : Byte := 16#01;
         length : Word := 16#005E;
         channel : Word := 16#0000;
         packet : Byte := 16#01;
         window : Byte := 16#00;
      END_STRUCT;
      data : Struct
         option : Struct
            "version" : Struct
               token : Byte := 16#00;
               offset : Word := 16#0024;
               length : Word := 16#0006;
            END_STRUCT;
            encryption : Struct
               token : Byte := 16#01;
               offset : Word := 16#002A;
               length : Word := 16#0001;
            END_STRUCT;
            instOpt : Struct
               token : Byte := 16#02;
               offset : Word := 16#002B;
               length : Word := 16#0001;
            END_STRUCT;
            threadId : Struct
               token : Byte := 16#03;
               offset : Word := 16#002C;
               length : Word := 16#0004;
            END_STRUCT;
            mars : Struct
               token : Byte := 16#04;
               offset : Word := 16#0030;
               length : Word := 16#0001;
            END_STRUCT;
            traceId : Struct
               token : Byte := 16#05;
               offset : Word := 16#0031;
               length : Word := 16#0024;
            END_STRUCT;
            fedAuthRequired : Struct
               token : Byte := 16#06;
               offset : Word := 16#0055;
               length : Word := 16#0001;
            END_STRUCT;
            terminator : Byte := 16#FF;
         END_STRUCT;
         data : Array[0..49] of Byte := 16#000C, 16#0000, 16#0013, 16#0088, 2(16#0000), 16#0002, 3(16#0000), 16#00B8, 16#000D, 16#0000, 16#00DD, 16#00DA, 16#0047, 16#001C, 16#006C, 16#0079, 16#00D4, 16#004D, 16#00AC, 16#0052, 16#007F, 16#0033, 16#0086, 16#00C7, 16#0042, 16#006A, 16#00BB, 16#00F2, 16#0026, 16#00EF, 16#0011, 16#0072, 16#000C, 16#0048, 16#00A8, 16#0066, 16#004B, 16#00F4, 16#004A, 16#00CD, 16#00E2, 16#00A4, 16#0001, 3(16#0000), 3(16#0001);
      END_STRUCT;
   END_STRUCT;

END_TYPE

FUNCTION "LSql_WriteData" : Void
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
   VAR_INPUT 
      inString : String;
      offsetOwn : Word;
   END_VAR

   VAR_OUTPUT 
      error : Bool;
      status : Word;
      offsetNext : Word;
      length : Word;
   END_VAR

   VAR_IN_OUT 
      data : Array[0..1023] of Byte;
      writer : Int;
   END_VAR

   VAR_TEMP 
      tempStatus : Word;
      tempBytes : Array[0..123] of Byte;
      tempBytesFinal : Array[0..123] of Byte;
      tempWriter : Int;
      tempLen : DInt;
      tempCounter : DInt;
      tempOffsetNext : Word;
      tempLength : Word;
      tempOffsetAsDInt : DInt;
      tempLenAsUInt : UInt;
   END_VAR

   VAR CONSTANT 
      STATUS_FINISHED_NO_ERROR : Word := 16#0000;
      ERR_UNDEFINED : Word := 16#8000;
      ERR_IN_BLOCK_OPERATION : Word := 16#8001;
      ERR_PARAMETRIZATION : Word := 16#8200;
      ERR_PROCESSING_EXTERN : Word := 16#8400;
      ERR_PROCESSING_INTERN : Word := 16#8600;
      ERR_AREA_RESERVED : Word := 16#8800;
      ERR_USER_DEFINED_CLASSES : Word := 16#9000;
   END_VAR


BEGIN
    REGION BLOCK INFO HEADER
        //===============================================================================
        // (Siemens AG (c)Copyright (2020)
        //-------------------------------------------------------------------------------
        // Title:            LSql_WriteData
        // Comment/Function: serialize input string to array of bytes
        // Library/Family:   LSql
        // Author:           Siemens SUP Applications 
        //Target:            S7-1500 / FW 2.6
        // Engineering:      TIA Portal (V16)
        // Restrictions:     no
        // Requirements:      CPU 1500
        //-------------------------------------------------------------------------------
        // Change log table:
        // Version  | Date       | Expert in charge         | Changes applied
        //----------|------------|--------------------------|------------------------------
        // 01.00.00 | 2020-02-19 | Siemens SUP Applications | First released version
        //===============================================================================
    END_REGION
    
    REGION INITIALISATION  
        // TODO: Initialize functionality: reset of variables, diagnostics, etc.
        #tempLenAsUInt := 0;
        #tempLen := 0;
        #tempCounter := 0;
        #tempOffsetAsDInt := 0;
        #tempOffsetNext := 16#0000;
        #tempStatus := #STATUS_FINISHED_NO_ERROR;
    END_REGION
    
    REGION PROGRAM LOGIC
        //convert string to char array
        Strg_TO_Chars(Strg := #inString,
                      pChars := 0,
                      Cnt => #tempLenAsUInt,
                      Chars := #tempBytes);
        #tempLen := UINT_TO_DINT(#tempLenAsUInt);
        
        
        //fill in zero bytes between each letter
        FOR #tempCounter := 0 TO #tempLen - 1 DO
            #data[#writer] := #tempBytes[#tempCounter];
            #writer += 1;
            #data[#writer] := 16#00;
            #writer += 1;
        END_FOR;
        
        
        //set offset to length * 2 cause of filled in zero bytes
        #tempOffsetAsDInt := WORD_TO_DINT(#offsetOwn);
        #tempOffsetAsDInt := ROR(IN := #tempOffsetAsDInt, N := 8) + #tempLen * 2;
        #tempOffsetNext := DINT_TO_WORD(#tempOffsetAsDInt);
        #tempOffsetNext := ROL(IN := #tempOffsetNext, N := 8);
        
        
        //set length to the number of letters(low Byte, high Byte)
        #tempLength := ROL(IN := DINT_TO_WORD(#tempLen), N := 8);
        // so lassen
        
    END_REGION
    
    REGION OUTPUTS
        
        #offsetNext := #tempOffsetNext;
        #length := #tempLength;
        #error := #tempStatus.%X15;
        #status := #tempStatus;
        // if ENO mechanism is not used / not wanted replace the following line by --> ENO := TRUE;
        ENO := NOT #tempStatus.%X15;
    END_REGION
    
END_FUNCTION

FUNCTION "LSql_WritePassword" : Void
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
   VAR_INPUT 
      inString : String;
      offsetOwn : Word;
   END_VAR

   VAR_OUTPUT 
      error : Bool;
      status : Word;
      offsetNext : Word;
      length : Word;
   END_VAR

   VAR_IN_OUT 
      data : Array[0..1023] of Byte;
      writer : Int;
   END_VAR

   VAR_TEMP 
      tempStatus : Word;
      tempConvBytes : Array[0..123] of Byte;
      tempLen : DInt;
      tempCounter : DInt;
      tempLowBits : Byte;
      tempHighBits : Byte;
      tempLenAsUInt : UInt;
      tempOffsetNext : Word;
      tempOffsetAsDInt : DInt;
      tempLength : Word;
   END_VAR

   VAR CONSTANT 
      STATUS_FINISHED_NO_ERROR : Word := 16#0000;
      ERR_UNDEFINED : Word := 16#8000;
      ERR_IN_BLOCK_OPERATION : Word := 16#8001;
      ERR_PARAMETRIZATION : Word := 16#8200;
      ERR_PROCESSING_EXTERN : Word := 16#8400;
      ERR_PROCESSING_INTERN : Word := 16#8600;
      ERR_AREA_RESERVED : Word := 16#8800;
      ERR_USER_DEFINED_CLASSES : Word := 16#9000;
   END_VAR


BEGIN
    REGION BLOCK INFO HEADER
        //===============================================================================
        // (Siemens AG (c)Copyright (2020)
        //-------------------------------------------------------------------------------
        // Title:            LSql_WriteData
        // Comment/Function: serialize input string (password) to array of bytes
        // Library/Family:   LSql
        // Author:           Siemens SUP Applications 
        //Target:            S7-1500 / FW 2.6
        // Engineering:      TIA Portal (V16)
        // Restrictions:     no
        // Requirements:      CPU 1500
        //-------------------------------------------------------------------------------
        // Change log table:
        // Version  | Date       | Expert in charge         | Changes applied
        //----------|------------|--------------------------|------------------------------
        // 01.00.00 | 2020-02-19 | Siemens SUP Applications | First released version
        //===============================================================================
    END_REGION
    
    REGION INITIALISATION  
        // TODO: Initialize functionality: reset of variables, diagnostics, etc.
        #tempLenAsUInt := 0;
        #tempLen := 0;
        #tempCounter := 0;
        #tempOffsetAsDInt := 0;
        #tempOffsetNext := 16#0000;
        #tempStatus := #STATUS_FINISHED_NO_ERROR;
    END_REGION
    
    REGION PROGRAM LOGIC
        Strg_TO_Chars(Strg := #inString,
                      pChars := 0,
                      Cnt => #tempLenAsUInt,
                      Chars := #tempConvBytes);
        #tempLen := UINT_TO_DINT(#tempLenAsUInt);
        
        //convert password (switch low<->high bits and xor 16#A5)
        FOR #tempCounter := 0 TO #tempLen - 1 DO
            #tempConvBytes[#tempCounter] := ROR(IN := #tempConvBytes[#tempCounter], N := 4) XOR 16#A5;
        END_FOR;
        
        //fill in 16#A5 bytes between each letter
        FOR #tempCounter := 0 TO #tempLen - 1 DO
            #data[#writer] := #tempConvBytes[#tempCounter];
            #writer += 1;
            #data[#writer] := 16#A5;
            #writer += 1;
        END_FOR;
        
        //set offset to length * 2 cause of filled in zero bytes
        #tempOffsetAsDInt := WORD_TO_DINT(#offsetOwn);
        #tempOffsetAsDInt := ROR(IN := #tempOffsetAsDInt, N := 8) + #tempLen * 2;
        #tempOffsetNext := DINT_TO_WORD(#tempOffsetAsDInt);
        #tempOffsetNext := ROL(IN := #tempOffsetNext, N := 8);
        
        //set length to the number of letters(low Byte, high Byte)
        #tempLength := ROL(IN := DINT_TO_WORD(#tempLen), N := 8);
    END_REGION
    
    REGION OUTPUTS
        #length := #tempLength;
        #offsetNext := #tempOffsetNext;
        #error := #tempStatus.%X15;
        #status := #tempStatus;
        // if ENO mechanism is not used / not wanted replace the following line by --> ENO := TRUE;
        ENO := NOT #tempStatus.%X15;
    END_REGION
    
END_FUNCTION

FUNCTION_BLOCK "LSql_Microsoft"
{ S7_Optimized_Access := 'TRUE' }
AUTHOR : '(department/personInCharge/contact)'
FAMILY : '(family)'
VERSION : 0.1
   VAR_INPUT 
      enable : Bool;
      connSettings {InstructionName := 'TCON_IP_v4'; LibVersion := '1.0'} : TCON_IP_v4;
      loginInfo : "LSql_typeLoginInformation";
      executeSqlCommand : Bool;
      sqlCommand : String;
      enableArchive : Bool;
      "db" : DB_ANY;
      dbmax : DInt;
   END_VAR

   VAR_OUTPUT 
      valid { ExternalWritable := 'False'} : Bool;
      busy { ExternalWritable := 'False'} : Bool;
      error { ExternalWritable := 'False'} : Bool;
      status { ExternalWritable := 'False'} : Word := #STATUS_NO_CALL;
      diagnostics { ExternalWritable := 'False'} : "LSql_typeDiagnostics";
      lastArchivElement { ExternalWritable := 'False'} : DInt;
      sqlCommandDone : Bool;
   END_VAR

   VAR 
      statEnableOld { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statEnable { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statValid { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statBusy { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statError { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statErrorUserCleared { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statErrorAutoCleared { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statStatus { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Word := #STATUS_NO_CALL;
      statSubfunctionStatus { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Word;
      statFBErrorState { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : DInt;
      statFBState { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : DInt := #FB_STATE_NO_PROCESSING;
      statDisablingCompleted { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statEmptyDiagnostics { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : "LSql_typeDiagnostics";
      statArchiveRequest { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statRcvData { ExternalWritable := 'False'} : "LSql_typeReceivedData";
      statExecuteSqlBatchOld { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statArchiveIndex { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : DInt;
      statWriteToArchiveStatus { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Word;
      statSqlBatchData { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : "LSql_typeSqlBatchData";
      statSqlBatch { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Array[0..2047] of Byte;
      statSqlBatchLen { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : DInt;
      statWriter { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Int;
      statStatusSqlBatchIntToWord { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Word;
      statLoginData { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : "LSql_typeLoginData";
      statLogin { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Array[0..2047] of Byte;
      statLoginLen { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : DInt;
      statStatusLoginIntToWord { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Word;
      statStatusPreLoginIntToWord { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Word;
      statPreLogin { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Array[0..2047] of Byte;
      statPreLoginLen { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : DInt;
      statPreLoginCache { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Array[0..2047] of Byte;
      statPreLoginData { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : "LSql_typePreloginData";
      instTsend {InstructionName := 'TSEND'; LibVersion := '4.0'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : TSEND;
      instTcon {InstructionName := 'TCON'; LibVersion := '4.0'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : TCON;
      instTrcv {InstructionName := 'TRCV'; LibVersion := '4.0'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : TRCV;
      instTdiscon {InstructionName := 'TDISCON'; LibVersion := '2.1'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : TDISCON;
      statErrorWriteData { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Bool;
      statWriteData { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Word;
      statSqlCommandDone : Bool;
      statTransmissionSqlCommand : Bool;
   END_VAR

   VAR_TEMP 
      tempPos : DInt;
      tempLength : Word;
      tempCounter : DInt;
      tempGaps : Int;
      tempLengthAsInt : DInt;
      tempLengthRor : DInt;
   END_VAR

   VAR CONSTANT 
      ERR_TDISCON_REMOTE_TERMINATION : Word := 16#80A3;
      FB_STATE_NO_PROCESSING : DInt := 0;
      FB_STATE_DISABLING : DInt := 1;
      FB_STATE_INITIAL_DISCON : DInt := 2;
      FB_STATE_CONNECT : DInt := 3;
      FB_STATE_PRELOGIN : DInt := 4;
      FB_STATE_LOGIN : DInt := 5;
      FB_STATE_SENDLOGIN : DInt := 6;
      FB_STATE_SQLBATCH : DInt := 7;
      STATUS_NO_CALL : Word := 16#7000;
      STATUS_FIRST_CALL : Word := 16#7001;
      STATUS_SUBSEQUENT_CALL : Word := 16#7002;
      STATUS_TDISCON_SUCCESSFUL : Word := 16#7011;
      SUB_STATUS_NO_ERROR : Word := 16#0000;
      ERR_UNDEFINED_STATE : Word := 16#8600;
      ERR_IN_BLOCK_OPERATION : Word := 16#8001;
      ERR_PARAMETRIZATION : Word := 16#8200;
      ERR_PROCESSING_EXTERN : Word := 16#8400;
      ERR_PROCESSING_INTERN : Word := 16#8600;
      ERR_AREA_RESERVED : Word := 16#8800;
      ERR_USER_DEFINED_CLASSES : Word := 16#9000;
      ERR_DISCONNECT : Word := 16#8601;
      ERR_CONNECT : Word := 16#8602;
      ERR_PRELOGIN : Word := 16#8603;
      ERR_PRELOGIN_DATA : Word := 16#8607;
      ERR_LOGIN : Word := 16#8604;
      ERR_LOGIN_DATA : Word := 16#8608;
      ERR_TRCV : Word := 16#8605;
      ERR_ARCHIVE : Word := 16#8606;
      ERR_SQLBATCH_DATA : Word := 16#8609;
      ERR_SQLBATCHSEND : Word := 16#8610;
   END_VAR


BEGIN
    REGION BLOCK INFO HEADER
        //=============================================================================
        // Siemens AG (c)Copyright (2019)
        //-----------------------------------------------------------------------------
        // Library:       SQL
        // Target:        S7-1500 / FW 2.6
        // Engineering:   TIA Portal V15.1
        // Restrictions:  no
        // Requirements:  CPU 1500
        // Functionality: For database access:
        //                set up connection to SQL server, send batch and save answers from server
        //-----------------------------------------------------------------------------
        // Change log table:
        // Version    Date         Expert in charge           Changes applied
        // 01.00.00   14.01.2019   Siemens SUP Applications   First released version
        //=============================================================================
    END_REGION
    
    #statEnable := #enable; // Work with temporary value / create process image
    
    REGION ENABLING/DISABLING
        IF (#statEnable = TRUE) AND (#statStatus = #STATUS_NO_CALL) THEN // Enable FB
            // First call; initialize FB
            #statValid := TRUE;
            #statBusy := TRUE;
            #statError := FALSE;
            #statErrorUserCleared := FALSE;
            #statErrorAutoCleared := FALSE;
            #statStatus := #STATUS_FIRST_CALL;
            #statSubfunctionStatus := #SUB_STATUS_NO_ERROR;
            #statFBErrorState := 0;
            #diagnostics := #statEmptyDiagnostics;
            #statDisablingCompleted := FALSE;
            #lastArchivElement := 0;
            // State machine - start functionality
            #statFBState := #FB_STATE_CONNECT;
            
            REGION Initialize functionality: call subsidiary FBs with FALSE
                #instTcon(REQ := FALSE,
                          ID := #connSettings.ID,
                          CONNECT := #connSettings);
                #instTrcv(EN_R := FALSE,
                          ID := #connSettings.ID,
                          DATA := #statRcvData);
                #instTdiscon(REQ := FALSE,
                             ID := #connSettings.ID);
                #instTsend(REQ := FALSE,
                           ID := #connSettings.ID,
                           LEN := DINT_TO_UINT(#statPreLoginLen),
                           DATA := #statPreLogin);
            END_REGION
            
            // BG TODO - IEC Konforme programmierung - in den baustein eigenschaften aktivieren
            // ein fehler taucht auf der mit Datntypen zusammenhängt
            
        ELSIF (#statEnable = FALSE) AND (#statEnableOld = TRUE) THEN // Disable FB 
            #statFBState := #FB_STATE_DISABLING;
            
        ELSIF (#statStatus = #STATUS_FIRST_CALL) THEN // Set status identifier of subsequent call
            #statStatus := #STATUS_SUBSEQUENT_CALL;
        END_IF;
        
        // Edge detection 'enable' input
        #statEnableOld := #statEnable;
    END_REGION ENABLING/DISABLING
    
    IF (#statStatus = #STATUS_NO_CALL) THEN // Nothing to do -> End here to reduce "system load"
        RETURN;
    END_IF;
    
    REGION STATE_MACHINE
        CASE #statFBState OF // State machine of FB
            #FB_STATE_NO_PROCESSING:
                REGION NO Processing
                    // No processing active (Note: this state must always be present and left empty)
                    ;
                END_REGION NO Processing
                
            #FB_STATE_INITIAL_DISCON:
                REGION make sure no connection is open before connecting          
                    #instTdiscon(REQ := TRUE,
                                 ID := #connSettings.ID);
                    
                    IF #instTdiscon.ERROR THEN
                        #statStatus := #ERR_DISCONNECT;
                        #statSubfunctionStatus := #instTdiscon.STATUS;
                        #statFBErrorState := #statFBState;
                        IF #ERR_TDISCON_REMOTE_TERMINATION = #instTdiscon.STATUS THEN
                            // disconnect done - proceed with wait for setup
                            // Error can be fixed by FB
                            #statStatus := #STATUS_TDISCON_SUCCESSFUL;
                            #instTdiscon(REQ := FALSE,
                                         ID := #connSettings.ID);
                            #statErrorAutoCleared := TRUE;
                            #statFBState := #FB_STATE_CONNECT;
                        ELSE
                            #statErrorUserCleared := TRUE;
                            #instTdiscon(REQ := FALSE,
                                         ID := #connSettings.ID);
                        END_IF;
                    ELSIF #instTdiscon.DONE THEN
                        #statStatus := #STATUS_TDISCON_SUCCESSFUL;
                        #instTdiscon(REQ := FALSE,
                                     ID := #connSettings.ID);
                        #statFBState := #FB_STATE_CONNECT;
                    END_IF;
                END_REGION ;
                
            #FB_STATE_CONNECT:
                REGION connecting
                    //if error was set in state before because of tdsicon error: #ERR_TDISCON_REMOTE_TERMINATION, reset
                    #statErrorAutoCleared := FALSE;
                    
                    #instTcon(REQ := TRUE,
                              ID := #connSettings.ID,
                              CONNECT := #connSettings);
                    
                    IF #instTcon.ERROR THEN
                        #statStatus := #ERR_CONNECT;
                        #statSubfunctionStatus := #instTcon.STATUS;
                        #statFBErrorState := #statFBState;
                        #statErrorUserCleared := TRUE;
                        #instTcon(REQ := FALSE,
                                  ID := #connSettings.ID,
                                  CONNECT := #connSettings);
                    ELSIF #instTcon.DONE THEN
                        #instTcon(REQ := FALSE,
                                  ID := #connSettings.ID,
                                  CONNECT := #connSettings);
                        #statFBState := #FB_STATE_PRELOGIN;
                    END_IF;
                END_REGION connecting
                
            #FB_STATE_PRELOGIN:
                REGION Prelogin active      
                    //copy data from data to cache
                    #statStatusPreLoginIntToWord := INT_TO_WORD(Serialize(SRC_VARIABLE := #statPreLoginData, DEST_ARRAY => #statPreLoginCache, POS := #tempPos));
                    
                    IF #statStatusPreLoginIntToWord > 16#0000 THEN
                        #statStatus := #ERR_PRELOGIN_DATA;
                        #statSubfunctionStatus := #statStatusPreLoginIntToWord;
                        #statFBErrorState := #statFBState;
                        #statErrorUserCleared := TRUE;
                    END_IF;
                    
                    //copy data from cache to preLogin (without unwanted filled zero bytes)
                    FOR #tempCounter := 0 TO #tempPos DO
                        //when serializing prelogin data a zero is added in between types
                        //delete these zeros
                        CASE #tempCounter OF
                            9, 15, 21, 27, 33, 39, 45, 51:
                                #tempGaps += 1;
                            ELSE
                                #statPreLogin[#tempCounter - #tempGaps] := #statPreLoginCache[#tempCounter];
                        END_CASE;
                        
                    END_FOR;
                    
                    //write number of bytes, that have to be transmitted, to preLoginLen (adjusted by the deleted gaps)
                    #statPreLoginLen := #tempPos - #tempGaps;
                    
                    #instTsend(REQ := TRUE,
                               ID := #connSettings.ID,
                               LEN := DINT_TO_UINT(#statPreLoginLen),
                               DATA := #statPreLogin);
                    
                    IF #instTsend.ERROR THEN
                        #statStatus := #ERR_PRELOGIN;
                        #statErrorUserCleared := TRUE;
                        #statSubfunctionStatus := #instTsend.STATUS;
                        #statFBErrorState := #statFBState;
                        #instTsend(REQ := FALSE,
                                   ID := #connSettings.ID,
                                   DATA := #statPreLogin);
                        
                    ELSIF #instTsend.DONE THEN
                        #instTsend(REQ := FALSE,
                                   ID := #connSettings.ID,
                                   DATA := #statPreLogin);
                        #statFBState := #FB_STATE_LOGIN;
                    END_IF;
                    
                END_REGION Prelogin
                
            #FB_STATE_LOGIN: // Login active
                REGION Login
                    REGION write login data to DB
                        #statWriter := 0; //varible for iterating through data array
                        #tempPos := 0;
                        
                        REGION write String TO data AND set length AND offset in offsetLength (hostName)
                            "LSql_WriteData"(inString := #loginInfo.hostName,
                                             offsetOwn := #statLoginData.login.offsetLength.hostName.offset,
                                             offsetNext => #statLoginData.login.offsetLength.userName.offset,
                                             length => #statLoginData.login.offsetLength.hostName.length,
                                             error => #statErrorWriteData,
                                             status => #statWriteData,
                                             data := #statLoginData.login.data,
                                             writer := #statWriter);
                            
                            //write string to data and set length and offset in offsetLength (userName)
                            "LSql_WriteData"(inString := #loginInfo.userName,
                                             offsetOwn := #statLoginData.login.offsetLength.userName.offset,
                                             offsetNext => #statLoginData.login.offsetLength.password.offset,
                                             length => #statLoginData.login.offsetLength.userName.length,
                                             error => #statErrorWriteData,
                                             status => #statWriteData,
                                             data := #statLoginData.login.data,
                                             writer := #statWriter);
                            
                            //write string to data and set length and offset in offsetLength (password)
                            "LSql_WritePassword"(inString := #loginInfo.password,
                                                 offsetOwn := #statLoginData.login.offsetLength.password.offset,
                                                 offsetNext => #statLoginData.login.offsetLength.appName.offset,
                                                 length => #statLoginData.login.offsetLength.password.length,
                                                 error => #statErrorWriteData,
                                                 status => #statWriteData,
                                                 data := #statLoginData.login.data,
                                                 writer := #statWriter);
                            
                            //write string to data and set length and offset in offsetLength (appName)
                            "LSql_WriteData"(inString := #loginInfo.appName,
                                             offsetOwn := #statLoginData.login.offsetLength.appName.offset,
                                             offsetNext => #statLoginData.login.offsetLength.serverName.offset,
                                             length => #statLoginData.login.offsetLength.appName.length,
                                             error => #statErrorWriteData,
                                             status => #statWriteData,
                                             data := #statLoginData.login.data,
                                             writer := #statWriter);
                            
                            //write string to data and set length and offset in offsetLength (serverName)
                            "LSql_WriteData"(inString := #loginInfo.serverName,
                                             offsetOwn := #statLoginData.login.offsetLength.serverName.offset,
                                             offsetNext => #statLoginData.login.offsetLength.unused.offset,
                                             length => #statLoginData.login.offsetLength.serverName.length,
                                             error => #statErrorWriteData,
                                             status => #statWriteData,
                                             data := #statLoginData.login.data,
                                             writer := #statWriter);
                            
                            //write string to data and set length and offset in offsetLength (unused)
                            "LSql_WriteData"(inString := '',
                                             offsetOwn := #statLoginData.login.offsetLength.unused.offset,
                                             offsetNext => #statLoginData.login.offsetLength.libraryName.offset,
                                             length => #statLoginData.login.offsetLength.unused.length,
                                             error => #statErrorWriteData,
                                             status => #statWriteData,
                                             data := #statLoginData.login.data,
                                             writer := #statWriter);
                            
                            //write string to data and set length and offset in offsetLength (libraryName)
                            "LSql_WriteData"(inString := #loginInfo.libraryName,
                                             offsetOwn := #statLoginData.login.offsetLength.libraryName.offset,
                                             offsetNext => #statLoginData.login.offsetLength.local.offset,
                                             length => #statLoginData.login.offsetLength.libraryName.length,
                                             error => #statErrorWriteData,
                                             status => #statWriteData,
                                             data := #statLoginData.login.data,
                                             writer := #statWriter);
                            
                            //wrtie string to data and set length and offset in offsetLength (local)
                            "LSql_WriteData"(inString := #loginInfo.local,
                                             offsetOwn := #statLoginData.login.offsetLength.local.offset,
                                             offsetNext => #statLoginData.login.offsetLength.databaseName.offset,
                                             length => #statLoginData.login.offsetLength.local.length,
                                             error => #statErrorWriteData,
                                             status => #statWriteData,
                                             data := #statLoginData.login.data,
                                             writer := #statWriter);
                            
                            //write string to data and set length and offset in offsetLength (databaseName)
                            "LSql_WriteData"(inString := #loginInfo.databaseName,
                                             offsetOwn := #statLoginData.login.offsetLength.databaseName.offset,
                                             offsetNext => #statLoginData.login.offsetLength.sspi.offset,
                                             length => #statLoginData.login.offsetLength.databaseName.length,
                                             error => #statErrorWriteData,
                                             status => #statWriteData,
                                             data := #statLoginData.login.data,
                                             writer := #statWriter);
                            
                            //string string to data and set length and offset in offsetLength (sspi)
                            "LSql_WriteData"(inString := #loginInfo.sspi,
                                             offsetOwn := #statLoginData.login.offsetLength.sspi.offset,
                                             offsetNext => #statLoginData.login.offsetLength.atchDbFile.offset,
                                             length => #statLoginData.login.offsetLength.sspi.length,
                                             error => #statErrorWriteData,
                                             status => #statWriteData,
                                             data := #statLoginData.login.data,
                                             writer := #statWriter);
                            
                            //string to data and set length and offset in offsetLength (atchDbFile)
                            "LSql_WriteData"(inString := #loginInfo.attachDbfile,
                                             offsetOwn := #statLoginData.login.offsetLength.atchDbFile.offset,
                                             offsetNext => #statLoginData.login.offsetLength.changePassword.offset,
                                             length => #statLoginData.login.offsetLength.atchDbFile.length,
                                             error => #statErrorWriteData,
                                             status => #statWriteData,
                                             data := #statLoginData.login.data,
                                             writer := #statWriter);
                            
                            //string to data and set length and offset in offsetLength (changePassword)
                            "LSql_WritePassword"(inString := #loginInfo.changePassword,
                                                 offsetOwn := #statLoginData.login.offsetLength.changePassword.offset,
                                                 offsetNext => #statLoginData.login.length,
                                                 length => #statLoginData.login.offsetLength.changePassword.length,
                                                 error => #statErrorWriteData,
                                                 status => #statWriteData,
                                                 data := #statLoginData.login.data,
                                                 writer := #statWriter);
                        END_REGION ;
                        
                        //set length parameters according to added data
                        #tempLengthAsInt := DWORD_TO_DINT(#statLoginData.login.length);
                        //rotate by 8 because word
                        #tempLengthRor := ROR(IN := #tempLengthAsInt, N := 8) + 8;
                        #statLoginData.header.length := DINT_TO_WORD(#tempLengthRor);
                        
                        //shift by 16 dword
                        #statLoginData.login.length := SHL(IN := #statLoginData.login.length, N := 16);
                        
                    END_REGION
                    
                    //copy standard data to login
                    #statStatusLoginIntToWord := INT_TO_WORD(Serialize(SRC_VARIABLE := #statLoginData, DEST_ARRAY => #statLogin, POS := #tempPos));
                    //check for error in INT_TO_WORD
                    IF #statStatusLoginIntToWord > 16#0000 THEN
                        #statStatus := #ERR_LOGIN_DATA;
                        #statSubfunctionStatus := #statStatusLoginIntToWord;
                        #statFBErrorState := #statFBState;
                        #statErrorUserCleared := TRUE;
                    END_IF;
                    
                    //write length to loginLen (adjusted by the length of the data (-1024+writer) and deleted gaps in the header)
                    #statLoginLen := #tempPos - 1024 + #statWriter;
                    // BG TODO - warum 1024 - nicht 2048?
                    // Keine magic numbers ;)
                    
                    //deletes last zero byte, if necessary
                    // BG TODO ein kommentar dazu der aussagt warum modulo 2 addiert wird wäre dann antürlich notwendig
                    #statLoginLen := #statLoginLen + #tempPos MOD 2;
                    
                    #statFBState := #FB_STATE_SENDLOGIN;
                END_REGION ; 
                
            #FB_STATE_SENDLOGIN:
                REGION send login data
                    #instTsend(REQ := #enable,
                               ID := #connSettings.ID,
                               LEN := DINT_TO_UINT(#statLoginLen),
                               DATA := #statLogin);
                    
                    IF #instTsend.ERROR THEN
                        #statStatus := #ERR_LOGIN;
                        #statSubfunctionStatus := #instTsend.STATUS;
                        #statFBErrorState := #statFBState;
                        #statErrorUserCleared := TRUE;
                        #instTsend(REQ := FALSE,
                                   ID := #connSettings.ID,
                                   DATA := #statLogin);
                    ELSIF #instTsend.DONE THEN
                        #instTsend(REQ := FALSE,
                                   ID := #connSettings.ID,
                                   DATA := #statLogin);
                        #statFBState := #FB_STATE_SQLBATCH;
                    END_IF;
                END_REGION send login data
                
            #FB_STATE_SQLBATCH:
                REGION sqlbatch execution
                    REGION call trcv
                        #instTrcv(EN_R := TRUE,
                                  ID := #connSettings.ID,
                                  DATA := #statRcvData);
                        
                        IF #instTrcv.ERROR THEN
                            #statStatus := #ERR_TRCV;
                            #statErrorUserCleared := TRUE;
                            #statSubfunctionStatus := #instTrcv.STATUS;
                            #statFBErrorState := #statFBState;
                        END_IF;
                    END_REGION
                    
                    REGION Archive
                        IF #enableArchive AND #statRcvData.len > 0 THEN
                            //write data to the arrayDB
                            #statWriteToArchiveStatus := INT_TO_WORD(WriteToArrayDB(db := #db, index := #statArchiveIndex, value := #statRcvData));
                            #lastArchivElement := #statArchiveIndex;
                            //raise the index for the next call
                            #statArchiveIndex += 1;
                            IF #statArchiveIndex > #dbmax THEN
                                #statArchiveIndex := 0;
                            END_IF;
                        END_IF;
                        
                        IF #statArchiveRequest AND (#statWriteToArchiveStatus > 16#0000) THEN
                            #statStatus := #ERR_ARCHIVE;
                            #statSubfunctionStatus := #statWriteToArchiveStatus;
                            #statFBErrorState := #statFBState;
                            #statErrorUserCleared := TRUE;
                        END_IF;
                    END_REGION Archive
                    
                    
                    REGION SqlBatch
                        //get positive edge of executeSqlBatch
                        IF #executeSqlCommand AND NOT #statExecuteSqlBatchOld THEN
                            
                            #statWriter := 0;
                            
                            //write string to data and set length parameter accoding to the added data
                            "LSql_WriteData"(inString := #sqlCommand,
                                             offsetOwn := 16#1E00,
                                             offsetNext => #statSqlBatchData.header.length,
                                             error => #statErrorWriteData,
                                             status => #statWriteData,
                                             length => #tempLength,
                                             data := #statSqlBatchData.sqlBatch.sqlText,
                                             writer := #statWriter);
                            
                            //transform (lowByte highByte) to (highByte lowByte)
                            #statSqlBatchData.header.length := ROR(IN := #statSqlBatchData.header.length, N := 8);
                            
                            //copy data from data to cache
                            #statStatusSqlBatchIntToWord := INT_TO_WORD(Serialize(SRC_VARIABLE := #statSqlBatchData, DEST_ARRAY => #statSqlBatch, POS := #tempPos));
                            //check for error in Int_To_Word Function
                            IF #statStatusSqlBatchIntToWord > 16#0000 THEN
                                #statStatus := #ERR_SQLBATCH_DATA;
                                #statSubfunctionStatus := #statStatusSqlBatchIntToWord;
                                #statFBErrorState := #statFBState;
                                #statErrorUserCleared := TRUE;
                            END_IF;
                            //write length to DB, adjusted by the length of the data (-1024+writer)
                            #statSqlBatchLen := #tempPos - 1024 + #statWriter;
                            
                            //deletes last zero byte, if necessary
                            
                            #statSqlBatchLen += #tempPos MOD 2;
                            #statTransmissionSqlCommand := TRUE;
                            #statExecuteSqlBatchOld := #executeSqlCommand;
                        END_IF; //finished creating stream 
                        
                        //get the negative edge 
                        IF NOT #executeSqlCommand AND #statExecuteSqlBatchOld THEN
                            #statExecuteSqlBatchOld := #executeSqlCommand;
                            #statSqlCommandDone := FALSE;
                        END_IF;
                        
                        //send data
                        IF #statTransmissionSqlCommand THEN
                            #instTsend(REQ := TRUE,
                                       ID := #connSettings.ID,
                                       LEN := DINT_TO_UINT(#statSqlBatchLen),
                                       DATA := #statSqlBatch);
                            //check for error in Tsend
                            IF #instTsend.ERROR THEN
                                #statStatus := #ERR_SQLBATCHSEND;
                                #statSubfunctionStatus := #instTsend.STATUS;
                                #statFBErrorState := #statFBState;
                                #statErrorUserCleared := TRUE;
                                #statTransmissionSqlCommand := FALSE;
                            ELSIF #instTsend.DONE THEN
                                #instTsend(REQ := FALSE,
                                           ID := #connSettings.ID,
                                           LEN := DINT_TO_UINT(#statSqlBatchLen),
                                           DATA := #statSqlBatch);
                                #statTransmissionSqlCommand := FALSE;
                                #statSqlCommandDone := TRUE;
                            END_IF;
                        END_IF;
                        
                    END_REGION SqlBatch
                    
                END_REGION sqlbatch execution
                
            #FB_STATE_DISABLING: // Disconnect active
                REGION Disabling
                    #instTrcv(EN_R := FALSE,
                              ID := #connSettings.ID,
                              DATA := #statRcvData);
                END_REGION ;
                REGION Disconnect
                    //disconnect function, closes the connection to the server
                    #instTdiscon(REQ := TRUE,
                                 ID := #connSettings.ID);
                    
                    IF #instTdiscon.ERROR THEN
                        #statStatus := #ERR_DISCONNECT;
                        #statSubfunctionStatus := #instTdiscon.STATUS;
                        #statFBErrorState := #statFBState;
                        IF #ERR_TDISCON_REMOTE_TERMINATION = #instTdiscon.STATUS THEN
                            #statDisablingCompleted := TRUE;
                            #instTdiscon(REQ := FALSE,
                                         ID := #connSettings.ID);
                        ELSE
                            #statErrorUserCleared := TRUE;
                            #instTdiscon(REQ := FALSE,
                                         ID := #connSettings.ID);
                        END_IF;
                        
                    ELSIF #instTdiscon.DONE THEN
                        #instTdiscon(REQ := FALSE,
                                     ID := #connSettings.ID);
                    END_IF;
                    
                    // BG TODO - hier noch mal schauen wann die Tags gesetzt werden - nach fehler oder done?
                    // Disabling completed beendet den baustein komplet, und statStatus wird auf "No Call" gesetzt,
                    // daurch kommt man hier nie mehr vorbei für die asynchrionen bedinungen
                    #statFBState := #FB_STATE_NO_PROCESSING;
                    
                    
                    #statDisablingCompleted := TRUE;
                END_REGION Disconnect
                
                
            ELSE // Undefined state in state machine reached
                REGION undefined state        
                    #statStatus := #ERR_UNDEFINED_STATE;
                    #statErrorUserCleared := TRUE;
                END_REGION undefined state
        END_CASE;
    END_REGION STATE_MACHINE
    
    REGION OUTPUTS
        // Write outputs
        IF (#statDisablingCompleted = TRUE) THEN
            REGION disabling
                // Reset outputs if disabling completed
                #statValid := FALSE;
                #statBusy := FALSE;
                #statError := FALSE;
                #statErrorUserCleared := FALSE;
                #statErrorAutoCleared := FALSE;
                #statStatus := #STATUS_NO_CALL;
                
            END_REGION disabling
            
        ELSIF (#statErrorUserCleared = TRUE) AND (#statError = FALSE) THEN
            REGION error user cleared
                // Error can only be cleared by user; rising edge at enable input is needed to continue 
                #statValid := FALSE;
                #statBusy := FALSE;
                #statError := TRUE;
                // Write diagnostics
                #diagnostics.status := #statStatus;
                #diagnostics.subfunctionStatus := #statSubfunctionStatus;
                #diagnostics.stateNumber := #statFBErrorState;
                // execution aborted --> set state no processing
                #statFBState := #FB_STATE_NO_PROCESSING;
            END_REGION error user cleared
            
        ELSIF (#statErrorAutoCleared = TRUE) AND (#statError = FALSE) THEN
            REGION error auto cleared
                // Error can be reset by FB automatically
                #statValid := FALSE;
                #statBusy := TRUE;
                #statError := TRUE;
                // Write diagnostics
                #diagnostics.status := #statStatus;
                #diagnostics.subfunctionStatus := #statSubfunctionStatus;
                #diagnostics.stateNumber := #statFBErrorState;
            END_REGION error auto cleared
            
        ELSIF (#statErrorAutoCleared = FALSE) AND (#statErrorUserCleared = FALSE) AND (#statError = TRUE) THEN
            REGION after user/auto cleared
                // If autocleared error is acknowledged
                #statValid := TRUE;
                #statBusy := TRUE;
                #statError := FALSE;
                // TODO: remove this line if more detailed status information is used instead of "#STATUS_SUBSEQUENT_CALL"
                #statStatus := #STATUS_SUBSEQUENT_CALL;
            END_REGION after user/auto cleared
        END_IF;
        
        REGION Write static values to outputs
            #valid := #statValid;
            #busy := #statBusy;
            #error := #statError;
            #status := #statStatus;
            #sqlCommandDone := #statSqlCommandDone;
            // if ENO mechanism is not used / not wanted replace the following line by --> ENO := TRUE;
            ENO := NOT #statError;
            
        END_REGION Write static values to outputs
    END_REGION OUTPUTS
END_FUNCTION_BLOCK



0

评论 (0)

取消