/******************************************************************************/
// @copyright   Copyright Notification
//   No part may be reproduced except as authorized by written permission.
//   The copyright and the foregoing restriction extend to reproduction in all media.
//   Trademark 2010, 3GPP Organizational Partners (ARIB, ATIS, CCSA, ETSI, TTA, TTC).
//   All rights reserved.
// @version: IWD_10wk37
// $Date: 2010-06-04 14:58:42 +0200 (Fri, 04 Jun 2010) $
// $Rev: 3274 $
/******************************************************************************/

module EUTRA_Timing {
  import from CommonDefs all;
  import from EUTRA_RRC_ASN1_Definitions language "ASN.1:1997" all;
  import from EUTRA_CommonDefs all;
  import from EUTRA_ASP_TypeDefs all;
  import from EUTRA_ASP_SrbDefs all;
  import from EUTRA_CellInfo all;
  import from EUTRA_Component all;
  import from EUTRA_SRB_Templates all;
  import from EUTRA_AspCommon_Templates all;

  //****************************************************************************
  // Handling Timers
  //----------------------------------------------------------------------------
  group TimersAndTimerValues {
  //============================================================================
  /*
   * @desc      calculate timer tolerance acc. ti 36.508 cl. 6.7
   * @param     p_CellId
   * @param     p_Timer
   * @return    float .. timer value
   * @status    APPROVED
   */
  function fl_EUTRA_TimerTolerance(CellId_Type p_CellId,
                                   float p_Timer) runs on EUTRA_PTC return float
  { /* calculate timer tolerance acc. to 36.508 cl. 6.7 */
    var EUTRA_FDD_TDD_Mode_Type v_FDD_TDD := f_EUTRA_CellInfo_GetFDD_TDD(p_CellId);
    var EUTRA_CellSysInfo_Type v_EUTRA_CellSysInfo;
    var TDD_Config v_Tdd_Configuration;
    var float v_Tolerance;
    var float v_Rtt_x_5;
    var integer v_RTT;

    if (v_FDD_TDD == FDD) {
      v_RTT := 8;
    } else {
      v_EUTRA_CellSysInfo := f_EUTRA_CellInfo_GetSYSINFO(p_CellId);
      v_Tdd_Configuration := v_EUTRA_CellSysInfo.BCCH_Info.SIB1.message_.c1.systemInformationBlockType1.tdd_Config;

      select (v_Tdd_Configuration.subframeAssignment) {    /* Maximum RTT acc. to 36.508 Table 6.7-1 */

        case (sa0) { v_RTT := 10; }
        case (sa1) { v_RTT := 11; }
        case (sa2) { v_RTT := 12; }
        case (sa3) { v_RTT := 15; }
        case (sa4) { v_RTT := 16; }
        case (sa5) { FatalError(__FILE__, __LINE__, "RTT not specified yet for subframeAssignment=sa5"); }
        case (sa6) { v_RTT := 11; }
        case else  { FatalError(__FILE__, __LINE__, "invalid subframeAssignment"); }
      }
    }
    v_Rtt_x_5 := int2float(v_RTT) * 0.001 * 5.0;  /* RTT * TTI * 5 */
    v_Tolerance := p_Timer * 0.1;
    if (v_Tolerance < v_Rtt_x_5) {
      v_Tolerance := v_Rtt_x_5;
    }
    return v_Tolerance;
  }

  //----------------------------------------------------------------------------
  /*
   * @desc      to calculate lower bound of timer tolerance acc. to 36.508 cl. 6.7
   * @param     p_CellId
   * @param     p_Timer
   * @return    float
   * @status    APPROVED
   */
  function f_EUTRA_SetTimerToleranceMin(CellId_Type p_CellId,
                                        float p_Timer) runs on EUTRA_PTC return float
  {
    var float v_Tolerance := fl_EUTRA_TimerTolerance(p_CellId, p_Timer);
    if (p_Timer < v_Tolerance) {
      return 0.0;
    }
    return p_Timer - v_Tolerance;
  }

  //----------------------------------------------------------------------------
  /*
   * @desc      calculate upper bound of timer tolerance acc. to 36.508 cl. 6.7
   * @param     p_CellId
   * @param     p_Timer
   * @return    float .. timer value
   * @status    APPROVED
   */
  function f_EUTRA_SetTimerToleranceMax(CellId_Type p_CellId,
                                        float p_Timer) runs on EUTRA_PTC return float
  {
    var float v_Tolerance := fl_EUTRA_TimerTolerance(p_CellId, p_Timer);
    return p_Timer + v_Tolerance;
  }

  //----------------------------------------------------------------------------
  /*
   * @desc      calculate difference between p_T1 and p_T2 (in milliseconds)
   * @param     p_T1
   * @param     p_T2
   * @return    integer
   * @status    APPROVED
   */
  function f_EUTRA_SubFrameTimingDuration(SubFrameTiming_Type p_T1,
                                          SubFrameTiming_Type p_T2) return integer
  {
    var integer v_MillisecondsT1;
    var integer v_MillisecondsT2;
    var integer v_SubframeNumberT1 := 0;
    var integer v_SubframeNumberT2 := 0;
    var integer v_MaxValue := 10240;   // SFN is 0..1023 in steps of 10ms
    var integer v_Duration;

    if (ischosen(p_T1.Subframe.Number) and
        ischosen(p_T2.Subframe.Number)) {
      v_SubframeNumberT1 := p_T1.Subframe.Number;
      v_SubframeNumberT2 := p_T2.Subframe.Number;
    }
    v_MillisecondsT1 := (p_T1.SFN.Number * 10) + v_SubframeNumberT1;
    v_MillisecondsT2 := (p_T2.SFN.Number * 10) + v_SubframeNumberT2;

    v_Duration := (v_MaxValue + v_MillisecondsT2 - v_MillisecondsT1) mod v_MaxValue;  // calculation considers wrap-around

    return v_Duration;
  };

  //----------------------------------------------------------------------------

  /*
   * @desc      same as f_EUTRA_SubFrameTimingDuration but returns difference as float
   * @param     p_T1 .. first (earlier) value
   * @param     p_T2 .. second (later) value
   * @return    float .. difference in milliseconds
   * @status    APPROVED
   */
  function f_EUTRA_SubFrameTimingDuration_Float(SubFrameTiming_Type p_T1,
                                                SubFrameTiming_Type p_T2) return float
  {
    return int2float(f_EUTRA_SubFrameTimingDuration(p_T1, p_T2)) / 1000.0;
  }

  //----------------------------------------------------------------------------
  /*
   * @desc      check whether given duration is valid considering expected duration and given tolerance
   * @param     p_CellId
   * @param     p_Duration .. messured duration (in milliseconds)
   * @param     p_ExpectedDuration .. duration as specified by the standard (in milliseconds)
   * @return    boolean
   * @status    APPROVED
   */
  function f_EUTRA_SubFrameTimingCheckDuration(CellId_Type p_CellId,
                                               integer p_Duration,
                                               integer p_ExpectedDuration) runs on EUTRA_PTC return boolean
  {
    var float v_ExpectedValue := int2float(p_ExpectedDuration) / 1000.0;
    var float v_MeasuredValue := int2float(p_Duration) / 1000.0;
    var float v_Tolerance := fl_EUTRA_TimerTolerance(p_CellId, v_ExpectedValue);
    var float v_Min := v_ExpectedValue - v_Tolerance;
    var float v_Max := v_ExpectedValue + v_Tolerance;

    return ((v_Min <= v_MeasuredValue) and (v_MeasuredValue <= v_Max));
  };

  //----------------------------------------------------------------------------

  /*
   * @desc      To calculate the timestamp which is p_MilliSeconds after the given timestamp
   *            If this function is used to calculate timestamps for DL scheduling of data or
   *            UL grants the subframe number incoming must be a valid one for the intended purpose.
   *            This cannot be checked by the present function.
   * @param     p_TimingInfo
   * @param     p_MilliSeconds
   * @return    SubFrameTiming_Type
   * @status    APPROVED
   */
  function f_EUTRA_TimingInfoAdd(SubFrameTiming_Type p_TimingInfo,
                                 UInt_Type p_MilliSeconds)  return SubFrameTiming_Type
  {
    var SubFrameTiming_Type v_TimingInfo;
    var UInt_Type v_SubframeNumber;
    var UInt_Type v_Subframes;

    if (ischosen(p_TimingInfo.Subframe.Number)) {
      v_SubframeNumber := p_TimingInfo.Subframe.Number;
    } else {
      v_SubframeNumber := tsc_EUTRA_SN4;
    }
    v_Subframes := v_SubframeNumber + p_MilliSeconds;
    v_TimingInfo.Subframe.Number := v_Subframes mod 10;
    v_TimingInfo.SFN.Number      := (p_TimingInfo.SFN.Number + (v_Subframes / 10)) mod 1024;

    return v_TimingInfo;
  }

  //----------------------------------------------------------------------------
  } // end of group TimersAndTimerValues
  //============================================================================

