AI: Add more utests for kernel module. v7.0.81 (#4478)

This PR significantly enhances the kernel module by adding comprehensive
unit test coverage and improving interface design for core buffer and
load balancer components.

- **ISrsDecoder**: New interface for decoding/deserialization operations
- **ISrsLbRoundRobin**: Extracted interface from concrete
SrsLbRoundRobin class for better abstraction
- **Enhanced Documentation**: Added comprehensive inline documentation
for ISrsEncoder, ISrsCodec, SrsBuffer, and SrsBitBuffer classes

---------

Co-authored-by: OSSRS-AI <winlinam@gmail.com>
This commit is contained in:
Winlin 2025-09-06 12:39:46 -04:00 committed by GitHub
parent 8976ce4c8d
commit 7c1e87ef5c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 3108 additions and 247 deletions

View File

@ -162,6 +162,60 @@ code_patterns:
- pattern: "srs_freepa"
description: "Custom macro for freeing arrays - use only when smart pointers are not suitable"
naming_conventions:
- pattern: "field_naming"
description: "MANDATORY - All class and struct fields must end with underscore (_)"
usage: |
WRONG: Fields without underscore
class SrsBuffer {
private:
char *p;
int size;
public:
bool enabled;
};
CORRECT: Fields with underscore
class SrsBuffer {
private:
char *p_;
int size_;
public:
bool enabled_;
};
scope: "Applies to ALL fields (private, protected, public) in both classes and structs"
exceptions: "Only applies to SRS-defined classes/structs - do NOT change 3rd party code like llhttp"
rationale: "Consistent naming convention across SRS codebase for better code readability and maintenance"
commenting_style:
- pattern: "C++ style comments"
description: "MANDATORY - Use C++ style comments (//) instead of C style comments (/* */)"
usage: |
WRONG: C style comments
/* This is a comment */
/**
* Multi-line comment
* with multiple lines
*/
CORRECT: C++ style comments
// This is a comment
// Multi-line comment
// with multiple lines
rationale: "Consistent with SRS codebase style and better for single-line documentation"
- pattern: "No thread-safety comments"
description: "Do NOT describe thread-safety in comments since SRS is a single-threaded application"
usage: |
WRONG: Mentioning thread-safety
// This implementation is thread-safe for single-threaded usage but may
// require external synchronization in multi-threaded environments.
CORRECT: Focus on functionality without thread-safety mentions
// This implementation maintains state between calls for consistent
// round-robin behavior across multiple selections.
rationale: "SRS uses single-threaded coroutine-based architecture, so thread-safety discussions are irrelevant and confusing"
error_handling:
- pattern: "srs_error_t"
description: "Custom error handling system - MANDATORY for all functions that return srs_error_t"

View File

@ -7,6 +7,7 @@ The changelog for SRS.
<a name="v7-changes"></a>
## SRS 7.0 Changelog
* v7.0, 2025-09-06, Merge [#4478](https://github.com/ossrs/srs/pull/4478): AI: Add more utests for kernel module. v7.0.81 (#4478)
* v7.0, 2025-09-06, Merge [#4475](https://github.com/ossrs/srs/pull/4475): AI: Support anonymous coroutine with code block. v7.0.80 (#4475)
* v7.0, 2025-09-05, Merge [#4474](https://github.com/ossrs/srs/pull/4474): WebRTC: Fix race condition in RTC nack timer callbacks. v7.0.79 (#4474)
* v7.0, 2025-09-04, Merge [#4467](https://github.com/ossrs/srs/pull/4467): WebRTC: Fix NACK recovered packets not being added to receive queue. v7.0.78 (#4467)

View File

@ -62,7 +62,7 @@ SrsEdgeRtmpUpstream::~SrsEdgeRtmpUpstream()
close();
}
srs_error_t SrsEdgeRtmpUpstream::connect(ISrsRequest *r, SrsLbRoundRobin *lb)
srs_error_t SrsEdgeRtmpUpstream::connect(ISrsRequest *r, ISrsLbRoundRobin *lb)
{
srs_error_t err = srs_success;
@ -173,7 +173,7 @@ SrsEdgeFlvUpstream::~SrsEdgeFlvUpstream()
close();
}
srs_error_t SrsEdgeFlvUpstream::connect(ISrsRequest *r, SrsLbRoundRobin *lb)
srs_error_t SrsEdgeFlvUpstream::connect(ISrsRequest *r, ISrsLbRoundRobin *lb)
{
// Because we might modify the r, which cause retry fail, so we must copy it.
ISrsRequest *cp = r->copy();
@ -185,7 +185,7 @@ srs_error_t SrsEdgeFlvUpstream::connect(ISrsRequest *r, SrsLbRoundRobin *lb)
return do_connect(cp, lb, 0);
}
srs_error_t SrsEdgeFlvUpstream::do_connect(ISrsRequest *r, SrsLbRoundRobin *lb, int redirect_depth)
srs_error_t SrsEdgeFlvUpstream::do_connect(ISrsRequest *r, ISrsLbRoundRobin *lb, int redirect_depth)
{
srs_error_t err = srs_success;
@ -458,11 +458,6 @@ void SrsEdgeIngester::stop()
}
}
string SrsEdgeIngester::get_curr_origin()
{
return lb->selected();
}
#ifdef SRS_APM
ISrsApmSpan *SrsEdgeIngester::span()
{
@ -999,11 +994,6 @@ void SrsPlayEdge::on_all_client_stop()
}
}
string SrsPlayEdge::get_curr_origin()
{
return ingester->get_curr_origin();
}
srs_error_t SrsPlayEdge::on_ingest_play()
{
srs_error_t err = srs_success;

View File

@ -25,7 +25,7 @@ class SrsRtmpCommonMessage;
class SrsMessageQueue;
class ISrsProtocolReadWriter;
class SrsKbps;
class SrsLbRoundRobin;
class ISrsLbRoundRobin;
class SrsTcpClient;
class SrsSimpleRtmpClient;
class SrsRtmpCommand;
@ -64,7 +64,7 @@ public:
virtual ~SrsEdgeUpstream();
public:
virtual srs_error_t connect(ISrsRequest *r, SrsLbRoundRobin *lb) = 0;
virtual srs_error_t connect(ISrsRequest *r, ISrsLbRoundRobin *lb) = 0;
virtual srs_error_t recv_message(SrsRtmpCommonMessage **pmsg) = 0;
virtual srs_error_t decode_message(SrsRtmpCommonMessage *msg, SrsRtmpCommand **ppacket) = 0;
virtual void close() = 0;
@ -94,7 +94,7 @@ public:
virtual ~SrsEdgeRtmpUpstream();
public:
virtual srs_error_t connect(ISrsRequest *r, SrsLbRoundRobin *lb);
virtual srs_error_t connect(ISrsRequest *r, ISrsLbRoundRobin *lb);
virtual srs_error_t recv_message(SrsRtmpCommonMessage **pmsg);
virtual srs_error_t decode_message(SrsRtmpCommonMessage *msg, SrsRtmpCommand **ppacket);
virtual void close();
@ -128,10 +128,10 @@ public:
virtual ~SrsEdgeFlvUpstream();
public:
virtual srs_error_t connect(ISrsRequest *r, SrsLbRoundRobin *lb);
virtual srs_error_t connect(ISrsRequest *r, ISrsLbRoundRobin *lb);
private:
virtual srs_error_t do_connect(ISrsRequest *r, SrsLbRoundRobin *lb, int redirect_depth);
virtual srs_error_t do_connect(ISrsRequest *r, ISrsLbRoundRobin *lb, int redirect_depth);
public:
virtual srs_error_t recv_message(SrsRtmpCommonMessage **pmsg);
@ -155,7 +155,7 @@ private:
SrsPlayEdge *edge;
ISrsRequest *req;
SrsCoroutine *trd;
SrsLbRoundRobin *lb;
ISrsLbRoundRobin *lb;
SrsEdgeUpstream *upstream;
public:
@ -166,7 +166,6 @@ public:
virtual srs_error_t initialize(SrsSharedPtr<SrsLiveSource> s, SrsPlayEdge *e, ISrsRequest *r);
virtual srs_error_t start();
virtual void stop();
virtual std::string get_curr_origin();
// Interface ISrsReusableThread2Handler
public:
@ -192,7 +191,7 @@ private:
ISrsRequest *req;
SrsCoroutine *trd;
SrsSimpleRtmpClient *sdk;
SrsLbRoundRobin *lb;
ISrsLbRoundRobin *lb;
// we must ensure one thread one fd principle,
// that is, a fd must be write/read by the one thread.
// The publish service thread will proxy(msg), and the edge forward thread
@ -243,7 +242,6 @@ public:
virtual srs_error_t on_client_play();
// When all client stopped play, disconnect to origin.
virtual void on_all_client_stop();
virtual std::string get_curr_origin();
public:
// When ingester start to play stream.

View File

@ -2521,8 +2521,3 @@ void SrsLiveSource::on_edge_proxy_unpublish()
{
publish_edge->on_proxy_unpublish();
}
string SrsLiveSource::get_curr_origin()
{
return play_edge->get_curr_origin();
}

View File

@ -638,9 +638,6 @@ public:
virtual srs_error_t on_edge_proxy_publish(SrsRtmpCommonMessage *msg);
// For edge, proxy stop publish
virtual void on_edge_proxy_unpublish();
public:
virtual std::string get_curr_origin();
};
#endif

View File

@ -9,6 +9,6 @@
#define VERSION_MAJOR 7
#define VERSION_MINOR 0
#define VERSION_REVISION 80
#define VERSION_REVISION 81
#endif

View File

@ -10,6 +10,14 @@
using namespace std;
ISrsLbRoundRobin::ISrsLbRoundRobin()
{
}
ISrsLbRoundRobin::~ISrsLbRoundRobin()
{
}
SrsLbRoundRobin::SrsLbRoundRobin()
{
index = -1;

View File

@ -12,18 +12,47 @@
#include <string>
#include <vector>
/**
* the round-robin load balance algorithm,
* used for edge pull and other multiple server feature.
*/
class SrsLbRoundRobin
// Interface for round-robin load balance algorithm.
//
// This interface defines the contract for load balancing algorithms that distribute
// requests across multiple servers in a round-robin fashion. It's primarily used
// for edge pull scenarios and other features that require distributing load across
// multiple backend servers.
//
// The round-robin algorithm ensures fair distribution by cycling through available
// servers in sequence, giving each server an equal opportunity to handle requests.
//
class ISrsLbRoundRobin
{
public:
ISrsLbRoundRobin();
virtual ~ISrsLbRoundRobin();
public:
// Select one server from the provided list of servers using the load balancing algorithm.
//
// @param servers A vector of server addresses/URLs to choose from. Must not be empty.
// @return The selected server address/URL as a string.
//
// @remark The implementation should handle the load balancing logic and maintain
// any necessary state to ensure proper distribution across servers.
// @remark Callers must ensure the servers vector is not empty before calling this method.
//
virtual std::string select(const std::vector<std::string> &servers) = 0;
};
// Implementation of round-robin load balance algorithm.
//
// This class provides a concrete implementation of the ISrsLbRoundRobin interface
// that distributes requests across multiple servers using a simple round-robin
// algorithm. It maintains internal state to track the current position in the
// server list and ensures fair distribution by cycling through servers sequentially.
//
class SrsLbRoundRobin : public ISrsLbRoundRobin
{
private:
// current selected index.
int index;
// total scheduled count.
uint32_t count;
// current selected server.
std::string elem;
public:
@ -31,8 +60,11 @@ public:
virtual ~SrsLbRoundRobin();
public:
// Get the current server index.
virtual uint32_t current();
// Get the currently selected server.
virtual std::string selected();
// Select the next server using round-robin algorithm.
virtual std::string select(const std::vector<std::string> &servers);
};

View File

@ -20,6 +20,14 @@ ISrsEncoder::~ISrsEncoder()
{
}
ISrsDecoder::ISrsDecoder()
{
}
ISrsDecoder::~ISrsDecoder()
{
}
ISrsCodec::ISrsCodec()
{
}
@ -468,14 +476,23 @@ srs_error_t SrsBitBuffer::read_bits_ue(uint32_t &v)
return srs_error_new(ERROR_HEVC_NALU_UEV, "empty stream");
}
// Unsigned Exp-Golomb decoding algorithm from ITU-T H.265 specification
// ue(v) in 9.2 Parsing process for Exp-Golomb codes
// ITU-T-H.265-2021.pdf, page 221.
// Syntax elements coded as ue(v), me(v), or se(v) are Exp-Golomb-coded.
// leadingZeroBits = -1;
// for( b = 0; !b; leadingZeroBits++ )
// b = read_bits( 1 )
// The variable codeNum is then assigned as follows:
// codeNum = (2<<leadingZeroBits) - 1 + read_bits( leadingZeroBits )
//
// Algorithm:
// 1. Count leading zero bits until first '1' bit
// 2. Read the '1' bit (prefix)
// 3. Read leadingZeroBits more bits (suffix)
// 4. Calculate: codeNum = (2^leadingZeroBits) - 1 + suffix_value
//
// Examples:
// "1" -> leadingZeroBits=0, suffix=none -> value=0
// "010" -> leadingZeroBits=1, suffix=0 -> value=1
// "011" -> leadingZeroBits=1, suffix=1 -> value=2
// "00100" -> leadingZeroBits=2, suffix=00 -> value=3
// Step 1: Count leading zero bits
int leadingZeroBits = -1;
for (int8_t b = 0; !b && !empty(); leadingZeroBits++) {
b = read_bit();
@ -485,7 +502,10 @@ srs_error_t SrsBitBuffer::read_bits_ue(uint32_t &v)
return srs_error_new(ERROR_HEVC_NALU_UEV, "%dbits overflow 31bits", leadingZeroBits);
}
// Step 2: Calculate base value: (2^leadingZeroBits) - 1
v = (1 << leadingZeroBits) - 1;
// Step 3: Read suffix bits and add to base value
for (int i = 0; i < (int)leadingZeroBits; i++) {
if (empty()) {
return srs_error_new(ERROR_HEVC_NALU_UEV, "no bytes for leadingZeroBits=%d", leadingZeroBits);
@ -506,15 +526,30 @@ srs_error_t SrsBitBuffer::read_bits_se(int32_t &v)
return srs_error_new(ERROR_HEVC_NALU_SEV, "empty stream");
}
// ue(v) in 9.2.1 General Parsing process for Exp-Golomb codes
// ITU-T-H.265-2021.pdf, page 221.
// Signed Exp-Golomb decoding algorithm from ITU-T H.265 specification
// se(v) in 9.2.2 Mapping process for signed Exp-Golomb codes
// ITU-T-H.265-2021.pdf, page 222.
//
// Algorithm:
// 1. First decode as unsigned Exp-Golomb to get codeNum
// 2. Map to signed value using alternating positive/negative pattern:
// - If codeNum is odd: se_value = (codeNum + 1) / 2
// - If codeNum is even: se_value = -(codeNum / 2)
//
// Mapping table:
// codeNum: 0 1 2 3 4 5 6 7 8 ...
// se(v): 0 1 -1 2 -2 3 -3 4 -4 ...
//
// This encoding efficiently represents signed integers with smaller
// absolute values using fewer bits.
// Step 1: Decode unsigned Exp-Golomb value
uint32_t val = 0;
if ((err = read_bits_ue(val)) != srs_success) {
return srs_error_wrap(err, "read uev");
}
// se(v) in 9.2.2 Mapping process for signed Exp-Golomb codes
// ITU-T-H.265-2021.pdf, page 222.
// Step 2: Map unsigned code to signed value
if (val & 0x01) {
v = (val + 1) / 2;
} else {

View File

@ -14,7 +14,28 @@
class SrsBuffer;
// Encoder.
// Abstract interface for encoding objects to binary format.
//
// ISrsEncoder defines the contract for classes that can serialize/encode objects
// into binary data within an SrsBuffer. This interface is commonly implemented by
// protocol handlers, message formatters, and data serializers throughout SRS.
//
// Key responsibilities:
// - Calculate the exact number of bytes needed for encoding
// - Serialize object data into a provided buffer
// - Handle encoding errors gracefully
//
// Usage pattern:
// ISrsEncoder* encoder = create_some_encoder();
// uint64_t required_size = encoder->nb_bytes();
//
// char buffer_data[required_size];
// SrsBuffer buffer(buffer_data, required_size);
//
// srs_error_t err = encoder->encode(&buffer);
// if (err != srs_success) {
// // Handle encoding error
// }
class ISrsEncoder
{
public:
@ -22,215 +43,552 @@ public:
virtual ~ISrsEncoder();
public:
/**
* get the number of bytes to code to.
*/
// Calculate the number of bytes required to encode this object.
// @return The exact number of bytes needed for successful encoding.
// @remark This should return a consistent value for the same object state.
// @remark Implementations must ensure the returned size is accurate to prevent buffer overruns.
virtual uint64_t nb_bytes() = 0;
/**
* encode object to bytes in SrsBuffer.
*/
// Encode this object into the provided buffer.
// @param buf The target buffer to write encoded data into. Must not be NULL.
// @return srs_success on successful encoding, error code on failure.
// @remark Caller must ensure buf has at least nb_bytes() space available.
// @remark The buffer's position will be advanced by the number of bytes written.
// @remark On error, the buffer state is undefined and should not be used.
virtual srs_error_t encode(SrsBuffer *buf) = 0;
};
/**
* the srs codec, to code and decode object with bytes:
* code: to encode/serialize object to bytes in buffer,
* decode: to decode/deserialize object from bytes in buffer.
* we use SrsBuffer as bytes helper utility,
* for example, to code:
* ISrsCodec* obj = ...
* char* bytes = new char[obj->size()];
*
* SrsBuffer* buf = new SrsBuffer();
* buf->initialize(bytes, obj->size())
*
* obj->encode(buf);
* for example, to decode:
* int nb_bytes = ...
* char* bytes = ...
*
* SrsBuffer* buf = new Srsbuffer();
* buf->initialize(bytes, nb_bytes);
*
* ISrsCodec* obj = ...
* obj->decode(buf);
* @remark protocol or amf0 or json should implements this interface.
*/
// TODO: FIXME: protocol, amf0, json should implements it.
class ISrsCodec : public ISrsEncoder
// Abstract interface for decoding/deserializing objects from binary format.
//
// ISrsDecoder defines the contract for classes that can deserialize objects
// from binary data within an SrsBuffer. This interface complements ISrsEncoder
// and is commonly implemented by protocol parsers, message decoders, and data
// deserializers throughout SRS.
//
// Key responsibilities:
// - Deserialize object data from a provided buffer
// - Advance buffer position by the number of bytes consumed
// - Handle decoding errors gracefully
// - Maintain object state consistency during decoding
//
// Usage pattern:
// ISrsDecoder* decoder = create_some_decoder();
// SrsBuffer buffer(received_data, data_size);
//
// srs_error_t err = decoder->decode(&buffer);
// if (err != srs_success) {
// // Handle decoding error
// }
// // Use decoded object...
class ISrsDecoder
{
public:
ISrsDecoder();
virtual ~ISrsDecoder();
public:
// Decode/deserialize object from the provided buffer.
// @param buf The source buffer containing binary data to decode. Must not be NULL.
// @return srs_success on successful decoding, error code on failure.
// @remark The buffer's position will be advanced by the number of bytes consumed.
// @remark On error, both the object state and buffer position are undefined.
// @remark Caller should ensure the buffer contains sufficient data for decoding.
// @remark Implementation should validate input data and handle malformed content gracefully.
virtual srs_error_t decode(SrsBuffer *buf) = 0;
};
// Abstract interface for bidirectional encoding/decoding of objects to/from binary format.
//
// ISrsCodec combines ISrsEncoder and ISrsDecoder to provide both serialization
// (encoding) and deserialization (decoding) capabilities. This interface is the
// foundation for protocol handlers, message parsers, and data format converters
// in SRS that need to both read and write binary data.
//
// Key capabilities:
// - Serialize objects to binary format (inherited from ISrsEncoder)
// - Deserialize objects from binary format (inherited from ISrsDecoder)
// - Calculate exact space requirements for encoding
// - Handle encoding/decoding errors gracefully
// - Ensure symmetric encode/decode operations
//
// Common use cases:
// - Protocol message handlers (RTMP, HTTP, WebRTC, etc.)
// - Media format parsers (AMF0, JSON, FLV, etc.)
// - Configuration and metadata serializers
// - Bidirectional data converters and transformers
//
// Complete workflow example:
// // Encoding workflow
// ISrsCodec* codec = create_some_codec();
// uint64_t size = codec->nb_bytes();
//
// char buffer_data[size];
// SrsBuffer encode_buffer(buffer_data, size);
//
// srs_error_t err = codec->encode(&encode_buffer);
// // Transmit or store encoded data...
//
// // Decoding workflow
// ISrsCodec* decoder = create_same_codec_type();
// SrsBuffer decode_buffer(received_data, data_size);
//
// err = decoder->decode(&decode_buffer);
// // Use decoded object (should match original)...
//
// @remark Protocol handlers, AMF0, and JSON parsers should implement this interface.
// @remark Implementations must ensure encode/decode operations are symmetric.
// @remark The same codec type should be able to decode what it encoded.
// TODO: FIXME: protocol, amf0, json should implement this interface.
class ISrsCodec : public ISrsEncoder, public ISrsDecoder
{
public:
ISrsCodec();
virtual ~ISrsCodec();
public:
// Get the number of bytes to code to.
virtual uint64_t nb_bytes() = 0;
// Encode object to bytes in SrsBuffer.
virtual srs_error_t encode(SrsBuffer *buf) = 0;
public:
// Decode object from bytes in SrsBuffer.
virtual srs_error_t decode(SrsBuffer *buf) = 0;
};
/**
* bytes utility, used to:
* convert basic types to bytes,
* build basic types from bytes.
* @remark the buffer never mange the bytes, user must manage it.
*/
// Byte-level buffer for reading and writing binary data with automatic position tracking.
//
// SrsBuffer provides a convenient interface for reading and writing basic data types
// (integers, strings, byte arrays) to/from a binary buffer. It maintains an internal
// position pointer that automatically advances as data is read or written, making it
// ideal for parsing and constructing binary protocols and file formats.
//
// Key features:
// - Automatic position tracking with read/write operations
// - Support for both big-endian (network byte order) and little-endian data
// - Type-safe reading/writing of 1, 2, 3, 4, and 8-byte integers
// - String and raw byte array operations
// - Buffer bounds checking and validation
// - Position manipulation (skip, reset) for flexible parsing
//
// Usage example:
// char data[1024];
// SrsBuffer buffer(data, sizeof(data));
//
// // Writing data
// buffer.write_4bytes(0x12345678); // Write 32-bit integer
// buffer.write_string("hello"); // Write string
//
// // Reading data (reset position first)
// buffer.skip(-buffer.pos()); // Reset to beginning
// int32_t value = buffer.read_4bytes(); // Read 32-bit integer
// std::string str = buffer.read_string(5); // Read 5-byte string
//
// Memory management:
// - SrsBuffer does NOT take ownership of the provided buffer
// - Caller is responsible for allocating and freeing the buffer memory
// - Buffer must remain valid for the lifetime of the SrsBuffer instance
//
// @remark The buffer never manages the bytes memory, user must manage it.
class SrsBuffer
{
private:
// current position at bytes.
// Current read/write position within the buffer
char *p;
// the bytes data for buffer to read or write.
// Pointer to the start of the buffer data (not owned by this class)
char *bytes;
// the total number of bytes.
// Total size of the buffer in bytes
int nb_bytes;
public:
// Create buffer with data b and size nn.
// @remark User must free the data b.
// Create a buffer wrapper around existing memory.
// @param b Pointer to the buffer data. Must not be NULL and must remain valid
// for the lifetime of this SrsBuffer instance.
// @param nn Size of the buffer in bytes. Must be non-negative.
// @remark User must manage the memory lifecycle of buffer b.
SrsBuffer(char *b, int nn);
~SrsBuffer();
// Destructor. Does not free the underlying buffer memory.
virtual ~SrsBuffer();
public:
// Copy the object, keep position of buffer.
// Create a copy of this buffer with the same position and data reference.
// @return A new SrsBuffer instance pointing to the same data with same position.
// @remark The returned buffer shares the same underlying data pointer.
// @remark Caller is responsible for deleting the returned buffer.
SrsBuffer *copy();
// Get the data and head of buffer.
// current-bytes = head() = data() + pos()
// Get pointer to the start of the buffer data.
// @return Pointer to the beginning of the buffer (same as constructor parameter).
char *data();
// Get pointer to the current position in the buffer.
// @return Pointer to current read/write position (data() + pos()).
char *head();
// Get the total size of buffer.
// left-bytes = size() - pos()
// Get the total size of the buffer.
// @return Total buffer size in bytes.
int size();
// Set the total size of the buffer.
// @param v New size in bytes. Should not exceed the original buffer size.
// @remark Use with caution - does not reallocate memory.
void set_size(int v);
// Get the current buffer position.
// Get the current position within the buffer.
// @return Current offset from the start of the buffer in bytes.
int pos();
// Left bytes in buffer, total size() minus the current pos().
// Get the number of bytes remaining from current position to end of buffer.
// @return Number of unread/unwritten bytes (size() - pos()).
int left();
// Whether buffer is empty.
// Check if the buffer has no remaining bytes.
// @return true if pos() >= size(), false otherwise.
bool empty();
// Whether buffer is able to supply required size of bytes.
// @remark User should check buffer by require then do read/write.
// @remark Assert the required_size is not negative.
// Check if the buffer has enough remaining bytes for a read/write operation.
// @param required_size Number of bytes needed for the operation.
// @return true if left() >= required_size, false otherwise.
// @remark Always call this before read/write operations to avoid buffer overruns.
// @remark Asserts that required_size is non-negative.
bool require(int required_size);
public:
// Skip some size.
// @param size can be any value. positive to forward; negative to backward.
// @remark to skip(pos()) to reset buffer.
// @remark assert initialized, the data() not NULL.
// Move the current position forward or backward within the buffer.
// @param size Number of bytes to skip. Positive values move forward,
// negative values move backward.
// @remark Use skip(-pos()) to reset buffer position to the beginning.
// @remark Asserts that the buffer is initialized (data() is not NULL).
// @remark Does not perform bounds checking - caller must ensure valid position.
void skip(int size);
public:
// Read 1bytes char from buffer.
// Read a single byte from the buffer and advance position.
// @return The byte value as a signed 8-bit integer.
// @remark Caller must ensure require(1) before calling.
int8_t read_1bytes();
// Read 2bytes int from buffer.
// Read a 2-byte integer from the buffer in big-endian (network) byte order.
// @return The 16-bit integer value.
// @remark Caller must ensure require(2) before calling.
int16_t read_2bytes();
// Read a 2-byte integer from the buffer in little-endian byte order.
// @return The 16-bit integer value.
// @remark Caller must ensure require(2) before calling.
int16_t read_le2bytes();
// Read 3bytes int from buffer.
// Read a 3-byte integer from the buffer in big-endian byte order.
// @return The 24-bit value as a 32-bit integer (high byte is 0).
// @remark Caller must ensure require(3) before calling.
int32_t read_3bytes();
// Read a 3-byte integer from the buffer in little-endian byte order.
// @return The 24-bit value as a 32-bit integer (high byte is 0).
// @remark Caller must ensure require(3) before calling.
int32_t read_le3bytes();
// Read 4bytes int from buffer.
// Read a 4-byte integer from the buffer in big-endian (network) byte order.
// @return The 32-bit integer value.
// @remark Caller must ensure require(4) before calling.
int32_t read_4bytes();
// Read a 4-byte integer from the buffer in little-endian byte order.
// @return The 32-bit integer value.
// @remark Caller must ensure require(4) before calling.
int32_t read_le4bytes();
// Read 8bytes int from buffer.
// Read an 8-byte integer from the buffer in big-endian (network) byte order.
// @return The 64-bit integer value.
// @remark Caller must ensure require(8) before calling.
int64_t read_8bytes();
// Read an 8-byte integer from the buffer in little-endian byte order.
// @return The 64-bit integer value.
// @remark Caller must ensure require(8) before calling.
int64_t read_le8bytes();
// Read string from buffer, length specifies by param len.
// Read a string of specified length from the buffer.
// @param len Number of bytes to read as string data.
// @return String containing the read bytes.
// @remark Caller must ensure require(len) before calling.
// @remark Does not assume null-termination - reads exactly len bytes.
std::string read_string(int len);
// Read bytes from buffer, length specifies by param len.
// Read raw bytes from the buffer into a provided array.
// @param data Destination buffer to copy bytes into. Must not be NULL.
// @param size Number of bytes to read and copy.
// @remark Caller must ensure require(size) before calling.
// @remark Caller must ensure data buffer has at least size bytes available.
void read_bytes(char *data, int size);
public:
// Write 1bytes char to buffer.
// Write a single byte to the buffer and advance position.
// @param value The byte value to write.
// @remark Caller must ensure require(1) before calling.
void write_1bytes(int8_t value);
// Write 2bytes int to buffer.
// Write a 2-byte integer to the buffer in big-endian (network) byte order.
// @param value The 16-bit integer value to write.
// @remark Caller must ensure require(2) before calling.
void write_2bytes(int16_t value);
// Write a 2-byte integer to the buffer in little-endian byte order.
// @param value The 16-bit integer value to write.
// @remark Caller must ensure require(2) before calling.
void write_le2bytes(int16_t value);
// Write 4bytes int to buffer.
// Write a 4-byte integer to the buffer in big-endian (network) byte order.
// @param value The 32-bit integer value to write.
// @remark Caller must ensure require(4) before calling.
void write_4bytes(int32_t value);
// Write a 4-byte integer to the buffer in little-endian byte order.
// @param value The 32-bit integer value to write.
// @remark Caller must ensure require(4) before calling.
void write_le4bytes(int32_t value);
// Write 3bytes int to buffer.
// Write a 3-byte integer to the buffer in big-endian byte order.
// @param value The 24-bit value to write (high byte ignored).
// @remark Caller must ensure require(3) before calling.
void write_3bytes(int32_t value);
// Write a 3-byte integer to the buffer in little-endian byte order.
// @param value The 24-bit value to write (high byte ignored).
// @remark Caller must ensure require(3) before calling.
void write_le3bytes(int32_t value);
// Write 8bytes int to buffer.
// Write an 8-byte integer to the buffer in big-endian (network) byte order.
// @param value The 64-bit integer value to write.
// @remark Caller must ensure require(8) before calling.
void write_8bytes(int64_t value);
// Write an 8-byte integer to the buffer in little-endian byte order.
// @param value The 64-bit integer value to write.
// @remark Caller must ensure require(8) before calling.
void write_le8bytes(int64_t value);
// Write string to buffer
// Write a string to the buffer as raw bytes.
// @param value The string to write. All bytes of the string are written.
// @remark Caller must ensure require(value.length()) before calling.
// @remark Does not write null terminator - only the string content.
void write_string(std::string value);
// Write bytes to buffer
// Write raw bytes from an array to the buffer.
// @param data Source buffer containing bytes to write. Must not be NULL.
// @param size Number of bytes to write from the source buffer.
// @remark Caller must ensure require(size) before calling.
// @remark Caller must ensure data buffer contains at least size bytes.
void write_bytes(char *data, int size);
};
/**
* the bit buffer, base on SrsBuffer,
* for exmaple, the h.264 avc buffer is bit buffer.
*/
// Bit-level buffer reader for parsing binary data at bit granularity.
//
// SrsBitBuffer provides bit-level access to binary data, commonly used for parsing
// video codec bitstreams like H.264/AVC, H.265/HEVC, and other formats that require
// bit-precise parsing. It wraps an SrsBuffer and maintains internal state to track
// partial byte consumption.
//
// Key features:
// - Bit-level reading with automatic byte boundary handling
// - Optimized fast paths for byte-aligned operations (8, 16, 32-bit reads)
// - Support for Exp-Golomb encoding used in H.264/H.265 standards
// - Efficient buffering with minimal memory overhead
//
// Usage example:
// char data[] = {0x12, 0x34, 0x56, 0x78};
// SrsBuffer buffer(data, 4);
// SrsBitBuffer bb(&buffer);
//
// int bit = bb.read_bit(); // Read single bit
// int nibble = bb.read_bits(4); // Read 4 bits
// int byte_val = bb.read_8bits(); // Read 8 bits (optimized)
//
// uint32_t ue_val;
// bb.read_bits_ue(ue_val); // Read unsigned Exp-Golomb
//
// @remark The underlying SrsBuffer must remain valid for the lifetime of SrsBitBuffer.
// @remark This class does not take ownership of the SrsBuffer pointer.
class SrsBitBuffer
{
private:
// Current byte being processed (cached from stream)
int8_t cb;
// Number of unread bits remaining in current byte (0-8)
uint8_t cb_left;
// Underlying byte buffer (not owned by this class)
SrsBuffer *stream;
public:
// Construct a bit buffer from an existing byte buffer.
// @param b The underlying SrsBuffer to read from. Must not be NULL and must
// remain valid for the lifetime of this SrsBitBuffer instance.
SrsBitBuffer(SrsBuffer *b);
~SrsBitBuffer();
// Destructor. Does not free the underlying SrsBuffer.
virtual ~SrsBitBuffer();
public:
// Check if the bit buffer is empty (no more bits to read).
// @return true if no more bits are available, false otherwise.
bool empty();
// Read a single bit from the buffer.
// @return The bit value (0 or 1).
// @remark Caller must ensure !empty() before calling.
int8_t read_bit();
// Check if the specified number of bits are available for reading.
// @param n Number of bits to check for availability. Must be non-negative.
// @return true if n bits are available, false otherwise.
bool require_bits(int n);
// Get the number of bits remaining in the buffer.
// @return Total number of unread bits available.
int left_bits();
// Skip the specified number of bits without reading their values.
// @param n Number of bits to skip. Must be <= left_bits().
void skip_bits(int n);
// Read the specified number of bits as an integer value.
// @param n Number of bits to read (1-32). Must be <= left_bits().
// @return The bits interpreted as an unsigned integer, MSB first.
int32_t read_bits(int n);
// Read 8 bits as a byte value.
// Uses optimized fast path when byte-aligned, falls back to read_bits(8) otherwise.
// @return The 8-bit value.
// @remark Caller must ensure at least 8 bits are available.
int8_t read_8bits();
// Read 16 bits as a short value.
// Uses optimized fast path when byte-aligned, falls back to read_bits(16) otherwise.
// @return The 16-bit value in network byte order.
// @remark Caller must ensure at least 16 bits are available.
int16_t read_16bits();
// Read 32 bits as an integer value.
// Uses optimized fast path when byte-aligned, falls back to read_bits(32) otherwise.
// @return The 32-bit value in network byte order.
// @remark Caller must ensure at least 32 bits are available.
int32_t read_32bits();
// Read an unsigned Exp-Golomb encoded value.
//
// Implements the ue(v) parsing algorithm from ITU-T H.265 specification:
// - Count leading zero bits (leadingZeroBits)
// - Read 1 bit (must be 1)
// - Read leadingZeroBits more bits
// - Calculate: codeNum = (2^leadingZeroBits) - 1 + read_bits(leadingZeroBits)
//
// Common in H.264/H.265 for encoding non-negative integers with variable length.
// Smaller values use fewer bits.
//
// @param v Output parameter to store the decoded unsigned value.
// @return srs_success on success, error code on failure (empty buffer, overflow).
srs_error_t read_bits_ue(uint32_t &v);
// Read a signed Exp-Golomb encoded value.
//
// Implements the se(v) parsing algorithm from ITU-T H.265 specification:
// - First reads ue(v) to get unsigned code
// - Maps to signed value: if (code & 1) result = (code + 1) / 2; else result = -(code / 2)
//
// This encoding alternates between positive and negative values:
// se(0)=0, se(1)=1, se(-1)=-1, se(2)=2, se(-2)=-2, etc.
//
// @param v Output parameter to store the decoded signed value.
// @return srs_success on success, error code on failure (propagated from read_bits_ue).
srs_error_t read_bits_se(int32_t &v);
};
// Memory block for shared payload data.
// This class encapsulates a memory buffer with size information,
// designed to be used with SrsSharedPtr for efficient memory sharing.
// Memory block for managing shared payload data with automatic lifecycle management.
//
// SrsMemoryBlock encapsulates a dynamically allocated memory buffer with size tracking,
// specifically designed for use with SrsSharedPtr to enable efficient memory sharing
// across multiple components without unnecessary copying. This is commonly used for
// message payloads, media data, and other binary content in SRS.
//
// Key features:
// - Automatic memory lifecycle management (allocation and deallocation)
// - Support for both copying and taking ownership of existing buffers
// - Size tracking for safe buffer operations
// - Designed for shared ownership via SrsSharedPtr
// - Suitable for chunked data that may arrive in multiple parts
//
// Usage patterns:
//
// Creating new memory block:
// SrsSharedPtr<SrsMemoryBlock> block(new SrsMemoryBlock());
// block->create(1024); // Allocate 1KB
// memcpy(block->payload(), data, data_size);
//
// Copying existing data:
// SrsSharedPtr<SrsMemoryBlock> block(new SrsMemoryBlock());
// block->create(source_data, source_size); // Copies the data
//
// Taking ownership of existing buffer:
// char* buffer = malloc(size); // Caller allocates
// SrsSharedPtr<SrsMemoryBlock> block(new SrsMemoryBlock());
// block->attach(buffer, size); // Takes ownership, caller must not free
//
// Shared usage:
// SrsSharedPtr<SrsMemoryBlock> block1 = original_block;
// SrsSharedPtr<SrsMemoryBlock> block2 = original_block;
// // Both share the same underlying memory
//
// @remark Not all payload data can be decoded to structured packets - some data
// (like video/audio packets) may remain as raw bytes.
// @remark The size may be less than the allocated buffer size for chunked data.
class SrsMemoryBlock
{
private:
// The current message parsed size,
// size <= allocated buffer size
// For the payload maybe sent in multiple chunks.
// Current size of valid data in the buffer.
// This may be less than the allocated buffer size for chunked data
// that arrives in multiple parts.
int size_;
// The payload of message, the SrsMemoryBlock manages the memory lifecycle.
// @remark, not all message payload can be decoded to packet. for example,
// video/audio packet use raw bytes, no video/audio packet.
// Pointer to the allocated memory buffer.
// SrsMemoryBlock owns this memory and is responsible for its lifecycle.
// The buffer contains the actual payload data.
char *payload_;
public:
// Construct an empty memory block.
// Call create() or attach() to initialize with actual memory.
SrsMemoryBlock();
// Destructor automatically frees the managed memory.
// Safe to call even if no memory was allocated.
virtual ~SrsMemoryBlock();
public:
// Get direct access to the payload buffer.
// @return Pointer to the memory buffer, or NULL if not initialized.
// @remark Caller should not free this pointer - it's managed by SrsMemoryBlock.
// @remark The returned pointer is valid until the SrsMemoryBlock is destroyed.
char *payload() { return payload_; }
// Get the current size of valid data in the buffer.
// @return Number of bytes of valid data, may be 0 if not initialized.
// @remark This may be less than the allocated buffer size for chunked data.
int size() { return size_; }
public:
// Create memory block with specified size.
// @param size, the size of memory to allocate. Must be non-negative.
// Allocate a new memory buffer of the specified size.
// @param size Number of bytes to allocate. Must be non-negative.
// @remark Any existing buffer is freed before allocating the new one.
// @remark The allocated memory is uninitialized - caller should fill it as needed.
// @remark If size is 0, creates a valid but empty memory block.
virtual void create(int size);
// Create memory block from existing buffer.
// @param data, the buffer to copy from.
// @param size, the size of buffer. Must be non-negative.
// @remark, this method will copy the data.
// Create a memory buffer by copying data from an existing buffer.
// @param data Source buffer to copy from. Must not be NULL if size > 0.
// @param size Number of bytes to copy. Must be non-negative.
// @remark Any existing buffer is freed before creating the new one.
// @remark This method creates an independent copy - changes to source won't affect this block.
// @remark If size is 0, creates a valid but empty memory block.
virtual void create(char *data, int size);
// Attach existing buffer to memory block.
// @param data, the buffer to attach, memory block takes ownership.
// @param size, the size of buffer. Must be non-negative.
// @remark, this method takes ownership of data, caller should not free it.
// Take ownership of an existing buffer without copying.
// @param data Buffer to take ownership of. Must be allocated with malloc/new[].
// @param size Size of the buffer in bytes. Must be non-negative.
// @remark Any existing buffer is freed before attaching the new one.
// @remark This method takes ownership - caller must NOT free the data pointer.
// @remark The provided buffer will be freed with delete[] when this object is destroyed.
// @remark If data is NULL and size is 0, creates a valid but empty memory block.
virtual void attach(char *data, int size);
};

View File

@ -19,6 +19,68 @@ VOID TEST(CoreAutoFreeTest, Free)
EXPECT_TRUE(data == NULL);
}
VOID TEST(CoreAutoFreeTest, FreepPointer)
{
int *ptr = new int(42);
EXPECT_TRUE(ptr != NULL);
EXPECT_EQ(42, *ptr);
srs_freep(ptr);
EXPECT_TRUE(ptr == NULL);
}
VOID TEST(CoreAutoFreeTest, FreepObject)
{
MyNormalObject *obj = new MyNormalObject(100);
EXPECT_TRUE(obj != NULL);
EXPECT_EQ(100, obj->id());
srs_freep(obj);
EXPECT_TRUE(obj == NULL);
}
VOID TEST(CoreAutoFreeTest, FreepNullPointer)
{
int *ptr = NULL;
srs_freep(ptr); // Should not crash
EXPECT_TRUE(ptr == NULL);
}
VOID TEST(CoreAutoFreeTest, FreepaArray)
{
int *arr = new int[10];
for (int i = 0; i < 10; i++) {
arr[i] = i * 2;
}
EXPECT_TRUE(arr != NULL);
EXPECT_EQ(0, arr[0]);
EXPECT_EQ(18, arr[9]);
srs_freepa(arr);
EXPECT_TRUE(arr == NULL);
}
VOID TEST(CoreAutoFreeTest, FreepaNullArray)
{
int *arr = NULL;
srs_freepa(arr); // Should not crash
EXPECT_TRUE(arr == NULL);
}
VOID TEST(CoreAutoFreeTest, FreepaCharArray)
{
char *chars = new char[256];
for (int i = 0; i < 10; i++) {
chars[i] = 'a' + i;
}
chars[10] = '\0';
EXPECT_TRUE(chars != NULL);
EXPECT_STREQ("abcdefghij", chars);
srs_freepa(chars);
EXPECT_TRUE(chars == NULL);
}
VOID TEST(CoreMacroseTest, Check)
{
#ifndef SRS_BUILD_TS
@ -75,11 +137,15 @@ VOID TEST(CoreLogger, CheckVsnprintf)
EXPECT_EQ(4, snprintf(buf, sizeof(buf), "Hell"));
EXPECT_STREQ("Hell", buf);
// Test intentional truncation - suppress warning as this is expected behavior
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-truncation"
EXPECT_EQ(5, snprintf(buf, sizeof(buf), "Hello"));
EXPECT_STREQ("Hell", buf);
EXPECT_EQ(10, snprintf(buf, sizeof(buf), "HelloWorld"));
EXPECT_STREQ("Hell", buf);
#pragma clang diagnostic pop
}
}
@ -342,6 +408,95 @@ VOID TEST(CoreSmartPtr, SharedPtrMove)
}
}
VOID TEST(CoreSmartPtr, SharedPtrResetMethod)
{
if (true) {
SrsSharedPtr<int> p(new int(100));
EXPECT_TRUE(p);
EXPECT_EQ(100, *p);
p.reset();
EXPECT_FALSE(p);
EXPECT_TRUE(p.get() == NULL);
}
if (true) {
SrsSharedPtr<MyNormalObject> p(new MyNormalObject(200));
EXPECT_TRUE(p);
EXPECT_EQ(200, p->id());
p.reset();
EXPECT_FALSE(p);
EXPECT_TRUE(p.get() == NULL);
}
}
VOID TEST(CoreSmartPtr, SharedPtrSelfAssignment)
{
if (true) {
SrsSharedPtr<int> p(new int(100));
int *original_ptr = p.get();
// Test self assignment - suppress warning as this is intentional
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wself-assign-overloaded"
p = p; // Self assignment
#pragma clang diagnostic pop
EXPECT_EQ(original_ptr, p.get());
EXPECT_EQ(100, *p);
}
}
VOID TEST(CoreSmartPtr, SharedPtrMultipleReferences)
{
int *ptr = new int(100);
SrsUniquePtr<int> ptr_uptr(ptr);
EXPECT_EQ(100, *ptr);
if (true) {
SrsSharedPtr<MockWrapper> p1(new MockWrapper(ptr));
EXPECT_EQ(101, *ptr);
SrsSharedPtr<MockWrapper> p2 = p1;
EXPECT_EQ(101, *ptr);
SrsSharedPtr<MockWrapper> p3(p1);
EXPECT_EQ(101, *ptr);
SrsSharedPtr<MockWrapper> p4(NULL);
p4 = p1;
EXPECT_EQ(101, *ptr);
// All should point to the same object
EXPECT_EQ(p1.get(), p2.get());
EXPECT_EQ(p1.get(), p3.get());
EXPECT_EQ(p1.get(), p4.get());
}
EXPECT_EQ(100, *ptr); // All references gone, object destroyed
}
VOID TEST(CoreSmartPtr, SharedPtrBoolOperator)
{
if (true) {
SrsSharedPtr<int> p(new int(42));
EXPECT_TRUE(p);
EXPECT_TRUE(static_cast<bool>(p));
}
if (true) {
SrsSharedPtr<int> p(NULL);
EXPECT_FALSE(p);
EXPECT_FALSE(static_cast<bool>(p));
}
if (true) {
SrsSharedPtr<int> p(new int(42));
EXPECT_TRUE(p);
p.reset();
EXPECT_FALSE(p);
}
}
class MockIntResource : public ISrsResource
{
public:
@ -450,6 +605,87 @@ VOID TEST(CoreSmartPtr, SharedResourceMove)
}
}
VOID TEST(CoreSmartPtr, SharedResourceNullPointer)
{
if (true) {
SrsSharedResource<MockIntResource> p(NULL);
EXPECT_FALSE(p);
EXPECT_TRUE(p.get() == NULL);
}
}
VOID TEST(CoreSmartPtr, SharedResourceSelfAssignment)
{
if (true) {
SrsSharedResource<MockIntResource> p(new MockIntResource(100));
MockIntResource *original_ptr = p.get();
// Test self assignment - suppress warning as this is intentional
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wself-assign-overloaded"
p = p; // Self assignment
#pragma clang diagnostic pop
EXPECT_EQ(original_ptr, p.get());
EXPECT_EQ(100, p->value_);
}
}
VOID TEST(CoreSmartPtr, SharedResourceBoolOperator)
{
if (true) {
SrsSharedResource<MockIntResource> p(new MockIntResource(42));
EXPECT_TRUE(p);
EXPECT_TRUE(static_cast<bool>(p));
}
if (true) {
SrsSharedResource<MockIntResource> p(NULL);
EXPECT_FALSE(p);
EXPECT_FALSE(static_cast<bool>(p));
}
}
VOID TEST(CoreSmartPtr, SharedResourceISrsResourceInterface)
{
if (true) {
SrsSharedResource<MockIntResource> *p = new SrsSharedResource<MockIntResource>(new MockIntResource(100));
// Test ISrsResource interface
const SrsContextId &id = p->get_id();
EXPECT_TRUE(id.empty());
std::string desc = p->desc();
EXPECT_TRUE(desc.empty());
// Test access to wrapped object
EXPECT_EQ(100, (*p)->value_);
EXPECT_EQ(100, p->get()->value_);
srs_freep(p);
}
}
VOID TEST(CoreSmartPtr, SharedResourceMultipleReferences)
{
if (true) {
SrsSharedResource<MockIntResource> p1(new MockIntResource(200));
SrsSharedResource<MockIntResource> p2 = p1;
SrsSharedResource<MockIntResource> p3(p1);
// All should point to the same object
EXPECT_EQ(p1.get(), p2.get());
EXPECT_EQ(p1.get(), p3.get());
EXPECT_EQ(200, p1->value_);
EXPECT_EQ(200, p2->value_);
EXPECT_EQ(200, p3->value_);
// Modify through one reference, should be visible through all
p1->value_ = 300;
EXPECT_EQ(300, p2->value_);
EXPECT_EQ(300, p3->value_);
}
}
VOID TEST(CoreSmartPtr, UniquePtrNormal)
{
if (true) {
@ -631,3 +867,161 @@ VOID TEST(CoreSmartPtr, UniquePtrDeleterMalloc)
}
EXPECT_TRUE(p.bytes_ == NULL);
}
VOID TEST(CoreSmartPtr, UniquePtrNullPointer)
{
if (true) {
SrsUniquePtr<int> ptr(NULL);
EXPECT_TRUE(ptr.get() == NULL);
}
if (true) {
SrsUniquePtr<MyNormalObject> ptr(NULL);
EXPECT_TRUE(ptr.get() == NULL);
}
}
VOID TEST(CoreSmartPtr, UniquePtrGetMethod)
{
if (true) {
int *raw_ptr = new int(42);
SrsUniquePtr<int> ptr(raw_ptr);
EXPECT_EQ(raw_ptr, ptr.get());
EXPECT_EQ(42, *ptr.get());
}
if (true) {
MyNormalObject *raw_obj = new MyNormalObject(100);
SrsUniquePtr<MyNormalObject> ptr(raw_obj);
EXPECT_EQ(raw_obj, ptr.get());
EXPECT_EQ(100, ptr.get()->id());
}
}
VOID TEST(CoreSmartPtr, UniquePtrArrowOperator)
{
if (true) {
MyNormalObject *raw_obj = new MyNormalObject(200);
SrsUniquePtr<MyNormalObject> ptr(raw_obj);
EXPECT_EQ(200, ptr->id());
}
}
VOID TEST(CoreSmartPtr, UniquePtrArrayIndexOperator)
{
if (true) {
int *arr = new int[5];
for (int i = 0; i < 5; i++) {
arr[i] = i * 10;
}
SrsUniquePtr<int[]> ptr(arr);
EXPECT_EQ(0, ptr[0]);
EXPECT_EQ(10, ptr[1]);
EXPECT_EQ(40, ptr[4]);
}
}
VOID TEST(CoreSmartPtr, UniquePtrArrayConstIndexOperator)
{
if (true) {
int *arr = new int[3];
arr[0] = 100;
arr[1] = 200;
arr[2] = 300;
const SrsUniquePtr<int[]> ptr(arr);
EXPECT_EQ(100, ptr[0]);
EXPECT_EQ(200, ptr[1]);
EXPECT_EQ(300, ptr[2]);
}
}
VOID TEST(CoreSmartPtr, SmartPointerEdgeCases)
{
// Test SrsUniquePtr with custom deleter and NULL pointer
if (true) {
SrsUniquePtr<char> ptr(NULL, mock_free_chars);
EXPECT_TRUE(ptr.get() == NULL);
// Should not crash when destroyed with NULL pointer
}
// Test SrsSharedPtr with NULL pointer in copy constructor
if (true) {
SrsSharedPtr<int> p1(NULL);
SrsSharedPtr<int> p2(p1);
EXPECT_FALSE(p1);
EXPECT_FALSE(p2);
EXPECT_EQ(p1.get(), p2.get());
}
// Test SrsSharedPtr with NULL pointer in assignment
if (true) {
SrsSharedPtr<int> p1(NULL);
SrsSharedPtr<int> p2(new int(42));
EXPECT_TRUE(p2);
p2 = p1;
EXPECT_FALSE(p1);
EXPECT_FALSE(p2);
}
// Test SrsSharedResource with NULL pointer in copy constructor
if (true) {
SrsSharedResource<MockIntResource> p1(NULL);
SrsSharedResource<MockIntResource> p2(p1);
EXPECT_FALSE(p1);
EXPECT_FALSE(p2);
EXPECT_EQ(p1.get(), p2.get());
}
}
VOID TEST(CoreSmartPtr, SmartPointerMemoryManagement)
{
// Test that SrsUniquePtr properly manages memory
int *counter = new int(0);
SrsUniquePtr<int> counter_uptr(counter);
if (true) {
SrsUniquePtr<MockWrapper> ptr(new MockWrapper(counter));
EXPECT_EQ(1, *counter);
}
EXPECT_EQ(0, *counter); // MockWrapper destructor should have decremented
// Test that SrsSharedPtr properly manages reference counting
if (true) {
SrsSharedPtr<MockWrapper> p1(new MockWrapper(counter));
EXPECT_EQ(1, *counter);
if (true) {
SrsSharedPtr<MockWrapper> p2 = p1;
EXPECT_EQ(1, *counter); // Same object, counter unchanged
SrsSharedPtr<MockWrapper> p3(new MockWrapper(counter));
EXPECT_EQ(2, *counter); // New object created
}
EXPECT_EQ(1, *counter); // p3 destroyed, one object remains
}
EXPECT_EQ(0, *counter); // All objects destroyed
}
VOID TEST(CoreSmartPtr, SmartPointerOperatorOverloads)
{
// Test SrsSharedPtr dereference operator
if (true) {
SrsSharedPtr<int> ptr(new int(42));
EXPECT_EQ(42, *ptr);
*ptr = 100;
EXPECT_EQ(100, *ptr);
}
// Test SrsSharedResource dereference operator
if (true) {
SrsSharedResource<MockIntResource> ptr(new MockIntResource(200));
EXPECT_EQ(200, (*ptr).value_);
(*ptr).value_ = 300;
EXPECT_EQ(300, (*ptr).value_);
}
}

