0ef6f25f0a42997769dce3c40bb9fa4539557195
[public/netxms.git] / src / server / core / accesspoint.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2017 Victor Kirhenshtein
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: accesspoint.cpp
20 **/
21
22 #include "nxcore.h"
23
24 /**
25 * Default constructor
26 */
27 AccessPoint::AccessPoint() : DataCollectionTarget()
28 {
29 m_nodeId = 0;
30 m_index = 0;
31 memset(m_macAddr, 0, MAC_ADDR_LENGTH);
32 m_vendor = NULL;
33 m_model = NULL;
34 m_serialNumber = NULL;
35 m_radioInterfaces = NULL;
36 m_apState = AP_ADOPTED;
37 m_prevState = m_apState;
38 }
39
40 /**
41 * Constructor for creating new access point object
42 */
43 AccessPoint::AccessPoint(const TCHAR *name, UINT32 index, const BYTE *macAddr) : DataCollectionTarget(name)
44 {
45 m_nodeId = 0;
46 m_index = index;
47 memcpy(m_macAddr, macAddr, MAC_ADDR_LENGTH);
48 m_vendor = NULL;
49 m_model = NULL;
50 m_serialNumber = NULL;
51 m_radioInterfaces = NULL;
52 m_apState = AP_ADOPTED;
53 m_prevState = m_apState;
54 m_isHidden = true;
55 }
56
57 /**
58 * Destructor
59 */
60 AccessPoint::~AccessPoint()
61 {
62 safe_free(m_vendor);
63 safe_free(m_model);
64 safe_free(m_serialNumber);
65 delete m_radioInterfaces;
66 }
67
68 /**
69 * Create object from database data
70 */
71 bool AccessPoint::loadFromDatabase(DB_HANDLE hdb, UINT32 dwId)
72 {
73 m_id = dwId;
74
75 if (!loadCommonProperties(hdb))
76 {
77 DbgPrintf(2, _T("Cannot load common properties for access point object %d"), dwId);
78 return false;
79 }
80
81 TCHAR query[256];
82 _sntprintf(query, 256, _T("SELECT mac_address,vendor,model,serial_number,node_id,ap_state,ap_index FROM access_points WHERE id=%d"), (int)m_id);
83 DB_RESULT hResult = DBSelect(hdb, query);
84 if (hResult == NULL)
85 return false;
86
87 DBGetFieldByteArray2(hResult, 0, 0, m_macAddr, MAC_ADDR_LENGTH, 0);
88 m_vendor = DBGetField(hResult, 0, 1, NULL, 0);
89 m_model = DBGetField(hResult, 0, 2, NULL, 0);
90 m_serialNumber = DBGetField(hResult, 0, 3, NULL, 0);
91 m_nodeId = DBGetFieldULong(hResult, 0, 4);
92 m_apState = (AccessPointState)DBGetFieldLong(hResult, 0, 5);
93 m_prevState = (m_apState != AP_DOWN) ? m_apState : AP_ADOPTED;
94 m_index = DBGetFieldULong(hResult, 0, 6);
95 DBFreeResult(hResult);
96
97 // Load DCI and access list
98 loadACLFromDB(hdb);
99 loadItemsFromDB(hdb);
100 for(int i = 0; i < m_dcObjects->size(); i++)
101 if (!m_dcObjects->get(i)->loadThresholdsFromDB(hdb))
102 return false;
103
104 // Link access point to node
105 bool success = false;
106 if (!m_isDeleted)
107 {
108 NetObj *object = FindObjectById(m_nodeId);
109 if (object == NULL)
110 {
111 nxlog_write(MSG_INVALID_NODE_ID, EVENTLOG_ERROR_TYPE, "dd", dwId, m_nodeId);
112 }
113 else if (object->getObjectClass() != OBJECT_NODE)
114 {
115 nxlog_write(MSG_NODE_NOT_NODE, EVENTLOG_ERROR_TYPE, "dd", dwId, m_nodeId);
116 }
117 else
118 {
119 object->addChild(this);
120 addParent(object);
121 success = true;
122 }
123 }
124 else
125 {
126 success = true;
127 }
128
129 return success;
130 }
131
132 /**
133 * Save object to database
134 */
135 bool AccessPoint::saveToDatabase(DB_HANDLE hdb)
136 {
137 // Lock object's access
138 lockProperties();
139
140 saveCommonProperties(hdb);
141
142 bool bResult;
143 if (m_modified & MODIFY_OTHER)
144 {
145 DB_STATEMENT hStmt;
146 if (IsDatabaseRecordExist(hdb, _T("access_points"), _T("id"), m_id))
147 hStmt = DBPrepare(hdb, _T("UPDATE access_points SET mac_address=?,vendor=?,model=?,serial_number=?,node_id=?,ap_state=?,ap_index=? WHERE id=?"));
148 else
149 hStmt = DBPrepare(hdb, _T("INSERT INTO access_points (mac_address,vendor,model,serial_number,node_id,ap_state,ap_index,id) VALUES (?,?,?,?,?,?,?,?)"));
150 if (hStmt != NULL)
151 {
152 TCHAR macStr[16];
153 DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, BinToStr(m_macAddr, MAC_ADDR_LENGTH, macStr), DB_BIND_STATIC);
154 DBBind(hStmt, 2, DB_SQLTYPE_VARCHAR, CHECK_NULL_EX(m_vendor), DB_BIND_STATIC);
155 DBBind(hStmt, 3, DB_SQLTYPE_VARCHAR, CHECK_NULL_EX(m_model), DB_BIND_STATIC);
156 DBBind(hStmt, 4, DB_SQLTYPE_VARCHAR, CHECK_NULL_EX(m_serialNumber), DB_BIND_STATIC);
157 DBBind(hStmt, 5, DB_SQLTYPE_INTEGER, m_nodeId);
158 DBBind(hStmt, 6, DB_SQLTYPE_INTEGER, (INT32)m_apState);
159 DBBind(hStmt, 7, DB_SQLTYPE_INTEGER, m_index);
160 DBBind(hStmt, 8, DB_SQLTYPE_INTEGER, m_id);
161
162 bResult = DBExecute(hStmt);
163
164 DBFreeStatement(hStmt);
165 }
166 else
167 {
168 bResult = false;
169 }
170 }
171 else
172 {
173 bResult = true;
174 }
175
176 // Save data collection items
177 if (bResult && (m_modified & MODIFY_DATA_COLLECTION))
178 {
179 lockDciAccess(false);
180 for(int i = 0; i < m_dcObjects->size(); i++)
181 m_dcObjects->get(i)->saveToDatabase(hdb);
182 unlockDciAccess();
183 }
184
185 // Save access list
186 saveACLToDB(hdb);
187
188 // Clear modifications flag and unlock object
189 if (bResult)
190 m_modified = 0;
191 unlockProperties();
192
193 return bResult;
194 }
195
196 /**
197 * Delete object from database
198 */
199 bool AccessPoint::deleteFromDatabase(DB_HANDLE hdb)
200 {
201 bool success = DataCollectionTarget::deleteFromDatabase(hdb);
202 if (success)
203 success = executeQueryOnObject(hdb, _T("DELETE FROM access_points WHERE id=?"));
204 return success;
205 }
206
207 /**
208 * Create CSCP message with object's data
209 */
210 void AccessPoint::fillMessageInternal(NXCPMessage *msg)
211 {
212 DataCollectionTarget::fillMessageInternal(msg);
213 msg->setField(VID_IP_ADDRESS, m_ipAddress);
214 msg->setField(VID_NODE_ID, m_nodeId);
215 msg->setField(VID_MAC_ADDR, m_macAddr, MAC_ADDR_LENGTH);
216 msg->setField(VID_VENDOR, CHECK_NULL_EX(m_vendor));
217 msg->setField(VID_MODEL, CHECK_NULL_EX(m_model));
218 msg->setField(VID_SERIAL_NUMBER, CHECK_NULL_EX(m_serialNumber));
219 msg->setField(VID_STATE, (UINT16)m_apState);
220 msg->setField(VID_AP_INDEX, m_index);
221
222 if (m_radioInterfaces != NULL)
223 {
224 msg->setField(VID_RADIO_COUNT, (WORD)m_radioInterfaces->size());
225 UINT32 varId = VID_RADIO_LIST_BASE;
226 for(int i = 0; i < m_radioInterfaces->size(); i++)
227 {
228 RadioInterfaceInfo *rif = m_radioInterfaces->get(i);
229 msg->setField(varId++, (UINT32)rif->index);
230 msg->setField(varId++, rif->name);
231 msg->setField(varId++, rif->macAddr, MAC_ADDR_LENGTH);
232 msg->setField(varId++, rif->channel);
233 msg->setField(varId++, (UINT32)rif->powerDBm);
234 msg->setField(varId++, (UINT32)rif->powerMW);
235 varId += 4;
236 }
237 }
238 else
239 {
240 msg->setField(VID_RADIO_COUNT, (WORD)0);
241 }
242 }
243
244 /**
245 * Modify object from message
246 */
247 UINT32 AccessPoint::modifyFromMessageInternal(NXCPMessage *msg)
248 {
249 return DataCollectionTarget::modifyFromMessageInternal(msg);
250 }
251
252 /**
253 * Attach access point to node
254 */
255 void AccessPoint::attachToNode(UINT32 nodeId)
256 {
257 if (m_nodeId == nodeId)
258 return;
259
260 if (m_nodeId != 0)
261 {
262 Node *currNode = (Node *)FindObjectById(m_nodeId, OBJECT_NODE);
263 if (currNode != NULL)
264 {
265 currNode->deleteChild(this);
266 deleteParent(currNode);
267 }
268 }
269
270 Node *newNode = (Node *)FindObjectById(nodeId, OBJECT_NODE);
271 if (newNode != NULL)
272 {
273 newNode->addChild(this);
274 addParent(newNode);
275 }
276
277 lockProperties();
278 m_nodeId = nodeId;
279 setModified(MODIFY_OTHER);
280 unlockProperties();
281 }
282
283 /**
284 * Update radio interfaces information
285 */
286 void AccessPoint::updateRadioInterfaces(const ObjectArray<RadioInterfaceInfo> *ri)
287 {
288 lockProperties();
289 if (m_radioInterfaces == NULL)
290 m_radioInterfaces = new ObjectArray<RadioInterfaceInfo>(ri->size(), 4, true);
291 m_radioInterfaces->clear();
292 for(int i = 0; i < ri->size(); i++)
293 {
294 RadioInterfaceInfo *info = new RadioInterfaceInfo;
295 memcpy(info, ri->get(i), sizeof(RadioInterfaceInfo));
296 m_radioInterfaces->add(info);
297 }
298 unlockProperties();
299 }
300
301 /**
302 * Check if given radio interface index (radio ID) is on this access point
303 */
304 bool AccessPoint::isMyRadio(int rfIndex)
305 {
306 bool result = false;
307 lockProperties();
308 if (m_radioInterfaces != NULL)
309 {
310 for(int i = 0; i < m_radioInterfaces->size(); i++)
311 {
312 if (m_radioInterfaces->get(i)->index == rfIndex)
313 {
314 result = true;
315 break;
316 }
317 }
318 }
319 unlockProperties();
320 return result;
321 }
322
323 /**
324 * Check if given radio MAC address (BSSID) is on this access point
325 */
326 bool AccessPoint::isMyRadio(const BYTE *macAddr)
327 {
328 bool result = false;
329 lockProperties();
330 if (m_radioInterfaces != NULL)
331 {
332 for(int i = 0; i < m_radioInterfaces->size(); i++)
333 {
334 if (!memcmp(m_radioInterfaces->get(i)->macAddr, macAddr, MAC_ADDR_LENGTH))
335 {
336 result = true;
337 break;
338 }
339 }
340 }
341 unlockProperties();
342 return result;
343 }
344
345 /**
346 * Get radio name
347 */
348 void AccessPoint::getRadioName(int rfIndex, TCHAR *buffer, size_t bufSize)
349 {
350 buffer[0] = 0;
351 lockProperties();
352 if (m_radioInterfaces != NULL)
353 {
354 for(int i = 0; i < m_radioInterfaces->size(); i++)
355 {
356 if (m_radioInterfaces->get(i)->index == rfIndex)
357 {
358 nx_strncpy(buffer, m_radioInterfaces->get(i)->name, bufSize);
359 break;
360 }
361 }
362 }
363 unlockProperties();
364 }
365
366 /**
367 * Get access point's parent node
368 */
369 Node *AccessPoint::getParentNode()
370 {
371 return (Node *)FindObjectById(m_nodeId, OBJECT_NODE);
372 }
373
374 /**
375 * Update access point information
376 */
377 void AccessPoint::updateInfo(const TCHAR *vendor, const TCHAR *model, const TCHAR *serialNumber)
378 {
379 lockProperties();
380
381 free(m_vendor);
382 m_vendor = (vendor != NULL) ? _tcsdup(vendor) : NULL;
383
384 free(m_model);
385 m_model = (model != NULL) ? _tcsdup(model) : NULL;
386
387 free(m_serialNumber);
388 m_serialNumber = (serialNumber != NULL) ? _tcsdup(serialNumber) : NULL;
389
390 setModified(MODIFY_OTHER);
391 unlockProperties();
392 }
393
394 /**
395 * Update access point state
396 */
397 void AccessPoint::updateState(AccessPointState state)
398 {
399 if (state == m_apState)
400 return;
401
402 lockProperties();
403 if (state == AP_DOWN)
404 m_prevState = m_apState;
405 m_apState = state;
406 if (m_status != STATUS_UNMANAGED)
407 {
408 switch(state)
409 {
410 case AP_ADOPTED:
411 m_status = STATUS_NORMAL;
412 break;
413 case AP_UNADOPTED:
414 m_status = STATUS_MAJOR;
415 break;
416 case AP_DOWN:
417 m_status = STATUS_CRITICAL;
418 break;
419 default:
420 m_status = STATUS_UNKNOWN;
421 break;
422 }
423 }
424 setModified(MODIFY_OTHER);
425 unlockProperties();
426
427 if ((state == AP_ADOPTED) || (state == AP_UNADOPTED) || (state == AP_DOWN))
428 {
429 static const TCHAR *names[] = { _T("id"), _T("name"), _T("macAddr"), _T("ipAddr"), _T("vendor"), _T("model"), _T("serialNumber") };
430 PostEventWithNames((state == AP_ADOPTED) ? EVENT_AP_ADOPTED : ((state == AP_UNADOPTED) ? EVENT_AP_UNADOPTED : EVENT_AP_DOWN),
431 m_nodeId, "ishAsss", names,
432 m_id, m_name, m_macAddr, &m_ipAddress,
433 CHECK_NULL_EX(m_vendor), CHECK_NULL_EX(m_model), CHECK_NULL_EX(m_serialNumber));
434 }
435 }
436
437 /**
438 * Do status poll
439 */
440 void AccessPoint::statusPollFromController(ClientSession *session, UINT32 rqId, Queue *eventQueue, Node *controller, SNMP_Transport *snmpTransport)
441 {
442 m_pollRequestor = session;
443
444 sendPollerMsg(rqId, _T(" Starting status poll on access point %s\r\n"), m_name);
445 sendPollerMsg(rqId, _T(" Current access point status is %s\r\n"), GetStatusAsText(m_status, true));
446
447 AccessPointState state = controller->getAccessPointState(this, snmpTransport);
448 if ((state == AP_UNKNOWN) && m_ipAddress.isValid())
449 {
450 DbgPrintf(6, _T("AccessPoint::statusPoll(%s [%d]): unable to get AP state from driver"), m_name, m_id);
451 sendPollerMsg(rqId, POLLER_WARNING _T(" Unable to get AP state from controller\r\n"));
452
453 UINT32 icmpProxy = 0;
454
455 if (IsZoningEnabled() && (controller->getZoneUIN() != 0))
456 {
457 Zone *zone = FindZoneByUIN(controller->getZoneUIN());
458 if (zone != NULL)
459 {
460 icmpProxy = zone->getProxyNodeId();
461 }
462 }
463
464 if (icmpProxy != 0)
465 {
466 sendPollerMsg(rqId, _T(" Starting ICMP ping via proxy\r\n"));
467 DbgPrintf(7, _T("AccessPoint::StatusPoll(%d,%s): ping via proxy [%u]"), m_id, m_name, icmpProxy);
468 Node *proxyNode = (Node *)g_idxNodeById.get(icmpProxy);
469 if ((proxyNode != NULL) && proxyNode->isNativeAgent() && !proxyNode->isDown())
470 {
471 DbgPrintf(7, _T("AccessPoint::StatusPoll(%d,%s): proxy node found: %s"), m_id, m_name, proxyNode->getName());
472 AgentConnection *conn = proxyNode->createAgentConnection();
473 if (conn != NULL)
474 {
475 TCHAR parameter[64], buffer[64];
476
477 _sntprintf(parameter, 64, _T("Icmp.Ping(%s)"), m_ipAddress.toString(buffer));
478 if (conn->getParameter(parameter, 64, buffer) == ERR_SUCCESS)
479 {
480 DbgPrintf(7, _T("AccessPoint::StatusPoll(%d,%s): proxy response: \"%s\""), m_id, m_name, buffer);
481 TCHAR *eptr;
482 long value = _tcstol(buffer, &eptr, 10);
483 if ((*eptr == 0) && (value >= 0))
484 {
485 m_pingLastTimeStamp = time(NULL);
486 m_pingTime = value;
487 if (value < 10000)
488 {
489 sendPollerMsg(rqId, POLLER_ERROR _T(" responded to ICMP ping\r\n"));
490 if (state == AP_DOWN)
491 state = m_prevState; /* FIXME: get actual AP state here */
492 }
493 else
494 {
495 sendPollerMsg(rqId, POLLER_ERROR _T(" no response to ICMP ping\r\n"));
496 state = AP_DOWN;
497 }
498 }
499 }
500 conn->disconnect();
501 conn->decRefCount();
502 }
503 else
504 {
505 DbgPrintf(7, _T("AccessPoint::StatusPoll(%d,%s): cannot connect to agent on proxy node"), m_id, m_name);
506 sendPollerMsg(rqId, POLLER_ERROR _T(" Unable to establish connection with proxy node\r\n"));
507 }
508 }
509 else
510 {
511 DbgPrintf(7, _T("AccessPoint::StatusPoll(%d,%s): proxy node not available"), m_id, m_name);
512 sendPollerMsg(rqId, POLLER_ERROR _T(" ICMP proxy not available\r\n"));
513 }
514 }
515 else // not using ICMP proxy
516 {
517 TCHAR buffer[64];
518 sendPollerMsg(rqId, _T(" Starting ICMP ping\r\n"));
519 DbgPrintf(7, _T("AccessPoint::StatusPoll(%d,%s): calling IcmpPing(%s,3,%d,NULL,%d)"), m_id, m_name, m_ipAddress.toString(buffer), g_icmpPingTimeout, g_icmpPingSize);
520 UINT32 dwPingStatus = IcmpPing(m_ipAddress, 3, g_icmpPingTimeout, &m_pingTime, g_icmpPingSize);
521 m_pingLastTimeStamp = time(NULL);
522 if (dwPingStatus == ICMP_SUCCESS)
523 {
524 sendPollerMsg(rqId, POLLER_ERROR _T(" responded to ICMP ping\r\n"));
525 if (state == AP_DOWN)
526 state = m_prevState; /* FIXME: get actual AP state here */
527 }
528 else
529 {
530 sendPollerMsg(rqId, POLLER_ERROR _T(" no response to ICMP ping\r\n"));
531 state = AP_DOWN;
532 m_pingTime = PING_TIME_TIMEOUT;
533 }
534 DbgPrintf(7, _T("AccessPoint::StatusPoll(%d,%s): ping result %d, state=%d"), m_id, m_name, dwPingStatus, state);
535 }
536 }
537
538 updateState(state);
539
540 sendPollerMsg(rqId, _T(" Access point status after poll is %s\r\n"), GetStatusAsText(m_status, true));
541 sendPollerMsg(rqId, _T(" Finished status poll on access point %s\r\n"), m_name);
542 }
543
544 /**
545 * Updates last ping time and ping time
546 */
547 void AccessPoint::updatePingData()
548 {
549 Node *pNode = getParentNode();
550 if (pNode == NULL)
551 {
552 DbgPrintf(7, _T("AccessPoint::updatePingData: Can't find parent node"));
553 return;
554 }
555 UINT32 icmpProxy = pNode->getIcmpProxy();
556
557 if (IsZoningEnabled() && (pNode->getZoneUIN() != 0) && (icmpProxy == 0))
558 {
559 Zone *zone = FindZoneByUIN(pNode->getZoneUIN());
560 if (zone != NULL)
561 {
562 icmpProxy = zone->getProxyNodeId();
563 }
564 }
565
566 if (icmpProxy != 0)
567 {
568 DbgPrintf(7, _T("AccessPoint::updatePingData: ping via proxy [%u]"), icmpProxy);
569 Node *proxyNode = (Node *)g_idxNodeById.get(icmpProxy);
570 if ((proxyNode != NULL) && proxyNode->isNativeAgent() && !proxyNode->isDown())
571 {
572 DbgPrintf(7, _T("AccessPoint::updatePingData: proxy node found: %s"), proxyNode->getName());
573 AgentConnection *conn = proxyNode->createAgentConnection();
574 if (conn != NULL)
575 {
576 TCHAR parameter[64], buffer[64];
577
578 _sntprintf(parameter, 64, _T("Icmp.Ping(%s)"), m_ipAddress.toString(buffer));
579 if (conn->getParameter(parameter, 64, buffer) == ERR_SUCCESS)
580 {
581 DbgPrintf(7, _T("AccessPoint::updatePingData: proxy response: \"%s\""), buffer);
582 TCHAR *eptr;
583 long value = _tcstol(buffer, &eptr, 10);
584 m_pingLastTimeStamp = time(NULL);
585 if ((*eptr == 0) && (value >= 0) && (value < 10000))
586 {
587 m_pingTime = value;
588 }
589 else
590 {
591 m_pingTime = PING_TIME_TIMEOUT;
592 DbgPrintf(7, _T("AccessPoint::updatePingData: incorrect value: %d or error while parsing: %s"), value, eptr);
593 }
594 }
595 conn->disconnect();
596 conn->decRefCount();
597 }
598 else
599 {
600 DbgPrintf(7, _T("AccessPoint::updatePingData: cannot connect to agent on proxy node [%u]"), icmpProxy);
601 }
602 }
603 else
604 {
605 DbgPrintf(7, _T("AccessPoint::updatePingData: proxy node not available [%u]"), icmpProxy);
606 }
607 }
608 else // not using ICMP proxy
609 {
610 UINT32 dwPingStatus = IcmpPing(m_ipAddress, 3, g_icmpPingTimeout, &m_pingTime, g_icmpPingSize);
611 if (dwPingStatus != ICMP_SUCCESS)
612 {
613 DbgPrintf(7, _T("AccessPoint::updatePingData: error getting ping %d"), dwPingStatus);
614 m_pingTime = PING_TIME_TIMEOUT;
615 }
616 m_pingLastTimeStamp = time(NULL);
617 }
618 }
619
620 /**
621 * Serialize object to JSON
622 */
623 json_t *AccessPoint::toJson()
624 {
625 json_t *root = DataCollectionTarget::toJson();
626 json_object_set_new(root, "index", json_integer(m_index));
627 json_object_set_new(root, "ipAddress", m_ipAddress.toJson());
628 json_object_set_new(root, "nodeId", json_integer(m_nodeId));
629 char macAddrText[64];
630 json_object_set_new(root, "macAddr", json_string_a(BinToStrA(m_macAddr, sizeof(m_macAddr), macAddrText)));
631 json_object_set_new(root, "vendor", json_string_t(m_vendor));
632 json_object_set_new(root, "model", json_string_t(m_model));
633 json_object_set_new(root, "serialNumber", json_string_t(m_serialNumber));
634 json_object_set_new(root, "radioInterfaces", json_object_array(m_radioInterfaces));
635 json_object_set_new(root, "state", json_integer(m_apState));
636 json_object_set_new(root, "prevState", json_integer(m_prevState));
637 return root;
638 }