  //****************************************************************************
  // Handling Racing conditions
  //----------------------------------------------------------------------------
  group SystemTiming {
  //============================================================================
  /*
   * @desc      Return the current SFN and subframe
   * @param     p_CellId
   * @return    SubFrameTiming_Type
   * @status    APPROVED
   */
  function f_EUTRA_GetCurrentTiming ( CellId_Type p_CellId ) runs on EUTRA_PTC return SubFrameTiming_Type
  {
    var SYSTEM_CTRL_CNF v_ReceivedCnf;
    var SubFrameTiming_Type v_Timing;

    SYS.send ( cas_EnquireTiming ( p_CellId ) );
    SYS.receive ( car_EnquireTiming ( p_CellId ) ) -> value v_ReceivedCnf;
    v_Timing := v_ReceivedCnf.Common.TimingInfo.SubFrame;

    return v_Timing;
  };

  /*
   * @desc      get the next send occasion;
   *            in case of TDD when the resulting subframe number is not 0
   *            it will be reset to 0 and the SFN is incremented by 1
   * @param     p_CellId
   * @param     p_MilliSeconds            (default value: 100)  ...  minimum time (in ms) before the next send occasion
   * @return    SubFrameTiming_Type
   * @status    APPROVED
   */
  function f_EUTRA_GetNextSendOccasion ( CellId_Type p_CellId,
                                         UInt_Type p_MilliSeconds := 100) runs on EUTRA_PTC return SubFrameTiming_Type
  { /* Notes:
     * - independent of FDD or TDD the Subframe Number is rese to 0 if necessary, so the the returned timing info is 100 <= timing info < 110 ms in advance
     * - when connect mode DRX needs to be supported, this can be done within this function
     * - the same is with measurement gaps */
    /* @sic R5s090368 sic@ */
    /* @sic R5s100172 sic@ */
    /* @sic R5s100224: SF4 to be used as it is valid for both TDD and FDD and different bandwidths sic@ */
    var SubFrameTiming_Type v_Timing;
    var SubFrameTiming_Type v_NextSendOccasion;

    if (p_MilliSeconds < 100) {
      FatalError(__FILE__, __LINE__, "next send occasion shall be at least 100ms in advance");
    }
    v_Timing := f_EUTRA_GetCurrentTiming(p_CellId);
    v_NextSendOccasion := f_EUTRA_TimingInfoAdd(v_Timing, p_MilliSeconds);

    if (v_NextSendOccasion.Subframe.Number > tsc_EUTRA_SN4) {      /* @sic R5s100224 sic@ */
      v_NextSendOccasion.SFN.Number := (v_NextSendOccasion.SFN.Number + 1) mod 1024;
    }
    v_NextSendOccasion.Subframe.Number := tsc_EUTRA_SN4;           /* @sic R5s100224 sic@ */
    return v_NextSendOccasion;
  }
  //----------------------------------------------------------------------------
  } // end of group SystemTiming
  //============================================================================

