heartbeat packet

This commit is contained in:
donnaskiez 2024-05-05 21:07:05 +10:00
parent c6b1f03db0
commit 7280c7eec6
12 changed files with 219 additions and 170 deletions

View file

@ -1,4 +1,127 @@
---
BasedOnStyle: LLVM
BasedOnStyle: webkit
AccessModifierOffset: -4
...
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: true
AlignConsecutiveMacros: true
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: TopLevel
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: true #false
BinPackArguments: false
BinPackParameters: false
AllowAllParametersOfDeclarationOnNextLine: true
BreakBeforeBraces: Stroustrup
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: true
AfterEnum: true
AfterFunction: true
AfterNamespace: false
AfterStruct: true
AfterUnion: true
AfterExternBlock: false
BeforeCatch: true
BeforeElse: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: AfterColon
BreakStringLiterals: false
ColumnLimit: 80
CommentPragmas: '^begin_wpp|^end_wpp|^FUNC |^USESUFFIX |^USESUFFIX '
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
ExperimentalAutoDetectBinPacking: false
IndentCaseLabels: false
IndentPPDirectives: AfterHash
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false
Language: Cpp
MacroBlockBegin: '^BEGIN_MODULE$|^BEGIN_TEST_CLASS$|^BEGIN_TEST_METHOD$'
MacroBlockEnd: '^END_MODULE$|^END_TEST_CLASS$|^END_TEST_METHOD$'
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None #All
PointerAlignment: Left
ReflowComments: true
SortIncludes: false
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
StatementMacros: [
'EXTERN_C',
'PAGED',
'PAGEDX',
'NONPAGED',
'PNPCODE',
'INITCODE',
'_At_',
'_When_',
'_Success_',
'_Check_return_',
'_Must_inspect_result_',
'_IRQL_requires_same_',
'_IRQL_requires_',
'_IRQL_requires_max_',
'_IRQL_requires_min_',
'_IRQL_saves_',
'_IRQL_restores_',
'_IRQL_saves_global_',
'_IRQL_restores_global_',
'_IRQL_raises_',
'_IRQL_lowers_',
'_Acquires_lock_',
'_Releases_lock_',
'_Acquires_exclusive_lock_',
'_Releases_exclusive_lock_',
'_Acquires_shared_lock_',
'_Releases_shared_lock_',
'_Requires_lock_held_',
'_Use_decl_annotations_',
'_Guarded_by_',
'__drv_preferredFunction',
'__drv_allocatesMem',
'__drv_freesMem',
]
TabWidth: '4'
UseTab: Never

View file

@ -1,127 +0,0 @@
BasedOnStyle: webkit
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: true
AlignConsecutiveMacros: true
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: TopLevel
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: true #false
BinPackArguments: false
BinPackParameters: false
AllowAllParametersOfDeclarationOnNextLine: true
BreakBeforeBraces: Stroustrup
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: true
AfterEnum: true
AfterFunction: true
AfterNamespace: false
AfterStruct: true
AfterUnion: true
AfterExternBlock: false
BeforeCatch: true
BeforeElse: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: AfterColon
BreakStringLiterals: false
ColumnLimit: 80
CommentPragmas: '^begin_wpp|^end_wpp|^FUNC |^USESUFFIX |^USESUFFIX '
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
ExperimentalAutoDetectBinPacking: false
IndentCaseLabels: false
IndentPPDirectives: AfterHash
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false
Language: Cpp
MacroBlockBegin: '^BEGIN_MODULE$|^BEGIN_TEST_CLASS$|^BEGIN_TEST_METHOD$'
MacroBlockEnd: '^END_MODULE$|^END_TEST_CLASS$|^END_TEST_METHOD$'
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None #All
PointerAlignment: Left
ReflowComments: true
SortIncludes: false
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
StatementMacros: [
'EXTERN_C',
'PAGED',
'PAGEDX',
'NONPAGED',
'PNPCODE',
'INITCODE',
'_At_',
'_When_',
'_Success_',
'_Check_return_',
'_Must_inspect_result_',
'_IRQL_requires_same_',
'_IRQL_requires_',
'_IRQL_requires_max_',
'_IRQL_requires_min_',
'_IRQL_saves_',
'_IRQL_restores_',
'_IRQL_saves_global_',
'_IRQL_restores_global_',
'_IRQL_raises_',
'_IRQL_lowers_',
'_Acquires_lock_',
'_Releases_lock_',
'_Acquires_exclusive_lock_',
'_Releases_exclusive_lock_',
'_Acquires_shared_lock_',
'_Releases_shared_lock_',
'_Requires_lock_held_',
'_Use_decl_annotations_',
'_Guarded_by_',
'__drv_preferredFunction',
'__drv_allocatesMem',
'__drv_freesMem',
]
TabWidth: '4'
UseTab: Never

