libosdp-sys 3.2.1

Sys crate for https://github.com/goToMain/libosdp
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
/*
 * Copyright (c) 2025-2026 Siddharth Chandrasekaran <sidcha.dev@gmail.com>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <fcntl.h>

#include <osdp.h>
#include "test.h"

/* PD enabled/disabled states */
#define PD_STATE_DISABLED  false
#define PD_STATE_ENABLED   true

/* Test context for hot-plug tests */
struct test_hotplug_ctx {
	osdp_t *cp_ctx;
	osdp_t *pd_ctx;
	int cp_runner;
	int pd_runner;

	/* Command tracking */
	bool cmd_seen;
	int last_cmd_id;

	/* Event tracking */
	bool event_seen;
	int last_event_type;
};

static struct test_hotplug_ctx g_test_ctx = {0};

int test_hotplug_event_callback(void *arg, int pd, struct osdp_event *ev)
{
	ARG_UNUSED(pd);
	struct test_hotplug_ctx *ctx = arg;

	ctx->event_seen = true;
	ctx->last_event_type = ev->type;
	return 0;
}

int test_hotplug_command_callback(void *arg, struct osdp_cmd *cmd)
{
	struct test_hotplug_ctx *ctx = arg;

	ctx->cmd_seen = true;
	ctx->last_cmd_id = cmd->id;
	return 0;
}

static int setup_test_environment(struct test *t)
{
	printf(SUB_1 "setting up OSDP devices\n");

	if (test_setup_devices(t, &g_test_ctx.cp_ctx, &g_test_ctx.pd_ctx)) {
		printf(SUB_1 "Failed to setup devices!\n");
		return -1;
	}

	osdp_cp_set_event_callback(g_test_ctx.cp_ctx, test_hotplug_event_callback, &g_test_ctx);
	osdp_pd_set_command_callback(g_test_ctx.pd_ctx, test_hotplug_command_callback, &g_test_ctx);

	printf(SUB_1 "starting async runners\n");

	g_test_ctx.cp_runner = async_runner_start(g_test_ctx.cp_ctx, osdp_cp_refresh);
	g_test_ctx.pd_runner = async_runner_start(g_test_ctx.pd_ctx, osdp_pd_refresh);

	if (g_test_ctx.cp_runner < 0 || g_test_ctx.pd_runner < 0) {
		printf(SUB_1 "Failed to created CP/PD runners\n");
		return -1;
	}

	/* Wait for devices to come online */
	int rc = 0;
	uint8_t status = 0;
	while (1) {
		if (rc > 10) {
			printf(SUB_1 "PD failed to come online\n");
			return -1;
		}
		osdp_get_status_mask(g_test_ctx.cp_ctx, &status);
		if (status & 1)
			break;
		usleep(1000 * 1000);
		rc++;
	}

	return 0;
}

static void teardown_test_environment()
{
	printf(SUB_1 "tearing down test environment\n");

	async_runner_stop(g_test_ctx.cp_runner);
	async_runner_stop(g_test_ctx.pd_runner);

	osdp_cp_teardown(g_test_ctx.cp_ctx);
	osdp_pd_teardown(g_test_ctx.pd_ctx);

	memset(&g_test_ctx, 0, sizeof(g_test_ctx));
}

static void reset_test_state()
{
	g_test_ctx.cmd_seen = false;
	g_test_ctx.last_cmd_id = 0;
	g_test_ctx.event_seen = false;
	g_test_ctx.last_event_type = 0;
}

static bool wait_for_command(int expected_cmd_id, int timeout_sec)
{
	int rc = 0;
	while (rc < timeout_sec) {
		if (g_test_ctx.cmd_seen && g_test_ctx.last_cmd_id == expected_cmd_id) {
			return true;
		}
		usleep(1000 * 1000);
		rc++;
	}
	return false;
}

static bool wait_for_pd_state(bool expected_state, int timeout_sec)
{
	int rc = 0;
	while (rc < timeout_sec) {
		bool current_state = osdp_cp_is_pd_enabled(g_test_ctx.cp_ctx, 0);
		if (current_state == expected_state) {
			return true;
		}
		usleep(100 * 1000);  /* Check every 100ms */
		rc++;
	}
	return false;
}