  //****************************************************************************
  // Handling Racing conditions
  //----------------------------------------------------------------------------
  group RacingConditionHandling {
  
  type record of SubFrameTiming_Type EventList_Type;   /* local type definition; @status    APPROVED */
  type enumerated RacingCond_ExpectedSequence_Type { RrcMsgBeforeSysInd, SysIndBeforeRrcMsg, AnyOrder };  /* @status    APPROVED */

  //----------------------------------------------------------------------------
  /*
   * @desc      check whether p_Event1 has been before p_Event2
   * @param     p_Event1
   * @param     p_Event2
   * @return    boolean
   * @status    APPROVED
   */
  function f_EUTRA_RacingCond_CheckTwoEvents(SubFrameTiming_Type p_Event1,
                                             SubFrameTiming_Type p_Event2) return boolean
  { // @sc R5s100116 Change 11 sic@
    var integer v_MilliSeconds;
    var boolean v_Result := true;

    v_MilliSeconds := f_EUTRA_SubFrameTimingDuration(p_Event1, p_Event2);
    if (v_MilliSeconds > 5120) {
      /* the wrap-around of the SFN is 10.24 seconds =>
       * if v_MilliSeconds is too big we assume Event2 has been occured before Event1 */
      v_Result := false;
    }
    return v_Result;
  }

  //============================================================================
  /*
   * @desc      check whether the given sequence of timestamps are "one after the other" in terms of racing conditions
   * @param     p_TimestampList
   * @return    boolean    true: all events are in the expected order
   * @status    APPROVED
   */
  function f_EUTRA_RacingCond_CheckSequence(EventList_Type p_TimestampList) return boolean
  { // @sc R5s100116 Change 11 sic@
    var integer i;
    var integer v_NoOfEvents := lengthof(p_TimestampList);
    var boolean v_Result := true;

    for (i:=0; i < v_NoOfEvents-1; i:=i+1) {
      if (not f_EUTRA_RacingCond_CheckTwoEvents(p_TimestampList[i], p_TimestampList[i+1])) {
        v_Result := false;
        break;
      }
    }
    return v_Result;
  }

  //============================================================================
  /*
   * @desc      check whether SYSTEM_IND and SRB_COMMON_IND have been in the correct sequence
   * @param     p_ExpectedSequence
   * @param     p_Timing_SrbInd
   * @param     p_Timing_SysInd
   * @status    APPROVED
   */
  function fl_EUTRA_RacingCond_CheckSequence(RacingCond_ExpectedSequence_Type p_ExpectedSequence,
                                             SubFrameTiming_Type p_Timing_SrbInd,
                                             SubFrameTiming_Type p_Timing_SysInd)
  { /* @sic R5s100138 Change 1 Note 5 sic@ */
    var SubFrameTiming_Type v_FirstEvent;
    var SubFrameTiming_Type v_SecondEvent;
    
    select (p_ExpectedSequence) {
      case (RrcMsgBeforeSysInd) {
        v_FirstEvent := p_Timing_SrbInd;
        v_SecondEvent := p_Timing_SysInd;
      }
      case (SysIndBeforeRrcMsg) {
        v_FirstEvent := p_Timing_SysInd;
        v_SecondEvent := p_Timing_SrbInd;
      }
      case (AnyOrder) {
        return;
      }
    }
    if (not f_EUTRA_RacingCond_CheckTwoEvents(v_FirstEvent, v_SecondEvent)) {
      f_EUTRA_SetVerdictInconc(__FILE__, __LINE__, "SYSTEM_IND and RRC message are in the wrong order");  // @sic R5-101050: Verdict assignment sic@
    }
  }
  
  /*
   * @desc      to deal with racing conditions:
   *            SYSTEM_IND and SRB_COMMON_IND are just stored and returned to the caller;
   *            there may be one or no SYSTEM_IND before the RRC message is received;
   *            if there is a SYSTEM_IND before the SRB_COMMON_IND the timing info is checked
   *            (i.e. it is checked that the RRC message has been received before the SYSTEM_IND)
   *            NOTE: p_SrbIndByRef and p_SysIndByRef shall be initialised as omit by the caller
   *                  (that cannot be done within the altstep)
   * @param     p_ExpectedSequence
   * @param     p_SrbIndByRef       (by reference)
   * @param     p_SysIndByRef       (by reference)
   * @param     p_ExpectedSrbInd    (default value: car_SRB_IND_Any)
   * @param     p_ExpectedSysInd    (default value: car_SYSTEM_IND_Any)
   * @status    APPROVED
   */
  altstep a_EUTRA_RacingCond_AwaitRrcMessageAndSysInd(RacingCond_ExpectedSequence_Type p_ExpectedSequence,
                                                      inout template (omit) SRB_COMMON_IND p_SrbIndByRef,
                                                      inout template (omit) SYSTEM_IND p_SysIndByRef,
                                                      template (present) SRB_COMMON_IND p_ExpectedSrbInd := car_SRB_IND_Any,
                                                      template (present) SYSTEM_IND p_ExpectedSysInd := car_SYSTEM_IND_Any) runs on EUTRA_PTC
  { /* note: since this is an altstep 'interleave' cannot be used */
    /* note: since the altstep uses repeat to receive the second message the altstep is left and re-entered again
     *       => local variables are uninitialised; the only way to store information of subsequent messages is by the inout parameters
     *          (which for that reason have to be "inout"; "out" would not work */
    /* @sic R5s100138 Change 1 Note 5 sic@ */
    /* @sic R5s100189 Change 13 sic@ */
    var SRB_COMMON_IND v_SRB_COMMON_IND;
    var SYSTEM_IND v_SYSTEM_IND;
    var SubFrameTiming_Type v_Timing_SysInd;
    var SubFrameTiming_Type v_Timing_SrbInd;

    [not isvalue(p_SrbIndByRef)] SRB.receive (p_ExpectedSrbInd) -> value v_SRB_COMMON_IND
      {
        p_SrbIndByRef := v_SRB_COMMON_IND;
        if (not isvalue(p_SysIndByRef)) {                               // SYSTEM_IND has not been received yet
          repeat;                                                       // => wait for SYSTEM_IND
        } else {
          v_Timing_SrbInd := valueof(p_SrbIndByRef.Common.TimingInfo.SubFrame);
          v_Timing_SysInd := valueof(p_SysIndByRef.Common.TimingInfo.SubFrame);  // R5s100189
          fl_EUTRA_RacingCond_CheckSequence(p_ExpectedSequence, v_Timing_SrbInd, v_Timing_SysInd);
        }
      }
    [not isvalue(p_SysIndByRef)] SYSIND.receive (p_ExpectedSysInd) -> value v_SYSTEM_IND
      {
        p_SysIndByRef := v_SYSTEM_IND;
        if (not isvalue(p_SrbIndByRef)) {                               // RRC message has not been received yet
          repeat;                                                       // => wait for RRC PDU
        } else {
          v_Timing_SrbInd := valueof(p_SrbIndByRef.Common.TimingInfo.SubFrame);  // R5s100189
          v_Timing_SysInd := valueof(p_SysIndByRef.Common.TimingInfo.SubFrame);
          fl_EUTRA_RacingCond_CheckSequence(p_ExpectedSequence, v_Timing_SrbInd, v_Timing_SysInd);
        }
      }
  }

  /*
   * @desc      wrapper function to be used when there is no further parallel behaviour
   *            RRC message is expecetd to be before the SYSTEM_IND
   *            SYSTEM_IND is just received, stored in p_SysIndByRef but needs to be matched in a second step
   * @param     p_ExpectedSrbInd
   * @param     p_SrbIndByRef       (by reference)
   * @param     p_SysIndByRef       (by reference)
   * @status    APPROVED
   */
  function f_EUTRA_RacingCond_AwaitRrcMessageBeforeSysInd(template (present) SRB_COMMON_IND p_ExpectedSrbInd,
                                                          out template (omit) SRB_COMMON_IND p_SrbIndByRef,
                                                          out template (omit) SYSTEM_IND p_SysIndByRef) runs on EUTRA_PTC
  { /* @sic R5s100138 Change 1 Note 5 sic@ */
    p_SrbIndByRef := omit;
    p_SysIndByRef := omit;

    alt {
      [] a_EUTRA_RacingCond_AwaitRrcMessageAndSysInd(RrcMsgBeforeSysInd,
                                                     p_SrbIndByRef,
                                                     p_SysIndByRef,
                                                     p_ExpectedSrbInd);
    }
  }
  /*
   * @desc      wrapper function to be used when there is no further parallel behaviour
   *            SYSTEM_IND is expecetd to be before the RRC message
   *            RRC message is just received, stored in p_SysIndByRef but needs to be matched in a second step
   * @param     p_ExpectedSysInd
   * @param     p_SrbIndByRef       (by reference)
   * @param     p_SysIndByRef       (by reference)
   * @status    APPROVED
   */
  function f_EUTRA_RacingCond_AwaitSysIndBeforeSrbMsg(template (present) SYSTEM_IND p_ExpectedSysInd,
                                                      out template (omit) SRB_COMMON_IND p_SrbIndByRef,
                                                      out template (omit) SYSTEM_IND p_SysIndByRef) runs on EUTRA_PTC
  {
    p_SrbIndByRef := omit;
    p_SysIndByRef := omit;

    alt {
      [] a_EUTRA_RacingCond_AwaitRrcMessageAndSysInd(SysIndBeforeRrcMsg,
                                                     p_SrbIndByRef,
                                                     p_SysIndByRef,
                                                     -,
                                                     p_ExpectedSysInd);
    }
  }

  /*
   * @desc      match SYS_IND
   * @param     p_ReceivedSysInd
   * @param     p_ExpectedSysInd
   * @status    APPROVED
   */
  function f_EUTRA_RacingCond_MatchSysInd(template (omit) SYSTEM_IND p_ReceivedSysInd,
                                          template SYSTEM_IND p_ExpectedSysInd) runs on EUTRA_PTC
  { /* @sic R5s100138 Change 1 Note 5 sic@ */
    var boolean v_Match;
    
    if (not isvalue(p_ReceivedSysInd)) {
      v_Match := false;
    } else {
      if (not match(valueof(p_ReceivedSysInd), p_ExpectedSysInd)) {
        v_Match := false;
      } else {
        v_Match := true;
      }
    }
    if (not v_Match) {
      f_EUTRA_SetVerdictFailOrInconc(__FILE__, __LINE__, "SYSTEM_IND does not match");
    }
  }

  /*
   * @desc      match RRC message
   * @param     p_ReceivedRrcInd
   * @param     p_ExpectedRrcInd
   * @status    APPROVED
   */
  function f_EUTRA_RacingCond_MatchRrcMsg(template (omit) SRB_COMMON_IND p_ReceivedRrcInd,
                                          template SRB_COMMON_IND p_ExpectedRrcInd) runs on EUTRA_PTC
  {
    var boolean v_Match;
    
    if (not isvalue(p_ReceivedRrcInd)) {
      v_Match := false;
    } else {
      if (not match(valueof(p_ReceivedRrcInd), p_ExpectedRrcInd)) {
        v_Match := false;
      } else {
        v_Match := true;
      }
    }
    if (not v_Match) {
      f_EUTRA_SetVerdictFailOrInconc(__FILE__, __LINE__, "received RRC message does not match");
    }
  }

  //----------------------------------------------------------------------------
  } // end of group RacingConditionHandling
  //============================================================================

}
