1 /*
2 * Copyright (c) 2020-2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <securec.h>
17 #include <pthread.h>
18 #include <ohos_init.h>
19 #include "gtest/gtest.h"
20 #include "utils/SamgrTestBase.h"
21
22 using namespace testing::ext;
23
24 const int SERVICE_NUM = 4;
25
26 const int INDEX0 = 0;
27 const int INDEX1 = 1;
28 const int INDEX2 = 2;
29 const int INDEX3 = 3;
30
31 static char *g_serviceNameArray[] = {
32 (char*)"SingleTS01",
33 (char*)"SingleTS02",
34 (char*)"SingleTS03",
35 (char*)"SingleTS04"
36 };
37
38 static const char *GetName(Service *service);
39 static BOOL Initialize(Service *service, Identity identity);
40 static BOOL MessageHandle(Service *service, Request *msg);
41 static TaskConfig GetTaskConfig(Service *service);
42
43 static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
44 static Vector g_nodeVector;
45 struct Node {
46 int id;
47 char *name;
48 };
49
50 struct DemoApi {
51 INHERIT_IUNKNOWN;
52 bool(*FeatureApi001)(IUnknown *iUnknown, const char *para1);
53 int32 (*SendRequestProxyF)(const Identity *identity, const Request *request, Handler handler);
54 };
55
56 struct DemoFeature {
57 INHERIT_FEATURE;
58 INHERIT_IUNKNOWNENTRY(DemoApi);
59 Identity identity;
60 int featureCalledCount;
61 };
62
63 struct DefaultFeatureApi {
64 INHERIT_IUNKNOWN;
65 BOOL(*DefaultApi001)
66 (IUnknown *iUnknown, char *para1);
67 int32 (*SendRequestProxyDF)(const Identity *identity, const Request *request, Handler handler);
68 };
69
70 struct DemoService {
71 INHERIT_SERVICE;
72 INHERIT_IUNKNOWNENTRY(DefaultFeatureApi);
73 Identity identity;
74 int serviceCalledCount;
75 };
76
DefaultApi001(IUnknown * iUnknown,char * para1)77 static BOOL DefaultApi001(IUnknown *iUnknown, char *para1)
78 {
79 (void)iUnknown;
80 return TRUE;
81 }
SendRequestProxyDF(const Identity * identity,const Request * request,Handler handler)82 static int32 SendRequestProxyDF(const Identity *identity, const Request *request, Handler handler)
83 {
84 return SAMGR_SendRequest(identity, request, handler);
85 }
86
FeatureApi001(IUnknown * iUnknown,const char * para1)87 static bool FeatureApi001(IUnknown *iUnknown, const char *para1)
88 {
89 (void)iUnknown;
90 return TRUE;
91 }
SendRequestProxyF(const Identity * identity,const Request * request,Handler handler)92 static int32 SendRequestProxyF(const Identity *identity, const Request *request, Handler handler)
93 {
94 return SAMGR_SendRequest(identity, request, handler);
95 }
96
97 static DemoService g_service[] = {
98 {
99 .GetName = GetName,
100 .Initialize = Initialize,
101 .MessageHandle = MessageHandle,
102 .GetTaskConfig = GetTaskConfig,
103 .ver = 0x20,
104 .ref = 1,
105 .iUnknown = {
106 DEFAULT_IUNKNOWN_IMPL,
107 .DefaultApi001 = DefaultApi001,
108 .SendRequestProxyDF = SendRequestProxyDF,
109 },
110 .identity = {-1, -1, nullptr},
111 .serviceCalledCount = 0,
112 },
113 {
114 .GetName = GetName,
115 .Initialize = Initialize,
116 .MessageHandle = MessageHandle,
117 .GetTaskConfig = GetTaskConfig,
118 .ver = 0x20,
119 .ref = 1,
120 .iUnknown = {
121 DEFAULT_IUNKNOWN_IMPL,
122 .DefaultApi001 = DefaultApi001,
123 .SendRequestProxyDF = SendRequestProxyDF,
124 },
125 .identity = {-1, -1, nullptr},
126 .serviceCalledCount = 0,
127 },
128 {
129 .GetName = GetName,
130 .Initialize = Initialize,
131 .MessageHandle = MessageHandle,
132 .GetTaskConfig = GetTaskConfig,
133 .ver = 0x20,
134 .ref = 1,
135 .iUnknown = {
136 DEFAULT_IUNKNOWN_IMPL,
137 .DefaultApi001 = DefaultApi001,
138 .SendRequestProxyDF = SendRequestProxyDF,
139 },
140 .identity = {-1, -1, nullptr},
141 .serviceCalledCount = 0,
142 },
143 {
144 .GetName = GetName,
145 .Initialize = Initialize,
146 .MessageHandle = MessageHandle,
147 .GetTaskConfig = GetTaskConfig,
148 .ver = 0x20,
149 .ref = 1,
150 .iUnknown = {
151 DEFAULT_IUNKNOWN_IMPL,
152 .DefaultApi001 = DefaultApi001,
153 .SendRequestProxyDF = SendRequestProxyDF,
154 },
155 .identity = {-1, -1, nullptr},
156 .serviceCalledCount = 0,
157 }
158 };
159
GetName(Service * service)160 static const char *GetName(Service *service)
161 {
162 if (service == (Service *)&g_service[INDEX0]) {
163 return g_serviceNameArray[INDEX0];
164 } else if (service == (Service *)&g_service[INDEX1]) {
165 return g_serviceNameArray[INDEX1];
166 } else if (service == (Service *)&g_service[INDEX2]) {
167 return g_serviceNameArray[INDEX2];
168 } else {
169 return g_serviceNameArray[INDEX3];
170 }
171 }
172
173 static int g_initlizationOrder = 0;
Initialize(Service * service,Identity identity)174 static BOOL Initialize(Service *service, Identity identity)
175 {
176 DemoService *demoService = (DemoService *)service;
177 demoService->identity = identity;
178
179 pthread_mutex_lock(&g_mutex);
180 g_initlizationOrder++;
181 pthread_mutex_unlock(&g_mutex);
182
183 Node *node = (Node *)malloc(sizeof(Node));
184 if (node == nullptr) {
185 ADD_FAILURE();
186 }
187 node->id = g_initlizationOrder;
188 node->name = (char *)service->GetName(service);
189 VECTOR_Add(&g_nodeVector, node);
190
191 printf("[hcpptest]serviceName %s priority %d \n", service->GetName(service),
192 service->GetTaskConfig(service).priority);
193
194 return TRUE;
195 }
196
MessageHandle(Service * service,Request * msg)197 static BOOL MessageHandle(Service *service, Request *msg)
198 {
199 DemoService *demoService = (DemoService *)service;
200 demoService->serviceCalledCount++;
201 return TRUE;
202 }
203
GetTaskConfig(Service * service)204 static TaskConfig GetTaskConfig(Service *service)
205 {
206 // stackSize: valid stackSize is [1600, 342000), the L0 RAM size is 342000, if stackSize = 800, system will crash
207 // queueSize: [0, system upper limit), 0: will not create taskpool, the max value depends on RAM size
208 // priority: PRI_ABOVE_NORMAL PRI_NORMAL PRI_BELOW_NORMAL PRI_LOW
209
210 TaskConfig config = {LEVEL_HIGH, PRI_NORMAL, 1600, 2, SINGLE_TASK};
211 if (service == (Service *)&g_service[INDEX0]) {
212 config.priority = PRI_LOW + 1;
213 } else if (service == (Service *)&g_service[INDEX1]) {
214 config.priority = PRI_BELOW_NORMAL;
215 } else if (service == (Service *)&g_service[INDEX2]) {
216 config.priority = PRI_NORMAL;
217 } else {
218 config.priority = PRI_ABOVE_NORMAL;
219 }
220 return config;
221 }
222
FEATURE_GetName(Feature * feature)223 static const char *FEATURE_GetName(Feature *feature)
224 {
225 (void)feature;
226 return "featureName501";
227 }
228
FEATURE_OnInitialize(Feature * feature,Service * parent,Identity identity)229 static void FEATURE_OnInitialize(Feature *feature, Service *parent, Identity identity)
230 {
231 DemoFeature *demoFeature = (DemoFeature *)feature;
232 demoFeature->identity = identity;
233 }
234
FEATURE_OnStop(Feature * feature,Identity identity)235 static void FEATURE_OnStop(Feature *feature, Identity identity)
236 {
237 (void)feature;
238 (void)identity;
239 }
240
FEATURE_OnMessage(Feature * feature,Request * request)241 static BOOL FEATURE_OnMessage(Feature *feature, Request *request)
242 {
243 DemoFeature *demoFeature = (DemoFeature *)feature;
244 demoFeature->featureCalledCount++;
245
246 return TRUE;
247 }
248
249 static DemoFeature g_feature = {
250 .GetName = FEATURE_GetName,
251 .OnInitialize = FEATURE_OnInitialize,
252 .OnStop = FEATURE_OnStop,
253 .OnMessage = FEATURE_OnMessage,
254 .ver = 0x20,
255 .ref = 1,
256 .iUnknown = {
257 DEFAULT_IUNKNOWN_IMPL,
258 .FeatureApi001 = FeatureApi001,
259 .SendRequestProxyF = SendRequestProxyF,
260 },
261 .identity = {-1, -1, nullptr},
262 .featureCalledCount = 0,
263 };
264
GServiceInit(void)265 static void GServiceInit(void)
266 {
267 for (int i = 0; i < SERVICE_NUM; i++) {
268 BOOL result = SAMGR_GetInstance()->RegisterService((Service *)&g_service[i]);
269 if (result == FALSE) {
270 printf("[hcpptest]E RegisterService failed, occurs: %d\n", i);
271 }
272 }
273 }
274 SYS_SERVICE_INIT(GServiceInit);
275
GFeatureInit(void)276 static void GFeatureInit(void)
277 {
278 for (int i = 0; i < SERVICE_NUM; i++) {
279 BOOL result1 = SAMGR_GetInstance()->RegisterDefaultFeatureApi(g_service[i].GetName((Service *)&g_service[i]),
280 GET_IUNKNOWN(g_service[i]));
281 BOOL result2 = SAMGR_GetInstance()->RegisterFeature(g_service[i].GetName((Service *)&g_service[i]),
282 (Feature *)&g_feature);
283 BOOL result3 = SAMGR_GetInstance()->RegisterFeatureApi(g_service[i].GetName((Service *)&g_service[i]),
284 "featureName501", GET_IUNKNOWN(g_feature));
285 if (result1 == FALSE || result2 == FALSE || result3 == FALSE) {
286 printf("[hcpptest]E failed to register feature or api.\n");
287 }
288 }
289 }
290 SYS_FEATURE_INIT(GFeatureInit);
291
GetIUnknown(const char * serviceName,const char * featureName)292 static DemoApi *GetIUnknown(const char *serviceName, const char *featureName)
293 {
294 DemoApi *demoApi = nullptr;
295 IUnknown *iUnknown = SAMGR_GetInstance()->GetFeatureApi(serviceName, featureName);
296 if (iUnknown == nullptr) {
297 return nullptr;
298 }
299 int result = iUnknown->QueryInterface(iUnknown, 0x20, (void **)&demoApi);
300 if (result == 0 && demoApi != nullptr) {
301 return demoApi;
302 } else {
303 return nullptr;
304 }
305 }
306
GetDefaultIUnknown(const char * serviceName)307 static DefaultFeatureApi *GetDefaultIUnknown(const char *serviceName)
308 {
309 DefaultFeatureApi *defaultApi = nullptr;
310 IUnknown *iUnknown = SAMGR_GetInstance()->GetDefaultFeatureApi(serviceName);
311 if (iUnknown == nullptr) {
312 return nullptr;
313 }
314 int result = iUnknown->QueryInterface(iUnknown, 0x20, (void **)&defaultApi);
315 if (result == 0 && defaultApi != nullptr) {
316 return defaultApi;
317 } else {
318 return nullptr;
319 }
320 }
321
322 class TaskpoolSingleTaskTest : public testing::Test {
323 protected:
324 // SetUpTestCase: Testsuit setup, run before 1st testcase
SetUpTestCase(void)325 static void SetUpTestCase(void)
326 {
327 printf("[hcpptest]SetUpTestCase ! \n");
328 SystemInitProxy();
329 usleep(OPER_INTERVAL * MS2US);
330 }
331 // TearDownTestCase: Testsuit teardown, run after last testcase
TearDownTestCase(void)332 static void TearDownTestCase(void)
333 {
334 }
335 // Testcase setup
SetUp()336 virtual void SetUp()
337 {
338 }
339 // Testcase teardown
TearDown()340 virtual void TearDown()
341 {
342 }
343 };
344
345 /**
346 * @tc.number : DMSLite_SAMGR_Taskpool_SingleTask_0010
347 * @tc.name : Service with PRI_LOW priority function is ok
348 * @tc.desc : [C- SOFTWARE -0200]
349 */
350 HWTEST_F(TaskpoolSingleTaskTest, testSingleTask0010, Function | MediumTest | Level2)
351 {
352 // test featureApi function
353 DemoApi *demoApi = GetIUnknown("SingleTS01", "featureName501");
354 if (demoApi == nullptr) {
355 ADD_FAILURE();
356 }
357 char *para = (char*)"xxxx";
358 bool result = demoApi->FeatureApi001((IUnknown *)demoApi, para);
359 ASSERT_EQ(result, TRUE);
360
361 g_feature.featureCalledCount = 0;
362 Request request = {.msgId = 0,
363 .len = 0,
364 .data = nullptr,
365 .msgValue = 0};
366 char *body = (char*)"I wanna async call good result!";
367 request.len = (uint32_t)(strlen(body) + 1);
368 request.data = malloc(request.len);
369 if (request.data == nullptr) {
370 ADD_FAILURE();
371 }
372 errno_t error = strcpy_s((char *)request.data, request.len, body);
373 if (error != EOK) {
374 ADD_FAILURE();
375 }
376 int32 result2 = demoApi->SendRequestProxyF(&(g_feature.identity), &request, nullptr);
377 ASSERT_EQ(result2 == 0, TRUE);
378 usleep(OPER_INTERVAL * MS2US);
379 ASSERT_EQ(g_feature.featureCalledCount == 1, TRUE);
380
381 // test defaultFeatureApi function
382 DefaultFeatureApi *defaultApi = GetDefaultIUnknown("SingleTS01");
383 if (defaultApi == nullptr) {
384 ADD_FAILURE();
385 }
386 result = defaultApi->DefaultApi001((IUnknown *)defaultApi, (char*)"yyyy");
387 ASSERT_EQ(result, TRUE);
388
389 Request request2 = {.msgId = 0, .len = 0, .data = nullptr, .msgValue = 0};
390 char *body2 = (char*)"I want to call defaultFeature!";
391 request2.len = (uint32_t)(strlen(body2) + 1);
392 request2.data = malloc(request2.len);
393 if (request2.data == nullptr) {
394 ADD_FAILURE();
395 }
396 error = strcpy_s((char *)request2.data, request2.len, body2);
397 if (error != EOK) {
398 ADD_FAILURE();
399 }
400 result2 = defaultApi->SendRequestProxyDF(&(g_service[0].identity), &request2, nullptr);
401 ASSERT_EQ(result2 == 0, TRUE);
402 usleep(OPER_INTERVAL * MS2US);
403 ASSERT_EQ(g_service[0].serviceCalledCount == 1, TRUE);
404 };
405
406 /**
407 * @tc.number : DMSLite_SAMGR_Taskpool_SingleTask_0020
408 * @tc.name : Service with PRI_BELOW_NORMAL priority function is ok
409 * @tc.desc : [C- SOFTWARE -0200]
410 */
411 HWTEST_F(TaskpoolSingleTaskTest, testSingleTask0020, Function | MediumTest | Level2)
412 {
413 DemoApi *demoApi = GetIUnknown("SingleTS02", "featureName501");
414 if (demoApi == nullptr) {
415 ADD_FAILURE();
416 }
417 bool result = demoApi->FeatureApi001((IUnknown *)demoApi, (char*)"xxxx");
418 ASSERT_EQ(result, TRUE);
419
420 g_feature.featureCalledCount = 0;
421 Request request = {.msgId = 0, .len = 0, .data = nullptr, .msgValue = 0};
422 char *body = (char*)"I wanna async call good result!";
423 request.len = (uint32_t)(strlen(body) + 1);
424 request.data = malloc(request.len);
425 if (request.data == nullptr) {
426 ADD_FAILURE();
427 }
428 errno_t error = strcpy_s((char *)request.data, request.len, body);
429 if (error != EOK) {
430 ADD_FAILURE();
431 }
432 int32 result2 = demoApi->SendRequestProxyF(&(g_feature.identity), &request, nullptr);
433 ASSERT_EQ(result2 == 0, TRUE);
434 usleep(OPER_INTERVAL * MS2US);
435 ASSERT_EQ(g_feature.featureCalledCount == 1, TRUE);
436
437 DefaultFeatureApi *defaultApi = GetDefaultIUnknown("SingleTS02");
438 if (defaultApi == nullptr) {
439 ADD_FAILURE();
440 }
441 result = defaultApi->DefaultApi001((IUnknown *)defaultApi, (char*)"yyyy");
442 ASSERT_EQ(result, TRUE);
443
444 Request request2 = {.msgId = 0, .len = 0, .data = nullptr, .msgValue = 0};
445 char *body2 = (char*)"I want to call defaultFeature!";
446 request2.len = (uint32_t)(strlen(body2) + 1);
447 request2.data = malloc(request2.len);
448 if (request2.data == nullptr) {
449 ADD_FAILURE();
450 }
451 error = strcpy_s((char *)request2.data, request2.len, body2);
452 if (error != EOK) {
453 ADD_FAILURE();
454 }
455 result2 = defaultApi->SendRequestProxyDF(&(g_service[1].identity), &request2, nullptr);
456 ASSERT_EQ(result2 == 0, TRUE);
457 usleep(OPER_INTERVAL * MS2US);
458 ASSERT_EQ(g_service[1].serviceCalledCount == 1, TRUE);
459 };
460
461 /**
462 * @tc.number : DMSLite_SAMGR_Taskpool_SingleTask_0030
463 * @tc.name : Service with PRI_NORMAL priority function is ok
464 * @tc.desc : [C- SOFTWARE -0200]
465 */
466 HWTEST_F(TaskpoolSingleTaskTest, testSingleTask0030, Function | MediumTest | Level2)
467 {
468 DemoApi *demoApi = GetIUnknown("SingleTS03", "featureName501");
469 if (demoApi == nullptr) {
470 ADD_FAILURE();
471 }
472 bool result = demoApi->FeatureApi001((IUnknown *)demoApi, (char*)"xxxx");
473 ASSERT_EQ(result, TRUE);
474
475 g_feature.featureCalledCount = 0;
476 Request request = {.msgId = 0, .len = 0, .data = nullptr, .msgValue = 0};
477 char *body = (char*)"I wanna async call good result!";
478 request.len = (uint32_t)(strlen(body) + 1);
479 request.data = malloc(request.len);
480 if (request.data == nullptr) {
481 ADD_FAILURE();
482 }
483 errno_t error = strcpy_s((char *)request.data, request.len, body);
484 if (error != EOK) {
485 ADD_FAILURE();
486 }
487 int32 result2 = demoApi->SendRequestProxyF(&(g_feature.identity), &request, nullptr);
488 ASSERT_EQ(result2 == 0, TRUE);
489 usleep(OPER_INTERVAL * MS2US);
490 ASSERT_EQ(g_feature.featureCalledCount == 1, TRUE);
491
492 DefaultFeatureApi *defaultApi = GetDefaultIUnknown("SingleTS03");
493 if (defaultApi == nullptr) {
494 ADD_FAILURE();
495 }
496 result = defaultApi->DefaultApi001((IUnknown *)defaultApi, (char*)"yyyy");
497 ASSERT_EQ(result, TRUE);
498
499 Request request2 = {.msgId = 0, .len = 0, .data = nullptr, .msgValue = 0};
500 char *body2 = (char*)"I want to call defaultFeature!";
501 request2.len = (uint32_t)(strlen(body2) + 1);
502 request2.data = malloc(request2.len);
503 if (request2.data == nullptr) {
504 ADD_FAILURE();
505 }
506 error = strcpy_s((char *)request2.data, request2.len, body2);
507 if (error != EOK) {
508 ADD_FAILURE();
509 }
510 result2 = defaultApi->SendRequestProxyDF(&(g_service[INDEX2].identity), &request2, nullptr);
511 ASSERT_EQ(result2 == 0, TRUE);
512 usleep(OPER_INTERVAL * MS2US);
513 ASSERT_EQ(g_service[INDEX2].serviceCalledCount == 1, TRUE);
514 };
515
516 /**
517 * @tc.number : DMSLite_SAMGR_Taskpool_SingleTask_0040
518 * @tc.name : Service with PRI_ABOVE_NORMAL priority function is ok
519 * @tc.desc : [C- SOFTWARE -0200]
520 */
521 HWTEST_F(TaskpoolSingleTaskTest, testSingleTask0040, Function | MediumTest | Level2)
522 {
523 DemoApi *demoApi = GetIUnknown("SingleTS04", "featureName501");
524 if (demoApi == nullptr) {
525 ADD_FAILURE();
526 }
527 bool result = demoApi->FeatureApi001((IUnknown *)demoApi, (char*)"xxxx");
528 ASSERT_EQ(result, TRUE);
529
530 g_feature.featureCalledCount = 0;
531 Request request = {.msgId = 0, .len = 0, .data = nullptr, .msgValue = 0};
532 char *body = (char*)"I wanna async call good result!";
533 request.len = (uint32_t)(strlen(body) + 1);
534 request.data = malloc(request.len);
535 if (request.data == nullptr) {
536 ADD_FAILURE();
537 }
538 errno_t error = strcpy_s((char *)request.data, request.len, body);
539 if (error != EOK) {
540 ADD_FAILURE();
541 }
542 int32 result2 = demoApi->SendRequestProxyF(&(g_feature.identity), &request, nullptr);
543 ASSERT_EQ(result2 == 0, TRUE);
544 usleep(OPER_INTERVAL * MS2US);
545 ASSERT_EQ(g_feature.featureCalledCount == 1, TRUE);
546
547 DefaultFeatureApi *defaultApi = GetDefaultIUnknown("SingleTS04");
548 if (defaultApi == nullptr) {
549 ADD_FAILURE();
550 }
551 result = defaultApi->DefaultApi001((IUnknown *)defaultApi, (char*)"yyyy");
552 ASSERT_EQ(result, TRUE);
553
554 Request request2 = {.msgId = 0, .len = 0, .data = nullptr, .msgValue = 0};
555 char *body2 = (char*)"I want to call defaultFeature!";
556 request2.len = (uint32_t)(strlen(body2) + 1);
557 request2.data = malloc(request2.len);
558 if (request2.data == nullptr) {
559 ADD_FAILURE();
560 }
561 error = strcpy_s((char *)request2.data, request2.len, body2);
562 if (error != EOK) {
563 ADD_FAILURE();
564 }
565 result2 = defaultApi->SendRequestProxyDF(&(g_service[INDEX3].identity), &request2, nullptr);
566 ASSERT_EQ(result2 == 0, TRUE);
567 usleep(OPER_INTERVAL * MS2US);
568 ASSERT_EQ(g_service[INDEX3].serviceCalledCount == 1, TRUE);
569 };
570
571 /**
572 * @tc.number : DMSLite_SAMGR_Taskpool_SingleTask_0050
573 * @tc.name : Service initialization order depends on service priority
574 * @tc.desc : [C- SOFTWARE -0200]
575 */
576 #ifndef __LINUX__
577 HWTEST_F(TaskpoolSingleTaskTest, testSingleTask0050, Function | MediumTest | Level2)
578 {
579 for (int i = 0; i < VECTOR_Num(&g_nodeVector); i++) {
580 Node *node = (Node *)VECTOR_At(&g_nodeVector, i);
581 if (node == nullptr) {
582 continue;
583 }
584 printf("[hcpptest]vector: service %s, serviceArray: %s \n", node->name,
585 g_serviceNameArray[SERVICE_NUM - (i + 1)]);
586 ASSERT_EQ(node->id, i + 1);
587 ASSERT_EQ(strcmp(node->name, g_serviceNameArray[SERVICE_NUM - (i + 1)]), 0);
588 }
589 };
590 #endif
591