static bool wait_for_pd_online(int timeout_sec)
{
	int rc = 0;
	while (rc < timeout_sec) {
		/* Check if PD is both enabled and online */
		bool enabled = osdp_cp_is_pd_enabled(g_test_ctx.cp_ctx, 0);
		uint8_t status_mask = 0;
		osdp_get_status_mask(g_test_ctx.cp_ctx, &status_mask);
		bool online = (status_mask & 0x01) != 0;

		if (enabled && online) {
			return true;
		}
		usleep(100 * 1000);  /* Check every 100ms */
		rc++;
	}
	return false;
}

static bool test_pd_disable_enable_basic()
{
	printf(SUB_2 "testing basic PD disable/enable\n");
	reset_test_state();

	/* Test initial state - should be enabled */
	bool enabled = osdp_cp_is_pd_enabled(g_test_ctx.cp_ctx, 0);
	if (enabled != PD_STATE_ENABLED) {
		printf(SUB_2 "PD should be enabled initially, got %s\n", enabled ? "true" : "false");
		return false;
	}

	/* Test status mask - note PD might still be coming online */
	uint8_t status_mask = 0;
	osdp_get_status_mask(g_test_ctx.cp_ctx, &status_mask);
	printf(SUB_2 "Initial status mask: 0x%02X (PD may still be initializing)\n", status_mask);

	/* Disable PD */
	if (osdp_cp_disable_pd(g_test_ctx.cp_ctx, 0) != 0) {
		printf(SUB_2 "Failed to disable PD\n");
		return false;
	}

	/* Wait for PD to actually be disabled */
	if (!wait_for_pd_state(PD_STATE_DISABLED, 3)) {
		printf(SUB_2 "PD didn't reach disabled state within timeout\n");
		return false;
	}

	/* Status mask should show PD offline */
	osdp_get_status_mask(g_test_ctx.cp_ctx, &status_mask);
	if (status_mask & 0x01) {
		printf(SUB_2 "Disabled PD should appear offline in status mask\n");
		return false;
	}

	/* Re-enable PD */
	if (osdp_cp_enable_pd(g_test_ctx.cp_ctx, 0) != 0) {
		printf(SUB_2 "Failed to enable PD\n");
		return false;
	}

	/* Wait for PD to actually be enabled */
	if (!wait_for_pd_state(PD_STATE_ENABLED, 3)) {
		printf(SUB_2 "PD didn't reach enabled state within timeout\n");
		return false;
	}

	return true;
}

static bool test_pd_command_blocking()
{
	printf(SUB_2 "testing command blocking on disabled PD\n");
	reset_test_state();

	/* Test command on enabled PD first */
	struct osdp_cmd cmd = {
		.id = OSDP_CMD_BUZZER,
		.buzzer = {
			.control_code = 1,
			.on_count = 10,
			.off_count = 10,
			.rep_count = 1,
		},
	};

	int ret = osdp_cp_submit_command(g_test_ctx.cp_ctx, 0, &cmd);
	if (ret == 0) {
		printf(SUB_2 "Command submission succeeded on enabled PD\n");
		/* Wait a bit for command to be processed */
		if (wait_for_command(OSDP_CMD_BUZZER, 3)) {
			printf(SUB_2 "Command received by PD\n");
		} else {
			printf(SUB_2 "Command not received (PD may not be fully online yet)\n");
		}
	} else {
		printf(SUB_2 "Command submission failed on enabled PD (PD not online yet)\n");
	}

	/* Disable PD */
	if (osdp_cp_disable_pd(g_test_ctx.cp_ctx, 0) != 0) {
		printf(SUB_2 "Failed to disable PD\n");
		return false;
	}

	/* Try command on disabled PD - should fail */
	reset_test_state();
	ret = osdp_cp_submit_command(g_test_ctx.cp_ctx, 0, &cmd);
	if (ret == 0) {
		printf(SUB_2 "Command should fail on disabled PD\n");
		return false;
	}

	/* Re-enable and test command works again */
	if (osdp_cp_enable_pd(g_test_ctx.cp_ctx, 0) != 0) {
		printf(SUB_2 "Warning: enable returned error (might already be enabled)\n");
	}

	/* Wait for PD to come online before testing commands */
	if (wait_for_pd_online(5)) {
		ret = osdp_cp_submit_command(g_test_ctx.cp_ctx, 0, &cmd);
		printf(SUB_2 "Command on re-enabled PD: %s\n",
			   ret == 0 ? "SUCCESS" : "FAILED");
	} else {
		printf(SUB_2 "PD didn't come online within timeout, skipping command test\n");
	}

	return true;
}

