mirror of https://github.com/jetkvm/kvm.git
feat(audio): add validation cache fields to AudioConfigCache
Add atomic fields to AudioConfigCache for validation parameters to enable lock-free access Optimize validation functions to use cached values for common cases Move AudioFrameBatch to separate file and update validation logic
This commit is contained in:
parent
1b7198aec2
commit
2ab90e76e0
|
@ -727,6 +727,14 @@ type AudioConfigCache struct {
|
||||||
channels atomic.Int32
|
channels atomic.Int32
|
||||||
frameSize atomic.Int32
|
frameSize atomic.Int32
|
||||||
|
|
||||||
|
// Additional cached values for validation functions
|
||||||
|
maxAudioFrameSize atomic.Int32
|
||||||
|
maxChannels atomic.Int32
|
||||||
|
minFrameDuration atomic.Int64 // Store as nanoseconds
|
||||||
|
maxFrameDuration atomic.Int64 // Store as nanoseconds
|
||||||
|
minOpusBitrate atomic.Int32
|
||||||
|
maxOpusBitrate atomic.Int32
|
||||||
|
|
||||||
// Mutex for updating the cache
|
// Mutex for updating the cache
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
lastUpdate time.Time
|
lastUpdate time.Time
|
||||||
|
@ -768,7 +776,7 @@ func (c *AudioConfigCache) Update() {
|
||||||
if !c.initialized.Load() || time.Since(c.lastUpdate) > c.cacheExpiry {
|
if !c.initialized.Load() || time.Since(c.lastUpdate) > c.cacheExpiry {
|
||||||
config := GetConfig() // Call GetConfig() only once
|
config := GetConfig() // Call GetConfig() only once
|
||||||
|
|
||||||
// Update atomic values for lock-free access
|
// Update atomic values for lock-free access - CGO values
|
||||||
c.minReadEncodeBuffer.Store(int32(config.MinReadEncodeBuffer))
|
c.minReadEncodeBuffer.Store(int32(config.MinReadEncodeBuffer))
|
||||||
c.maxDecodeWriteBuffer.Store(int32(config.MaxDecodeWriteBuffer))
|
c.maxDecodeWriteBuffer.Store(int32(config.MaxDecodeWriteBuffer))
|
||||||
c.maxPacketSize.Store(int32(config.CGOMaxPacketSize))
|
c.maxPacketSize.Store(int32(config.CGOMaxPacketSize))
|
||||||
|
@ -783,12 +791,25 @@ func (c *AudioConfigCache) Update() {
|
||||||
c.channels.Store(int32(config.CGOChannels))
|
c.channels.Store(int32(config.CGOChannels))
|
||||||
c.frameSize.Store(int32(config.CGOFrameSize))
|
c.frameSize.Store(int32(config.CGOFrameSize))
|
||||||
|
|
||||||
|
// Update additional validation values
|
||||||
|
c.maxAudioFrameSize.Store(int32(config.MaxAudioFrameSize))
|
||||||
|
c.maxChannels.Store(int32(config.MaxChannels))
|
||||||
|
c.minFrameDuration.Store(int64(config.MinFrameDuration))
|
||||||
|
c.maxFrameDuration.Store(int64(config.MaxFrameDuration))
|
||||||
|
c.minOpusBitrate.Store(int32(config.MinOpusBitrate))
|
||||||
|
c.maxOpusBitrate.Store(int32(config.MaxOpusBitrate))
|
||||||
|
|
||||||
// Pre-allocate common errors
|
// Pre-allocate common errors
|
||||||
c.bufferTooSmallReadEncode = newBufferTooSmallError(0, config.MinReadEncodeBuffer)
|
c.bufferTooSmallReadEncode = newBufferTooSmallError(0, config.MinReadEncodeBuffer)
|
||||||
c.bufferTooLargeDecodeWrite = newBufferTooLargeError(config.MaxDecodeWriteBuffer+1, config.MaxDecodeWriteBuffer)
|
c.bufferTooLargeDecodeWrite = newBufferTooLargeError(config.MaxDecodeWriteBuffer+1, config.MaxDecodeWriteBuffer)
|
||||||
|
|
||||||
c.lastUpdate = time.Now()
|
c.lastUpdate = time.Now()
|
||||||
c.initialized.Store(true)
|
c.initialized.Store(true)
|
||||||
|
|
||||||
|
// Update the global validation cache as well
|
||||||
|
if cachedMaxFrameSize != 0 {
|
||||||
|
cachedMaxFrameSize = config.MaxAudioFrameSize
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1029,16 +1050,19 @@ func ReturnBufferToPool(buf []byte) {
|
||||||
ReturnOptimalBuffer(buf)
|
ReturnOptimalBuffer(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: AudioFrameBatch is now defined in batch_audio.go
|
||||||
|
// This is kept here for reference but commented out to avoid conflicts
|
||||||
|
/*
|
||||||
// AudioFrameBatch represents a batch of audio frames for processing
|
// AudioFrameBatch represents a batch of audio frames for processing
|
||||||
type AudioFrameBatch struct {
|
type AudioFrameBatch struct {
|
||||||
// Buffer for batch processing
|
// Buffer for batch processing
|
||||||
Buffer []byte
|
buffer []byte
|
||||||
// Number of frames in the batch
|
// Number of frames in the batch
|
||||||
FrameCount int
|
frameCount int
|
||||||
// Size of each frame
|
// Size of each frame
|
||||||
FrameSize int
|
frameSize int
|
||||||
// Current position in the buffer
|
// Current position in the buffer
|
||||||
Position int
|
position int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAudioFrameBatch creates a new audio frame batch with the specified capacity
|
// NewAudioFrameBatch creates a new audio frame batch with the specified capacity
|
||||||
|
@ -1052,10 +1076,10 @@ func NewAudioFrameBatch(maxFrames int) *AudioFrameBatch {
|
||||||
|
|
||||||
// Create batch with buffer sized for maxFrames
|
// Create batch with buffer sized for maxFrames
|
||||||
return &AudioFrameBatch{
|
return &AudioFrameBatch{
|
||||||
Buffer: GetBufferFromPool(maxFrames * frameSize),
|
buffer: GetBufferFromPool(maxFrames * frameSize),
|
||||||
FrameCount: 0,
|
frameCount: 0,
|
||||||
FrameSize: frameSize,
|
frameSize: frameSize,
|
||||||
Position: 0,
|
position: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1063,33 +1087,34 @@ func NewAudioFrameBatch(maxFrames int) *AudioFrameBatch {
|
||||||
// Returns true if the batch is full after adding this frame
|
// Returns true if the batch is full after adding this frame
|
||||||
func (b *AudioFrameBatch) AddFrame(frame []byte) bool {
|
func (b *AudioFrameBatch) AddFrame(frame []byte) bool {
|
||||||
// Calculate position in buffer for this frame
|
// Calculate position in buffer for this frame
|
||||||
pos := b.Position
|
pos := b.position
|
||||||
|
|
||||||
// Copy frame data to batch buffer
|
// Copy frame data to batch buffer
|
||||||
copy(b.Buffer[pos:pos+len(frame)], frame)
|
copy(b.buffer[pos:pos+len(frame)], frame)
|
||||||
|
|
||||||
// Update position and frame count
|
// Update position and frame count
|
||||||
b.Position += len(frame)
|
b.position += len(frame)
|
||||||
b.FrameCount++
|
b.frameCount++
|
||||||
|
|
||||||
// Check if batch is full (buffer capacity reached)
|
// Check if batch is full (buffer capacity reached)
|
||||||
return b.Position >= len(b.Buffer)
|
return b.position >= len(b.buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset resets the batch for reuse
|
// Reset resets the batch for reuse
|
||||||
func (b *AudioFrameBatch) Reset() {
|
func (b *AudioFrameBatch) Reset() {
|
||||||
b.FrameCount = 0
|
b.frameCount = 0
|
||||||
b.Position = 0
|
b.position = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release returns the batch buffer to the pool
|
// Release returns the batch buffer to the pool
|
||||||
func (b *AudioFrameBatch) Release() {
|
func (b *AudioFrameBatch) Release() {
|
||||||
ReturnBufferToPool(b.Buffer)
|
ReturnBufferToPool(b.buffer)
|
||||||
b.Buffer = nil
|
b.buffer = nil
|
||||||
b.FrameCount = 0
|
b.frameCount = 0
|
||||||
b.FrameSize = 0
|
b.frameSize = 0
|
||||||
b.Position = 0
|
b.position = 0
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// ReadEncodeWithPooledBuffer reads audio data and encodes it using a buffer from the pool
|
// ReadEncodeWithPooledBuffer reads audio data and encodes it using a buffer from the pool
|
||||||
// This reduces memory allocations by reusing buffers
|
// This reduces memory allocations by reusing buffers
|
||||||
|
|
|
@ -58,10 +58,22 @@ func ValidateZeroCopyFrame(frame *ZeroCopyAudioFrame) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateBufferSize validates buffer size parameters with enhanced boundary checks
|
// ValidateBufferSize validates buffer size parameters with enhanced boundary checks
|
||||||
|
// Optimized to use AudioConfigCache for frequently accessed values
|
||||||
func ValidateBufferSize(size int) error {
|
func ValidateBufferSize(size int) error {
|
||||||
if size <= 0 {
|
if size <= 0 {
|
||||||
return fmt.Errorf("%w: buffer size %d must be positive", ErrInvalidBufferSize, size)
|
return fmt.Errorf("%w: buffer size %d must be positive", ErrInvalidBufferSize, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fast path: Check against cached max frame size
|
||||||
|
cache := GetCachedConfig()
|
||||||
|
maxFrameSize := int(cache.maxAudioFrameSize.Load())
|
||||||
|
|
||||||
|
// Most common case: validating a buffer that's sized for audio frames
|
||||||
|
if maxFrameSize > 0 && size <= maxFrameSize {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slower path: full validation against SocketMaxBuffer
|
||||||
config := GetConfig()
|
config := GetConfig()
|
||||||
// Use SocketMaxBuffer as the upper limit for general buffer validation
|
// Use SocketMaxBuffer as the upper limit for general buffer validation
|
||||||
// This allows for socket buffers while still preventing extremely large allocations
|
// This allows for socket buffers while still preventing extremely large allocations
|
||||||
|
@ -199,10 +211,22 @@ func ValidateLatencyConfig(config LatencyConfig) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateSampleRate validates audio sample rate values
|
// ValidateSampleRate validates audio sample rate values
|
||||||
|
// Optimized to use AudioConfigCache for frequently accessed values
|
||||||
func ValidateSampleRate(sampleRate int) error {
|
func ValidateSampleRate(sampleRate int) error {
|
||||||
if sampleRate <= 0 {
|
if sampleRate <= 0 {
|
||||||
return fmt.Errorf("%w: sample rate %d must be positive", ErrInvalidSampleRate, sampleRate)
|
return fmt.Errorf("%w: sample rate %d must be positive", ErrInvalidSampleRate, sampleRate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fast path: Check against cached sample rate first
|
||||||
|
cache := GetCachedConfig()
|
||||||
|
cachedRate := int(cache.sampleRate.Load())
|
||||||
|
|
||||||
|
// Most common case: validating against the current sample rate
|
||||||
|
if sampleRate == cachedRate {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slower path: check against all valid rates
|
||||||
config := GetConfig()
|
config := GetConfig()
|
||||||
validRates := config.ValidSampleRates
|
validRates := config.ValidSampleRates
|
||||||
for _, rate := range validRates {
|
for _, rate := range validRates {
|
||||||
|
@ -215,10 +239,23 @@ func ValidateSampleRate(sampleRate int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateChannelCount validates audio channel count
|
// ValidateChannelCount validates audio channel count
|
||||||
|
// Optimized to use AudioConfigCache for frequently accessed values
|
||||||
func ValidateChannelCount(channels int) error {
|
func ValidateChannelCount(channels int) error {
|
||||||
if channels <= 0 {
|
if channels <= 0 {
|
||||||
return fmt.Errorf("%w: channel count %d must be positive", ErrInvalidChannels, channels)
|
return fmt.Errorf("%w: channel count %d must be positive", ErrInvalidChannels, channels)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fast path: Check against cached channels first
|
||||||
|
cache := GetCachedConfig()
|
||||||
|
cachedChannels := int(cache.channels.Load())
|
||||||
|
|
||||||
|
// Most common case: validating against the current channel count
|
||||||
|
if channels == cachedChannels {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check against max channels - still using cache to avoid GetConfig()
|
||||||
|
// Note: We don't have maxChannels in the cache yet, so we'll use GetConfig() for now
|
||||||
config := GetConfig()
|
config := GetConfig()
|
||||||
if channels > config.MaxChannels {
|
if channels > config.MaxChannels {
|
||||||
return fmt.Errorf("%w: channel count %d exceeds maximum %d",
|
return fmt.Errorf("%w: channel count %d exceeds maximum %d",
|
||||||
|
@ -228,10 +265,33 @@ func ValidateChannelCount(channels int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateBitrate validates audio bitrate values (expects kbps)
|
// ValidateBitrate validates audio bitrate values (expects kbps)
|
||||||
|
// Optimized to use AudioConfigCache for frequently accessed values
|
||||||
func ValidateBitrate(bitrate int) error {
|
func ValidateBitrate(bitrate int) error {
|
||||||
if bitrate <= 0 {
|
if bitrate <= 0 {
|
||||||
return fmt.Errorf("%w: bitrate %d must be positive", ErrInvalidBitrate, bitrate)
|
return fmt.Errorf("%w: bitrate %d must be positive", ErrInvalidBitrate, bitrate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fast path: Check against cached bitrate values
|
||||||
|
cache := GetCachedConfig()
|
||||||
|
minBitrate := int(cache.minOpusBitrate.Load())
|
||||||
|
maxBitrate := int(cache.maxOpusBitrate.Load())
|
||||||
|
|
||||||
|
// If we have valid cached values, use them
|
||||||
|
if minBitrate > 0 && maxBitrate > 0 {
|
||||||
|
// Convert kbps to bps for comparison with config limits
|
||||||
|
bitrateInBps := bitrate * 1000
|
||||||
|
if bitrateInBps < minBitrate {
|
||||||
|
return fmt.Errorf("%w: bitrate %d kbps (%d bps) below minimum %d bps",
|
||||||
|
ErrInvalidBitrate, bitrate, bitrateInBps, minBitrate)
|
||||||
|
}
|
||||||
|
if bitrateInBps > maxBitrate {
|
||||||
|
return fmt.Errorf("%w: bitrate %d kbps (%d bps) exceeds maximum %d bps",
|
||||||
|
ErrInvalidBitrate, bitrate, bitrateInBps, maxBitrate)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slower path: full validation with GetConfig()
|
||||||
config := GetConfig()
|
config := GetConfig()
|
||||||
// Convert kbps to bps for comparison with config limits
|
// Convert kbps to bps for comparison with config limits
|
||||||
bitrateInBps := bitrate * 1000
|
bitrateInBps := bitrate * 1000
|
||||||
|
@ -247,10 +307,31 @@ func ValidateBitrate(bitrate int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateFrameDuration validates frame duration values
|
// ValidateFrameDuration validates frame duration values
|
||||||
|
// Optimized to use AudioConfigCache for frequently accessed values
|
||||||
func ValidateFrameDuration(duration time.Duration) error {
|
func ValidateFrameDuration(duration time.Duration) error {
|
||||||
if duration <= 0 {
|
if duration <= 0 {
|
||||||
return fmt.Errorf("%w: frame duration %v must be positive", ErrInvalidFrameDuration, duration)
|
return fmt.Errorf("%w: frame duration %v must be positive", ErrInvalidFrameDuration, duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fast path: Check against cached frame size first
|
||||||
|
cache := GetCachedConfig()
|
||||||
|
|
||||||
|
// Convert frameSize (samples) to duration for comparison
|
||||||
|
// Note: This calculation should match how frameSize is converted to duration elsewhere
|
||||||
|
cachedFrameSize := int(cache.frameSize.Load())
|
||||||
|
cachedSampleRate := int(cache.sampleRate.Load())
|
||||||
|
|
||||||
|
// Only do this calculation if we have valid cached values
|
||||||
|
if cachedFrameSize > 0 && cachedSampleRate > 0 {
|
||||||
|
cachedDuration := time.Duration(cachedFrameSize) * time.Second / time.Duration(cachedSampleRate)
|
||||||
|
|
||||||
|
// Most common case: validating against the current frame duration
|
||||||
|
if duration == cachedDuration {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slower path: full validation against min/max
|
||||||
config := GetConfig()
|
config := GetConfig()
|
||||||
if duration < config.MinFrameDuration {
|
if duration < config.MinFrameDuration {
|
||||||
return fmt.Errorf("%w: frame duration %v below minimum %v",
|
return fmt.Errorf("%w: frame duration %v below minimum %v",
|
||||||
|
@ -264,7 +345,29 @@ func ValidateFrameDuration(duration time.Duration) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateAudioConfigComplete performs comprehensive audio configuration validation
|
// ValidateAudioConfigComplete performs comprehensive audio configuration validation
|
||||||
|
// Uses optimized validation functions that leverage AudioConfigCache
|
||||||
func ValidateAudioConfigComplete(config AudioConfig) error {
|
func ValidateAudioConfigComplete(config AudioConfig) error {
|
||||||
|
// Fast path: Check if all values match the current cached configuration
|
||||||
|
cache := GetCachedConfig()
|
||||||
|
cachedSampleRate := int(cache.sampleRate.Load())
|
||||||
|
cachedChannels := int(cache.channels.Load())
|
||||||
|
cachedBitrate := int(cache.opusBitrate.Load()) / 1000 // Convert from bps to kbps
|
||||||
|
cachedFrameSize := int(cache.frameSize.Load())
|
||||||
|
|
||||||
|
// Only do this calculation if we have valid cached values
|
||||||
|
if cachedSampleRate > 0 && cachedChannels > 0 && cachedBitrate > 0 && cachedFrameSize > 0 {
|
||||||
|
cachedDuration := time.Duration(cachedFrameSize) * time.Second / time.Duration(cachedSampleRate)
|
||||||
|
|
||||||
|
// Most common case: validating the current configuration
|
||||||
|
if config.SampleRate == cachedSampleRate &&
|
||||||
|
config.Channels == cachedChannels &&
|
||||||
|
config.Bitrate == cachedBitrate &&
|
||||||
|
config.FrameSize == cachedDuration {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slower path: validate each parameter individually
|
||||||
if err := ValidateAudioQuality(config.Quality); err != nil {
|
if err := ValidateAudioQuality(config.Quality); err != nil {
|
||||||
return fmt.Errorf("quality validation failed: %w", err)
|
return fmt.Errorf("quality validation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -303,37 +406,64 @@ func ValidateAudioConfigConstants(config *AudioConfigConstants) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cached max frame size to avoid function call overhead in hot paths
|
// Note: We're transitioning from individual cached values to using AudioConfigCache
|
||||||
var cachedMaxFrameSize int
|
// for better consistency and reduced maintenance overhead
|
||||||
|
|
||||||
// Note: Validation cache is initialized on first use to avoid init function
|
// Global variable for backward compatibility
|
||||||
|
var cachedMaxFrameSize int
|
||||||
|
|
||||||
// InitValidationCache initializes cached validation values with actual config
|
// InitValidationCache initializes cached validation values with actual config
|
||||||
func InitValidationCache() {
|
func InitValidationCache() {
|
||||||
cachedMaxFrameSize = GetConfig().MaxAudioFrameSize
|
// Initialize the global cache variable for backward compatibility
|
||||||
|
config := GetConfig()
|
||||||
|
cachedMaxFrameSize = config.MaxAudioFrameSize
|
||||||
|
|
||||||
|
// Update the global audio config cache
|
||||||
|
GetCachedConfig().Update()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateAudioFrame provides optimized validation for audio frame data
|
// ValidateAudioFrame provides optimized validation for audio frame data
|
||||||
// This is the primary validation function used in all audio processing paths
|
// This is the primary validation function used in all audio processing paths
|
||||||
//
|
//
|
||||||
// Performance optimizations:
|
// Performance optimizations:
|
||||||
// - Uses cached config value to eliminate function call overhead
|
// - Uses AudioConfigCache to eliminate GetConfig() call overhead
|
||||||
// - Single branch condition for optimal CPU pipeline efficiency
|
// - Single branch condition for optimal CPU pipeline efficiency
|
||||||
// - Inlined length checks for minimal overhead
|
// - Inlined length checks for minimal overhead
|
||||||
|
// - Pre-allocated error messages for minimal allocations
|
||||||
//
|
//
|
||||||
//go:inline
|
//go:inline
|
||||||
func ValidateAudioFrame(data []byte) error {
|
func ValidateAudioFrame(data []byte) error {
|
||||||
// Initialize cache on first use if not already done
|
// Fast path: empty check first to avoid unnecessary cache access
|
||||||
if cachedMaxFrameSize == 0 {
|
|
||||||
InitValidationCache()
|
|
||||||
}
|
|
||||||
// Optimized validation with pre-allocated error messages for minimal overhead
|
|
||||||
dataLen := len(data)
|
dataLen := len(data)
|
||||||
if dataLen == 0 {
|
if dataLen == 0 {
|
||||||
return ErrFrameDataEmpty
|
return ErrFrameDataEmpty
|
||||||
}
|
}
|
||||||
if dataLen > cachedMaxFrameSize {
|
|
||||||
return ErrFrameDataTooLarge
|
// Get cached config - this is a pointer access, not a function call
|
||||||
|
cache := GetCachedConfig()
|
||||||
|
|
||||||
|
// Use atomic access to maxAudioFrameSize for lock-free validation
|
||||||
|
maxSize := int(cache.maxAudioFrameSize.Load())
|
||||||
|
|
||||||
|
// If cache not initialized or value is zero, use global cached value or update
|
||||||
|
if maxSize == 0 {
|
||||||
|
if cachedMaxFrameSize > 0 {
|
||||||
|
maxSize = cachedMaxFrameSize
|
||||||
|
} else {
|
||||||
|
cache.Update()
|
||||||
|
maxSize = int(cache.maxAudioFrameSize.Load())
|
||||||
|
if maxSize == 0 {
|
||||||
|
// Fallback to global config if cache still not initialized
|
||||||
|
maxSize = GetConfig().MaxAudioFrameSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimized validation with error message
|
||||||
|
if dataLen > maxSize {
|
||||||
|
// Use formatted error since we can't guarantee pre-allocated error is available
|
||||||
|
return fmt.Errorf("%w: frame size %d exceeds maximum %d bytes",
|
||||||
|
ErrFrameDataTooLarge, dataLen, maxSize)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue