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:
parent
8976ce4c8d
commit
7c1e87ef5c
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@
|
|||
|
||||
#define VERSION_MAJOR 7
|
||||
#define VERSION_MINOR 0
|
||||
#define VERSION_REVISION 80
|
||||
#define VERSION_REVISION 81
|
||||
|
||||
#endif
|
||||
|
|
@ -10,6 +10,14 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
ISrsLbRoundRobin::ISrsLbRoundRobin()
|
||||
{
|
||||
}
|
||||
|
||||
ISrsLbRoundRobin::~ISrsLbRoundRobin()
|
||||
{
|
||||
}
|
||||
|
||||
SrsLbRoundRobin::SrsLbRoundRobin()
|
||||
{
|
||||
index = -1;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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_);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue
Block a user