Conversation
…. unit-tests will be implemented later
WalkthroughBlock-wise transfer keying moved from token.Hash() to an exchangeKey (derived from token or remoteAddr). BlockWise APIs (Do, Handle) now accept a net.Addr and thread exchangeKey through cache/handlers. Client call-sites updated to pass RemoteAddr(). Misc: root .gitignore tightened, example UDP ports changed, libcoap tests and CI install steps added. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor App
participant ClientConn as ClientConn (TCP/UDP)
participant BlockWise as BlockWise
participant Keyer as getExchangeKey
participant Cache as Exchange Cache
App->>ClientConn: Do(req)
ClientConn->>BlockWise: Do(req, maxSZX, maxMsgSize, RemoteAddr, doInternal)
BlockWise->>Keyer: getExchangeKey(token, RemoteAddr)
Keyer-->>BlockWise: exchangeKey / error
alt exchangeKey OK
BlockWise->>Cache: Lookup(exchangeKey)
alt cached in-progress
BlockWise-->>ClientConn: attach/wait
else no cache
BlockWise->>ClientConn: send blocks via doInternal
ClientConn-->>BlockWise: responses
BlockWise->>Cache: Update/Cleanup(exchangeKey)
end
BlockWise-->>App: final response
else error
BlockWise-->>App: error
end
sequenceDiagram
autonumber
actor RemotePeer
participant ServerConn as ServerConn
participant BlockWise as BlockWise
participant Keyer as getExchangeKey
participant Cache as Exchange Cache
participant Handler as next()
RemotePeer->>ServerConn: Incoming message
ServerConn->>BlockWise: Handle(w, r, maxSZX, maxMsgSize, RemoteAddr, next)
BlockWise->>Keyer: getExchangeKey(r.Token, RemoteAddr)
Keyer-->>BlockWise: exchangeKey / error
alt exchangeKey OK
BlockWise->>Cache: Get/Assemble(exchangeKey)
BlockWise->>Handler: next(w, r)
Handler-->>BlockWise: handled
BlockWise->>Cache: Cleanup/Retain(exchangeKey)
else error
BlockWise-->>ServerConn: write error
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
net/blockwise/blockwise.go (2)
289-300: Passing 0 for exchangeKey relies on implicit fallback logic.The
WriteMessagefunction passes0as theexchangeKeyparameter tostartSendingMessage. While this is handled correctly bystartSendingMessage(lines 570-572), which computes the key from the token when it's 0, this implicit behavior could be confusing.Consider making this more explicit by computing the exchangeKey here:
func (b *BlockWise[C]) WriteMessage(request *pool.Message, maxSZX SZX, maxMessageSize uint32, writeMessage func(r *pool.Message) error) error { startSendingMessageBlock, err := EncodeBlockOption(maxSZX, 0, true) if err != nil { return fmt.Errorf("cannot encode start sending message block option(%v,%v,%v): %w", maxSZX, 0, true, err) } w := newWriteRequestResponse(b.cc, request) - err = b.startSendingMessage(w, maxSZX, maxMessageSize, startSendingMessageBlock, 0) + exchangeKey := request.Token().Hash() + err = b.startSendingMessage(w, maxSZX, maxMessageSize, startSendingMessageBlock, exchangeKey) if err != nil { return fmt.Errorf("cannot start writing request: %w", err) } return writeMessage(w.Message()) }
867-879: Consider hash collision implications for remote address keying.The
getExchangeKeyfunction uses FNV-1a to hash the string representation ofnet.Addr. While FNV-1a is fast and provides reasonable distribution, there are considerations:
Address string stability: The function relies on
addr.String()being consistent. This is generally safe fornet.Addrimplementations, but could be fragile if custom address types have non-deterministic string representations.Hash collisions: With a 64-bit hash space, the probability of collision is low but non-zero. For a large number of concurrent connections from different endpoints, collisions could cause cross-contamination of blockwise state between different clients.
Empty token semantics: According to RFC 7252, empty tokens are valid when "no other tokens are in use to a destination." The exchange key mechanism assumes one concurrent blockwise exchange per remote address when tokens are empty, which may not hold if the remote sends multiple concurrent requests with empty tokens.
Consider these enhancements:
- Document the limitations and assumptions in a comment above the function.
- For production scenarios with high connection counts, consider incorporating additional entropy (e.g., local address, connection ID) to reduce collision probability.
- Add collision detection/handling in cache operations if feasible.
Example documentation:
+// getExchangeKey returns a cache key for blockwise exchanges derived from the remote address. +// This is used when the CoAP token is empty per RFC 7252 section 5.3.1. +// +// Limitations: +// - Relies on addr.String() being deterministic and unique per endpoint +// - Hash collisions are possible but unlikely with 64-bit FNV-1a +// - Assumes at most one concurrent empty-token blockwise exchange per remote address func getExchangeKey(addr net.Addr) (uint64, error) { if addr == nil { return 0, errors.New("cannot get exchange key: addr is nil") } h := fnv.New64a() h.Write([]byte(addr.String())) return h.Sum64(), nil }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
.gitignore(1 hunks)examples/simple/client/main.go(1 hunks)net/blockwise/blockwise.go(19 hunks)tcp/client/conn.go(2 hunks)udp/client/conn.go(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
examples/simple/client/main.go (1)
udp/client.go (1)
Dial(25-40)
tcp/client/conn.go (2)
udp/client/conn.go (1)
Session(51-68)tcp/client/session.go (1)
Session(22-43)
udp/client/conn.go (4)
message/pool/message.go (1)
Message(26-41)net/responsewriter/responseWriter.go (1)
ResponseWriter(17-21)tcp/client/conn.go (1)
Conn(44-57)mux/client.go (1)
Conn(17-51)
net/blockwise/blockwise.go (4)
message/pool/message.go (1)
Message(26-41)message/message.go (1)
Message(12-21)pkg/cache/cache.go (2)
NewElement(32-39)Element(14-18)net/responsewriter/responseWriter.go (2)
New(23-38)ResponseWriter(17-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: test (windows-latest)
- GitHub Check: test (macOS-latest)
- GitHub Check: Fuzzing
🔇 Additional comments (9)
.gitignore (1)
14-14: LGTM!The change from
.idea/to/.idea/correctly narrows the ignore pattern to only the root.ideadirectory, preventing unintended ignores in subdirectories.net/blockwise/blockwise.go (4)
8-10: LGTM!The new imports
hash/fnvandnetare correctly added to support the exchange key mechanism based on remote addresses.
207-226: LGTM with note about empty token handling.The
Dofunction correctly computes theexchangeKeyfrom the token hash and validates that the token is non-empty. This ensures consistent cache behavior.Note: Unlike the
Handlefunction which allows empty tokens (falling back to remote address),Dorequires a valid token. This design is appropriate sinceDois for client-initiated requests that always have tokens.
354-393: LGTM! Robust fallback mechanism for empty tokens.The
Handlefunction correctly implements a fallback mechanism:
- Uses token hash if token is non-empty (lines 360-362)
- Falls back to
getExchangeKey(remoteAddr)if token is empty (lines 363-369)This handles both standard CoAP messages with tokens and edge cases where tokens may be absent (as per RFC 7252).
546-580: LGTM! Handles zero exchangeKey correctly.The function correctly handles the case where
exchangeKeyis 0 by computing it from the token (lines 570-572). This provides a sensible fallback for callers likeWriteMessagethat may not compute the key upfront.tcp/client/conn.go (2)
210-210: LGTM!The
Docall correctly passescc.RemoteAddr()to the updated blockwise API, enabling exchange key derivation from the remote address.
355-355: LGTM!The
Handlecall correctly passescc.RemoteAddr()to the updated blockwise API, enabling exchange key derivation from the remote address.udp/client/conn.go (2)
443-443: LGTM!The
Docall correctly passescc.RemoteAddr()to the updated blockwise API, enabling exchange key derivation from the remote address.
666-666: LGTM!The
Handlecall correctly passescc.RemoteAddr()to the updated blockwise API, enabling exchange key derivation from the remote address.
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
net/blockwise/blockwise.go (1)
208-226: Critical: UnusedremoteAddrparameter and inconsistent empty token handling.The
remoteAddrparameter is unused, andDorejects empty tokens (line 213) despite RFC 7252 allowing them. This is inconsistent withHandle(lines 359-370), which derives anexchangeKeyfromremoteAddrwhen the token is empty.Apply this diff to align
DowithHandleand support empty tokens:func (b *BlockWise[C]) Do(r *pool.Message, maxSzx SZX, maxMessageSize uint32, remoteAddr net.Addr, do func(req *pool.Message) (*pool.Message, error)) (*pool.Message, error) { if maxSzx > SZXBERT { return nil, errors.New("invalid szx") } - if len(r.Token()) == 0 { - return nil, errors.New("invalid token") - } expire, ok := r.Context().Deadline() if !ok { expire = time.Now().Add(b.expiration) } - exchangeKey := r.Token().Hash() + var exchangeKey uint64 + if len(r.Token()) != 0 { + exchangeKey = r.Token().Hash() + } else { + var err error + exchangeKey, err = getExchangeKey(remoteAddr) + if err != nil { + return nil, fmt.Errorf("cannot get exchange key: %w", err) + } + } + _, loaded := b.sendingMessagesCache.LoadOrStore(exchangeKey, cache.NewElement(r, expire, nil)) if loaded { - return nil, errors.New("invalid token") + return nil, errors.New("message with the same exchange key already exists") }
🧹 Nitpick comments (3)
net/blockwise/blockwise.go (3)
354-370: Remove unnecessary trailing newline.The empty token handling logic is correct, but there's an unnecessary trailing newline at line 370.
Apply this diff:
exchangeKey, err = getExchangeKey(remoteAddr) if err != nil { b.errors(fmt.Errorf("cannot get exchange key from request Parameters(%v): %w", r, err)) return } - }As per static analysis.
582-583: Fix formatting issues.The method signature is correct, but there are formatting issues: unnecessary leading newline and improper formatting.
Apply this diff:
- -func (b *BlockWise[C]) getSentRequest(exchangeKey uint64, token message.Token) *pool.Message { - +func (b *BlockWise[C]) getSentRequest(exchangeKey uint64, token message.Token) *pool.Message { data, ok := b.sendingMessagesCache.LoadWithFunc(exchangeKey, func(value *cache.Element[*pool.Message]) *cache.Element[*pool.Message] {As per static analysis.
869-881: Remove unnecessary leading newline.The
getExchangeKeyimplementation is correct: FNV-1a provides a fast, non-cryptographic hash suitable for cache keys, and the nil check prevents panics. However, there's an unnecessary leading newline.Apply this diff to fix formatting:
} - // getExchangeKey returns a key for the blockwise exchange cache. // According to RFC 7252: "[...] An empty token value is appropriate e.g., when no other tokens are in use to a destination" // so we need to generate a key from the remote address of the corresponding endpoint. func getExchangeKey(addr net.Addr) (uint64, error) { - if addr == nil { return 0, errors.New("cannot get exchange key: addr is nil") } - h := fnv.New64a() h.Write([]byte(addr.String())) return h.Sum64(), nilAs per static analysis.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
net/blockwise/blockwise.go(17 hunks)net/blockwise/blockwise_test.go(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
net/blockwise/blockwise.go (4)
message/pool/message.go (1)
Message(26-41)message/message.go (1)
Message(12-21)pkg/cache/cache.go (2)
NewElement(32-39)Element(14-18)net/responsewriter/responseWriter.go (2)
New(23-38)ResponseWriter(17-21)
net/blockwise/blockwise_test.go (3)
message/pool/message.go (1)
Message(26-41)net/responsewriter/responseWriter.go (2)
New(23-38)ResponseWriter(17-21)mux/router.go (1)
ResponseWriter(15-20)
🪛 GitHub Actions: Golangci-lint
net/blockwise/blockwise.go
[error] 583-583: File is not properly formatted (gofumpt).
🪛 GitHub Check: lint
net/blockwise/blockwise.go
[failure] 208-208:
unused-parameter: parameter 'remoteAddr' seems to be unused, consider removing or renaming it as _ (revive)
[failure] 370-370:
unnecessary trailing newline (whitespace)
[failure] 582-582:
unnecessary leading newline (whitespace)
[failure] 583-583:
File is not properly formatted (gofumpt)
[failure] 872-872:
unnecessary leading newline (whitespace)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: test (macOS-latest)
- GitHub Check: test (windows-latest)
- GitHub Check: Fuzzing
🔇 Additional comments (4)
net/blockwise/blockwise_test.go (1)
80-80: LGTM!Test updates correctly accommodate the new
remoteAddrparameter inHandleandDosignatures. Usingnilas a placeholder in tests is appropriate for this context.Also applies to: 83-83, 302-302, 499-499, 505-505
net/blockwise/blockwise.go (3)
8-8: LGTM!New imports are properly utilized:
hash/fnvfor generating exchange keys from addresses, andnetfor thenet.Addrparameter.Also applies to: 10-10
546-580: LGTM!The fallback logic to derive
exchangeKeyfrom the token when it's zero (line 570-572) is correct and maintains compatibility. The rest of the function properly integrates the exchangeKey-based caching.
756-867: LGTM!The method correctly propagates the
exchangeKeyparameter throughout all cache operations, including proper handling of observe responses that require a new exchange key (line 793).
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #626 +/- ##
==========================================
+ Coverage 73.17% 73.27% +0.10%
==========================================
Files 73 73
Lines 6986 7031 +45
==========================================
+ Hits 5112 5152 +40
- Misses 1489 1493 +4
- Partials 385 386 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
net/blockwise/blockwise.go (1)
577-599: Fix formatting issues.The function logic correctly uses
exchangeKeyfor cache lookup, but there are formatting issues flagged by the linter.Lines 577-578 have formatting issues:
- Line 577: Unnecessary leading newline
- Line 578: File is not properly formatted
Apply this diff to fix:
- func (b *BlockWise[C]) getSentRequest(exchangeKey uint64, token message.Token) *pool.Message { - data, ok := b.sendingMessagesCache.LoadWithFunc(exchangeKey, func(value *cache.Element[*pool.Message]) *cache.Element[*pool.Message] {
🧹 Nitpick comments (2)
net/blockwise/blockwise.go (2)
207-229: Consider refactoring to reduce cyclomatic complexity.The function logic correctly integrates
remoteAddrto computeexchangeKeyfor cache operations. Error handling is appropriate.The cyclomatic complexity (16) slightly exceeds the threshold (15). Consider extracting some of the block-handling logic into helper functions to improve maintainability:
- Extract the payload size validation and block option encoding (lines 233-258)
- Extract the buffer read logic (lines 259-276)
This would make the function easier to test and maintain, though the current implementation is still readable.
541-575: Consider the robustness of using 0 as a sentinel value.The logic correctly handles the
WriteMessagecase by treatingexchangeKey == 0as "compute from token." However, there's a theoretical edge case.The FNV-1a hash could theoretically return 0 for a remote address (probability: 1 in 2^64). If this occurs, lines 565-567 would incorrectly recompute the key using
token.Hash().While extremely unlikely, a more robust approach would be to use a sentinel value outside the valid hash range (e.g.,
maxUint64) or pass a boolean flag indicating whether to compute from token. However, given the low probability and the limited impact (cache key mismatch), the current implementation is acceptable for practical purposes.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
net/blockwise/blockwise.go(17 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
net/blockwise/blockwise.go (3)
message/message.go (1)
Message(12-21)message/getToken.go (1)
Token(9-9)pkg/cache/cache.go (2)
NewElement(32-39)Element(14-18)
🪛 GitHub Actions: Golangci-lint
net/blockwise/blockwise.go
[error] 208-208: golangci-lint: cyclomatic complexity 16 of func (*BlockWise).Do is high (>15) (gocyclo)
🪛 GitHub Check: lint
net/blockwise/blockwise.go
[failure] 208-208:
cyclomatic complexity 16 of func (*BlockWise).Do is high (> 15) (gocyclo)
[failure] 371-371:
shadow: declaration of "err" shadows declaration at line 362 (govet)
[failure] 577-577:
unnecessary leading newline (whitespace)
[failure] 578-578:
File is not properly formatted (gofumpt)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: test (windows-latest)
- GitHub Check: Fuzzing
🔇 Additional comments (4)
net/blockwise/blockwise.go (4)
8-10: LGTM: Necessary imports for the new functionality.The
hash/fnvimport is used for hashing remote addresses when tokens are empty, andnetimport is needed for thenet.Addrparameter in the updated API.
751-862: LGTM: Correct handling of exchangeKey propagation.The function correctly uses
exchangeKeyfor cache operations throughout. The special handling for observe responses (lines 782-789) is appropriate—when a new token is generated for observation, theexchangeKeyis recomputed usingtoken.Hash(), which ensures proper cache indexing for the subsequent GET requests.
864-879: Exchange key hashing is safe
net.Addr.String()yields a stable, uniqueIP:portfor TCP/UDP/DTLS connections. No changes required.
292-304: All BlockWise.WriteMessage callers use non-empty tokens
Verified that every client request is created viaAcquireMessage+SetToken(...)(e.g. in NewGet/Post/Put/Delete/Observe), soexchangeKey=0correctly computes from a non-nil token. No changes needed.
|
Hi @jkralik, thanks for fixing the tests. Does anything else still needs to be done for it to be complete? Potentially a unit test with no tokens but remoteAddr set |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
net/blockwise/blockwise.go (3)
207-216: Improve error message clarity.The error messages are too generic and don't guide users on what's wrong:
- Line 210: "invalid szx" → should specify the valid range (0-7 or up to SZXBERT)
- Line 213: "invalid token" → should explain the constraint (e.g., "empty token requires valid remoteAddr" if that becomes supported)
Apply this diff to improve the messages:
func validateDoInput(r *pool.Message, maxSzx SZX) error { if maxSzx > SZXBERT { - return errors.New("invalid szx") + return fmt.Errorf("invalid szx: must be <= SZXBERT (%d), got %d", SZXBERT, maxSzx) } if len(r.Token()) == 0 { - return errors.New("invalid token") + return errors.New("empty token not supported in Do method") } return nil }
619-621: Clarify fallback logic or remove if unnecessary.The fallback to
token.Hash()whenexchangeKeyis 0 suggests there are code paths whereexchangeKeymight not be provided. However, looking at the callers ofstartSendingMessage:
- Line 353 in
WriteMessage: passes0explicitly- Line 473 in
handleReceivedMessage: passes the computedexchangeKeyThe explicit
0fromWriteMessageseems to be the intended use case. Please add a comment explaining this fallback behavior:+ // If exchangeKey is 0 (e.g., from WriteMessage), fall back to token-based key. + // This maintains compatibility with scenarios where remote address tracking isn't needed. if exchangeKey == 0 { exchangeKey = sendingMessage.Token().Hash() }
917-932: Well-implemented exchange key derivation.The function correctly handles both non-empty tokens (using token hash) and empty tokens (using address hash), aligning with RFC 7252's guidance that empty tokens are valid "when no other tokens are in use to a destination."
Minor observation on hash collisions:
Using FNV-1a hash of the address string is simple and practical. While hash collisions are theoretically possible (especially if address strings format inconsistently), they're unlikely in typical deployments. If stricter collision avoidance is needed in the future, consider:
- Using the raw address bytes instead of the string representation
- Combining address and port as separate hash inputs
- Using a cryptographic hash (though FNV is sufficient for cache keys)
For now, the current implementation is acceptable.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
net/blockwise/blockwise.go(19 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
net/blockwise/blockwise.go (5)
message/pool/message.go (1)
Message(26-41)net/responsewriter/responseWriter.go (2)
New(23-38)ResponseWriter(17-21)message/getToken.go (1)
Token(9-9)pkg/cache/cache.go (2)
NewElement(32-39)Element(14-18)message/option.go (5)
Size1(77-77)Block1(73-73)Block2(72-72)Size2(74-74)OptionID(27-27)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: test (windows-latest)
- GitHub Check: Fuzzing
🔇 Additional comments (1)
net/blockwise/blockwise.go (1)
425-425: Variable shadowing fixed.Good fix! The previous linter error about shadowing
errat this line has been resolved by usingerr =instead oferr :=.
|
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
net/blockwise/blockwise.go (1)
800-807: Remove early return for empty tokens in processReceivedMessage.
This breaks blockwise assembly when tokens are empty, despite the new exchangeKey support via remoteAddr. With empty tokens, we should still process using exchangeKey.Apply this diff:
func (b *BlockWise[C]) processReceivedMessage(w *responsewriter.ResponseWriter[C], r *pool.Message, maxSzx SZX, next func(w *responsewriter.ResponseWriter[C], r *pool.Message), exchangeKey uint64, blockType message.OptionID, sizeType message.OptionID) error { token := r.Token() - if len(token) == 0 { - next(w, r) - return nil - } if r.Code() == codes.GET || r.Code() == codes.DELETE { next(w, r) return nil }
♻️ Duplicate comments (2)
net/blockwise/blockwise.go (2)
290-302: Add early remoteAddr validation in Do when token is empty.
Avoid relying on a later getExchangeKey failure; return a clear error upfront.Apply this diff:
func (b *BlockWise[C]) Do(r *pool.Message, maxSzx SZX, maxMessageSize uint32, remoteAddr net.Addr, do func(req *pool.Message) (*pool.Message, error)) (*pool.Message, error) { // Input validation if err := validateDoInput(maxSzx); err != nil { return nil, err } + // Require remoteAddr when token is empty + if len(r.Token()) == 0 && remoteAddr == nil { + return nil, errors.New("token required unless remoteAddr is set") + } // Register sending message (token uniqueness + expiration) exchangeKey, err := b.registerSendingMessage(r, remoteAddr) if err != nil { return nil, err }
407-417: Add early remoteAddr validation in Handle when token is empty.
Mirror Do’s behavior for clearer error handling before getExchangeKey.Apply this diff:
func (b *BlockWise[C]) Handle(w *responsewriter.ResponseWriter[C], r *pool.Message, maxSZX SZX, maxMessageSize uint32, remoteAddr net.Addr, next func(w *responsewriter.ResponseWriter[C], r *pool.Message)) { if maxSZX > SZXBERT { panic("invalid maxSZX") } + if len(r.Token()) == 0 && remoteAddr == nil { + b.errors(errors.New("remoteAddr cannot be nil when token is empty")) + return + } exchangeKey, err := getExchangeKey(r.Token(), remoteAddr) if err != nil { b.errors(fmt.Errorf("cannot get exchange key from request Parameters(%v): %w", r, err)) return }
🧹 Nitpick comments (4)
net/blockwise/blockwise.go (4)
223-235: Clarify collision error message in registerSendingMessage.
When LoadOrStore finds an existing entry, “invalid token” is misleading now that exchangeKey may come from remoteAddr. Prefer an error that reflects an exchangeKey collision.Apply this diff:
func (b *BlockWise[C]) registerSendingMessage(r *pool.Message, remoteAddr net.Addr) (uint64, error) { expire := b.computeExpire(r) exchangeKey, err := getExchangeKey(r.Token(), remoteAddr) if err != nil { return 0, fmt.Errorf("cannot get exchange key from request Parameters(%v): %w", r, err) } _, loaded := b.sendingMessagesCache.LoadOrStore(exchangeKey, cache.NewElement(r, expire, nil)) if loaded { - return 0, errors.New("invalid token") + return 0, fmt.Errorf("duplicate exchange key (token=%x, addr=%v): another request is already in progress", r.Token(), remoteAddr) } return exchangeKey, nil }
554-577: Fix stale error text referencing token in continueSendingMessage.
Lookup is by exchangeKey. Error should reflect that for accurate diagnostics.Apply this diff:
if err == nil && sendMessage == nil { - err = fmt.Errorf("cannot find sending message for token(%v)", r.Token()) + err = fmt.Errorf("cannot find sending message for exchangeKey(%d)", exchangeKey) }
628-649: Minor: naming nit in getSentRequest.
Parameter is an exchangeKey; consider renaming in a follow-up for clarity. No functional change needed.
914-929: getExchangeKey approach is reasonable; add minor guard if desired.
FNV-1a over remoteAddr.String() is fine for cache keys. Optionally, guard against empty remoteAddr.String().Apply this diff:
func getExchangeKey(token message.Token, remoteAddr net.Addr) (uint64, error) { if len(token) != 0 { return token.Hash(), nil } if remoteAddr == nil { return 0, errors.New("cannot get exchange key: remoteAddr is nil") } h := fnv.New64a() - h.Write([]byte(remoteAddr.String())) + addr := remoteAddr.String() + if addr == "" { + return 0, errors.New("cannot get exchange key: remoteAddr string is empty") + } + _, _ = h.Write([]byte(addr)) return h.Sum64(), nil }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
net/blockwise/blockwise.go(19 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
net/blockwise/blockwise.go (5)
net/responsewriter/responseWriter.go (2)
New(23-38)ResponseWriter(17-21)message/pool/message.go (1)
Message(26-41)message/getToken.go (1)
Token(9-9)pkg/cache/cache.go (2)
NewElement(32-39)Element(14-18)message/option.go (5)
Size1(77-77)Block1(73-73)Block2(72-72)Size2(74-74)OptionID(27-27)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: test (macOS-latest)
- GitHub Check: test (windows-latest)
- GitHub Check: Fuzzing
🔇 Additional comments (7)
net/blockwise/blockwise.go (7)
8-11: Imports look correct for new exchangeKey implementation.
FNV and net imports are appropriate for hashing remoteAddr and passing it through APIs.
116-130: OK: error propagation in DecodeBlockOption.
Returning the error explicitly improves clarity; no issues.
207-213: Good: token check removed from Do validation.
This aligns with allowing empty tokens when remoteAddr is provided.
215-222: computeExpire helper is sound.
Respects context deadlines; falls back to BlockWise expiration.
237-289: First-block builder looks correct.
Validates methods, sets Size1/Block1, handles body read/seek and releases on error. No issues.
592-626: startSendingMessage exchangeKey fallback is fine.
Using token.Hash() when exchangeKey is 0 preserves legacy behavior for server-initiated paths (e.g., WriteMessage).
290-328: Ensure all Do/Handle invocations supply a non-nil remoteAddr
Manually verify that every call toBlockWise[…].Do(...) and.Handle(...)passes a validremoteAddr(none should benil).
Hi @Theoyor, |
|
Sure, do you mean the compiled "coap-client" of libcoap? Is it already present in the testing environment? |
No, we need to create it. |
|
@jkralik I am not sure if I understand what you mean by "in UDP". Are there any tests for "with token" that I can use as reference to write mine and to know how to structure them as closely to the existing ones? Or should I do it in the blockwise_test.go file? |
I mean |
|
@jkralik I implemented a test using libcoap for GET PUT POST. DELETE is not used with a payload at all so I would leave it out. The tests were successful after a small fix. I am not sure whether writing tests using cc.NewGetRequest(...) etc. would make any sense, as it doesn't support the option of no token (and doesn't need to). Still remaining is "Observe", which I will now start. Do you think it is also necessary to include non-token tests into blockwise_test.go as well? |
It’s not necessary to include non-token tests in blockwise_test.go, as they are already covered through UDP. |
|
So I found out that it seems coap observe does not work with blockwise as intended, neither token nor no-token. I will further dig into that |
|
@jkralik ok. I did some research and testing. Let's start with what the standards say: For Blockwise observe WITH token: https://datatracker.ietf.org/doc/html/rfc7959#section-3.4 the server first sends a direct response to the initial GET request (the resulting block-wise transfer is as in Figure 4 and has therefore been left out). The second transfer is started by a 2.05 notification that contains just the first block; the client then goes on to obtain the rest of the blocks starting from the second block.[...] Note [...]that the requests for additional blocks cannot make use of the token of the Observation relationship For Blockwise observe WITHOUT token:
What I found out during tests:
My coclusion
My Proposal: I do a PR with the unit tests using coap-client onto thios branch here (so you can still modify before its merged onto master). Then we have blockwise without token working. Blcokwise + observe remains unchanged and untested. This should then be a different issue and PR. What do you think? |
|
I created #627 for unit tests please take a look. It merges into this branch so you can make changes before it is merged to master Also one would need to add coap-client into the test environment, which I cannot do. |
|
@jkralik any opinion? |
* feat: unuttests using libcoap coapo-client. GET works with and without token, post and put only with token. * fix: libcoap tests successful, fixed blockwise * fix: cleanup * fix: remove fixed ports from tests --------- Co-authored-by: Theodor Rauch <theodor.rauch@ml-pa.com>
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
net/blockwise/blockwise.go (1)
598-604: Use <= threshold to avoid unnecessary blockwise on exact-size payloads.Keep behavior consistent with Do (which uses <=).
- if payloadSize < maxSZX.Size() { + if payloadSize <= maxSZX.Size() { return nil }
🧹 Nitpick comments (18)
udp/server_libcoap_test.go (14)
96-103: Remove unused client dial.The Dialed UDP client connection isn’t used; drop it to speed up tests and reduce noise.
- cc, err := Dial(l.LocalAddr().String()) - require.NoError(t, err) - defer func() { - errC := cc.Close() - require.NoError(t, errC) - <-cc.Done() - }()
111-116: Fix indent-error-flow (revive): drop else after return.Simplify flow to satisfy linter.
- if tt.wantErr { - require.Error(t, err) - return - } else { - require.NoError(t, err) - } + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err)
77-83: Align content-format with expectations.Handler sets AppOctets but test table lists TextPlain; either assert the content-format or switch to TextPlain for clarity.
- errS := w.SetResponse(codes.Content, message.AppOctets, bytes.NewReader([]byte(bigPayload10KiB))) + errS := w.SetResponse(codes.Content, message.TextPlain, bytes.NewReader([]byte(bigPayload10KiB)))
165-169: Skip on missing coap-client (PUT).Mirror the skip behavior here too.
- _, err := exec.LookPath("coap-client") - if err != nil { - t.Error("coap-client not found in PATH") - return - } + if _, err := exec.LookPath("coap-client"); err != nil { + t.Skipf("skipping: coap-client not found in PATH") + }
183-191: Fix err shadowing (govet).Avoid shadowing outer err when reading body.
- received, err := r.ReadBody() - assert.NoError(t, err) + received, readErr := r.ReadBody() + assert.NoError(t, readErr)
216-221: Fix indent-error-flow (revive) in PUT test loop.- if tt.wantErr { - require.Error(t, err) - return - } else { - require.NoError(t, err) - } + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err)
264-269: Skip on missing coap-client (POST).Mirror the skip behavior here as well.
- _, err := exec.LookPath("coap-client") - if err != nil { - t.Error("coap-client not found in PATH") - return - } + if _, err := exec.LookPath("coap-client"); err != nil { + t.Skipf("skipping: coap-client not found in PATH") + }
281-290: Fix err shadowing (govet) in POST handler.- received, err := r.ReadBody() + received, readErr := r.ReadBody() - assert.NoError(t, err) + assert.NoError(t, readErr)
315-320: Fix indent-error-flow (revive) in POST test loop.- if tt.wantErr { - require.Error(t, err) - return - } else { - require.NoError(t, err) - } + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err)
333-336: Prefer errors.New for static error text.No formatting args used.
- return nil, fmt.Errorf("coap-client not found in PATH") + return nil, errors.New("coap-client not found in PATH")
341-346: Generate token once and use strconv.FormatUint (perfsprint).Avoid double rand call; format efficiently.
- if token { - rand.Uint64() - args = append(args, "-T", fmt.Sprintf("%d", rand.Uint64())) - } + if token { + tok := rand.Uint64() + args = append(args, "-T", strconv.FormatUint(tok, 10)) + }(Optional: seed math/rand or use crypto/rand for better randomness.)
345-347: Avoid redundant conversion.bigPayload10KiB is already a string.
- args = append(args, "-e", string(bigPayload10KiB)) + args = append(args, "-e", bigPayload10KiB)
26-33: Table fields unused/inconsistent; assert what you specify.wantCode and wantContentFormat are never asserted; GET’s wantPayload type mismatches checks. Either add assertions (parse coap-client output) or drop the fields to avoid confusion.
Would you like me to add minimal parsing to assert response code/content-format from coap-client output?
Also applies to: 35-57, 137-163, 235-262
3-17: Add command timeout to avoid hangs.Use exec.CommandContext with a short timeout when invoking external tools.
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() - cmd := exec.Command(coapClientPath, args...) + cmd := exec.CommandContext(ctx, coapClientPath, args...)Also applies to: 337-357, 359-363
net/blockwise/blockwise.go (4)
291-301: Validate remoteAddr early in Do.Return a clear error before attempting register/getExchangeKey.
if err := validateDoInput(maxSzx); err != nil { return nil, err } + if remoteAddr == nil { + return nil, errors.New("remoteAddr cannot be nil") + }
408-417: Validate remoteAddr early in Handle.Short-circuit with a clear error when remoteAddr is nil and token is empty.
func (b *BlockWise[C]) Handle(w *responsewriter.ResponseWriter[C], r *pool.Message, maxSZX SZX, maxMessageSize uint32, remoteAddr net.Addr, next func(w *responsewriter.ResponseWriter[C], r *pool.Message)) { if maxSZX > SZXBERT { panic("invalid maxSZX") } + if remoteAddr == nil && len(r.Token()) == 0 { + b.errors(errors.New("cannot get exchange key: remoteAddr is nil and token is empty")) + return + } exchangeKey, err := getExchangeKey(r.Token(), remoteAddr)
231-233: Clarify error on duplicate in-flight request.“invalid token” is misleading when exchangeKey is address-derived; make the message explicit.
- return 0, errors.New("invalid token") + return 0, errors.New("duplicate exchangeKey: in-flight request already registered")
574-579: Reference exchangeKey in not-found error.Current message mentions token, but the cache is keyed by exchangeKey.
- err = fmt.Errorf("cannot find sending message for token(%v)", r.Token()) + err = fmt.Errorf("cannot find sending message for exchangeKey(%d)", exchangeKey)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
net/blockwise/blockwise.go(19 hunks)udp/server_libcoap_test.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
udp/server_libcoap_test.go (6)
message/type.go (2)
Type(11-11)Confirmable(17-17)message/option.go (3)
MediaType(162-162)TextPlain(166-166)AppOctets(172-172)message/codes/codes.go (5)
Content(29-29)GET(16-16)Changed(28-28)PUT(18-18)POST(17-17)net/connUDP.go (1)
NewListenUDP(233-243)mux/router.go (1)
NewRouter(67-83)options/commonOptions.go (1)
WithMux(120-124)
net/blockwise/blockwise.go (3)
message/pool/message.go (1)
Message(26-41)message/getToken.go (1)
Token(9-9)pkg/cache/cache.go (2)
NewElement(32-39)Element(14-18)
🪛 GitHub Actions: Golangci-lint
udp/server_libcoap_test.go
[error] 361-361: golangci-lint: non-wrapping format verb for fmt.Errorf. Use %w to format errors (errorlint).
🪛 GitHub Check: lint
udp/server_libcoap_test.go
[failure] 318-318:
indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)
[failure] 219-219:
indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)
[failure] 114-114:
indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)
[failure] 342-342:
integer-format: fmt.Sprintf can be replaced with faster strconv.FormatUint (perfsprint)
[failure] 334-334:
error-format: fmt.Errorf can be replaced with errors.New (perfsprint)
[failure] 283-283:
shadow: declaration of "err" shadows declaration at line 264 (govet)
[failure] 185-185:
shadow: declaration of "err" shadows declaration at line 165 (govet)
[failure] 20-20:
File is not properly formatted (gofumpt)
[failure] 5-5:
File is not properly formatted (gci)
[failure] 361-361:
non-wrapping format verb for fmt.Errorf. Use %w to format errors (errorlint)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Fuzzing
- GitHub Check: test (windows-latest)
🔇 Additional comments (3)
net/blockwise/blockwise.go (1)
910-925: LGTM: getExchangeKey matches RFC intent and is simple/robust.Hashing remoteAddr for empty tokens is reasonable; low collision risk for FNV-1a 64-bit.
Please confirm remoteAddr.String() includes port for your transports (expected “ip:port”). If not, consider including family/zone for uniqueness.
udp/server_libcoap_test.go (2)
3-17: Fix import ordering: run gci and gofumpt locally.The import block has mixed standard library and third-party imports (e.g.,
bytes,fmtbeforemath/rand,os/exec,sync,testing). These tools are not available in the sandbox, so you'll need to run them locally:gci write -s standard -s default -s blank -s dot -s local . gofumpt -w .This will group standard library imports separately from third-party imports and apply consistent formatting.
341-343: The review comment is based on an incorrect assumption about libcoap's-Tbehavior.libcoap treats the absence of a Token as a valid zero-length token, and coap-client's -T option, when omitted, forces a zero-length token—not a default non-empty token. The current code correctly implements this:
- When
token=true: appends-Twith a random Uint64 (non-empty token)- When
token=false: omits-Tentirely, resulting in a zero-length tokenThe "without-token" tests (e.g., "block-without-token") already exercise the empty-token scenario correctly. No changes are required.
Likely an incorrect or invalid review comment.
| if tt.wantPayload != nil { | ||
| if wantPayloadBytes, ok := tt.wantPayload.([]byte); ok { | ||
| // add 1 for the extra newline added by coap-client | ||
| require.Len(t, receivedBody, len(wantPayloadBytes)+1) | ||
| require.Equal(t, wantPayloadBytes, receivedBody[0:len(wantPayloadBytes)]) | ||
| } | ||
| } |
There was a problem hiding this comment.
Make GET payload check effective.
wantPayload is a string here, but the assertion only runs for []byte; compare consistently as bytes.
- if tt.wantPayload != nil {
- if wantPayloadBytes, ok := tt.wantPayload.([]byte); ok {
- // add 1 for the extra newline added by coap-client
- require.Len(t, receivedBody, len(wantPayloadBytes)+1)
- require.Equal(t, wantPayloadBytes, receivedBody[0:len(wantPayloadBytes)])
- }
- }
+ if tt.wantPayload != nil {
+ want := []byte(tt.wantPayload.(string))
+ // add 1 for the extra newline added by coap-client
+ require.Len(t, receivedBody, len(want)+1)
+ require.Equal(t, want, receivedBody[:len(want)])
+ }Or change table to use []byte for all payload expectations.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if tt.wantPayload != nil { | |
| if wantPayloadBytes, ok := tt.wantPayload.([]byte); ok { | |
| // add 1 for the extra newline added by coap-client | |
| require.Len(t, receivedBody, len(wantPayloadBytes)+1) | |
| require.Equal(t, wantPayloadBytes, receivedBody[0:len(wantPayloadBytes)]) | |
| } | |
| } | |
| if tt.wantPayload != nil { | |
| want := []byte(tt.wantPayload.(string)) | |
| // add 1 for the extra newline added by coap-client | |
| require.Len(t, receivedBody, len(want)+1) | |
| require.Equal(t, want, receivedBody[:len(want)]) | |
| } |
🤖 Prompt for AI Agents
In udp/server_libcoap_test.go around lines 118 to 124, the GET payload assertion
only runs when tt.wantPayload is a []byte but the table uses a string; make the
assertion work for strings by converting expected string to bytes (or update the
table to use []byte for all payload expectations). Change the conditional to
handle both []byte and string (e.g. if b, use as-is; if s, []byte(s)), then
compare lengths and bytes against receivedBody the same way so the test actually
checks string payloads.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
udp/server_libcoap_test.go (1)
113-119: Fix broken payload assertion in GET test.The test table defines
wantPayloadasbigPayload10KiB(a string constant at line 350), but the assertion at line 114 only runs whenwantPayloadis[]byte. This means the payload is never actually verified in GET tests.Apply this diff to fix the assertion:
if tt.wantPayload != nil { - if wantPayloadBytes, ok := tt.wantPayload.([]byte); ok { - // add 1 for the extra newline added by coap-client - require.Len(t, receivedBody, len(wantPayloadBytes)+1) - require.Equal(t, wantPayloadBytes, receivedBody[0:len(wantPayloadBytes)]) - } + want := []byte(tt.wantPayload.(string)) + // add 1 for the extra newline added by coap-client + require.Len(t, receivedBody, len(want)+1) + require.Equal(t, want, receivedBody[:len(want)]) }
🧹 Nitpick comments (4)
mux/router.go (1)
125-136: LGTM! Explicit returns improve readability.Converting from bare returns to explicit return statements makes the code more readable and maintainable. This is a good practice, especially in functions with multiple exit points.
dtls/server_test.go (1)
88-138: LGTM! Explicit error returns enhance clarity.Converting all error paths from bare returns to explicit return statements improves code clarity and makes error propagation more obvious. This pattern is now consistent throughout the function and aligns well with the changes in
tcp/server_test.go.tcp/server_test.go (1)
85-133: LGTM! Consistent with DTLS test improvements.The explicit return statements follow the same refactoring pattern applied in
dtls/server_test.go, making error handling more transparent across the test suite. This consistency is valuable for maintainability.udp/server_libcoap_test.go (1)
325-326: Remove wasteful call to rand.Uint64().Line 325 calls
rand.Uint64()but discards the result. The next line generates a new random value for the token.Apply this diff:
if token { - rand.Uint64() args = append(args, "-T", strconv.FormatUint(rand.Uint64(), 10)) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
.github/workflows/test-for-fork.yml(1 hunks).github/workflows/test.yml(2 hunks)dtls/server_test.go(3 hunks)mux/router.go(2 hunks)tcp/server_test.go(3 hunks)udp/server_libcoap_test.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
tcp/server_test.go (2)
examples/dtls/pki/cert_gen.go (1)
GenerateCertificate(82-122)examples/dtls/pki/cert_util.go (2)
LoadKeyAndCertificate(67-78)LoadCertPool(81-96)
mux/router.go (1)
mux/router_test.go (4)
testRoute(135-158)TestMux(20-121)title(11-18)testRegexp(123-133)
udp/server_libcoap_test.go (6)
message/option.go (3)
MediaType(162-162)TextPlain(166-166)AppOctets(172-172)message/codes/codes.go (5)
Content(29-29)GET(16-16)Changed(28-28)PUT(18-18)POST(17-17)net/connUDP.go (1)
NewListenUDP(233-243)mux/router.go (3)
NewRouter(67-83)HandlerFunc(30-30)ResponseWriter(15-20)message/type.go (2)
Confirmable(17-17)Type(11-11)options/commonOptions.go (1)
WithMux(120-124)
dtls/server_test.go (2)
examples/dtls/pki/cert_gen.go (1)
GenerateCertificate(82-122)examples/dtls/pki/cert_util.go (2)
LoadKeyAndCertificate(67-78)LoadCertPool(81-96)
🪛 actionlint (1.7.8)
.github/workflows/test.yml
132-132: property "os" is not defined in object type {}
(expression)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Fuzzing
- GitHub Check: test (macOS-latest)
- GitHub Check: test (windows-latest)
🔇 Additional comments (4)
.github/workflows/test-for-fork.yml (1)
25-33: LGTM!The conditional installation of
coap-clientis correctly implemented for Ubuntu and macOS. Windows tests will gracefully skip when the binary is unavailable, which aligns with the test logic inudp/server_libcoap_test.go..github/workflows/test.yml (1)
34-42: LGTM!The conditional installation of
coap-clientis correctly implemented for the matrix job.udp/server_libcoap_test.go (2)
59-64: LGTM!The use of
t.Skipcorrectly allows CI to pass whencoap-clientis unavailable. This resolves the previous concern about hard failures.
343-347: LGTM!The error wrapping now correctly uses
%wto preserve the error chain, resolving the previous linting issue.
|
Hi @Theoyor . I have merged your changes and also I updated ubuntu/macos to install coap-client. But test with coap-client fails. Pls could you look at it ? |
|
Hi @jkralik thanks for installing and merging. I was quite confused on why it doesn't work anymore. After some testing I found two issues. The first one is what we see more prominent in the error logs of the PR tests. However after fixing that locally, I encountered another, more complicated issue. Long story short, I developed the testing using an older pre-compiled libcoap coap-client, and it worked. Now I self-compiled the coap-client for another project and the current coap-client sends a different token for each blockwise exchange for the same request. The issue is gone when I tested again with the old one. Which version of coap-client did you install in the test environment? How should we procceed? |
How do you arrive at that conclusion? I can't find that in the section you cite. The token should be only the concern of the client, the server just echos it back without processing it. So the client sending a different token for each block request shouldn't be an issue for the server. |



Summary by CodeRabbit
New Features
Refactor
Tests
Chores