static bool test_pd_edge_cases()
{
	printf(SUB_2 "testing edge cases\n");

	/* Ensure PD starts in enabled and online state */
	osdp_cp_enable_pd(g_test_ctx.cp_ctx, 0);  /* Ignore return - might already be enabled */
	if (!wait_for_pd_online(5)) {
		printf(SUB_2 "Failed to get PD online for edge test, proceeding anyway\n");
	}

	/* Test disabling PD */
	if (osdp_cp_disable_pd(g_test_ctx.cp_ctx, 0) != 0) {
		printf(SUB_2 "First disable failed\n");
		return false;
	}

	/* Wait for PD to be disabled */
	if (!wait_for_pd_state(PD_STATE_DISABLED, 3)) {
		printf(SUB_2 "PD didn't reach disabled state within timeout\n");
		return false;
	}

	if (osdp_cp_disable_pd(g_test_ctx.cp_ctx, 0) == 0) {
		printf(SUB_2 "Disabling already disabled PD should return error\n");
		return false;
	}

	/* Test enabling already enabled PD */
	if (osdp_cp_enable_pd(g_test_ctx.cp_ctx, 0) != 0) {
		printf(SUB_2 "Enable failed\n");
		return false;
	}

	/* Wait for PD to be enabled */
	if (!wait_for_pd_state(PD_STATE_ENABLED, 3)) {
		printf(SUB_2 "PD didn't reach enabled state within timeout\n");
		return false;
	}

	if (osdp_cp_enable_pd(g_test_ctx.cp_ctx, 0) == 0) {
		printf(SUB_2 "Enabling already enabled PD should return error\n");
		return false;
	}

	/* Test invalid PD index */
	if (osdp_cp_disable_pd(g_test_ctx.cp_ctx, 99) == 0) {
		printf(SUB_2 "Invalid PD index should fail\n");
		return false;
	}

	if (osdp_cp_enable_pd(g_test_ctx.cp_ctx, 99) == 0) {
		printf(SUB_2 "Invalid PD index should fail\n");
		return false;
	}

	bool enabled = osdp_cp_is_pd_enabled(g_test_ctx.cp_ctx, 99);
	/* Note: invalid PD returns -1 which converts to true in bool context */
	if (enabled != true) {
		printf(SUB_2 "Invalid PD index returns -1 (converted to true), got %s\n", enabled ? "true" : "false");
		return false;
	}

	return true;
}

static bool test_multiple_pd_hotplug()
{
	printf(SUB_2 "testing multiple PD hot-plug simulation\n");

	/* This test demonstrates the concept with a single PD, but shows
	 * how multiple PDs could be managed independently */

	/* Ensure PD starts in enabled and online state */
	osdp_cp_enable_pd(g_test_ctx.cp_ctx, 0);  /* Ignore return - might already be enabled */
	if (!wait_for_pd_online(5)) {
		printf(SUB_2 "Failed to get PD online for hotplug test, proceeding anyway\n");
	}

	/* Get initial status */
	uint8_t status_mask = 0;
	osdp_get_status_mask(g_test_ctx.cp_ctx, &status_mask);
	printf(SUB_2 "Initial status mask: 0x%02X\n", status_mask);

	/* Simulate "unplugging" PD 0 */
	printf(SUB_2 "Simulating PD 0 unplug...\n");
	if (osdp_cp_disable_pd(g_test_ctx.cp_ctx, 0) != 0) {
		printf(SUB_2 "Failed to disable PD 0\n");
		return false;
	}

	osdp_get_status_mask(g_test_ctx.cp_ctx, &status_mask);
	printf(SUB_2 "After PD 0 unplug: 0x%02X\n", status_mask);

	/* Wait a bit */
	usleep(500 * 1000);

	/* Simulate "plugging back" PD 0 */
	printf(SUB_2 "Simulating PD 0 plug-in...\n");
	if (osdp_cp_enable_pd(g_test_ctx.cp_ctx, 0) != 0) {
		printf(SUB_2 "Failed to enable PD 0\n");
		return false;
	}

	/* Give time for re-initialization */
	usleep(1000 * 1000);

	osdp_get_status_mask(g_test_ctx.cp_ctx, &status_mask);
	printf(SUB_2 "After PD 0 plug-in: 0x%02X (may still be initializing)\n", status_mask);

	return true;
}

