Summary
Five functions in internal/b2bua/handler.go share the same ~20-parameter call signature, making the code fragile and hard to extend:
| Function |
Line |
Param Count |
Callers |
trunkResponseLoop |
830 |
23 |
handleTrunkInvite (1 call) |
handleTrunk200OK |
927 |
19 |
trunkResponseLoop (1 call) |
b2buaResponseLoop |
1162 |
23 |
handleInternalInvite (1 call) |
handleBob200OK |
1318 |
19 |
b2buaResponseLoop (1 call) |
Every new feature (session timers, PRACK, History-Info, etc.) requires threading yet another parameter through all four functions.
Proposed refactoring
Extract a context struct that holds the shared call state and pass a pointer instead of individual parameters.
Step 1: Define callCtx
type callCtx struct {
req *proto.SIPMessage
tx sip.Transaction
target *sip.Target
transportImpl sip.Transport
uac *sip.UACTransaction
rtpConnA *media.RTPConn
rtpConnB *media.RTPConn
from *proto.SIPAddress
aliceFromTag string
serverTag string
callID string
calleeTag string
bobCallID string
to *proto.SIPAddress
selectedPT uint8
hasEarlyOffer bool
aliceSDPOffer *proto.SDP
aliceSDPBytes []byte
recordRoute string
serverPort string
}
This collapses the 19 shared parameters into a single struct field.
Step 2: Collapse signatures
Before:
func (h *Handler) trunkResponseLoop(ctx context.Context,
req, tx, target, transportImpl, uac, rtpConnA, rtpConnB, ... 23 params ...
) {
After:
func (h *Handler) trunkResponseLoop(ctx context.Context, cc *callCtx,
bobInvite *proto.SIPMessage,
trunkName string, bobReqURI string,
sessionExpires time.Duration,
) {
Ditto for the other three functions — they each keep only their specific parameters.
Step 3: Promote serverPort to Handler
serverPort is computed as net.SplitHostPort(h.serverAddr) in handleTrunkInvite, handleInternalInvite, HandleBye, and the session timer. It never changes after startup. Store it as a Handler field (e.g. serverPort string) during NewHandler, removing it from callCtx and from all parameter lists.
type Handler struct {
// ... existing fields ...
serverPort string
}
// In NewHandler:
hostPort := net.SplitHostPort(cfg.Server.Addr())
if hostPort == "" { hostPort = "5060" }
h.serverPort = hostPort
Step 4: Trunk-specific extension
For the trunk path, the callCtx can embed or be extended inline:
func (h *Handler) handleTrunkInvite(...) {
cc := &callCtx{
req: req, tx: tx, rtpConnA: rtpConnA, // ... etc
}
// bobReqURI and sessionExpires are trunk-specific and stay as params
go h.trunkResponseLoop(ctx, cc, bobInvite, trk.Name, bobReqURI, sessionExpires)
}
Step 5: Visitor pattern (optional future enhancement)
After the struct extraction, the trunk and internal 200 OK handlers (handleTrunk200OK, handleBob200OK) still contain near-identical logic for constructing dialogs, sessions, and bridges from the completed call. A follow-up could extract a method:
func (h *Handler) finalizeCall(ctx context.Context, cc *callCtx, bobTo *proto.SIPAddress, resp *proto.SIPMessage, trunkName string) *Call
Benefits
- Modularity — Adding a new field (e.g.
History-Info headers) means adding one field to callCtx instead of threading through 4 function signatures
- Testability — Callers can construct a
callCtx directly in tests
- Correctness — Eliminates the risk of passing parameters in the wrong order (two adjacent
string parameters are currently order-dependent)
- Readability — Each function signature drops from ~5 screenfuls to 2–3 lines
Checklist
Summary
Five functions in
internal/b2bua/handler.goshare the same ~20-parameter call signature, making the code fragile and hard to extend:trunkResponseLoophandleTrunkInvite(1 call)handleTrunk200OKtrunkResponseLoop(1 call)b2buaResponseLoophandleInternalInvite(1 call)handleBob200OKb2buaResponseLoop(1 call)Every new feature (session timers, PRACK, History-Info, etc.) requires threading yet another parameter through all four functions.
Proposed refactoring
Extract a context struct that holds the shared call state and pass a pointer instead of individual parameters.
Step 1: Define
callCtxThis collapses the 19 shared parameters into a single struct field.
Step 2: Collapse signatures
Before:
After:
Ditto for the other three functions — they each keep only their specific parameters.
Step 3: Promote
serverPorttoHandlerserverPortis computed asnet.SplitHostPort(h.serverAddr)inhandleTrunkInvite,handleInternalInvite,HandleBye, and the session timer. It never changes after startup. Store it as aHandlerfield (e.g.serverPort string) duringNewHandler, removing it fromcallCtxand from all parameter lists.Step 4: Trunk-specific extension
For the trunk path, the
callCtxcan embed or be extended inline:Step 5: Visitor pattern (optional future enhancement)
After the struct extraction, the trunk and internal 200 OK handlers (
handleTrunk200OK,handleBob200OK) still contain near-identical logic for constructing dialogs, sessions, and bridges from the completed call. A follow-up could extract a method:Benefits
History-Infoheaders) means adding one field tocallCtxinstead of threading through 4 function signaturescallCtxdirectly in testsstringparameters are currently order-dependent)Checklist
serverPortfield toHandler, initialize inNewHandler, remove allnet.SplitHostPortrecomputationscallCtxstruct with shared fieldsb2buaResponseLoop+handleBob200OKto usecallCtxtrunkResponseLoop+handleTrunk200OKto usecallCtxhandleInternalInviteandhandleTrunkInvitemake testandmake lint