Chore/pjsip ios15 rebuild - #6
Merged
Merged
Conversation
added 4 commits
August 2, 2026 19:18
The XCFramework carried minos 16.0 while Package.swift, the podspec and the build script all declare 15.0, so every consumer with a 15.x deployment target linked it with one `built for newer 'iOS' version` warning per object file. The floor had already been lowered in build-pjsip-xcframework.sh; the binary had simply never been rebuilt since. Rebuilt with MIN_IOS_VERSION=15.0. Every slice and every architecture now reports minos 15.0 — ios-arm64/arm64, and both arm64 and x86_64 inside ios-arm64_x86_64-simulator — with zero objects left at 16.x. The rebuild changes nothing else: exported symbols are identical (2241 before and after, none added, none removed), the headers are untouched, the XCFramework's Info.plist is byte-identical, and the file inventory is the same 336 files per slice. A link probe against the device slice went from 240 warnings to none, and the package's own test suite still passes 42/42. pod lib lint now passes without --allow-warnings, so the comments that justified that flag by the version mismatch are corrected rather than removed: the flag stays as a safety net, and the README section that told readers to rebuild the binary to silence the warnings is replaced with a per-slice verification snippet.
A VoIP push routinely beats the INVITE by a second or more — the logs show "INVITE observed after 1840 ms" — and the two paths out of that window were asymmetric. Answering polls for the call until answerTimeout; rejecting did not. With no call id bound yet, -terminateCallWithUUID:declining:completion: sent nothing to SIP, deleted the pending record and returned CallWaveErrorNoActiveCall. Nothing then told the peer anything. The INVITE arrived to a registry with no pending call, on_incoming_call answered 180 Ringing before consulting any state, and handleIncomingSIPCall: took the host-owned branch and registered a second call under a fresh UUID. The intercom went on ringing until incomingCallTimeout, and the host saw .incoming again after .ended. The rejection is now remembered rather than discarded. A pending call that the user rejects is marked cancelled and kept, the state goes to .ended and the completion reports success — the user's intent did succeed, so an error there was wrong. on_incoming_call asks for such a cancellation before it rings and answers 603 Decline instead. This side is the callee, so it answers the INVITE rather than sending CANCEL. Two rules keep a stale cancellation from doing harm: it expires after answerTimeout, the same budget an INVITE gets to arrive, and a call still legitimately awaiting its INVITE always wins over a pending cancellation, so an INVITE that may belong to a live call is never refused. Both checks run on the PJSIP callback thread under the registry's own lock, like -canAcceptAnotherIncomingCall — a 180 or a 603 cannot wait for a queue hop. Nothing is reported to CallKit from this path: in host-owned mode the provider belongs to the application, which ends the call from the state stream. CallWaveCallRegistry sits behind a private header, so its tests are a small Objective-C target that reaches it through a header search path — 14 cases covering that a cancellation keeps the record, that a cancelled call is no longer offered as awaiting an INVITE, that an expired cancellation is equivalent to no record and is purged, and that a live pending call takes precedence. No public API was widened for testing, and the public headers are untouched. The end-to-end behaviour needs a real intercom and is not covered here.
The unit tests cover parsing and property contracts; they cover none of the answer path, the push path, the audio session or the registration lifecycle, which is where the decline-before-INVITE bug lived and why nothing caught it. FIELD-TESTING.md is the twelve-scenario pass that has to be run by hand on a device against a real intercom, and RELEASING.md now names it as step 0 — before the version bump, not after the tag. Three claims in the checklist did not match the code and are corrected here: the log categories are lower-case and are sip/call/audio/push/network/pjsip with no "Engine"; the decline-before-INVITE case failed in 0.3.0 and earlier rather than in 0.3.1, which is the release that fixes it; and scenario 4 now names the log line that proves the fix ran.
Every log line, default and behavioural claim in the checklist was verified against CallWaveKit/*.m and CallWaveKit/include/. The quoted message formats, the defaults (acceptDelay 0.5 s, incomingCallTimeout 60 s, answerTimeout 10 s, maximumCalls 1, redaction on), the 486/480 responses, the RFC 2833 to SIP INFO fallback, the RTP-level mute, pjsua_handle_ip_change() and TLS verification all matched and are left alone. So do the Swift spellings, including CallWaveLog.redactsIdentifiers, which reads as isRedactingIdentifiers but is still assignable under that name — confirmed by compiling it. What did not match: - the scenario markers used [Call], [Audio], [SIP] and [Push] while the categories the library registers are lower-case, contradicting the list in the same file. All markers are now lower-case, and the setup section says that the bracket stands for the category column rather than for something the library prints; - scenario 10 sent the reader into the PJSIP trace at .debug to confirm a network handover, when there is an Info-level line for exactly that; - scenario 3 described the DTMF fallback without naming the line that shows it. Two shipped bugs had no scenario. Un-REGISTER reporting the account as still registered — the 0.3.0 fix around PJSIP_EXPIRES_NOT_SPECIFIED — is now scenario 8, and ending one of two concurrent calls killing the wrong one is folded into scenario 9 rather than given a number of its own. Thirteen scenarios instead of twelve, still an evening: scenario 8 is a call to unregister() and a state read, and the addition to 9 reuses the two callers that scenario already needs. The remaining Fixed entries are thread races and a deadlock, which have no honest by-hand reproduction, so no scenarios were invented for them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.