Line data Source code
1 : /**
2 : * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3 : * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4 : * CANN Open Software License Agreement Version 2.0 (the "License").
5 : * Please refer to the License for details. You may not use this file except in compliance with the License.
6 : * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7 : * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8 : * See LICENSE in the root of the software repository for the full text of the License.
9 : */
10 :
11 : #include "ccu_connection.h"
12 :
13 : #include <cstdlib>
14 : #include "hccp_ctx.h"
15 : #include "exception_util.h"
16 : #include "orion_adapter_rts.h"
17 : #include "internal_exception.h"
18 : #include "rdma_handle_manager.h"
19 :
20 : namespace Hccl {
21 :
22 269 : CcuConnection::CcuConnection(const IpAddress &locAddr, const IpAddress &rmtAddr,
23 269 : const CcuChannelInfo &channelInfo, const std::vector<CcuJetty *> &ccuJettys)
24 269 : : locAddr_(locAddr), rmtAddr_(rmtAddr), channelInfo_(channelInfo), ccuJettys_(ccuJettys)
25 : {
26 269 : }
27 :
28 1 : CcuTpConnection::CcuTpConnection(const IpAddress &locAddr, const IpAddress &rmtAddr,
29 1 : const CcuChannelInfo &channelInfo, const std::vector<CcuJetty *> &ccuJettys)
30 1 : : CcuConnection(locAddr, rmtAddr, channelInfo, ccuJettys)
31 : {
32 1 : tpProtocol = TpProtocol::TP;
33 1 : }
34 :
35 30 : CcuCtpConnection::CcuCtpConnection(const IpAddress &locAddr, const IpAddress &rmtAddr,
36 30 : const CcuChannelInfo &channelInfo, const std::vector<CcuJetty *> &ccuJettys)
37 30 : : CcuConnection(locAddr, rmtAddr, channelInfo, ccuJettys)
38 : {
39 30 : tpProtocol = TpProtocol::CTP;
40 30 : }
41 :
42 20 : HcclResult CcuConnection::Init()
43 : {
44 20 : TRY_CATCH_RETURN(
45 : devLogicId = HrtGetDevice();
46 : uint32_t devPhyId = HrtGetDevicePhyIdByIndex(devLogicId);
47 :
48 : auto &rdmaHandleMgr = RdmaHandleManager::GetInstance();
49 : rdmaHandle = rdmaHandleMgr.GetByIp(devPhyId, locAddr_);
50 : dieId = rdmaHandleMgr.GetDieAndFuncId(rdmaHandle).first;
51 : );
52 :
53 20 : CHK_RET(GetLocalCcuRmaBufferInfo());
54 :
55 20 : jettyNum = channelInfo_.jettyInfos.size();
56 20 : CHK_PRT_RET(jettyNum == 0,
57 : HCCL_ERROR("[CcuConnection][%s] failed, jetty num[0] is unexpected.", __func__),
58 : HcclResult::HCCL_E_PARA);
59 :
60 20 : GenerateLocalPsn();
61 20 : status = CcuConnStatus::INIT;
62 20 : innerStatus = InnerStatus::INIT;
63 20 : return HcclResult::HCCL_SUCCESS;
64 : }
65 :
66 38 : CcuConnStatus CcuConnection::GetStatus()
67 : {
68 38 : if (status == CcuConnStatus::CONNECTED
69 38 : || status == CcuConnStatus::CONN_INVALID) {
70 3 : return status;
71 : }
72 :
73 35 : if (StatusMachine() != HcclResult::HCCL_SUCCESS) {
74 2 : status = CcuConnStatus::CONN_INVALID;
75 2 : innerStatus = InnerStatus::CONN_INVALID;
76 : }
77 :
78 35 : return status;
79 : }
80 : // 获取本端内存,为部分远端不可写的tokenId
81 20 : HcclResult CcuConnection::GetLocalCcuRmaBufferInfo()
82 : {
83 20 : uint64_t ccuBufSize = 0; // 暂未使用
84 20 : CHK_RET(CcuDeviceManager::GetCcuResourceSpaceBufInfo(
85 : devLogicId, dieId, ccuBufAddr, ccuBufSize));
86 :
87 20 : uint64_t tokenId = 0;
88 20 : uint64_t tokenValue = 0;
89 20 : CHK_RET(CcuDeviceManager::GetCcuResourceSpaceTokenInfo(
90 : devLogicId, dieId, tokenId, tokenValue));
91 20 : ccuBufTokenId = static_cast<uint32_t>(tokenId);
92 20 : ccuBufTokenValue = static_cast<uint32_t>(tokenValue);
93 20 : return HcclResult::HCCL_SUCCESS;
94 : }
95 :
96 35 : HcclResult CcuConnection::StatusMachine()
97 : {
98 161 : TRY_CATCH_RETURN(
99 : if (status == CcuConnStatus::INIT) {
100 : UpdateInitStatus();
101 : return HcclResult::HCCL_SUCCESS;
102 : }
103 :
104 : if (innerStatus == InnerStatus::JETTY_IMPORTING) {
105 : UpdateExchangeStatus();
106 : return HcclResult::HCCL_SUCCESS;
107 : }
108 : );
109 :
110 0 : return HcclResult::HCCL_SUCCESS;
111 : }
112 :
113 29 : void CcuConnection::UpdateInitStatus()
114 : {
115 29 : switch (innerStatus) {
116 21 : case InnerStatus::INIT:
117 : case InnerStatus::JETTY_CREATING: {
118 21 : if (!CreateJetty()) {
119 10 : innerStatus = InnerStatus::JETTY_CREATING;
120 10 : break; // 状态不改变退出,下轮状态机进入继续执行
121 : }
122 :
123 10 : if (GetTpInfo()) { // 如果有缓存的tp信息,可以直接完成
124 1 : innerStatus = InnerStatus::EXCHANGEABLE;
125 1 : status = CcuConnStatus::EXCHANGEABLE;
126 1 : break;
127 : }
128 :
129 9 : innerStatus = InnerStatus::TP_INFO_GETTING; // 不退出继续调用下个异步接口
130 9 : break;
131 : }
132 8 : case InnerStatus::TP_INFO_GETTING: {
133 8 : if (!GetTpInfo()) {
134 0 : break; // 状态不改变退出,下轮状态机进入继续执行
135 : }
136 8 : innerStatus = InnerStatus::EXCHANGEABLE;
137 8 : status = CcuConnStatus::EXCHANGEABLE;
138 8 : break;
139 : }
140 0 : default:
141 0 : ThrowAbnormalStatus(std::string(__func__));
142 : }
143 28 : }
144 :
145 22 : bool CcuConnection::CreateJetty()
146 : {
147 22 : if (isJettyCreated) {
148 1 : return true;
149 : }
150 :
151 21 : isJettyCreated = true;
152 61 : for (size_t i = 0; i < jettyNum; i++) {
153 41 : auto ret = ccuJettys_[i]->CreateJetty();
154 41 : if (ret == HcclResult::HCCL_E_AGAIN) {
155 : // 不提供日志避免刷屏
156 10 : isJettyCreated = isJettyCreated && false;
157 10 : continue;
158 : }
159 :
160 31 : if (ret != HcclResult::HCCL_SUCCESS) {
161 3 : HCCL_ERROR("[CcuConnection][%s] failed, hccl result[%d]", __func__, ret);
162 2 : ThrowAbnormalStatus(std::string(__func__));
163 : }
164 : }
165 :
166 20 : return isJettyCreated;
167 : }
168 :
169 0 : inline uint32_t GetRandomNum()
170 : {
171 0 : uint32_t randNum = std::rand();
172 0 : return randNum;
173 : }
174 :
175 40 : void CcuConnection::GenerateLocalPsn()
176 : {
177 40 : jettyImportCfg.localPsn = GetRandomNum();
178 40 : }
179 :
180 19 : bool CcuConnection::GetTpInfo()
181 : {
182 19 : if (tpProtocol == TpProtocol::INVALID) { // 不感知tp建链,当前默认不支持
183 3 : HCCL_ERROR("[CcuConnection][%s] failed, tpProtocol[%s] is not expected.",
184 : __func__, tpProtocol.Describe().c_str());
185 2 : ThrowAbnormalStatus(std::string(__func__));
186 : }
187 :
188 18 : HcclResult ret = TpManager::GetInstance(devLogicId)
189 18 : .GetTpInfo({locAddr_, rmtAddr_, tpProtocol}, tpInfo);
190 18 : if (ret == HcclResult::HCCL_E_AGAIN) {
191 : // 此处可能刷屏,非必要勿加日志
192 9 : return false;
193 : }
194 :
195 9 : if (ret != HcclResult::HCCL_SUCCESS) {
196 0 : HCCL_ERROR("[CcuConnection][%s] failed, hccl result[%d]", __func__, ret);
197 0 : ThrowAbnormalStatus(std::string(__func__));
198 : }
199 :
200 9 : jettyImportCfg.localTpHandle = tpInfo.tpHandle;
201 9 : return true;
202 : }
203 :
204 9 : void CcuConnection::Serialize(std::vector<char> &dtoData)
205 : {
206 9 : if (status != CcuConnStatus::EXCHANGEABLE) {
207 3 : HCCL_ERROR("[CcuConnection][%s] failed, not init completed yet, "
208 : "status[%s].", __func__, status.Describe().c_str());
209 2 : ThrowAbnormalStatus(std::string(__func__));
210 : }
211 :
212 8 : BinaryStream dtoStream;
213 8 : dtoStream << ccuBufAddr;
214 8 : dtoStream << ccuBufTokenId;
215 8 : dtoStream << ccuBufTokenValue;
216 24 : HCCL_INFO("[CcuConnection][%s], ccuBufAddr[%llx]", __func__, ccuBufAddr);
217 :
218 8 : dtoStream << jettyNum;
219 24 : HCCL_INFO("[CcuConnection][%s], jettyNum[%u]", __func__, jettyNum);
220 24 : for (const auto &ccuJetty : ccuJettys_) {
221 16 : dtoStream << ccuJetty->GetCreateJettyParam().tokenValue;
222 16 : const auto &outParam = ccuJetty->GetJettyedOutParam();
223 16 : dtoStream << outParam.key;
224 16 : dtoStream << outParam.keySize;
225 : }
226 :
227 8 : if (tpProtocol != TpProtocol::INVALID) {
228 8 : dtoStream << jettyImportCfg.localTpHandle;
229 8 : dtoStream << jettyImportCfg.localPsn;
230 24 : HCCL_INFO("[CcuConnection][%s] tpProtocol[%s], localTpHandle[0x%llx], localPsn[%u].",
231 : __func__, tpProtocol.Describe().c_str(), jettyImportCfg.localTpHandle,
232 : jettyImportCfg.localPsn);
233 : }
234 :
235 8 : dtoData.clear();
236 8 : dtoStream.Dump(dtoData);
237 8 : }
238 :
239 8 : void CcuConnection::Deserialize(const std::vector<char> &dtoData)
240 : {
241 8 : if (status != CcuConnStatus::EXCHANGEABLE) {
242 3 : HCCL_ERROR("[CcuConnection][%s] failed, not init completed yet, "
243 : "status[%s].", __func__, status.Describe().c_str());
244 2 : ThrowAbnormalStatus(std::string(__func__));
245 : }
246 :
247 7 : vector<char> rmtDtoData = dtoData;
248 7 : BinaryStream dtoStream(rmtDtoData);
249 7 : dtoStream >> rmtCcuBufAddr;
250 7 : dtoStream >> rmtCcuBufTokenId;
251 7 : dtoStream >> rmtCcuBufTokenValue;
252 21 : HCCL_INFO("[CcuConnection][%s], rmtCcuBufAddr[%llx].", __func__, rmtCcuBufAddr);
253 :
254 7 : uint32_t remoteJettySize{0};
255 7 : dtoStream >> remoteJettySize;
256 :
257 7 : importJettyCtxs.clear();
258 7 : importJettyCtxs.resize(remoteJettySize);
259 21 : HCCL_INFO("[CcuConnection][%s], remoteJettySize[%u].", __func__, remoteJettySize);
260 :
261 21 : for (auto &importCtx : importJettyCtxs) {
262 14 : dtoStream >> importCtx.inParam.tokenValue;
263 14 : dtoStream >> importCtx.remoteQpKey; // 保存key数组
264 14 : importCtx.inParam.key = importCtx.remoteQpKey; // 保存指针用于接口调用
265 14 : dtoStream >> importCtx.inParam.keyLen;
266 : }
267 :
268 7 : if (tpProtocol != TpProtocol::INVALID) {
269 7 : dtoStream >> jettyImportCfg.remoteTpHandle;
270 7 : dtoStream >> jettyImportCfg.remotePsn;
271 :
272 21 : HCCL_INFO("[CcuConnection][%s] tpEnable, remoteTpHandle[0x%llx], remotePsn[%u].",
273 : __func__, jettyImportCfg.remoteTpHandle, jettyImportCfg.remotePsn);
274 : }
275 7 : }
276 :
277 11 : void CcuConnection::ImportJetty()
278 : {
279 11 : if (isJettyImported) {
280 3 : HCCL_INFO("[CcuConnection][%s] taJettys has been imported already.", __func__);
281 1 : return;
282 : }
283 :
284 10 : if (innerStatus != InnerStatus::EXCHANGEABLE) {
285 4 : ThrowAbnormalStatus(std::string(__func__));
286 : }
287 :
288 8 : if (jettyNum != importJettyCtxs.size()) {
289 3 : HCCL_ERROR("[CcuConnection][%s] failed to ImportJetty, "
290 : "jettyNum[%u] is not equal to importJettyCtxs.size[%u].",
291 : __func__, jettyNum, importJettyCtxs.size());
292 2 : ThrowAbnormalStatus(std::string(__func__));
293 : }
294 :
295 7 : ResetRequestCtxs();
296 19 : for (size_t i = 0; i < jettyNum; i++) {
297 13 : if (StartImportJettyRequest(i, reqHandles[i]) != HcclResult::HCCL_SUCCESS) {
298 2 : ThrowAbnormalStatus(std::string(__func__));
299 : }
300 : }
301 :
302 6 : innerStatus = InnerStatus::JETTY_IMPORTING;
303 : }
304 :
305 7 : void CcuConnection::ResetRequestCtxs()
306 : {
307 7 : reqHandles.clear();
308 7 : reqHandles.resize(jettyNum);
309 :
310 7 : reqDataBuffers.clear();
311 7 : reqDataBuffers.resize(jettyNum);
312 :
313 7 : remoteJettyHandlePtrs.clear();
314 7 : remoteJettyHandlePtrs.resize(jettyNum);
315 7 : }
316 :
317 12 : HcclResult CcuConnection::StartImportJettyRequest(uint32_t jettyIndex, RequestHandle &reqHandle)
318 : {
319 12 : if (tpProtocol == TpProtocol::INVALID) {
320 0 : ThrowAbnormalStatus(std::string(__func__));
321 : }
322 :
323 12 : auto &importCtx = importJettyCtxs[jettyIndex];
324 12 : auto &importCtxInParam = importCtx.inParam;
325 12 : importCtxInParam.jettyImportCfg = jettyImportCfg;
326 12 : importCtxInParam.jettyImportCfg.protocol = tpProtocol;
327 12 : TRY_CATCH_RETURN(
328 : reqHandle = RaUbTpImportJettyAsync(rdmaHandle, importCtxInParam, reqDataBuffers[jettyIndex],
329 : remoteJettyHandlePtrs[jettyIndex]);
330 : );
331 :
332 12 : return HcclResult::HCCL_SUCCESS;
333 : }
334 :
335 6 : bool CcuConnection::CheckRequestResults()
336 : {
337 6 : if (reqHandles.size() == 0) {
338 0 : return true;
339 : }
340 :
341 : // 检查所有下发异步请求是否完成
342 6 : vector<size_t> completedReqs;
343 18 : for (size_t i = 0; i < reqHandles.size(); i++) {
344 12 : ReqHandleResult result = HrtRaGetAsyncReqResult(reqHandles[i]);
345 12 : if (result == ReqHandleResult::NOT_COMPLETED) {
346 0 : continue;
347 : }
348 :
349 12 : if (result != ReqHandleResult::COMPLETED) {
350 0 : THROW<InternalException>(
351 0 : StringFormat("[CcuConnection][%s] failed, result[%s] is unexpected.",
352 0 : __func__, result.Describe().c_str()));
353 : }
354 :
355 : // 记录已完成的reqHandles
356 12 : completedReqs.push_back(i);
357 : }
358 :
359 : // 删除已完成的reqHandles,避免重复查询
360 18 : for (int i = completedReqs.size() - 1; i >= 0; --i) {
361 12 : reqHandles.erase(reqHandles.begin() + completedReqs[i]);
362 : }
363 :
364 : // 检查是否有剩余reqHandles
365 6 : return reqHandles.size() == 0;
366 6 : }
367 :
368 6 : void CcuConnection::UpdateExchangeStatus()
369 : {
370 : // 状态机保证为 InnerStatus::JETTY_IMPORTING
371 6 : if (!CheckRequestResults()) {
372 0 : return;
373 : }
374 :
375 18 : for (size_t i = 0; i < jettyNum; i++) {
376 12 : auto &outParam = importJettyCtxs[i].outParam;
377 12 : struct QpImportInfoT *infoPtr = reinterpret_cast<QpImportInfoT *>(reqDataBuffers[i].data());
378 12 : outParam.handle = reinterpret_cast<TargetJettyHandle>(remoteJettyHandlePtrs[i]);
379 12 : outParam.targetJettyVa = infoPtr->out.ub.tjettyHandle; // 该信息当前未使用
380 12 : outParam.tpn = infoPtr->out.ub.tpn;
381 : }
382 6 : isJettyImported = true;
383 :
384 6 : ConfigChannel();
385 5 : status = CcuConnStatus::CONNECTED;
386 5 : innerStatus = InnerStatus::CONNECTED;
387 : }
388 :
389 7 : void CcuConnection::ConfigChannel()
390 : {
391 7 : if (jettyNum != importJettyCtxs.size()) {
392 3 : HCCL_ERROR("[CcuConnection][%s] failed, jettyNum[%u] is not equal to "
393 : "importJettyCtxs.size[%u].", __func__, jettyNum, importJettyCtxs.size());
394 2 : ThrowAbnormalStatus(std::string(__func__));
395 : }
396 :
397 6 : ChannelCfg cfg{};
398 6 : cfg.channelId = channelInfo_.channelId;
399 6 : cfg.remoteEid = rmtAddr_.GetReverseEid();
400 18 : HCCL_INFO("[CcuComponent::ConfigChannel] cfg.remoteEid=%s", cfg.remoteEid.Describe().c_str());
401 6 : cfg.tpn = importJettyCtxs[0].outParam.tpn; // tp handle复用所以tpn一致
402 6 : cfg.remoteCcuVa = rmtCcuBufAddr;
403 6 : cfg.memTokenId = rmtCcuBufTokenId;
404 6 : cfg.memTokenValue = rmtCcuBufTokenValue;
405 :
406 18 : for (size_t i = 0; i < jettyNum; i++) {
407 12 : const auto &ccuJetty = ccuJettys_[i];
408 12 : const auto &inParam = ccuJetty->GetCreateJettyParam();
409 12 : const auto &outParam = ccuJetty->GetJettyedOutParam();
410 12 : const auto &jettyInfo = channelInfo_.jettyInfos[i];
411 12 : cfg.jettyCfgs.emplace_back(JettyCfg{
412 12 : jettyInfo.jettyCtxId,
413 12 : outParam.dbVa,
414 12 : outParam.dbTokenId,
415 12 : inParam.tokenValue}); // 安全问题,禁止打印token相关信息
416 : }
417 :
418 6 : if (CcuDeviceManager::ConfigChannel(devLogicId, dieId, cfg) != HcclResult::HCCL_SUCCESS) {
419 3 : HCCL_ERROR("[CcuConnection][%s] failed, devLogicId[%d], dieId[%u] channelId[%u].",
420 : __func__, devLogicId, dieId, cfg.channelId);
421 2 : ThrowAbnormalStatus(std::string(__func__));
422 : }
423 6 : }
424 :
425 269 : CcuConnection::~CcuConnection()
426 : {
427 269 : DECTOR_TRY_CATCH("CcuConnection", ReleaseConnRes());
428 269 : }
429 :
430 258 : HcclResult CcuConnection::ReleaseConnRes()
431 : {
432 312 : for (auto &item : importJettyCtxs) {
433 54 : if (item.outParam.handle != 0) {
434 0 : HrtRaUbUnimportJetty(rdmaHandle, item.outParam.handle);
435 0 : item.outParam.handle = 0;
436 : }
437 : }
438 :
439 258 : if (tpInfo.tpHandle != 0) { // tp handle 复用,只释放一次
440 1 : (void)TpManager::GetInstance(devLogicId)
441 1 : .ReleaseTpInfo({locAddr_, rmtAddr_, tpProtocol}, tpInfo);
442 1 : tpInfo.tpHandle = 0;
443 : }
444 :
445 : // CcuJetty 生命周期跟随通信域CcuJettyMgr
446 : // 不需要connection主动销毁
447 258 : return HcclResult::HCCL_SUCCESS;
448 : }
449 :
450 10 : void CcuConnection::ThrowAbnormalStatus(const string &funcName)
451 : {
452 : auto errMsg = StringFormat("[CcuConnection][%s] failed, [%s].",
453 10 : funcName.c_str(), Describe().c_str());
454 10 : status = CcuConnStatus::CONN_INVALID;
455 10 : innerStatus = InnerStatus::CONN_INVALID;
456 10 : THROW<InternalException>(errMsg);
457 10 : }
458 :
459 10 : std::string CcuConnection::Describe()
460 : {
461 : return StringFormat("[CcuConnection[locAddr=%s, rmtAddr=%s, protocol=%s, "
462 : "status=%s, innerStatus=%s, [dieId=%u, channelId=%u, jettyNum=%u]]]",
463 40 : locAddr_.Describe().c_str(), rmtAddr_.Describe().c_str(), tpProtocol.Describe().c_str(),
464 20 : status.Describe().c_str(), innerStatus.Describe().c_str(), dieId, channelInfo_.channelId,
465 60 : jettyNum);
466 : }
467 :
468 9 : uint32_t CcuConnection::GetDieId() const
469 : {
470 9 : return dieId;
471 : }
472 :
473 872 : uint32_t CcuConnection::GetChannelId() const
474 : {
475 872 : return channelInfo_.channelId;
476 : }
477 :
478 9 : int32_t CcuConnection::GetDevLogicId() const
479 : {
480 9 : return devLogicId;
481 : }
482 :
483 19 : std::vector<ConnJettyInfo> CcuConnection::GetDeleteJettyInfo()
484 : {
485 19 : std::vector<ConnJettyInfo> connDeleteJettyInfos;
486 19 : ConnJettyInfo jettyInfo;
487 43 : for (auto &ccuJetty : ccuJettys_) {
488 24 : if (ccuJetty != nullptr) {
489 24 : ccuJetty->GetJettyInfo(jettyInfo);
490 24 : jettyInfo.rdmaHandle = rdmaHandle;
491 24 : connDeleteJettyInfos.push_back(jettyInfo);
492 : }
493 : }
494 19 : return connDeleteJettyInfos;
495 0 : }
496 :
497 19 : std::vector<ConnJettyInfo> CcuConnection::GetUnimportJettyInfo()
498 : {
499 19 : std::vector<ConnJettyInfo> connUnimportJettyInfos;
500 19 : ConnJettyInfo jettyInfo;
501 43 : for (auto &item : importJettyCtxs) {
502 24 : if (item.outParam.handle != 0) {
503 14 : jettyInfo.remoteJetty = item.outParam.handle;
504 14 : jettyInfo.rdmaHandle = rdmaHandle;
505 14 : item.outParam.handle = 0;
506 14 : connUnimportJettyInfos.push_back(jettyInfo);
507 : }
508 : }
509 19 : return connUnimportJettyInfos;
510 0 : }
511 :
512 20 : void CcuConnection::Clean()
513 : {
514 20 : status = CcuConnStatus::INIT;
515 20 : innerStatus = InnerStatus::INIT;
516 20 : isJettyCreated = false;
517 20 : isJettyImported = false;
518 20 : ReleaseConnRes();
519 20 : GenerateLocalPsn();
520 :
521 : // 销毁jetty要在ReleaseConnRes之后
522 46 : for (auto &ccuJetty : ccuJettys_) {
523 26 : ccuJetty->Clean();
524 : }
525 20 : }
526 :
527 : } // namespace Hccl
|