mirror of
https://github.com/YikeStone/MT7601u.git
synced 2025-08-03 03:14:08 +05:30
503 lines
16 KiB
C
503 lines
16 KiB
C
/****************************************************************************
|
|
* Ralink Tech Inc.
|
|
* 4F, No. 2 Technology 5th Rd.
|
|
* Science-based Industrial Park
|
|
* Hsin-chu, Taiwan, R.O.C.
|
|
* (c) Copyright 2002, Ralink Technology, Inc.
|
|
*
|
|
* All rights reserved. Ralink's source code is an unpublished work and the
|
|
* use of a copyright notice does not imply otherwise. This source code
|
|
* contains confidential trade secret material of Ralink Tech. Any attemp
|
|
* or participation in deciphering, decoding, reverse engineering or in any
|
|
* way altering the source code is stricitly prohibited, unless the prior
|
|
* written consent of Ralink Technology, Inc. is obtained.
|
|
****************************************************************************
|
|
|
|
Module Name:
|
|
ap_sanity.c
|
|
|
|
Abstract:
|
|
Handle association related requests either from WSTA or from local MLME
|
|
|
|
Revision History:
|
|
Who When What
|
|
-------- ---------- ----------------------------------------------
|
|
John Chang 08-14-2003 created for 11g soft-AP
|
|
John Chang 12-30-2004 merge with STA driver for RT2600
|
|
*/
|
|
|
|
#include "rt_config.h"
|
|
|
|
extern UCHAR CISCO_OUI[];
|
|
|
|
extern UCHAR WPA_OUI[];
|
|
extern UCHAR RSN_OUI[];
|
|
extern UCHAR WME_INFO_ELEM[];
|
|
extern UCHAR WME_PARM_ELEM[];
|
|
extern UCHAR RALINK_OUI[];
|
|
|
|
extern UCHAR BROADCOM_OUI[];
|
|
extern UCHAR WPS_OUI[];
|
|
|
|
/*
|
|
==========================================================================
|
|
Description:
|
|
MLME message sanity check
|
|
Return:
|
|
TRUE if all parameters are OK, FALSE otherwise
|
|
==========================================================================
|
|
*/
|
|
|
|
BOOLEAN PeerAssocReqCmmSanity(
|
|
RTMP_ADAPTER *pAd,
|
|
BOOLEAN isReassoc,
|
|
VOID *Msg,
|
|
INT MsgLen,
|
|
IE_LISTS *ie_lists)
|
|
{
|
|
CHAR *Ptr;
|
|
PFRAME_802_11 Fr = (PFRAME_802_11)Msg;
|
|
PEID_STRUCT eid_ptr;
|
|
UCHAR Sanity = 0;
|
|
UCHAR WPA1_OUI[4] = { 0x00, 0x50, 0xF2, 0x01 };
|
|
UCHAR WPA2_OUI[3] = { 0x00, 0x0F, 0xAC };
|
|
MAC_TABLE_ENTRY *pEntry = (MAC_TABLE_ENTRY *)NULL;
|
|
#ifdef P2P_SUPPORT
|
|
PRT_P2P_CONFIG pP2PCtrl = &pAd->P2pCfg;
|
|
UCHAR P2POUIBYTE[4] = {0x50, 0x6f, 0x9a, 0x9};
|
|
#endif /* P2P_SUPPORT */
|
|
#ifdef P2P_SUPPORT
|
|
UCHAR *pP2pSubelement = &ie_lists->P2pSubelement[0];
|
|
#endif /* P2P_SUPPORT */
|
|
HT_CAPABILITY_IE *pHtCapability = &ie_lists->HTCapability;
|
|
|
|
|
|
pEntry = MacTableLookup(pAd, &Fr->Hdr.Addr2[0]);
|
|
if (pEntry == NULL)
|
|
return FALSE;
|
|
|
|
COPY_MAC_ADDR(&ie_lists->Addr2[0], &Fr->Hdr.Addr2[0]);
|
|
|
|
Ptr = (PCHAR)Fr->Octet;
|
|
|
|
NdisMoveMemory(&ie_lists->CapabilityInfo, &Fr->Octet[0], 2);
|
|
NdisMoveMemory(&ie_lists->ListenInterval, &Fr->Octet[2], 2);
|
|
|
|
if (isReassoc)
|
|
{
|
|
NdisMoveMemory(&ie_lists->ApAddr[0], &Fr->Octet[4], 6);
|
|
eid_ptr = (PEID_STRUCT) &Fr->Octet[10];
|
|
}
|
|
else
|
|
{
|
|
eid_ptr = (PEID_STRUCT) &Fr->Octet[4];
|
|
}
|
|
|
|
|
|
/* get variable fields from payload and advance the pointer */
|
|
while (((UCHAR *)eid_ptr + eid_ptr->Len + 1) < ((UCHAR *)Fr + MsgLen))
|
|
{
|
|
switch(eid_ptr->Eid)
|
|
{
|
|
case IE_SSID:
|
|
if (((Sanity&0x1) == 1))
|
|
break;
|
|
|
|
if ((eid_ptr->Len <= MAX_LEN_OF_SSID))
|
|
{
|
|
Sanity |= 0x01;
|
|
NdisMoveMemory(&ie_lists->Ssid[0], eid_ptr->Octet, eid_ptr->Len);
|
|
ie_lists->SsidLen = eid_ptr->Len;
|
|
DBGPRINT(RT_DEBUG_TRACE, ("PeerAssocReqSanity - SsidLen = %d \n", ie_lists->SsidLen));
|
|
}
|
|
else
|
|
{
|
|
DBGPRINT(RT_DEBUG_TRACE, ("PeerAssocReqSanity - wrong IE_SSID\n"));
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
case IE_SUPP_RATES:
|
|
if ((eid_ptr->Len <= MAX_LEN_OF_SUPPORTED_RATES) &&
|
|
(eid_ptr->Len > 0))
|
|
{
|
|
Sanity |= 0x02;
|
|
NdisMoveMemory(&ie_lists->SupportedRates[0], eid_ptr->Octet, eid_ptr->Len);
|
|
|
|
DBGPRINT(RT_DEBUG_TRACE,
|
|
("PeerAssocReqSanity - IE_SUPP_RATES., Len=%d. "
|
|
"Rates[0]=%x\n", eid_ptr->Len, ie_lists->SupportedRates[0]));
|
|
DBGPRINT(RT_DEBUG_TRACE,
|
|
("Rates[1]=%x %x %x %x %x %x %x\n",
|
|
ie_lists->SupportedRates[1], ie_lists->SupportedRates[2],
|
|
ie_lists->SupportedRates[3], ie_lists->SupportedRates[4],
|
|
ie_lists->SupportedRates[5], ie_lists->SupportedRates[6],
|
|
ie_lists->SupportedRates[7]));
|
|
|
|
ie_lists->SupportedRatesLen = eid_ptr->Len;
|
|
}
|
|
else
|
|
{
|
|
UCHAR RateDefault[8] = \
|
|
{ 0x82, 0x84, 0x8b, 0x96, 0x12, 0x24, 0x48, 0x6c };
|
|
|
|
/* HT rate not ready yet. return true temporarily. rt2860c */
|
|
/*DBGPRINT(RT_DEBUG_TRACE, ("PeerAssocReqSanity - wrong IE_SUPP_RATES\n")); */
|
|
Sanity |= 0x02;
|
|
ie_lists->SupportedRatesLen = 8;
|
|
NdisMoveMemory(&ie_lists->SupportedRates[0], RateDefault, 8);
|
|
|
|
DBGPRINT(RT_DEBUG_TRACE,
|
|
("PeerAssocReqSanity - wrong IE_SUPP_RATES., Len=%d\n",
|
|
eid_ptr->Len));
|
|
}
|
|
break;
|
|
|
|
case IE_EXT_SUPP_RATES:
|
|
if (eid_ptr->Len + ie_lists->SupportedRatesLen <= MAX_LEN_OF_SUPPORTED_RATES)
|
|
{
|
|
NdisMoveMemory(&ie_lists->SupportedRates[ie_lists->SupportedRatesLen], eid_ptr->Octet,
|
|
eid_ptr->Len);
|
|
ie_lists->SupportedRatesLen += eid_ptr->Len;
|
|
}
|
|
else
|
|
{
|
|
NdisMoveMemory(&ie_lists->SupportedRates[ie_lists->SupportedRatesLen], eid_ptr->Octet,
|
|
MAX_LEN_OF_SUPPORTED_RATES - (ie_lists->SupportedRatesLen));
|
|
ie_lists->SupportedRatesLen = MAX_LEN_OF_SUPPORTED_RATES;
|
|
}
|
|
break;
|
|
|
|
case IE_HT_CAP:
|
|
if (eid_ptr->Len >= sizeof(HT_CAPABILITY_IE))
|
|
{
|
|
NdisMoveMemory(pHtCapability, eid_ptr->Octet, SIZE_HT_CAP_IE);
|
|
|
|
*(USHORT *)(&pHtCapability->HtCapInfo) = cpu2le16(*(USHORT *)(&pHtCapability->HtCapInfo));
|
|
|
|
#ifdef UNALIGNMENT_SUPPORT
|
|
{
|
|
EXT_HT_CAP_INFO extHtCapInfo;
|
|
|
|
NdisMoveMemory((PUCHAR)(&extHtCapInfo), (PUCHAR)(&pHtCapability->ExtHtCapInfo), sizeof(EXT_HT_CAP_INFO));
|
|
*(USHORT *)(&extHtCapInfo) = cpu2le16(*(USHORT *)(&extHtCapInfo));
|
|
NdisMoveMemory((PUCHAR)(&pHtCapability->ExtHtCapInfo), (PUCHAR)(&extHtCapInfo), sizeof(EXT_HT_CAP_INFO));
|
|
}
|
|
#else
|
|
*(USHORT *)(&pHtCapability->ExtHtCapInfo) = cpu2le16(*(USHORT *)(&pHtCapability->ExtHtCapInfo));
|
|
#endif /* UNALIGNMENT_SUPPORT */
|
|
|
|
ie_lists->ht_cap_len = SIZE_HT_CAP_IE;
|
|
Sanity |= 0x10;
|
|
DBGPRINT(RT_DEBUG_WARN, ("PeerAssocReqSanity - IE_HT_CAP\n"));
|
|
}
|
|
else
|
|
{
|
|
DBGPRINT(RT_DEBUG_WARN, ("PeerAssocReqSanity - wrong IE_HT_CAP.eid_ptr->Len = %d\n", eid_ptr->Len));
|
|
}
|
|
|
|
break;
|
|
case IE_EXT_CAPABILITY:
|
|
if (eid_ptr->Len >= sizeof(EXT_CAP_INFO_ELEMENT))
|
|
{
|
|
NdisMoveMemory(&ie_lists->ExtCapInfo, eid_ptr->Octet, sizeof(EXT_CAP_INFO_ELEMENT));
|
|
DBGPRINT(RT_DEBUG_WARN, ("PeerAssocReqSanity - IE_EXT_CAPABILITY!\n"));
|
|
}
|
|
|
|
break;
|
|
|
|
case IE_WPA: /* same as IE_VENDOR_SPECIFIC */
|
|
case IE_WPA2:
|
|
|
|
#ifdef P2P_SUPPORT
|
|
if (NdisEqualMemory(eid_ptr->Octet, P2POUIBYTE, sizeof(P2POUIBYTE)) && (eid_ptr->Len >= 4))
|
|
{
|
|
if (ie_lists->P2PSubelementLen == 0)
|
|
{
|
|
RTMPMoveMemory(pP2pSubelement, &eid_ptr->Eid, (eid_ptr->Len+2));
|
|
ie_lists->P2PSubelementLen = (eid_ptr->Len+2);
|
|
}
|
|
else if (ie_lists->P2PSubelementLen > 0)
|
|
{
|
|
RTMPMoveMemory(pP2pSubelement + ie_lists->P2PSubelementLen, &eid_ptr->Eid, (eid_ptr->Len+2));
|
|
ie_lists->P2PSubelementLen += (eid_ptr->Len+2);
|
|
}
|
|
|
|
DBGPRINT(RT_DEBUG_TRACE, (" ! ===>P2P - PeerAssocReqSanity P2P IE Len becomes = %d. %s\n", ie_lists->P2PSubelementLen, decodeP2PState(pP2PCtrl->P2PConnectState)));
|
|
break;
|
|
}
|
|
#endif /* P2P_SUPPORT */
|
|
if (NdisEqualMemory(eid_ptr->Octet, WPS_OUI, 4))
|
|
{
|
|
#ifdef WSC_AP_SUPPORT
|
|
#ifdef WSC_V2_SUPPORT
|
|
if ((pAd->ApCfg.MBSSID[pEntry->apidx].WscControl.WscV2Info.bWpsEnable) ||
|
|
(pAd->ApCfg.MBSSID[pEntry->apidx].WscControl.WscV2Info.bEnableWpsV2 == FALSE))
|
|
#endif /* WSC_V2_SUPPORT */
|
|
ie_lists->bWscCapable = TRUE;
|
|
#endif /* WSC_AP_SUPPORT */
|
|
break;
|
|
}
|
|
|
|
/* Handle Atheros and Broadcom draft 11n STAs */
|
|
if (NdisEqualMemory(eid_ptr->Octet, BROADCOM_OUI, 3))
|
|
{
|
|
switch (eid_ptr->Octet[3])
|
|
{
|
|
case 0x33:
|
|
if ((eid_ptr->Len-4) == sizeof(HT_CAPABILITY_IE))
|
|
{
|
|
NdisMoveMemory(pHtCapability, &eid_ptr->Octet[4], SIZE_HT_CAP_IE);
|
|
|
|
*(USHORT *)(&pHtCapability->HtCapInfo) = cpu2le16(*(USHORT *)(&pHtCapability->HtCapInfo));
|
|
#ifdef UNALIGNMENT_SUPPORT
|
|
{
|
|
EXT_HT_CAP_INFO extHtCapInfo;
|
|
|
|
NdisMoveMemory((PUCHAR)(&extHtCapInfo), (PUCHAR)(&pHtCapability->ExtHtCapInfo), sizeof(EXT_HT_CAP_INFO));
|
|
*(USHORT *)(&extHtCapInfo) = cpu2le16(*(USHORT *)(&extHtCapInfo));
|
|
NdisMoveMemory((PUCHAR)(&pHtCapability->ExtHtCapInfo), (PUCHAR)(&extHtCapInfo), sizeof(EXT_HT_CAP_INFO));
|
|
}
|
|
#else
|
|
*(USHORT *)(&pHtCapability->ExtHtCapInfo) = cpu2le16(*(USHORT *)(&pHtCapability->ExtHtCapInfo));
|
|
#endif /* UNALIGNMENT_SUPPORT */
|
|
|
|
ie_lists->ht_cap_len = SIZE_HT_CAP_IE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* ignore other cases */
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NdisEqualMemory(eid_ptr->Octet, RALINK_OUI, 3) && (eid_ptr->Len == 7))
|
|
{
|
|
if (eid_ptr->Octet[3] != 0)
|
|
ie_lists->RalinkIe = eid_ptr->Octet[3];
|
|
else
|
|
ie_lists->RalinkIe = 0xf0000000; /* Set to non-zero value (can't set bit0-2) to represent this is Ralink Chip. So at linkup, we will set ralinkchip flag. */
|
|
break;
|
|
}
|
|
|
|
/* WMM_IE */
|
|
if (NdisEqualMemory(eid_ptr->Octet, WME_INFO_ELEM, 6) && (eid_ptr->Len == 7))
|
|
{
|
|
ie_lists->bWmmCapable = TRUE;
|
|
|
|
#ifdef UAPSD_SUPPORT
|
|
if (pEntry)
|
|
{
|
|
UAPSD_AssocParse(pAd,
|
|
pEntry, (UINT8 *)&eid_ptr->Octet[6],
|
|
pAd->ApCfg.MBSSID[\
|
|
pEntry->apidx].UapsdInfo.bAPSDCapable);
|
|
}
|
|
#endif /* UAPSD_SUPPORT */
|
|
|
|
break;
|
|
}
|
|
|
|
if (pAd->ApCfg.MBSSID[pEntry->apidx].AuthMode < Ndis802_11AuthModeWPA)
|
|
break;
|
|
|
|
/* If this IE did not begins with 00:0x50:0xf2:0x01,
|
|
it would be proprietary. So we ignore it. */
|
|
if (!NdisEqualMemory(eid_ptr->Octet, WPA1_OUI, sizeof(WPA1_OUI))
|
|
&& !NdisEqualMemory(&eid_ptr->Octet[2], WPA2_OUI, sizeof(WPA2_OUI)))
|
|
{
|
|
DBGPRINT(RT_DEBUG_TRACE, ("Not RSN IE, maybe WMM IE!!!\n"));
|
|
break;
|
|
}
|
|
|
|
if (/*(eid_ptr->Len <= MAX_LEN_OF_RSNIE) &&*/ (eid_ptr->Len >= MIN_LEN_OF_RSNIE))
|
|
{
|
|
hex_dump("Received RSNIE in Assoc-Req", (UCHAR *)eid_ptr, eid_ptr->Len + 2);
|
|
|
|
/* Copy whole RSNIE context */
|
|
NdisMoveMemory(&ie_lists->RSN_IE[0], eid_ptr, eid_ptr->Len + 2);
|
|
ie_lists->RSNIE_Len =eid_ptr->Len + 2;
|
|
|
|
}
|
|
else
|
|
{
|
|
ie_lists->RSNIE_Len = 0;
|
|
DBGPRINT(RT_DEBUG_TRACE, ("PeerAssocReqSanity - missing IE_WPA(%d)\n",eid_ptr->Len));
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
#ifdef WAPI_SUPPORT
|
|
case IE_WAPI:
|
|
if ((pAd->ApCfg.MBSSID[pEntry->apidx].AuthMode != Ndis802_11AuthModeWAICERT) &&
|
|
(pAd->ApCfg.MBSSID[pEntry->apidx].AuthMode != Ndis802_11AuthModeWAIPSK))
|
|
break;
|
|
|
|
/* Sanity check the validity of WIE */
|
|
/* Todo - AlbertY */
|
|
|
|
/* Copy whole WAPI-IE context */
|
|
NdisMoveMemory(&ie_lists->RSN_IE[0], eid_ptr, eid_ptr->Len + 2);
|
|
ie_lists->RSNIE_Len =eid_ptr->Len + 2;
|
|
DBGPRINT(RT_DEBUG_TRACE, ("PeerAssocReqSanity - IE_WAPI(%d)\n",eid_ptr->Len));
|
|
break;
|
|
#endif /* WAPI_SUPPORT */
|
|
|
|
|
|
|
|
#ifdef DOT11_VHT_AC
|
|
case IE_VHT_CAP:
|
|
if (eid_ptr->Len == sizeof(VHT_CAP_IE))
|
|
{
|
|
NdisMoveMemory(&ie_lists->vht_cap, eid_ptr->Octet, sizeof(VHT_CAP_IE));
|
|
ie_lists->vht_cap_len = eid_ptr->Len;
|
|
DBGPRINT(RT_DEBUG_TRACE, ("%s():IE_VHT_CAP\n", __FUNCTION__));
|
|
}
|
|
else
|
|
{
|
|
DBGPRINT(RT_DEBUG_WARN, ("%s():wrong IE_VHT_CAP, eid->Len = %d\n",
|
|
__FUNCTION__, eid_ptr->Len));
|
|
}
|
|
#endif /* DOT11_VHT_AC */
|
|
default:
|
|
break;
|
|
}
|
|
|
|
eid_ptr = (PEID_STRUCT)((UCHAR*)eid_ptr + 2 + eid_ptr->Len);
|
|
}
|
|
|
|
if ((Sanity&0x3) != 0x03)
|
|
{
|
|
DBGPRINT(RT_DEBUG_WARN, ("%s(): - missing mandatory field\n", __FUNCTION__));
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
DBGPRINT(RT_DEBUG_TRACE, ("%s() - success\n", __FUNCTION__));
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
==========================================================================
|
|
Description:
|
|
MLME message sanity check
|
|
Return:
|
|
TRUE if all parameters are OK, FALSE otherwise
|
|
==========================================================================
|
|
*/
|
|
BOOLEAN PeerDisassocReqSanity(
|
|
IN PRTMP_ADAPTER pAd,
|
|
IN VOID *Msg,
|
|
IN ULONG MsgLen,
|
|
OUT PUCHAR pAddr2,
|
|
OUT UINT16 *SeqNum,
|
|
OUT USHORT *Reason)
|
|
{
|
|
PFRAME_802_11 Fr = (PFRAME_802_11)Msg;
|
|
|
|
COPY_MAC_ADDR(pAddr2, &Fr->Hdr.Addr2);
|
|
*SeqNum = Fr->Hdr.Sequence;
|
|
NdisMoveMemory(Reason, &Fr->Octet[0], 2);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*
|
|
==========================================================================
|
|
Description:
|
|
MLME message sanity check
|
|
Return:
|
|
TRUE if all parameters are OK, FALSE otherwise
|
|
==========================================================================
|
|
*/
|
|
BOOLEAN PeerDeauthReqSanity(
|
|
IN PRTMP_ADAPTER pAd,
|
|
IN VOID *Msg,
|
|
IN ULONG MsgLen,
|
|
OUT PUCHAR pAddr2,
|
|
OUT UINT16 *SeqNum,
|
|
OUT USHORT *Reason)
|
|
{
|
|
PFRAME_802_11 Fr = (PFRAME_802_11)Msg;
|
|
|
|
COPY_MAC_ADDR(pAddr2, &Fr->Hdr.Addr2);
|
|
*SeqNum = Fr->Hdr.Sequence;
|
|
NdisMoveMemory(Reason, &Fr->Octet[0], 2);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*
|
|
==========================================================================
|
|
Description:
|
|
MLME message sanity check
|
|
Return:
|
|
TRUE if all parameters are OK, FALSE otherwise
|
|
==========================================================================
|
|
*/
|
|
BOOLEAN APPeerAuthSanity(
|
|
IN PRTMP_ADAPTER pAd,
|
|
IN VOID *Msg,
|
|
IN ULONG MsgLen,
|
|
OUT PUCHAR pAddr1,
|
|
OUT PUCHAR pAddr2,
|
|
OUT USHORT *Alg,
|
|
OUT USHORT *Seq,
|
|
OUT USHORT *Status,
|
|
CHAR *ChlgText
|
|
)
|
|
{
|
|
PFRAME_802_11 Fr = (PFRAME_802_11)Msg;
|
|
|
|
COPY_MAC_ADDR(pAddr1, &Fr->Hdr.Addr1); /* BSSID */
|
|
COPY_MAC_ADDR(pAddr2, &Fr->Hdr.Addr2); /* SA */
|
|
NdisMoveMemory(Alg, &Fr->Octet[0], 2);
|
|
NdisMoveMemory(Seq, &Fr->Octet[2], 2);
|
|
NdisMoveMemory(Status, &Fr->Octet[4], 2);
|
|
|
|
if (*Alg == AUTH_MODE_OPEN)
|
|
{
|
|
if (*Seq == 1 || *Seq == 2)
|
|
{
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
DBGPRINT(RT_DEBUG_TRACE, ("APPeerAuthSanity fail - wrong Seg# (=%d)\n", *Seq));
|
|
return FALSE;
|
|
}
|
|
}
|
|
else if (*Alg == AUTH_MODE_KEY)
|
|
{
|
|
if (*Seq == 1 || *Seq == 4)
|
|
{
|
|
return TRUE;
|
|
}
|
|
else if (*Seq == 2 || *Seq == 3)
|
|
{
|
|
NdisMoveMemory(ChlgText, &Fr->Octet[8], CIPHER_TEXT_LEN);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
DBGPRINT(RT_DEBUG_TRACE, ("APPeerAuthSanity fail - wrong Seg# (=%d)\n", *Seq));
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DBGPRINT(RT_DEBUG_TRACE, ("APPeerAuthSanity fail - wrong algorithm (=%d)\n", *Alg));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|