Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / src / app / server / Server.cpp
1 /*
2  *
3  *    Copyright (c) 2020 Project CHIP Authors
4  *
5  *    Licensed under the Apache License, Version 2.0 (the "License");
6  *    you may not use this file except in compliance with the License.
7  *    You may obtain a copy of the License at
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *    Unless required by applicable law or agreed to in writing, software
12  *    distributed under the License is distributed on an "AS IS" BASIS,
13  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *    See the License for the specific language governing permissions and
15  *    limitations under the License.
16  */
17
18 #include <inttypes.h>
19
20 #include <app/server/Server.h>
21
22 #include <app/InteractionModelEngine.h>
23 #include <app/server/DataModelHandler.h>
24 #include <app/server/EchoHandler.h>
25 #include <app/server/RendezvousServer.h>
26 #include <app/server/SessionManager.h>
27
28 #include <ble/BLEEndPoint.h>
29 #include <core/CHIPPersistentStorageDelegate.h>
30 #include <inet/IPAddress.h>
31 #include <inet/InetError.h>
32 #include <inet/InetLayer.h>
33 #include <messaging/ExchangeMgr.h>
34 #include <platform/CHIPDeviceLayer.h>
35 #include <platform/KeyValueStoreManager.h>
36 #include <setup_payload/SetupPayload.h>
37 #include <support/CodeUtils.h>
38 #include <support/ErrorStr.h>
39 #include <support/logging/CHIPLogging.h>
40 #include <sys/param.h>
41 #include <system/SystemPacketBuffer.h>
42 #include <system/TLVPacketBufferBackingStore.h>
43 #include <transport/SecureSessionMgr.h>
44 #include <transport/StorablePeerConnection.h>
45
46 #include "Mdns.h"
47
48 using namespace ::chip;
49 using namespace ::chip::Inet;
50 using namespace ::chip::Transport;
51 using namespace ::chip::DeviceLayer;
52 using namespace ::chip::Messaging;
53
54 namespace {
55
56 constexpr bool isRendezvousBypassed()
57 {
58 #if defined(CHIP_BYPASS_RENDEZVOUS) && CHIP_BYPASS_RENDEZVOUS
59     return true;
60 #elif defined(CONFIG_RENDEZVOUS_MODE)
61     return static_cast<RendezvousInformationFlags>(CONFIG_RENDEZVOUS_MODE) == RendezvousInformationFlags::kNone;
62 #else
63     return false;
64 #endif
65 }
66
67 constexpr bool useTestPairing()
68 {
69     // Use the test pairing whenever rendezvous is bypassed. Otherwise, there wouldn't be
70     // any way to communicate with the device using CHIP protocol.
71     // This is used to bypass BLE in the cirque test.
72     // Only in the cirque test this is enabled with --args='bypass_rendezvous=true'.
73     return isRendezvousBypassed();
74 }
75
76 class ServerStorageDelegate : public PersistentStorageDelegate
77 {
78     void SetStorageDelegate(PersistentStorageResultDelegate * delegate) override
79     {
80         ChipLogError(AppServer, "ServerStorageDelegate does not support async operations");
81         chipDie();
82     }
83
84     void AsyncSetKeyValue(const char * key, const char * value) override
85     {
86         ChipLogError(AppServer, "ServerStorageDelegate does not support async operations");
87         chipDie();
88     }
89
90     CHIP_ERROR SyncGetKeyValue(const char * key, void * buffer, uint16_t & size) override
91     {
92         return PersistedStorage::KeyValueStoreMgr().Get(key, buffer, size);
93     }
94
95     CHIP_ERROR SyncSetKeyValue(const char * key, const void * value, uint16_t size) override
96     {
97         return PersistedStorage::KeyValueStoreMgr().Put(key, value, size);
98     }
99
100     void AsyncDeleteKeyValue(const char * key) override { PersistedStorage::KeyValueStoreMgr().Delete(key); }
101 };
102
103 ServerStorageDelegate gServerStorage;
104
105 CHIP_ERROR PersistAdminPairingToKVS(AdminPairingInfo * admin, AdminId nextAvailableId)
106 {
107     ReturnErrorCodeIf(admin == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
108     ChipLogProgress(AppServer, "Persisting admin ID %d, next available %d", admin->GetAdminId(), nextAvailableId);
109
110     ReturnErrorOnFailure(admin->StoreIntoKVS(gServerStorage));
111     ReturnErrorOnFailure(PersistedStorage::KeyValueStoreMgr().Put(kAdminTableCountKey, &nextAvailableId, sizeof(nextAvailableId)));
112
113     ChipLogProgress(AppServer, "Persisting admin ID successfully");
114     return CHIP_NO_ERROR;
115 }
116
117 CHIP_ERROR RestoreAllAdminPairingsFromKVS(AdminPairingTable & adminPairings, AdminId & nextAvailableId)
118 {
119     // It's not an error if the key doesn't exist. Just return right away.
120     VerifyOrReturnError(PersistedStorage::KeyValueStoreMgr().Get(kAdminTableCountKey, &nextAvailableId) == CHIP_NO_ERROR,
121                         CHIP_NO_ERROR);
122     ChipLogProgress(AppServer, "Next available admin ID is %d", nextAvailableId);
123
124     // TODO: The admin ID space allocation should be re-evaluated. With the current approach, the space could be
125     //       exhausted while IDs are still available (e.g. if the admin IDs are allocated and freed over a period of time).
126     //       Also, the current approach can make ID lookup slower as more IDs are allocated and freed.
127     for (AdminId id = 0; id < nextAvailableId; id++)
128     {
129         AdminPairingInfo * admin = adminPairings.AssignAdminId(id);
130         // Recreate the binding if one exists in persistent storage. Else skip to the next ID
131         if (admin->FetchFromKVS(gServerStorage) != CHIP_NO_ERROR)
132         {
133             adminPairings.ReleaseAdminId(id);
134         }
135         else
136         {
137             ChipLogProgress(AppServer, "Found admin pairing for %d, node ID 0x%08" PRIx32 "%08" PRIx32, admin->GetAdminId(),
138                             static_cast<uint32_t>(admin->GetNodeId() >> 32), static_cast<uint32_t>(admin->GetNodeId()));
139         }
140     }
141
142     return CHIP_NO_ERROR;
143 }
144
145 void EraseAllAdminPairingsUpTo(AdminId nextAvailableId)
146 {
147     PersistedStorage::KeyValueStoreMgr().Delete(kAdminTableCountKey);
148
149     for (AdminId id = 0; id < nextAvailableId; id++)
150     {
151         AdminPairingInfo::DeleteFromKVS(gServerStorage, id);
152     }
153 }
154
155 static CHIP_ERROR RestoreAllSessionsFromKVS(SecureSessionMgr & sessionMgr, RendezvousServer & server)
156 {
157     uint16_t nextSessionKeyId = 0;
158     // It's not an error if the key doesn't exist. Just return right away.
159     VerifyOrReturnError(PersistedStorage::KeyValueStoreMgr().Get(kStorablePeerConnectionCountKey, &nextSessionKeyId) ==
160                             CHIP_NO_ERROR,
161                         CHIP_NO_ERROR);
162     ChipLogProgress(AppServer, "Found %d stored connections", nextSessionKeyId);
163
164     PASESession * session = chip::Platform::New<PASESession>();
165     VerifyOrReturnError(session != nullptr, CHIP_ERROR_NO_MEMORY);
166
167     for (uint16_t keyId = 0; keyId < nextSessionKeyId; keyId++)
168     {
169         StorablePeerConnection connection;
170         if (CHIP_NO_ERROR == connection.FetchFromKVS(gServerStorage, keyId))
171         {
172             connection.GetPASESession(session);
173
174             ChipLogProgress(AppServer, "Fetched the session information: from 0x%08" PRIx32 "%08" PRIx32,
175                             static_cast<uint32_t>(session->PeerConnection().GetPeerNodeId() >> 32),
176                             static_cast<uint32_t>(session->PeerConnection().GetPeerNodeId()));
177             sessionMgr.NewPairing(Optional<Transport::PeerAddress>::Value(session->PeerConnection().GetPeerAddress()),
178                                   session->PeerConnection().GetPeerNodeId(), session,
179                                   SecureSessionMgr::PairingDirection::kResponder, connection.GetAdminId(), nullptr);
180             session->Clear();
181         }
182     }
183
184     chip::Platform::Delete(session);
185
186     server.GetRendezvousSession()->SetNextKeyId(nextSessionKeyId);
187     return CHIP_NO_ERROR;
188 }
189
190 void EraseAllSessionsUpTo(uint16_t nextSessionKeyId)
191 {
192     PersistedStorage::KeyValueStoreMgr().Delete(kStorablePeerConnectionCountKey);
193
194     for (uint16_t keyId = 0; keyId < nextSessionKeyId; keyId++)
195     {
196         StorablePeerConnection::DeleteFromKVS(gServerStorage, keyId);
197     }
198 }
199
200 // TODO: The following class is setting the discriminator in Persistent Storage. This is
201 //       is needed since BLE reads the discriminator using ConfigurationMgr APIs. The
202 //       better solution will be to pass the discriminator to BLE without changing it
203 //       in the persistent storage.
204 //       https://github.com/project-chip/connectedhomeip/issues/4767
205 class DeviceDiscriminatorCache
206 {
207 public:
208     CHIP_ERROR UpdateDiscriminator(uint16_t discriminator)
209     {
210         if (!mOriginalDiscriminatorCached)
211         {
212             // Cache the original discriminator
213             ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().GetSetupDiscriminator(mOriginalDiscriminator));
214             mOriginalDiscriminatorCached = true;
215         }
216
217         return DeviceLayer::ConfigurationMgr().StoreSetupDiscriminator(discriminator);
218     }
219
220     CHIP_ERROR RestoreDiscriminator()
221     {
222         if (mOriginalDiscriminatorCached)
223         {
224             // Restore the original discriminator
225             ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().StoreSetupDiscriminator(mOriginalDiscriminator));
226             mOriginalDiscriminatorCached = false;
227         }
228
229         return CHIP_NO_ERROR;
230     }
231
232 private:
233     bool mOriginalDiscriminatorCached = false;
234     uint16_t mOriginalDiscriminator   = 0;
235 };
236
237 DeviceDiscriminatorCache gDeviceDiscriminatorCache;
238 AdminPairingTable gAdminPairings;
239 AdminId gNextAvailableAdminId = 0;
240
241 class ServerRendezvousAdvertisementDelegate : public RendezvousAdvertisementDelegate
242 {
243 public:
244     CHIP_ERROR StartAdvertisement() const override
245     {
246         ReturnErrorOnFailure(chip::DeviceLayer::ConnectivityMgr().SetBLEAdvertisingEnabled(true));
247         if (mDelegate != nullptr)
248         {
249             mDelegate->OnPairingWindowOpened();
250         }
251         return CHIP_NO_ERROR;
252     }
253     CHIP_ERROR StopAdvertisement() const override
254     {
255         gDeviceDiscriminatorCache.RestoreDiscriminator();
256
257         ReturnErrorOnFailure(chip::DeviceLayer::ConnectivityMgr().SetBLEAdvertisingEnabled(false));
258         {
259             if (mDelegate != nullptr)
260                 mDelegate->OnPairingWindowClosed();
261         }
262
263         AdminPairingInfo * admin = gAdminPairings.FindAdmin(mAdmin);
264         if (admin != nullptr)
265         {
266             ReturnErrorOnFailure(PersistAdminPairingToKVS(admin, gNextAvailableAdminId));
267         }
268
269         return CHIP_NO_ERROR;
270     }
271
272     void RendezvousComplete() const override
273     {
274         // Once rendezvous completed, assume we are operational
275         if (app::Mdns::AdvertiseOperational() != CHIP_NO_ERROR)
276         {
277             ChipLogError(Discovery, "Failed to start advertising operational state at rendezvous completion time.");
278         }
279     }
280
281     void SetDelegate(AppDelegate * delegate) { mDelegate = delegate; }
282
283     void SetAdminId(AdminId id) { mAdmin = id; }
284
285 private:
286     AppDelegate * mDelegate = nullptr;
287     AdminId mAdmin;
288 };
289
290 DemoTransportMgr gTransports;
291 SecureSessionMgr gSessions;
292 RendezvousServer gRendezvousServer;
293
294 ServerRendezvousAdvertisementDelegate gAdvDelegate;
295
296 static CHIP_ERROR OpenPairingWindowUsingVerifier(uint16_t discriminator, PASEVerifier & verifier)
297 {
298     RendezvousParameters params;
299
300     ReturnErrorOnFailure(gDeviceDiscriminatorCache.UpdateDiscriminator(discriminator));
301
302 #if CONFIG_NETWORK_LAYER_BLE
303     params.SetPASEVerifier(verifier)
304         .SetBleLayer(DeviceLayer::ConnectivityMgr().GetBleLayer())
305         .SetPeerAddress(Transport::PeerAddress::BLE())
306         .SetAdvertisementDelegate(&gAdvDelegate);
307 #else
308     params.SetPASEVerifier(verifier);
309 #endif // CONFIG_NETWORK_LAYER_BLE
310
311     AdminId admin                = gNextAvailableAdminId;
312     AdminPairingInfo * adminInfo = gAdminPairings.AssignAdminId(admin);
313     VerifyOrReturnError(adminInfo != nullptr, CHIP_ERROR_NO_MEMORY);
314     gNextAvailableAdminId++;
315
316     return gRendezvousServer.WaitForPairing(std::move(params), &gTransports, &gSessions, adminInfo);
317 }
318
319 class ServerCallback : public SecureSessionMgrDelegate
320 {
321 public:
322     void OnMessageReceived(const PacketHeader & header, const PayloadHeader & payloadHeader, SecureSessionHandle session,
323                            System::PacketBufferHandle buffer, SecureSessionMgr * mgr) override
324     {
325         auto state = mgr->GetPeerConnectionState(session);
326         char src_addr[PeerAddress::kMaxToStringSize];
327
328         // as soon as a client connects, assume it is connected
329         VerifyOrExit(!buffer.IsNull(), ChipLogProgress(AppServer, "Received data but couldn't process it..."));
330         VerifyOrExit(header.GetSourceNodeId().HasValue(), ChipLogProgress(AppServer, "Unknown source for received message"));
331
332         VerifyOrExit(state->GetPeerNodeId() != kUndefinedNodeId, ChipLogProgress(AppServer, "Unknown source for received message"));
333
334         state->GetPeerAddress().ToString(src_addr);
335
336         ChipLogProgress(AppServer, "Packet received from %s: %u bytes", src_addr, buffer->DataLength());
337
338         // TODO: This code is temporary, and must be updated to use the Cluster API.
339         // Issue: https://github.com/project-chip/connectedhomeip/issues/4725
340         if (payloadHeader.HasProtocol(chip::Protocols::ServiceProvisioning::Id))
341         {
342             CHIP_ERROR err = CHIP_NO_ERROR;
343             uint32_t timeout;
344             uint16_t discriminator;
345             PASEVerifier verifier;
346
347             ChipLogProgress(AppServer, "Received service provisioning message. Treating it as OpenPairingWindow request");
348             chip::System::PacketBufferTLVReader reader;
349             reader.Init(std::move(buffer));
350             reader.ImplicitProfileId = chip::Protocols::ServiceProvisioning::Id.ToTLVProfileId();
351
352             SuccessOrExit(reader.Next(kTLVType_UnsignedInteger, TLV::ProfileTag(reader.ImplicitProfileId, 1)));
353             SuccessOrExit(reader.Get(timeout));
354
355             err = reader.Next(kTLVType_UnsignedInteger, TLV::ProfileTag(reader.ImplicitProfileId, 2));
356             if (err == CHIP_NO_ERROR)
357             {
358                 SuccessOrExit(reader.Get(discriminator));
359
360                 err = reader.Next(kTLVType_ByteString, TLV::ProfileTag(reader.ImplicitProfileId, 3));
361                 if (err == CHIP_NO_ERROR)
362                 {
363                     SuccessOrExit(reader.GetBytes(reinterpret_cast<uint8_t *>(verifier), sizeof(verifier)));
364                 }
365             }
366
367             ChipLogProgress(AppServer, "Pairing Window timeout %d seconds", timeout);
368
369             if (err != CHIP_NO_ERROR)
370             {
371                 SuccessOrExit(err = OpenDefaultPairingWindow(ResetAdmins::kNo));
372             }
373             else
374             {
375                 ChipLogProgress(AppServer, "Pairing Window discriminator %d", discriminator);
376                 err = OpenPairingWindowUsingVerifier(discriminator, verifier);
377                 SuccessOrExit(err);
378             }
379             ChipLogProgress(AppServer, "Opened the pairing window");
380         }
381         else
382         {
383             HandleDataModelMessage(header.GetSourceNodeId().Value(), std::move(buffer));
384         }
385
386     exit:;
387     }
388
389     void OnReceiveError(CHIP_ERROR error, const Transport::PeerAddress & source, SecureSessionMgr * mgr) override
390     {
391         ChipLogProgress(AppServer, "Packet received error: %s", ErrorStr(error));
392         if (mDelegate != nullptr)
393         {
394             mDelegate->OnReceiveError();
395         }
396     }
397
398     void OnNewConnection(SecureSessionHandle session, SecureSessionMgr * mgr) override
399     {
400         ChipLogProgress(AppServer, "Received a new connection.");
401     }
402
403     void SetDelegate(AppDelegate * delegate) { mDelegate = delegate; }
404
405 private:
406     AppDelegate * mDelegate = nullptr;
407 };
408
409 #if CHIP_ENABLE_INTERACTION_MODEL || defined(CHIP_APP_USE_ECHO)
410 Messaging::ExchangeManager gExchangeMgr;
411 #endif
412 ServerCallback gCallbacks;
413 SecurePairingUsingTestSecret gTestPairing;
414
415 } // namespace
416
417 SecureSessionMgr & chip::SessionManager()
418 {
419     return gSessions;
420 }
421
422 CHIP_ERROR OpenDefaultPairingWindow(ResetAdmins resetAdmins)
423 {
424     gDeviceDiscriminatorCache.RestoreDiscriminator();
425
426     uint32_t pinCode;
427     ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().GetSetupPinCode(pinCode));
428
429     RendezvousParameters params;
430
431 #if CONFIG_NETWORK_LAYER_BLE
432     params.SetSetupPINCode(pinCode)
433         .SetBleLayer(DeviceLayer::ConnectivityMgr().GetBleLayer())
434         .SetPeerAddress(Transport::PeerAddress::BLE())
435         .SetAdvertisementDelegate(&gAdvDelegate);
436 #else
437     params.SetSetupPINCode(pinCode);
438 #endif // CONFIG_NETWORK_LAYER_BLE
439
440     if (resetAdmins == ResetAdmins::kYes)
441     {
442         uint16_t nextKeyId = gRendezvousServer.GetRendezvousSession()->GetNextKeyId();
443         EraseAllAdminPairingsUpTo(gNextAvailableAdminId);
444         EraseAllSessionsUpTo(nextKeyId);
445         gNextAvailableAdminId = 0;
446         gAdminPairings.Reset();
447     }
448
449     AdminId admin                = gNextAvailableAdminId;
450     AdminPairingInfo * adminInfo = gAdminPairings.AssignAdminId(admin);
451     VerifyOrReturnError(adminInfo != nullptr, CHIP_ERROR_NO_MEMORY);
452     gNextAvailableAdminId++;
453
454     return gRendezvousServer.WaitForPairing(std::move(params), &gTransports, &gSessions, adminInfo);
455 }
456
457 // The function will initialize datamodel handler and then start the server
458 // The server assumes the platform's networking has been setup already
459 void InitServer(AppDelegate * delegate)
460 {
461     CHIP_ERROR err = CHIP_NO_ERROR;
462
463     chip::Platform::MemoryInit();
464
465     InitDataModelHandler();
466     gCallbacks.SetDelegate(delegate);
467
468     err = gRendezvousServer.Init(delegate, &gServerStorage);
469     SuccessOrExit(err);
470
471     gAdvDelegate.SetDelegate(delegate);
472
473     // Init transport before operations with secure session mgr.
474 #if INET_CONFIG_ENABLE_IPV4
475     err = gTransports.Init(UdpListenParameters(&DeviceLayer::InetLayer).SetAddressType(kIPAddressType_IPv6),
476                            UdpListenParameters(&DeviceLayer::InetLayer).SetAddressType(kIPAddressType_IPv4));
477 #else
478     err = gTransports.Init(UdpListenParameters(&DeviceLayer::InetLayer).SetAddressType(kIPAddressType_IPv6));
479 #endif
480     SuccessOrExit(err);
481
482     err = gSessions.Init(chip::kTestDeviceNodeId, &DeviceLayer::SystemLayer, &gTransports, &gAdminPairings);
483     SuccessOrExit(err);
484
485 #if CHIP_ENABLE_INTERACTION_MODEL || defined(CHIP_APP_USE_ECHO)
486     err = gExchangeMgr.Init(&gSessions);
487     SuccessOrExit(err);
488 #else
489     gSessions.SetDelegate(&gCallbacks);
490 #endif
491
492 #if CHIP_ENABLE_INTERACTION_MODEL
493     err = chip::app::InteractionModelEngine::GetInstance()->Init(&gExchangeMgr, nullptr);
494     SuccessOrExit(err);
495 #endif
496
497 #if defined(CHIP_APP_USE_ECHO)
498     err = InitEchoHandler(&gExchangeMgr);
499     SuccessOrExit(err);
500 #endif
501
502     if (useTestPairing())
503     {
504         ChipLogProgress(AppServer, "Rendezvous and secure pairing skipped");
505         SuccessOrExit(err = AddTestPairing());
506     }
507     else if (DeviceLayer::ConnectivityMgr().IsWiFiStationProvisioned() || DeviceLayer::ConnectivityMgr().IsThreadProvisioned())
508     {
509         // If the network is already provisioned, proactively disable BLE advertisement.
510         ChipLogProgress(AppServer, "Network already provisioned. Disabling BLE advertisement");
511         chip::DeviceLayer::ConnectivityMgr().SetBLEAdvertisingEnabled(false);
512
513         // Restore any previous admin pairings
514         VerifyOrExit(CHIP_NO_ERROR == RestoreAllAdminPairingsFromKVS(gAdminPairings, gNextAvailableAdminId),
515                      ChipLogError(AppServer, "Could not restore admin table"));
516
517         VerifyOrExit(CHIP_NO_ERROR == RestoreAllSessionsFromKVS(gSessions, gRendezvousServer),
518                      ChipLogError(AppServer, "Could not restore previous sessions"));
519     }
520     else
521     {
522 #if CHIP_DEVICE_CONFIG_ENABLE_PAIRING_AUTOSTART
523         SuccessOrExit(err = OpenDefaultPairingWindow(ResetAdmins::kYes));
524 #endif
525     }
526
527 // Starting mDNS server only for Thread devices due to problem reported in issue #5076.
528 #if CHIP_DEVICE_CONFIG_ENABLE_THREAD
529     app::Mdns::StartServer();
530 #endif
531
532 exit:
533     if (err != CHIP_NO_ERROR)
534     {
535         ChipLogError(AppServer, "ERROR setting up transport: %s", ErrorStr(err));
536     }
537     else
538     {
539         ChipLogProgress(AppServer, "Server Listening...");
540     }
541 }
542
543 CHIP_ERROR AddTestPairing()
544 {
545     CHIP_ERROR err               = CHIP_NO_ERROR;
546     AdminPairingInfo * adminInfo = nullptr;
547
548     for (const AdminPairingInfo & admin : gAdminPairings)
549         if (admin.IsInitialized() && admin.GetNodeId() == chip::kTestDeviceNodeId)
550             ExitNow();
551
552     adminInfo = gAdminPairings.AssignAdminId(gNextAvailableAdminId);
553     VerifyOrExit(adminInfo != nullptr, err = CHIP_ERROR_NO_MEMORY);
554
555     adminInfo->SetNodeId(chip::kTestDeviceNodeId);
556     SuccessOrExit(err = gSessions.NewPairing(Optional<PeerAddress>{ PeerAddress::Uninitialized() }, chip::kTestControllerNodeId,
557                                              &gTestPairing, SecureSessionMgr::PairingDirection::kResponder, gNextAvailableAdminId));
558     ++gNextAvailableAdminId;
559
560 exit:
561     if (err != CHIP_NO_ERROR && adminInfo != nullptr)
562         gAdminPairings.ReleaseAdminId(gNextAvailableAdminId);
563
564     return err;
565 }
566
567 AdminPairingTable & GetGlobalAdminPairingTable()
568 {
569     return gAdminPairings;
570 }