static bool test_dynamic_pd_management()
{
	printf(SUB_2 "testing dynamic PD management scenarios\n");

	/* Test scenario: Disable PD during operation, add commands to queue,
	 * then re-enable and see behavior */

	/* First, ensure PD is enabled and online */
	osdp_cp_enable_pd(g_test_ctx.cp_ctx, 0);  /* Ignore return value - might already be enabled */
	if (!osdp_cp_is_pd_enabled(g_test_ctx.cp_ctx, 0)) {
		printf(SUB_2 "PD should be enabled but isn't\n");
		return false;
	}

	/* Wait for PD to be online before proceeding */
	if (!wait_for_pd_online(5)) {
		printf(SUB_2 "PD didn't come online, proceeding with test anyway\n");
	}

	printf(SUB_2 "PD management scenario: disable -> attempt commands -> enable\n");

	/* Disable PD (ensure it's enabled first) */
	if (!osdp_cp_is_pd_enabled(g_test_ctx.cp_ctx, 0)) {
		/* PD is disabled from previous test, enable it first */
		printf(SUB_2 "PD was disabled from previous test, enabling first\n");
		osdp_cp_enable_pd(g_test_ctx.cp_ctx, 0);
		if (!wait_for_pd_state(PD_STATE_ENABLED, 3)) {
			printf(SUB_2 "Failed to enable PD for management test\n");
			return false;
		}
	}
	if (osdp_cp_disable_pd(g_test_ctx.cp_ctx, 0) != 0) {
		printf(SUB_2 "Failed to disable PD\n");
		return false;
	}

	/* Wait for PD to actually be disabled */
	if (!wait_for_pd_state(PD_STATE_DISABLED, 3)) {
		printf(SUB_2 "PD didn't reach disabled state within timeout\n");
		return false;
	}

	/* Try multiple commands while disabled */
	struct osdp_cmd cmd1 = { .id = OSDP_CMD_BUZZER, .buzzer = { .control_code = 1 } };
	struct osdp_cmd cmd2 = { .id = OSDP_CMD_LED, .led = { .led_number = 0 } };

	int ret1 = osdp_cp_submit_command(g_test_ctx.cp_ctx, 0, &cmd1);
	int ret2 = osdp_cp_submit_command(g_test_ctx.cp_ctx, 0, &cmd2);

	printf(SUB_2 "Commands on disabled PD: buzzer=%s, led=%s (both should fail)\n",
	       ret1 == 0 ? "SUCCESS" : "FAILED", ret2 == 0 ? "SUCCESS" : "FAILED");

	if (ret1 == 0 || ret2 == 0) {
		printf(SUB_2 "Commands should fail on disabled PD\n");
		return false;
	}

	/* Re-enable PD */
	if (osdp_cp_enable_pd(g_test_ctx.cp_ctx, 0) != 0) {
		printf(SUB_2 "Failed to re-enable PD\n");
		return false;
	}

	printf(SUB_2 "PD re-enabled - will restart initialization sequence\n");

	/* Give time for re-initialization */
	usleep(2000 * 1000);

	/* Check final state */
	bool enabled = osdp_cp_is_pd_enabled(g_test_ctx.cp_ctx, 0);
	uint8_t status_mask = 0;
	osdp_get_status_mask(g_test_ctx.cp_ctx, &status_mask);

	printf(SUB_2 "Final state: enabled=%s, status_mask=0x%02X\n",
		   enabled == PD_STATE_ENABLED ? "YES" : "NO", status_mask);

	return enabled == PD_STATE_ENABLED;  /* PD should be enabled regardless of online status */
}

void run_hotplug_tests(struct test *t)
{
	bool overall_result = true;

	printf("\nBegin Hot-Plug Tests\n");

	/* Setup test environment once */
	if (setup_test_environment(t) != 0) {
		printf(SUB_1 "Failed to setup test environment\n");
		TEST_REPORT(t, false);
		return;
	}

	printf(SUB_1 "running hot-plug tests\n");

	/* Run all hot-plug tests */
	overall_result &= test_pd_disable_enable_basic();
	overall_result &= test_pd_command_blocking();
	overall_result &= test_pd_edge_cases();
	overall_result &= test_multiple_pd_hotplug();
	overall_result &= test_dynamic_pd_management();

	/* Teardown test environment */
	teardown_test_environment();

	printf(SUB_1 "Hot-plug tests %s\n", overall_result ? "succeeded" : "failed");
	TEST_REPORT(t, overall_result);
}