View File

@ -3321,6 +3321,327 @@ VOID TEST(KernelLBRRTest, CoverAll)
}
}
VOID TEST(KernelLBRRTest, InterfaceTest)
{
if (true) {
vector<string> servers;
servers.push_back("s0");
servers.push_back("s1");
servers.push_back("s2");
SrsUniquePtr<ISrsLbRoundRobin> lb(new SrsLbRoundRobin());
// Test round-robin behavior through interface
EXPECT_TRUE("s0" == lb->select(servers));
EXPECT_TRUE("s1" == lb->select(servers));
EXPECT_TRUE("s2" == lb->select(servers));
EXPECT_TRUE("s0" == lb->select(servers)); // Should wrap around
}
}
VOID TEST(KernelLBRRTest, SingleServer)
{
if (true) {
vector<string> servers;
servers.push_back("single-server");
SrsLbRoundRobin lb;
// Test with single server - should always return the same server
EXPECT_TRUE("single-server" == lb.select(servers));
EXPECT_EQ(0, (int)lb.current());
EXPECT_TRUE("single-server" == lb.selected());
EXPECT_TRUE("single-server" == lb.select(servers));
EXPECT_EQ(0, (int)lb.current());
EXPECT_TRUE("single-server" == lb.selected());
EXPECT_TRUE("single-server" == lb.select(servers));
EXPECT_EQ(0, (int)lb.current());
EXPECT_TRUE("single-server" == lb.selected());
}
}
VOID TEST(KernelLBRRTest, TwoServers)
{
if (true) {
vector<string> servers;
servers.push_back("server-a");
servers.push_back("server-b");
SrsLbRoundRobin lb;
// Test alternating between two servers
EXPECT_TRUE("server-a" == lb.select(servers));
EXPECT_EQ(0, (int)lb.current());
EXPECT_TRUE("server-a" == lb.selected());
EXPECT_TRUE("server-b" == lb.select(servers));
EXPECT_EQ(1, (int)lb.current());
EXPECT_TRUE("server-b" == lb.selected());
EXPECT_TRUE("server-a" == lb.select(servers));
EXPECT_EQ(0, (int)lb.current());
EXPECT_TRUE("server-a" == lb.selected());
EXPECT_TRUE("server-b" == lb.select(servers));
EXPECT_EQ(1, (int)lb.current());
EXPECT_TRUE("server-b" == lb.selected());
}
}
VOID TEST(KernelLBRRTest, LargeServerList)
{
if (true) {
vector<string> servers;
// Create a list of 100 servers
for (int i = 0; i < 100; i++) {
char server_name[32];
snprintf(server_name, sizeof(server_name), "server-%d", i);
servers.push_back(string(server_name));
}
SrsLbRoundRobin lb;
// Test that we cycle through all servers correctly
for (int round = 0; round < 3; round++) {
for (int i = 0; i < 100; i++) {
char expected_name[32];
snprintf(expected_name, sizeof(expected_name), "server-%d", i);
string selected = lb.select(servers);
EXPECT_TRUE(expected_name == selected);
EXPECT_EQ(i, (int)lb.current());
EXPECT_TRUE(expected_name == lb.selected());
}
}
}
}
VOID TEST(KernelLBRRTest, LongRunningTest)
{
if (true) {
vector<string> servers;
servers.push_back("s0");
servers.push_back("s1");
servers.push_back("s2");
SrsLbRoundRobin lb;
// Test long running behavior to ensure counter doesn't cause issues
// This simulates a long-running server that has been selecting for a while
const int large_iterations = 100000;
for (int i = 0; i < large_iterations; i++) {
string selected = lb.select(servers);
int expected_index = i % 3;
char expected_name[8];
snprintf(expected_name, sizeof(expected_name), "s%d", expected_index);
EXPECT_TRUE(expected_name == selected);
EXPECT_EQ(expected_index, (int)lb.current());
// Only check every 10000 iterations to avoid too much output
if (i % 10000 == 0) {
EXPECT_TRUE(expected_name == lb.selected());
}
}
}
}
VOID TEST(KernelLBRRTest, DifferentServerFormats)
{
if (true) {
vector<string> servers;
servers.push_back("192.168.1.1:1935");
servers.push_back("rtmp://example.com/live");
servers.push_back("server.domain.com:8080");
servers.push_back("localhost");
servers.push_back("10.0.0.1:443");
SrsLbRoundRobin lb;
// Test with different server name formats
EXPECT_TRUE("192.168.1.1:1935" == lb.select(servers));
EXPECT_EQ(0, (int)lb.current());
EXPECT_TRUE("rtmp://example.com/live" == lb.select(servers));
EXPECT_EQ(1, (int)lb.current());
EXPECT_TRUE("server.domain.com:8080" == lb.select(servers));
EXPECT_EQ(2, (int)lb.current());
EXPECT_TRUE("localhost" == lb.select(servers));
EXPECT_EQ(3, (int)lb.current());
EXPECT_TRUE("10.0.0.1:443" == lb.select(servers));
EXPECT_EQ(4, (int)lb.current());
// Should wrap around
EXPECT_TRUE("192.168.1.1:1935" == lb.select(servers));
EXPECT_EQ(0, (int)lb.current());
}
}
VOID TEST(KernelLBRRTest, MultipleInstances)
{
if (true) {
vector<string> servers;
servers.push_back("s0");
servers.push_back("s1");
servers.push_back("s2");
SrsLbRoundRobin lb1;
SrsLbRoundRobin lb2;
// Test that multiple instances work independently
EXPECT_TRUE("s0" == lb1.select(servers));
EXPECT_TRUE("s0" == lb2.select(servers));
EXPECT_TRUE("s1" == lb1.select(servers));
EXPECT_TRUE("s1" == lb2.select(servers));
// Advance lb1 further
EXPECT_TRUE("s2" == lb1.select(servers));
EXPECT_TRUE("s0" == lb1.select(servers));
// lb2 should still be at s2
EXPECT_TRUE("s2" == lb2.select(servers));
EXPECT_EQ(0, (int)lb1.current());
EXPECT_EQ(2, (int)lb2.current());
}
}
VOID TEST(KernelLBRRTest, StressTest)
{
if (true) {
vector<string> servers;
servers.push_back("s0");
servers.push_back("s1");
servers.push_back("s2");
servers.push_back("s3");
servers.push_back("s4");
SrsLbRoundRobin lb;
// Perform many selections to test stability
const int iterations = 10000;
for (int i = 0; i < iterations; i++) {
string selected = lb.select(servers);
int expected_index = i % 5;
char expected_name[8];
snprintf(expected_name, sizeof(expected_name), "s%d", expected_index);
EXPECT_TRUE(expected_name == selected);
EXPECT_EQ(expected_index, (int)lb.current());
EXPECT_TRUE(expected_name == lb.selected());
}
}
}
VOID TEST(KernelLBRRTest, InterfacePolymorphism)
{
if (true) {
vector<string> servers;
servers.push_back("server-a");
servers.push_back("server-b");
servers.push_back("server-c");
// Test polymorphism - using interface pointer to concrete implementation
SrsUniquePtr<ISrsLbRoundRobin> lb_interface(new SrsLbRoundRobin());
SrsLbRoundRobin lb_concrete;
// Both should behave identically
for (int i = 0; i < 6; i++) {
string interface_result = lb_interface->select(servers);
string concrete_result = lb_concrete.select(servers);
EXPECT_TRUE(interface_result == concrete_result);
int expected_index = i % 3;
char expected_name[16];
snprintf(expected_name, sizeof(expected_name), "server-%c", 'a' + expected_index);
EXPECT_TRUE(expected_name == interface_result);
EXPECT_TRUE(expected_name == concrete_result);
}
}
}
VOID TEST(KernelLBRRTest, SpecialServerNames)
{
if (true) {
vector<string> servers;
servers.push_back(""); // Empty string
servers.push_back("server with spaces");
servers.push_back("server-with-dashes");
servers.push_back("server_with_underscores");
servers.push_back("server.with.dots");
servers.push_back("server:with:colons");
servers.push_back("192.168.1.1");
servers.push_back("::1"); // IPv6 localhost
SrsLbRoundRobin lb;
// Test with special characters and formats
EXPECT_TRUE("" == lb.select(servers));
EXPECT_EQ(0, (int)lb.current());
EXPECT_TRUE("server with spaces" == lb.select(servers));
EXPECT_EQ(1, (int)lb.current());
EXPECT_TRUE("server-with-dashes" == lb.select(servers));
EXPECT_EQ(2, (int)lb.current());
EXPECT_TRUE("server_with_underscores" == lb.select(servers));
EXPECT_EQ(3, (int)lb.current());
EXPECT_TRUE("server.with.dots" == lb.select(servers));
EXPECT_EQ(4, (int)lb.current());
EXPECT_TRUE("server:with:colons" == lb.select(servers));
EXPECT_EQ(5, (int)lb.current());
EXPECT_TRUE("192.168.1.1" == lb.select(servers));
EXPECT_EQ(6, (int)lb.current());
EXPECT_TRUE("::1" == lb.select(servers));
EXPECT_EQ(7, (int)lb.current());
// Should wrap around to empty string
EXPECT_TRUE("" == lb.select(servers));
EXPECT_EQ(0, (int)lb.current());
}
}
VOID TEST(KernelLBRRTest, ConsistentState)
{
if (true) {
vector<string> servers;
servers.push_back("s0");
servers.push_back("s1");
servers.push_back("s2");
SrsLbRoundRobin lb;
// Test that current() and selected() remain consistent after each select()
for (int i = 0; i < 10; i++) {
string selected = lb.select(servers);
uint32_t current_index = lb.current();
string current_selected = lb.selected();
// Verify consistency
EXPECT_TRUE(selected == current_selected);
EXPECT_EQ(i % 3, (int)current_index);
char expected_name[8];
snprintf(expected_name, sizeof(expected_name), "s%d", i % 3);
EXPECT_TRUE(expected_name == selected);
EXPECT_TRUE(expected_name == current_selected);
}
}
}
VOID TEST(KernelCodecTest, CoverAll)
{
if (true) {
@ -6932,93 +7253,3 @@ VOID TEST(KernelUtilityTest, Base64Decode)
EXPECT_STRNE("admin:admin", plaintext.c_str());
}
}
VOID TEST(KernelMemoryBlockTest, MemoryBlockBasic)
{
// Test basic construction and destruction
if (true) {
SrsMemoryBlock block;
EXPECT_EQ(0, block.size());
EXPECT_EQ(NULL, block.payload());
}
// Test create with size
if (true) {
SrsMemoryBlock block;
block.create(1024);
EXPECT_EQ(1024, block.size());
EXPECT_NE((char *)NULL, block.payload());
}
// Test create with data
if (true) {
SrsMemoryBlock block;
char test_data[] = "Hello, World!";
int test_size = strlen(test_data);
block.create(test_data, test_size);
EXPECT_EQ(test_size, block.size());
EXPECT_NE((char *)NULL, block.payload());
EXPECT_EQ(0, memcmp(block.payload(), test_data, test_size));
}
// Test attach
if (true) {
SrsMemoryBlock block;
char *test_data = new char[100];
memset(test_data, 0x42, 100);
block.attach(test_data, 100);
EXPECT_EQ(100, block.size());
EXPECT_EQ(test_data, block.payload());
// Memory will be freed by block destructor
}
}
VOID TEST(KernelMemoryBlockTest, SharedMemoryBlock)
{
// Test basic shared memory block usage
if (true) {
SrsSharedPtr<SrsMemoryBlock> shared_block(new SrsMemoryBlock());
shared_block->create(1024);
EXPECT_EQ(1024, shared_block->size());
EXPECT_NE((char *)NULL, shared_block->payload());
// Test sharing
SrsSharedPtr<SrsMemoryBlock> shared_copy = shared_block;
EXPECT_EQ(shared_block->payload(), shared_copy->payload());
EXPECT_EQ(shared_block->size(), shared_copy->size());
}
// Test multiple references
if (true) {
SrsSharedPtr<SrsMemoryBlock> original(new SrsMemoryBlock());
char test_data[] = "Shared memory test data";
original->create(test_data, strlen(test_data));
// Create multiple references
SrsSharedPtr<SrsMemoryBlock> copy1 = original;
SrsSharedPtr<SrsMemoryBlock> copy2 = original;
SrsSharedPtr<SrsMemoryBlock> copy3 = copy1;
// All should point to the same memory
EXPECT_EQ(original->payload(), copy1->payload());
EXPECT_EQ(original->payload(), copy2->payload());
EXPECT_EQ(original->payload(), copy3->payload());
// All should have the same size
EXPECT_EQ(original->size(), copy1->size());
EXPECT_EQ(original->size(), copy2->size());
EXPECT_EQ(original->size(), copy3->size());
// Verify data integrity
EXPECT_EQ(0, memcmp(original->payload(), test_data, strlen(test_data)));
EXPECT_EQ(0, memcmp(copy1->payload(), test_data, strlen(test_data)));
EXPECT_EQ(0, memcmp(copy2->payload(), test_data, strlen(test_data)));
EXPECT_EQ(0, memcmp(copy3->payload(), test_data, strlen(test_data)));
}
}

File diff suppressed because it is too large Load Diff