/** @file
Parser for IFR binary encoding.
Copyright (c) 2007 - 2016, Intel Corporation. All rights reserved.
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include "Setup.h"
UINT16           mStatementIndex;
UINT16           mExpressionOpCodeIndex;
EFI_QUESTION_ID  mUsedQuestionId;
extern LIST_ENTRY      gBrowserStorageList;
/**
  Initialize Statement header members.
  @param  OpCodeData             Pointer of the raw OpCode data.
  @param  FormSet                Pointer of the current FormSet.
  @param  Form                   Pointer of the current Form.
  @return The Statement.
**/
FORM_BROWSER_STATEMENT *
CreateStatement (
  IN UINT8                        *OpCodeData,
  IN OUT FORM_BROWSER_FORMSET     *FormSet,
  IN OUT FORM_BROWSER_FORM        *Form
  )
{
  FORM_BROWSER_STATEMENT    *Statement;
  EFI_IFR_STATEMENT_HEADER  *StatementHdr;
  INTN                      ConditionalExprCount; 
  if (Form == NULL) {
    //
    // Only guid op may out side the form level.
    //
    ASSERT (((EFI_IFR_OP_HEADER *) OpCodeData)->OpCode == EFI_IFR_GUID_OP);
  }
  Statement = &FormSet->StatementBuffer[mStatementIndex];
  mStatementIndex++;
  InitializeListHead (&Statement->DefaultListHead);
  InitializeListHead (&Statement->OptionListHead);
  InitializeListHead (&Statement->InconsistentListHead);
  InitializeListHead (&Statement->NoSubmitListHead);
  InitializeListHead (&Statement->WarningListHead);
  Statement->Signature = FORM_BROWSER_STATEMENT_SIGNATURE;
  Statement->Operand = ((EFI_IFR_OP_HEADER *) OpCodeData)->OpCode;
  Statement->OpCode  = (EFI_IFR_OP_HEADER *) OpCodeData;
  StatementHdr = (EFI_IFR_STATEMENT_HEADER *) (OpCodeData + sizeof (EFI_IFR_OP_HEADER));
  CopyMem (&Statement->Prompt, &StatementHdr->Prompt, sizeof (EFI_STRING_ID));
  CopyMem (&Statement->Help, &StatementHdr->Help, sizeof (EFI_STRING_ID));
  ConditionalExprCount = GetConditionalExpressionCount(ExpressStatement);
  if (ConditionalExprCount > 0) {
    //
    // Form is inside of suppressif
    //
    
    Statement->Expression = (FORM_EXPRESSION_LIST *) AllocatePool( 
                                             (UINTN) (sizeof(FORM_EXPRESSION_LIST) + ((ConditionalExprCount -1) * sizeof(FORM_EXPRESSION *))));
    ASSERT (Statement->Expression != NULL);
    Statement->Expression->Count     = (UINTN) ConditionalExprCount;
    Statement->Expression->Signature = FORM_EXPRESSION_LIST_SIGNATURE;
    CopyMem (Statement->Expression->Expression, GetConditionalExpressionList(ExpressStatement), (UINTN) (sizeof (FORM_EXPRESSION *) * ConditionalExprCount));
  }
  //
  // Insert this Statement into current Form
  //
  if (Form == NULL) {
    InsertTailList (&FormSet->StatementListOSF, &Statement->Link);
  } else {
    InsertTailList (&Form->StatementListHead, &Statement->Link);
  }
  return Statement;
}
/**
  Convert a numeric value to a Unicode String and insert it to String Package.
  This string is used as the Unicode Name for the EFI Variable. This is to support
  the deprecated vareqval opcode.
  @param FormSet        The FormSet.
  @param Statement      The numeric question whose VarStoreInfo.VarName is the
                        numeric value which is used to produce the Unicode Name
                        for the EFI Variable.
  If the Statement is NULL, the ASSERT.
  If the opcode is not Numeric, then ASSERT.
  @retval EFI_SUCCESS The funtion always succeeds.
**/
EFI_STATUS
UpdateCheckBoxStringToken (
  IN CONST FORM_BROWSER_FORMSET *FormSet,
  IN       FORM_BROWSER_STATEMENT *Statement
  )
{
  CHAR16                  Str[MAXIMUM_VALUE_CHARACTERS];
  EFI_STRING_ID           Id;
  ASSERT (Statement != NULL);
  ASSERT (Statement->Operand == EFI_IFR_NUMERIC_OP);
  UnicodeValueToString (Str, 0, Statement->VarStoreInfo.VarName, MAXIMUM_VALUE_CHARACTERS - 1);
  Id = HiiSetString (FormSet->HiiHandle, 0, Str, NULL);
  if (Id == 0) {
    return EFI_OUT_OF_RESOURCES;
  }
  Statement->VarStoreInfo.VarName = Id;
  return EFI_SUCCESS;
}
/**
  Check if the next opcode is the EFI_IFR_EXTEND_OP_VAREQNAME.
  @param OpCodeData     The current opcode.
  @retval TRUE Yes.
  @retval FALSE No.
**/
BOOLEAN
IsNextOpCodeGuidedVarEqName (
  IN UINT8 *OpCodeData
  )
{
  //
  // Get next opcode
  //
  OpCodeData += ((EFI_IFR_OP_HEADER *) OpCodeData)->Length;
  if (*OpCodeData == EFI_IFR_GUID_OP) {
    if (CompareGuid (&gEfiIfrFrameworkGuid, (EFI_GUID *)(OpCodeData + sizeof (EFI_IFR_OP_HEADER)))) {
      //
      // Specific GUIDed opcodes to support IFR generated from Framework HII VFR
      //
      if ((((EFI_IFR_GUID_VAREQNAME *) OpCodeData)->ExtendOpCode) == EFI_IFR_EXTEND_OP_VAREQNAME) {
        return TRUE;
      }
    }
  }
  return FALSE;
}
/**
  Initialize Question's members.
  @param  OpCodeData             Pointer of the raw OpCode data.
  @param  FormSet                Pointer of the current FormSet.
  @param  Form                   Pointer of the current Form.
  @return The Question.
**/
FORM_BROWSER_STATEMENT *
CreateQuestion (
  IN UINT8                        *OpCodeData,
  IN OUT FORM_BROWSER_FORMSET     *FormSet,
  IN OUT FORM_BROWSER_FORM        *Form
  )
{
  FORM_BROWSER_STATEMENT   *Statement;
  EFI_IFR_QUESTION_HEADER  *QuestionHdr;
  LIST_ENTRY               *Link;
  FORMSET_STORAGE          *Storage;
  NAME_VALUE_NODE          *NameValueNode;
  EFI_STATUS               Status;
  BOOLEAN                  Find;
  Statement = CreateStatement (OpCodeData, FormSet, Form);
  if (Statement == NULL) {
    return NULL;
  }
  QuestionHdr = (EFI_IFR_QUESTION_HEADER *) (OpCodeData + sizeof (EFI_IFR_OP_HEADER));
  CopyMem (&Statement->QuestionId, &QuestionHdr->QuestionId, sizeof (EFI_QUESTION_ID));
  CopyMem (&Statement->VarStoreId, &QuestionHdr->VarStoreId, sizeof (EFI_VARSTORE_ID));
  CopyMem (&Statement->VarStoreInfo.VarOffset, &QuestionHdr->VarStoreInfo.VarOffset, sizeof (UINT16));
  Statement->QuestionFlags = QuestionHdr->Flags;
  if (Statement->VarStoreId == 0) {
    //
    // VarStoreId of zero indicates no variable storage
    //
    return Statement;
  }
  //
  // Take a look at next OpCode to see whether it is a GUIDed opcode to support
  // Framework Compatibility
  //
  if (FeaturePcdGet (PcdFrameworkCompatibilitySupport)) {
    if ((*OpCodeData == EFI_IFR_NUMERIC_OP) && IsNextOpCodeGuidedVarEqName (OpCodeData)) {
      Status = UpdateCheckBoxStringToken (FormSet, Statement);
      if (EFI_ERROR (Status)) {
        return NULL;
      }
    }
  }
  //
  // Find Storage for this Question
  //
  Link = GetFirstNode (&FormSet->StorageListHead);
  while (!IsNull (&FormSet->StorageListHead, Link)) {
    Storage = FORMSET_STORAGE_FROM_LINK (Link);
    if (Storage->VarStoreId == Statement->VarStoreId) {
      Statement->Storage = Storage->BrowserStorage;
      break;
    }
    Link = GetNextNode (&FormSet->StorageListHead, Link);
  }
  ASSERT (Statement->Storage != NULL);
  //
  // Initialilze varname for Name/Value or EFI Variable
  //
  if ((Statement->Storage->Type == EFI_HII_VARSTORE_NAME_VALUE) ||
      (Statement->Storage->Type == EFI_HII_VARSTORE_EFI_VARIABLE)) {
    Statement->VariableName = GetToken (Statement->VarStoreInfo.VarName, FormSet->HiiHandle);
    ASSERT (Statement->VariableName != NULL);
    if (Statement->Storage->Type == EFI_HII_VARSTORE_NAME_VALUE) {
      //
      // Check whether old string node already exist.
      //
      Find = FALSE;
      if (!IsListEmpty(&Statement->Storage->NameValueListHead)) {  
        Link = GetFirstNode (&Statement->Storage->NameValueListHead);
        while (!IsNull (&Statement->Storage->NameValueListHead, Link)) {
          NameValueNode = NAME_VALUE_NODE_FROM_LINK (Link);
          if (StrCmp (Statement->VariableName, NameValueNode->Name) == 0) {
            Find = TRUE;
            break;
          }
          Link = GetNextNode (&Statement->Storage->NameValueListHead, Link);
        }
      }
      if (!Find) {
        //
        // Insert to Name/Value varstore list
        //
        NameValueNode = AllocateZeroPool (sizeof (NAME_VALUE_NODE));
        ASSERT (NameValueNode != NULL);
        NameValueNode->Signature = NAME_VALUE_NODE_SIGNATURE;
        NameValueNode->Name = AllocateCopyPool (StrSize (Statement->VariableName), Statement->VariableName);
        ASSERT (NameValueNode->Name != NULL);
        NameValueNode->Value = AllocateZeroPool (0x10);
        ASSERT (NameValueNode->Value != NULL);
        NameValueNode->EditValue = AllocateZeroPool (0x10);
        ASSERT (NameValueNode->EditValue != NULL);
        InsertTailList (&Statement->Storage->NameValueListHead, &NameValueNode->Link);
      }
    }
  }
  return Statement;
}
/**
  Allocate a FORM_EXPRESSION node.
  @param  Form                   The Form associated with this Expression
  @param  OpCode                 The binary opcode data.
  @return Pointer to a FORM_EXPRESSION data structure.
**/
FORM_EXPRESSION *
CreateExpression (
  IN OUT FORM_BROWSER_FORM        *Form,
  IN     UINT8                    *OpCode
  )
{
  FORM_EXPRESSION  *Expression;
  Expression = AllocateZeroPool (sizeof (FORM_EXPRESSION));
  ASSERT (Expression != NULL);
  Expression->Signature = FORM_EXPRESSION_SIGNATURE;
  InitializeListHead (&Expression->OpCodeListHead);
  Expression->OpCode = (EFI_IFR_OP_HEADER *) OpCode;
  return Expression;
}
/**
  Create ConfigHdr string for a storage.
  @param  FormSet                Pointer of the current FormSet
  @param  Storage                Pointer of the storage
  @retval EFI_SUCCESS            Initialize ConfigHdr success
**/
EFI_STATUS
InitializeConfigHdr (
  IN FORM_BROWSER_FORMSET  *FormSet,
  IN OUT FORMSET_STORAGE   *Storage
  )
{
  CHAR16      *Name;
  if (Storage->BrowserStorage->Type == EFI_HII_VARSTORE_BUFFER || 
      Storage->BrowserStorage->Type == EFI_HII_VARSTORE_EFI_VARIABLE_BUFFER) {
    Name = Storage->BrowserStorage->Name;
  } else {
    Name = NULL;
  }
  Storage->ConfigHdr = HiiConstructConfigHdr (
                         &Storage->BrowserStorage->Guid,
                         Name,
                         FormSet->DriverHandle
                         );
  if (Storage->ConfigHdr == NULL) {
    return EFI_NOT_FOUND;
  }
  return EFI_SUCCESS;
}
/**
  Find the global storage link base on the input storate type, name and guid.
  For EFI_HII_VARSTORE_EFI_VARIABLE and EFI_HII_VARSTORE_EFI_VARIABLE_BUFFER,
  same guid + name = same storage
  For EFI_HII_VARSTORE_NAME_VALUE:
  same guid + HiiHandle = same storage
  For EFI_HII_VARSTORE_BUFFER:
  same guid + name + HiiHandle = same storage
  @param  StorageType                Storage type.
  @param  StorageGuid                Storage guid.
  @param  StorageName                Storage Name.
  @param  HiiHandle                  HiiHandle for this varstore.
  @return Pointer to a GLOBAL_STORAGE data structure.
**/
BROWSER_STORAGE *
FindStorageInList (
  IN UINT8                 StorageType,
  IN EFI_GUID              *StorageGuid,
  IN CHAR16                *StorageName,
  IN EFI_HII_HANDLE        HiiHandle
  )
{
  LIST_ENTRY       *Link;
  BROWSER_STORAGE  *BrowserStorage;
  Link  = GetFirstNode (&gBrowserStorageList);
  while (!IsNull (&gBrowserStorageList, Link)) {
    BrowserStorage = BROWSER_STORAGE_FROM_LINK (Link);
    Link = GetNextNode (&gBrowserStorageList, Link);
    if ((BrowserStorage->Type == StorageType) && CompareGuid (&BrowserStorage->Guid, StorageGuid)) {
      if (StorageType == EFI_HII_VARSTORE_NAME_VALUE) {
        if (BrowserStorage->HiiHandle == HiiHandle) {
          return BrowserStorage;
        }
        continue;
      }
      ASSERT (StorageName != NULL);
      if (StrCmp (BrowserStorage->Name, StorageName) == 0) {
        if (StorageType == EFI_HII_VARSTORE_EFI_VARIABLE || StorageType == EFI_HII_VARSTORE_EFI_VARIABLE_BUFFER) {
          return BrowserStorage;
        } else if (StorageType == EFI_HII_VARSTORE_BUFFER && BrowserStorage->HiiHandle == HiiHandle) {
          return BrowserStorage;
        }
      }
    }
  }
  return NULL;
}
/**
  Intialize the Global Storage.
  @param  BrowserStorage              Pointer to the global storage.
  @param  StorageType                Storage type.
  @param  OpCodeData                 Binary data for this opcode.
**/
VOID
IntializeBrowserStorage (
  IN BROWSER_STORAGE       *BrowserStorage,
  IN UINT8                 StorageType,
  IN UINT8                 *OpCodeData
  )
{
  switch (StorageType) {
    case EFI_HII_VARSTORE_BUFFER:
      CopyMem (&BrowserStorage->Guid, &((EFI_IFR_VARSTORE *) OpCodeData)->Guid, sizeof (EFI_GUID));
      CopyMem (&BrowserStorage->Size, &((EFI_IFR_VARSTORE *) OpCodeData)->Size, sizeof (UINT16));
      BrowserStorage->Buffer     = AllocateZeroPool (BrowserStorage->Size);
      BrowserStorage->EditBuffer = AllocateZeroPool (BrowserStorage->Size);
      break;
    case EFI_HII_VARSTORE_EFI_VARIABLE:
    case EFI_HII_VARSTORE_EFI_VARIABLE_BUFFER:
      CopyMem (&BrowserStorage->Guid,       &((EFI_IFR_VARSTORE_EFI *) OpCodeData)->Guid,       sizeof (EFI_GUID));
      CopyMem (&BrowserStorage->Attributes, &((EFI_IFR_VARSTORE_EFI *) OpCodeData)->Attributes, sizeof (UINT32));
      CopyMem (&BrowserStorage->Size,       &((EFI_IFR_VARSTORE_EFI *) OpCodeData)->Size,       sizeof (UINT16));
      if (StorageType ==  EFI_HII_VARSTORE_EFI_VARIABLE_BUFFER) {
        BrowserStorage->Buffer     = AllocateZeroPool (BrowserStorage->Size);
        BrowserStorage->EditBuffer = AllocateZeroPool (BrowserStorage->Size);
      }
      break;
    case EFI_HII_VARSTORE_NAME_VALUE:
      CopyMem (&BrowserStorage->Guid, &((EFI_IFR_VARSTORE_NAME_VALUE *) OpCodeData)->Guid, sizeof (EFI_GUID));
      InitializeListHead (&BrowserStorage->NameValueListHead);
      break;
    default:
      break;
  }
}
/**
  Check whether exist device path info in the ConfigHdr string.
  @param  String                 UEFI configuration string
  @retval TRUE                   Device Path exist.
  @retval FALSE                  Not exist device path info.
**/
BOOLEAN
IsDevicePathExist (
  IN  EFI_STRING                   String
  )
{
  UINTN                    Length;
  for (; (*String != 0 && StrnCmp (String, L"PATH=", StrLen (L"PATH=")) != 0); String++);
  if (*String == 0) {
    return FALSE;
  }
  String += StrLen (L"PATH=");
  if (*String == 0) {
    return FALSE;
  }
  for (Length = 0; *String != 0 && *String != L'&'; String++, Length++);
  if (((Length + 1) / 2) < sizeof (EFI_DEVICE_PATH_PROTOCOL)) {
    return FALSE;
  }
  return TRUE;
}
/**
  Allocate a FORMSET_STORAGE data structure and insert to FormSet Storage List.
  @param  FormSet                    Pointer of the current FormSet
  @param  StorageType                Storage type.
  @param  OpCodeData                 Binary data for this opcode.
  @return Pointer to a FORMSET_STORAGE data structure.
**/
FORMSET_STORAGE *
CreateStorage (
  IN FORM_BROWSER_FORMSET  *FormSet,
  IN UINT8                 StorageType,
  IN UINT8                 *OpCodeData
  )
{
  FORMSET_STORAGE         *Storage;
  CHAR16                  *UnicodeString;
  UINT16                  Index;
  BROWSER_STORAGE         *BrowserStorage;
  EFI_GUID                *StorageGuid;
  CHAR8                   *StorageName;
  UnicodeString = NULL;
  StorageName   = NULL;
  switch (StorageType) {
    case EFI_HII_VARSTORE_BUFFER:
      StorageGuid = (EFI_GUID *) (CHAR8*) &((EFI_IFR_VARSTORE *) OpCodeData)->Guid;
      StorageName = (CHAR8 *) ((EFI_IFR_VARSTORE *) OpCodeData)->Name;
      break;
    case EFI_HII_VARSTORE_EFI_VARIABLE:
    case EFI_HII_VARSTORE_EFI_VARIABLE_BUFFER:
      StorageGuid = (EFI_GUID *) (CHAR8*) &((EFI_IFR_VARSTORE_EFI *) OpCodeData)->Guid;
      StorageName = (CHAR8 *) ((EFI_IFR_VARSTORE_EFI *) OpCodeData)->Name;
      break;
    default:
      ASSERT (StorageType == EFI_HII_VARSTORE_NAME_VALUE);
      StorageGuid = &((EFI_IFR_VARSTORE_NAME_VALUE *) OpCodeData)->Guid;
      break;
  }
  if (StorageType != EFI_HII_VARSTORE_NAME_VALUE) {
    ASSERT (StorageName != NULL);
    UnicodeString = AllocateZeroPool (AsciiStrSize (StorageName) * 2);
    ASSERT (UnicodeString != NULL);
    for (Index = 0; StorageName[Index] != 0; Index++) {
      UnicodeString[Index] = (CHAR16) StorageName[Index];
    }
  }
  Storage = AllocateZeroPool (sizeof (FORMSET_STORAGE));
  ASSERT (Storage != NULL);
  Storage->Signature = FORMSET_STORAGE_SIGNATURE;
  InsertTailList (&FormSet->StorageListHead, &Storage->Link);
  BrowserStorage = FindStorageInList(StorageType, StorageGuid, UnicodeString, FormSet->HiiHandle);
  if (BrowserStorage == NULL) {
    BrowserStorage = AllocateZeroPool (sizeof (BROWSER_STORAGE));
    ASSERT (BrowserStorage != NULL);
    BrowserStorage->Signature = BROWSER_STORAGE_SIGNATURE;
    InsertTailList (&gBrowserStorageList, &BrowserStorage->Link);
    IntializeBrowserStorage (BrowserStorage, StorageType, OpCodeData);
    BrowserStorage->Type = StorageType;
    if (StorageType != EFI_HII_VARSTORE_NAME_VALUE) {
      BrowserStorage->Name = UnicodeString;
    }
    BrowserStorage->HiiHandle = FormSet->HiiHandle;
    BrowserStorage->Initialized = FALSE;
  }
  Storage->BrowserStorage = BrowserStorage;
  InitializeConfigHdr (FormSet, Storage);
  Storage->ConfigRequest = AllocateCopyPool (StrSize (Storage->ConfigHdr), Storage->ConfigHdr);
  Storage->SpareStrLen = 0;
  return Storage;
}
/**
  Get Formset_storage base on the input varstoreid info.
  @param  FormSet                Pointer of the current FormSet.
  @param  VarStoreId             Varstore ID info.
  @return Pointer to a FORMSET_STORAGE data structure.
**/
FORMSET_STORAGE *
GetFstStgFromVarId (
  IN FORM_BROWSER_FORMSET  *FormSet,
  IN EFI_VARSTORE_ID       VarStoreId
  )
{
  FORMSET_STORAGE  *FormsetStorage;
  LIST_ENTRY       *Link;
  BOOLEAN          Found;
  Found = FALSE;
  FormsetStorage = NULL;
  //
  // Find Formset Storage for this Question
  //
  Link = GetFirstNode (&FormSet->StorageListHead);
  while (!IsNull (&FormSet->StorageListHead, Link)) {
    FormsetStorage = FORMSET_STORAGE_FROM_LINK (Link);
    if (FormsetStorage->VarStoreId == VarStoreId) {
      Found = TRUE;
      break;
    }
    Link = GetNextNode (&FormSet->StorageListHead, Link);
  }
  return Found ? FormsetStorage : NULL;
}
/**
  Get Formset_storage base on the input browser storage.
  More than one formsets may share the same browser storage,
  this function just get the first formset storage which
  share the browser storage.
  @param  Storage              browser storage info.
  @return Pointer to a FORMSET_STORAGE data structure.
  
**/
FORMSET_STORAGE *
GetFstStgFromBrsStg (
  IN BROWSER_STORAGE       *Storage
  )
{
  FORMSET_STORAGE      *FormsetStorage;
  LIST_ENTRY           *Link;
  LIST_ENTRY           *FormsetLink;
  FORM_BROWSER_FORMSET *FormSet;
  BOOLEAN              Found;
  Found = FALSE;
  FormsetStorage = NULL;
  FormsetLink = GetFirstNode (&gBrowserFormSetList);
  while (!IsNull (&gBrowserFormSetList, FormsetLink)) {
    FormSet = FORM_BROWSER_FORMSET_FROM_LINK (FormsetLink);
    FormsetLink = GetNextNode (&gBrowserFormSetList, FormsetLink);
    Link = GetFirstNode (&FormSet->StorageListHead);
    while (!IsNull (&FormSet->StorageListHead, Link)) {
      FormsetStorage = FORMSET_STORAGE_FROM_LINK (Link);
      Link = GetNextNode (&FormSet->StorageListHead, Link);
      if (FormsetStorage->BrowserStorage == Storage) {
        Found = TRUE;
        break;
      }
    }
    if (Found) {
      break;
    }
  }
  return Found ? FormsetStorage : NULL;
}
/**
  Initialize Request Element of a Question.  ::= '&' | '&'