/** @file
Parser for IFR binary encoding.
Copyright (c) 2007 - 2020, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "Setup.h"
UINTN              mStatementIndex;
UINTN              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;
  Statement->QuestionReferToBitField = FALSE;
  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;
}
/**
  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;
  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;
  }
  //
  // 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;
  }
}
/**
  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.  ::= '&' | '&'