mirror of
https://github.com/ekinnee/SharpCAT.git
synced 2025-12-06 03:31:59 +01:00
339 lines
9.1 KiB
C
339 lines
9.1 KiB
C
|
|
/*++
|
||
|
|
|
||
|
|
Copyright (c) Microsoft Corporation, All Rights Reserved
|
||
|
|
|
||
|
|
Module Name:
|
||
|
|
|
||
|
|
RingBuffer.c
|
||
|
|
|
||
|
|
Abstract:
|
||
|
|
|
||
|
|
This file implements the Ring Buffer
|
||
|
|
|
||
|
|
Environment:
|
||
|
|
|
||
|
|
--*/
|
||
|
|
|
||
|
|
#include "internal.h"
|
||
|
|
|
||
|
|
VOID
|
||
|
|
RingBufferInitialize(
|
||
|
|
_In_ PRING_BUFFER Self,
|
||
|
|
_In_reads_bytes_(BufferSize)
|
||
|
|
BYTE* Buffer,
|
||
|
|
_In_ size_t BufferSize
|
||
|
|
)
|
||
|
|
{
|
||
|
|
Self->Size = BufferSize;
|
||
|
|
Self->Base = Buffer;
|
||
|
|
Self->End = Buffer + BufferSize;
|
||
|
|
Self->Head = Buffer;
|
||
|
|
Self->Tail = Buffer;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
VOID
|
||
|
|
RingBufferGetAvailableSpace(
|
||
|
|
_In_ PRING_BUFFER Self,
|
||
|
|
_Out_ size_t *AvailableSpace
|
||
|
|
)
|
||
|
|
{
|
||
|
|
BYTE* headSnapshot = NULL;
|
||
|
|
BYTE* tailSnapshot = NULL;
|
||
|
|
BYTE* tailPlusOne = NULL;
|
||
|
|
|
||
|
|
ASSERT(AvailableSpace);
|
||
|
|
|
||
|
|
//
|
||
|
|
// Take a snapshot of the head and tail pointers. We will compute the
|
||
|
|
// available space based on this snapshot. This is safe to do in a
|
||
|
|
// single-producer, single-consumer model, because -
|
||
|
|
// * A producer will call GetAvailableSpace() to determine whether
|
||
|
|
// there is enough space to write the data it is trying to write.
|
||
|
|
// The only other thread that could modify the amount of space
|
||
|
|
// available is the consumer thread, which can only increase the
|
||
|
|
// amount of space available. Hence it is safe for the producer
|
||
|
|
// to write based on this snapshot.
|
||
|
|
// * A consumer thread will call GetAvailableSpace() to determine
|
||
|
|
// whether there is enough data in the buffer for it to read.
|
||
|
|
// (Available data = Buffer size - Available space). The only
|
||
|
|
// other thread that could modify the amount of space available
|
||
|
|
// is the producer thread, which can only decrease the amount of
|
||
|
|
// space available (thereby increasing the amount of data
|
||
|
|
// available. Hence it is safe for the consumer to read based on
|
||
|
|
// this snapshot.
|
||
|
|
//
|
||
|
|
headSnapshot = Self->Head;
|
||
|
|
tailSnapshot = Self->Tail;
|
||
|
|
|
||
|
|
//
|
||
|
|
// In order to distinguish between a full buffer and an empty buffer,
|
||
|
|
// we always leave the last byte of the buffer unused. So, an empty
|
||
|
|
// buffer is denoted by -
|
||
|
|
// tail == head
|
||
|
|
// ... and a full buffer is denoted by -
|
||
|
|
// (tail+1) == head
|
||
|
|
//
|
||
|
|
tailPlusOne = ((tailSnapshot+1) == Self->End) ? Self->Base : (tailSnapshot+1);
|
||
|
|
|
||
|
|
if (tailPlusOne == headSnapshot)
|
||
|
|
{
|
||
|
|
//
|
||
|
|
// Buffer full
|
||
|
|
//
|
||
|
|
*AvailableSpace = 0;
|
||
|
|
}
|
||
|
|
else if (tailSnapshot == headSnapshot)
|
||
|
|
{
|
||
|
|
//
|
||
|
|
// Buffer empty
|
||
|
|
// The -1 in the computation below is to account for the fact that
|
||
|
|
// we always leave the last byte of the ring buffer unused in order
|
||
|
|
// to distinguish between an empty buffer and a full buffer.
|
||
|
|
//
|
||
|
|
*AvailableSpace = Self->Size - 1;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if (tailSnapshot > headSnapshot)
|
||
|
|
{
|
||
|
|
//
|
||
|
|
// Data has not wrapped around the end of the buffer
|
||
|
|
// The -1 in the computation below is to account for the fact
|
||
|
|
// that we always leave the last byte of the ring buffer unused
|
||
|
|
// in order to distinguish between an empty buffer and a full
|
||
|
|
// buffer.
|
||
|
|
//
|
||
|
|
*AvailableSpace = Self->Size - (tailSnapshot - headSnapshot) - 1;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
//
|
||
|
|
// Data has wrapped around the end of the buffer
|
||
|
|
// The -1 in the computation below is to account for the fact
|
||
|
|
// that we always leave the last byte of the ring buffer unused
|
||
|
|
// in order to distinguish between an empty buffer and a full
|
||
|
|
// buffer.
|
||
|
|
//
|
||
|
|
*AvailableSpace = (headSnapshot - tailSnapshot) - 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
VOID
|
||
|
|
RingBufferGetAvailableData(
|
||
|
|
_In_ PRING_BUFFER Self,
|
||
|
|
_Out_ size_t *AvailableData
|
||
|
|
)
|
||
|
|
{
|
||
|
|
size_t availableSpace;
|
||
|
|
|
||
|
|
ASSERT(AvailableData);
|
||
|
|
|
||
|
|
RingBufferGetAvailableSpace(Self, &availableSpace);
|
||
|
|
|
||
|
|
//
|
||
|
|
// The -1 in the arithmetic below accounts for the fact that we always
|
||
|
|
// keep 1 byte of the ring buffer unused in order to distinguish
|
||
|
|
// between a full buffer and an empty buffer.
|
||
|
|
//
|
||
|
|
*AvailableData = Self->Size - availableSpace - 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
NTSTATUS
|
||
|
|
RingBufferWrite(
|
||
|
|
_In_ PRING_BUFFER Self,
|
||
|
|
_In_reads_bytes_(DataSize)
|
||
|
|
BYTE* Data,
|
||
|
|
_In_ size_t DataSize
|
||
|
|
)
|
||
|
|
{
|
||
|
|
size_t availableSpace;
|
||
|
|
size_t bytesToCopy;
|
||
|
|
size_t spaceFromCurrToEnd;
|
||
|
|
|
||
|
|
ASSERT(Data && (0 != DataSize));
|
||
|
|
|
||
|
|
if (Self->Tail >= Self->End)
|
||
|
|
{
|
||
|
|
return STATUS_INTERNAL_ERROR;
|
||
|
|
}
|
||
|
|
|
||
|
|
//
|
||
|
|
// Get the amount of space available in the buffer
|
||
|
|
//
|
||
|
|
RingBufferGetAvailableSpace(Self, &availableSpace);
|
||
|
|
|
||
|
|
//
|
||
|
|
// If there is not enough space to fit in all the data passed in by the
|
||
|
|
// caller then copy as much as possible and throw away the rest
|
||
|
|
//
|
||
|
|
if (availableSpace < DataSize)
|
||
|
|
{
|
||
|
|
bytesToCopy = availableSpace;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
bytesToCopy = DataSize;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (bytesToCopy)
|
||
|
|
{
|
||
|
|
//
|
||
|
|
// The buffer has some space at least
|
||
|
|
//
|
||
|
|
if ((Self->Tail + bytesToCopy) > Self->End)
|
||
|
|
{
|
||
|
|
//
|
||
|
|
// The data being written will wrap around the end of the buffer.
|
||
|
|
// So the copy has to be done in two steps -
|
||
|
|
// * X bytes from current position to end of the buffer
|
||
|
|
// * the remaining (bytesToCopy - X) from the start of the buffer
|
||
|
|
//
|
||
|
|
|
||
|
|
//
|
||
|
|
// The first step of the copy ...
|
||
|
|
//
|
||
|
|
spaceFromCurrToEnd = Self->End - Self->Tail;
|
||
|
|
|
||
|
|
RtlCopyMemory(Self->Tail, Data, spaceFromCurrToEnd);
|
||
|
|
|
||
|
|
Data += spaceFromCurrToEnd;
|
||
|
|
|
||
|
|
bytesToCopy -= spaceFromCurrToEnd;
|
||
|
|
|
||
|
|
//
|
||
|
|
// The second step of the copy ...
|
||
|
|
//
|
||
|
|
RtlCopyMemory(Self->Base, Data, bytesToCopy);
|
||
|
|
|
||
|
|
//
|
||
|
|
// Advance the tail pointer
|
||
|
|
//
|
||
|
|
Self->Tail = Self->Base + bytesToCopy;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
//
|
||
|
|
// Data does NOT wrap around the end of the buffer. Just copy it
|
||
|
|
// over in a single step
|
||
|
|
//
|
||
|
|
RtlCopyMemory(Self->Tail, Data, bytesToCopy);
|
||
|
|
|
||
|
|
//
|
||
|
|
// Advance the tail pointer
|
||
|
|
//
|
||
|
|
Self->Tail += bytesToCopy;
|
||
|
|
if (Self->Tail == Self->End)
|
||
|
|
{
|
||
|
|
//
|
||
|
|
// We have exactly reached the end of the buffer. The next
|
||
|
|
// write should wrap around and start from the beginning.
|
||
|
|
//
|
||
|
|
Self->Tail = Self->Base;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
ASSERT(Self->Tail < Self->End);
|
||
|
|
}
|
||
|
|
|
||
|
|
return STATUS_SUCCESS;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
NTSTATUS
|
||
|
|
RingBufferRead(
|
||
|
|
_In_ PRING_BUFFER Self,
|
||
|
|
_Out_writes_bytes_to_(DataSize, *BytesCopied)
|
||
|
|
BYTE* Data,
|
||
|
|
_In_ size_t DataSize,
|
||
|
|
_Out_ size_t *BytesCopied
|
||
|
|
)
|
||
|
|
{
|
||
|
|
size_t availableData;
|
||
|
|
size_t dataFromCurrToEnd;
|
||
|
|
|
||
|
|
ASSERT(Data && (DataSize != 0));
|
||
|
|
|
||
|
|
if (Self->Head >= Self->End)
|
||
|
|
{
|
||
|
|
return STATUS_INTERNAL_ERROR;
|
||
|
|
}
|
||
|
|
|
||
|
|
//
|
||
|
|
// Get the amount of data available in the buffer
|
||
|
|
//
|
||
|
|
RingBufferGetAvailableData(Self, &availableData);
|
||
|
|
|
||
|
|
if (availableData == 0)
|
||
|
|
{
|
||
|
|
*BytesCopied = 0;
|
||
|
|
return STATUS_SUCCESS;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (DataSize > availableData)
|
||
|
|
{
|
||
|
|
DataSize = availableData;
|
||
|
|
}
|
||
|
|
|
||
|
|
*BytesCopied = DataSize;
|
||
|
|
|
||
|
|
if ((Self->Head + DataSize) > Self->End)
|
||
|
|
{
|
||
|
|
//
|
||
|
|
// The data requested by the caller is wrapped around the end of the
|
||
|
|
// buffer. So we'll do the copy in two steps -
|
||
|
|
// * Copy X bytes from the current position to the end buffer into
|
||
|
|
// the caller's buffer
|
||
|
|
// * Copy (DataSize - X) bytes from the beginning to the buffer into
|
||
|
|
// the caller's buffer
|
||
|
|
//
|
||
|
|
|
||
|
|
//
|
||
|
|
// The first step of the copy ...
|
||
|
|
//
|
||
|
|
dataFromCurrToEnd = Self->End - Self->Head;
|
||
|
|
RtlCopyMemory(Data, Self->Head, dataFromCurrToEnd);
|
||
|
|
Data += dataFromCurrToEnd;
|
||
|
|
DataSize -= dataFromCurrToEnd;
|
||
|
|
|
||
|
|
//
|
||
|
|
// The second step of the copy ...
|
||
|
|
//
|
||
|
|
RtlCopyMemory(Data, Self->Base, DataSize);
|
||
|
|
|
||
|
|
//
|
||
|
|
// Advance the head pointer
|
||
|
|
//
|
||
|
|
Self->Head = Self->Base + DataSize;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
//
|
||
|
|
// The data in the buffer is NOT wrapped around the end of the buffer.
|
||
|
|
// Simply copy the data over to the caller's buffer in a single step.
|
||
|
|
//
|
||
|
|
RtlCopyMemory(Data, Self->Head, DataSize);
|
||
|
|
|
||
|
|
//
|
||
|
|
// Advance the head pointer
|
||
|
|
//
|
||
|
|
Self->Head += DataSize;
|
||
|
|
if (Self->Head == Self->End)
|
||
|
|
{
|
||
|
|
//
|
||
|
|
// We have exactly reached the end of the buffer. The next
|
||
|
|
// read should wrap around and start from the beginning.
|
||
|
|
//
|
||
|
|
Self->Head = Self->Base;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
ASSERT(Self->Head < Self->End);
|
||
|
|
|
||
|
|
return STATUS_SUCCESS;
|
||
|
|
}
|
||
|
|
|