4
.clang-format-cpp Normal file
View file

@ -0,0 +1,4 @@
---
BasedOnStyle: LLVM
...

View file

@ -695,7 +695,7 @@ ObPreOpCallbackRoutine(_In_ PVOID RegistrationContext,
process_creator_name,
HANDLE_REPORT_PROCESS_NAME_MAX_LENGTH);
IrpQueueCompleteIrp(report,
IrpQueueCompletePacket(report,
sizeof(OPEN_HANDLE_FAILURE_REPORT));
}
@ -871,7 +871,7 @@ EnumHandleCallback(_In_ PHANDLE_TABLE HandleTable,
HANDLE_REPORT_PROCESS_NAME_MAX_LENGTH);
if (!NT_SUCCESS(
IrpQueueCompleteIrp(report, sizeof(OPEN_HANDLE_FAILURE_REPORT)))) {
IrpQueueCompletePacket(report, sizeof(OPEN_HANDLE_FAILURE_REPORT)))) {
DEBUG_ERROR("IrpQueueCompleteIrp failed with no status.");
goto end;
}

View file

@ -194,6 +194,7 @@ typedef struct _IRP_QUEUE_HEAD {
volatile UINT32 irp_count;
volatile UINT32 total_reports_completed;
volatile UINT32 total_irps_completed;
volatile UINT32 total_heartbeats_completed;
IO_CSQ csq;
KSPIN_LOCK lock;
DEFERRED_REPORTS_LIST deferred_reports;

View file

@ -876,7 +876,7 @@ ReportInvalidProcessModule(_In_ PPROCESS_MODULE_INFORMATION Module)
RtlCopyMemory(
report->module_path, Module->module_path, sizeof(report->module_path));
IrpQueueCompleteIrp(report, sizeof(PROCESS_MODULE_VALIDATION_REPORT));
IrpQueueCompletePacket(report, sizeof(PROCESS_MODULE_VALIDATION_REPORT));
}
/*
@ -2147,11 +2147,23 @@ STATIC
PHEARTBEAT_PACKET
BuildHeartbeatPacket(_In_ PHEARTBEAT_CONFIGURATION Configuration)
{
PIRP_QUEUE_HEAD queue = GetIrpQueueHead();
PHEARTBEAT_PACKET packet = ImpExAllocatePool2(
POOL_FLAG_NON_PAGED, sizeof(HEARTBEAT_PACKET), POOL_TAG_HEARTBEAT);
if (!packet)
return NULL;
INIT_PACKET_HEADER(&packet->header, PACKET_TYPE_HEARTBEAT);
/* This routine always runs at DPC level */
KeAcquireSpinLockAtDpcLevel(&queue->lock);
packet->total_heartbeats_completed = queue->total_heartbeats_completed;
packet->total_irps_completed = queue->total_irps_completed;
packet->total_reports_completed = queue->total_reports_completed;
KeReleaseSpinLockFromDpcLevel(&queue->lock);
return packet;
}
STATIC
@ -2169,14 +2181,18 @@ HeartbeatDpcRoutine(_In_ PKDPC Dpc,
return;
PHEARTBEAT_CONFIGURATION config = (PHEARTBEAT_CONFIGURATION)DeferredContext;
PHEARTBEAT_PACKET packet = NULL;
DEBUG_VERBOSE("Heartbeat timer alerted. Generating heartbeat packet.");
SetHeartbeatActive(config);
#if DEBUG
DEBUG_INFO("heartbeat called!");
#endif
packet = BuildHeartbeatPacket(config);
IncrementHeartbeatCounter(config);
if (packet) {
IrpQueueCompletePacket(packet, sizeof(HEARTBEAT_PACKET));
IncrementHeartbeatCounter(config);
}
end:

View file

@ -122,48 +122,75 @@ IrpQueueRemove(_In_ PIO_CSQ Csq, _In_ PIRP Irp)
STATIC
BOOLEAN
IrpQueueIsThereDeferredReport(_In_ PIRP_QUEUE_HEAD Queue)
IrpQueueIsThereDeferredPackets(_In_ PIRP_QUEUE_HEAD Queue)
{
return Queue->deferred_reports.count > 0 ? TRUE : FALSE;
}
STATIC
PDEFERRED_REPORT
IrpQueueRemoveDeferredReport(_In_ PIRP_QUEUE_HEAD Queue)
IrpQueueRemoveDeferredPacket(_In_ PIRP_QUEUE_HEAD Queue)
{
return RemoveHeadList(&Queue->deferred_reports.head);
}
STATIC
VOID
IrpQueueFreeDeferredReport(_In_ PDEFERRED_REPORT Report)
IrpQueueFreeDeferredPacket(_In_ PDEFERRED_REPORT Report)
{
ImpExFreePoolWithTag(Report->buffer, REPORT_POOL_TAG);
ImpExFreePoolWithTag(Report, REPORT_POOL_TAG);
}
FORCEINLINE
STATIC
UINT16
GetPacketType(_In_ PVOID Buffer)
{
PPACKET_HEADER header = (PPACKET_HEADER)Buffer;
return header->packet_type;
}
FORCEINLINE
STATIC
VOID
IncrementPacketMetics(_In_ PIRP_QUEUE_HEAD Queue, UINT16 Type)
{
if (Type == PACKET_TYPE_REPORT)
Queue->total_reports_completed++;
if (Type == PACKET_TYPE_HEARTBEAT)
Queue->total_heartbeats_completed++;
Queue->total_irps_completed++;
}
STATIC
NTSTATUS
IrpQueueCompleteDeferredReport(_In_ PDEFERRED_REPORT Report, _In_ PIRP Irp)
IrpQueueCompleteDeferredPacket(_In_ PDEFERRED_REPORT Report, _In_ PIRP Irp)
{
NTSTATUS status = ValidateIrpOutputBuffer(Irp, Report->buffer_size);
NTSTATUS status = ValidateIrpOutputBuffer(Irp, Report->buffer_size);
PIRP_QUEUE_HEAD queue = GetIrpQueueHead();
UINT16 type = GetPacketType(Report->buffer);
if (!NT_SUCCESS(status))
return status;
IncrementPacketMetics(queue, type);
RtlCopyMemory(
Irp->AssociatedIrp.SystemBuffer, Report->buffer, Report->buffer_size);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = Report->buffer_size;
IofCompleteRequest(Irp, IO_NO_INCREMENT);
IrpQueueFreeDeferredReport(Report);
IrpQueueFreeDeferredPacket(Report);
return STATUS_SUCCESS;
}
STATIC
NTSTATUS
IrpQueueQueryPendingReports(_In_ PIRP Irp)
IrpQueueQueryPendingPackets(_In_ PIRP Irp)
{
PIRP_QUEUE_HEAD queue = GetIrpQueueHead();
PDEFERRED_REPORT report = NULL;
@ -180,12 +207,12 @@ IrpQueueQueryPendingReports(_In_ PIRP Irp)
*/
KeAcquireSpinLock(&GetIrpQueueHead()->deferred_reports.lock, &irql);
if (IrpQueueIsThereDeferredReport(queue)) {
report = IrpQueueRemoveDeferredReport(queue);
status = IrpQueueCompleteDeferredReport(report, Irp);
if (IrpQueueIsThereDeferredPackets(queue)) {
report = IrpQueueRemoveDeferredPacket(queue);
status = IrpQueueCompleteDeferredPacket(report, Irp);
if (!NT_SUCCESS(status)) {
IrpQueueFreeDeferredReport(report);
IrpQueueFreeDeferredPacket(report);
goto end;
}
@ -219,7 +246,7 @@ IrpQueueCompleteCancelledIrp(_In_ PIO_CSQ Csq, _In_ PIRP Irp)
STATIC
PDEFERRED_REPORT
IrpQueueAllocateDeferredReport(_In_ PVOID Buffer, _In_ UINT32 BufferSize)
IrpQueueAllocateDeferredPacket(_In_ PVOID Buffer, _In_ UINT32 BufferSize)
{
PDEFERRED_REPORT report = ImpExAllocatePool2(
POOL_FLAG_NON_PAGED, sizeof(DEFERRED_REPORT), REPORT_POOL_TAG);
@ -236,7 +263,7 @@ IrpQueueAllocateDeferredReport(_In_ PVOID Buffer, _In_ UINT32 BufferSize)
STATIC
VOID
IrpQueueDeferReport(_In_ PIRP_QUEUE_HEAD Queue,
IrpQueueDeferPacket(_In_ PIRP_QUEUE_HEAD Queue,
_In_ PVOID Buffer,
_In_ UINT32 BufferSize)
{
@ -251,7 +278,7 @@ IrpQueueDeferReport(_In_ PIRP_QUEUE_HEAD Queue,
return;
}
report = IrpQueueAllocateDeferredReport(Buffer, BufferSize);
report = IrpQueueAllocateDeferredPacket(Buffer, BufferSize);
if (!report)
return;
@ -268,11 +295,12 @@ IrpQueueDeferReport(_In_ PIRP_QUEUE_HEAD Queue,
* IMPORTANT: All report buffers must be allocated in non paged memory.
*/
NTSTATUS
IrpQueueCompleteIrp(_In_ PVOID Buffer, _In_ ULONG BufferSize)
IrpQueueCompletePacket(_In_ PVOID Buffer, _In_ ULONG BufferSize)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
PIRP_QUEUE_HEAD queue = GetIrpQueueHead();
PIRP irp = IoCsqRemoveNextIrp(&queue->csq, NULL);
UINT16 type = GetPacketType(Buffer);
/*
* If no irps are available in our queue, lets store it in a deferred
@ -280,7 +308,7 @@ IrpQueueCompleteIrp(_In_ PVOID Buffer, _In_ ULONG BufferSize)
* into the queue.
*/
if (!irp) {
IrpQueueDeferReport(queue, Buffer, BufferSize);
IrpQueueDeferPacket(queue, Buffer, BufferSize);
return STATUS_SUCCESS;
}
@ -298,6 +326,8 @@ IrpQueueCompleteIrp(_In_ PVOID Buffer, _In_ ULONG BufferSize)
return status;
}
IncrementPacketMetics(queue, type);
irp->IoStatus.Status = STATUS_SUCCESS;
irp->IoStatus.Information = BufferSize;
RtlCopyMemory(irp->AssociatedIrp.SystemBuffer, Buffer, BufferSize);
@ -307,7 +337,7 @@ IrpQueueCompleteIrp(_In_ PVOID Buffer, _In_ ULONG BufferSize)
}
VOID
IrpQueueFreeDeferredReports()
IrpQueueFreeDeferredPackets()
{
PIRP_QUEUE_HEAD queue = GetIrpQueueHead();
PDEFERRED_REPORT report = NULL;
@ -316,9 +346,9 @@ IrpQueueFreeDeferredReports()
/* just in case... */
KeAcquireSpinLock(&GetIrpQueueHead()->deferred_reports.lock, &irql);
while (IrpQueueIsThereDeferredReport(queue)) {
report = IrpQueueRemoveDeferredReport(queue);
IrpQueueFreeDeferredReport(report);
while (IrpQueueIsThereDeferredPackets(queue)) {
report = IrpQueueRemoveDeferredPacket(queue);
IrpQueueFreeDeferredPacket(report);
}
KeReleaseSpinLock(&GetIrpQueueHead()->deferred_reports.lock, irql);
@ -1048,7 +1078,7 @@ DeviceControl(_In_ PDEVICE_OBJECT DeviceObject, _Inout_ PIRP Irp)
/* before we queue our IRP, check if we can complete a deferred
* report */
status = IrpQueueQueryPendingReports(Irp);
status = IrpQueueQueryPendingPackets(Irp);
/* if we return success, weve completed the irp, we can return
* success */

View file

@ -63,6 +63,6 @@ NTSTATUS
IrpQueueInitialise();
NTSTATUS
IrpQueueCompleteIrp(_In_ PVOID Buffer, _In_ ULONG BufferSize);
IrpQueueCompletePacket(_In_ PVOID Buffer, _In_ ULONG BufferSize);
#endif

View file

@ -683,7 +683,7 @@ ReportInvalidDriverObject(_In_ PINVALID_DRIVERS_HEAD Head)
ImpRtlUnicodeStringToAnsiString(
&string, &Head->first_entry->driver->DriverName, FALSE);
IrpQueueCompleteIrp(report, sizeof(MODULE_VALIDATION_FAILURE));
IrpQueueCompletePacket(report, sizeof(MODULE_VALIDATION_FAILURE));
}
NTSTATUS
@ -817,7 +817,7 @@ ReportNmiBlocking()
report->invalid_rip = NULL;
report->were_nmis_disabled = TRUE;
IrpQueueCompleteIrp(report, sizeof(NMI_CALLBACK_FAILURE));
IrpQueueCompletePacket(report, sizeof(NMI_CALLBACK_FAILURE));
}
STATIC
@ -844,7 +844,7 @@ ReportMissingCidTableEntry(_In_ PNMI_CONTEXT Context)
report->thread_address = Context->kthread;
RtlCopyMemory(report->thread, Context->kthread, sizeof(report->thread));
IrpQueueCompleteIrp(report, sizeof(HIDDEN_SYSTEM_THREAD_REPORT));
IrpQueueCompletePacket(report, sizeof(HIDDEN_SYSTEM_THREAD_REPORT));
}
STATIC
@ -866,7 +866,7 @@ ReportInvalidRipFoundDuringNmi(_In_ PNMI_CONTEXT Context)
report->invalid_rip = Context->interrupted_rip;
report->were_nmis_disabled = FALSE;
IrpQueueCompleteIrp(report, sizeof(HIDDEN_SYSTEM_THREAD_REPORT));
IrpQueueCompletePacket(report, sizeof(HIDDEN_SYSTEM_THREAD_REPORT));
}
/*
@ -1153,7 +1153,7 @@ ReportApcStackwalkViolation(_In_ UINT64 Rip)
report->invalid_rip = Rip;
// report->driver ?? todo!
IrpQueueCompleteIrp(report, sizeof(APC_STACKWALK_REPORT));
IrpQueueCompletePacket(report, sizeof(APC_STACKWALK_REPORT));
}
/*
@ -1471,7 +1471,7 @@ ReportDpcStackwalkViolation(_In_ PDPC_CONTEXT Context, _In_ UINT64 Frame)
// - 0x50,
// APC_STACKWALK_BUFFER_SIZE);
IrpQueueCompleteIrp(report, sizeof(DPC_STACKWALK_REPORT));
IrpQueueCompletePacket(report, sizeof(DPC_STACKWALK_REPORT));
}
STATIC
@ -1835,7 +1835,7 @@ ReportDataTableInvalidRoutine(_In_ TABLE_ID TableId, _In_ UINT64 Address)
RtlCopyMemory(report->routine, Address, DATA_TABLE_ROUTINE_BUF_SIZE);
if (!NT_SUCCESS(
IrpQueueCompleteIrp(report, sizeof(DATA_TABLE_ROUTINE_REPORT))))
IrpQueueCompletePacket(report, sizeof(DATA_TABLE_ROUTINE_REPORT))))
DEBUG_ERROR("IrpQueueCompleteIrp failed with no status.");
}
@ -2166,7 +2166,7 @@ ReportWin32kBase_DxgInterfaceViolation(_In_ UINT32 TableIndex,
// todo! report->routine = ??
// todo: maybe get routine by name from index ?
IrpQueueCompleteIrp(report, sizeof(DPC_STACKWALK_REPORT));
IrpQueueCompletePacket(report, sizeof(DPC_STACKWALK_REPORT));
}
STATIC

View file

@ -736,7 +736,7 @@ FindUnlinkedProcesses()
RtlCopyMemory(
report->process, allocation, REPORT_INVALID_PROCESS_BUFFER_SIZE);
if (!NT_SUCCESS(IrpQueueCompleteIrp(
if (!NT_SUCCESS(IrpQueueCompletePacket(
report, sizeof(INVALID_PROCESS_ALLOCATION_REPORT)))) {
DEBUG_ERROR("IrpQueueCompleteIrp failed with no status.");
continue;

View file

@ -119,7 +119,7 @@ DetectAttachedThreadsProcessCallback(_In_ PTHREAD_LIST_ENTRY ThreadListEntry,
report->thread_id = ImpPsGetThreadId(ThreadListEntry->thread);
report->thread_address = ThreadListEntry->thread;
if (!NT_SUCCESS(IrpQueueCompleteIrp(report, sizeof(ATTACH_PROCESS_REPORT))))
if (!NT_SUCCESS(IrpQueueCompletePacket(report, sizeof(ATTACH_PROCESS_REPORT))))
DEBUG_ERROR("IrpQueueCompleteIrp failed with no status.");
}

View file

@ -169,7 +169,9 @@ typedef struct _PROCESS_MODULE_VALIDATION_REPORT {
typedef struct _HEARTBEAT_PACKET {
PACKET_HEADER header;
UINT32 heartbeat_count;
UINT32 last_report_id;
UINT32 total_reports_completed;
UINT32 total_irps_completed;
UINT32 total_heartbeats_completed;
} HEARTBEAT_PACKET, *PHEARTBEAT_PACKET;