Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

892 lines
20 KiB

  1. /*++
  2. Copyright (c) 1992-1997 Microsoft Corporation
  3. Module Name:
  4. regions.c
  5. Abstract:
  6. Contains routines for manipulating MIB region structures.
  7. Environment:
  8. User Mode - Win32
  9. Revision History:
  10. 10-Feb-1997 DonRyan
  11. Rewrote to implement SNMPv2 support.
  12. --*/
  13. ///////////////////////////////////////////////////////////////////////////////
  14. // //
  15. // Header files //
  16. // //
  17. ///////////////////////////////////////////////////////////////////////////////
  18. #include "globals.h"
  19. #include "subagnts.h"
  20. #include "regions.h"
  21. ///////////////////////////////////////////////////////////////////////////////
  22. // //
  23. // Private procedures //
  24. // //
  25. ///////////////////////////////////////////////////////////////////////////////
  26. BOOL
  27. UpdateSupportedRegion(
  28. PMIB_REGION_LIST_ENTRY pExistingRLE,
  29. PMIB_REGION_LIST_ENTRY pRLE
  30. )
  31. /*++
  32. Routine Description:
  33. Updates MIB region properties based on supporting subagent.
  34. Arguments:
  35. pExisingRLE - pointer to existing MIB region to be updated.
  36. pRLE - pointer to subagent MIB region to be analyzed and saved.
  37. Return Values:
  38. Returns true if successful.
  39. --*/
  40. {
  41. INT nDiff;
  42. PMIB_REGION_LIST_ENTRY pSubagentRLE;
  43. // see if source is subagent
  44. if (pRLE->pSubagentRLE == NULL) {
  45. // save pointer
  46. pSubagentRLE = pRLE;
  47. } else {
  48. // save pointer
  49. pSubagentRLE = pRLE->pSubagentRLE;
  50. }
  51. // see if target uninitialized
  52. if (pExistingRLE->pSubagentRLE == NULL) {
  53. // save pointer to subagent region
  54. pExistingRLE->pSubagentRLE = pSubagentRLE;
  55. // save pointer to supporting subagent
  56. pExistingRLE->pSLE = pSubagentRLE->pSLE;
  57. } else {
  58. UINT nSubIds1;
  59. UINT nSubIds2;
  60. // determine length of existing subagent's original prefix
  61. nSubIds1 = pExistingRLE->pSubagentRLE->PrefixOid.idLength;
  62. // determine length of new subagent's prefix
  63. nSubIds2 = pSubagentRLE->PrefixOid.idLength;
  64. // update if more specific
  65. if (nSubIds1 <= nSubIds2) {
  66. // save pointer to subagent region
  67. pExistingRLE->pSubagentRLE = pSubagentRLE;
  68. // save pointer to supporting subagent
  69. pExistingRLE->pSLE = pSubagentRLE->pSLE;
  70. }
  71. }
  72. return TRUE;
  73. }
  74. BOOL
  75. SplitSupportedRegion(
  76. PMIB_REGION_LIST_ENTRY pRLE1,
  77. PMIB_REGION_LIST_ENTRY pRLE2,
  78. PMIB_REGION_LIST_ENTRY * ppLastSplitRLE
  79. )
  80. /*++
  81. Routine Description:
  82. Splits existing MIB region in order to insert new region.
  83. Arguments:
  84. pRLE1 - pointer to first MIB region to be split.
  85. pRLE2 - pointer to second MIB region to be split (not released).
  86. ppLastSplitRLE - pointer to receiver pointer to last split MIB region.
  87. Return Values:
  88. Returns true if successful.
  89. --*/
  90. {
  91. INT nLimitDiff;
  92. INT nPrefixDiff;
  93. PMIB_REGION_LIST_ENTRY pRLE3 = NULL;
  94. PMIB_REGION_LIST_ENTRY pRLE4 = NULL;
  95. PMIB_REGION_LIST_ENTRY pRLE5 = NULL;
  96. BOOL fOk = TRUE;
  97. // allocate regions
  98. if (!AllocRLE(&pRLE3) ||
  99. !AllocRLE(&pRLE4) ||
  100. !AllocRLE(&pRLE5)) {
  101. // release
  102. FreeRLE(pRLE3);
  103. FreeRLE(pRLE4);
  104. FreeRLE(pRLE5);
  105. // failure
  106. return FALSE;
  107. }
  108. // initialize pointer
  109. *ppLastSplitRLE = pRLE5;
  110. // calculate difference betweeen mib region limits
  111. nLimitDiff = SnmpUtilOidCmp(&pRLE1->LimitOid, &pRLE2->LimitOid);
  112. // calculate difference betweeen mib region prefixes
  113. nPrefixDiff = SnmpUtilOidCmp(&pRLE1->PrefixOid, &pRLE2->PrefixOid);
  114. // check for same prefix
  115. if (nPrefixDiff != 0) {
  116. // first prefix less
  117. if (nPrefixDiff < 0) {
  118. // r3.prefix equals min(rl.prefix,r2.prefix)
  119. SnmpUtilOidCpy(&pRLE3->PrefixOid, &pRLE1->PrefixOid);
  120. // r3.limit equals max(rl.prefix,r2.prefix)
  121. SnmpUtilOidCpy(&pRLE3->LimitOid, &pRLE2->PrefixOid);
  122. // r3 is supported by r1 subagent
  123. UpdateSupportedRegion(pRLE3, pRLE1);
  124. } else {
  125. // r3.prefix equals min(rl.prefix,r2.prefix)
  126. SnmpUtilOidCpy(&pRLE3->PrefixOid, &pRLE2->PrefixOid);
  127. // r3.limit equals max(rl.prefix,r2.prefix)
  128. SnmpUtilOidCpy(&pRLE3->LimitOid, &pRLE1->PrefixOid);
  129. // r3 is supported by r2 subagent
  130. UpdateSupportedRegion(pRLE3, pRLE2);
  131. }
  132. // r4.prefix equals r3.limit
  133. SnmpUtilOidCpy(&pRLE4->PrefixOid, &pRLE3->LimitOid);
  134. // r4 is supported by both subagents
  135. UpdateSupportedRegion(pRLE4, pRLE1);
  136. UpdateSupportedRegion(pRLE4, pRLE2);
  137. // first limit less
  138. if (nLimitDiff < 0) {
  139. // r4.limit equals min(rl.limit,r2.limit)
  140. SnmpUtilOidCpy(&pRLE4->LimitOid, &pRLE1->LimitOid);
  141. // r5.prefix equals r4.limit
  142. SnmpUtilOidCpy(&pRLE5->PrefixOid, &pRLE4->LimitOid);
  143. // r5.limit equals max(rl.limit,r2.limit)
  144. SnmpUtilOidCpy(&pRLE5->LimitOid, &pRLE2->LimitOid);
  145. // r5 is supported by r2 subagent
  146. UpdateSupportedRegion(pRLE5, pRLE2);
  147. // insert third mib region into list first
  148. InsertHeadList(&pRLE1->Link, &pRLE5->Link);
  149. } else if (nLimitDiff > 0) {
  150. // r4.limit equals min(rl.limit,r2.limit)
  151. SnmpUtilOidCpy(&pRLE4->LimitOid, &pRLE2->LimitOid);
  152. // r5.prefix equals r4.limit
  153. SnmpUtilOidCpy(&pRLE5->PrefixOid, &pRLE4->LimitOid);
  154. // r5.limit equals max(rl.limit,r2.limit)
  155. SnmpUtilOidCpy(&pRLE5->LimitOid, &pRLE1->LimitOid);
  156. // r5 is supported by r1 subagent
  157. UpdateSupportedRegion(pRLE5, pRLE1);
  158. // insert third mib region into list first
  159. InsertHeadList(&pRLE1->Link, &pRLE5->Link);
  160. } else {
  161. // r4.limit equals min(rl.limit,r2.limit)
  162. SnmpUtilOidCpy(&pRLE4->LimitOid, &pRLE2->LimitOid);
  163. // return r4 as last
  164. *ppLastSplitRLE = pRLE4;
  165. // release
  166. FreeRLE(pRLE5);
  167. }
  168. // insert remaining mib regions into list
  169. InsertHeadList(&pRLE1->Link, &pRLE4->Link);
  170. InsertHeadList(&pRLE1->Link, &pRLE3->Link);
  171. // remove existing
  172. RemoveEntryList(&pRLE1->Link);
  173. // release
  174. FreeRLE(pRLE1);
  175. } else if (nLimitDiff != 0) {
  176. // r3.prefix equals same prefix for r1 and r2
  177. SnmpUtilOidCpy(&pRLE3->PrefixOid, &pRLE1->PrefixOid);
  178. // r3 is supported by both subagents
  179. UpdateSupportedRegion(pRLE3, pRLE1);
  180. UpdateSupportedRegion(pRLE3, pRLE2);
  181. // first limit less
  182. if (nLimitDiff < 0) {
  183. // r3.limit equals min(rl.limit,r2.limit)
  184. SnmpUtilOidCpy(&pRLE3->LimitOid, &pRLE1->LimitOid);
  185. // r4.prefix equals r3.limit
  186. SnmpUtilOidCpy(&pRLE4->PrefixOid, &pRLE3->LimitOid);
  187. // r4.limit equals max(rl.limit,r2.limit)
  188. SnmpUtilOidCpy(&pRLE4->LimitOid, &pRLE2->LimitOid);
  189. // r4 is supported by r2 subagent
  190. UpdateSupportedRegion(pRLE4, pRLE2);
  191. } else {
  192. // r3.limit equals min(rl.limit,r2.limit)
  193. SnmpUtilOidCpy(&pRLE3->LimitOid, &pRLE2->LimitOid);
  194. // r4.prefix equals r3.limit
  195. SnmpUtilOidCpy(&pRLE4->PrefixOid, &pRLE3->LimitOid);
  196. // r4.limit equals max(rl.limit,r2.limit)
  197. SnmpUtilOidCpy(&pRLE4->LimitOid, &pRLE1->LimitOid);
  198. // r4 is supported by r1 subagent
  199. UpdateSupportedRegion(pRLE4, pRLE1);
  200. }
  201. // return r4 as last
  202. *ppLastSplitRLE = pRLE4;
  203. // insert mib regions into list
  204. InsertHeadList(&pRLE1->Link, &pRLE4->Link);
  205. InsertHeadList(&pRLE1->Link, &pRLE3->Link);
  206. // remove existing
  207. RemoveEntryList(&pRLE1->Link);
  208. // release
  209. FreeRLE(pRLE1);
  210. FreeRLE(pRLE5);
  211. } else {
  212. // region supported existing subagent
  213. UpdateSupportedRegion(pRLE1, pRLE2);
  214. // return r1 as last
  215. *ppLastSplitRLE = pRLE1;
  216. // release
  217. FreeRLE(pRLE3);
  218. FreeRLE(pRLE4);
  219. FreeRLE(pRLE5);
  220. }
  221. // success
  222. return TRUE;
  223. }
  224. BOOL
  225. InsertSupportedRegion(
  226. PMIB_REGION_LIST_ENTRY pExistingRLE,
  227. PMIB_REGION_LIST_ENTRY pRLE
  228. )
  229. /*++
  230. Routine Description:
  231. Splits existing MIB region in order to insert new region.
  232. Arguments:
  233. pExisingRLE - pointer to existing MIB region to be split.
  234. pRLE - pointer to MIB region to be inserted.
  235. Return Values:
  236. Returns true if successful.
  237. --*/
  238. {
  239. BOOL fOk;
  240. PLIST_ENTRY pLE;
  241. PMIB_REGION_LIST_ENTRY pLastSplitRLE = NULL;
  242. INT nDiff;
  243. // attempt to split mib regions into pieces parts
  244. fOk = SplitSupportedRegion(pExistingRLE, pRLE, &pLastSplitRLE);
  245. // process remaining entries
  246. while (pLastSplitRLE != NULL) {
  247. // re-use stack pointer
  248. pExistingRLE = pLastSplitRLE;
  249. // re-initialize
  250. pLastSplitRLE = NULL;
  251. // obtain pointer to next entry
  252. pLE = pExistingRLE->Link.Flink;
  253. // make sure entries remaining
  254. if (pLE != &g_SupportedRegions) {
  255. // retrieve pointer to mib region that follows last split one
  256. pRLE = CONTAINING_RECORD(pLE, MIB_REGION_LIST_ENTRY, Link);
  257. // compare mib regions
  258. nDiff = SnmpUtilOidCmp(
  259. &pExistingRLE->LimitOid,
  260. &pRLE->PrefixOid
  261. );
  262. // overlapped?
  263. if (nDiff > 0) {
  264. // remove from list
  265. RemoveEntryList(&pRLE->Link);
  266. // split the two new overlapped mib regions
  267. SplitSupportedRegion(pExistingRLE, pRLE, &pLastSplitRLE);
  268. // release
  269. FreeRLE(pRLE);
  270. }
  271. }
  272. }
  273. return fOk;
  274. }
  275. /*---debug purpose only----
  276. void PrintSupportedRegion()
  277. {
  278. PLIST_ENTRY pLE;
  279. PMIB_REGION_LIST_ENTRY pRLE;
  280. // obtain pointer to list head
  281. pLE = g_SupportedRegions.Flink;
  282. // process all entries in list
  283. while (pLE != &g_SupportedRegions) {
  284. // retrieve pointer to mib region structure
  285. pRLE = CONTAINING_RECORD(pLE, MIB_REGION_LIST_ENTRY, Link);
  286. SNMPDBG((SNMP_LOG_VERBOSE,"\t[%s\n", SnmpUtilOidToA(&(pRLE->PrefixOid))));
  287. SNMPDBG((SNMP_LOG_VERBOSE,"\t\t%s]\n", SnmpUtilOidToA(&(pRLE->LimitOid))));
  288. // next entry
  289. pLE = pLE->Flink;
  290. }
  291. SNMPDBG((SNMP_LOG_VERBOSE,"----\n"));
  292. }
  293. */
  294. BOOL
  295. AddSupportedRegion(
  296. PMIB_REGION_LIST_ENTRY pRLE
  297. )
  298. /*++
  299. Routine Description:
  300. Add subagent's MIB region into master agent's list.
  301. Arguments:
  302. pRLE - pointer to MIB region to add to supported list.
  303. Return Values:
  304. Returns true if successful.
  305. --*/
  306. {
  307. PLIST_ENTRY pLE;
  308. PMIB_REGION_LIST_ENTRY pRLE2;
  309. PMIB_REGION_LIST_ENTRY pExistingRLE;
  310. BOOL fFoundOk = FALSE;
  311. BOOL fOk = FALSE;
  312. INT nDiff;
  313. // PrintSupportedRegion();
  314. // attempt to locate prefix in existing mib regions
  315. if (FindFirstOverlappingRegion(&pExistingRLE, pRLE)) {
  316. // split existing region into bits
  317. fOk = InsertSupportedRegion(pExistingRLE, pRLE);
  318. } else {
  319. // obtain pointer to list head
  320. pLE = g_SupportedRegions.Flink;
  321. // process all entries in list
  322. while (pLE != &g_SupportedRegions) {
  323. // retrieve pointer to mib region
  324. pExistingRLE = CONTAINING_RECORD(pLE, MIB_REGION_LIST_ENTRY, Link);
  325. // compare region prefix
  326. nDiff = SnmpUtilOidCmp(&pRLE->PrefixOid, &pExistingRLE->PrefixOid);
  327. // found match?
  328. if (nDiff < 0) {
  329. // success
  330. fFoundOk = TRUE;
  331. break; // bail...
  332. }
  333. // next entry
  334. pLE = pLE->Flink;
  335. }
  336. // validate pointer
  337. if (AllocRLE(&pRLE2)) {
  338. // transfer prefix oid from subagent region
  339. SnmpUtilOidCpy(&pRLE2->PrefixOid, &pRLE->PrefixOid);
  340. // transfer limit oid from subagent region
  341. SnmpUtilOidCpy(&pRLE2->LimitOid, &pRLE->LimitOid);
  342. // save region pointer
  343. pRLE2->pSubagentRLE = pRLE;
  344. // save subagent pointer
  345. pRLE2->pSLE = pRLE->pSLE;
  346. // validate
  347. if (fFoundOk) {
  348. // add new mib range into supported list
  349. InsertTailList(&pExistingRLE->Link, &pRLE2->Link);
  350. } else {
  351. // add new mib range into global supported list
  352. InsertTailList(&g_SupportedRegions, &pRLE2->Link);
  353. }
  354. // success
  355. fOk = TRUE;
  356. }
  357. }
  358. return fOk;
  359. }
  360. ///////////////////////////////////////////////////////////////////////////////
  361. // //
  362. // Public procedures //
  363. // //
  364. ///////////////////////////////////////////////////////////////////////////////
  365. BOOL
  366. AllocRLE(
  367. PMIB_REGION_LIST_ENTRY * ppRLE
  368. )
  369. /*++
  370. Routine Description:
  371. Allocates MIB region structure and initializes.
  372. Arguments:
  373. ppRLE - pointer to receive pointer to list entry.
  374. Return Values:
  375. Returns true if successful.
  376. --*/
  377. {
  378. BOOL fOk = FALSE;
  379. PMIB_REGION_LIST_ENTRY pRLE;
  380. // attempt to allocate structure
  381. pRLE = AgentMemAlloc(sizeof(MIB_REGION_LIST_ENTRY));
  382. // validate pointer
  383. if (pRLE != NULL) {
  384. // initialize links
  385. InitializeListHead(&pRLE->Link);
  386. // success
  387. fOk = TRUE;
  388. } else {
  389. SNMPDBG((
  390. SNMP_LOG_ERROR,
  391. "SNMP: SVC: could not allocate region entry.\n"
  392. ));
  393. }
  394. // transfer
  395. *ppRLE = pRLE;
  396. return fOk;
  397. }
  398. BOOL
  399. FreeRLE(
  400. PMIB_REGION_LIST_ENTRY pRLE
  401. )
  402. /*++
  403. Routine Description:
  404. Releases MIB region structure.
  405. Arguments:
  406. ppRLE - pointer to MIB region to be freed.
  407. Return Values:
  408. Returns true if successful.
  409. --*/
  410. {
  411. // validate pointer
  412. if (pRLE != NULL) {
  413. // release memory for prefix oid
  414. SnmpUtilOidFree(&pRLE->PrefixOid);
  415. // release memory for limit oid
  416. SnmpUtilOidFree(&pRLE->LimitOid);
  417. // release memory
  418. AgentMemFree(pRLE);
  419. }
  420. return TRUE;
  421. }
  422. BOOL
  423. UnloadRegions(
  424. PLIST_ENTRY pListHead
  425. )
  426. /*++
  427. Routine Description:
  428. Destroys list of MIB regions.
  429. Arguments:
  430. pListHead - pointer to list of regions.
  431. Return Values:
  432. Returns true if successful.
  433. --*/
  434. {
  435. PLIST_ENTRY pLE;
  436. PMIB_REGION_LIST_ENTRY pRLE;
  437. // process entries until empty
  438. while (!IsListEmpty(pListHead)) {
  439. // extract next entry from head
  440. pLE = RemoveHeadList(pListHead);
  441. // retrieve pointer to mib region structure
  442. pRLE = CONTAINING_RECORD(pLE, MIB_REGION_LIST_ENTRY, Link);
  443. // release
  444. FreeRLE(pRLE);
  445. }
  446. return TRUE;
  447. }
  448. BOOL
  449. FindFirstOverlappingRegion(
  450. PMIB_REGION_LIST_ENTRY * ppRLE,
  451. PMIB_REGION_LIST_ENTRY pNewRLE
  452. )
  453. /*++
  454. Routine Description:
  455. Detects if any existent region overlapps with the new one to be added.
  456. Arguments:
  457. ppRLE - pointer to receive pointer to list entry.
  458. pNewRLE - pointer to new region to be tested
  459. Return Values:
  460. Returns true if match found.
  461. --*/
  462. {
  463. PLIST_ENTRY pLE;
  464. PMIB_REGION_LIST_ENTRY pRLE;
  465. // initialize
  466. *ppRLE = NULL;
  467. // obtain pointer to list head
  468. pLE = g_SupportedRegions.Flink;
  469. // process all entries in list
  470. while (pLE != &g_SupportedRegions) {
  471. // retrieve pointer to mib region structure
  472. pRLE = CONTAINING_RECORD(pLE, MIB_REGION_LIST_ENTRY, Link);
  473. if (SnmpUtilOidCmp(&pNewRLE->PrefixOid, &pRLE->LimitOid) < 0 &&
  474. SnmpUtilOidCmp(&pNewRLE->LimitOid, &pRLE->PrefixOid) > 0)
  475. {
  476. *ppRLE = pRLE;
  477. return TRUE;
  478. }
  479. // next entry
  480. pLE = pLE->Flink;
  481. }
  482. // failure
  483. return FALSE;
  484. }
  485. BOOL
  486. FindSupportedRegion(
  487. PMIB_REGION_LIST_ENTRY * ppRLE,
  488. AsnObjectIdentifier * pPrefixOid,
  489. BOOL fAnyOk
  490. )
  491. /*++
  492. Routine Description:
  493. Locates MIB region in list.
  494. Arguments:
  495. ppRLE - pointer to receive pointer to list entry.
  496. pPrefixOid - pointer to OID to locate within MIB region.
  497. fAnyOk - true if exact match not necessary.
  498. Return Values:
  499. Returns true if match found.
  500. --*/
  501. {
  502. PLIST_ENTRY pLE;
  503. PMIB_REGION_LIST_ENTRY pRLE;
  504. INT nDiff;
  505. // initialize
  506. *ppRLE = NULL;
  507. // obtain pointer to list head
  508. pLE = g_SupportedRegions.Flink;
  509. // process all entries in list
  510. while (pLE != &g_SupportedRegions) {
  511. // retrieve pointer to mib region structure
  512. pRLE = CONTAINING_RECORD(pLE, MIB_REGION_LIST_ENTRY, Link);
  513. // region prefix should be also the prefix for the given OID
  514. nDiff = SnmpUtilOidNCmp(pPrefixOid, &pRLE->PrefixOid, pRLE->PrefixOid.idLength);
  515. // found match?
  516. if ((nDiff < 0 && fAnyOk) ||
  517. (nDiff == 0 && SnmpUtilOidCmp(pPrefixOid, &pRLE->LimitOid) < 0))
  518. {
  519. *ppRLE = pRLE;
  520. return TRUE;
  521. }
  522. // next entry
  523. pLE = pLE->Flink;
  524. }
  525. // failure
  526. return FALSE;
  527. }
  528. BOOL
  529. LoadSupportedRegions(
  530. )
  531. /*++
  532. Routine Description:
  533. Creates global list of supported MIB regions from subagent MIB regions.
  534. Arguments:
  535. None.
  536. Return Values:
  537. Returns true if successful.
  538. --*/
  539. {
  540. PLIST_ENTRY pLE1;
  541. PLIST_ENTRY pLE2;
  542. PSUBAGENT_LIST_ENTRY pSLE;
  543. PMIB_REGION_LIST_ENTRY pRLE;
  544. // get subagent list head
  545. pLE1 = g_Subagents.Flink;
  546. // process all entries in list
  547. while (pLE1 != &g_Subagents) {
  548. // retrieve pointer to subagent structure
  549. pSLE = CONTAINING_RECORD(pLE1, SUBAGENT_LIST_ENTRY, Link);
  550. SNMPDBG((
  551. SNMP_LOG_VERBOSE,
  552. "SNMP: SVC: Scan views supported by %s.\n",
  553. pSLE->pPathname
  554. ));
  555. // get supported regions list head
  556. pLE2 = pSLE->SupportedRegions.Flink;
  557. // process all entries in list
  558. while (pLE2 != &pSLE->SupportedRegions) {
  559. // retrieve pointer to mib region structure
  560. pRLE = CONTAINING_RECORD(pLE2, MIB_REGION_LIST_ENTRY, Link);
  561. SNMPDBG((
  562. SNMP_LOG_VERBOSE,
  563. "SNMP: SVC: view %s\n",
  564. SnmpUtilOidToA(&pRLE->PrefixOid)
  565. ));
  566. // attempt to add mib region
  567. if (!AddSupportedRegion(pRLE)) {
  568. // failure
  569. return FALSE;
  570. }
  571. // next mib region
  572. pLE2 = pLE2->Flink;
  573. }
  574. // next subagent
  575. pLE1 = pLE1->Flink;
  576. }
  577. // success
  578. return TRUE;
  579. }
  580. BOOL
  581. UnloadSupportedRegions(
  582. )
  583. /*++
  584. Routine Description:
  585. Destroys list of MIB regions.
  586. Arguments:
  587. None.
  588. Return Values:
  589. Returns true if successful.
  590. --*/
  591. {
  592. // unload global supported regions
  593. return UnloadRegions(&g_SupportedRegions);
  594. }